Skip to content

fix(#376): four grouping rules were dead on real issues - #377

Merged
randomm merged 1 commit into
mainfrom
fix/issue-376-grouping-rules
Aug 7, 2026
Merged

fix(#376): four grouping rules were dead on real issues#377
randomm merged 1 commit into
mainfrom
fix/issue-376-grouping-rules

Conversation

@randomm

@randomm randomm commented Aug 7, 2026

Copy link
Copy Markdown
Owner

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:

Rule Before After
R3 split markers fired on 0 of 7 fires on 3
R4 subsystem tags structurally impossible for /plan issues reads the conventional-commit scope
R2 path overlap 0 paths from 5 of 7 fires on bare module.ts:NNN
R1 link markers worked unchanged

All four were "passing" — against synthetic fixtures written to match their own regexes.

Root cause for two of them

commands.ts fetched with gh issue view --json and handed the raw stdout to groupIssues. That 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 right with a plain gh issue view. The body is now parsed out before grouping sees it.

The other three were semantic

  • R3's literal was a sentence nobody writes. It wanted This work must ship separately; the standing sentence in this repo is This 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.
  • R4 required [tag] at the head of a title, which a /plan issue can never have (feat: /fix: prefixes are mandated). The subsystem was already there as the conventional-commit scope, so fix(work-driver): is 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, 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 their notes line unconditionally. Running the real backlog reported R4 subsystem: #287 ↔ #368 for 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.ts uses 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. Its bodyFor() helper rebuilds what commands.ts now 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: true still 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 — /work comprehending 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.

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
@randomm
randomm merged commit fc46581 into main Aug 7, 2026
1 check passed
@randomm
randomm deleted the fix/issue-376-grouping-rules branch August 7, 2026 06:29
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.

fix(work-driver): four grouping rules are dead on real issues — --json single-line bodies, wrong R3 literal, R4 vs title prefixes, R2 path shape

1 participant