uttrflow / uttrflow/uttrflow-swift

The churn cap counts small-word edits without distinguishing addition from substitution, so one preposition or modal swap per sentence passes

Open
#194 1 comment 0 reactions 0 assignees View on GitHub
area:dictation bug P2
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

**P2 — meaning change.**

### What happens

"send the invoice to him before Friday" coming back as "Send the invoice from him before Friday." is accepted; so is "we can ship on Tuesday" -> "we could ship on Tuesday". A rewrite with "p.m." or "e.g." in it gets a larger allowance than its sentence count deserves.

### Why

One numeric cap stands in for a rule about categories. With no alignment there is no way to tell an addition from a substitution, so "which small words changed, and in which direction" is not expressible and a count is what was written instead.

### What the code shows

The code is as cited: :147-150 caps churn at `3 * sentenceCount(rewritten)`, `functionWordChurn` (:215-226) is a multiset difference, and `functionWords` (:240-263) holds the prepositions, the personal pronouns and the modals. A single substitution costs two units, so "send the invoice to him" -> "send the invoice from him" (churn 2) and "we can ship on Tuesday" -> "we could ship on Tuesday" (churn 2) are both inside a one-sentence allowance of 3. The `sentenceCount` inflation is also real: :232-234 counts any ".!?" followed by whitespace, so "call me at 5 p.m. tomorrow." scores 2. But the finding's framing is wrong on the point it leans hardest on. It claims the implementation diverges from Docs/cleanup-design.md §4; §4 in fact says "the edit distance in function words is capped per sentence", and Docs/cleanup.md:31 says "the function words a sentence gains and loses are capped at three" — the cap IS the documented decision, restated in two places, not a drift from it. Its own worked example also contradicts itself mid-sentence (it computes a churn of four, then concedes the realistic case is one swap). The residual, genuine observation is that a numeric cap cannot distinguish an addition from a substitution, so a direction or modality reversal is free.

### Where

- `Sources/UttrflowAI/MeaningPreservationGuard.swift:147`
- `Sources/UttrflowAI/MeaningPreservationGuard.swift:229`
- `Sources/UttrflowAI/MeaningPreservationGuard.swift:240`
- `Sources/UttrflowCore/Cleaning/FunctionWords.swift:11`

### Fix direction

Do not delete the cap on this reasoning alone — it is stated as the design in two documents and any replacement changes what the model is allowed to do, which Docs/cleanup.md:122-123 says must be measured before it lands. The structural answer, once the alignment exists: an `.added` or `.dropped` function word is legal only when it is an article or a preposition (the bound Docs/cleanup-design.md §4 actually states for the model), a `.formChanged` edit only under the shared `FormChange` relation, and a preposition, pronoun or modal replaced by a different one is a `.replaced` edit and refused — a content change wearing a function word's clothes. Split `UttrflowCore.FunctionWords` into `articles`, `prepositions`, `auxiliaries` and `pronouns` so each rule reads the subset it needs, and delete the guard's private copy (`MeaningPreservationGuard.functionWords`) in the same change, which is the follow-up FunctionWords.swift:11 already promises. Fix `sentenceCount` separately and cheaply: reuse the rule Docs/cleanup.md:53 already states for `FirstWordPass` — a word carrying a stop inside itself does not end a sentence. Run `make bakeoff` on both halves; the cap's removal is a rule change, not a refactor.

### Principles

Open/closed and single responsibility: a category table the rules read is extended by adding a word; a magic constant of three has to be re-tuned globally for every new case. DRY: the guard's private function-word list disappears into `FunctionWords`.

---

Found by a code-path audit of the dictation pipeline. Traced in the source and checked against `Docs/` by a second reader; **not reproduced at runtime**, which is why this carries no `confirmed` label. Any rule or prompt change proposed above is measured against the corpus with `make bakeoff` before it lands.

Contributor guide

Open the contributing guide

Research direction

Start by reading MeaningPreservationGuard.swift at the cited churn, function-word, and sentence-count sections, then compare its private list with FunctionWords.swift:11 and the referenced cleanup rules. Run make bakeoff before changing behavior. Done means the proposed edit categories and sentence counting are measured, shared function-word definitions are used, and the bakeoff results support the rule change.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
ai
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.