Skip to content

/affinage

When to invoke: 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 ”, “fix the failing build”, “resolve the conflicts and respond”. Do NOT use for a bare diff with no PR (route to /age).


Act on external claims about a PR — review comments from humans or bots, plus failing CI checks and merge conflicts — grading them through the same lens /age uses for fresh review, then handing them 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.

/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> (blocker, high, medium+, all) matches /cure’s semantics. Skips selection, dispatches /cure --auto --stake <floor>, posts replies without prompting. Mechanics: references/auto-mode.md.
  • --safe — also gates cure-selection and merge-conflict resolution (autonomous by default). Reply posting is gated by default regardless — only --auto skips it.
  • --open-pr — let terminal /plate open a new PR when none exists (else it only updates the open one).
  • --plate — one-shot publish combo = --auto --stake medium+ --open-pr: triage, cure the recommended floor, post every reply, then plate. An explicit --stake <floor> overrides medium+.
  • --hard — propagated metacognitive-gate flag; forwarded to terminal /plate, not fired here.
  • --full — un-collapses ## Low when ≥10 low-severity findings exist (mirrors /age --full).
  • --include-outdated — include outdated review threads (default: skip).
  • --no-age — skip the standalone fresh /age pass; no effect when chained.

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.

Exact CLI invocations, exit-code hints, and grading rationale for steps 2, 3, 6, and 9 below: references/flow-details.md.

  1. Resolve PR. From <pr-ref> or gh pr view --json number; resolve <owner>/<repo> from the git remote.
  2. Fetch PR status. affinage.pyz pr-status <pr>. Exit 3 halts status: halt: pr-status-logs-expired; any other non-zero halts status: halt: pr-status-unavailable. Conflicting/dirty merge state routes to ## Merge-conflict resolution before grading. Exit-code detail: references/flow-details.md.
  3. Fresh-window review. Standalone and --no-age not passed: score the PR diff, route it through age_route.route(...) sized with affinage’s comment count and CI failure class, run /age with the returned n/lenses/effort, and fold each finding tagged [from-age:<dimension>]. See ## Fresh-window review.
  4. Fetch comments. Inline threads: gh api repos/<owner>/<repo>/pulls/<pr>/comments (REST; no thread-resolution state, so skip position: null comments unless --include-outdated). Review bodies: gh api repos/<owner>/<repo>/pulls/<pr>/reviews, filtered to non-empty bodies, deduped against inline comments via pull_request_review_id.
  5. Skip already-replied threads. A thread last-commented by the resolved GitHub handle (§Rules) is already answered — skip it; the footer renders as agent on behalf of <handle>.
  6. Grade through the age lens. Classify each input (comment, CI failure, or fresh /age finding) by dimension — code/claim, or check type/failure for CI — per ../age/references/dimensions.md, and by severity (base + location + compounding, same rubric as /age); ignore reviewer-asserted urgency (CHANGES_REQUESTED is metadata, never a severity bump). Bucket into severity sections (contained fixes), ## Needs-investigation (needs out-of-diff evidence), or ## Reviewer-rejected (wrong/ungrounded, or a lot of follow-up work). Full bucketing criteria: references/flow-details.md.
  7. Write report to .cheese/affinage/pr-<n>.md: four-line handoff slug, then the age-format body plus two extra sections. See ## Output.
  8. Act or ask — per §Handoff.
  9. Draft non-cure replies, then gate before posting (whenever grading produced these items, with or without /cure). Never post blind — requires the reply-approval gate (§Handoff), or --auto. Draft per references/flow-details.md; post approved ones via affinage.pyz post-reply. CI-sourced (from-check:<job>) and fresh-review (from-age:<dimension>) findings get no reply.
  10. Post-cure reply posting (only when /cure ran). Once /cure returns, read .cheese/cure/pr-<n>.md’s ### Applied/### Deferred and post per-finding replies via affinage.pyz post-reply: Applied (from-comment:<id>) → "Fixed — <applied summary>."; Deferred (from-comment:<id>) → "Attempted fix reverted — <reason>."
  11. Plate — once every approved reply is posted (steps 9–10) and the cure applied ≥1 fix, dispatch terminal /plate [--open-pr] [--hard] [--safe]; publication lands after every reply. After it lands, run the § Post-PR learnings write-back (../cure/SKILL.md § Handoff) — affinage owns the write-back the chained /cure suppressed. Skip plate and write-back when no fix was applied.

