kotoshu 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +22 -5
  4. data/Cargo.lock +291 -0
  5. data/Cargo.toml +15 -0
  6. data/README.adoc +116 -0
  7. data/Rakefile +103 -0
  8. data/TODO.impl/00-vision.md +161 -0
  9. data/TODO.impl/22-url-pin-single-source-of-truth.md +120 -0
  10. data/TODO.impl/23-fix-url-defaults.md +57 -0
  11. data/TODO.impl/24-cli-auto-setup-prompt.md +76 -0
  12. data/TODO.impl/25-soft-onnxruntime-require.md +75 -0
  13. data/TODO.impl/26-onnx-vocab-json-generation.md +96 -0
  14. data/TODO.impl/27-end-to-end-smoke-spec.md +113 -0
  15. data/TODO.impl/28-readme-quickstart-verification.md +68 -0
  16. data/TODO.impl/29-status-command.md +84 -0
  17. data/TODO.impl/30-language-auto-detection.md +74 -0
  18. data/TODO.impl/31-download-progress.md +58 -0
  19. data/TODO.impl/32-sarif-output-verification.md +72 -0
  20. data/TODO.impl/36-cleanup-regressions-0.3.1.md +17 -0
  21. data/TODO.impl/37-hunspell-correctness-tier2.md +21 -0
  22. data/TODO.impl/38-onnx-semantic-gating.md +63 -0
  23. data/TODO.impl/39-tier3-and-beyond.md +36 -0
  24. data/TODO.impl/65-universal-kotoshu.md +251 -0
  25. data/TODO.impl/66-kotoshu-core.md +169 -0
  26. data/TODO.impl/67-kotoshu-rs-and-access-libraries.md +137 -0
  27. data/TODO.impl/68-sota-adoption.md +152 -0
  28. data/TODO.impl/69-sota-typo-eval.md +74 -0
  29. data/TODO.impl/70-sota-confidence-cascade.md +59 -0
  30. data/TODO.impl/71-sota-nested-model.md +63 -0
  31. data/TODO.impl/72-model2vec-evaluation.md +49 -0
  32. data/TODO.impl/73-rl-reranker.md +42 -0
  33. data/TODO.impl/74-distilled-reranker.md +36 -0
  34. data/TODO.impl/75-llm-evaluator.md +35 -0
  35. data/TODO.impl/76-optional-llm-backend.md +37 -0
  36. data/conformance/vectors.jsonl +2630 -0
  37. data/data/en/grammar/rules.yaml +109 -0
  38. data/docs/cache-architecture.md +502 -0
  39. data/ext/kotoshu_native/Cargo.toml +37 -0
  40. data/ext/kotoshu_native/extconf.rb +15 -0
  41. data/ext/kotoshu_native/src/lib.rs +15 -0
  42. data/lib/kotoshu/algorithms/edit_distance.rb +57 -7
  43. data/lib/kotoshu/algorithms/lookup/affix_form.rb +132 -0
  44. data/lib/kotoshu/algorithms/lookup/compound_checks.rb +209 -0
  45. data/lib/kotoshu/algorithms/lookup/compound_form.rb +50 -0
  46. data/lib/kotoshu/algorithms/lookup.rb +95 -236
  47. data/lib/kotoshu/algorithms/suggest.rb +231 -50
  48. data/lib/kotoshu/cache/base_cache.rb +46 -3
  49. data/lib/kotoshu/cache/model_cache.rb +449 -7
  50. data/lib/kotoshu/cache/model_registry.rb +109 -0
  51. data/lib/kotoshu/cache.rb +1 -0
  52. data/lib/kotoshu/cli/cache_command.rb +44 -38
  53. data/lib/kotoshu/cli.rb +34 -4
  54. data/lib/kotoshu/configuration.rb +61 -7
  55. data/lib/kotoshu/conformance_exporter.rb +256 -0
  56. data/lib/kotoshu/conformance_runner.rb +176 -0
  57. data/lib/kotoshu/core/indexed_dictionary.rb +7 -2
  58. data/lib/kotoshu/debug_mode.rb +2 -0
  59. data/lib/kotoshu/dictionary/base.rb +14 -0
  60. data/lib/kotoshu/dictionary/plain_text.rb +17 -0
  61. data/lib/kotoshu/embeddings/onnx_runtime_model.rb +11 -1
  62. data/lib/kotoshu/embeddings/vocabulary.rb +25 -1
  63. data/lib/kotoshu/metrics_collector.rb +16 -0
  64. data/lib/kotoshu/metrics_module.rb +11 -0
  65. data/lib/kotoshu/models/onnx_model.rb +22 -23
  66. data/lib/kotoshu/native.rb +83 -0
  67. data/lib/kotoshu/native_backend.rb +131 -0
  68. data/lib/kotoshu/readers/aff_data.rb +87 -16
  69. data/lib/kotoshu/readers/aff_reader.rb +5 -4
  70. data/lib/kotoshu/readers/lookup_builder.rb +1 -15
  71. data/lib/kotoshu/resource_manager.rb +176 -22
  72. data/lib/kotoshu/source_registry.rb +21 -4
  73. data/lib/kotoshu/spellchecker.rb +24 -0
  74. data/lib/kotoshu/suggestions/frequency_provider.rb +59 -0
  75. data/lib/kotoshu/suggestions/semantic_cascade.rb +73 -0
  76. data/lib/kotoshu/suggestions/strategies/base_strategy.rb +10 -0
  77. data/lib/kotoshu/suggestions/strategies/composite_strategy.rb +62 -3
  78. data/lib/kotoshu/suggestions/strategies/edit_distance_strategy.rb +70 -199
  79. data/lib/kotoshu/suggestions/strategies/semantic_strategy.rb +9 -0
  80. data/lib/kotoshu/suggestions/suggestion_set.rb +9 -2
  81. data/lib/kotoshu/suggestions.rb +2 -0
  82. data/lib/kotoshu/version.rb +1 -1
  83. data/lib/kotoshu.rb +20 -4
  84. data/rakelib/compile.rake +10 -0
  85. metadata +61 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 503b4db65b4971cdc8854123771d9c1855af0de6f408f625d63219d5470c848c
