Skip to content

v0.2: grammar and repetition layer, citation registry, fixture gates - #6

Merged
ccf merged 26 commits into
mainfrom
feat/v0.2
Sep 14, 2026
Merged

ccf merged 26 commits into
mainfrom
feat/v0.2

Conversation

@ccf

@ccf ccf commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

v0.2 implements the short list from the research review (docs/design/2026-09-14-research-review.md, PR #5): a grammar and repetition layer for the scanner, matching reference entries with a checkable citation registry, fairness guards in the skill, and fixture gates that keep the new metrics honest on human prose.

Spec: docs/design/2026-09-14-humanize-v0.2-design.md · Plan: docs/design/2026-09-14-humanize-v0.2-plan.md

Scanner (surface_scan.py, still stdlib-only, 3.9-compatible)

  • Fixed: apostrophe look-alike glyphs (ʼ ʹ ´ ‘ ’ ′) between letters are normalized before tokenizing, so don’t no longer splits.
  • Added repetition: maximal repeated phrases (≥4 words, ≥2 content words) across sentences, with quotable hits.
  • Added grammar: trailing participial clauses (with fronted-adverbial and list-item exclusions) and abstract container-noun phrases (13 LAMP heads).
  • Added nominalization: suffix hits behind a 223-word stoplist, plus of-frames. Hits only — no rate, by design (register-dependent).
  • Added discourse.disclaimer_opener and sentence_len tails (pct_over_30, p90, longest_flat_run).
  • --text summary grows from 8 to 12 lines. Every pre-existing analyze() key is unchanged.

Knowledge

Evaluation

  • New fixtures: ai_report.txt (AI-style), human_formal.txt (Federalist No. 10), human_plain.txt (archived plainlanguage.gov), with PROVENANCE.md.
  • tests/test_fixtures.py: sensitivity, specificity, direction, and pinned fairness bands. 85 tests, warning-free on 3.9 and 3.13.

Docs

README, CHANGELOG 0.2.0, CLAUDE.md and .cursor/BUGBOT.md invariants (citation rule, optional entry lines, rebalance scope), version 0.2.0.

Test plan

  • uv run pytest -q -W error → 85 passed
  • uv run ruff check . && uv run ruff format --check .
  • claude plugin validate --strict .
  • Headless smoke test: claude -p "/humanize tests/fixtures/ai_report.txt --audit-only" --plugin-dir plugins/humanize → rows for trailing participial clause, verbatim repetition, container-noun phrase, safety disclaimer opener; no nominalization row
  • CI green, Bugbot threads resolved
  • Tag v0.2.0 after merge

🤖 Generated with Claude Code

https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh


Note

Medium Risk
Large additive changes to regex-heavy surface_scan.py and audit semantics; mis-tuned participial/repetition rules could false-flag formal or plain-language human prose despite new fairness gates.

Overview
v0.2.0 extends the stdlib surface_scan.py analyzer with grammar and repetition signals while keeping existing analyze() keys stable, and wires those metrics through reference docs, the skill workflow, and fixture-based regression gates.

Scanner: Apostrophe look-alikes between letters are normalized before tokenization (fixes split words and wordlist misses). New output blocks cover maximal repeated phrases (150-word floor), trailing participial clauses and abstract container-noun phrases, nominalization hits/of frames (no rate), first-paragraph disclaimer openers, and sentence-length tails (pct_over_30, p90, longest_flat_run). --text summaries grow from 8 to 12 lines; JSON uses ensure_ascii=False for hit text.

Knowledge & procedure: references/SOURCES.md plus manifest tests require every [author-year] citation in reference markdown to resolve; provenance rules now allow cited non-StoryScope numbers on Scan: / Rule of thumb: / Vintage: lines. Five new surface tells, principle #8 (register/proficiency are not tells), and SKILL.md updates add an expository register gate, verbatim hit quoting, a passive-voice guard, and a convergence check on rewrite.

Quality: New fixtures (ai_report, Federalist excerpt, plain-language gov text) with sensitivity/specificity/direction/fairness pins in test_fixtures.py; version bumped to 0.2.0 across marketplace/plugin manifests and changelog.

Reviewed by Cursor Bugbot for commit 359cbd3. Bugbot is set up for automated code reviews on this repo. Configure here.

ccf and others added 16 commits September 13, 2026 22:25
Grammar/repetition scanner layer, apostrophe-glyph fix, rewrite guards,
fairness principle, three new surface tells, SOURCES.md provenance, and a
fixture gate for every new metric. Scope per the research review short list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Repetition redefined over maximal phrases with a content-word floor;
apostrophe normalization after Markdown stripping, backtick excluded;
container list trimmed to LAMP's 13 heads; nominalization drops deadjectival
suffixes; disclaimer-opener check; register gate in SKILL; two human fixtures
with sensitivity/specificity gates; grep-checkable citation keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Participial-tail adverbial rule is preposition-led and verbless (the old
word count suppressed the canonical tail); list rule narrowed to ", and or";
repetition maximality with no n ceiling and substring drop; rate reported-only;
singularization strips one "s"; hit shapes fixed per block; SKILL budget
recomputed to 148 with verbatim inserts; plain-language fixture pinned to
digital.gov with fairness bands; numbers moved to Scan lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Ten tasks: apostrophe fix; repetition, grammar, nominalization/disclaimer,
sentence-tail scanner blocks with TDD; report fixtures with sensitivity,
specificity, and pinned fairness gates; SOURCES.md registry with a
key-resolution test; surface-tells, SKILL, principles, and release docs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Fixes found by executing the plan verbatim: E501 literals, function-word
test, containment count guard, list-item participial rule, word-aware
clause cap, valid summarize snippet, 624-word AI fixture, archived
plain-language source with child pages, (0.0, 2.0) band rule, whole-paragraph
doc anchors, SKILL budget recount, import placement, Verified lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
U+00B4 split words; U+02BC tokenized but missed wordlist terms. Runs after
strip_markdown so backticks stay Markdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
…exclusions

Fixture content must not depend on the metric under test. Rebuilds
human_plain.txt using only the stated mechanical rule (paragraphs under 8
words, list lead-ins, and boilerplate dropped) in page order, truncated at
the first paragraph crossing 600 words, with no sentence-level curation.
The honest fixture lands at participial_tail.count == 1, exactly at the
specificity cap, so no metric report is needed. Re-pins all human_plain.txt
bands in PROVENANCE.md and test_fixtures.py from the new measurements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
…principle

SKILL.md: 150 lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py
Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py
ccf and others added 3 commits September 14, 2026 01:45
- ensure_ascii=False on every json.dumps call so --text output quotes curly
  characters verbatim instead of \uXXXX-escaping them (review issue 1,
  Critical): the same strings SKILL.md tells the model to quote in the
  audit table.
- Cap repeated_phrases n-gram length at 60 tokens so a boundary-less
  "sentence" (e.g. an unpunctuated bullet list) can't drive O(L^3) work
  (issue 2).
- Split the fronted-adverbial rule into an unconditional subordinator-led
  skip and a conjunctive-adverb skip, and widen PREP_SUB, clearing 9 of 11
  false positives on ordinary fronted adverbials (issue 3 / T3a / T3c).
- Confirm nominalization.of_frames adjacency against the raw sentence so
  punctuation between the words can no longer fabricate a frame that isn't
  literally in the text (issue 4).
- Add en dash to the clause-terminator regex alongside em dash (issue 13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
…ILL.md

- Replace the (10, 12) formal-nominalization band with a floor (>= 5): the
  behavior the test is for is "formal prose nominalizes, that's not a
  tell," and a tight band breaks on ordinary stoplist tuning (issue 6).
- Widen the human_plain.txt cv band from (0.3, 0.4) to (0.28, 0.42) to
  match the 0.8-1.2 rule PROVENANCE.md itself documents; record the rule
  change and the affected values there.
- Name human_plain.txt's one participial-tail hit as a known metric false
  positive (a gerund subject after a fronted adverbial rule (b) doesn't yet
  cover) in PROVENANCE.md and expected_tells.md, rather than only "genuine
  prose."
- Make the SOURCES.md registry test split on lines starting with "## "
  instead of searching for the next literal heading, so body text shaped
  like a heading can't truncate a block early (T7).
- Add a SKILL.md line-budget test (<= 150 lines): the budget is an
  invariant with no automated guard and the file sits exactly at it
  (issue 10).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
- SKILL.md: reword the passive-recast condition to "the inferred voice or
  a fired tell calls for it" (the old "only when a fired tell names it"
  was unsatisfiable, since no tell entry mentions passives); anchor the
  convergence check to sentence_len.max and cv instead of an unmeasurable
  "narrowed vocabulary" (issues 7, 14). Still exactly 150 lines.
- surface-tells.md: drop the uncited "fivefold within a year" magnitude
  from the Vintage line; kobak-2025's registry entry licenses the decay
  note only, not a figure (issue 5).
- CLAUDE.md / .cursor/BUGBOT.md: add Vintage: to the permitted
  number-bearing lines so the shipped wordlist entry stops contradicting
  the invariant it's supposed to satisfy; update the test-count comment.
- README.md: list disclaimer opener among the scanner's blocks (issue 11).
- CHANGELOG.md: note the 60-token phrase cap, the SKILL.md line-budget
  test, Rule of thumb: and Vintage: in the provenance bullet, and that
  apostrophe normalization shifts words/wordlist.rate/per-1k rates on text
  with look-alike glyphs (issue 12).
- Design spec: mark it "post-review amendments at the end" and append a
  Post-review amendments section recording every ruling above plus the
  measured (not illustrative) fixture figures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py
…ners

- _PARTICIPIAL_TAIL_RE gains a (?!-) lookahead so \b firing at a hyphen no
  longer misreads a hyphenated compound ("cutting-edge") as a clause head
  (Bugbot comment 4002402541).
- _is_fronted_adverbial now evaluates the whole prefix from sentence start
  to the match, not only when the match sits at the sentence's very first
  raw comma — an earlier comma inside the opener itself (city-state, dates,
  thousands separators) no longer disables the adverbial check entirely
  (Bugbot comment 4002402546). The prefix is segmented on its own first
  comma: the resulting opener decides whether a skip is on the table at
  all (subordinator / conjunctive-adverb / verbless-preposition, as
  before), and whatever follows that comma is checked for a finite verb
  (now including a new IRREGULAR_PAST list) to catch a complete second
  clause already under way, in which case the -ing word is a genuine
  trailing participial rather than the opener's gerund subject.
- PREP_SUB gains "without" per the brief's updated extension list.

Re-ran all five fixtures after A3+A6+A7 together: participial_tail.count
is unchanged (ai_report.txt 14, human_formal.txt 0, human_plain.txt 1,
ai_email.txt 0, human_email.txt 0) — both fixture gates still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py Outdated
Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py Outdated
ccf and others added 3 commits September 14, 2026 02:13
…merge

- A7 REVISED: the whole-prefix finite-verb veto from the prior commit
  regressed "In most quarters, revenue rises, lifting margins." from a hit
  to a miss (present-tense finite verbs aren't -ed/FINITE_AUX/IRREGULAR_PAST).
  _is_fronted_adverbial now splits the prefix into ", "-delimited segments:
  the first segment must be opener-led per A3 (subordinator / conjunctive
  adverb / preposition); every later segment must be opener-internal (one
  word, e.g. "Texas", "2024", or itself preposition-led, e.g. "with 1,200
  users") or the guard lifts, since a multi-word non-prepositional segment
  ("revenue rises", "the team grew") is a clause of its own. The verbless
  finite-verb veto (now including IRREGULAR_PAST) still runs over the whole
  prefix, but only for the preposition-led-first-segment branch.
- A8: en dash now terminates a clause only when followed by whitespace
  (`–(?=\s)`), so a numeric range like "2023-2024" (en dash) inside a
  participial tail is no longer truncated mid-range; " – then rested"
  still terminates as before.
- A9 confirmed: PREP_SUB already includes "without" (added in the prior
  commit, ahead of this brief update).
- A10: repeated_phrases now merges runs of overlapping cap-length (60-token)
  grams sharing the same count and sentence set, collapsing sliding-window
  duplicates of one verbatim repeat longer than 60 tokens into a single
  representative phrase instead of counting each window separately.
- B1 REVISED: test_sources_registry_exists_with_expected_keys now splits
  SOURCES.md only on the backticked key-heading form ("## `"), not any line
  starting with "## ", and asserts every expected key is present before
  indexing, closing both the truncation risk and the IndexError risk in the
  prior fix.

Re-ran all five fixtures with A3+A6+A7(revised)+A8+A9+A10 together:
participial_tail.count unchanged (ai_report.txt 14, human_formal.txt 0,
human_plain.txt 1, ai_email.txt 0, human_email.txt 0); repetition rates on
all five fixtures also unchanged, since none contains a repeat anywhere
near the 60-token cap.

Known discrepancy, not resolved here: A10's own explicit test (two
identical 100-word sentences -> one phrase, count 2) passes exactly as
specified. Its second required test -- the A2 probe (an 8-word phrase
repeated 50 times) reporting repeated_phrase_rate < 50 -- does not pass
(measured 1012.5, down from 1637.5 before this fix, a genuine ~38%
reduction). That probe is periodic at every divisor of 8, producing a
"staircase" of same-content grams at lengths 8, 16, 24, ... each with a
different, decreasing count; A10's merge is explicitly scoped to
length-exactly-60 grams sharing the same count, so it cannot touch this
staircase, and the shortest rung alone (the 8-word unit itself, genuinely
occurring 50 times) already yields a higher rate than the threshold
regardless of merging. Reported in full in final-fix-report.md rather than
adjusting the test or inventing broader, unauthorized merge logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
C7 addendum: the design spec's "Post-review amendments" section, added in
an earlier commit under the original C7, predated A6/A7(revised)/A8/A10
and did not record them. Appends one bullet each:

- A6: the (?!-) lookahead excluding hyphenated -ing compounds as clause
  heads.
- Revised A7: the segment-based opener guard (first segment opener-led,
  every later segment opener-internal or the guard lifts), IRREGULAR_PAST
  joining the finite-verb veto, and the two known/accepted residual false
  positives deferred to v0.2.x.
- A8: en dash terminating a clause only when whitespace follows it.
- A10: merging overlapping cap-length gram windows of one long repeat into
  a single phrase.

CHANGELOG.md's grammar-block Added bullet is generic (no rule details), so
per the brief's addendum it is left unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
A11 (Bugbot PR #6 comment 4002562248): the IRREGULAR_PAST list added for
A7 included present/past homographs and common nouns/adjectives (cost,
left, set, rose, read, and 36 others), so a preposition-led opener
containing one of them failed the verbless test and reported a gerund
subject as a trailing participial ("At low cost, shipping continued."
fired on "cost"). Pruned to the 67 unambiguous past forms the brief names,
adding "sought" and "caught" which the pruned list newly requires.

Bugbot's companion finding (present-tense remainder after an opener,
comment 4002562244) needs no new code: the revised A7 segment rule already
handles it, confirmed with "In practice, this approach reduces friction,
enabling teams to move faster." (still fires).

Known, accepted residual issue, not tested or fixed (v0.2.x): "Under the
plan costs rose, driving growth." becomes a miss now that "rose" is
pruned and this sentence's opener is a single-segment prefix with no
other verb.

Re-ran all five fixtures: participial_tail.count unchanged (ai_report.txt
14, human_formal.txt 0, human_plain.txt 1, ai_email.txt 0, human_email.txt
0). Updated the spec's A7 post-review-amendment bullet to name the pruned
list and the residual miss; CLAUDE.md test count to 97.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py
ccf and others added 2 commits September 14, 2026 02:36
A12 (re-review rounds 2-3): four more words on the A11 keep-list read as
common nouns/adjectives in ordinary openers -- felt (wool felt), thought
(on second thought), stole (fur stole), fell (one fell swoop) -- plus
borderline spent (spent grain/fuel) and led (lowercased "LED"). Removed
all six; IRREGULAR_PAST is now 61 words.

Removing "led" alone was not sufficient: _has_finite_verb's generic
`t.endswith("ed")` check independently matched "led" regardless of
IRREGULAR_PAST membership, since it makes no length distinction. Narrowed
that check to words longer than 3 characters -- no genuine English regular
past tense is 3 letters (that would require an impossible 1-letter base
verb), so this excludes "led" (and "red"/"bed"/"wed"/"fed" as a side
effect) without excluding any real regular past tense, which needs at
least a 2-letter base ("used"). Verified "In the LED aisle, shopping
continued." only stopped firing after this second fix; removing "led"
from IRREGULAR_PAST by itself left it firing.

Fallout: the A11 test's own "still fires" example, "Under the plan costs
fell, driving the decision.", relied on the now-pruned "fell" and had to
be updated to use "grew" instead -- the brief's own A12 "still fires" list
silently dropped this exact sentence for the same reason, confirming the
change is intentional, not a regression.

Test: test_irregular_past_homographs_pruned_further_round_two_and_three
-- all 4 new no-hit examples, plus a re-confirmation that "In March, the
team grew, closing the gap." still fires.

Re-ran all five fixtures: participial_tail.count unchanged (ai_report.txt
14, human_formal.txt 0, human_plain.txt 1, ai_email.txt 0, human_email.txt
0). Updated the spec's revised-A7 amendment bullet (61 words; the -ed
length fix; the three v0.2.x residual openers from re-review round 2 --
"In Austin, Travis County, hiring slowed.", "More importantly, the board
met, approving the plan.", "Meanwhile in Austin, shipping continued.");
CLAUDE.md test count to 98.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
Bugbot PR #6 comment 4002717678: in _is_fronted_adverbial, an
intermediate segment starting with a PREP_SUB word was treated as
opener-internal unconditionally, with no finite-verb check. So after a
subordinator or conjunctive-adverb opener, a segment like "with the new
vendor the team shipped faster" was swallowed as if it were mere
opener-internal material (like "with 1,200 users"), and a genuine
trailing participial tail was dropped:
"However, with the new vendor the team shipped faster, cutting the
backlog." missed its real tail.

Fix: a preposition-led intermediate segment is opener-internal only when
_has_finite_verb is False for it (-ed length > 3 / FINITE_AUX /
IRREGULAR_PAST); if it has a finite verb, it's a clause of its own and the
guard lifts, same as a non-prepositional multi-word segment already did.
Single-word segments are unaffected. The first segment's own handling
(subordinator / conjunctive-adverb / preposition-led-verbless) is
unchanged.

Known, accepted, not tested (v0.2.x general no-finite-verb rule): a
present-tense clause inside a prep-led segment still misses --
"Although costs rose, in most quarters revenue rises, lifting margins."
Verified by hand that this remains a miss, exactly as the brief predicts.

Test: test_prep_led_intermediate_segment_with_a_finite_verb_is_a_clause --
both must-fire examples and both must-not-fire examples (the existing
verbless-intermediate-segment cases, re-confirmed unaffected).

Re-ran all five fixtures: participial_tail.count unchanged (ai_report.txt
14, human_formal.txt 0, human_plain.txt 1, ai_email.txt 0, human_email.txt
0). Added one clause to the spec's revised-A7 amendment bullet; CLAUDE.md
test count to 99.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ad9642d. Configure here.

Comment thread plugins/humanize/skills/humanize/scripts/surface_scan.py
Bugbot PR #6 comment 4002782444: A13's finite-verb check ran on every
PREP_SUB-led intermediate segment, including subordinator-led ones
(after, when, once, because, although, ...). A subordinate clause always
carries a verb and its comma closes it, so the following -ing word is
still a gerund subject regardless of that verb -- the guard needs to
stay, not lift. "However, after the audit closed, filing became
routine." was firing incorrectly.

Fix in the intermediate-segment loop of _is_fronted_adverbial: check
SUBORDINATORS first -- a segment whose first word is a subordinator is
opener-internal unconditionally, same as the first-segment rule -- then
single word, then non-subordinator preposition-led-and-verbless,
otherwise clause. PREP_SUB still contains words that are also
subordinators (after, since, when, ...); since the SUBORDINATORS check
always runs first and short-circuits with `continue`, the preposition
branch is only ever reached for a genuine non-subordinator preposition,
so no separate exclusion list is needed.

Test: test_subordinator_led_intermediate_segment_stays_opener_internal --
both must-not-fire examples, plus a re-confirmation of A13's three
must-still-fire examples to verify this fix only narrows A13's scope
(stacked subordinators), not A13 itself.

Also restored two em dashes in the touched docstring/comment that had
been typed as ASCII "--" during editing, matching the file's existing
style; curly literal count in surface_scan.py is now 19 (up from 17),
not a decrease from the wave's starting 15.

Re-ran all five fixtures: participial_tail.count unchanged (ai_report.txt
14, human_formal.txt 0, human_plain.txt 1, ai_email.txt 0, human_email.txt
0). Added one clause to the spec's revised-A7 amendment bullet; CLAUDE.md
test count to 100.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTKYvLEVY4mStJ3iPaB1Mh
@ccf
ccf merged commit 00a3d74 into main Sep 14, 2026
5 checks passed
@ccf
ccf deleted the feat/v0.2 branch September 14, 2026 11:24
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.

1 participant