Skip to content

Triageable Review - #284

Merged
FrankRay78 merged 4 commits into
mainfrom
feature/281-triageable-review
Sep 12, 2026
Merged

Triageable Review#284
FrankRay78 merged 4 commits into
mainfrom
feature/281-triageable-review

Conversation

@FrankRay78

Copy link
Copy Markdown
Owner

Why

The review comment offered two options: read all of it, or read none of it. Every gap arrived at identical visual weight — same bold numbered heading, same framing, same recommendation, same answer slot — so nothing distinguished a gap that would change an acceptance criterion from one that settled a file name. Finding out which was which meant reading both. Skipping is the rational response to that when the review is long.

Four properties of the prompt produced it: no summary, no length bound, no ordering by consequence, and a mandated sweep of fourteen categories where breadth was instructed but proportion to the issue in hand was not.

What changes

  • An at-a-glance table above the first group gives every gap's number, title and consequence, so the review can be triaged before it is read.
  • Every gap names its kindChanges what gets built or Settles a detail — and says in one clause what a different answer would change.
  • Gaps are ordered by consequence within each group, then numbered. Requirements still precedes Technical.
  • A 120-word bound per gap replaces "keep each gap tight", which bounded sub-bullets and offered splitting as the remedy — raising the gap count while leaving the word count where it was.
  • The category lists become things to consider, not a sweep to complete. A category with nothing open produces nothing: no gap, no placeholder, no commentary bullet saying it is fine.

Yield is unchanged, which is the constraint the issue was most insistent about. No gap is dropped, demoted or capped on merit, and every gap keeps its recommendation and answer slot in exactly the form /speckit.confirmissue already parses.

Non-obvious things a reviewer should know

A downstream parser chose the table's shape. The obvious index mirrors the gaps — a bold-numbered list — and that is precisely what /speckit.confirmissue parses as a gap. #281 puts that command's prompt out of scope, so the constraint had to be designed around rather than fixed. A table row carries a bare number and plain text and matches nothing the parser looks for.

The failure mode that rules this out is not the obvious one. The first draft of this branch said a table row would be "folded as a phantom decision the author never made". It would not: /speckit.confirmissue hard-stops in step 2 whenever a parsed gap looks unanswered, so a row in gap shape either blocks the whole fold, or its block-end resolves to the first real gap's answer slot and corrupts the decisions that do land. Corrected here, and it is a stronger argument for the rule than the phantom was.

