miodek 1.1.0__tar.gz
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.
- miodek-1.1.0/.gitignore +33 -0
- miodek-1.1.0/LICENSE +21 -0
- miodek-1.1.0/PKG-INFO +437 -0
- miodek-1.1.0/README.md +419 -0
- miodek-1.1.0/deploy/README.md +121 -0
- miodek-1.1.0/hooks/hooks.json +15 -0
- miodek-1.1.0/hooks/miodek_write_gate.py +227 -0
- miodek-1.1.0/hooks/miodek_write_gate.schema.md +113 -0
- miodek-1.1.0/manieryzm-ai.md +435 -0
- miodek-1.1.0/pyproject.toml +53 -0
- miodek-1.1.0/src/miodek/__init__.py +2 -0
- miodek-1.1.0/src/miodek/adapter.py +540 -0
- miodek-1.1.0/src/miodek/ai_linter.py +727 -0
- miodek-1.1.0/src/miodek/ci_gate.py +209 -0
- miodek-1.1.0/src/miodek/cli.py +94 -0
- miodek-1.1.0/src/miodek/config.py +437 -0
- miodek-1.1.0/src/miodek/corrector.py +394 -0
- miodek-1.1.0/src/miodek/data/config.json +99 -0
- miodek-1.1.0/src/miodek/data/rules.json +338 -0
- miodek-1.1.0/src/miodek/decision_log.py +125 -0
- miodek-1.1.0/src/miodek/dictionary.py +92 -0
- miodek-1.1.0/src/miodek/engines.py +722 -0
- miodek-1.1.0/src/miodek/languagetool.py +184 -0
- miodek-1.1.0/src/miodek/languagetool_check.py +95 -0
- miodek-1.1.0/src/miodek/metrics.py +450 -0
- miodek-1.1.0/src/miodek/metrics_exporter.py +406 -0
- miodek-1.1.0/src/miodek/publish_gate.py +214 -0
- miodek-1.1.0/src/miodek/resources.py +43 -0
- miodek-1.1.0/src/miodek/runner.py +414 -0
- miodek-1.1.0/src/miodek/runpod_lifecycle.py +496 -0
- miodek-1.1.0/src/miodek/runpod_pod_up.py +153 -0
- miodek-1.1.0/tests/GROUND_TRUTH.md +81 -0
- miodek-1.1.0/tests/antithesis_eval.md +62 -0
- miodek-1.1.0/tests/baseline_en_cover_letter.md +14 -0
- miodek-1.1.0/tests/baseline_en_doc.md +7 -0
- miodek-1.1.0/tests/baseline_pl_intro.md +7 -0
- miodek-1.1.0/tests/baseline_pl_raport.md +11 -0
- miodek-1.1.0/tests/control_pl_clean.md +9 -0
- miodek-1.1.0/tests/run_tests.sh +216 -0
- miodek-1.1.0/tests/sentence_eval.md +26 -0
- miodek-1.1.0/tests/triad_eval.md +39 -0
- miodek-1.1.0/tools/build_dict.py +152 -0
- miodek-1.1.0/tools/check_build_dict.py +75 -0
- miodek-1.1.0/tools/check_ci_gate.py +164 -0
- miodek-1.1.0/tools/check_cli.py +86 -0
- miodek-1.1.0/tools/check_config.py +65 -0
- miodek-1.1.0/tools/check_corrector.py +210 -0
- miodek-1.1.0/tools/check_decision_log.py +78 -0
- miodek-1.1.0/tools/check_dictionary.py +74 -0
- miodek-1.1.0/tools/check_engines.py +338 -0
- miodek-1.1.0/tools/check_id_consistency.py +173 -0
- miodek-1.1.0/tools/check_languagetool.py +174 -0
- miodek-1.1.0/tools/check_metrics.py +238 -0
- miodek-1.1.0/tools/check_metrics_exporter.py +320 -0
- miodek-1.1.0/tools/check_publish_gate.py +146 -0
- miodek-1.1.0/tools/check_resources.py +109 -0
- miodek-1.1.0/tools/check_routing.py +194 -0
- miodek-1.1.0/tools/check_runner.py +200 -0
- miodek-1.1.0/tools/check_runpod_ephemeral.py +444 -0
- miodek-1.1.0/tools/check_runpod_lifecycle.py +341 -0
- miodek-1.1.0/tools/check_write_gate.py +177 -0
- miodek-1.1.0/tools/gen_doc_catalog.py +160 -0
- miodek-1.1.0/tools/gen_rules_json.py +83 -0
- miodek-1.1.0/tools/measure_antithesis.py +112 -0
- miodek-1.1.0/tools/measure_attribution.py +88 -0
- miodek-1.1.0/tools/measure_health.py +89 -0
- miodek-1.1.0/tools/measure_markdown.py +117 -0
- miodek-1.1.0/tools/measure_reduction.py +91 -0
- miodek-1.1.0/tools/measure_sentences.py +55 -0
- miodek-1.1.0/tools/measure_structural.py +143 -0
- miodek-1.1.0/tools/measure_triad.py +107 -0
- miodek-1.1.0/tools/metrics_exporter.py +20 -0
- miodek-1.1.0/tools/runpod_idle_watchdog.README.md +72 -0
- miodek-1.1.0/tools/runpod_idle_watchdog.sh +95 -0
miodek-1.1.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
env/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
|
|
12
|
+
# macOS
|
|
13
|
+
.DS_Store
|
|
14
|
+
|
|
15
|
+
# Logi i pliki tymczasowe
|
|
16
|
+
*.log
|
|
17
|
+
*.tmp
|
|
18
|
+
*.bak
|
|
19
|
+
|
|
20
|
+
# Log decyzji accept/reject (D4) — dane runtime, append-only; nie wersjonujemy
|
|
21
|
+
decisions.jsonl
|
|
22
|
+
|
|
23
|
+
# Edytory / IDE
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
*~
|
|
29
|
+
|
|
30
|
+
# Prywatna warstwa terminologii domenowej (NIE commitować)
|
|
31
|
+
RADA_JEZYKOWA.md
|
|
32
|
+
Chase Hughes Framework/
|
|
33
|
+
dist/
|
miodek-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tomasz Jakubowski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
miodek-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: miodek
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Audyt polszczyzny i eradykacja manieryzmu AI (AI-tells) w tekstach PL/EN — deterministyczny linter plus opcjonalny osąd modelu.
|
|
5
|
+
Project-URL: Homepage, https://github.com/researchanddeploy/sztuczny-miodek
|
|
6
|
+
Author: Tomasz Jakubowski
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai-detection,ai-tells,language-quality,linter,miodek,polish
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Natural Language :: Polish
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Provides-Extra: exporter
|
|
16
|
+
Provides-Extra: lt
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# sztuczny-miodek
|
|
20
|
+
|
|
21
|
+
Skill Claude Code do audytu polszczyzny i eradykacji manieryzmu AI (AI-tellów) w tekstach polskich i angielskich. Metodologia: pragmatyczny puryzm Jana Miodka.
|
|
22
|
+
|
|
23
|
+
## Co robi
|
|
24
|
+
|
|
25
|
+
Skill realizuje dwie misje:
|
|
26
|
+
|
|
27
|
+
1. **Wzorcowa polszczyzna (PL)** — pełny audyt tekstu polskiego wg dziesięciu priorytetów: cyrylica, kalki angielskie, fałszywi przyjaciele, anglicyzmy, sztuczne kolokacje, interpunkcja, styl i gramatyka, ortografia terminów łacińskich/greckich, manieryzm AI, typografia.
|
|
28
|
+
2. **Usuwanie AI-tellów (PL + EN)** — wykrywa i usuwa manieryzmy generatywne: puste signposty, triady (rule-of-three), antytezę „nie X — to Y", paralelizm, nadużycie myślnika, puste superlatywy, klisze redefinicyjne, emoji w nagłówkach. Dla raportów, syntez, listów motywacyjnych, CV i dokumentacji.
|
|
29
|
+
|
|
30
|
+
Skill działa jako **twarda bramka jakości** przed deklaracją „done". Obowiązuje semantyka **„PASS z uwagami = NIE PASS"**: każdy nierozwiązany flag blokuje werdykt PASS. Werdykt FAIL zapada przy cyrylicy w tekście PL (FAIL-HARD), markerze klasy `block` po przekroczeniu progu albo gęstości ważonej trafień powyżej 8 na 500 słów.
|
|
31
|
+
|
|
32
|
+
Zasada Miodka: poprawiaj to, co ma polski odpowiednik; zachowuj to, co przyjęło się w danej dziedzinie. Dla manieryzmu AI: zmieniaj teksturę prozy, zachowuj fakty i metryki.
|
|
33
|
+
|
|
34
|
+
## Kluczowe funkcje
|
|
35
|
+
|
|
36
|
+
**Deterministyczny linter `ai_linter.py` (Stage 1).** Pre-scan bez kosztu tokenów LLM. Generuje manifest podejrzeń w formacie `plik:linia:ID:KLASA:fragment` plus blok `== SUMMARY ==` z liczbą słów, trafień, maksymalną liczbą myślników na akapit, gęstością i werdyktem. Linter łapie szeroko (wysoki recall), świadomie dopuszcza false-positives w klasie `review`. Wyłącznie biblioteka standardowa Pythona 3, zero zależności pip.
|
|
37
|
+
|
|
38
|
+
**Osąd kontekstowy (Stage 2).** Dla każdego trafienia z manifestu: przeczytaj pełne zdanie, rozstrzygnij czy to realny manieryzm czy uzasadnienie kontekstowe, nanieś poprawkę, zweryfikuj kolokację po zamianie, wystaw werdykt. Wzorzec manifest → celowany Edit oszczędza około 60% tokenów względem czytania całych plików.
|
|
39
|
+
|
|
40
|
+
**Kanon 14 kategorii manieryzmu (`manieryzm-ai.md`).** Źródło prawdy taksonomii. Każde ID kategorii ma odpowiednik w linterze. Kategorie PL: PL-SIGN, PL-CLICHE, PL-RHET, PL-RHYTHM, PL-HEDGE, PL-TYPO. Kategorie EN: EN-DASH, EN-ANTI, EN-TRIAD, EN-PARA, EN-CLICHE, EN-HEDGE, EN-SUPER, EN-CONCL.
|
|
41
|
+
|
|
42
|
+
**Priorytety 1–10 (`SKILL.md`).** Ranking wykrywania błędów polszczyzny, od cyrylicy (Priorytet 1) przez sztuczne kolokacje (Priorytet 5, „najczęściej przeoczana kategoria") i interpunkcję (Priorytet 6) po typografię (Priorytet 10). Każdy priorytet ma tabelę wzorców z poprawkami i regexami skanowania.
|
|
43
|
+
|
|
44
|
+
## Instalacja
|
|
45
|
+
|
|
46
|
+
### Tryb A — bezpośredni clone (skill)
|
|
47
|
+
|
|
48
|
+
Najprostszy. Skill ląduje wprost w katalogu skilli Claude Code:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mkdir -p ~/.claude/skills
|
|
52
|
+
git clone https://github.com/researchanddeploy/sztuczny-miodek.git ~/.claude/skills/sztuczny-miodek
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Claude Code wykryje skill automatycznie na podstawie `SKILL.md`. Wywołanie: `/sztuczny-miodek`.
|
|
56
|
+
|
|
57
|
+
Aktualizacja: `cd ~/.claude/skills/sztuczny-miodek && git pull`.
|
|
58
|
+
|
|
59
|
+
### Tryb B — plugin przez marketplace
|
|
60
|
+
|
|
61
|
+
Wersjonowane aktualizacje i instalacja jedną komendą:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
/plugin marketplace add researchanddeploy/sztuczny-miodek
|
|
65
|
+
/plugin install sztuczny-miodek@sztuczny-miodek
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Wywołanie: `/sztuczny-miodek:sztuczny-miodek`. Aktualizacja: `/plugin update sztuczny-miodek@sztuczny-miodek`.
|
|
69
|
+
|
|
70
|
+
Oba tryby korzystają z tego samego `SKILL.md` w korzeniu repo. Katalog `.claude-plugin/` jest używany tylko w trybie B.
|
|
71
|
+
|
|
72
|
+
### Tryb C — CLI przez uvx
|
|
73
|
+
|
|
74
|
+
Narzędzie linii poleceń `miodek` można uruchomić bez ręcznej instalacji. Wymaga [uv](https://docs.astral.sh/uv/). Najprościej, wprost z PyPI:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uvx miodek lint --lang both ŚCIEŻKA_DO_PLIKU.md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`uvx` pobiera paczkę do cache i uruchamia ulotnie, bez instalowania niczego na stałe. Polecenie `miodek` to dispatcher z podkomendami `lint`, `correct`, `gate`, `lt`. Eksporter metryk Prometheus jest osobnym poleceniem `miodek-exporter`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uvx --from miodek miodek-exporter --help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Alternatywnie, wprost ze źródła git (np. dla gałęzi roboczej przed wydaniem na PyPI):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uvx --from git+https://github.com/hretheum/sztuczny-miodek@epic-a-reguly-jako-dane \
|
|
90
|
+
miodek lint --lang both ŚCIEŻKA_DO_PLIKU.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Rdzeń nie ma żadnych zależności (sama biblioteka standardowa). Warstwy opcjonalne wydzielają extras `[exporter]` i `[lt]`. Są one dziś puste, bo wszystkie komponenty działają na bibliotece standardowej, więc instalacja z extra (`uv tool install "miodek[exporter]"`) daje na razie ten sam wynik co bez niego. Powiązanie z homelabem (quadlet, systemd) zostaje poza paczką, deklaratywnie w repozytorium infrastruktury.
|
|
94
|
+
|
|
95
|
+
## Użycie
|
|
96
|
+
|
|
97
|
+
### W Claude Code
|
|
98
|
+
|
|
99
|
+
Skill uruchamia się przez jeden z wyzwalaczy w rozmowie:
|
|
100
|
+
|
|
101
|
+
- `sprawdź polszczyznę`
|
|
102
|
+
- `sztuczny miodek`
|
|
103
|
+
- `audyt językowy`
|
|
104
|
+
- `korekta tekstu`, `manieryzm AI`, `AI-tell`, `de-AI`, `usuń ślady AI`, `odAI-uj`
|
|
105
|
+
|
|
106
|
+
Claude przeprowadzi pełny protokół: pre-scan linterem, osąd kontekstowy, korektę, przebieg weryfikacyjny i werdykt PASS/FAIL.
|
|
107
|
+
|
|
108
|
+
### Linter z linii poleceń
|
|
109
|
+
|
|
110
|
+
Pre-scan można uruchomić samodzielnie podkomendą `lint`:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
miodek lint --lang both ŚCIEŻKA_DO_PLIKU.md
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Po instalacji przez uvx (Tryb C) zadziała też bez klonu repo. Z klonu repo, bez instalacji, ten sam linter uruchomisz przez `python3 -m miodek.ai_linter --lang both ŚCIEŻKA_DO_PLIKU.md` (z `PYTHONPATH=src`).
|
|
117
|
+
|
|
118
|
+
Flaga `--lang` przyjmuje `pl`, `en` lub `both`. Można podać kilka ścieżek naraz.
|
|
119
|
+
|
|
120
|
+
### Interpretacja manifestu i werdyktu
|
|
121
|
+
|
|
122
|
+
Manifest to jedna linia na trafienie:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
raport.md:42:PL-SIGN:review:Warto podkreślić, że
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Pola: ścieżka pliku, numer linii, ID kategorii, klasa (`review` lub `block`), dopasowany fragment. Klasa `review` wymaga osądu kontekstowego (możliwy false-positive). Klasa `block` to bloker werdyktu po przekroczeniu progu.
|
|
129
|
+
|
|
130
|
+
Blok `== SUMMARY ==` podaje werdykt na końcu:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
== SUMMARY ==
|
|
134
|
+
plik | słowa | trafienia | em-dash/akapit(max) | gęstość/500 | blokery | WERDYKT
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
PASS zapada tylko przy zerze blokerów i gęstości nie większej niż 8.
|
|
138
|
+
|
|
139
|
+
## Bramka write-time (hook na zapisie pliku)
|
|
140
|
+
|
|
141
|
+
Bramka write-time uruchamia linter przy każdym zapisie pliku prozy (`.md`/`.txt`) i zatrzymuje pracę WYŁĄCZNIE przy twardych blokerach. To inna bramka niż dwie pozostałe:
|
|
142
|
+
|
|
143
|
+
| Bramka | Kiedy | Co zatrzymuje |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| write-time (ta) | przy zapisie pliku prozy (hook lub pre-commit) | tylko twarde blokery (klasa `block` lub `FAIL-HARD`) |
|
|
146
|
+
| CI | przed mergem (`ai_linter.py`, exit 1) | pełny werdykt FAIL/FAIL-HARD, łapie też samą gęstość |
|
|
147
|
+
| przed publikacją | przed wysyłką | pełny werdykt plus osąd modelu (Stage 2) |
|
|
148
|
+
|
|
149
|
+
Niuans: sama wysoka gęstość trafień klasy `review` (czyli `verdict == FAIL` przy zerze blokerów) NIE zatrzymuje write-time, daje co najwyżej ostrzeżenie. Dzięki temu codzienna edycja nie jest przerywana z powodu gęstości, a twarde blokery (cyrylica, em-dash od trzech na akapit, emoji w nagłówku, serie antytez) zatrzymują pracę od razu.
|
|
150
|
+
|
|
151
|
+
### Włączenie hooka w pluginie (opt-in)
|
|
152
|
+
|
|
153
|
+
Plugin deklaruje hook `PostToolUse` na `Write|Edit|MultiEdit` w `hooks/hooks.json`, ale w trybie hooka jest on bierny do jawnego włączenia. Sama instalacja pluginu nie zaczyna nikomu blokować edycji. Aktywacja zmienną środowiskową:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export MIODEK_WRITE_GATE=1 # albo true / on / yes
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Przy twardym blokerze hook blokuje zapis dwoma kanałami naraz, dla odporności na wersję Claude Code: na stdout wypisuje JSON z polem `decision: block` i powodem (oraz lustrzane `hookSpecificOutput.permissionDecision: deny`), a równolegle kończy kodem 2 z tym samym powodem na stderr (kanon PostToolUse, stderr wraca do agenta). Powód to lista blokerów: ID markera, numer linii, fragment, żeby agent wiedział, co poprawić. Sama wysoka gęstość przechodzi (exit 0, brak wyjścia). Bez ustawionej zmiennej hook kończy natychmiast bez działania.
|
|
160
|
+
|
|
161
|
+
### Użycie jako git pre-commit
|
|
162
|
+
|
|
163
|
+
Skrypt działa też z linii poleceń (bez zmiennej środowiskowej, bo jawne wywołanie to świadoma decyzja): bierze ścieżki z argumentów, kończy kodem 1 przy twardym blokerze.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
hooks/miodek_write_gate.py plik.md notatka.txt
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
W roli `pre-commit` zatrzyma commit, gdy któryś plik prozy ma twardy bloker. Bezpieczeństwo: każda własna awaria bramki (brak pliku, błąd lintera, niepoprawny manifest) przepuszcza zapis (fail-open). Kontrakt pełny w `hooks/miodek_write_gate.schema.md`.
|
|
170
|
+
|
|
171
|
+
## Bramka CI na merge request (F2)
|
|
172
|
+
|
|
173
|
+
Druga bramka działa na pull requeście, nie przy zapisie. Workflow `.github/workflows/miodek-gate.yml` (GitHub Actions, trigger `pull_request`) liczy pliki prozy (`.md`/`.txt`) ZMIENIONE w PR względem bazy, woła na nich `ai_linter.py` i FAIL-uje check przy PEŁNYM werdykcie. Sterownik to `tools/ci_gate.py` (zero zależności, czysta biblioteka standardowa plus git).
|
|
174
|
+
|
|
175
|
+
Kluczowa różnica wobec write-time. Bramka CI zatrzymuje cały pełny werdykt, czyli `FAIL` oraz `FAIL-HARD`, a więc także samą gęstość ponad próg, nie tylko twarde blokery. To odwrotna polityka niż write-time, która gęstość przepuszcza. Z tabeli trzech bramek (sekcja wyżej) bierze wiersz „CI": pełny werdykt, łapie też samą gęstość. Trzecią bramkę, przed publikacją (z opcjonalnym osądem modelu Stage 2), opisuje sekcja „Bramka przed publikacją (F3)" niżej.
|
|
176
|
+
|
|
177
|
+
| Bramka | Polityka | Zakres |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| write-time (F1) | tylko twarde blokery, gęstość przechodzi | zapisywany plik, opt-in (`MIODEK_WRITE_GATE=1`) |
|
|
180
|
+
| CI na MR (F2) | pełny werdykt FAIL/FAIL-HARD, w tym sama gęstość | pliki prozy zmienione w PR względem bazy |
|
|
181
|
+
| przed publikacją (F3) | pełny werdykt Stage 1 plus opcjonalny osąd Stage 2 (opt-in, domyślnie wyłączony) | jawnie wskazane pliki do publikacji |
|
|
182
|
+
|
|
183
|
+
Zakres ograniczony do zmienionych plików jest celowy. Bramka FAIL-uje wyłącznie na prozie tkniętej w PR, nigdy na zastanym długu w plikach nieruszanych. Diff liczony jest symetrycznie od wspólnego przodka (`base...HEAD`, trzy kropki), kanon recenzji PR. Plik bez żadnej zmienionej prozy daje zielony check (exit 0), nie błąd.
|
|
184
|
+
|
|
185
|
+
Kody wyjścia `ci_gate.py` (równe kodom lintera):
|
|
186
|
+
|
|
187
|
+
| Exit | Znaczenie |
|
|
188
|
+
|---|---|
|
|
189
|
+
| 0 | brak zmienionych plików prozy LUB wszystkie PASS |
|
|
190
|
+
| 1 | którykolwiek zmieniony plik FAIL/FAIL-HARD (blokery lub gęstość) |
|
|
191
|
+
| 2 | błąd reguł/konfiguracji lintera lub błąd git/środowiska (bramka jakości nie zazielenia się po cichu) |
|
|
192
|
+
|
|
193
|
+
Użycie ręczne i w self-teście (jawne ścieżki):
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
python3 tools/ci_gate.py plik.md notatka.txt # exit 1 przy pełnym werdykcie FAIL
|
|
197
|
+
python3 tools/ci_gate.py --changed --base origin/main # tryb CI: diff względem bazy
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
W przeciwieństwie do write-time bramka CI nie jest fail-open: błąd reguł lub konfiguracji lintera kończy check niezerowo, bo to bramka jakości przed mergem. Self-test rdzenia: `tools/check_ci_gate.py` (wpięty do `tests/run_tests.sh`).
|
|
201
|
+
|
|
202
|
+
## Bramka przed publikacją (F3)
|
|
203
|
+
|
|
204
|
+
Trzecia i najsurowsza bramka to wymienny krok wołany PRZED publikacją prozy (wysyłka na Confluence, Notion lub stronę). Inny przepływ publikacji woła ją na jawnie wskazanych plikach „do publikacji", żeby zatrzymać tekst nieprzechodzący jakości. Sterownik to `tools/publish_gate.py` (zero zależności, czysta biblioteka standardowa). Bramka ma dwa poziomy.
|
|
205
|
+
|
|
206
|
+
Stage 1 działa zawsze. To pełny werdykt lintera na podanych plikach, ta sama polityka co F2, tylko na jawnych plikach zamiast na diffie. Werdykt `FAIL` albo `FAIL-HARD` (blokery lub gęstość ponad próg) zamyka publikację. Stage 1 reużywa `ci_gate.filter_prose` i `ci_gate.run_linter`, więc polityka pełnego werdyktu jest jednym kodem dla F2 i F3.
|
|
207
|
+
|
|
208
|
+
Stage 2 jest opcjonalny i włącza się flagą `--stage2`. Buduje manifest (`ai_linter.py --format json`), wybiera silnik osądu z `config.json` (sekcja `stage2`) przez `runner.build_engine_from_config` i woła `runner.run_stage2_managed` (osąd plus auto-offload poda RunPod dla silników zdalnych). Bramka jest surowa: jakikolwiek werdykt `rewrite` zamyka publikację. To realizuje zasadę „PASS z uwagami to NIE PASS", więc F3 jako jedyna może dołożyć osąd modelu i jest tym surowsza niż F2.
|
|
209
|
+
|
|
210
|
+
Czym F3 różni się od dwóch pozostałych bramek:
|
|
211
|
+
|
|
212
|
+
| Bramka | Polityka | Zakres | Model |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| write-time (F1) | tylko twarde blokery, gęstość przechodzi | zapisywany plik | nie |
|
|
215
|
+
| CI na MR (F2) | pełny werdykt Stage 1 | pliki prozy zmienione w PR | nie |
|
|
216
|
+
| przed publikacją (F3) | pełny werdykt Stage 1 plus opcjonalny osąd Stage 2 | jawnie wskazane pliki do publikacji | opcjonalnie (opt-in) |
|
|
217
|
+
|
|
218
|
+
Domyślnie F3 nie sięga sieci. Bez `--stage2` woła sam Stage 1, więc nie buduje silnika ani nie rusza modelu. Z `--stage2` na domyślnym configu (`stage2.engine` to `stub`) buduje atrapę `StubJudgeEngine`, czyli osąd offline bez sieci. Realny endpoint wymaga jawnej zmiany `config.json` na `openai` albo `ollama` (lub flagi `--engine`) oraz klucza w zmiennej środowiskowej. To znaczy: nawet z włączonym Stage 2 sieć rusza dopiero po świadomym wskazaniu żywego silnika.
|
|
219
|
+
|
|
220
|
+
Kody wyjścia `publish_gate.py`:
|
|
221
|
+
|
|
222
|
+
| Exit | Znaczenie |
|
|
223
|
+
|---|---|
|
|
224
|
+
| 0 | brak prozy LUB Stage 1 PASS i (Stage 2 wyłączony LUB gate PASS) |
|
|
225
|
+
| 1 | Stage 1 FAIL/FAIL-HARD (blokery lub gęstość) LUB Stage 2 gate FAIL (jakiś `rewrite`) |
|
|
226
|
+
| 2 | błąd reguł/konfiguracji lintera LUB błąd budowy silnika LUB niepoprawny manifest (bramka jakości nie zazielenia się po cichu) |
|
|
227
|
+
|
|
228
|
+
Wpięcie w przepływ publikacji i ręczne użycie:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
python3 tools/publish_gate.py artykul.md notatka.txt # sam Stage 1 (zero sieci)
|
|
232
|
+
python3 tools/publish_gate.py --stage2 artykul.md # plus osąd Stage 2 (silnik z config.json)
|
|
233
|
+
python3 tools/publish_gate.py --stage2 --engine ollama art.md # Stage 2 na wskazanym silniku
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Przepływ publikacji woła ten krok przed wysyłką i przerywa wysyłkę na kodzie niezerowym. Jak F2, F3 nie jest fail-open: błąd reguł albo budowy silnika kończy się niezerowo, bo to bramka jakości. Self-test rdzenia: `tools/check_publish_gate.py` (wpięty do `tests/run_tests.sh`, w całości offline na atrapie silnika).
|
|
237
|
+
|
|
238
|
+
## Testy
|
|
239
|
+
|
|
240
|
+
Katalog `tests/` zawiera zestaw regresyjny:
|
|
241
|
+
|
|
242
|
+
- `run_tests.sh` — uruchamia linter na plikach bazowych i porównuje wynik z oczekiwaniami.
|
|
243
|
+
- `GROUND_TRUTH.md` — oczekiwane werdykty i trafienia dla każdego pliku testowego (oracle).
|
|
244
|
+
- Pliki bazowe z manieryzmem (`baseline_pl_raport.md`, `baseline_pl_intro.md`, `baseline_en_doc.md`, `baseline_en_cover_letter.md`) oraz plik kontrolny czystego tekstu (`control_pl_clean.md`), na którym linter ma zwrócić PASS bez false-positives.
|
|
245
|
+
|
|
246
|
+
Uruchomienie:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
cd tests && ./run_tests.sh
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Ekonomia i obserwowalność (metryki z manifestu)
|
|
253
|
+
|
|
254
|
+
Linter zdejmuje pracę z modelu. Ile dokładnie, da się zmierzyć z samego manifestu, bez wołania LLM i bez kosztu tokenów. Granicą między etapami jest manifest, więc te metryki liczy się po stronie Stage 1. Moduł `metrics.py`, narzędzia w `tools/`.
|
|
255
|
+
|
|
256
|
+
Najpierw zbuduj manifest maszynowy, potem przepuść go przez narzędzie:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
miodek lint --format json *.md > manifest.json
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Współczynnik redukcji (`tools/measure_reduction.py`).** Udział treści wejścia, której model NIE tyka. Treść routowana do Stage 2 to akapity z trafieniem klasy `review`. Punkt odniesienia z praktyki autora po wprowadzeniu lintera: routed rzędu 4 do 5 procent.
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
python3 tools/measure_reduction.py --manifest manifest.json
|
|
266
|
+
python3 tools/measure_reduction.py --manifest manifest.json --max-routed 0.10 # exit 1 gdy za dużo idzie do modelu
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Atrybucja pracy (`tools/measure_attribution.py`).** Która reguła i która warstwa generuje najwięcej trafień. Raport diagnostyczny, bez progu.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python3 tools/measure_attribution.py --manifest manifest.json
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Zdrowie ekonomii (`tools/measure_health.py`).** Bierze współczynnik routed i porównuje z progiem alarmu z `config.json` (sekcja `economy`). Gdy linter przestaje odsiewać, routed rośnie i alarm zapala się, zanim wyląduje w rachunku za tokeny. Exit 1 przy ALARM, więc nadaje się na bramkę w CI.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
python3 tools/measure_health.py --manifest manifest.json
|
|
279
|
+
python3 tools/measure_health.py --manifest manifest.json --alarm 0.08 # nadpisz próg
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Eksporter metryk Prometheus i dashboard Grafany (KAN-219)
|
|
283
|
+
|
|
284
|
+
Te same metryki da się podać na dashboard. `tools/metrics_exporter.py` to eksporter HTTP zero-dep (biblioteka standardowa, `http.server`), który na ścieżce `/metrics` wystawia format tekstowy Prometheus. Na scrape buduje manifest (uruchamia linter na korpusie, z krótkim cache, żeby nie mielić go na każde zapytanie), liczy `metrics.py` i doczytuje log Stage 2. Stack Prometheus plus Grafana zakładamy gotowy; tu dostarczamy artefakty do wpięcia.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
MIODEK_CORPUS=. MIODEK_PORT=9112 python3 tools/metrics_exporter.py
|
|
288
|
+
curl -s localhost:9112/metrics | head
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Serie: `miodek_reduction_ratio`, `miodek_routed_ratio`, `miodek_total_words`, `miodek_routed_words`, `miodek_hits_total{rule,klasa}`, `miodek_health` (1 OK, 0 ALARM) plus `miodek_health_na`, `miodek_routed_ratio_alarm_threshold`, `miodek_stage2_runs_total{engine,verdict}`, oraz zdrowie samego eksportera (`miodek_exporter_up`, `miodek_scrape_duration_seconds`). Konfiguracja przez zmienne środowiskowe (`MIODEK_CORPUS`, `MIODEK_PORT`, `MIODEK_LOG`, `MIODEK_PROFILE`, `MIODEK_DICT`).
|
|
292
|
+
|
|
293
|
+
Uczciwość danych: E1, E2 i E4 są realne od zaraz (z manifestu Stage 1, zero kosztu modelu). Panel przebiegów Stage 2 wypełnia się dopiero, gdy realny silnik osądu nazbiera przebiegów; dziś osąd chodzi na atrapie, więc ta seria bywa pusta. To realny panel czekający na dane (żadna zaślepka).
|
|
294
|
+
|
|
295
|
+
Artefakty wdrożeniowe (jednostka systemd eksportera, fragment scrape do `prometheus.yml`, provider provisioningu i dashboard Grafany) leżą w `deploy/`. Runbook wdrożenia i pełen schemat metryk: `deploy/README.md` oraz `metrics-exporter.schema.md`.
|
|
296
|
+
|
|
297
|
+
**Runner Stage 2 (`runner.py`).** Spina linter z osądem modelu. Czyta manifest, wybiera segmenty `review` (tą samą funkcją co współczynnik redukcji), woła wymienialny silnik osądu i stosuje bramkę „PASS z uwagami to NIE PASS". Domyślny silnik to deterministyczna atrapa (bez sieci); realny silnik wpina się przez `engines.JudgeEngine` bez zmian w runnerze.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
python3 runner.py --manifest manifest.json # exit 1 gdy bramka FAIL
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Realny silnik osądu (`engines.py`).** Domyślnie runner woła atrapę (bez kosztu, bez sieci). Realny model serwowany po HTTP wpina się przez dwa adaptery zero-dep (biblioteka standardowa, `urllib`), wybierane sekcją `stage2` w `config.json`. Klucz API czytany jest wyłącznie ze zmiennej środowiskowej (`api_key_env`), nigdy z pliku.
|
|
304
|
+
|
|
305
|
+
Endpoint zgodny z OpenAI Chat Completions (OpenRouter, vLLM, RunPod):
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
"stage2": {
|
|
309
|
+
"engine": "openai",
|
|
310
|
+
"openai": { "base_url": "https://openrouter.ai/api/v1", "model": "speakleash/bielik-11b-v2.3-instruct",
|
|
311
|
+
"api_key_env": "OPENROUTER_API_KEY", "extra_headers": {} }
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
export OPENROUTER_API_KEY=... # sekret czytany z ENV
|
|
317
|
+
python3 runner.py --manifest manifest.json --engine openai
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Ollama (lokalna albo zdalna na RunPodzie) — `base_url` wskazuje host Ollamy:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
"stage2": { "engine": "ollama", "ollama": { "host": "http://localhost:11434", "model": "bielik" } }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
python3 runner.py --manifest manifest.json --engine ollama
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`--engine` na CLI nadpisuje wybór z configu; brak sekcji `stage2` znaczy atrapa (zero zmiany). Adaptery, prompt osądu i fail-safe parsowania opisuje `engines.schema.md`. Uwaga: realny smoke (Bielik) wymaga dostępnego endpointu, np. modelu serwowanego na RunPodzie; testy w repo działają w pełni offline na atrapie HTTP, bez wywołań sieci.
|
|
331
|
+
|
|
332
|
+
### Auto-offload poda RunPod po przebiegu Stage 2
|
|
333
|
+
|
|
334
|
+
Gdy model serwowany jest na podzie RunPod, pod może bić pod prąd GPU także między przebiegami osądu. Skill umie zgasić pod automatycznie po wsadzie Stage 2. Włącza się to podsekcją `lifecycle` w `stage2` (`config.json`); domyślnie `manage: false`, więc nic się nie dzieje (zero zmiany zachowania).
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
"stage2": {
|
|
338
|
+
"engine": "ollama",
|
|
339
|
+
"ollama": { "host": "https://<pod>.runpod.net", "model": "bielik" },
|
|
340
|
+
"lifecycle": {
|
|
341
|
+
"manage": true,
|
|
342
|
+
"pod_id": "<id-poda>",
|
|
343
|
+
"on_finish": "stop",
|
|
344
|
+
"idle_backstop_s": 600,
|
|
345
|
+
"api_key_env": "RUNPOD_API_KEY"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
export RUNPOD_API_KEY=... # sekret WYŁĄCZNIE z ENV, nigdy z pliku
|
|
352
|
+
python3 runner.py --manifest manifest.json --engine ollama
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Gdy `manage: true` i silnik jest zdalny (`ollama`/`openai`), runner owija przebieg w menedżer kontekstu, który gasi pod ZAWSZE po wsadzie. Odporność na padnięcie procesu zbudowano warstwowo: blok `finally` (gasi też przy wyjątku), handlery SIGINT/SIGTERM (gaszą przed zniknięciem procesu i przywracają poprzedni handler), oraz backstop NA PODZIE (`tools/runpod_idle_watchdog.sh`) gaszący pod po `idle_backstop_s` bezczynności na wypadek `kill -9`. Polityka `on_finish`: `stop` (domyślne, GPU gaśnie, model zostaje na dysku) albo `terminate` (trwała kasacja). Błąd gaszenia leci głośno na stderr, bo to bramka kosztowa. Klucz API czytany wyłącznie z ENV (`RUNPOD_API_KEY`). Szczegóły: `runpod-lifecycle.schema.md`; instalacja watchdoga na podzie: `tools/runpod_idle_watchdog.README.md`.
|
|
356
|
+
|
|
357
|
+
### Efemeryczny pod jednym krokiem: flaga `--runpod` (KAN-222)
|
|
358
|
+
|
|
359
|
+
Dotąd, żeby osądzić tekst realnym Bielikiem, trzeba było ręcznie: postawić pod (`tools/runpod_pod_up.py`), wpisać host do `config.json`, uruchomić przebieg i zgasić pod. Flaga `--runpod` składa to w jeden krok. Stawia efemeryczny pod z wolumenu sieciowego (model nie jest pobierany, jeśli już leży na wolumenie), uruchamia przebieg na realnym silniku (Ollama na podzie) i gasi pod automatycznie przez `terminate` po zakończeniu.
|
|
360
|
+
|
|
361
|
+
Parametry poda czyta podsekcja `stage2.runpod` z `config.json` (wolumen, data center, model, GPU, mount, obraz) z bezpiecznymi domyślnymi, więc flaga działa od ręki. Cykl to create, czekanie na Ollamę, zapewnienie modelu, przebieg, terminate. Teardown jest gwarantowany tą samą warstwową odpornością co auto-offload (blok `finally`, handlery sygnałów, backstop na podzie), z dodatkowym sprzątaniem osieroconego poda: gdy Ollama nie wstanie albo modelu nie da się zapewnić w fazie wejścia, już utworzony pod jest terminowany przed zgłoszeniem błędu. Klucz API wyłącznie z ENV (`RUNPOD_API_KEY`).
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
python3 runner.py --manifest manifest.json --runpod # osąd na świeżym efemerycznym Bieliku
|
|
365
|
+
python3 corrector.py --file artykul.md --runpod # korekta realnym Bielikiem, pod gaśnie sam
|
|
366
|
+
python3 tools/publish_gate.py --runpod artykul.md # --runpod sam włącza Stage 2 na podzie
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Flaga nadpisuje `--engine` i sekcję `lifecycle` (efemeryczny pod sam jest owijaczem przebiegu). Bez `--runpod` zachowanie jest bez zmian: domyślnie stub, zero sieci, zero kosztu. Szczegóły cyklu i testu offline: `runpod-lifecycle.schema.md` (sekcja „Tryb EFEMERYCZNY"); parametry poda: `config.schema.md` (podsekcja `stage2.runpod`).
|
|
370
|
+
|
|
371
|
+
### Routing silnika: lejek kosztowy (G3)
|
|
372
|
+
|
|
373
|
+
Stage 2 da się prowadzić dwoma silnikami naraz, żeby mocny model dotykał tylko trudnego marginesu. Silnik `routing` owija dwa silniki za tym samym interfejsem: `primary` (lekki, lokalny, na przykład Bielik przez Ollama) osądza każdy segment, a `appellate` (mocniejszy sędzia apelacyjny) jest wołany tylko po eskalacji. Domyślna polityka eskaluje, gdy primary chce ruszyć tekst (werdykt `rewrite`, czyli potencjalny fałszywy alarm) albo gdy segment jest trudny (liczba trafień review co najmniej `hard_hits_threshold`). Po eskalacji werdykt apelacji jest ostateczny, więc sędzia tnie fałszywe alarmy primary. Gdy primary daje `pass` na łatwym segmencie, appellate nie jest wołany. To obniża koszt rozumowy: mocny model dotyka tylko marginesu.
|
|
374
|
+
|
|
375
|
+
```json
|
|
376
|
+
"stage2": {
|
|
377
|
+
"engine": "routing",
|
|
378
|
+
"routing": {
|
|
379
|
+
"escalate_on_rewrite": true,
|
|
380
|
+
"hard_hits_threshold": 2,
|
|
381
|
+
"primary": { "engine": "ollama", "ollama": { "host": "http://localhost:11434", "model": "bielik" } },
|
|
382
|
+
"appellate": { "engine": "openai", "openai": { "base_url": "https://openrouter.ai/api/v1", "model": "..." } }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
`primary` i `appellate` to pod-konfiguracje o tym samym kształcie co sekcja `stage2`, budowane rekurencyjnie. Routing jest jednopoziomowy: nie wolno zagnieżdżać `engine: "routing"` w primary ani appellate. Kontrakt, polityka i ograniczenie wobec auto-offloadu poda są w `engines.schema.md` (sekcja „Routing silnika"). Self-test offline na atrapach: `tools/check_routing.py`.
|
|
388
|
+
|
|
389
|
+
Schematy: `metrics.schema.md` (redukcja, atrybucja, zdrowie), `runner.schema.md` (kontrakt orkiestracji), `engines.schema.md` (kontrakt realnych adapterów silnika), `runpod-lifecycle.schema.md` (auto-offload poda RunPod), `decision-log.schema.md` (wspólny strumień zdarzeń runnera i logu decyzji).
|
|
390
|
+
|
|
391
|
+
## Korektor: pętla audyt, poprawka, ponowny audyt (G2)
|
|
392
|
+
|
|
393
|
+
Dotąd skill tylko wytykał manieryzm. Korektor (`corrector.py`) zamyka pętlę nad linterem i osądem modelu, więc narzędzie samo doprowadza tekst do czysta. Jedna iteracja to audyt (Stage 1 plus osąd Stage 2), przepisanie spornych akapitów przez silnik, zapis zwrotny przez adapter i ponowny audyt na poprawionym tekście.
|
|
394
|
+
|
|
395
|
+
Pętla zatrzymuje się w jednym z trzech przypadków. Pierwszy to PASS, czyli bramka Stage 2 nie zwraca już segmentów do przepisania. Drugi to brak postępu, gdy żadne przepisanie nie zmieniło tekstu w danej iteracji (ochrona przed pętlą bez końca). Trzeci to wyczerpanie limitu iteracji (domyślnie 4, konfigurowalne). Zwracany jest finalny tekst plus raport: liczba iteracji, czy osiągnięto PASS, powód zatrzymania, ślad ile segmentów poprawiono w każdej iteracji.
|
|
396
|
+
|
|
397
|
+
Silnik jest wymienny przez ten sam interfejs co osąd Stage 2. Korektor woła go wyłącznie przez `judge` i `rewrite`. Domyślny silnik z configu (`stub`) daje deterministyczną atrapę offline (`StubRewriteEngine`), która neutralizuje wykryty wzorzec tak, by ponowny audyt go nie łapał, więc pętla zbiega bez sieci. Realny model (`openai`/`ollama`) wpina się bez zmiany pętli: dostaje osobny prompt po polsku „przepisz akapit usuwając manieryzm, zachowaj sens i rejestr".
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
python3 corrector.py --file artykul.md --engine ollama # korekta realnym modelem (sieć)
|
|
401
|
+
python3 corrector.py --file artykul.md --runpod # realny Bielik na efemerycznym podzie (KAN-222)
|
|
402
|
+
python3 corrector.py --file artykul.md --runpod --in-place # plus zapis poprawionego tekstu do pliku
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Bramka UX (KAN-222): korektor mieli tekst, więc na atrapie (stub) nic realnie nie poprawi i nie wolno mu udawać pracy. Bez `--runpod` i bez jawnie wskazanego realnego silnika (`stage2.engine` na `ollama`/`openai` w `config.json` albo `--engine ollama/openai`) korektor odmawia z kodem wyjścia 2 i kieruje: użyj `--runpod` (efemeryczny Bielik jednym krokiem) albo ustaw realny silnik. Stub zostaje trybem testowym, nie ścieżką użytkownika (furtka self-testów: zmienna `MIODEK_ALLOW_STUB_CORRECTOR=1`). Runner i bramka publikacji tej odmowy nie mają, bo osąd na atrapie bywa tam legalny jako diagnostyka. Odmowa dotyczy tylko korektora, który przepisuje tekst.
|
|
406
|
+
|
|
407
|
+
Zakres korektora to proza klasy review. Twarde blokery Stage 1 spoza prozy zostają nietknięte: emoji w nagłówku, cyrylica czy struktura nie-akapitowa to robota lintera i autora, bo korektor przepisuje wyłącznie sporne akapity. Dlatego dokument z czystą już prozą, ale z emoji w nagłówku, da PASS na bramce Stage 2 korektora i wciąż FAIL na pełnym werdykcie lintera. To podział celowy.
|
|
408
|
+
|
|
409
|
+
Jakość przepisania zależy od silnika. Atrapa offline (`stub`) neutralizuje wzorzec deterministycznie, więc pętla zbiega bez sieci i nadaje się do testów potoku, ale jej wynik tekstowy bywa pokaleczony (wycina dopasowany fragment). Naturalne przepisanie daje dopiero realny model za interfejsem, na przykład Bielik przez Ollama lub model z półki przez OpenRouter. Pełny smoke z żywym endpointem jest osobnym krokiem.
|
|
410
|
+
|
|
411
|
+
Dwa wzmocnienia chronią pętlę przed gadatliwym modelem. Parser odpowiedzi (`clean_rewrite_reply`) odcina meta-preambuły i komentarze, na przykład „Poprawiona wersja:” czy „Oto poprawiony akapit:”, a gdy model poda dwie wersje, bierze pierwszy zwarty akapit prozy. Zestaw fraz jest zamknięty i etykieta musi być krótka, więc legalne zdanie z dwukropkiem nie jest zjadane; pusta lub bezsensowna odpowiedź wciąż daje fallback na oryginał. Strażnik regresji po każdym przepisaniu robi tani audyt Stage 1 obu wersji akapitu i odrzuca poprawkę, która pogarsza, czyli ma więcej trafień lub dokłada bloker. Realny model bywa „leczy chorobę, dokłada gorączkę”: przepisując dorzuca nowy manieryzm. Strażnik akceptuje tylko poprawki nie pogarszające, dzięki czemu taki rozjazd kończy się brakiem postępu zamiast biegu do limitu iteracji. Zmiana neutralna przechodzi, więc realny postęp bez zbieżności nadal trafia na limit.
|
|
412
|
+
|
|
413
|
+
Finalny tekst leci na stdout, raport na stderr. Exit 0, gdy osiągnięto PASS, 1 w przeciwnym razie (gate-owalne). Kontrakt pętli, mapowanie segmentu na edycję i warunki zatrzymania opisuje `corrector.schema.md`; zdolność `rewrite` w silniku jest w `engines.schema.md`. Self-test offline: `tools/check_corrector.py` (wpięty do `tests/run_tests.sh`).
|
|
414
|
+
|
|
415
|
+
## LanguageTool: pełna korekta polszczyzny na żądanie (G4)
|
|
416
|
+
|
|
417
|
+
Rdzeń skilla jest lekki i celuje w manieryzm AI. Czasem przyda się pełna korekta polszczyzny: literówki, gramatyka, interpunkcja. Do tego jest opcjonalny dostawca na żądanie: klient LanguageTool (`languagetool.py`). To narzędzie pomocnicze poza bramką. Nie jest częścią Stage 1, Stage 2 ani żadnej bramki jakości i nie odpala się nigdzie automatycznie. Operator uruchamia je świadomie, gdy chce drugiej pary oczu nad polszczyzną.
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
python3 tools/languagetool_check.py --file artykul.md
|
|
421
|
+
python3 tools/languagetool_check.py --text "Mam pewien błont ortograficzny."
|
|
422
|
+
python3 tools/languagetool_check.py --file artykul.md --json
|
|
423
|
+
python3 tools/languagetool_check.py --text "..." --endpoint http://localhost:8081/v2/check
|
|
424
|
+
LANGUAGETOOL_ENDPOINT=http://localhost:8081/v2/check python3 tools/languagetool_check.py --file artykul.md
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Klient jest zero-dep (biblioteka standardowa, `urllib`) i odpytuje serwer LanguageTool po HTTP. Endpoint wybiera priorytet: flaga `--endpoint`, potem zmienna środowiskowa `LANGUAGETOOL_ENDPOINT`. Domyślnego endpointu NIE ma (KAN-225): bez wyboru klient zgłasza błąd, więc nie wysyła tekstu nigdzie domyślnie. Operator świadomie wskazuje jedną z dwóch dróg: lokalny serwer (np. `http://localhost:8081/v2/check`, tekst zostaje u niego) albo publiczne `api.languagetool.org` (wysyła tekst na cudze serwery). Wzór w `.env.example`. Zwraca strukturalne sugestie: pozycja, długość, komunikat, proponowane zamienniki, identyfikator reguły i kategorii. Parsowanie odpowiedzi jest odporne na brak pól. Realny serwer jest wołany wyłącznie przy faktycznym uruchomieniu; self-test (`tools/check_languagetool.py`) działa w pełni offline na atrapie transportu, bez wywołań sieci. Kontrakt: `languagetool.schema.md`.
|
|
428
|
+
|
|
429
|
+
## Opcjonalna warstwa terminologii domenowej
|
|
430
|
+
|
|
431
|
+
Skill obsługuje opcjonalny tryb z własnym słownikiem terminów branżowych. Jeśli posiadasz taki plik, terminy w nim zdefiniowane mają pierwszeństwo nad ogólnymi regułami dla swojej dziedziny. Bez słownika skill działa w trybie ogólnym: pełny audyt polszczyzny i manieryzmu AI. Słownik domenowy jest zewnętrzny i nie wchodzi w skład repozytorium.
|
|
432
|
+
|
|
433
|
+
## Atrybucja i licencja
|
|
434
|
+
|
|
435
|
+
Kod, taksonomia AI-tellów, reguły polszczyzny i układ skilla: licencja **MIT** (zob. plik `LICENSE`).
|
|
436
|
+
|
|
437
|
+
Metodologia opiera się na pracy **Jana Miodka** (pragmatyczny puryzm, „Ojczyzna polszczyzna"). To referencja i atrybucja, nie redystrybucja chronionej treści. Licencja MIT obejmuje wyłącznie materiały tego repozytorium; nie rozciąga się na cudzą własność intelektualną, do której repo się odwołuje.
|