Standalone runs (see intro) 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 report’s severity sections tagged [from-age:<dimension>] — they flow to /cure like any other finding but get no GitHub reply (no reviewer to notify, same as [from-check:…] items).

Run the fresh pass before grading external claims so an echoing comment can be deduped, under the same sub-agent gate as grading (## Sub-agent context gate) to keep the parent context lean.

When pr-status reports unresolved conflicts, /affinage routes to /melt (mergiraf → rerere → kdiff3) rather than resolving by hand. Default/--auto run checkout + /melt automatically before /cure; --safe gates it behind the handoff prompt. If /melt cannot resolve, write status: halt: merge-conflicts-need-human and stop. Full steps: references/merge-conflict.md.

/affinage keeps dialogue, selection, approval state, and reply posting in the parent context. When the parent context would balloon — inputs exceed 10, diff exceeds ~25 KB, or threads span more than 5 files — resolve a fresh read-only reviewer through the shared agent resolver (a general worker qualifies only with degraded: true). The sub-agent returns a digest of graded findings (dimension, severity, confidence, evidence cite, pre-drafted push-back for Reviewer-rejected items); the parent owns the report write, selection gate, /cure dispatch, and reply posting. Digest size and selection detail: ../age/references/sub-agent-gate.md.

Call source-code search/read backends per code-intelligence-routing.md. Affinage-specific tools:

Need Prefer Fallback
PR status (build + merge) skills/affinage/scripts/affinage.pyz pr-status manual gh pr checks + gh pr view
GitHub fetch gh api none (skill halts)
Reply posting skills/affinage/scripts/affinage.pyz post-reply none — direct gh api calls bypass the agent on behalf of <handle> attribution
Diff inspection delta git diff --unified=3

Write to .cheese/affinage/pr-<n>.md: the four-line handoff slug, then the age-style body plus two extra sections (## PR status and the same severity / ## Needs-investigation / ## Reviewer-rejected shape /age uses). Full annotated template: references/report-template.md.

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>

Empty severity sections are omitted; so are ## Needs-investigation/## Reviewer-rejected when empty. status: ok when grading completed; halt: <reason> when gh/pr-status failed. next: per ## Handoff § Slug next: values.

Pipeline: culture → mold → cook → press → age → cure → plate · /affinage is parallel to /age and feeds /cure.

Default: affinage acts without asking, and asks only for a genuine reason (a sprawling/structural fix in the recommended set, conflicting findings) or under --safe (Flow step 8).

  • Severity-section findings exist (any severity, including Low) — compute the recommended composite (all-medium, cheap). No reason to ask and no --safe: announce the selection, dispatch /cure with the locked handoff_context (shape: references/handoff-templates.md § Cure dispatch context), then render the reply-approval gate before posting (Flow steps 9–10) — never post blind. A reason to ask, or --safe: render the cure-selection gate per ../cheese/references/handoff-gate.md instead, pre-selecting the composite and flagging heavy rows. --auto skips both gates (## Auto mode).
  • No severity-section findings, but Reviewer-rejected/Needs-investigation items exist — nothing for /cure to act on; render the reply-approval gate and post nothing until chosen. Only --auto skips it.

After the selection, post approved replies (Flow step 9–10), then — only when the cure applied ≥1 fix — dispatch terminal /plate [--open-pr] [--hard] [--safe] (Flow step 11); publication lands after every reply. Exit status: ok / next: done when there is nothing to act on.

Slug next: values. cure when ≥1 finding meets the medium+ floor; done when no severity-section finding exists or all meeting items resolve to an empty selection.

Skips the selection gate. Resolves merge conflicts via /melt first (halt status: halt: merge-conflicts-need-human if unresolved). If standalone, runs the fresh /age pass. Auto-selects every finding meeting <floor> (--plate enters this mode at --stake medium+ --open-pr) and dispatches /cure --auto --stake <floor>; once its chain settles, posts replies for the originally graded items only, then dispatches terminal /plate --open-pr [--hard] once every reply posts (skipped if no fix applied). If no findings meet the floor: skip /cure, post rejection/investigation replies only, exit status: ok / next: done. Full mechanics: references/auto-mode.md.

/affinage passes --hard to its terminal /plate, which fires /hard-cheese after verifying the final artifact state. /cure never dispatches plate in this chain, so the gate fires once — at affinage’s publication boundary.

  • Grading is code-grounded, not reviewer-asserted — see Flow step 6.
  • Prefer fixing over pushing back. A grounded nit with a contained fix goes to /cure as Low; reserve ## Reviewer-rejected for claims that are wrong, ungrounded, or a lot of work (Flow step 6, ../age/references/voice.md).
  • Never auto-apply fixes itself — code fixes go through /cure, merge conflicts through /melt (## Merge-conflict resolution).
  • Never post a reply without approval — see the reply-approval gate (## Handoff, references/handoff-templates.md).
  • 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_HANDLEgh api user --jq .logingit config user.name. Never call gh api directly to post.
  • Idempotent re-runs rely on the latest-comment-from-self heuristic (Flow step 5) — the REST /comments endpoint exposes no thread resolution state; use GraphQL reviewThreads if cross-session resolution state is ever needed.
  • Apply the shared voice kernel (../age/references/voice.md): name confidence as certain | speculating | don't know; agree when no findings warrant grading.

Affinage-local, each also routed inline above: references/flow-details.md, references/merge-conflict.md, references/report-template.md, references/handoff-templates.md, references/auto-mode.md. sub-agent-gate.md is ../age/references/sub-agent-gate.md (shared, not affinage-local).

Scripts: skills/affinage/scripts/affinage.pyz post-reply (reply posting), pr-status (PR status fetcher).

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 carries the shared agent_resolution block.

Read this when running (or dispatching) /affinage --auto --stake <floor> (or --plate, which enters this mode with --stake medium+ --open-pr). The body’s ## Auto mode states the decision spine; this is the full step detail.

  • Skip the selection gate.
  • If the PR has merge conflicts, resolve them via /melt first (see merge-conflict.md). 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.

Flow — full command and rationale detail

Section titled “Flow — full command and rationale detail”

Read this when executing ## Flow steps 2, 3, 6, or 9 — the exact CLI invocations, exit-code hints, and bucketing rationale the body’s numbered list summarizes.

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.

  • 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.

Compute the PR diff’s review_surface score via the review-surface CLI (source: src/fanout/review_surface_cli.py, wrapping src/fanout/review_surface.py::score()) over the diff’s git numstat rows. Run it through the .pyz bundle — the direct script imports cli/git_utils from shared/scripts/, which are only co-staged flat inside the bundle, so running it directly fails with ModuleNotFoundError: No module named 'cli': python3 skills/affinage/scripts/affinage.pyz review-surface --repo . <base>...HEAD — the range must be the PR’s full diff against its base branch (after gh pr checkout <pr>, origin/<base>...HEAD), never the CLI’s bare HEAD default, which scores only the uncommitted delta. Grep the diff’s added lines outside skills/** and .hallouminate/** for age_route.OVERRIDE_FLAGS risk flags — a missed token means no promoted lens, not a missing security lens — and call age_route.route(score=<float>, 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 '{"score": <float>, "risk_flags": [...], "entry": "affinage", "comments": <n>, "ci_class": <"failing"|"red"|"flaky"|null>}' | python3 skills/affinage/scripts/affinage.pyz age-route is the fallback for the router call (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.

  • 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 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).
  • 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 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.

Post each approved reply with python3 skills/affinage/scripts/affinage.pyz post-reply — never a direct gh api call, which would bypass the agent on behalf of <handle> attribution.

  • 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).

Read this when rendering either handoff gate SKILL.md § Handoff describes — the exact option wording for the cure-selection gate and the reply-approval gate.

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/references/handoff-detail.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.md, 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/references/handoff-detail.md § Selection 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.

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.

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.

Affinage report — full annotated template

Section titled “Affinage report — full annotated template”

Read this when writing .cheese/affinage/pr-<n>.md — the worked example for every section below the four-line handoff slug (which stays in SKILL.md § Output, since downstream skills parse it directly).

# 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.