v2.0 — sixteen tells, the grammar that was missing, and gates that can fail - #29
Merged
Conversation
…ered whole Until now nothing measured whether the checker finds what it claims. Tests were green while behaviour could break silently — the classic failure the director's golden-set rule exists to prevent. Thirteen cases across every domain: info-style, канцелярит, typography, UX copy, business letter, punctuation, numerals, and four neuroslop texts. AD-1, AD-3 and AD-4 had no text at all before this, so the loss of their carve-outs during any corpus edit would have gone unnoticed. Two controls matter more than the eleven cases: - **12-clean-control** must return zero findings. A false positive costs more than a miss: the user stops trusting the tool and turns it off. - **13-carveouts-control** is built entirely from constructions the rules EXPLICITLY exempt — a character's direct speech, «честно говоря» in conversational register, a genuine step-by-step walkthrough, informative «важно», «итак» as a real summative connective. It catches overreach rather than omission, and no automated test can see that class. Case 09 records a known gap deliberately: «Скажу честно» may NOT be found before the neuroslop rules land, because «честно говоря» is an AD-7 trigger that AD-7.5 then exempts for conversational register — and the assistant's own service register fell through that carve-out. This file is where the difference will show. Case 07 keeps deliberately borderline numeral forms: the set measures not only "does it find" but "does it invent". Runs are done by the model through /ru-text:ru-check and /ru-text:ru-score, with transcripts in the PR. There is no shell harness and there will not be one — simulating a deterministic run of an LLM check would measure the simulation. Red-test: n/a (test fixtures only, no production-code paths touched; selftest 45/45 and the no-loss gate green before and after)
…hat catches drift The three gates were three commands a person had to remember, written down in a handoff note and in the CI workflow, and nowhere a machine would read. That is also what kept the pre-push hook from ever running them: with no package.json and no pyproject, this repository looked to that hook like a project with no tests at all. tools/gates.sh runs what .github/workflows/gates.yml runs, in the same order — checker syntax, selftest, the paid-MCP contract, the pinned baseline and the no-loss gate. The sha256 tool is detected rather than assumed: macOS ships shasum and no sha256sum, and the Linux runner is the other way round. So is the shell that checks syntax — /bin/sh is bash 3.2 here and dash on the runner, so a bashism passed locally and failed in CI until this used dash -n where dash exists. Two copies of one sequence drift, so the selftest now fails when they do. It compares the checker scripts each file actually invokes (comments stripped, so naming one in prose does not count), the shape of each invocation — a checker whose failure is swallowed by `|| true` is not being run — and the baseline pin, file and hash, with comments stripped on BOTH sides, because stripping only one let a commented-out pin block in CI compare equal. Both files now walk tools/ with find rather than the tools/*.sh glob. A checker one directory down was invisible to the syntax gate and to the parity case at once. Also fixed pre-existing: fresh_copy cloned only references/ and SKILL.md, leaving out skills/ru-text/agents/*.yaml whose eight atoms the baseline counts — no case had noticed, because none had run the no-loss gate against a copy. And the NFC check spliced $ROOT into python source, so a checkout under a directory named with an apostrophe executed whatever followed it; ROOT now arrives through argv, as it already did in the folding check beside it. Red-test: tools/selftest.sh — «gates.sh passes on an untouched copy» падает без tools/gates.sh (нет файла); «gates.sh invokes exactly the checkers CI invokes» — там же
…e never controlling The set measured nothing about the seven neuroslop rules, because it predates them. Six cases carry them: declared sincerity and forced triples (14), hollowed mechanism and phantom attribution (15), a chat transcript published as an article (16), a text addressed to a search engine (17), additive pseudo-pairs (18), and — the one that matters most — a second carve-out control (19) built entirely from constructions the seven rules explicitly exempt: a character saying «скажу честно», a named and dated source, a three-item list the subject actually has, a pair whose poles carry different numbers, a transcript published as evidence for the author's own finding, an identifier repeated because a pronoun would be ambiguous. The two existing controls asserted zero findings and had never been run. Both failed on the first run — seven findings and six, and every one of them a real typographic defect in the fixture: neither text contained a single non-breaking space, though both claim to be written by the rules of the corpus. Fixed, and the count is now written into 12's expected.md together with the command that verifies it, because a NBSP and an ordinary space are indistinguishable by eye and that is exactly how the fixture rotted unnoticed. The new fixtures are typeset correctly for the same reason: the defect under test should be the only defect in the text. Red-test: n/a (набор — не автотест: прогон делает модель через /ru-text:ru-check, см. tools/golden/README.md; уликами идут транскрипты «до/после» в теле PR)
… the golden set
AD-10 declared sincerity · AD-11 mandatory tricolon · AD-12 hollowed mechanism · AD-13
phantom attribution · AD-14 chat transcript as the artifact · AD-15 search-engine addressee
· AD-16 additive pseudo-pair. Two of them — AD-14 and AD-15 — are charged to the document
rather than to a fragment, which is new to this addenda set and is why they are graded
High: the defect is the shape of the piece, and no local edit removes it.
The rules were not collected by introspection. Four evidence channels were read blind to
each other — what readers name themselves, what detectors flag, what platforms have already
written into their rules, and what the linguistic literature measures — and nine further
candidates were rejected on the record. Two of those rejections matter more than any
acceptance: even sentence rhythm and vocabulary poverty are what detectors systematically
mistake for machine text, and they are also what dry regulatory prose and non-native
Russian look like. A rule catching them would punish a genre and an accent.
AD-7 gains the trigger forms a model actually reaches for («скажу честно», «если честно»,
«не буду врать»), and AD-7.5 is narrowed: the register carve-out now protects a speaker
INSIDE the text — dialogue, quotation, a character — and not an author writing about their
own text in a conversational tone. The assistant register is a monologue written to sound
like speech, and it fell straight through the old wording. The boundary is stated once, at
AD-10.4, and governs both rules.
Measured on 19 golden texts, before and after. Three control texts returned zero findings
both times: seven new ways to fire on honest writing, and none of them fired. The run also
caught three defects in these rules that reading did not.
· A document-level charge replaced ten ordinary findings with three, and the text came
out looking cleaner than before the rule existed.
· Density replaced the per-instance findings with one line about density, costing the
writer the list of lines to fix.
· AD-16 absorbed a pleonasm that lived in Grammar, and the text scored 1.1 HIGHER than
before the rule against it existed.
All three are now rules of their own. The last is AD-16.8, and its principle is general: a
new rule must never make its target score better.
atom-map.tsv gains its first nine rows. Eight are the scoring.md lines whose scope this
change extends. The ninth is AD-7.5, and its rationale says in plain words that the meaning
was changed on purpose — NORMALISED is simply the only disposition the format has for a
line rewritten in place, and a row describing a change of meaning as a change of wording is
the failure that file exists to prevent.
Red-test: n/a (правила — данные, не поведение кода; golden-set намеренно не харнес, см. tools/golden/README.md. Улики — транскрипты «до/после» и покейсовое сравнение в теле PR; tools/gates.sh зелёный до и после)
«~1 044 rules» was a hand-maintained figure nobody could reproduce. It had drifted — the corpus is 2089 atoms now — and correcting it meant editing eleven files at once, which is the reason it drifted in the first place. It is replaced everywhere by «over 2,000 linguistic atoms» / «более 2 000 лингвистических атомов»: the unit this repository's own no-loss gate counts, reproducible with `tools/extract-atoms.sh skills/ru-text | wc -l`. Two things were chosen deliberately. The UNIT: «rules» would have been false at this number. Counting rule-bearing lines — numbered rules, AD sub-rules, table rows, list items — gives 1597, so «2,000 rules» is not a claim this corpus can support. An atom is what the gate tracks, it is defined in extract-atoms.sh, and the README says in the same breath what it consists of. The FLOOR: «over 2,000», not a figure. A floor survives the corpus growing, so adding a rule no longer obliges anyone to re-stamp eleven files — and the convention now says to raise it only when the count clears the next thousand, and to raise it everywhere in one commit. The per-file counts in scoring.md were checked against the corpus while here and are accurate: typography.md declares 96 rules and carries R1…R96 with no gaps. The change was proofread by the plugin's own check, which found four defects in it. Three were introduced by the edit itself: two em dashes with an ordinary space before them (R16/R44) and a paragraph pushed from one em dash to three (AD-1.1). One rewrite closes all three — the apposition moves into parentheses, which is exactly the replacement AD-1.2 prescribes for an aside. Also fixed pre-existing: three missing non-breaking spaces after single-letter words in notion/README.md (R30), and a digit separator there that was an ordinary space where R32 requires a non-breaking one. Left alone, on the record: list items in both READMEs carry no closing punctuation (editorial-grammar §F.4). That is a decision about a whole list, not about the lines this commit touched, and fixing one line would make the list less consistent, not more. Red-test: n/a (правка чисел и формулировок в документации; поведение кода не меняется, tools/gates.sh зелёный до и после)
…ption had lost Three numbers a person was keeping in step by hand, and getting wrong. The v1.10.1 gate found both READMEs advertising the previous release, then found .claude/CLAUDE.md doing the same; three days ago the corpus-size figure turned out to have drifted across eleven files at once. A number repeated in N places has N chances to be wrong and one script to be right. The checker holds three unlike things. The version is an EQUALITY across ten points — seven JSON fields in six manifests, plus three lines of prose a release has to remember — and it does not care what the version is, only that everything agrees. The description length is a BUDGET, and the file says out loud that 250 is ours and not a platform limit: the spec allows 1024. The trigger phrases are a PRESENCE test, and they are why this file matters. **The description had no Russian in it at all.** On hosts with no instruction file — the Claude apps, ChatGPT — that string is the entire trigger, so a user typing «вычитай» or «проверь текст» got nothing, and every gate stayed green while they did. It now leads with the six phrases people actually type, inside the first 100 characters so a truncating picker still shows them, and keeps «Also any Russian output», which carries the always-on semantics and is the easiest clause to lose in a rewrite. 210 characters against the 250 budget. The three atom-map rows record it as one deliberate change, not three. Six selftest cases prove the checker can fail, each for its stated reason: one manifest out of step, a version line missing from the RU README, a lost trigger phrase, phrases pushed past the head where a truncating picker stops showing them, and a description over budget. Two defects in the checker itself, both found by running it rather than reading it. Under `LC_ALL=C` — which the file sets, for the same reason every tool here does — `wc -m` counts BYTES, so a 210-character description measured 255 and failed its own budget. And the RU version line is written with a non-breaking space before the em dash, because this repository obeys its own R16/R44, so a pattern with an ordinary space reported the line as missing. Also fixed pre-existing: fresh_copy did not clone the manifests or the READMEs, so the new checker failed inside every gates.sh case for a reason those cases are not about. Red-test: tools/selftest.sh — все шесть случаев секции «selftest: check-version.sh» падают без tools/check-version.sh (нет файла); «a lost trigger phrase is caught» падал и на старом описании, в котором русских фраз не было вовсе
A `/simplify` pass over the finished module, behaviour-preserving, re-verified against nine golden cases. The largest finding was not redundancy but a false claim about redundancy. **«The same boundary is written once, at AD-10.4»** — while AD-7.5 restated that boundary in full, four-item list and identical example included. The fix is the claim, not the duplication: the year-long miss of «скажу честно» happened precisely because the boundary was NOT operational at AD-7's own site, and a rule read on its own has to decide on its own. AD-10.4 is now named the canonical statement, and AD-7.5 keeps its working copy, said out loud rather than denied. Three retellings of one motive folded into a reference. AD-11.3 carried the same metaphor as the governing section in a different suit — «a loss of coverage dressed as a summary» beside «wearing the clothes of a summary» — and AD-15.6 retold one incident with the same numbers the section already gives. Each keeps its local imperative, because an anchor jump lands inside a rule and not above it; only the second telling of the reasoning is gone. Five run dates left the normative text. `**Acknowledged:**` is where this corpus keeps provenance, AD-1…AD-9 keep theirs there, and a date is not an input to any trigger — it is read by a model that has to decide about a text, and it will read as debris in a year. The three dates that pin a paraphrased external document stay: without them a claim about someone else's rules is unverifiable. Also: the section titled «Two rules» had three bolded rules, the third being a qualifier of the first; the table of contents listed two sections after AD-16 that stand before AD-1; and two carve-outs restated their rule's own test rather than pointing at it. **Measured consequence, stated rather than buried.** Case 17 rose from 5.4 to 6.7 — and from 5.5 before these rules existed. The cause is a correction: AD-15.6 used to charge the repeated noun phrase as tautology in Grammar, and `editorial-grammar.md` §E defines tautology as ADJACENT cognates, so that charge was never one the corpus carried. Removing it returned Grammar to 9.5, which is what an impeccably grammatical SEO stub deserves by the rubric as written. The defect itself is charged, once, by AD-15 in Reader precision — where the score is already 3.0 and has nowhere further to fall. That is the arithmetic hole the Fable judge predicted, now with a live number against it. Not tuned here: moving a score by editing a rule until the number looks right is the failure the five orthogonal dimensions exist to prevent. It goes to the director as a decision, with the measurement attached. Re-verified: nine cases re-run against the edited corpus. Both carve-out controls stayed at zero findings. No rule-level finding was lost; the two fragments that moved are info-style judgement calls on cases whose scores did not change. Red-test: n/a (поведение-сохраняющий проход по формулировкам; корпус — данные, tools/gates.sh зелёный до и после, re-verify девяти случаев в теле сообщения)
… right and the word false A document charged with AD-14 or AD-15 is never labelled «Эталонный» or «Хороший», whatever the weighted sum returns. The number is reported as computed; only the word a reader carries away is constrained, with one line naming the rule that constrained it. This is not the non-compensatory cap that was deliberately declined, and the distinction is the point. A cap keyed to a soft dimension would put a hard ceiling on a score behind a judgement call a reader could dispute. A label floor overrides nothing in the five dimensions; it refuses to summarise a document as good when the whole genre is the defect. Measured, not supposed. A search-engine stub — grammatical, typographically clean, useless — scored 6.7 on Typography 9.5 and Grammar 9.5: the two hard dimensions carry 0.35 of the weight, and the entire harm sits in Reader precision, already at 3.0 with nowhere left to fall. A flawless chat transcript is the same shape and worse: with nothing wrong in any sentence it reaches 9.2 — «Эталонный» — for a genre platforms remove on sight. No rule about words can lower either one, and that was worth establishing before reaching for this. Driving Clarity on the stub from 7.0 all the way to 3.0 moves the total from 6.7 to 5.7: still «Средний», the label unchanged. On the transcript it does nothing at all, because a well-written transcript has no defect in its words. The hole is in the summary, so the repair belongs in the summary. Red-test: n/a (правило рубрики — данные, не поведение кода; tools/gates.sh зелёный до и после, арифметика обоих сценариев посчитана в теле сообщения)
…tead of gutting it Two things the plan asked for, one of them measured before it was believed. **The v3.0 backlog.** Everything ru-text does today is a refusal — stop-words, канцелярит, anti-patterns, sixteen tells of machine prose — and it is good at it. What a living Russian sentence *does* is the other half, and it is not in the corpus. `docs/roadmap-v3-formative.md` opens that direction and, more usefully, records why it cannot be a patch to v2.0: a refusal is cheap to check and cheap to be wrong about, while a formative rule has no list to match, depends on what the sentence is doing, and tells a competent writer their good sentence is wrong when it misfires. The scoring model does not fit it either — five dimensions built to subtract have nowhere to put a well-chosen verb, and adding points for virtue invites exactly the optimisation those dimensions exist to prevent. **§H.2 keeps its thirty-six rules.** The plan said to deduplicate it into a pointer at the §B catalogue. Measured with the same parser check-frozen.sh uses: 42 entries here, 92 there, and exactly **6** in both. The step was written on the assumption that this section mostly repeats the catalogue. It does not, and following it literally would have deleted thirty-six rules that live nowhere else — which is precisely the silent loss the no-loss gate was built to catch, arriving through the front door as a planned refactor. So the six get what the corpus already does for overlapping rules: a cross-reference naming them, §B named as their canonical home, and one charge per fragment. Nothing is deleted. Red-test: n/a (документация и перекрёстная ссылка; поведение кода не меняется, tools/gates.sh зелёный до и после)
…itself must be true The catalogue size lives in seven files. The last time it moved, four of them went stale and nobody noticed until a release gate looked. This checker measures the size and the category count from `info-style.md` with the same parser check-frozen.sh uses, and compares them with every place that states them. The first version scanned for claims with patterns like «(\d+) записей» and reported twenty-two false ones on its first run: `anti-patterns.md` counts its own sections in the same words. Deciding which number in a sentence is a claim about the catalogue is not a problem a shell script solves, and a checker that cries wolf is one people disable. So the consumer list is EXPLICIT, and a second check makes the list self-maintaining: the current value must not appear anywhere outside it. Move the catalogue to 93 and a stale «92» is still a «92» — the guard names the file and the list gets a row. A fixed list alone has exactly the failure mode that put four consumers out of date; a fixed list with a completeness guard does not. Two false-positive classes are excluded by hand, each for a stated reason. CHANGELOG.md and `.github/` record what the numbers WERE and must keep saying 97 where 97 was once wrong. And a letter before the digits means an identifier rather than a count — typography.md carries rule R92, and a guard that reads it as a stale claim is the crying wolf again. Three defects in the checker, all found by running it. `case "$f" in *.md|*.json) ;; esac` inside a command substitution does not parse under bash 3.2 — which is /bin/sh on macOS — and `sh -n` does not catch it; the error surfaces only when the line runs. `printf "$tmpl"` reads a template beginning with «- » as an option, and two claims are markdown list items. And `grep -qF "$want"` does the same with the same two. Also fixed pre-existing: fresh_copy produced a directory rather than a repository and left out notion/, so every gates.sh case failed inside the new checker for a reason none of them is about. Red-test: tools/selftest.sh — четыре случая секции «selftest: check-dogfood.sh» падают без tools/check-dogfood.sh (нет файла); «a consumer left behind is caught» падает и на предыдущей версии чекера, где списка потребителей не было
…ammar the corpus lacked
A coverage sweep asked where the corpus is thin and answered somewhere unexpected: not in
the formative half, but in basic literacy. Government had **no rules at all** — §D covers
agreement and nothing covered which case a preposition takes. Neither did a gerund phrase
whose doer is not the sentence's, nor the homophone pairs a spellchecker cannot decide.
The cost is not what it looks like, and the arbiter was right to reframe it. ru-text is
executed by a language model, and a model flags «согласно приказа» with or without a rule.
What was missing is the SUPPORT for the verdict: a finding with no rule ID, no golden
anchor, no reproducible ground in the one dimension that calls itself objective.
Narrow on purpose. Seven families passed the sweep; three ship. §I is a **closed list**,
because the open form — checking every case after every preposition — is a detector of
non-native Russian, and building one is forbidden here: it would punish an accent rather
than an error. Participial commas, declension and aspect went to `docs/roadmap-v3-grammar.md`
with the reason each was deferred; the wandering pronoun was rejected outright, because the
channels split on it and that split is itself the evidence that the trigger is a judgement.
The gate found fifteen defects in this package, and the five worst were in the norms
themselves — the failure that would have had the tool confidently breaking correct text:
· «Скучать по вам» was listed as the error and «по вас» as the fix. It is the other way
round — and the pair is variable, so it does not belong in a closed list at all. Gone,
with §I.1.3 now saying why a variable pair can never be here.
· «Превосходство перед» is dated-but-attested, not wrong. Gone.
· J.1.1 tested «the grammatical subject of the main clause». Rozental's criterion is the
PRODUCER OF THE ACTION, and a one-member definite-personal sentence has no grammatical
subject at all — so «Открыв файл, проверяем кодировку» was being called wrong.
· «В течении» was said to be only a river. It is also the course of an illness, of time,
and a movement in art or politics.
· «В виду» was said to exist only inside «иметь в виду». It also means within sight —
«эскадра шла в виду берега», where the "fix" would read as «because of the shore».
Also added from the same gate: carve-outs for gerunds that became adverbs («молча»,
«не спеша»), for the inclusive «давайте» and the definite-personal, for a gerund sharing its
doer with an infinitive, and — the one carried from AD-10.4 — for a quotation, where the
error belongs to the speaker and charging the author for someone else's grammar is worse
than missing it.
Measured across three golden cases. §I: 8 of 8. §J and §K: all targets. The carve-out
control returned **zero** twice — first on the constructions the rules illustrate, then on a
harder text built from the four false positives the gate had found, none of which the first
version of that control could catch. It failed once in between, and correctly: the carve-out
examples were written with «следует», «нужно» and «можно», which are catalogued stop-words.
A control that tests one rule has to be clean under all the others, or zero is unreachable
and the criterion means nothing — the same lesson the non-breaking spaces taught in cases 12
and 13.
Red-test: n/a (правила — данные, не поведение кода; golden-set намеренно не харнес. Улики — три прогона в теле сообщения: контроль карв-аутов 0 → 2 → 0, где двойка была настоящим дефектом фикстуры)
…and proven by unpacking
Hosts disagree about what a skill is, so a release ships two archives of the same corpus:
`ru-text-skills.zip` with ru-text/, ru-check/ and ru-score/ at the top, for hosts that
install a directory of skills; `ru-text-skill.zip` with one folder, for hosts that take
exactly one and would treat the other two as strays. Neither carries the tools, the golden
set or the manifests — nobody installing a skill needs the machinery that verifies it.
Three rules the script exists to enforce. It reads `git ls-files`, never the working tree,
so an asset cannot ship a scratch file that is invisible once zipped. It refuses to build
from a dirty checkout, because an artefact whose contents match no commit makes the version
inside it a claim about code that does not exist. And it proves itself by unpacking: every
build extracts into a clean directory and diffs the result against the source before the
archive is allowed to exist, because a zip that lists the right names can still unpack
wrong.
`--check` builds, verifies and deletes. That mode runs in gates.sh and in CI, where it
catches the failure with no symptom — a renamed or dropped reference file leaves the skill
installing, activating, and answering from whatever corpus it still has.
Two defects, both found by the selftest rather than by reading. The reference-completeness
check compared `git ls-files` with a tree copied from `git ls-files`, so removing a file
from the index moved both numbers together and the equality stayed true — a tautology
wearing the clothes of a check. It now also asserts the floor the paid-MCP contract fixes
at ten. And the case that was supposed to prove a dirty tree is refused had been running in
`--check` mode all along, because `${4:---check}` substitutes its default for an empty
fourth argument as well as a missing one; the mode is named now, so the mistake cannot
repeat silently.
Red-test: tools/selftest.sh — четыре случая секции «selftest: build-release.sh» падают без tools/build-release.sh (нет файла); «a reference file missing from the asset is caught» и «a dirty tree refuses a real build» падали и на первой версии скрипта — по тавтологичной проверке и по подстановке умолчания
All 22 texts, blind to the expected findings, against the corpus as it will ship. The table is in tools/golden/README.md; the transcripts are outside the repository. The number that matters: **every carve-out control returned zero**. Sixteen tells of machine prose and three grammar sections, and none of them fired on an honest fragment. Three of the four had to be repaired first, and every time the text was at fault rather than the rules. Cases 12 and 13 asserted zero findings while containing no non-breaking space at all. Case 22 wrote its carve-out examples with «следует», «нужно» and «можно», which are catalogued stop-words. And this run found the last one: «Переписка приведена целиком» in case 19 is passive voice with the agent named in the previous sentence — a real anti-patterns finding, in the control text, not in the carve-outs it was testing. The lesson is now written into the set's README, because it will recur: a text that tests one rule has to be clean under all the others, or zero is unreachable and «the control is at zero» means nothing. Red-test: n/a (фикстура и запись измерения; поведение кода не меняется, tools/gates.sh зелёный до и после. Улика — перепрогон случая 19: 1 находка → 0 после снятия пассива)
The «Домены» table quoted seven per-domain rule counts. They sum to 1035 — the retired «~1 044 rules» figure, split up. v1.10.1 recounted them by hand and corrected four; one release later three were wrong again (57 comma traps against a stated 56, 59 button labels against a stated 58) and four of the seven appear nowhere in the corpus at all. The table now names the reference file for each section, so a reader who wants a number opens the file and counts. Two sections that had no row — the sixteen tells and the scoring rubric — now have one. Two more claims were measured and found false: SKILL.md holds 583 words, not the 587 advertised since v1.9.0, and `references/` holds ten files, not nine. The note about the community marketplace was false in the direction that costs users the release. Since #19 both READMEs have said the pin «advances automatically with up to a day's lag». Measured today: the pin is at v1.8.0, three releases behind. The nightly sweep opens at most thirty PRs per run against 2283 entries and walks them roughly alphabetically; ru-text sits at index 1653 and has not been bumped once in the last 300 commits to that manifest. It is not on the freeze list and it passes `claude plugins validate` — this is starvation, not rejection. Both READMEs now describe the mechanism and point at `npx skills add` or a source install for anyone who needs the current version. `.claude/CLAUDE.md` carried the same false claim as a convention; corrected there with the measurement, so the next release does not repeat it. check-version.sh gains a fourth section so the size claim cannot drift again. It compares numbers rather than the sentence, because Russian inflects the noun with the numeral and a literal-string check would go red on a correct line the day the count crossed a declension boundary. A review of that section found seven defects, all of them now closed and each proven by the mutation that used to survive: - it read the first «SKILL.md:» line and ignored a second, so two lines both wrong passed. §1 of the same file has lived by «exactly once» since v1.10.1; §4 did not inherit it. - README.en.md was in the argv but pinned by nothing: dropping it left the selftest green. Same for the line count — only the word count was asserted. Three selftest cases now bind each hand separately. - the thousands-separator normalisation was a provable no-op, because the digit regex never matched a separator to begin with. It read «1 083 слова» as the pair (1, 083) and failed a correct README. Verified on a 1 103-word file. - an empty measurement reported ok. A python3 stub exiting 0 in silence now fails closed. - the comment claimed parity with `wc -w` while using python's split(), which treats U+00A0 as whitespace where byte-wise wc does not — on a file carrying the non-breaking spaces R16/R44 require, the two disagree. - the diagnostic joined findings with «; » and split on the same, tearing a quoted README line in half mid-sentence. The new prose was read by the ru-text check itself, against the corpus files rather than a summary of them: eleven findings, all applied. One was a Critical — a missing non-breaking space before an em dash that I had just written into the file. Every em dash in the Russian README is now verified by codepoint. Selftest 70 → 75 cases. Red-test: tools/selftest.sh — «a stale SKILL.md word count in the RU README is caught» and its three siblings fail with «check-version PASSED on corrupted input» until §4 of check-version.sh exists and binds each hand separately.
…gainst memory All 81 install-and-update claims in both READMEs were run against the vendors' live documentation and the live state of each registry. 31 were wrong. The instructions had been written once and never re-checked; the platforms moved underneath them. - **Claude Desktop never took the CLI's commands.** `/plugin` is terminal-only; in the app the path is the + button → Plugins → Add plugin. And «one install works in CLI, Desktop, VS Code, JetBrains and Web» was false on Web — a user-scope install does not reach a cloud session, which needs `enabledPlugins` in the repository's `.claude/settings.json`. WSL sessions have no plugins at all. - **Codex needs a marketplace configured before `/plugins` shows anything**, and a new session before the bundled skills load. Neither step was in the README. - **ru-text is not in the Cursor marketplace** the README told users to search. The catalogue lists 216 plugins and ru-text is not among them; `/marketplace/ru-text` is a 404. The manual copy, previously the fallback, is now the instruction. - **Both Antigravity paths moved.** Global is `~/.gemini/config/skills/`, read by all three Antigravity products; per-project is `.agents/skills/` — plural — with the old singular still accepted for compatibility. - **OpenClaw refs are owner-qualified**, `@talkstream/ru-text`, as ClawHub's own page for this skill instructs. - **`npx skills add` does less than the README implied.** It installs three skills, not one. Without `-y` it opens an interactive picker, so pasted into a script it installs nothing. It writes `.windsurf/skills`, `.junie/skills` and `.continue/skills` only when those directories already exist, so on a fresh project it leaves those three platforms unserved — their sections now lead with the manual copy. It is project-scoped, so a `-g` install has to be updated with `-g` or the update reports success over a stale copy. `.codex-plugin/plugin.json` had `logo` at the top level, which the Codex manifest schema does not define; it belongs inside `interface`. Moved. The Notion template inventory was missing the AI-text-tells section it grew in June, and quoted a word count that had drifted from 1 454 to 1 865. The section is now listed and the count is gone rather than re-stamped — the same reasoning that removed the domain table's per-domain figures. Two entries in the convention file were wrong in ways that would have cost the release: - it claimed the corpus floor is stated «in eleven files». Nine files state it, and `.codex-plugin/plugin.json` on main was still advertising «~1,044 rules» while the sentence claimed the sweep was complete. The hand-maintained count is replaced by the grep that reproduces it — the same defect the sentence itself warns about. - it claimed `clawhub skill publish` REQUIRES `--version` and fails without it. On clawhub 0.23.1 the guard fires only when the flag is present and malformed; omitted, it defaults to the registry's next patch. A dry run without it printed «Would publish ru-text@1.10.2» and exited 0, so publishing 2.0.0 without `--version 2.0.0` would have silently shipped a patch. Codex self-serve publishing, recorded as unavailable, now exists: the portal explicitly accepts a skills-only plugin, which is what this is. Noted for a directorial decision, not acted on. The new Russian prose was read by the ru-text check itself, against the corpus files rather than a summary: nineteen findings, all applied. Five were Critical typography — three literal tildes that leaked in from my own NBSP placeholder and two missing non-breaking spaces after a single-letter preposition. Both READMEs are now verified by codepoint: zero stray tildes, zero em dashes preceded by an ordinary space. Red-test: n/a (documentation and one manifest field; no behaviour path in the diff — the pre-patch selftest is green against the post-patch tree, 75 passed / 0 failed, and gates.sh passes end to end).
…d of the prose The «Быстрый старт» section was thirteen platform-by-platform recipes — 195 of the README's 411 lines, and the half that had just been found 31 claims wrong. It is now one sentence to hand to an AI agent, because the agent knows where its own platform keeps skills better than an instruction written a year ago does. The call was tested rather than argued. Three fresh agents, sandboxed, denied the local checkout, each role-framed as a different platform's agent and given nothing but that sentence: two installed correctly, and one — Windsurf — found a global path this README never documented and refused a flag it could not verify. The third put Codex's copy in `~/.codex/skills` on the strength of a December-2025 blog post, while OpenAI's current documentation says user skills live in `$HOME/.agents/skills`. All three read this repository as their first source. That is why the per-platform material moved to INSTALL.md instead of being deleted: the prose's audience has changed from humans to agents, and an agent that finds nothing here takes its answer from a stale blog. INSTALL.md is organised around what the audit turned up rather than by platform: `~/.agents/skills/` is read by Codex, Cursor, Windsurf AND GitHub Copilot, so it is one shared path plus exceptions. It also keeps the four things the one-liner cannot carry — the Claude Desktop and Notion click-paths no agent can drive; the negative facts trial cannot discover; the per-platform directories; and the update story a one-shot install does not have. The gate moved with it. Reading instructions against vendor documentation gates the prose, not the outcome, costs 57 agents a pass, and — measured this session — has a half-life of about one release: Windsurf's docs are now on the Devin domain, Codex's on learn.chatgpt.com, and both Antigravity paths moved. `tools/probe-install.sh` gates the outcome instead: it builds a sandbox, prints the prompt, and afterwards judges the disk against `tools/install-paths.tsv`, which carries each path with the vendor URL and the date it was read. Four assertions, and the second is the one that earns its keep — a copy landing anywhere the platform does NOT read. An agent usually writes several copies, so a probe that only looked for a hit would have called the `~/.codex/skills` failure a pass. It does not run the agent: a shell cannot start another vendor's agent, and one that pretended to would be a gate testing itself. Both READMEs are now about half their former length, and every path they used to state lives in one machine-readable file instead of in twenty-six prose sentences across two languages. Selftest 75 → 81. Red-test: tools/selftest.sh — the five probe-install cases fail with «probe said PASS, wanted FAIL» (empty sandbox, undocumented path, stale bytes, truncated corpus) and «probe said FAIL, wanted PASS» (correct install) until probe-install.sh implements each assertion; verified by building each sandbox shape by hand before the script existed.
… install Both defects came out of the first live run, which is the argument for running a gate for real rather than reasoning about it. **A symlinked install is the vendor's documented shape, not a suspicious one.** The script said, in a comment, that «a symlinked skill is a different thing from an installed one and must not read as a pass». OpenAI's page says the opposite in as many words: «Codex supports symlinked skill folders and follows the symlink target when scanning these locations» (learn.chatgpt.com/docs/build-skills.md). The probe agent linked deliberately, so that a pull on its clone updates the install — a better install than a copy, and the gate would have failed it. `find` now follows symlinks. **A clone is an intermediate, not a rogue install.** With symlinks followed, the source tree the agent cloned was reported as four copies landing where Codex does not look. A SKILL.md inside a git working tree is now excluded, by walking up to a `.git` and stopping at the sandbox root so a `.git` above the sandbox cannot excuse everything below it. **And the stray check compared directory names where it had to compare physical paths.** An agent that links `~/.agents/skills/ru-text` at a copy under `~/src` has ONE install reachable two ways; the name comparison called the target a second copy. Both sides now resolve through `pwd -P` before comparing, which collapses them into the one install they are. The selftest case that caught this used a non-git source directory on purpose — the live run's clone had a `.git` and so was excused by the previous fix, hiding the deeper defect underneath it. The byte-mismatch line now names the commit the agent actually fetched. Its commonest cause is not a fork but probing before the work reaches the default branch, and a diagnostic that does not say so sends the reader looking for a supply-chain problem. Two selftest cases, one per defect: a symlinked install must pass, and a source clone must not be reported. Selftest 81 → 83. Red-test: tools/selftest.sh — «a symlinked install at the documented path passes» fails with «probe said FAIL, wanted PASS» against the previous `find -type f`, and «the source clone is not reported as a rogue install» fails the same way once symlinks are followed but clones are not excluded. Both reproduced against the live sandbox before the fix.
A background security review reproduced it in one command. The exclusion that stops the
source clone being reported as a rogue install accepted ANY git working tree above the file:
mkdir ~/.codex/skills/../.git
and an install at the stale-blog path — the exact failure this probe exists to catch — was
excused, and the run printed PASS. A gate a directory disarms is worse than no gate, because
it reports success.
The second attempt was still wrong, and wrong without any decoy. It required the tree to hold
`skills/ru-text/SKILL.md`, which `~/.codex/skills/ru-text` has by construction: every
platform whose skills directory is literally named `skills` would have excused itself.
A clone is now a `.git` WITH `.claude-plugin/plugin.json` beside it — the plugin manifest,
which lives at this repository's root and has no reason to exist inside an installed skill.
Everything under such a root is source, `tools/testdata/` included; an earlier `skills/`-only
restriction reported this repository's own test fixture as a rogue install. Cloning into a
stale path needs no special case: the repository has no root SKILL.md, so such a clone
produces nothing loadable and check 1 fails for the plain reason.
Three cases added, each pinning one of the three wrong answers: the decoy `.git` must not
excuse anything, a fixture outside `skills/` must not make a genuine clone look rogue, and
the clone fixture now carries the manifest that makes it a clone. Selftest 83 → 85.
Red-test: tools/selftest.sh — «a decoy .git does not excuse an install at an undocumented
path» reports «probe said PASS, wanted FAIL» against both earlier rules, reproduced by hand
in the sandbox before the fix.
… of it The director asked whether the current Codex had been checked or an old picture copied. An old picture had been copied. Verified today against the live 0.144.0 binary and the current docs, which moved onto a ChatGPT host — developers.openai.com/codex/* now 308-redirects to learn.chatgpt.com, a signal that was in front of me twice and not followed through. - **Plugins are not Codex-only.** «Plugins are available with ChatGPT Work on the web and with ChatGPT Work or Codex in the ChatGPT desktop app. Codex CLI also has a plugin browser» (learn.chatgpt.com/docs/plugins). ChatGPT Work users install from the Work switcher → Plugins, an audience these docs never addressed. Both INSTALL files now name it, and the section is «Codex и ChatGPT» rather than «Codex CLI». - **There is a non-interactive install we never documented**: `codex plugin add <PLUGIN>@<MARKETPLACE>`, from `codex plugin add --help` on the live binary. The docs told users to open a browser and search, which cannot be scripted or copy-pasted. - **Codex ships `claude-plugins-official` as a default marketplace**, alongside openai-primary-runtime, openai-bundled and openai-curated — `codex plugin marketplace list` on a stock install. ru-text is not in it: we are in `claude-plugins-community`. So promotion to Anthropic's official tier would reach Codex users with no setup at all, which raises the value of that ask above the one channel it was filed under. - **The version note was stale by twenty-seven minors**: «pre-1.0 (v0.118.0)» against 0.144.0 installed and 0.145.0 on npm. Replaced with the measurement and the instruction to re-check rather than trust the line. Also removed the last hand-maintained corpus number to survive this release: «Плагин подгружает 217 UX-правил» / «loads 217 UX rules», in the use-case section, missed when the same class of figure was cleared out of the domain table. A sweep of both READMEs and both INSTALL files finds no other; what remains is publication years and external sources' own counts. The Cursor catalogue size lost its number too — «216 плагинов» is a third-party figure that drifts weekly, and the claim that matters is that ru-text is not in it. Red-test: n/a (documentation and one convention file; no behaviour path in the diff — the selftest is green before and after at 85 passed / 0 failed, and gates.sh passes end to end).
An independent review read the cut-down README as a first-time user rather than as its author, and found three places where shortening had left a claim the product does not honour. - **The front door told the reader to do something that fails.** «Начните писать на русском — плагин подключится сам» sat directly under the install prompt, but skills load at session start: Codex says so in as many words («start a new session before using its bundled skills»), and the skill-reading platforms behave the same. The reader's very next action was the one action guaranteed not to work. Both READMEs now say to start a new session. - **Notion was listed among the platforms the one-line prompt covers.** It is not: Notion installs by copying a template page and clicking through Use with AI, on a Business or Enterprise plan, and there is no agent to hand a sentence to. Moved out of that list and pointed at INSTALL. - **«более 2 000 атомов … по 10 справочным файлам» against a table listing nine.** Both true — the tenth is `sources.md`, which is attribution rather than a rule section — but a reader who counts the table catches a contradiction. Now «в девяти справочниках и файле источников». The neuroslop scenario went from nine bullets to five. The review's diagnosis matches what the director smelled: bullets carrying a quotable specimen («Отличный вопрос!», «скажу честно») read as a scenario, and the three that carry only a term read as an index of rules. The five with specimens stay; the rest fold into the sentence after them, which already existed. «Обновление» loses the paragraph explaining the marketplace pin, which INSTALL.md states in full — the README keeps the fact and the link. Two Codex facts the same review turned up, both from the current docs: - a skill copied into `~/.agents/skills/` is read by THREE surfaces — the CLI, the ChatGPT desktop app in Codex mode, and the IDE extension. Plugins are not available in the IDE extension but standalone skills are, so the file copy reaches further than the plugin path. Stated in both INSTALL files, because it is the strongest argument for the shared directory. - `codex plugin marketplace upgrade` exists and was missing from the update block. The skills-CLI update line now repeats that it installs three skills: a reader arriving at «Обновление» through the README anchor skips the section where that was said. Red-test: n/a (documentation only; no behaviour path in the diff — selftest 85 passed / 0 failed before and after, gates.sh green, and every relative link and heading anchor across the four files resolves).
README.md says, in its own words, that «каждое тире, кавычка и пробел в этом документе соответствуют правилам самого плагина». Nothing enforced it. It was checked by remembering to ask a model, and in one afternoon it failed twice: a literal `~` reached the file from a placeholder for a non-breaking space, and a missing NBSP before an em dash shipped inside prose written to explain non-breaking spaces. This checks the mechanical subset that needs no judgement — R16/R44, R30, R1/R2, the ellipsis, and a stray literal `~`, which is not a corpus rule but is the defect that actually shipped. Both are reproduced as fixtures and both are caught. Scope is an explicit two-file list, and the alternative was measured before being rejected: the same patterns over the corpus files and the Notion template report 53 «violations» that are all correct content — the wrong-hand column of a «плохо → хорошо» table, and stop-word entries like «в настоящее время» that are dictionary keys rather than sentences. In a file that teaches typography by showing it broken, breakage is the point. Every exclusion is named in the header with its reason. Inline code and link targets are blanked before matching, keeping offsets: `~/.agents/skills` is a home directory, not a mangled space. Also fixed, pre-existing: the selftest's `fresh_copy` did not carry INSTALL.md or INSTALL.en.md, so the new checker failed inside every gates.sh case for a reason no case was about. That list grows whenever a checker learns to read a new file, and the comment above it already said so. Red-test: tools/check-typography.sh — against a copy of README.md with the two defects reinstated (a `~` before an em dash at line 41, a plain space after «в» at line 75) it exits 1 and names both by rule and line; against the tree it exits 0.
The checker committed one commit ago as the systemic answer to «did you remember to run the check» did not run the check it claimed to. A model reading the same two files found eight R30 violations it had passed, and named the reason: both halves of the pattern were narrower than the rule. - `PREP` held lowercase letters only, so «В» and «У» — where a Russian sentence most often starts one — were never tested at all. Six of the eight. - The follower class demanded a Cyrillic letter or a digit next, so «в [README]», «и **`.agents/skills/`**» and «и /ru-score» escaped. A preposition is no less stranded for being followed by a bracket, an asterisk or a slash. Both widened; the eight defects fixed in README.md and INSTALL.md. Four selftest cases, and the shape of two of them is the point. The first pair written failed the requirement they exist for: «В WSL-сессиях» and «В [README]» each combine a capital preposition WITH a non-Cyrillic follower, so either mutation killed both and neither hand was guarded — the same fixture-to-the-scenario defect that produced the gap in the first place. Reshaped to isolate one half each, and verified by mutation: restoring the lowercase-only set now breaks exactly one case, restoring the narrow follower class exactly the other. Also fixed, found by the same review: README.md said «Плагин ловит 16 примет машинного текста. Девять видны прямо во фрагменте» above a list of five. The list was cut from nine to five in 6072e51 and only the second number was updated. And two numerals moved to words per §G.1 — «ещё семь платформ», «на семь тематических блоков». Selftest 85 → 90. Red-test: tools/selftest.sh — «a capital single-letter preposition is caught» and «a preposition before a non-letter is caught» both report «check-typography PASSED on corrupted input» against the narrower pattern; each survives the mutation aimed at the other half.
Written from the ground up after a four-lens panel — a technology-adoption psychologist, a
UX writer, a Russian editor in the information-style tradition and a DX technical writer —
read the old page, and a fact-check found seven false claims in it.
206 lines → 128 1813 words → 1061
671 words before the reader's first possible action → 53
5 tables → 0 7 badges → 3 15 sections → 11
What the panel said, and what it changed:
- **«Благодарности» stood before the page said what the tool does** — 70 words, naming
nobody (verified: no personal name appears in that block), ending in «пожалуйста, купите
их книги». A debt with no address. Moved down to «Источники и благодарности», where the
names are, and the imperative is gone.
- **The best thing the product has — one sentence that delegates the whole install to the
agent — sat under two-thirds of a page.** It is now the first thing after the promise.
- **There was no example.** In 206 lines the page never showed what the tool does to a
sentence. «Как это выглядит» is a before/after pair taken from the golden set, with the
three changes named.
- **«Границы»**, the psychologist's central ask: what the skill will not do, and how to
switch it off. A tool that edits a person's writing owes them the edges.
Claims removed as unprovable: «гарантирует предсказуемо высокое качество от любого
Claude-агента», «Проверено на [12 платформ]» (three agents were probed; one failed),
«глубокое понимание», «полное покрытие», «канонических источников», and «7 тематических
блоков» — nothing in the repository defines the seven.
The one claim that mattered more than the others is now a mechanism rather than a hope. The
draft said «Чужой текст не трогается. Цитаты, код — вне зоны», and a judge found nothing in
the corpus that enforced it: the same class as the guarantee the panel had just killed.
SKILL.md now carries the rule — «Someone else's words stay theirs: quoted material, code
blocks and third-party text inside the user's document are reproduced as-is» — so the README
describes behaviour instead of promising it.
Two facts stopped having two sources. The prose version line is gone from both READMEs: the
badge renders it live from the releases API, so `check-version` drops from ten points to
eight and the class of drift it was catching there no longer exists. The size of SKILL.md is
gone too — a manifest's word count answers «did the author follow the spec», which is not a
question a reader has. §4 of check-version stays, self-activating: state the size again and
it is checked; state it wrongly and the gate fails. Its four selftest cases now build the
claim in the sandbox before corrupting it, since testing a checker against a claim the
product no longer makes would be testing nothing.
check-typography gains R32/R53, digit grouping — and it gains it because it was needed. The
one number left with a group separator, «более 2 000 атомов», was set with an ordinary space
and walked through the green gate; a judge found it by reading. Third time this checker has
been narrower than the rule it names, and the third case is pinned like the others.
Red-test: tools/selftest.sh — «an ordinary space between digit groups is caught» reports
«check-typography PASSED on corrupted input» before R32/R53 exists; the four size cases
report «check-version PASSED on corrupted input» if §4 is removed rather than made optional.
…omising The judge verified the eight round-one fixes (all landed, one with a residue) and found two things worth another pass. **«У двенадцати примет из шестнадцати выписано, где приём законен» was wrong, and wrong in the way this release spent its whole day removing.** Counted by substance rather than by the phrase «carve-out», thirteen of the sixteen carry a legitimacy clause, not twelve — AD-2.3 («parcellation is a legitimate author's device and is not penalized») and AD-5.2 («Exception — normative technical terminology») say it in their own words. A reader could not have reconstructed the counting rule that produces twelve. The numeral is gone rather than corrected: the sentence now says where a device is legitimate and that the carve-out is read before the finding is raised, which is the part a reader can use, and there is no number left to drift. **«Чужой текст не трогается… вне зоны» promised more than the rule gives.** SKILL.md reserves the right to REPORT an issue in third-party text and forbids only rewriting it; «вне зоны» said it would not even be mentioned. That is the round-one defect one step along — a sentence wider than its mechanism — and the English file had it right while the Russian did not. Both now say the same thing: a remark, perhaps; an edit, never. Three more from the same review: - The page named **zero** supported platforms. A reader asking «does this work with Cursor?» had to leave for INSTALL.md. One line, twelve names, in both files. - `/ru-text:ru-check` and `/ru-text:ru-score` are Claude Code's slash syntax, and the README's primary install path — copying the skill directory — gives other platforms no such command. Scoring still works there by asking in words; now the page says so. - README.en.md had had no independent reading. Five calques fixed («where its own platform keeps skills is something the agent knows better than», «the passive named who acts», «a floor rather than a figure» — a floor IS a figure), and the Russian specimens it quotes now carry English glosses, including a translation of the after-example. A page whose examples are opaque to its reader is not a mirror of anything. Red-test: n/a (documentation only; no behaviour path in the diff — selftest 90 passed / 0 failed before and after, all four checkers green, every relative link and anchor resolves in both languages).
The project's own check was run over the finished page against the v2.0 corpus, and blocked it on four findings. All four are fixed. - **R31 — initials were not bound to their surnames.** «А. Э. Мильчин», «Л. К. Чельцова», «М. Ильяхов», «Л. Сарычева», «Д. Э. Розенталя»: ten ordinary spaces where the rule asks for non-breaking ones, so a line could break between a person's initials and their name in the section that thanks them. - **AD-1.1 — four em dashes in the legal paragraph**, against the rule's own limit of one or two. The aside became parentheses, which is what AD-1.2 prescribes; the two dashes left are required by the punctuation reference and cannot go. - **§G.1 — «двенадцати», «шестнадцать», «десяти» spelled out**, where the rule puts ten and above in digits. Four places. «Ещё девять», «Ещё две» and «четыре вещи» stay as words: they are below ten, which is the same rule. - **The page credited `ux-writing.md` with a section it does not have.** «онбординг» / «onboarding» appears nowhere in that file — verified by grep, its sections run A to M with no onboarding among them. Replaced with «диалоги подтверждения» / «confirmation dialogs», which section I does cover. This is the class of self-description that `check-dogfood.sh` catches for numbers and nothing catches for words. check-typography gains R31, and the pattern in these misses is worth naming: every one has been a rule the corpus has and the checker did not — R30 too narrow, then digit grouping absent, now initials — never a rule it got wrong. The checker grows one rule per finding, and each is pinned by a case that fails without it. Selftest 90 → 91. One thing the judge raised and I am not deciding: §G.1 can be read as not meant to reach emblematic counters like «шестнадцать примет», in which case the corpus wants the clarifying sentence rather than the page wanting the edit. Today the law is the corpus as written and the page now obeys it; changing the rule instead is the director's call, not mine. Red-test: tools/selftest.sh — «an ordinary space after an initial is caught» reports «check-typography PASSED on corrupted input» until R31 is implemented.
… is for The director asked to raise the emphasis. It was the fourth bullet inside a list of four — the capability the product is most needed for, filed behind buttons and emails. Now it is a section of its own with its own before/after pair, taken from the golden set: five devices in four sentences — an assistant-register reply, declared sincerity, self-praise for brevity, a hollow opener and a manufactured antithesis — and not one of them carrying a fact. The «after» is what is left when they go, which is the point the list of tells cannot make on its own. The lead changed with it. It used to promise typography and reference files; it now says the thing a reader actually wants: sixteen tells of machine writing, caught before a reader notices them. The bureaucratic before/after keeps its place and takes the title it always deserved, «Канцелярит»; the remaining scenarios are «Что он поправит ещё». Red-test: n/a (documentation only; selftest 91 passed / 0 failed and all four checkers green before and after).
A liveness pass over the rewritten page — the director's reading was «местами совсем машинно» — found three defects in the newest prose, two of them factual. **The opener refuted the product.** «Текст, написанный моделью, узнаётся не по теме и не по словарю. Его выдают приёмы: …» is AD-6's trigger shape: nobody had proposed catching a model by its vocabulary, so the negated pole has no antecedent, and removing it loses no fact — AD-6.1(3) passes. A section about false antithesis, opening with a false antithesis, in the README of the tool that detects it. Rewritten to earn the contrast by naming the antecedent out loud: vocabulary-based detection does exist, and it does not work. **«пять приёмов в четырёх предложениях» — the fragment has five sentences.** On a page that invites the reader to count, the arithmetic was wrong. **The bullet list and the «Ушли» list disagreed by one.** The example carries declared sincerity («Скажу честно»), not phantom attribution, and the fifth bullet named the one that is not there. Also removed: both comma-plus-dash junctions in our own prose, at the director's request. The construction is normative — Rozental §64, «сначала ставится запятая, а затем тире» — but it is rare in living professional writing, and a model reaches it by applying two correct rules where a person would have restructured the sentence. Neither Lebedev's Ководство §143 nor anything found from Ilyakhov forbids it, and the research note says so plainly rather than inventing a citation: `~/.claude/plans/ru-text-v2/comma-dash-research.md`, with a draft AD-17 and the four things that must happen before it becomes a rule. The corpus has no rule producing the construction — it is emergent, which is why nothing caught it. Red-test: n/a (documentation only; selftest 91 passed / 0 failed and all five checkers green before and after).
The branch declared 1.10.1, the number main already holds, while carrying sixteen tells instead of nine and three new grammar sections. Installed locally it would sit in the plugin cache under a version string that lies, and Claude Code keys cache invalidation on exactly that string: the working install would keep serving the old corpus while claiming to be current. All eight version points move to 2.0.0-rc.1; 2.0.0 itself is reserved for the release after PR #29 merges. The bump found a defect in the checker it exists to satisfy. The prose pattern in check-version.sh captured a bare X.Y.Z triple, read «2.0.0-rc.1» as «2.0.0», and failed a correct tree as an eight-way disagreement. The capture now admits a semver pre-release suffix; a real disagreement — prose 2.0.0 against manifests 2.0.0-rc.1 — still fails, verified on a mutated copy against both the old pattern and the new. Two selftest repairs ride along. The «one manifest out of step» case corrupted versions with the class [0-9.]*, which matches nothing on a pre-release tree — the corruption no-oped, and the case stayed green only because check-version was failing for the unrelated truncation reason. The class is now [^"]*. And a new case pins the regex fix itself — all eight points agreeing on 9.9.9-rc.1 must pass — because the in-tree coverage evaporates the day the release strips the suffix from the tree. Selftest 91 -> 92. Red-test: tools/selftest.sh — «a pre-release version agreeing at every point passes» reports «8 version points disagree» under the previous prose pattern.
Both `ru-check` and `ru-score` carried `disable-model-invocation: true`. The commit that added it gave a real reason — «so the model does not fire a 15–20k-token check on any Russian text it happens to see» — but it solved the cost problem by making the tool unreachable, and that broke the whole point for anyone whose agent does the typing. The director's discipline says «ru-text = ВСЕГДА СКИЛЛ… прогонять ИНСТРУМЕНТОМ», and with the flag set the agent literally could not: the skill was hidden from its pool. It had been invoked 531 times before that. The flag is gone from both. In its place, two things. **The descriptions now carry the words a Russian speaker actually says.** They were terse English — «Run a comprehensive Russian text quality check» — which is not a trigger for anything. They now name «вычитай», «прогони ru-text», «проверь текст по ru-text», «оцени текст», and say outright that a project gate naming ru-text is a reason to run. The always-on skill has declared its triggers this way since v1; the two commands never did, because as commands they never needed to. **And the check now has two depths, chosen by nobody.** The measurement that justifies this: the corpus is ~86k tokens, and `ru-check` was loading four files unconditionally — ~50.8k per run, 69% of it `addenda.md` alone. An explicit request always runs the full corpus: a person asking to «вычитать» gets exactly what they asked for, as before. A self-initiated run — the agent checking its own Russian out of discipline — starts with triage: the always-on skill, the «Neuroslop index» section of `addenda.md` (1 832 chars), and §B of `info-style.md` (3 694 chars, and byte-frozen by `frozen.sha256`, so it is machine-parseable by contract). Roughly 3k tokens against 51k. The load-bearing safety rule is one sentence: **triage may report only what a single line decides.** Typography and confirmed catalog hits, yes. A neuroslop tell, never — every AD rule carries carve-outs that live only in the full file, and «не X, а Y» with a real antecedent is ordinary prose. A tell seen in triage escalates the run; it does not become a finding. Escalation is also triggered by five confirmed findings, or by text bound for a reader, and it is silent. No new skill, no new reference file, no new frontmatter. A fourth skill would be a fourth name competing for the router at the exact point where misrouting is what we are fixing, and an eleventh reference file would break `check-frozen`'s `files=10` contract with the paid MCP. The two assets the cheap tier needs already existed for other reasons; this only stops ignoring them. `tools/golden/README.md` pins the golden set to full depth — it is an explicit invocation, so triage does not exist there, and a set measuring a narrower check than its expectations were written against would be worthless. Both READMEs gain a section saying all of this to users in plain words, with the numbers. People deserve to know their context budget is being spent deliberately. Red-test: n/a (behaviour of a skill body and its frontmatter; no code path in the diff — all five checkers green and selftest 92/0 before and after. The golden set is the instrument that would measure the behaviour change, and it runs at full depth by construction.)
… version string Committing the two-depth change and running `claude plugins update` reported «ru-text is already at the latest version (2.0.0-rc.1)», and the cache still held the old skill: the flag was there, the new section was not, `cmp` against the repository differed. This is the exact failure this branch has been warning users about in prose — Claude Code keys cache invalidation on the version string, so content that changes under an unchanged number is content nobody receives. It caught the author of the warning, one commit after writing it, which is worth recording rather than quietly fixing. The rule it implies, for every future local install: a change to skill content that people are meant to receive needs a version bump, not just a commit. The pre-release suffix exists so this costs nothing. Red-test: n/a (version strings only; all five checkers green and selftest 92/0 before and after — verified against the reinstalled cache with `cmp`, not against the version label.)
The director read a junction in this project's own README — «перечисление, у которого правда три элемента, — выписано в самом правиле» — and named it: normative, and still a tell, because «люди так не пишут в живой жизни, даже профессионалы языка». The research is honest about what backs it and what does not. Rozental §64 PERMITS the junction: «при „встрече“ внутри предложения запятой и тире сначала ставится запятая, а затем тире». Lebedev's Ководство §143 does not discuss combining marks at all, and nothing was found from Ilyakhov. So the rule does not claim a source forbids the construction — it observes that living prose avoids what the norm allows, which is exactly the kind of rule `addenda.md` exists for. Notes: `~/.claude/plans/ru-text-v2/comma-dash-research.md`. The corpus had no rule producing the junction. It emerges from two correct rules applied together — a comma closing a clause, a dash opening a predicate — which is why nothing caught it, and why the tell is not an error of grammar but the fingerprint of rule-following without an ear. The scope took the most care, because a rule that catches direct speech would catch this corpus's own examples and the golden set. **The trigger is a JUNCTION, not the pair of characters.** In «„Хороший вопрос“, — ответил инженер» the comma closes the reply and the dash introduces the author — two constructions, prescribed and universal, never flagged. Every `, —` in the corpus outside AD-17's own examples is of that kind. Three further carve-outs: a run of homogeneous subordinate clauses before a main clause (Rozental §40, §46); a book or a text being copy-edited as one, which is the director's own scope; and quoted material, per «someone else's words stay theirs». Plus a single-count clause with AD-1, which orders the fixes: remove the junction first, then re-count dashes, because removing it usually removes one. Indexed in the Neuroslop index and the file's table of contents. The index matters more than usual now: it is what the triage tier loads, so a rule missing from it is invisible to a cheap run. Both READMEs move sixteen tells to seventeen — the count went stale the moment the rule landed, which is the drift this release has spent two days removing everywhere else. Version to 2.0.0-rc.3, because content people are meant to receive needs a bump: the cache invalidates on the version string, not on the bytes. That lesson is one commit old. Atoms 2 219 → 2 280; the no-loss gate accounts for every atom of the old corpus. Red-test: n/a (corpus content and version strings; no code path in the diff — all five checkers green, selftest 92/0, and diff-atoms PASS before and after. The instrument that would measure AD-17's behaviour is a golden case, and writing one is the next step, tracked in the handoff.)
…nds of the loop The director's verdict on the previous version was «далёкий от лаконичной заботы о человеке и от простоты, и язык не живой, а какая-то синтетика», and his criterion for this one is the acceptance test: «ощущение эталонного инструмента… комфортным, приятным для восприятия человека (но без токсичной позитивной душноты), и одновременно без потери месседжей и вайба автора». Four lenses read the page independently — a cognitive psychologist on sequencing and working memory, a psychiatrist on therapeutic framing, a documentation lead against the raw READMEs of top-tier projects, and a literary editor on voice. Their central diagnosis: «за страницей нет свидетеля». The author appeared twice and both times in the footer; the body had no «я» at all. What that produced: - **The fact that removes the reader's main fear moved into the lead.** «Ничего не переписывается молча» sat 83 lines below the point where the reader decides to let an agent into their text. A cautious reader — the target reader for this product — left before reaching it. - **«Границы» → «Что решаете вы»**, plus a right the page never granted: «Мандат выдаёте вы». The install sentence asks the agent to run on any Russian text, and a reader may want less; now the page says how to narrow it. - **The inventory 5 + 9 + 2 is gone.** It summed to 16 against seventeen stated three times — the one number a reader could check in their head, on a page whose argument is that its numbers are machine-counted. - **Every token figure is gone**, including 1 700. Measured: the estimate swings from 1 085 to 1 727 depending on the tokeniser, so none of them was a fact. «4 килобайта» replaced them, which `wc -c` settles. - **Nine «а не» constructions cut to two**, the product's own AD-6.5 quota. - **The sentence only a person could write**: «Раньше это число я вписывал руками. Оно разъехалось по файлам, я правил его сразу в девяти — и в записи об этой правке ошибся даже в числе файлов.» Verified: `51404bf` touched nine files and the note about it says eleven. - Removed «Я занимаюсь социальными проектами» from beside the request for money — an unverifiable claim of virtue, which our own AD-7 catches. Then three rounds of the loop the director asked for. Round 1, `ru-check`: BLOCK, and on the best possible grounds — the footer claimed «вычитана текущей версией ru-text» while AD-17, the newest rule in that version, fired on line 164 of the same page. Round 2, Fable: BLOCK on two self-refutations. The first is the one worth recording: the shop window said «лапки вместо ёлочек» as a mark of machine text, but `typography.md` R4 makes лапки the correct nested quote and R6 clemms the straight ones — the page named its own norm a defect, in the first place anyone looks. Round 3: BLOCK on a regression of mine. «онбординг» was written back into the description of `ux-writing.md`, whose thirteen sections run A to M with no onboarding among them; the same word was corrected two days earlier. So `check-dogfood.sh` now guards NAMES, not only numbers: a phrase the README uses to describe a reference file must exist in that file. Numbers had a guard and names did not, which is how a corrected word came back. Selftest 92 → 93, and the case reproduces exactly this return. Red-test: tools/selftest.sh — «a file credited with a section it does not have is caught» reports «check-dogfood PASSED on corrupted input» until verify_names exists.
Eight version points, and the CHANGELOG section dated. What the release contains, in the order it matters: **Neuroslop, from nine tells to seventeen.** AD-10 declared sincerity · AD-11 mandatory tricolon · AD-12 hollowed mechanism · AD-13 phantom attribution · AD-14 chat transcript as the artifact · AD-15 search-engine addressee · AD-16 additive pseudo-pair · AD-17 comma welded to a dash. Two of them — AD-14 and AD-15 — are charged to the document rather than to a fragment, which is new to this set: the defect is the shape of the piece, no local edit removes it, and a document carrying either never reaches the top two labels whatever it scores. **A grammar layer the corpus lacked:** §I verb government from a closed list, §J the gerund whose subject is not the sentence's, §K context-dependent homophones. An open case check was written first and rejected on purpose — over a whole text it flags the ordinary variation of a fluent writer, which makes it a detector of non-native Russian rather than a grammar rule. **The model can call the check again, and it starts cheap.** The flag that hid `ru-check` and `ru-score` from the agent is gone; the descriptions carry the Russian phrases a person says; and the cost problem it was solving is solved where it belongs — an explicit request reads the whole corpus, a self-initiated run starts with triage at ~3k tokens against ~51k and escalates on evidence. **Measurement, where there was none.** Twenty-two golden cases with expected findings, four of them controls asserting zero. Eight gates as one command. Ninety-three selftest cases, each proving a checker can fail. The corpus size is a machine-counted floor: 1 825 atoms at v1.10.1, 2 281 now, and every atom of the old corpus accounted for by the no-loss gate. **Both READMEs rewritten**, the Russian one through a four-lens psychological panel and five rounds of a correction loop that returned BLOCK three times from the project's own checker and twice from an adversarial judge. Every number on the page is now printed by a script or reproduced by a command in the text beside it. Red-test: n/a (version strings and a CHANGELOG date; no behaviour path in the diff — all six checkers green, selftest 93/0, build-release --check round-trips both assets at 2.0.0.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v2.0 — sixteen tells, the grammar that was missing, and gates that can fail
Seven rules for machine-written Russian, three for the grammar the corpus never had, a floor
under the score label, four new checkers, and a golden set grown from 13 texts to 22.
The rules
Machine-written prose — AD-10…AD-16. Declared sincerity (Medium, Ч) · mandatory tricolon
(Low, С) · hollowed mechanism (Medium, Ц) · phantom attribution (Medium, Ц) · chat
transcript as the artifact (High, document-level, С) · search-engine addressee (High,
document-level, Ц) · additive pseudo-pair (Low, С).
AD-14 and AD-15 are the first rules charged to the document rather than to a fragment,
which is what High means here: the defect is the shape of the piece and no local edit
removes it.
AD-7gains the trigger forms a model actually reaches for, andAD-7.5is narrowed — theregister carve-out now protects a speaker inside the text, not an author writing about
their own text in a conversational tone.
Grammar — §I, §J, §K in
editorial-grammar.md. Government had no rules at all: §Dcovers agreement and nothing covered which case a preposition takes. Nor did a gerund phrase
whose doer is not the sentence's, nor the homophone pairs a spellchecker cannot decide.
§I is a closed list on purpose. The open form — checking every case after every
preposition — is a detector of non-native Russian, and this project does not build one: it
would punish an accent rather than an error.
Nine rejections and four deferrals, on the record
Rejected for the neuroslop set: even rhythm, vocabulary poverty, batch sameness, synonym
carousel, fading specificity, template section skeletons, straight quotes, «безусловно»,
restatement of common knowledge. Two of those matter more than any acceptance — even rhythm
and vocabulary poverty are what detectors mistake for machine text, and also what dry
regulatory prose and non-native Russian look like.
Deferred to
docs/roadmap-v3-grammar.mdwith a reason each: participial commas (an openclass whose comma noise would drown the stylistic findings), declension, aspect mismatch,
paronyms. The wandering pronoun was rejected outright — the evidence channels split on it,
and that split is itself proof the trigger is a judgement.
docs/roadmap-v3-formative.mdopens the direction this release does not take: what aliving sentence does, rather than what it must not.
A floor under the label
A document charged with AD-14 or AD-15 is never labelled «Эталонный» or «Хороший», whatever
the arithmetic returns. Not a non-compensatory cap — those stay on the hard dimensions — and
not a change to any dimension: only the word a reader carries away.
Measured, not supposed: a search-engine stub, grammatical and typographically clean and
useless, scored 6.7 on Typography 9.5 and Grammar 9.5. A flawless chat transcript reaches
9.2. Driving Clarity on the stub from 7.0 to 3.0 moves the total to 5.7 — the label does not
change, and on the transcript no rule about words does anything at all, because a
well-written transcript has no defect in its words.
Machinery
tools/gates.shruns the CI sequence as one command, which is what lets the pre-push hookrun it: with no
package.jsonthis repository looked to that hook like a project with notests. Seven gates now, and the selftest grew from 45 cases to 70.
check-version.sh— one version across ten points, a named style budget for the skilldescription, and the six trigger phrases. It found that the description contained no
Russian at all, while on the Claude apps and ChatGPT that string is the entire trigger.
check-dogfood.sh— the numbers the product states about itself, with a completenessguard so the consumer list cannot go stale silently.
build-release.sh— two assets from tracked files only, refusing a dirty checkout andproving itself by unpacking and diffing.
atom-map.tsvgains its first sixteen rows.The corpus size is now quoted as a machine-counted floor — «over 2,000 linguistic atoms»,
reproducible with
tools/extract-atoms.sh skills/ru-text | wc -l— replacing «~1 044 rules»,a figure nobody could recompute that had drifted across eleven files. The per-file rule
counts in
scoring.mdare gone for the same reason.How it was measured
22 curated texts, run through the
/ru-text:ru-checkprocedure blind to the expectedfindings, before and after. Three carve-out controls returned zero findings — one per
rule generation, so a lost exception names its own generation.
The runs caught defects reading did not:
looking cleaner than before the rule existed.
before the rule against it existed. That is now AD-16.8, and its principle is general: a
new rule must never make its target score better.
it is the current norm, the gerund test written against «the grammatical subject» where
Rozental's criterion is the producer of the action, «в течении» called only a river, «в
виду» called only a fixed phrase.
first time — no non-breaking spaces at all, while claiming to be written by the rules of
the corpus.