Skip to content

fix(voice): count a run-together technical match as code evidence for the sentence - #627

Merged
mrgoonie merged 4 commits into
mainfrom
fix/621-run-together-code-evidence
Oct 8, 2026
Merged

mrgoonie merged 4 commits into
mainfrom
fix/621-run-together-code-evidence

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #621. Refs #608, #613, #590, #607.

Problem

#613 lets words that run together into a longer term win over a shorter exact form at the same position ("web app" reads as webapp, not web + "app"). The shorter form was the sentence's only evidence that it was about code, so in an English sentence other corrections were dropped:

Change

packages/voice-adapters/src/transcript-normalizer.ts

  • matchAt keeps the exact form a run-together match passed over (passedOver), and takes an end bound.
  • The hit loop moves into hitsBetween(from, end), so the words inside a run-together span can be matched on their own.
  • When a hit is not evidence itself and a shorter exact form starts its span, that passed-over form and the exact matches after it inside the span are checked with the same evidence test. A span that no shorter form starts adds nothing. These absorbed anchors sit inside the run-together span, so they never support that span itself: "web app" still stays as heard in an English sentence with nothing else in it.
  • Absorbed anchors are kept apart from cues and main's evidence. They support only a span that absorbed no words itself and holds no cue or evidence term of its own. So two run-together spans cannot vouch for each other, and neither can a run-together span and a span holding evidence, such as "Follow-up" for the tool follow-up or the cue in "code review" for codeReview.
  • Comment wording on the compact rule generalised (review note N1 on fix(voice): prefer a run-together compact over a shorter two-word match #613).

"user service api" still abstains with userservice-api|userserviceApi, as the issue allows: the words really can be read as either.

Tests

New named suite "still counts the words a run-together term absorbed as evidence for the rest of the sentence", with the three inputs from the issue. On main (f40c8cb) the ClarkCant and React tests fail (text left as heard) and the abstention test passes, as expected. All three pass on this branch. The #590, #607 and #608 suites are unchanged and green (173/173 in voice-adapters at the current head).

Differential

Harness: one script importing the #613 parent (ac8e843e), main (f40c8cb7) and this branch side by side. Six sessions: the #608 issue vocabulary, clarkcant-web only, the bench corpus context, a large workspace session, the case-ambiguous session, and a short-tool-plus-run-together session. Inputs: every 1 to 3 word sequence over a 47-word pool in four frames (Vietnamese, English, bare, mở file … đi), the issue's mixes, and every corpus reference and recognizer output.

  • 2,547,120 normalisations.
  • main vs branch: 54 differ. All 54 are corrections the fix(voice): prefer a run-together compact over a shorter two-word match #613 parent made and main lost, now restored with the parent's exact change (ClarkCant / clarkcant beside web app, next js, react native, market place, git hub, play wright; React beside web app).
  • Still lost relative to the parent: 8, all the intended user service api abstention.
  • Branch changes that neither main nor the parent made: this claim was wrong at 86907b0c (see review B1). After review, see "Changes after review" for the re-run.

Bench

pnpm --filter @clarkcant/voice-adapters bench:transcription output is byte-identical on main and on this branch (normalized: WER 3.7%, CER 0.8%, TTER 16.2%, exact 77.4% / 84.5%, 64 changes, 1 abstained, 0 regressions). ADR-003 tables (EN and VI) already show these numbers, so they are unchanged.

Verification

  • focused vitest: voice-adapters 173 passed (current head)
  • pnpm typecheck: clean
  • pnpm invariants: 14/14 passed
  • eslint on both changed files: clean
  • pnpm verify: passed on the current head a08285bf (7681 passed, 37 skipped, 1 todo)

No user-visible docs change: the matcher's evidence rule is internal and no doc describes it.

Changes after review

Review B1: a word absorbed by one run-together span counted as evidence for another run-together span in the same sentence, even when that span was rewritten too. Two guesses vouched for each other, for example with tool web and package webapp, "the web app and the web app" became "the webapp and the webapp". main and the #613 parent leave it as heard.

  • Fix (transcript-normalizer.ts): absorbed anchors go to their own list. A span that itself passed over a shorter form (a run-together span that absorbed words) is supported only by technical cues and by terms heard in their own spelling, as on main. Every other span may also use absorbed anchors. This restores main's invariant: a span rewritten only because the sentence looked like code never counts as evidence.
  • Still holds: "fix the clark cant web app now" gives ClarkCant web app, "fix the web app react now" restores React, and "user service api" still abstains.
  • New named tests: "the web app and the web app" (web + webapp), "set up the web app for grandma" and "the front end of the web app" all stay as heard. All three fail on 86907b0c and pass now.
  • Reviewer's differential harness, re-run on main vs this head, with the fix(voice): prefer a run-together compact over a shorter two-word match #613 parent ac8e843e added and a minimal web/front/set + webapp/frontend/setup session (glossary off) added: 2,743,644 normalisations, 848 differ in 17 groups. All of them are casing restorations beside "web app" (React, Electron), ClarkCant and GitHub, the same as the parent. No spacing rewrite of a run-together span remains. Cases where this head rewrites text that both main and the parent leave as heard: 0. That count missed outputs where each baseline makes one of two changes and the head makes both (review B2, fixed in round 2 below). Self-support isolation violations: 0. The minimal session and the glossary-only session have 0 differences. The only output matching neither main nor the parent is "the user service api react": head gives React and abstains on "user service api", while the parent also wrote API. That is the intended abstention.
  • bench:transcription: byte-identical to main (normalized WER 3.7%, CER 0.8%, TTER 16.2%, 64 changes, 1 abstained, 0 regressions).
  • voice-adapters 169/169, including the Voice normalizer: do not re-case a term whose spelling is ambiguous by case #590, fix(voice): leave a term the vocabulary spells two ways by case as heard #607 and fix(voice): prefer a run-together compact over a shorter two-word match ('clark cant web') #608 suites. pnpm typecheck is clean, eslint is clean on both changed files, and pnpm invariants passes 14/14.

Round 2

Review B2: a term that counts as evidence while it is itself rewritten could support a run-together span, and then be supported in return by the word that span absorbed. With tools follow-up and web and package webapp, "Follow-up on the web app" became "follow-up on the webapp". With tool s3, "S3 is on the web app" became "s3 is on the webapp". main keeps the capital, and the parent keeps "web app".

  • Fix (transcript-normalizer.ts): absorbed words no longer support a hit that is itself evidence. Such a term already supports every run-together span outside it, so the word that span absorbed is always rewritten away and cannot be support. Round 3 widens this from evidence terms to any hit holding a cue word. "Follow-up on the web app" now gives "Follow-up on the webapp", the same as main.
  • New tests: "Follow-up on the web app" (follow-up, web, webapp) and "S3 is on the web app" (s3, web, webapp) keep the capital and rewrite only webapp, as main does. Both fail on 66212bc5 and pass now.
  • The reviewer's round-2 harness (7 sessions, including the follow-up/check-in/s3 session, and 3-span chains), run on main, the parent ac8e843e and this head: 1,471,155 normalisations. Head outputs that match neither main nor the parent: 0 (2,097 at 66212bc5). Self-support violations: 0. The reviewer's probes still give ClarkCant web app and web app React, the user service api abstention holds, and every B1 chain stays as heard.
  • bench:transcription: byte-identical to main. voice-adapters 171/171, including the Voice normalizer: do not re-case a term whose spelling is ambiguous by case #590, fix(voice): leave a term the vocabulary spells two ways by case as heard #607 and fix(voice): prefer a run-together compact over a shorter two-word match ('clark cant web') #608 suites. pnpm typecheck is clean, eslint is clean on both changed files, and pnpm invariants passes 14/14.

Round 3

Review N1: a coding cue inside a rewritten span and a run-together span could still support each other. With tool web, package webapp and symbol codeReview, "the code review of the web app" became "the codeReview of the webapp". With the default glossary, "type script web app" became "TypeScript webapp". main keeps "code review" and "type script"; the parent keeps "web app".

  • Fix: absorbed words support a hit only when no strong anchor (a cue or an evidence term) lies inside that hit. This covers both evidence terms (round 2) and cue words inside a span. A hit that holds a strong anchor already supports every run-together span outside it, so the word that span absorbed is always rewritten away. With this, a rewrite never counts as support for another rewrite that in turn supports it: absorbed words only ever support spans that give no evidence and absorb nothing themselves.
  • New tests: "the code review of the web app" and "type script web app" give webapp only, as main does. Both fail on dd667399 and pass now. The issue's three results still hold.
  • Reviewer's round-3 harness (8 sessions, 2,284,824 normalisations) and the cue harness (3 sessions, 2,441,250 normalisations), on main, the parent ac8e843e and this head. Head outputs that match neither main nor the parent: 0 in both. Losses against main: 0 in both. Self-support violations: 0.
  • bench:transcription: byte-identical to main. voice-adapters 173/173. pnpm typecheck is clean, eslint is clean on both changed files, pnpm invariants passes 14/14, and pnpm verify passes on a08285bf (7681 passed).

…ence for the sentence

When words ran together into a longer term, the shorter exact form they passed over no longer anchored the rest of an English sentence, so ClarkCant and React beside a run-together webapp were left as heard. The passed-over form and the exact matches inside the span now count as evidence, as they would on their own; they still never support the run-together span itself.

Refs #608, #613
A word a run-together term absorbed now supports only spans that did not
absorb a word themselves. Such a span is rewritten only on a technical cue
or a term heard in its own spelling, so "the web app and the web app" and
"set up the web app for grandma" stay as heard, while "fix the clark cant
web app now" still gives ClarkCant and "fix the web app react now" still
restores React.
…let a span absorb

A term that counts as evidence while it is itself rewritten, such as
"Follow-up" heard for the tool follow-up or "S3" for s3, supports a
run-together span that absorbs a word like "web". That absorbed word no
longer supports lowering the term in return, so "Follow-up on the web app"
keeps "Follow-up" as main does, instead of each change resting only on the
other.
…dence itself

A span that holds a coding cue or an evidence term already supports every
run-together span outside it, so the words those spans absorbed are
rewritten away. They no longer support that span in return: "the code
review of the web app" keeps "code review" and "type script web app" keeps
"type script", as main does.
@mrgoonie

mrgoonie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at a08285bf600109ede2ea9eba72e6dd032ec41a15, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

@mrgoonie
mrgoonie enabled auto-merge (squash) October 8, 2026 01:38
@mrgoonie
mrgoonie merged commit d7897f8 into main Oct 8, 2026
22 of 23 checks passed
@mrgoonie
mrgoonie deleted the fix/621-run-together-code-evidence branch October 8, 2026 01:39
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(voice): count a run-together technical match as code evidence for the sentence

1 participant