fix(#376): four grouping rules were dead on real issues - #377
Merged
Conversation
The grouping rules were tested only against synthetic fixtures written to match their own regexes, so all four passed while four of them did nothing on the actual backlog. Verified by running the production regexes over the live bodies of #279/#287/#288/#289/#290/#366/#368: R3 fired on ZERO of seven, and path extraction returned zero for five of seven. Root cause for two of them: commands.ts fetched bodies with `gh issue view --json` and handed the RAW stdout to groupIssues. That stdout is one line of compact JSON with `\n` as two-character escapes, so R3 and R4 — both `^`-anchored under /m — could only ever see `{"body":"` as their line start. The explore step already did this correctly with a plain `gh issue view`. Now the body is parsed out before grouping sees it. The other three were semantic: - R3's literal was `This work must ship separately`. Nobody has ever written that. The standing sentence in this repo is `This work must ship as its own separate PR, independent of any other open issue`. Both forms now match; bare "independent" stays excluded, since it was removed pre-#312 as a false-positive magnet. - R4 required `[tag]` as the first characters of a title, which is structurally impossible for a /plan-authored issue — /plan mandates `feat: ` / `fix: ` prefixes. The subsystem was already present as the conventional-commit scope, so `fix(work-driver):` is now read as the tag rather than asking anyone to change how titles are written. - R2 demanded a directory component, but issues cite code as `work-driver.ts:1274`. Bare `module.ts:NNN` now counts. The trailing line anchor is required precisely so a prose mention of a filename does not: an anchored reference is deliberate, a prose mention is not. Also makes the decision log truthful. `union()` silently refuses when either side carries a split marker, but callers pushed their `notes` line unconditionally — so running the real backlog reported `R4 subsystem: #287 ↔ #368` for a union that never happened. union() now returns whether it merged, and notes are conditional on that. The notes are the only explanation of why grouping decided what it decided; ones the operator cannot trust are worse than none. The regression test uses the real issue bodies as committed fixtures. Synthetic fixtures are what let this survive; these are the exact inputs that produced the measured failures. Verified the test catches the original bug: reverting R3's literal alone produces 5 failures. 73/73 offline smoke tests, tsc + biome clean. Fixes #376
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #376. First of the loop-sweep waves.
The measurement
Running the production regexes over the live bodies of #279/#287/#288/#289/#290/#366/#368:
module.ts:NNNAll four were "passing" — against synthetic fixtures written to match their own regexes.
Root cause for two of them
commands.tsfetched withgh issue view --jsonand handed the raw stdout togroupIssues. That is one line of compact JSON with\nas two-character escapes, so R3 and R4 — both^-anchored under/m— could only ever see{"body":"as their line start. The explore step already did this right with a plaingh issue view. The body is now parsed out before grouping sees it.The other three were semantic
This work must ship separately; the standing sentence in this repo isThis work must ship as its own separate PR, independent of any other open issue. Both now match. Bare "independent" stays excluded — removed pre-fix(work-driver): R3 SPLIT detection matches the bare word 'independent' — splits issues whose bodies say they must ship together #312 as a false-positive magnet.[tag]at the head of a title, which a/planissue can never have (feat:/fix:prefixes are mandated). The subsystem was already there as the conventional-commit scope, sofix(work-driver):is read as the tag — rather than asking anyone to change how titles are written.work-driver.ts:1274. Baremodule.ts:NNNnow counts, and the trailing line anchor is required so a prose mention of a filename does not: an anchored reference is deliberate, a prose mention is not. Tested both ways.Also: the decision log was lying
union()silently refuses when either side carries a split marker, but callers pushed theirnotesline unconditionally. Running the real backlog reportedR4 subsystem: #287 ↔ #368for a union that never happened, because #287 is split-marked.union()now returns whether it merged and notes are conditional on that.The notes are the only explanation of why grouping decided what it decided. Ones the operator cannot trust are worse than none.
Verification
Full §1 gate: build,
tsc --noEmit,bun run check, 73/73 offline smoke tests.test-grouping-real-issues.tsuses the real issue bodies as committed fixtures (44 KB). Synthetic fixtures are precisely what let this survive undetected; these are the exact inputs that produced the measured failures. ItsbodyFor()helper rebuilds whatcommands.tsnow hands the grouper, with a comment noting that if the two diverge the file stops testing production.Every rule has a no-regression assertion alongside the fix (
Split: truestill works,[tag]still works, directory paths still work), plus precision assertions (unanchored prose is not a path; bare "independent" is not a split).I confirmed the test catches the original bug: reverting R3's literal alone produces 5 failures; restoring it returns to exit 0.
Scope
Fast-path repair only. The larger Wave 1 item —
/workcomprehending intent from any spec, including hand-written and terse ones, so grouping markers become an optimisation rather than a contract — is a separate issue and PR. No change to/plan.