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
- 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
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