What is load-bearing is the grouping, not the section order. The first draft claimed "this order is load-bearing". It is not — that command routes each folded decision by the heading its gap sat under, so swapping the two sections leaves its output byte-identical. Requirements-before-Technical is retained on its own merits (requirements are probed first; #281 puts the groups' order out of scope), not because the parser needs it. This matters because labelling a false claim "load-bearing" invites a future editor to test it, find it false, and relax the genuine constraint next to it.

The bound is scoped to first composition, deliberately. Step 6 requires a re-framed hedging gap to gain 2–4 options with trade-offs plus a worked example. That cannot fit in 120 words, and an unscoped bound would have forced a refine run to silently break one of two instructions.

A pre-post self-check was added because composing to a bound is not meeting one. The first draft written under this prompt overshot on 3 of 3 gaps, unaided, and no instruction caught it. The check is self-administered rather than gated — see the CIR's residual for why a hook cannot reach this invariant: a first run pipes the comment body straight into gh issue comment, leaving no committed artefact to check.

The zero-gap case is deliberately left alone. An earlier draft added a "no gaps at all → omit the table" exception. The template has conditional-omit directives for Technical gaps and Commentary but none for Requirements gaps, and the intro sentence promises points that would not exist — so the exception had no support and half-fixing it was worse than not touching it. That path was gap 3 of #278's own review and belongs with #278's successor.

The taxonomy does not cover process artefacts. A gap asking which release a change ships under, or what its RED step is, is about neither built behaviour nor a detail within it. The "when borderline, take Settles a detail" tie-break absorbed both cases in trial without a wrong call, but the two kinds are not a complete partition. Recorded as a residual.

How to verify

Per Constitution Principle I's configuration-and-tooling carve-out there is no xUnit test here — the evidence is the command's own output before and after. The controlled half is done; two live checks are not, and should happen before merge.

A blind A/B was run to avoid grading my own work: two clean-context agents each composed a review of the same issue (#242), one given the pre-change prompt and one the new prompt, neither told a comparison was happening or what was being tested.

pre-change this branch
Gaps raised 6 5
Words per gap 171–240 (mean 205) 103–117 (mean 112)
Over the 120-word bound 6 of 6 0 of 5
Up-front table absent present, titles verbatim
Consequence lines 0 5
  • AC5 — every gap breached the bound under the old prompt and none under the new one, unaided. That rule exists only on this branch.
  • AC1, AC2 — table with number/title/consequence; every gap carries a consequence line, and rows agree with gap lines exactly.
  • AC4, AC8 — contiguous 1–5, Requirements first; 5 recommendations, 5 reasons, 5 answer slots.
  • AC9 (mechanically) — zero bold-numbered forms and zero > _Answer:_ lines above the first group, checked by script against the composed body.
  • dotnet build ./src clean (0 warnings), dotnet test ./src green — 648 passed, 0 failed, 0 skipped. No C# changed; this is the unconditional gate, not evidence for the change.
  • AC9 live — post a review on a throwaway issue, answer it, and run /speckit.confirmissue end to end. Confirm the table contributes no bullet to ## Confirmed decisions. The mechanical check above is inspection, not execution.
  • AC10, AC11 live — answer one gap with a hedge, re-run /speckit.reviewissue, and confirm the re-framed gap's consequence line and its table row both move, every other gap and row comes through byte-for-byte, and nothing is renumbered.
  • AC7 — run the new prompt against a large, genuinely open issue and confirm its review is visibly longer than Mark SpeedTestResult and LatencyTestResult properties as required #242's. Not evidenced: one A/B pair cannot separate prompt effect from agent variation.

Two caveats a reviewer should weigh rather than take on trust. AC3's ordering rule was only trivially exercised — all three Requirements gaps came out Changes what gets built and both Technical ones Settles a detail, so nothing actually needed sorting within a group. And the pre-change run produced more informative gap titles: declarative findings ("The CSV writer evaluates the placeholder before discarding it, so the obvious null conversion breaks the build") against this branch's questions ("How do the writers hold a result that may not exist?"). It also found two things the new run missed. I cannot attribute that to the prompt — that agent compiled the change to find it and the other did not — but it is the one signal pointing against this change, and it deserves a human eye.

Related

Closes #281

FrankRay78 and others added 4 commits September 12, 2026 14:49
Four changes to the pre-specification review, all in its own prompt:

- An at-a-glance table above the first group gives every gap's number,
  title and consequence, so the review can be triaged before it is read.
  Its shape is forced by /speckit.confirmissue, which folds every
  **N. <title>** block ending in `> _Answer:_` into the issue body — a
  bold-numbered index would have been folded as phantom decisions.
- Every gap names one of two kinds — changes what gets built, or settles
  a detail — and says in one clause what a different answer would change.
- Gaps are ordered by consequence within each group, then numbered.
  Requirements still precedes Technical, since that grouping is what
  /speckit.confirmissue routes folded decisions by.
- A 120-word bound per gap replaces "keep each gap tight", which bounded
  sub-bullets and offered splitting as the remedy — raising the gap count
  while leaving the word count alone.

The category lists become things to consider rather than a sweep to
complete: a category with nothing open produces no gap. Yield is
unchanged — no gap is dropped, demoted, or capped on merit, and every
gap keeps its recommendation and answer slot in the form the downstream
parser already reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six decisions a future maintainer would reasonably question, and the
constraint none of them make obvious: the up-front view is a table
because /speckit.confirmissue folds anything shaped like a numbered gap
into the issue body, so the obvious bold-numbered index would have
posted phantom decisions the author never made.

Also records why consequence is two values rather than a severity
scale, why the sweep is narrowed at source rather than capped, why
splitting is not how a gap meets the length bound, and where 120 came
from — the four gaps of the review on #278 ran 184 to 317 words.

Four residuals, including the third consumer of this comment
(/speckit.draftissue), enumerated here because reviews of this same
section missed it on both preceding issues.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One line per paragraph, per CLAUDE.md. The four paragraphs added around
the comment template were hard-wrapped to match the older text beside
them, which reflows a whole block on a one-word edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four corrections where the new text asserted something untrue or
contradicted a rule elsewhere in the file:

- The 120-word bound was stated as an unscoped absolute, contradicting
  step 6's mandate that a re-framed gap gain 2-4 options with trade-offs
  and a worked example. Scoped to first composition.
- A refine run was told to update the table row but never the gap's own
  consequence line, so the up-front view could drift from the gap it
  describes — the one thing AC10 exists to prevent.
- "This order is load-bearing" was false. /speckit.confirmissue routes by
  the heading a gap sat under, not by the sequence of the two sections;
  swap them and its output is byte-identical. Reattributed to grouping.
- The phantom-decision mechanism was wrong. That command hard-stops in
  step 2 when a parsed gap looks unanswered, so a table row in gap shape
  either blocks the fold or takes the first real gap's answer slot.

Also: narrowed an absolute that excluded step 1's existing-comments rule;
dropped the zero-gap table exception, which had no template support (the
empty-group path belongs to #278's successor); dropped an unsupported
claim that contiguous numbering helps the parser, which strips numbers;
named "Settles a detail" rather than "the lower of the two", since the
design denies there is a scale; softened "the sweep is manufacturing
gaps" to a likelihood, per #281's own open question; and fixed the
example table to show the consequence column restarting at the group
boundary.

Added a pre-post self-check step. Composing to a bound is not meeting
one: the first draft written under this prompt overshot on 3 of 3 gaps
unaided, which no instruction caught.

CIR: the 184/192/196/317 figure was wrong — the counting script ran past
the answer slot to end-of-file, adding 94 words of trailing heading and
commentary. Two independent counts put the fourth gap at 223. Corrected,
with the counting method stated. Also split the gating residual, which
conflated the two judgement-only criteria with nine that are mechanically
decidable, and recorded why a hook cannot reach them: a first run pipes
the body straight to gh, leaving no committed artefact to check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@FrankRay78

Copy link
Copy Markdown
Owner Author

@claude Review this pull request. Analyse the code changes and provide feedback covering:

  • Bugs or correctness issues
  • Security concerns (including any flagged by static analysis)
  • Adherence to the project conventions in CLAUDE.md
  • Test coverage — do the tests adequately cover the new behaviour?
  • Any spec/test-plan mismatches

End the review with a recommendation whether to merge the PR, and if not, what you suggest needs addressing first.

@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @FrankRay78's task in 3m 2s —— View job


Review complete

  • Gather context (diff, changed files, CLAUDE.md conventions)
  • Review .claude/commands/speckit.reviewissue.md changes for correctness/logic issues
  • Review the CIR doc for consistency with the actual diff
  • Check adherence to CLAUDE.md / constitution conventions
  • Assess test coverage claims (prompt-only change, TDD carve-out)
  • Check for spec/test-plan mismatches
  • Post final review with merge recommendation

Summary

This is a prompt-only change to .claude/commands/speckit.reviewissue.md (adds an at-a-glance triage table, per-gap consequence labelling, ordering-by-consequence, a 120-word bound, and narrows the category "sweep" to an open-ness test), plus a CIR documenting the six decisions and five residuals. No C# was touched.

I checked the diff (git diff origin/main...HEAD) against the live behaviour of the downstream consumer, .claude/commands/speckit.confirmissue.md, and against the CIR's own claims.

Correctness

  • The parser-collision claim (a bare table row imitating **N. <title>** would either hard-stop the fold or steal the first real gap's answer slot) checks out against speckit.confirmissue.md step 2 (If any answer is empty, stop and report...) and step 3 (which parses **N. <title>** blocks ending at > _Answer:_). Good catch, and the fix genuinely avoids the collision by keeping the table's shape (| # | Gap | ... |) disjoint from that pattern.
  • The "grouping, not sequence, is load-bearing" correction also checks out: speckit.confirmissue.md lines 153–155 route bullets by the ### Requirements gaps / ### Technical gaps heading, not by list position, so swapping section order would indeed leave that command's output byte-identical. Good that the CIR and prompt were corrected from the (false) original "order is load-bearing" claim rather than left as an overclaim.
  • The 120-word-bound scoping (first composition only, not refine-run expansion) is internally consistent with step 6's separate mandate that a re-framed gap gain 2–4 options — these would otherwise directly contradict each other, and the fix commit (3bfda8c) correctly resolved that before I got to it.
  • Referenced artefacts all exist: docs/change-intent-records/2026-09-07-automated-prespec-review.md, .claude/hooks/traceability-gate.sh, docs/study/279.md, docs/study/280.md.

One small robustness gap I didn't see addressed: the table is Markdown, and cells are delimited by |. Neither the template nor the surrounding rules say what to do if a gap's short title or compressed consequence text itself contains a literal | (e.g. a title referencing a bitwise-or or a pipe-delimited format). Low-probability given titles are short phrases, but worth a one-line escaping note (\|) if it's ever hit — not a blocker.

CLAUDE.md / constitution adherence

  • Principle I's configuration/tooling carve-out is correctly invoked and correctly applied: no bespoke test stands in for /speckit.reviewissue's own output, and the PR body records the real RED/GREEN evidence (an A/B comparison of composed reviews, pre- vs post-change).
  • No public NetPace.Core API touched, no CLI flag touched, no README/USER_GUIDE update needed — correctly out of scope for a .claude/commands prompt change.
  • CIR follows the repo's template (docs/conventions/change-intent-records.md), including the "Known residuals" heading precedent from the 2026-09-07 CIR (the fix commit correctly renamed "Residual risks" → "Known residuals" for consistency).
  • Markdown is soft-wrapped (one line per paragraph), matching this repo's established convention.

Test coverage — the main thing worth flagging

For a prompt file there's no xUnit suite; the real "tests" here are the manual verification checklist in the PR body. That checklist is honest about its own gaps, and three items are still unchecked:

  • AC9 live — post a review, answer it, run /speckit.confirmissue end-to-end, confirm the table contributes nothing to ## Confirmed decisions. Currently only mechanically inspected (script-checked shape), not executed.
  • AC10/AC11 live — re-run /speckit.reviewissue after a hedge answer, confirm the consequence line and table row both update in step, and nothing else shifts.
  • AC7 — confirm review length scales up on a genuinely large, open issue (the one A/B pair used Mark SpeedTestResult and LatencyTestResult properties as required #242 and can't separate prompt effect from agent variance).

Given the parser-collision risk is the single most consequential thing this PR is trying to avoid, I'd want AC9 exercised for real (not just shape-inspected) before merging — that's the one scenario where a mistake silently corrupts confirmed decisions on a real issue rather than just producing a worse review.

Spec/test-plan mismatches

None found — there is no specs/281-* folder or spec.md/test-plan.md for this issue (consistent with this being a direct meta-change to the SDD tooling itself rather than a change run through the SDD pipeline), so Principle VIII/IX traceability rules don't apply here. The PR body's own AC list is self-consistent with the diff.

Security

No concerns. No new shell/eval patterns, no new secrets handling; existing gh api/heredoc escaping rules are preserved unchanged.

Recommendation

Hold merge until the AC9 live check is run. Everything else — the corrected parser-routing claims, the word-bound/refine-run interaction, the CIR's honesty about residuals (including the self-administered vs. gated enforcement gap, which is well-argued and appropriately scoped out of this PR) — is sound and I have no correctness objections. But this PR's entire value proposition rests on the table never being mis-parsed by /speckit.confirmissue, and that's currently verified by static inspection of the composed body, not by an actual fold. Given the PR author already flagged this gap explicitly as outstanding, running it before merge (or explicitly accepting the residual risk in the PR if time-boxed) is the only thing I'd add.

Branch: feature/281-triageable-reviewmain

@FrankRay78
FrankRay78 merged commit c125fa3 into main Sep 12, 2026
3 checks passed
@FrankRay78
FrankRay78 deleted the feature/281-triageable-review branch September 12, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make a /speckit.reviewissue comment triageable, and stop the category sweep inflating it

1 participant