blun-king-cli 9.1.563 → 9.1.565
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.
- package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
- package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
- package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
- package/agent-spine-plugin/.mcp.json +8 -8
- package/agent-spine-plugin/CONTRIBUTING.md +52 -0
- package/agent-spine-plugin/LICENSE +186 -186
- package/agent-spine-plugin/README.md +394 -0
- package/agent-spine-plugin/SECURITY.md +47 -0
- package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
- package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
- package/agent-spine-plugin/bin/agentspine.js +7 -7
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/hooks/codex.json +47 -47
- package/agent-spine-plugin/hooks/hooks.json +106 -106
- package/agent-spine-plugin/hooks/version.json +5 -5
- package/agent-spine-plugin/package.json +69 -69
- package/agent-spine-plugin/scripts/check-hosts.js +199 -199
- package/agent-spine-plugin/skill/SKILL.md +76 -76
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
- package/agent-spine-plugin/src/cli.js +1488 -1442
- package/agent-spine-plugin/src/hook.js +886 -812
- package/agent-spine-plugin/src/index.js +93 -93
- package/agent-spine-plugin/src/lib/acceptance.js +333 -333
- package/agent-spine-plugin/src/lib/attention.js +755 -755
- package/agent-spine-plugin/src/lib/audit.js +351 -342
- package/agent-spine-plugin/src/lib/authentication.js +515 -515
- package/agent-spine-plugin/src/lib/briefing.js +317 -317
- package/agent-spine-plugin/src/lib/catalog.js +167 -167
- package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
- package/agent-spine-plugin/src/lib/context.js +154 -154
- package/agent-spine-plugin/src/lib/continuity.js +338 -338
- package/agent-spine-plugin/src/lib/coordination.js +577 -577
- package/agent-spine-plugin/src/lib/documents.js +217 -217
- package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
- package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
- package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
- package/agent-spine-plugin/src/lib/graph.js +337 -337
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/https-transport.js +392 -392
- package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
- package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
- package/agent-spine-plugin/src/lib/learning.js +6923 -6466
- package/agent-spine-plugin/src/lib/object-transport.js +206 -206
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
- package/agent-spine-plugin/src/lib/paths.js +109 -109
- package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
- package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
- package/agent-spine-plugin/src/lib/preflight.js +702 -702
- package/agent-spine-plugin/src/lib/runtime.js +13 -13
- package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
- package/agent-spine-plugin/src/lib/sharing.js +969 -969
- package/agent-spine-plugin/src/lib/source-roots.js +529 -482
- package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
- package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
- package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
- package/agent-spine-plugin/src/mcp.js +597 -572
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +202 -195
- package/bin/active-steer-priority-policy.cjs +24 -0
- package/bin/launcher-runtime.js +8 -1
- package/bin/mnemo-tool-agent-policy.cjs +22 -0
- package/bin/thinking-activity-status-policy.cjs +132 -0
- package/bin/thinking-only-guard.cjs +75 -0
- package/bin/tool-call-loop-policy.cjs +53 -0
- package/bin/turn-thinking-policy.cjs +25 -1
- package/blun.mjs +554 -64
- package/package.json +4 -1
- package/standard-skills/translate-native/LICENSE +21 -21
- package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
- package/standard-skills/translate-native/references/native-orthography.md +79 -79
- package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
- package/standard-skills/translate-native/references/structured-content.md +72 -72
- package/standard-skills/translate-native/references/translationese-review.md +77 -77
- package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
- package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
- package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
- package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
- package/standard-skills/translate-native/scripts/language_quality.py +377 -377
- package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
- package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
- package/standard-tools/language-guard/blun_language_guard.py +697 -697
- package/standard-tools/language-guard/check_diacritics.py +353 -353
- package/standard-tools/language-guard/guard_service_client.py +264 -264
- package/standard-tools/language-guard/language_quality.py +377 -377
- package/standard-tools/language-guard/translation_guard.py +916 -916
|
@@ -1,353 +1,353 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Flag common ASCII substitutions and non-NFC text in prose files."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import re
|
|
8
|
-
import sys
|
|
9
|
-
import unicodedata
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
RULES: dict[str, tuple[tuple[str, str], ...]] = {
|
|
14
|
-
"de": (
|
|
15
|
-
(r"\bfuer\b", "für"),
|
|
16
|
-
(r"\bueber", "über…"),
|
|
17
|
-
(r"\bzurueck", "zurück…"),
|
|
18
|
-
(r"\bmuess", "müss…"),
|
|
19
|
-
(r"\bkoenn", "könn…"),
|
|
20
|
-
(r"\bmoech", "möch…"),
|
|
21
|
-
(r"\bschoen", "schön…"),
|
|
22
|
-
(r"\bgrues", "grüß…"),
|
|
23
|
-
(r"\bkoeln\b", "Köln"),
|
|
24
|
-
(r"\bgroess", "größ…"),
|
|
25
|
-
(r"\baender", "änder…"),
|
|
26
|
-
(r"\bpruef", "prüf…"),
|
|
27
|
-
(r"\bgepruef", "geprüf…"),
|
|
28
|
-
(r"\buebersetz", "übersetz…"),
|
|
29
|
-
(r"\boeffn", "öffn…"),
|
|
30
|
-
(r"\bloesch", "lösch…"),
|
|
31
|
-
(r"\bwaehr", "währ…"),
|
|
32
|
-
(r"\bwaer(?:e|en|st|t)\b", "wär…"),
|
|
33
|
-
(r"\bhaett", "hätt…"),
|
|
34
|
-
(r"\bwuerd", "würd…"),
|
|
35
|
-
(r"\bduerf", "dürf…"),
|
|
36
|
-
(r"\berklaer", "erklär…"),
|
|
37
|
-
(r"\bgeklaer", "geklär…"),
|
|
38
|
-
(r"\bnaechst", "nächst…"),
|
|
39
|
-
(r"\bspaet", "spät…"),
|
|
40
|
-
(r"\bfrueh", "früh…"),
|
|
41
|
-
(r"\bwuensch", "wünsch…"),
|
|
42
|
-
(r"\bgueltig", "gültig…"),
|
|
43
|
-
(r"\bzuverlaess", "zuverläss…"),
|
|
44
|
-
(r"\bhaeufig", "häufig…"),
|
|
45
|
-
(r"\bzusaetz", "zusätz…"),
|
|
46
|
-
(r"\bvollstaendig", "vollständig…"),
|
|
47
|
-
(r"\bpersoenlich", "persönlich…"),
|
|
48
|
-
(r"\bnatuerlich", "natürlich…"),
|
|
49
|
-
(r"\bmoeglich", "möglich…"),
|
|
50
|
-
(r"\bnoetig", "nötig…"),
|
|
51
|
-
(r"\baehnlich", "ähnlich…"),
|
|
52
|
-
(r"\bgehoer", "gehör…"),
|
|
53
|
-
(r"\benthaelt", "enthält"),
|
|
54
|
-
(r"\bwaehl", "wähl…"),
|
|
55
|
-
(r"\bveroeffent", "veröffent…"),
|
|
56
|
-
(r"\bunterstuetz", "unterstütz…"),
|
|
57
|
-
(r"\bbestaet", "bestät…"),
|
|
58
|
-
(r"\bergaenz", "ergänz…"),
|
|
59
|
-
),
|
|
60
|
-
"sv": (
|
|
61
|
-
(r"\bGoteborg\b", "Göteborg"),
|
|
62
|
-
(r"\bMalmo\b", "Malmö"),
|
|
63
|
-
(r"\bOresund\b", "Öresund"),
|
|
64
|
-
(r"\bsmorgasbord\b", "smörgåsbord"),
|
|
65
|
-
(r"\bAngstrom\b", "Ångström"),
|
|
66
|
-
(r"\bforstar\b", "förstår"),
|
|
67
|
-
(r"\bbehover\b", "behöver"),
|
|
68
|
-
(r"\bmojlig", "möjlig…"),
|
|
69
|
-
(r"\bborja", "börja…"),
|
|
70
|
-
(r"\bfraga", "fråga…"),
|
|
71
|
-
(r"\bsjalv", "själv…"),
|
|
72
|
-
(r"\bhar ar\b", "här är"),
|
|
73
|
-
(r"\bdet ar\b", "det är"),
|
|
74
|
-
(r"\bjag ar\b", "jag är"),
|
|
75
|
-
(r"\bvi ar\b", "vi är"),
|
|
76
|
-
(r"\bdu ar\b", "du är"),
|
|
77
|
-
(r"\bar det\b", "är det"),
|
|
78
|
-
(r"\bfor att\b", "för att"),
|
|
79
|
-
(r"\bfor dig\b", "för dig"),
|
|
80
|
-
(r"\bfor er\b", "för er"),
|
|
81
|
-
(r"\bpa en\b", "på en"),
|
|
82
|
-
(r"\bpa ett\b", "på ett"),
|
|
83
|
-
(r"\bpa den\b", "på den"),
|
|
84
|
-
(r"\bpa det\b", "på det"),
|
|
85
|
-
(r"\bsprak", "språk…"),
|
|
86
|
-
(r"\boppn", "öppn…"),
|
|
87
|
-
),
|
|
88
|
-
"es": (
|
|
89
|
-
(r"\bespanol\b", "español"),
|
|
90
|
-
(r"\bsenor", "señor…"),
|
|
91
|
-
(r"\binformacion\b", "información"),
|
|
92
|
-
(r"\btambien\b", "también"),
|
|
93
|
-
(r"\bpagina\b", "página"),
|
|
94
|
-
(r"\bnumero\b", "número"),
|
|
95
|
-
(r"\badios\b", "adiós"),
|
|
96
|
-
(r"\bQue\?", "¿Qué?"),
|
|
97
|
-
),
|
|
98
|
-
"fr": (
|
|
99
|
-
(r"\bfrancais\b", "français"),
|
|
100
|
-
(r"\becole\b", "école"),
|
|
101
|
-
(r"\bcreme brulee\b", "crème brûlée"),
|
|
102
|
-
(r"\btres\b", "très"),
|
|
103
|
-
(r"\bdeja\b", "déjà"),
|
|
104
|
-
(r"\betre\b", "être"),
|
|
105
|
-
),
|
|
106
|
-
"pt": (
|
|
107
|
-
(r"\bSao Paulo\b", "São Paulo"),
|
|
108
|
-
(r"\bcoracao\b", "coração"),
|
|
109
|
-
(r"\bportugues\b", "português"),
|
|
110
|
-
(r"\bvoce\b", "você"),
|
|
111
|
-
(r"\bnao\b", "não"),
|
|
112
|
-
),
|
|
113
|
-
"it": (
|
|
114
|
-
(r"\bperche\b", "perché"),
|
|
115
|
-
(r"\bcitta\b", "città"),
|
|
116
|
-
(r"\bpiu\b", "più"),
|
|
117
|
-
),
|
|
118
|
-
"da": (
|
|
119
|
-
(r"\bKobenhavn\b", "København"),
|
|
120
|
-
(r"\bvaer", "vær…"),
|
|
121
|
-
(r"\bsprogforstaelse\b", "sprogforståelse"),
|
|
122
|
-
),
|
|
123
|
-
"no": (
|
|
124
|
-
(r"\bvaer", "vær…"),
|
|
125
|
-
(r"\bforsta", "forstå…"),
|
|
126
|
-
(r"\bsporsmal\b", "spørsmål"),
|
|
127
|
-
),
|
|
128
|
-
"is": (
|
|
129
|
-
(r"\bReykjavik\b", "Reykjavík"),
|
|
130
|
-
(r"\bThingvellir\b", "Þingvellir"),
|
|
131
|
-
(r"\bislenska\b", "íslenska"),
|
|
132
|
-
),
|
|
133
|
-
"cs": (
|
|
134
|
-
(r"\bcestina\b", "čeština"),
|
|
135
|
-
(r"\bcesk", "česk…"),
|
|
136
|
-
(r"\bprilis\b", "příliš"),
|
|
137
|
-
(r"\bDvorak\b", "Dvořák"),
|
|
138
|
-
(r"\bdekuji\b", "děkuji"),
|
|
139
|
-
(r"\bmesto\b", "město"),
|
|
140
|
-
),
|
|
141
|
-
"sk": (
|
|
142
|
-
(r"\bslovencina\b", "slovenčina"),
|
|
143
|
-
(r"\bdakujem\b", "ďakujem"),
|
|
144
|
-
),
|
|
145
|
-
"pl": (
|
|
146
|
-
(r"\bLodz\b", "Łódź"),
|
|
147
|
-
(r"\bjezyk\b", "język"),
|
|
148
|
-
(r"\bdziekuje\b", "dziękuję"),
|
|
149
|
-
(r"\bzolty\b", "żółty"),
|
|
150
|
-
),
|
|
151
|
-
"hu": (
|
|
152
|
-
(r"\bMagyarorszag\b", "Magyarország"),
|
|
153
|
-
(r"\bkoszonom\b", "köszönöm"),
|
|
154
|
-
(r"\borom\b", "öröm"),
|
|
155
|
-
),
|
|
156
|
-
"ro": (
|
|
157
|
-
(r"\bromana\b", "română"),
|
|
158
|
-
(r"\bBucuresti\b", "București"),
|
|
159
|
-
(r"\bmultumesc\b", "mulțumesc"),
|
|
160
|
-
(r"\btara\b", "țară"),
|
|
161
|
-
),
|
|
162
|
-
"tr": (
|
|
163
|
-
(r"\bIstanbul\b", "İstanbul"),
|
|
164
|
-
(r"\bTurkce\b", "Türkçe"),
|
|
165
|
-
(r"\btesekkur", "teşekkür…"),
|
|
166
|
-
(r"\bgorusuruz\b", "görüşürüz"),
|
|
167
|
-
),
|
|
168
|
-
"hr": (
|
|
169
|
-
(r"\bDorde\b", "Đorđe"),
|
|
170
|
-
(r"\bvec\b", "već"),
|
|
171
|
-
(r"\bcovjek\b", "čovjek"),
|
|
172
|
-
),
|
|
173
|
-
"bs": (
|
|
174
|
-
(r"\bDorde\b", "Đorđe"),
|
|
175
|
-
(r"\bvec\b", "već"),
|
|
176
|
-
(r"\bcovjek\b", "čovjek"),
|
|
177
|
-
),
|
|
178
|
-
"sr": (
|
|
179
|
-
(r"\bDorde\b", "Đorđe"),
|
|
180
|
-
(r"\bvec\b", "već"),
|
|
181
|
-
(r"\bcovek\b", "čovek"),
|
|
182
|
-
),
|
|
183
|
-
"sl": (
|
|
184
|
-
(r"\bslovenscina\b", "slovenščina"),
|
|
185
|
-
(r"\bzivjo\b", "živjo"),
|
|
186
|
-
),
|
|
187
|
-
"nl": (
|
|
188
|
-
(r"\bgeinteresseerd\b", "geïnteresseerd"),
|
|
189
|
-
),
|
|
190
|
-
"fi": (
|
|
191
|
-
(r"\bhyvaa\b", "hyvää"),
|
|
192
|
-
(r"\bpaiva\b", "päivä"),
|
|
193
|
-
),
|
|
194
|
-
"et": (
|
|
195
|
-
(r"\bTonu\b", "Tõnu"),
|
|
196
|
-
(r"\bvoimalik\b", "võimalik"),
|
|
197
|
-
),
|
|
198
|
-
"lv": (
|
|
199
|
-
(r"\bRiga\b", "Rīga"),
|
|
200
|
-
(r"\blatviesu\b", "latviešu"),
|
|
201
|
-
),
|
|
202
|
-
"lt": (
|
|
203
|
-
(r"\blietuviu\b", "lietuvių"),
|
|
204
|
-
(r"\baciu\b", "ačiū"),
|
|
205
|
-
),
|
|
206
|
-
"vi": (
|
|
207
|
-
(r"\bTieng Viet\b", "Tiếng Việt"),
|
|
208
|
-
(r"\bcam on\b", "cảm ơn"),
|
|
209
|
-
(r"\bViet Nam\b", "Việt Nam"),
|
|
210
|
-
),
|
|
211
|
-
"ca": (
|
|
212
|
-
(r"\bcatala\b", "català"),
|
|
213
|
-
(r"\bcollegi\b", "col·legi"),
|
|
214
|
-
),
|
|
215
|
-
"ga": (
|
|
216
|
-
(r"\bSlainte\b", "Sláinte"),
|
|
217
|
-
(r"\bfailte\b", "fáilte"),
|
|
218
|
-
),
|
|
219
|
-
"mt": (
|
|
220
|
-
(r"\bghaliex\b", "għaliex"),
|
|
221
|
-
),
|
|
222
|
-
"sq": (
|
|
223
|
-
(r"\bShqiperi\b", "Shqipëri"),
|
|
224
|
-
),
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
LANGUAGE_MARKERS: dict[str, tuple[str, ...]] = {
|
|
228
|
-
"de": (" der ", " die ", " das ", " und ", " ist ", " nicht ", " ich ", " wir ", " mit "),
|
|
229
|
-
"sv": (" den ", " det ", " och ", " att ", " är ", " inte ", " jag ", " vi ", " med "),
|
|
230
|
-
"es": (" el ", " la ", " los ", " las ", " que ", " una ", " con ", " para "),
|
|
231
|
-
"fr": (" le ", " la ", " les ", " des ", " est ", " une ", " avec ", " pour "),
|
|
232
|
-
"pt": (" o ", " os ", " uma ", " que ", " com ", " para ", " não "),
|
|
233
|
-
"it": (" il ", " lo ", " gli ", " che ", " una ", " con ", " per "),
|
|
234
|
-
"da": (" den ", " det ", " og ", " ikke ", " jeg ", " med ", " til "),
|
|
235
|
-
"no": (" den ", " det ", " og ", " ikke ", " jeg ", " med ", " til "),
|
|
236
|
-
"is": (" og ", " ekki ", " ég ", " með ", " til ", " sem "),
|
|
237
|
-
"cs": (" ten ", " tato ", " a ", " že ", " není ", " jsem ", " pro "),
|
|
238
|
-
"sk": (" ten ", " táto ", " a ", " že ", " nie ", " som ", " pre "),
|
|
239
|
-
"pl": (" ten ", " ta ", " i ", " że ", " nie ", " jest ", " dla "),
|
|
240
|
-
"hu": (" az ", " egy ", " és ", " nem ", " van ", " hogy "),
|
|
241
|
-
"ro": (" un ", " o ", " și ", " nu ", " este ", " pentru "),
|
|
242
|
-
"tr": (" bu ", " bir ", " ve ", " değil ", " için ", " ile "),
|
|
243
|
-
"hr": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
244
|
-
"bs": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
245
|
-
"sr": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
246
|
-
"sl": (" ta ", " ena ", " in ", " ni ", " za ", " je "),
|
|
247
|
-
"nl": (" de ", " het ", " een ", " en ", " niet ", " voor "),
|
|
248
|
-
"fi": (" tämä ", " yksi ", " ja ", " ei ", " on ", " varten "),
|
|
249
|
-
"et": (" see ", " üks ", " ja ", " ei ", " on ", " jaoks "),
|
|
250
|
-
"lv": (" tas ", " viena ", " un ", " nav ", " ir ", " par "),
|
|
251
|
-
"lt": (" tai ", " viena ", " ir ", " nėra ", " yra ", " už "),
|
|
252
|
-
"vi": (" một ", " và ", " không ", " là ", " cho ", " của "),
|
|
253
|
-
"ca": (" el ", " la ", " els ", " una ", " amb ", " per "),
|
|
254
|
-
"ga": (" an ", " na ", " agus ", " ní ", " tá ", " le "),
|
|
255
|
-
"mt": (" il ", " u ", " mhux ", " huwa ", " għal "),
|
|
256
|
-
"sq": (" ky ", " një ", " dhe ", " nuk ", " është ", " për "),
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def mask_technical_text(text: str) -> str:
|
|
261
|
-
"""Mask common technical spans while retaining line positions."""
|
|
262
|
-
patterns = (
|
|
263
|
-
r"```.*?```",
|
|
264
|
-
r"`[^`\n]+`",
|
|
265
|
-
r"https?://\S+",
|
|
266
|
-
r"\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b",
|
|
267
|
-
)
|
|
268
|
-
masked = text
|
|
269
|
-
for pattern in patterns:
|
|
270
|
-
masked = re.sub(
|
|
271
|
-
pattern,
|
|
272
|
-
lambda match: "".join("\n" if char == "\n" else " " for char in match.group(0)),
|
|
273
|
-
masked,
|
|
274
|
-
flags=re.DOTALL,
|
|
275
|
-
)
|
|
276
|
-
return masked
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def detect_languages(text: str) -> tuple[str, ...]:
|
|
280
|
-
padded = f" {text.casefold()} "
|
|
281
|
-
scores = {
|
|
282
|
-
language: sum(padded.count(marker) for marker in markers)
|
|
283
|
-
for language, markers in LANGUAGE_MARKERS.items()
|
|
284
|
-
}
|
|
285
|
-
best = max(scores.values(), default=0)
|
|
286
|
-
if best == 0:
|
|
287
|
-
return ()
|
|
288
|
-
return tuple(language for language, score in scores.items() if score == best)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def iter_findings(text: str, languages: tuple[str, ...]):
|
|
292
|
-
for language in languages:
|
|
293
|
-
for pattern, suggestion in RULES[language]:
|
|
294
|
-
for match in re.finditer(pattern, text, flags=re.IGNORECASE):
|
|
295
|
-
line = text.count("\n", 0, match.start()) + 1
|
|
296
|
-
yield line, language, match.group(0), suggestion
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def check_file(path: Path, language: str) -> int:
|
|
300
|
-
try:
|
|
301
|
-
text = path.read_text(encoding="utf-8")
|
|
302
|
-
except UnicodeDecodeError:
|
|
303
|
-
print(f"{path}: not valid UTF-8", file=sys.stderr)
|
|
304
|
-
return 1
|
|
305
|
-
except OSError as error:
|
|
306
|
-
print(f"{path}: {error}", file=sys.stderr)
|
|
307
|
-
return 1
|
|
308
|
-
|
|
309
|
-
status = 0
|
|
310
|
-
if text != unicodedata.normalize("NFC", text):
|
|
311
|
-
print(f"{path}: Unicode text is not NFC-normalized")
|
|
312
|
-
status = 1
|
|
313
|
-
|
|
314
|
-
prose = mask_technical_text(text)
|
|
315
|
-
normalized_language = language.casefold().split("-", 1)[0].split("_", 1)[0]
|
|
316
|
-
if language == "auto":
|
|
317
|
-
languages = detect_languages(prose)
|
|
318
|
-
elif language == "all":
|
|
319
|
-
languages = tuple(RULES)
|
|
320
|
-
elif normalized_language not in RULES:
|
|
321
|
-
languages = ()
|
|
322
|
-
print(
|
|
323
|
-
f"{path}: no deterministic diacritics rules for {language!r}; "
|
|
324
|
-
"Unicode NFC passed, but the native-orthography review is still required."
|
|
325
|
-
)
|
|
326
|
-
else:
|
|
327
|
-
languages = (normalized_language,)
|
|
328
|
-
for line, code, found, suggestion in iter_findings(prose, languages):
|
|
329
|
-
print(f"{path}:{line}: [{code}] {found!r} -> check {suggestion!r}")
|
|
330
|
-
status = 1
|
|
331
|
-
return status
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def main() -> int:
|
|
335
|
-
parser = argparse.ArgumentParser(
|
|
336
|
-
description="Flag common missing diacritics in UTF-8 prose files."
|
|
337
|
-
)
|
|
338
|
-
parser.add_argument("files", nargs="+", type=Path)
|
|
339
|
-
parser.add_argument(
|
|
340
|
-
"--language",
|
|
341
|
-
default="auto",
|
|
342
|
-
help=(
|
|
343
|
-
"target language or BCP 47 tag; unsupported languages still receive "
|
|
344
|
-
"Unicode checks and exit successfully when clean; default: auto"
|
|
345
|
-
),
|
|
346
|
-
)
|
|
347
|
-
args = parser.parse_args()
|
|
348
|
-
|
|
349
|
-
return max(check_file(path, args.language) for path in args.files)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if __name__ == "__main__":
|
|
353
|
-
raise SystemExit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Flag common ASCII substitutions and non-NFC text in prose files."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import unicodedata
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
RULES: dict[str, tuple[tuple[str, str], ...]] = {
|
|
14
|
+
"de": (
|
|
15
|
+
(r"\bfuer\b", "für"),
|
|
16
|
+
(r"\bueber", "über…"),
|
|
17
|
+
(r"\bzurueck", "zurück…"),
|
|
18
|
+
(r"\bmuess", "müss…"),
|
|
19
|
+
(r"\bkoenn", "könn…"),
|
|
20
|
+
(r"\bmoech", "möch…"),
|
|
21
|
+
(r"\bschoen", "schön…"),
|
|
22
|
+
(r"\bgrues", "grüß…"),
|
|
23
|
+
(r"\bkoeln\b", "Köln"),
|
|
24
|
+
(r"\bgroess", "größ…"),
|
|
25
|
+
(r"\baender", "änder…"),
|
|
26
|
+
(r"\bpruef", "prüf…"),
|
|
27
|
+
(r"\bgepruef", "geprüf…"),
|
|
28
|
+
(r"\buebersetz", "übersetz…"),
|
|
29
|
+
(r"\boeffn", "öffn…"),
|
|
30
|
+
(r"\bloesch", "lösch…"),
|
|
31
|
+
(r"\bwaehr", "währ…"),
|
|
32
|
+
(r"\bwaer(?:e|en|st|t)\b", "wär…"),
|
|
33
|
+
(r"\bhaett", "hätt…"),
|
|
34
|
+
(r"\bwuerd", "würd…"),
|
|
35
|
+
(r"\bduerf", "dürf…"),
|
|
36
|
+
(r"\berklaer", "erklär…"),
|
|
37
|
+
(r"\bgeklaer", "geklär…"),
|
|
38
|
+
(r"\bnaechst", "nächst…"),
|
|
39
|
+
(r"\bspaet", "spät…"),
|
|
40
|
+
(r"\bfrueh", "früh…"),
|
|
41
|
+
(r"\bwuensch", "wünsch…"),
|
|
42
|
+
(r"\bgueltig", "gültig…"),
|
|
43
|
+
(r"\bzuverlaess", "zuverläss…"),
|
|
44
|
+
(r"\bhaeufig", "häufig…"),
|
|
45
|
+
(r"\bzusaetz", "zusätz…"),
|
|
46
|
+
(r"\bvollstaendig", "vollständig…"),
|
|
47
|
+
(r"\bpersoenlich", "persönlich…"),
|
|
48
|
+
(r"\bnatuerlich", "natürlich…"),
|
|
49
|
+
(r"\bmoeglich", "möglich…"),
|
|
50
|
+
(r"\bnoetig", "nötig…"),
|
|
51
|
+
(r"\baehnlich", "ähnlich…"),
|
|
52
|
+
(r"\bgehoer", "gehör…"),
|
|
53
|
+
(r"\benthaelt", "enthält"),
|
|
54
|
+
(r"\bwaehl", "wähl…"),
|
|
55
|
+
(r"\bveroeffent", "veröffent…"),
|
|
56
|
+
(r"\bunterstuetz", "unterstütz…"),
|
|
57
|
+
(r"\bbestaet", "bestät…"),
|
|
58
|
+
(r"\bergaenz", "ergänz…"),
|
|
59
|
+
),
|
|
60
|
+
"sv": (
|
|
61
|
+
(r"\bGoteborg\b", "Göteborg"),
|
|
62
|
+
(r"\bMalmo\b", "Malmö"),
|
|
63
|
+
(r"\bOresund\b", "Öresund"),
|
|
64
|
+
(r"\bsmorgasbord\b", "smörgåsbord"),
|
|
65
|
+
(r"\bAngstrom\b", "Ångström"),
|
|
66
|
+
(r"\bforstar\b", "förstår"),
|
|
67
|
+
(r"\bbehover\b", "behöver"),
|
|
68
|
+
(r"\bmojlig", "möjlig…"),
|
|
69
|
+
(r"\bborja", "börja…"),
|
|
70
|
+
(r"\bfraga", "fråga…"),
|
|
71
|
+
(r"\bsjalv", "själv…"),
|
|
72
|
+
(r"\bhar ar\b", "här är"),
|
|
73
|
+
(r"\bdet ar\b", "det är"),
|
|
74
|
+
(r"\bjag ar\b", "jag är"),
|
|
75
|
+
(r"\bvi ar\b", "vi är"),
|
|
76
|
+
(r"\bdu ar\b", "du är"),
|
|
77
|
+
(r"\bar det\b", "är det"),
|
|
78
|
+
(r"\bfor att\b", "för att"),
|
|
79
|
+
(r"\bfor dig\b", "för dig"),
|
|
80
|
+
(r"\bfor er\b", "för er"),
|
|
81
|
+
(r"\bpa en\b", "på en"),
|
|
82
|
+
(r"\bpa ett\b", "på ett"),
|
|
83
|
+
(r"\bpa den\b", "på den"),
|
|
84
|
+
(r"\bpa det\b", "på det"),
|
|
85
|
+
(r"\bsprak", "språk…"),
|
|
86
|
+
(r"\boppn", "öppn…"),
|
|
87
|
+
),
|
|
88
|
+
"es": (
|
|
89
|
+
(r"\bespanol\b", "español"),
|
|
90
|
+
(r"\bsenor", "señor…"),
|
|
91
|
+
(r"\binformacion\b", "información"),
|
|
92
|
+
(r"\btambien\b", "también"),
|
|
93
|
+
(r"\bpagina\b", "página"),
|
|
94
|
+
(r"\bnumero\b", "número"),
|
|
95
|
+
(r"\badios\b", "adiós"),
|
|
96
|
+
(r"\bQue\?", "¿Qué?"),
|
|
97
|
+
),
|
|
98
|
+
"fr": (
|
|
99
|
+
(r"\bfrancais\b", "français"),
|
|
100
|
+
(r"\becole\b", "école"),
|
|
101
|
+
(r"\bcreme brulee\b", "crème brûlée"),
|
|
102
|
+
(r"\btres\b", "très"),
|
|
103
|
+
(r"\bdeja\b", "déjà"),
|
|
104
|
+
(r"\betre\b", "être"),
|
|
105
|
+
),
|
|
106
|
+
"pt": (
|
|
107
|
+
(r"\bSao Paulo\b", "São Paulo"),
|
|
108
|
+
(r"\bcoracao\b", "coração"),
|
|
109
|
+
(r"\bportugues\b", "português"),
|
|
110
|
+
(r"\bvoce\b", "você"),
|
|
111
|
+
(r"\bnao\b", "não"),
|
|
112
|
+
),
|
|
113
|
+
"it": (
|
|
114
|
+
(r"\bperche\b", "perché"),
|
|
115
|
+
(r"\bcitta\b", "città"),
|
|
116
|
+
(r"\bpiu\b", "più"),
|
|
117
|
+
),
|
|
118
|
+
"da": (
|
|
119
|
+
(r"\bKobenhavn\b", "København"),
|
|
120
|
+
(r"\bvaer", "vær…"),
|
|
121
|
+
(r"\bsprogforstaelse\b", "sprogforståelse"),
|
|
122
|
+
),
|
|
123
|
+
"no": (
|
|
124
|
+
(r"\bvaer", "vær…"),
|
|
125
|
+
(r"\bforsta", "forstå…"),
|
|
126
|
+
(r"\bsporsmal\b", "spørsmål"),
|
|
127
|
+
),
|
|
128
|
+
"is": (
|
|
129
|
+
(r"\bReykjavik\b", "Reykjavík"),
|
|
130
|
+
(r"\bThingvellir\b", "Þingvellir"),
|
|
131
|
+
(r"\bislenska\b", "íslenska"),
|
|
132
|
+
),
|
|
133
|
+
"cs": (
|
|
134
|
+
(r"\bcestina\b", "čeština"),
|
|
135
|
+
(r"\bcesk", "česk…"),
|
|
136
|
+
(r"\bprilis\b", "příliš"),
|
|
137
|
+
(r"\bDvorak\b", "Dvořák"),
|
|
138
|
+
(r"\bdekuji\b", "děkuji"),
|
|
139
|
+
(r"\bmesto\b", "město"),
|
|
140
|
+
),
|
|
141
|
+
"sk": (
|
|
142
|
+
(r"\bslovencina\b", "slovenčina"),
|
|
143
|
+
(r"\bdakujem\b", "ďakujem"),
|
|
144
|
+
),
|
|
145
|
+
"pl": (
|
|
146
|
+
(r"\bLodz\b", "Łódź"),
|
|
147
|
+
(r"\bjezyk\b", "język"),
|
|
148
|
+
(r"\bdziekuje\b", "dziękuję"),
|
|
149
|
+
(r"\bzolty\b", "żółty"),
|
|
150
|
+
),
|
|
151
|
+
"hu": (
|
|
152
|
+
(r"\bMagyarorszag\b", "Magyarország"),
|
|
153
|
+
(r"\bkoszonom\b", "köszönöm"),
|
|
154
|
+
(r"\borom\b", "öröm"),
|
|
155
|
+
),
|
|
156
|
+
"ro": (
|
|
157
|
+
(r"\bromana\b", "română"),
|
|
158
|
+
(r"\bBucuresti\b", "București"),
|
|
159
|
+
(r"\bmultumesc\b", "mulțumesc"),
|
|
160
|
+
(r"\btara\b", "țară"),
|
|
161
|
+
),
|
|
162
|
+
"tr": (
|
|
163
|
+
(r"\bIstanbul\b", "İstanbul"),
|
|
164
|
+
(r"\bTurkce\b", "Türkçe"),
|
|
165
|
+
(r"\btesekkur", "teşekkür…"),
|
|
166
|
+
(r"\bgorusuruz\b", "görüşürüz"),
|
|
167
|
+
),
|
|
168
|
+
"hr": (
|
|
169
|
+
(r"\bDorde\b", "Đorđe"),
|
|
170
|
+
(r"\bvec\b", "već"),
|
|
171
|
+
(r"\bcovjek\b", "čovjek"),
|
|
172
|
+
),
|
|
173
|
+
"bs": (
|
|
174
|
+
(r"\bDorde\b", "Đorđe"),
|
|
175
|
+
(r"\bvec\b", "već"),
|
|
176
|
+
(r"\bcovjek\b", "čovjek"),
|
|
177
|
+
),
|
|
178
|
+
"sr": (
|
|
179
|
+
(r"\bDorde\b", "Đorđe"),
|
|
180
|
+
(r"\bvec\b", "već"),
|
|
181
|
+
(r"\bcovek\b", "čovek"),
|
|
182
|
+
),
|
|
183
|
+
"sl": (
|
|
184
|
+
(r"\bslovenscina\b", "slovenščina"),
|
|
185
|
+
(r"\bzivjo\b", "živjo"),
|
|
186
|
+
),
|
|
187
|
+
"nl": (
|
|
188
|
+
(r"\bgeinteresseerd\b", "geïnteresseerd"),
|
|
189
|
+
),
|
|
190
|
+
"fi": (
|
|
191
|
+
(r"\bhyvaa\b", "hyvää"),
|
|
192
|
+
(r"\bpaiva\b", "päivä"),
|
|
193
|
+
),
|
|
194
|
+
"et": (
|
|
195
|
+
(r"\bTonu\b", "Tõnu"),
|
|
196
|
+
(r"\bvoimalik\b", "võimalik"),
|
|
197
|
+
),
|
|
198
|
+
"lv": (
|
|
199
|
+
(r"\bRiga\b", "Rīga"),
|
|
200
|
+
(r"\blatviesu\b", "latviešu"),
|
|
201
|
+
),
|
|
202
|
+
"lt": (
|
|
203
|
+
(r"\blietuviu\b", "lietuvių"),
|
|
204
|
+
(r"\baciu\b", "ačiū"),
|
|
205
|
+
),
|
|
206
|
+
"vi": (
|
|
207
|
+
(r"\bTieng Viet\b", "Tiếng Việt"),
|
|
208
|
+
(r"\bcam on\b", "cảm ơn"),
|
|
209
|
+
(r"\bViet Nam\b", "Việt Nam"),
|
|
210
|
+
),
|
|
211
|
+
"ca": (
|
|
212
|
+
(r"\bcatala\b", "català"),
|
|
213
|
+
(r"\bcollegi\b", "col·legi"),
|
|
214
|
+
),
|
|
215
|
+
"ga": (
|
|
216
|
+
(r"\bSlainte\b", "Sláinte"),
|
|
217
|
+
(r"\bfailte\b", "fáilte"),
|
|
218
|
+
),
|
|
219
|
+
"mt": (
|
|
220
|
+
(r"\bghaliex\b", "għaliex"),
|
|
221
|
+
),
|
|
222
|
+
"sq": (
|
|
223
|
+
(r"\bShqiperi\b", "Shqipëri"),
|
|
224
|
+
),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
LANGUAGE_MARKERS: dict[str, tuple[str, ...]] = {
|
|
228
|
+
"de": (" der ", " die ", " das ", " und ", " ist ", " nicht ", " ich ", " wir ", " mit "),
|
|
229
|
+
"sv": (" den ", " det ", " och ", " att ", " är ", " inte ", " jag ", " vi ", " med "),
|
|
230
|
+
"es": (" el ", " la ", " los ", " las ", " que ", " una ", " con ", " para "),
|
|
231
|
+
"fr": (" le ", " la ", " les ", " des ", " est ", " une ", " avec ", " pour "),
|
|
232
|
+
"pt": (" o ", " os ", " uma ", " que ", " com ", " para ", " não "),
|
|
233
|
+
"it": (" il ", " lo ", " gli ", " che ", " una ", " con ", " per "),
|
|
234
|
+
"da": (" den ", " det ", " og ", " ikke ", " jeg ", " med ", " til "),
|
|
235
|
+
"no": (" den ", " det ", " og ", " ikke ", " jeg ", " med ", " til "),
|
|
236
|
+
"is": (" og ", " ekki ", " ég ", " með ", " til ", " sem "),
|
|
237
|
+
"cs": (" ten ", " tato ", " a ", " že ", " není ", " jsem ", " pro "),
|
|
238
|
+
"sk": (" ten ", " táto ", " a ", " že ", " nie ", " som ", " pre "),
|
|
239
|
+
"pl": (" ten ", " ta ", " i ", " że ", " nie ", " jest ", " dla "),
|
|
240
|
+
"hu": (" az ", " egy ", " és ", " nem ", " van ", " hogy "),
|
|
241
|
+
"ro": (" un ", " o ", " și ", " nu ", " este ", " pentru "),
|
|
242
|
+
"tr": (" bu ", " bir ", " ve ", " değil ", " için ", " ile "),
|
|
243
|
+
"hr": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
244
|
+
"bs": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
245
|
+
"sr": (" ovaj ", " jedna ", " i ", " nije ", " za ", " sa "),
|
|
246
|
+
"sl": (" ta ", " ena ", " in ", " ni ", " za ", " je "),
|
|
247
|
+
"nl": (" de ", " het ", " een ", " en ", " niet ", " voor "),
|
|
248
|
+
"fi": (" tämä ", " yksi ", " ja ", " ei ", " on ", " varten "),
|
|
249
|
+
"et": (" see ", " üks ", " ja ", " ei ", " on ", " jaoks "),
|
|
250
|
+
"lv": (" tas ", " viena ", " un ", " nav ", " ir ", " par "),
|
|
251
|
+
"lt": (" tai ", " viena ", " ir ", " nėra ", " yra ", " už "),
|
|
252
|
+
"vi": (" một ", " và ", " không ", " là ", " cho ", " của "),
|
|
253
|
+
"ca": (" el ", " la ", " els ", " una ", " amb ", " per "),
|
|
254
|
+
"ga": (" an ", " na ", " agus ", " ní ", " tá ", " le "),
|
|
255
|
+
"mt": (" il ", " u ", " mhux ", " huwa ", " għal "),
|
|
256
|
+
"sq": (" ky ", " një ", " dhe ", " nuk ", " është ", " për "),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def mask_technical_text(text: str) -> str:
|
|
261
|
+
"""Mask common technical spans while retaining line positions."""
|
|
262
|
+
patterns = (
|
|
263
|
+
r"```.*?```",
|
|
264
|
+
r"`[^`\n]+`",
|
|
265
|
+
r"https?://\S+",
|
|
266
|
+
r"\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b",
|
|
267
|
+
)
|
|
268
|
+
masked = text
|
|
269
|
+
for pattern in patterns:
|
|
270
|
+
masked = re.sub(
|
|
271
|
+
pattern,
|
|
272
|
+
lambda match: "".join("\n" if char == "\n" else " " for char in match.group(0)),
|
|
273
|
+
masked,
|
|
274
|
+
flags=re.DOTALL,
|
|
275
|
+
)
|
|
276
|
+
return masked
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def detect_languages(text: str) -> tuple[str, ...]:
|
|
280
|
+
padded = f" {text.casefold()} "
|
|
281
|
+
scores = {
|
|
282
|
+
language: sum(padded.count(marker) for marker in markers)
|
|
283
|
+
for language, markers in LANGUAGE_MARKERS.items()
|
|
284
|
+
}
|
|
285
|
+
best = max(scores.values(), default=0)
|
|
286
|
+
if best == 0:
|
|
287
|
+
return ()
|
|
288
|
+
return tuple(language for language, score in scores.items() if score == best)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def iter_findings(text: str, languages: tuple[str, ...]):
|
|
292
|
+
for language in languages:
|
|
293
|
+
for pattern, suggestion in RULES[language]:
|
|
294
|
+
for match in re.finditer(pattern, text, flags=re.IGNORECASE):
|
|
295
|
+
line = text.count("\n", 0, match.start()) + 1
|
|
296
|
+
yield line, language, match.group(0), suggestion
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def check_file(path: Path, language: str) -> int:
|
|
300
|
+
try:
|
|
301
|
+
text = path.read_text(encoding="utf-8")
|
|
302
|
+
except UnicodeDecodeError:
|
|
303
|
+
print(f"{path}: not valid UTF-8", file=sys.stderr)
|
|
304
|
+
return 1
|
|
305
|
+
except OSError as error:
|
|
306
|
+
print(f"{path}: {error}", file=sys.stderr)
|
|
307
|
+
return 1
|
|
308
|
+
|
|
309
|
+
status = 0
|
|
310
|
+
if text != unicodedata.normalize("NFC", text):
|
|
311
|
+
print(f"{path}: Unicode text is not NFC-normalized")
|
|
312
|
+
status = 1
|
|
313
|
+
|
|
314
|
+
prose = mask_technical_text(text)
|
|
315
|
+
normalized_language = language.casefold().split("-", 1)[0].split("_", 1)[0]
|
|
316
|
+
if language == "auto":
|
|
317
|
+
languages = detect_languages(prose)
|
|
318
|
+
elif language == "all":
|
|
319
|
+
languages = tuple(RULES)
|
|
320
|
+
elif normalized_language not in RULES:
|
|
321
|
+
languages = ()
|
|
322
|
+
print(
|
|
323
|
+
f"{path}: no deterministic diacritics rules for {language!r}; "
|
|
324
|
+
"Unicode NFC passed, but the native-orthography review is still required."
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
languages = (normalized_language,)
|
|
328
|
+
for line, code, found, suggestion in iter_findings(prose, languages):
|
|
329
|
+
print(f"{path}:{line}: [{code}] {found!r} -> check {suggestion!r}")
|
|
330
|
+
status = 1
|
|
331
|
+
return status
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def main() -> int:
|
|
335
|
+
parser = argparse.ArgumentParser(
|
|
336
|
+
description="Flag common missing diacritics in UTF-8 prose files."
|
|
337
|
+
)
|
|
338
|
+
parser.add_argument("files", nargs="+", type=Path)
|
|
339
|
+
parser.add_argument(
|
|
340
|
+
"--language",
|
|
341
|
+
default="auto",
|
|
342
|
+
help=(
|
|
343
|
+
"target language or BCP 47 tag; unsupported languages still receive "
|
|
344
|
+
"Unicode checks and exit successfully when clean; default: auto"
|
|
345
|
+
),
|
|
346
|
+
)
|
|
347
|
+
args = parser.parse_args()
|
|
348
|
+
|
|
349
|
+
return max(check_file(path, args.language) for path in args.files)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
if __name__ == "__main__":
|
|
353
|
+
raise SystemExit(main())
|