Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
bc073f0
docs: RFC for a single-pass period-classifier abbreviation engine
claude Jun 14, 2026
9e33936
test(v2): add Phase-0 acceptance harness for the abbreviation engine
yisding Jun 14, 2026
8c536bf
feat(abbr): add V2 PeriodClassifier for English (en/en_legal opt-in)
yisding Jun 14, 2026
68fe7af
feat(abbr): enable V2 PeriodClassifier for spanish
yisding Jun 14, 2026
acd71ce
feat(abbr): enable V2 PeriodClassifier for danish
yisding Jun 14, 2026
04b7ab4
feat(abbr): enable V2 PeriodClassifier for greek
yisding Jun 14, 2026
89db41c
feat(abbr): enable V2 PeriodClassifier for dutch
yisding Jun 14, 2026
24766ee
feat(abbr): enable V2 PeriodClassifier for italian
yisding Jun 14, 2026
e3f4a67
feat(abbr): enable V2 PeriodClassifier for french
yisding Jun 14, 2026
568eb2b
feat(abbr): enable V2 PeriodClassifier for polish
yisding Jun 14, 2026
cb85c27
feat(abbr): enable V2 PeriodClassifier for hindi
yisding Jun 14, 2026
0d6e41a
feat(marathi): route abbreviation protection through V2 PeriodClassifier
yisding Jun 14, 2026
a611e1f
feat(abbr): enable V2 PeriodClassifier for tagalog
yisding Jun 14, 2026
4d909bb
feat(abbr): enable V2 PeriodClassifier for armenian
yisding Jun 14, 2026
da8f0c4
feat(abbr): enable V2 PeriodClassifier for amharic
yisding Jun 14, 2026
5292cf4
feat(abbr): enable V2 PeriodClassifier for burmese
yisding Jun 14, 2026
caa1ac0
feat(abbr): enable V2 PeriodClassifier for urdu
yisding Jun 14, 2026
c06eb52
feat(abbr): enable V2 PeriodClassifier for en_es_zh
yisding Jun 14, 2026
7fa4a5f
feat(abbr): enable V2 PeriodClassifier for deutsch
yisding Jun 14, 2026
2df83b7
feat(abbr): enable V2 PeriodClassifier for russian
yisding Jun 14, 2026
4df2fc7
feat(abbr): enable V2 PeriodClassifier for slovak
yisding Jun 14, 2026
2d210b4
feat(abbr): enable V2 PeriodClassifier for bulgarian
yisding Jun 14, 2026
07aa34b
feat(abbr): enable V2 PeriodClassifier for arabic and persian
yisding Jun 14, 2026
3d0180b
feat(abbr): enable V2 PeriodClassifier for chinese
yisding Jun 14, 2026
c64d7ca
feat(abbr): enable V2 PeriodClassifier for japanese
yisding Jun 14, 2026
4383e77
feat(abbr): enable V2 PeriodClassifier for kazakh
yisding Jun 14, 2026
c8dcb10
docs: V2 abbreviation engine implementation report
yisding Jun 14, 2026
6412023
refactor(abbr): retire dead legacy abbreviation engine; classifier is…
yisding Jun 14, 2026
993ff6f
perf(abbr): cache PeriodClassifier per (policy, split_mode); single-p…
yisding Jun 14, 2026
13a5661
fix(abbr): join titled-name prefixes and spelled-out a.m./p.m. timezo…
yisding Jun 14, 2026
8b7daed
docs(v2): finishing-pass report — legacy retired, perf reclaimed, 3 t…
yisding Jun 14, 2026
352f37c
docs(v2): append independently-audited verification (perf +2.5%, net …
yisding Jun 14, 2026
42e175c
test(v2): add 26-language segment() baseline snapshot + diff helper
yisding Jun 14, 2026
fd37a27
fix(abbr): strip trailing dot from ar/pl/sk single-token abbreviations
yisding Jun 14, 2026
de7677f
refactor(kk): strip trailing dot from Kazakh abbreviations; retire wh…
yisding Jun 14, 2026
00df7a7
test(abbr): guard against single-token abbreviations stored with a tr…
yisding Jun 14, 2026
c0249cb
refactor(abbr): drop dead _classify_number wrapper from PeriodClassifier
yisding Jun 14, 2026
1861e13
docs(abbr): V2 abbreviation data + dead-code cleanup report
yisding Jun 14, 2026
1cef46d
test(abbr): regression-test the dot-normalization behavior changes
yisding Jun 14, 2026
9873669
refactor(abbr): add shared _cjk_regular_only_policy factory; dedupe z…
yisding Jun 14, 2026
3c577c4
refactor(abbr): co-locate language-specific AbbrPolicies into their l…
yisding Jun 14, 2026
82a3cc5
docs: prioritized v2 refactor roadmap (post-abbreviation-engine)
yisding Jun 14, 2026
baa65a0
test(v2): wire the 26-language segment snapshot into CI
yisding Jun 14, 2026
de63148
refactor(lang): promote shared whole-span abbr policy to lang/common
yisding Jun 14, 2026
608b47d
test(corpus-en): guard empty-param xfail skip with skipif on xfail_ca…
yisding Jun 14, 2026
71bfded
feat(api): export public exceptions and registry funcs at top level
yisding Jun 14, 2026
7e299cc
test(xfail): index the six standing xfails with stable BACKLOG reasons
yisding Jun 14, 2026
3c5979f
test(v2): retire the frozen-against-deleted-code v2 oracle
yisding Jun 14, 2026
a8ae56c
refactor(processor): fold the static self.lang.* rule hooks into Lang…
yisding Jun 14, 2026
fb32833
test(abbr): behavioral data-lint (quarantined) + canonical abbreviati…
yisding Jun 14, 2026
89399fe
refactor(abbr): own the downstream post-period pipeline in AbbrPolicy…
yisding Jun 14, 2026
652ec5c
test: add dedicated processor / period_classifier unit suites (T4)
yisding Jun 14, 2026
4067267
fix(abbr): recognise non-ASCII multi-period abbreviations (S6)
yisding Jun 14, 2026
d93816d
feat(api)!: make spans canonical and unify the lookahead result shape…
yisding Jun 14, 2026
052b7fb
refactor: extract boundary_resplit module out of processor.py (S3)
yisding Jun 15, 2026
208b98a
refactor: extract shared normalization helper into _normalize (S9)
yisding Jun 15, 2026
609574f
docs: clarify spaCy entry point contract status (S-decide)
yisding Jun 15, 2026
9a490e5
refactor(kk): express Kazakh WIDE-follower stems as a policy field (S10)
yisding Jun 15, 2026
91dcf13
test(properties): add core segment() property tests (no-crash/idempot…
yisding Jun 15, 2026
df8a905
test(lang): standardize per-language SBD tests on the assert_segments…
yisding Jun 15, 2026
04ed7f3
docs: V2 tech-debt paydown release-readiness report
yisding Jun 15, 2026
d2c204f
bench(codspeed): track the v2-relevant paths (abbreviation-dense + se…
yisding Jun 15, 2026
2024134
docs: drop the stale next-release version hint from CLAUDE.md
yisding Jun 15, 2026
e4d77ff
fixes
yisding Jun 17, 2026
0ba4cf0
fix(abbr): decide en_legal starter-aware boundaries per occurrence
yisding Jun 17, 2026
c1d90c7
refactor(abbr): unify abbreviation-span math and make Kazakh multi-pe…
yisding Jun 17, 2026
d074e76
Merge remote-tracking branch 'origin/main' into feat/v2-abbreviation-…
yisding Jun 17, 2026
21d4d41
remove analysis folder
yisding Jun 17, 2026
13cb75f
chore: relocate tests to functional homes; drop v2/analysis references
yisding Jun 17, 2026
f4e8e7e
test: reorganize suite into role-based dirs and prune redundant cases
yisding Jun 18, 2026
ca0bded
Merge pull request #79 from yisding/test/suite-reorg-and-prune
yisding Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jobs:
- name: Test with pytest
run: uv run --python ${{ matrix.python-version }} pytest --cov=sentencesplit tests/ --color yes

- name: Test benchmark harness (path redaction)
# The corpus-compare harness lives under benchmarks/ (never shipped), so its
# correctness tests sit outside testpaths=["tests"]. Run them explicitly so the
# path-redaction guard keeps running. pytest's pythonpath=["."] puts the repo
# root on sys.path for `from benchmarks.corpus_compare import segmenters`.
run: uv run --python ${{ matrix.python-version }} pytest benchmarks/test_corpus_compare_segmenters.py --color yes

free-threaded-test:
runs-on: ubuntu-latest

Expand Down Expand Up @@ -180,9 +187,9 @@ jobs:
if arc not in names:
errors.append(f"missing language module {arc}")

# (c) no test/analysis/benchmark files may leak into the wheel.
# (c) no test/benchmark files may leak into the wheel.
for name in sorted(names):
if name.startswith(("tests/", "analysis/", "benchmarks/")):
if name.startswith(("tests/", "benchmarks/")):
errors.append(f"leaked non-shipping path {name}")

if errors:
Expand All @@ -194,6 +201,6 @@ jobs:
print(f"Wheel contents OK: {wheel}")
print(
f" py.typed shipped, {len(expected)} language modules present, "
"no test/analysis/benchmark leakage"
"no test/benchmark leakage"
)
PY
7 changes: 2 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,8 @@ ENV/
# vscode
.vscode/

# Regenerable benchmark/analysis JSON dumps (produced by sibling
# analysis/benchmark scripts; not consumed by tests or the package).
analysis/*comparison*.json
analysis/*results*.json
analysis/verdicts.json
# Regenerable benchmark JSON dumps (produced by the comparison harness;
# not consumed by tests or the package).
benchmarks/corpus_compare/results/divergences_all.json
benchmarks/corpus_compare/results/verdicts.json

3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ Tests are in `tests/`:
Docs and research assets:
- `README.md`: install, public API, lookahead behavior, multi-language usage, and spaCy integration.
- `CONTRIBUTING.md`: contribution workflow and TDD guidance.
- `analysis/`: comparison scripts plus checked-in JSON/Markdown reports.
- `benchmarks/`: benchmark helpers and golden-rule benchmark data.
- `benchmarks/`: benchmark helpers, the cross-library comparison harness, and golden-rule benchmark data.
- `examples/`: runnable examples, including the spaCy component and timing script.

## Build, Test, and Development Commands
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ CI runs lint + tests on Python 3.11, 3.12, 3.13, 3.14.
- Ruff is the sole linter/formatter. Line length: 127.
- `snake_case` functions/variables, `PascalCase` classes, `UPPER_SNAKE_CASE` constants.
- Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/) — `python-semantic-release` parses them to group changelog entries. Format: `<type>(<optional scope>): <imperative subject>`. Common types: `feat`, `fix`, `perf`, `refactor`, `docs`, `test`, `build`, `ci`, `chore`. Breaking changes use `!` after the type/scope (e.g. `feat!: drop Python 3.10`) or a `BREAKING CHANGE:` footer. Keep the subject short, imperative, and specific.
- The release workflow (`.github/workflows/release.yml`) does **not** derive the version number from commit types: the version bump is chosen manually from a `workflow_dispatch` dropdown (`patch` / `minor` / `major` / `prerelease`), which forces the corresponding level. Conventional commit types only drive changelog grouping. Therefore the person cutting a release must pick the right level by hand: a cycle containing any `feat:` must be released as a **minor**, and any breaking change as a **major**. (The next release must be **0.1.0** (minor) — `feat: support free-threaded Python` landed since v0.0.5.)
- The release workflow (`.github/workflows/release.yml`) does **not** derive the version number from commit types: the version bump is chosen manually from a `workflow_dispatch` dropdown (`patch` / `minor` / `major` / `prerelease`), which forces the corresponding level. Conventional commit types only drive changelog grouping. Therefore the person cutting a release must pick the right level by hand: a cycle containing any `feat:` must be released as a **minor**, and any breaking change as a **major**.
- Bug fixes get a regression test in `tests/regression/` before the fix.
- Public API changes (lookahead, split_mode, spans) go in `tests/test_segmenter.py`.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ seg.segment_spans("My name is Jonas E. Smith. Please turn to p. 55.")
# TextSpan(sent='Please turn to p. 55.', start=27, end=48)]
```

`segment_spans()` always returns `TextSpan` objects with `.sent`, `.start`, `.end` regardless of the `char_span` constructor flag.
`segment_spans()` always returns `TextSpan` objects with `.sent`, `.start`, `.end`; `segment()` always returns plain strings. Spans are byte-for-byte faithful: every span is an exact slice of the source and reassembling them reproduces it verbatim.

### Streaming / lookahead

Expand Down Expand Up @@ -126,7 +126,7 @@ stream.feed(full_text)
assert stream.get_completed_sentences() + stream.flush() == Segmenter(language="en").segment(full_text)
```

`StreamSegmenter` accepts the same `language` / `clean` / `char_span` / `split_mode` params as `Segmenter`, plus a streaming-specific `buffering_mode` (`"conservative"` (default) / `"balanced"` / `"aggressive"`) and an optional `max_buffer_size` guard against an unbounded tail.
`StreamSegmenter` accepts the same `language` / `clean` / `split_mode` params as `Segmenter`, plus a `char_span` flag selecting `TextSpan` vs plain-string output, a streaming-specific `buffering_mode` (`"conservative"` (default) / `"balanced"` / `"aggressive"`), and an optional `max_buffer_size` guard against an unbounded tail.

See [examples/streaming_to_tts_recipe.py](examples/streaming_to_tts_recipe.py) for a runnable LLM-to-TTS recipe.

Expand Down Expand Up @@ -251,7 +251,7 @@ seg = sentencesplit.Segmenter(language="en", clean=False)
seg.segment("My name is Jonas E. Smith. Please turn to p. 55.")
```

`Segmenter(language=..., clean=..., char_span=...)`, `segment()`, and the `TextSpan` fields (`.sent`, `.start`, `.end`) all behave as they do in pySBD, and the English [Golden Rules](https://github.com/diasks2/pragmatic_segmenter#the-golden-rules) pass identically. What you gain on top:
`Segmenter(language=..., clean=...)`, `segment()`, and the `TextSpan` fields (`.sent`, `.start`, `.end`) all behave as they do in pySBD, and the English [Golden Rules](https://github.com/diasks2/pragmatic_segmenter#the-golden-rules) pass identically. The one break: pySBD's `char_span=True` constructor flag is gone — call `segment_spans()` for `TextSpan` output instead (`Segmenter(char_span=True).segment(text)` → `Segmenter().segment_spans(text)`). What you gain on top:

- **Streaming/lookahead** — `segment_with_lookahead()` / `should_wait_for_more()` for incremental input, plus the higher-level [`StreamSegmenter`](#streaming-segmentation) feed/flush wrapper for token-by-token sources (LLM output, ASR partials).
- **`split_mode`** — a `"conservative"` / `"balanced"` / `"aggressive"` bias for ambiguous boundaries (`"balanced"` is the default and matches the historically tuned output).
Expand Down Expand Up @@ -375,6 +375,16 @@ from `sentencesplit.languages` and the documented ISO 639-1 language codes (see
`Processor` internals, and the nested language hooks — is private and may change
without notice.

**spaCy component.** The package registers a `spacy_factories` entry point so spaCy
users can do `nlp.add_pipe("sentencesplit")` (see [spaCy integration](#spacy-integration)).
The *stable* contract is the registered factory name `"sentencesplit"` and its
`language` config option, both of which follow the SemVer policy above. The underlying
Python factory (`sentencesplit.spacy_component.create_sentencesplit` and the
`SentenceSplitFactory` class) is deliberately **not** in `sentencesplit.__all__`: its
call signature tracks spaCy's factory protocol rather than this library's API, so it may
change with spaCy's requirements without a SemVer bump here. Add the component by name —
do not import or subclass the factory directly.

**Output stability.** Sentence segmentation output is *not* part of the frozen API.
It MAY change in minor or patch releases when the change is a net accuracy
improvement; any such output change is recorded in [CHANGELOG.md](CHANGELOG.md).
Expand Down
Loading
Loading