Browser diff page, Korean post-editese advisory analyzer, and detector calibration fixes.
Semver rationale: minor — adds the --browser rewrite diff page, the Korean post-editese advisory analyzer surfaced through analyzeText, and an optional structural-classifier scoring hook; the rest is bug fixes to Korean detection rules, local-CLI backends, score-prompt reliability, and npm packaging. Note: 4.0.1 was tagged in this file but never published, so npm consumers upgrade straight from 4.0.0 and pick up its patina-cli bin alias here.
Added
--browserrewrite add-on: rewrites one local file (stdout stays byte-for-byte identical for the selected--format), then writes a self-contained local HTML before/after diff page — side-by-side text, changed-block highlights, deterministic score summaries, and a best-effort diff-explanation backend call (one extra model call; explanation failure never fails the rewrite). Rejects stdin/--batch/URLs/non-rewrite modes; if the browser cannot be opened, the saved HTML path is printed on stderr.- Korean post-editese advisory analyzer (
koPostEditese.v1): deterministic descriptive metrics (lexical, endings, interference, rhythm) surfaced throughanalyzeTextas advisory-only metadata — never folded into the hot verdict or the score. - Optional structural-classifier scoring hook: config
stylometry.structural_model.path(or thePATINA_STRUCTURAL_MODELenv var) can point at a local structural model; when a loaded model marks text hot, the deterministic score gets a 70-point floor and astructuralClassifierband (available/hot/score/floor) appears in the deterministic-score JSON. Without a model, behavior is unchanged.
Fixed
-
Korean
koPostEditeseno longer misclassifies regular formal-ㅂ니다 / -ㅂ니까endings (됩니다, 표시됩니다, 합니까…) as declarative-다style, so clean 합쇼체 prose is no longer pushed toward register-changing rewrites. -
Translationese
a16-pronoun-literalno longer fires on ordinary nouns ending in그(로그/버그/태그) or on 그녀석/그것참; it now requires eojeol boundaries on both sides. -
Translationese
t2-by-passivenow matches the common fused passive forms (된다/됩니다/될/진다) that the previous jamo alternation silently missed. -
Deterministic audit backstop now respects each rule's
minCount(e.g.c11-connective-commano longer surfaces on a single match). -
Local-CLI backends (claude/gemini/kimi) decode stdout as streaming UTF-8, fixing silent corruption of multi-byte CJK output split across pipe reads; all four backends now handle stdin EPIPE instead of crashing the process when a child exits before draining a large prompt.
-
Ouroboros loop strips
[BODY]/[SELF_AUDIT]tags before scoring and re-feeding, returns the best-scoring text paired with its score, and treats a failed MPS scorer as a floor violation (fail closed, matching fidelity). -
Rewrite tone-footer removal anchors to the final
---block, so a markdown thematic break in the body no longer truncates everything after it. -
--config <file>now wins over an ambient./.patina.yaml/~/.patina.yaml(reproducible runs); config merge is guarded against prototype pollution. -
Mutually exclusive output modes (
--diff/--audit/--score/--ouroboros) are now rejected up front instead of silently resolving to one mode (which could make a--scoreCI gate always exit 0). -
Discourse tells (fake-candor openers / thematic breaks) are now attributed to the paragraphs that carry them (#391), so flagged paragraphs enter rewrite scope and reach the deterministic score through the hot ratio — matching the playground. The interim document-level 35-point score floor (added and removed within this release; never published) is gone; the ≥2/≥3 density gates are unchanged. Discourse-hot paragraphs also carry signal strength (tell count normalized by the density gate), keeping the signal-score ranking leg consistent with the hot verdict (no hot-with-zero-signal rows). Prose gates (
precommit-score/dogfood) keep their hot-prose-ratio semantics: bare---divider pseudo-paragraphs are excluded from the gate ratio, while divider spam still reaches the mdx ranking throughflooredScore. -
npm package now ships the scripts behind the
benchmark:rebaseline:generate-modern,benchmark:rebaseline:claim-manifest,benchmark:rebaseline:fp-fixtures, andqa:mdxnpm scripts (rebaseline-generate-modern.mjs,rebaseline-build-claim-manifest.mjs,fp-fixture-export.mjs,qa/mdx-score.mjs), which previously failed with MODULE_NOT_FOUND for npm consumers (#411).
Changed
--format textoutput no longer appends theTone: <tone> (<source>)trailer line.--score/scoreTextprompts now embed per-pack pattern counts and a full catalog digest, andscoreTextfollows a single strict-JSON output contract (with strip options and aflooredScorefield); thepatina-scoreprose gate strips paired emphasis markers only (so URL-leakage signals likeutm_source=chatgpt.comsurvive) and scores throughscoreTextwith the lexicon channel and canonical floors — gate scores can shift slightly vs 4.0.0.kimi-clibackend runs with--max-steps-per-turn 20(up from 1). It stays in non-interactive--printmode with no--yolo, so the agent cannot auto-approve shell/file tools — the extra steps only cover reasoning/formatting within a turn (verified that injected tool-use instructions in user text do not execute).- Internal:
src/cli.jsdecomposed into per-concern modules (src/cli/args.js,batch.js,input.js,score-gate.js,run.js— #409, #413, #414), and the generateddocs/API.mdno longer claims boilerplate@throwson functions that cannot throw.