/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
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 upstreamhandoff_context. The PR diff has not been reviewed in this session, so/affinageruns/ageover it and folds the findings into the same report (unless--no-age). - Chained — reached from
/cookor/curewith ahandoff_context./agealready ran in that chain, so/affinageskips 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
Section titled “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>(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--autoskips it.--open-pr— let terminal/plateopen 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>overridesmedium+.--hard— propagated metacognitive-gate flag; forwarded to terminal/plate, not fired here.--full— un-collapses## Lowwhen ≥10 low-severity findings exist (mirrors/age --full).--include-outdated— include outdated review threads (default: skip).--no-age— skip the standalone fresh/agepass; 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.
- Resolve PR. From
<pr-ref>orgh pr view --json number; resolve<owner>/<repo>from the git remote. - Fetch PR status.
affinage.pyz pr-status <pr>. Exit 3 haltsstatus: halt: pr-status-logs-expired; any other non-zero haltsstatus: halt: pr-status-unavailable. Conflicting/dirty merge state routes to## Merge-conflict resolutionbefore grading. Exit-code detail:references/flow-details.md. - Fresh-window review. Standalone and
--no-agenot passed: score the PR diff, route it throughage_route.route(...)sized with affinage’s comment count and CI failure class, run/agewith the returnedn/lenses/effort, and fold each finding tagged[from-age:<dimension>]. See## Fresh-window review. - Fetch comments. Inline threads:
gh api repos/<owner>/<repo>/pulls/<pr>/comments(REST; no thread-resolution state, so skipposition: nullcomments unless--include-outdated). Review bodies:gh api repos/<owner>/<repo>/pulls/<pr>/reviews, filtered to non-empty bodies, deduped against inline comments viapull_request_review_id. - 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>. - Grade through the age lens. Classify each input (comment, CI failure, or fresh
/agefinding) 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_REQUESTEDis 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. - Write report to
.cheese/affinage/pr-<n>.md: four-line handoff slug, then the age-format body plus two extra sections. See## Output. - Act or ask — per §Handoff.
- 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 perreferences/flow-details.md; post approved ones viaaffinage.pyz post-reply. CI-sourced (from-check:<job>) and fresh-review (from-age:<dimension>) findings get no reply. - Post-cure reply posting (only when
/cureran). Once/curereturns, read.cheese/cure/pr-<n>.md’s### Applied/### Deferredand post per-finding replies viaaffinage.pyz post-reply: Applied (from-comment:<id>) →"Fixed — <applied summary>."; Deferred (from-comment:<id>) →"Attempted fix reverted — <reason>." - 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/curesuppressed. Skip plate and write-back when no fix was applied.
Fresh-window review
Section titled “Fresh-window review”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.
Merge-conflict resolution
Section titled “Merge-conflict resolution”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.
Sub-agent context gate
Section titled “Sub-agent context gate”/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.
Preferred tools and fallbacks
Section titled “Preferred tools and fallbacks”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 |
Output
Section titled “Output”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 | doneartifact: <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.
Handoff
Section titled “Handoff”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/curewith the lockedhandoff_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.mdinstead, pre-selecting the composite and flagging heavy rows.--autoskips both gates (## Auto mode). - No severity-section findings, but
Reviewer-rejected/Needs-investigationitems exist — nothing for/cureto act on; render the reply-approval gate and post nothing until chosen. Only--autoskips 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.
Auto mode
Section titled “Auto mode”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.
–hard mode
Section titled “–hard mode”/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
/cureasLow; reserve## Reviewer-rejectedfor 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 viaskills/affinage/scripts/affinage.pyz post-reply, where<handle>is resolved fromRESPOND_GH_HANDLE→gh api user --jq .login→git config user.name. Never callgh apidirectly to post. - Idempotent re-runs rely on the latest-comment-from-self heuristic (Flow step 5) — the REST
/commentsendpoint exposes no thread resolution state; use GraphQLreviewThreadsif cross-session resolution state is ever needed. - Apply the shared voice kernel (
../age/references/voice.md): name confidence ascertain | speculating | don't know; agree when no findings warrant grading.
References
Section titled “References”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).
Agent resolution
Section titled “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 carries the shared agent_resolution block.
Auto mode — full mechanics
Section titled “Auto mode — full mechanics”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
/meltfirst (seemerge-conflict.md). If/meltcannot resolve, halt withstatus: halt: merge-conflicts-need-humanbefore any/curedispatch. - If standalone (and
--no-agenot passed), run the fresh/agepass 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 ismedium+(same floor semantics as/cure). - Dispatch
/cure --auto --stake <floor>. - After
/cure --autoand its downstream/age --scope --autochain 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),
/affinagedispatches terminal/plate --open-pr [--hard]to publish cure’s fixes — the final writes precede publication./curesuppresses its own terminal/platefor the/affinagechain (keyed onsource_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.
Step 2 — Fetch PR status
Section titled “Step 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.
- 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. Writestatus: halt: pr-status-logs-expiredand 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’surl, or read it fromgh pr checks) and re-invoke/affinage.” - Any other non-zero (1 PR/gh API error, 2 missing gh binary) — write
status: halt: pr-status-unavailableand stop.
Step 3 — Fresh-window review
Section titled “Step 3 — Fresh-window review”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.
Step 6 — Grading rationale
Section titled “Step 6 — Grading rationale”- 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: failingchecks fromaffinage.pyz pr-statusand route them to/cureexactly like test failures. Tag CI-sourced items[from-check:<job>]. - Fresh
/agefindings (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_REQUESTEDas 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 todeslop(e.g.[deslop:low]). The rule is to route these grounded, contained-fix nits to/cure(usually asLow) 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-investigationwhen the claim is plausible but requires evidence outside the diff (e.g., downstream caller in another repo).## Reviewer-rejectedonly 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/sprawlingorfix-cost-later: structural— a refactor or scope expansion beyond this PR). Reject the wrong ones; defer the expensive ones.
- Standard severity sections (
Step 9 — Reply drafting rules
Section titled “Step 9 — Reply drafting rules”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).
Handoff gate templates
Section titled “Handoff gate templates”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.
Cure-selection gate
Section titled “Cure-selection 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, thenall,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//cureverbs (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 +
/meltpermerge-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.
Reply-approval gate
Section titled “Reply-approval gate”The single gate both Handoff branches use before any post-reply call:
- Post pushbacks only (recommended) — post
Reviewer-rejecteddrafts; holdNeeds-investigationitems for investigation. - Investigate now, then post — for each
Needs-investigationitem, run the follow-up investigation (/pasteurizefor a regression test,/briesearchto 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-investigationfollow-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.
Cure dispatch context
Section titled “Cure dispatch context”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.
Merge-conflict resolution
Section titled “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).
- Materialise the conflicts locally:
gh pr checkout <pr>, thengit merge origin/<base>. (gh pr checkoutneither opens nor updates the PR, so it does not breach the no-/ghrule.) - 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. - After
/meltresolves cleanly, the resolution commit is owned by/melt//cure./plateowns the verified commit and existing-PR update transaction.
- Default and
--automode: run the checkout +/meltautomatically before dispatching/cure, then re-runaffinage.pyz pr-statusto confirmmergeablecleared. If/meltcannot resolve (manual kdiff3 needed, or squash residue), writestatus: halt: merge-conflicts-need-humanand stop. --safemode: gate the checkout +/meltbehind 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 stepAuto-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.