4
- data.tar.gz: caa57938fc7a76f3c0f7d026346db8e7f247db34b1a9b51d79ab2e9962668f57
3
+ metadata.gz: 48277511d3fd0b5244b98f54a43c1867991fd943092c9b78f657885addfa940e
4
+ data.tar.gz: 0434b01a14db2d334fceb53ced9003ac1378abf75db4386aa5397dfc5d853367
5
5
  SHA512:
6
- metadata.gz: 9bb38cd338777586b8bf3c4700d36c5bd083b81f002fc9df0ee3ab667bde36a2a31b0dd8ebd9096543b57de8e151c18b9265df900306c7c71ae06cdc8e33825f
7
- data.tar.gz: 6a11f2cbc34632d9533af0665308943063f06d952816e277fd1e995adfbbe84a56915693a6b9a9a53a4c64589ff8d09399c66ace9c7f7c657f865e4b0ef7adc4
6
+ metadata.gz: adb238648d03837cb4b0fc326c4038d3a40bb5cea968f6ffc49bd2a4bc2ef2614a42a1b79f4cd69e5efc279c0730878aa64885a2384f5ab9cca61c8ade7f096e
7
+ data.tar.gz: 3c1f20fd74572d1ae069767c78faad2fb37caffa3a051d8f6ca2355816f3e6d96fee23de275166b8307cd2622ac80d6982d4aaab35db2617c675e4766d705514
data/CHANGELOG.md CHANGED
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] — 2026-09-05
9
+
10
+ The universal-kotoshu cut (plan 65's roadmap milestone): tiered
11
+ models, the Rust core, and the native extension.
12
+
13
+ ### Added
14
+
15
+ - **Model tiers** — `full` (~120 MB) / `fluency` (~15 MB, the new
16
+ default) / `mini` (~3 MB), resolved through the models repo's
17
+ registry (`kotoshu://models/{lang}/{tier}`) with sha256-verified
18
+ primary→mirror→vocab downloads; `KOTOSHU_MODEL_TIER`, `setup
19
+ LANG --model --tier`, and a tier-less legacy-cache bridge.
20
+ - **Native extension** (`ext/kotoshu_native`, optional Rust
21
+ accelerator): `KOTOSHU_BACKEND=ruby|native|auto` (default ruby;
22
+ pure-Ruby installs unchanged), `Kotoshu::Native.available?`, and
23
+ `rake kotoshu:conformance:compare` — 2630 vectors, zero divergences
24
+ between the Ruby and Rust engines.
25
+ - **Conformance export** — `rake kotoshu:conformance:export` freezes
26
+ engine behavior as golden vectors (2630 lines) consumed by
27
+ kotoshu-rs CI.
28
+ - **Confidence cascade** — `semantic_cascade_threshold`
29
+ (`KOTOSHU_SEMANTIC_CASCADE_THRESHOLD`): skip the ONNX rerank when
30
+ the composite strategies are already confident.
31
+ - Hunspell correctness: CHECKCOMPOUNDPATTERN replacement support,
32
+ dot-split casing, INITCAP ngram-root skip, AF alias flag-format
33
+ parsing, and a long tail of fixture fixes (integrational suite
34
+ 176 examples, 0 failures, 1 documented pending).
35
+
36
+ ### Fixed
37
+
38
+ - Model downloads cached 134-byte LFS pointer stubs whose own sha256
39
+ verified forever — the model source now fetches from the media host
40
+ and rejects stubs at download and verify.
41
+ - `Embeddings::Vocabulary` crashed on the shipped wrapped vocab
42
+ format; shared normalizer added.
43
+ - `Models::OnnxModel` used a nonexistent ONNX Runtime API and wrong
44
+ IO names — rewritten against `InferenceSession`.
45
+ - CLI JSON output restored to the documented camelCase contract;
46
+ `DictionaryNotFoundError` mapping restored at the cache boundary.
47
+
8
48
  ## [0.5.0] — 2026-06-30
9
49
 
10
50
  Tier 2.5 release. Adds the personal-dictionary CLI, completes direct
data/CLAUDE.md CHANGED
@@ -9,8 +9,25 @@ dictionary/affix backend (Hunspell-style) with ONNX-converted FastText word
9
9
  embeddings for context-aware suggestions. The README.adoc is the authoritative
10
10
  user-facing description; this file is the contributor-facing map.
11
11
 
12
- Key dependencies (`kotoshu.gemspec`): `thor` (CLI), `suika` (tokenizer),
13
- `onnxruntime` (semantic inference). Ruby 3.1+.
12
+ Runtime deps (`kotoshu.gemspec`): `thor` (CLI), `rubyzip` (archive extraction during setup), `lutaml-model` (all model (de)serialization — see below). **Soft deps** (NOT in gemspec; `gem install kotoshu` works without them): `onnxruntime` (semantic / ONNX inference) and `suika` (Japanese morphological tokenization; its native ext `dartsclone` would otherwise break slim installs). Ruby 3.1+.
13
+
14
+ ## Serialization rule (load-bearing)
15
+
16
+ All model classes use `lutaml-model` for serialization — declare attributes via `attribute :name, :type` and wire names via `mapping do ... end`. **Never hand-roll `to_h` / `from_h` / `to_json` / `from_json` / `serialize` / `deserialize`** on a model. Renames go in `mapping`, not in method bodies. This is a global rule (~/.claude/CLAUDE.md) — existing hand-rolled serialization is debt to migrate, not a pattern to extend.
17
+
18
+ ## Workspace: five repos under `kotoshu/`
19
+
20
+ The parent directory `/Users/mulgogi/src/kotoshu/` is a multi-repo workspace — the gem is one piece. The other four are **content repos** fetched on demand by the cache layer, never vendored:
21
+
22
+ | Sibling dir | Repo | Role |
23
+ |---|---|---|
24
+ | `kotoshu/` (this one) | `kotoshu/kotoshu` | The gem — Ruby API + CLI + analysis pipeline |
25
+ | `dictionaries/` | `kotoshu/dictionaries` | Hunspell `.aff`/`.dic` for ~98 languages (download source for `LanguageCache`) |
26
+ | `frequency-list-kelly/` | `kotoshu/frequency-list-kelly` | Kelly Project frequency tiers (download source for `FrequencyCache`) |
27
+ | `models-fasttext-onnx/` | `kotoshu/models-fasttext-onnx` | FastText `.vec` → ONNX (download source for `ModelCache`) |
28
+ | `kotoshu.github.io/` | `kotoshu/kotoshu.github.io` | Marketing/docs site |
29
+
30
+ Each content repo has its own `TODO.impl/`. The vision and cross-repo plan index live in `TODO.impl/00-vision.md`.
14
31
 
15
32
  ## Development Commands
16
33
 
@@ -105,7 +122,7 @@ Three caches under `~/.cache/kotoshu/` (see `CACHE_ARCHITECTURE.md` for detail,
105
122
  |---|---|---|---|
106
123
  | `Cache::LanguageCache` | `~/.cache/kotoshu/languages/{code}/spelling/` | `github.com/kotoshu/dictionaries` | 7 days |
107
124
  | `Cache::FrequencyCache` | `~/.cache/kotoshu/frequency-lists/{code}/` | `github.com/kotoshu/frequency-list-kelly` | 7 days |
108
- | `Cache::ModelCache` | `~/.cache/kotoshu/models/{code}/...` | `github.com/kotoshu/models-fasttext-onnx` (FastText `.vec` → ONNX converted upstream) | 30 days |
125
+ | `Cache::ModelCache` | `~/.cache/kotoshu/models/{code}/...` | `github.com/kotoshu/models-fasttext-onnx` (FastText `.vec` → ONNX converted upstream; tiers: full/fluency/mini, registry-resolved) | 30 days |
109
126
 
110
127
  `FrequencyCache` feeds `frequency_bonus` in `Suggestions::Strategies::EditDistanceStrategy` — high-frequency words get a ranking boost. The `kotoshu cache` subcommand exposes list/status/download/info/purge/clean operations.
111
128
 
@@ -170,7 +187,7 @@ When implementing features, study these alongside Kotoshu:
170
187
 
171
188
  ## Other Notes
172
189
 
173
- - License is **BSD-2-Clause** (not MIT the README's "License" section is wrong).
190
+ - License is **BSD-2-Clause** (per `kotoshu.gemspec` and `LICENSE`).
174
191
  - RBS signatures live in `sig/kotoshu.rbs` (the `sig/kotoshu/` subdirectory is empty). Update signatures when changing public APIs.
175
- - `scripts/` contains one-off utilities (FastText→ONNX conversion in Python, Kelly frequency parsing, diagnostics). `examples/` has numbered walkthrough scripts (`01_*.rb` … `07_*.rb`).
192
+ - `scripts/` contains one-off utilities (FastText→ONNX conversion in Python, Kelly frequency parsing, diagnostics). `examples/` has numbered walkthrough scripts (`01_*.rb` … `14_*.rb`) plus per-language demo scripts and Spylls-comparison scripts.
176
193
  - Design history and superseded planning docs live in `docs/` (`architecture.md`, `cache-architecture.md`, `performance.md`, `plugins.md`, `getting-started.md`, plus integrated planning docs like `KOTOSHU_SOLIDIFICATION_PLAN.md`, `ARCHITECTURE_IMPROVEMENTS.md`, `TDD_ITERATION_STRATEGY.md`). Treat them as historical context, verify against current code before relying on them. `TODO.impl/` is the current source of truth for execution plans.
data/Cargo.lock ADDED
@@ -0,0 +1,291 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.5"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "bindgen"
16
+ version = "0.72.1"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
19
+ dependencies = [
20
+ "bitflags",
21
+ "cexpr",
22
+ "clang-sys",
23
+ "itertools",
24
+ "proc-macro2",
25
+ "quote",
26
+ "regex",
27
+ "rustc-hash",
28
+ "shlex",
29
+ "syn",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "bitflags"
34
+ version = "2.13.1"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
37
+
38
+ [[package]]
39
+ name = "cexpr"
40
+ version = "0.6.0"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
43
+ dependencies = [
44
+ "nom",
45
+ ]
46
+
47
+ [[package]]
48
+ name = "cfg-if"
49
+ version = "1.0.4"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
52
+
53
+ [[package]]
54
+ name = "clang-sys"
55
+ version = "1.9.1"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
58
+ dependencies = [
59
+ "glob",
60
+ "libc",
61
+ "libloading",
62
+ ]
63
+
64
+ [[package]]
65
+ name = "either"
66
+ version = "1.18.0"
67
+ source = "registry+https://github.com/rust-lang/crates.io-index"
68
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
69
+
70
+ [[package]]
71
+ name = "glob"
72
+ version = "0.3.4"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
75
+
76
+ [[package]]
77
+ name = "itertools"
78
+ version = "0.13.0"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
81
+ dependencies = [
82
+ "either",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "kotoshu"
87
+ version = "0.1.0"
88
+ source = "git+https://github.com/kotoshu/kotoshu-rs?branch=main#9941f1ea1a6157c57659e428f475959508d4d0b3"
89
+ dependencies = [
90
+ "magnus",
91
+ ]
92
+
93
+ [[package]]
94
+ name = "kotoshu_native"
95
+ version = "0.0.0"
96
+ dependencies = [
97
+ "kotoshu",
98
+ "magnus",
99
+ "rb-sys",
100
+ ]
101
+
102
+ [[package]]
103
+ name = "lazy_static"
104
+ version = "1.5.0"
105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
106
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
107
+
108
+ [[package]]
109
+ name = "libc"
110
+ version = "0.2.189"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
113
+
114
+ [[package]]
115
+ name = "libloading"
116
+ version = "0.8.9"
117
+ source = "registry+https://github.com/rust-lang/crates.io-index"
118
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
119
+ dependencies = [
120
+ "cfg-if",
121
+ "windows-link",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "magnus"
126
+ version = "0.8.2"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
129
+ dependencies = [
130
+ "magnus-macros",
131
+ "rb-sys",
132
+ "rb-sys-env",
133
+ "seq-macro",
134
+ ]
135
+
136
+ [[package]]
137
+ name = "magnus-macros"
138
+ version = "0.8.0"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
141
+ dependencies = [
142
+ "proc-macro2",
143
+ "quote",
144
+ "syn",
145
+ ]
146
+
147
+ [[package]]
148
+ name = "memchr"
149
+ version = "2.8.3"
150
+ source = "registry+https://github.com/rust-lang/crates.io-index"
151
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
152
+
153
+ [[package]]
154
+ name = "minimal-lexical"
155
+ version = "0.2.1"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
158
+
159
+ [[package]]
160
+ name = "nom"
161
+ version = "7.1.3"
162
+ source = "registry+https://github.com/rust-lang/crates.io-index"
163
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
164
+ dependencies = [
165
+ "memchr",
166
+ "minimal-lexical",
167
+ ]
168
+
169
+ [[package]]
170
+ name = "proc-macro2"
171
+ version = "1.0.107"
172
+ source = "registry+https://github.com/rust-lang/crates.io-index"
173
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
174
+ dependencies = [
175
+ "unicode-ident",
176
+ ]
177
+
178
+ [[package]]
179
+ name = "quote"
180
+ version = "1.0.47"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
183
+ dependencies = [
184
+ "proc-macro2",
185
+ ]
186
+
187
+ [[package]]
188
+ name = "rb-sys"
189
+ version = "0.9.130"
190
+ source = "registry+https://github.com/rust-lang/crates.io-index"
191
+ checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
192
+ dependencies = [
193
+ "rb-sys-build",
194
+ ]
195
+
196
+ [[package]]
197
+ name = "rb-sys-build"
198
+ version = "0.9.130"
199
+ source = "registry+https://github.com/rust-lang/crates.io-index"
200
+ checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
201
+ dependencies = [
202
+ "bindgen",
203
+ "lazy_static",
204
+ "proc-macro2",
205
+ "quote",
206
+ "regex",
207
+ "shell-words",
208
+ "syn",
209
+ ]
210
+
211
+ [[package]]
212
+ name = "rb-sys-env"
213
+ version = "0.2.3"
214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
215
+ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
216
+
217
+ [[package]]
218
+ name = "regex"
219
+ version = "1.13.1"
220
+ source = "registry+https://github.com/rust-lang/crates.io-index"
221
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
222
+ dependencies = [
223
+ "aho-corasick",
224
+ "memchr",
225
+ "regex-automata",
226
+ "regex-syntax",
227
+ ]
228
+
229
+ [[package]]
230
+ name = "regex-automata"
231
+ version = "0.4.18"
232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
233
+ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
234
+ dependencies = [
235
+ "aho-corasick",
236
+ "memchr",
237
+ "regex-syntax",
238
+ ]
239
+
240
+ [[package]]
241
+ name = "regex-syntax"
242
+ version = "0.8.11"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
245
+
246
+ [[package]]
247
+ name = "rustc-hash"
248
+ version = "2.1.3"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
251
+
252
+ [[package]]
253
+ name = "seq-macro"
254
+ version = "0.3.6"
255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
256
+ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
257
+
258
+ [[package]]
259
+ name = "shell-words"
260
+ version = "1.1.1"
261
+ source = "registry+https://github.com/rust-lang/crates.io-index"
262
+ checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
263
+
264
+ [[package]]
265
+ name = "shlex"
266
+ version = "1.3.0"
267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
268
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
269
+
270
+ [[package]]
271
+ name = "syn"
272
+ version = "2.0.119"
273
+ source = "registry+https://github.com/rust-lang/crates.io-index"
274
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
275
+ dependencies = [
276
+ "proc-macro2",
277
+ "quote",
278
+ "unicode-ident",
279
+ ]
280
+
281
+ [[package]]
282
+ name = "unicode-ident"
283
+ version = "1.0.24"
284
+ source = "registry+https://github.com/rust-lang/crates.io-index"
285
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
286
+
287
+ [[package]]
288
+ name = "windows-link"
289
+ version = "0.2.1"
290
+ source = "registry+https://github.com/rust-lang/crates.io-index"
291
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
data/Cargo.toml ADDED
@@ -0,0 +1,15 @@
1
+ # Kotoshu gem Rust workspace (plan 66 / P4b) — mirrors parsanol-ruby's
2
+ # root Cargo.toml: rb_sys's compile task runs `cargo metadata` from the gem
3
+ # root, so the extension crate must be reachable through a root manifest.
4
+ [workspace]
5
+ resolver = "2"
6
+ members = ["ext/kotoshu_native"]
7
+
8
+ # Release profile follows the kotoshu-rs workspace policy (plan 66): fat
9
+ # LTO and single codegen unit so the shipped accelerator is as fast as the
10
+ # core's own optimized builds. Local development builds use the dev profile
11
+ # via RB_SYS_CARGO_PROFILE (see ext/kotoshu_native/extconf.rb).
12
+ [profile.release]
13
+ opt-level = 3
14
+ lto = "fat"
15
+ codegen-units = 1
data/README.adoc CHANGED
@@ -171,6 +171,50 @@ analyzer.suggest_corrections("helo", context: "I said helo to the world").map(&:
171
171
  NOTE: The semantic path requires the optional `onnxruntime` gem. See
172
172
  <<_requirements,Requirements>>.
173
173
 
174
+ [[confidence-cascade]]
175
+ === Confidence cascade
176
+
177
+ When the traditional (dictionary + edit distance) strategies are already
178
+ confident about a word, the semantic rerank adds latency and model load
179
+ without changing the answer. The confidence cascade formalizes the
180
+ hybrid split: dictionary verdict first, ONNX rerank only for uncertain
181
+ candidates.
182
+
183
+ The `semantic_cascade_threshold` configuration option controls it:
184
+
185
+ [cols="1,3"]
186
+ |===
187
+ |Threshold |Behavior
188
+
189
+ |`1.0` (default)
190
+ |Always rerank — identical to the classic pipeline. This is the sentinel:
191
+ the composite can legitimately score exactly `1.0`, so `1.0` never skips.
192
+
193
+ |`0.9`
194
+ |Rerank only when the top traditional candidate scores below `0.9`;
195
+ otherwise skip the ONNX rerank for that word.
196
+
197
+ |`0.0`
198
+ |Never rerank — the semantic path is bypassed entirely.
199
+ |===
200
+
201
+ [source,bash]
202
+ ----
203
+ # Tune via environment variable
204
+ KOTOSHU_SEMANTIC_CASCADE_THRESHOLD=0.9 kotoshu check document.txt
205
+
206
+ # Or via the Ruby API
207
+ Kotoshu.configuration.semantic_cascade_threshold = 0.9
208
+ ----
209
+
210
+ Each skip is counted in the metrics registry
211
+ (`Kotoshu::Metrics.stats[:semantic_cascade_skips]`, also exported in
212
+ StatsD and Prometheus formats) and logged under debug/verbose output.
213
+
214
+ Tuning guidance will come from the eval harness once the corpus
215
+ benchmark lands; until then, `0.8`–`0.9` is a reasonable range for
216
+ typo-heavy text.
217
+
174
218
  [[multi-language]]
175
219
  == Multi-Language Support
176
220
 
@@ -276,6 +320,78 @@ https://github.com/kotoshu/models-fasttext-onnx[`kotoshu/models-fasttext-onnx']
276
320
  repo. The CLI downloads pre-converted artifacts; users do not run
277
321
  conversion locally.
278
322
 
323
+ [[model-tiers]]
324
+ === Model tiers
325
+
326
+ ONNX models come in three tiers, published by
327
+ https://github.com/kotoshu/models-fasttext-onnx[models-fasttext-onnx]
328
+ in a resource registry under stable ids (`kotoshu://models/{lang}/{tier}`):
329
+
330
+ [cols="1,2,3"]
331
+ |===
332
+ |Tier |Approx. size |What it is
333
+
334
+ |`full`
335
+ |~120 MB
336
+ |Unquantized, ~100k-word vocabulary. The same artifact and cache layout
337
+ as before tiers existed.
338
+
339
+ |`fluency`
340
+ |~15-18 MB
341
+ |int8-quantized, reduced vocabulary (~50k-60k words).
342
+
343
+ |`mini`
344
+ |~3 MB
345
+ |int8-quantized, 10k-word vocabulary.
346
+ |===
347
+
348
+ Tiered downloads are verified: the model bytes are fetched from the
349
+ registry entry's primary URL, falling back to its mirror, with the vocab
350
+ sibling downloaded alongside; the SHA-256 is checked against the
351
+ registry entry before the model is accepted into the cache.
352
+
353
+ .Picking a tier
354
+ [example]
355
+ ====
356
+ [source,ruby]
357
+ ----
358
+ # One-time setup at a specific tier
359
+ Kotoshu.setup(:en, want: %i[model], tier: :fluency)
360
+
361
+ # Resolve stays cache-only: the requested tier must already be cached,
362
+ # otherwise ResourceNotSetupError is raised (no download, no fallback
363
+ # to another tier)
364
+ Kotoshu.resolve(language: :en, want: %i[spelling model], tier: :fluency)
365
+
366
+ # "Is this tier cached?" (tier: :any asks whether any tier is cached)
367
+ Kotoshu.setup?(:en, resource: :model, tier: :mini)
368
+ ----
369
+
370
+ [source,bash]
371
+ ----
372
+ kotoshu setup en --model --tier mini
373
+ ----
374
+ ====
375
+
376
+ `tier: :any` on resolve maps to the *single* cached tier. It resolves
377
+ when exactly one tier is cached and raises otherwise: with no tier
378
+ cached it raises `ResourceNotSetupError`, and with several it raises
379
+ `Kotoshu::ResourceResolutionError` listing them. That listing uses the
380
+ fixed order `mini`, `fluency`, `full` — a reporting order for
381
+ deterministic error messages, not a fallback chain; tiers are never
382
+ silently substituted for one another.
383
+
384
+ The default tier is `fluency` (~15 MB, near-lossless; owner decision
385
+ 2026-09-04) — caches written under the old `full` default keep working
386
+ via the tier-less legacy bridge. Override it per call with `tier:`, or set
387
+ it globally via the `model_tier` configuration option (environment
388
+ variable `KOTOSHU_MODEL_TIER`; one of `full`, `fluency`, `mini`):
389
+
390
+ [source,bash]
391
+ ----
392
+ KOTOSHU_MODEL_TIER=mini kotoshu setup en --model
393
+ ----
394
+
279
395
  [[interactive-mode]]
280
396
  [[interactive-mode]]
281
397
  == Interactive Mode
data/Rakefile CHANGED
@@ -10,3 +10,106 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  task default: %i[spec rubocop]
13
+
14
+ # Conformance failure reporters (kept as lambdas so nothing leaks into
15
+ # Object from the Rakefile).
16
+ report_failures = lambda do |failures, label = nil|
17
+ failures.first(10).each do |failure|
18
+ prefix = label ? "[#{label}] " : ""
19
+ puts "#{prefix}##{failure.index} #{failure.kind} #{failure.input.inspect} " \
20
+ "on #{failure.dictionary}: expected #{failure.expected.inspect}, got #{failure.actual.inspect}"
21
+ end
22
+ puts "... and #{failures.size - 10} more" if failures.size > 10
23
+ end
24
+
25
+ report_divergences = lambda do |divergences|
26
+ divergences.first(10).each do |divergence|
27
+ puts "[divergence] ##{divergence.index} #{divergence.kind} #{divergence.input.inspect} " \
28
+ "on #{divergence.dictionary}: ruby #{divergence.expected.inspect}, " \
29
+ "native #{divergence.actual.inspect}"
30
+ end
31
+ puts "... and #{divergences.size - 10} more" if divergences.size > 10
32
+ end
33
+
34
+ namespace :kotoshu do
35
+ namespace :conformance do
36
+ desc <<~DESC
37
+ Export conformance vectors for kotoshu-rs (plan 67 M3). Walks the Spylls \
38
+ fixtures (spec/integrational/fixtures) plus the spec fixture corpus, runs \
39
+ the real engine over every fixture word, and writes one JSON object per \
40
+ line. `expected` values are FROZEN engine behavior (byte-reproducible \
41
+ cross-implementation), not claims about linguistic correctness. \
42
+ Output path: ENV["CONFORMANCE_VECTORS_PATH"] or conformance/vectors.jsonl \
43
+ (repo root). The generated file IS committed -- conformance/ is \
44
+ deliberately NOT gitignored -- and kotoshu-rs copies it into \
45
+ tests/conformance/ via its documented sync step so its CI needs no gem.
46
+ DESC
47
+ task :export do
48
+ path = ENV.fetch("CONFORMANCE_VECTORS_PATH", nil) || "conformance/vectors.jsonl"
49
+ require "kotoshu"
50
+ result = Kotoshu::ConformanceExporter.export(path: path)
51
+ puts "Wrote #{result.row_count} vectors from #{result.corpus_count} corpora to #{result.path}"
52
+ unless result.skipped_corpora.empty?
53
+ puts "Skipped corpora:"
54
+ result.skipped_corpora.each { |id, reason| puts " #{id}: #{reason}" }
55
+ end
56
+ result.skipped_words.each { |(id, word), reason| puts " Skipped word #{id} #{word.inspect}: #{reason}" }
57
+ end
58
+
59
+ desc <<~DESC
60
+ Replay conformance/vectors.jsonl through the pure-Ruby engine \
61
+ (plan 66 dual-backend suite). Every vector must reproduce its frozen \
62
+ expectation; exits nonzero on any mismatch.
63
+ DESC
64
+ task :ruby do
65
+ require "kotoshu"
66
+ result = Kotoshu::ConformanceRunner.new.run(backend: :ruby)
67
+ puts "ruby backend: #{result.row_count} vectors, #{result.failures.size} failures"
68
+ report_failures.call(result.failures)
69
+ abort "kotoshu:conformance:ruby: #{result.failures.size} of #{result.row_count} vectors diverged" unless result.ok?
70
+ end
71
+
72
+ desc <<~DESC
73
+ Replay conformance/vectors.jsonl through the native (Rust) engine \
74
+ (plan 66 dual-backend suite). Requires the extension to be built \
75
+ (rake compile); exits nonzero on any mismatch.
76
+ DESC
77
+ task :native do
78
+ require "kotoshu"
79
+ unless Kotoshu::Native.available?
80
+ abort "kotoshu:conformance:native requires the native extension -- run `rake compile` first"
81
+ end
82
+
83
+ result = Kotoshu::ConformanceRunner.new.run(backend: :native)
84
+ puts "native backend: #{result.row_count} vectors, #{result.failures.size} failures"
85
+ report_failures.call(result.failures)
86
+ abort "kotoshu:conformance:native: #{result.failures.size} of #{result.row_count} vectors diverged" unless result.ok?
87
+ end
88
+
89
+ desc <<~DESC
90
+ Run the conformance vectors through BOTH backends and diff every row \
91
+ (plan 66 acceptance: zero diffs). Also fails when either backend \
92
+ diverges from the frozen expectations. Requires the extension \
93
+ (rake compile).
94
+ DESC
95
+ task :compare do
96
+ require "kotoshu"
97
+ unless Kotoshu::Native.available?
98
+ abort "kotoshu:conformance:compare requires the native extension -- run `rake compile` first"
99
+ end
100
+
101
+ result = Kotoshu::ConformanceRunner.new.compare
102
+ puts "compare: #{result.ruby.row_count} vectors -- ruby failures: " \
103
+ "#{result.ruby.failures.size}, native failures: #{result.native.failures.size}, " \
104
+ "backend divergences: #{result.divergences.size}"
105
+ report_failures.call(result.ruby.failures, "ruby")
106
+ report_failures.call(result.native.failures, "native")
107
+ report_divergences.call(result.divergences)
108
+ unless result.ok?
109
+ abort "kotoshu:conformance:compare: backends diverged " \
110
+ "(ruby #{result.ruby.failures.size}, native #{result.native.failures.size}, " \
111
+ "cross #{result.divergences.size} of #{result.ruby.row_count})"
112
+ end
113
+ end
114
+ end
115
+ end