patina-cli 3.11.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.patina.default.yaml +29 -29
- package/CHANGELOG.md +53 -0
- package/NOTICE +21 -0
- package/README.md +117 -224
- package/README_JA.md +134 -77
- package/README_KR.md +132 -74
- package/README_ZH.md +137 -80
- package/SKILL.md +11 -20
- package/artifacts/rebaseline-2025/README.md +147 -0
- package/artifacts/rebaseline-2025/human-controls.public.jsonl +250 -0
- package/artifacts/rebaseline-2025/intake.example.jsonl +2 -0
- package/artifacts/rebaseline-2025/intake.local.example.jsonl +25 -0
- package/artifacts/rebaseline-2025/prompts.template.jsonl +7 -0
- package/artifacts/rebaseline-2025/sources.ko-public.jsonl +39 -0
- package/assets/brand/patina-badge.svg +18 -0
- package/assets/brand/patina-mark.svg +8 -0
- package/assets/demo/README.md +79 -0
- package/core/scoring.md +12 -12
- package/core/standalone-prompt.md +3 -1
- package/core/stylometry.md +93 -22
- package/docs/API.md +1554 -0
- package/docs/AUTHENTICATION.md +50 -26
- package/docs/AUTHENTICATION_KR.md +54 -29
- package/docs/BRANDING.md +9 -8
- package/docs/CLI.md +55 -14
- package/docs/COOKBOOK.md +8 -21
- package/docs/DEMO.md +32 -5
- package/docs/EXIT-CODES.md +2 -3
- package/docs/FALSE-POSITIVES.md +63 -0
- package/docs/FAQ.md +9 -1
- package/docs/FAQ_KR.md +3 -1
- package/docs/FLAG-PARITY.md +33 -47
- package/docs/ISSUE-WAVES.md +57 -0
- package/docs/PATTERNS-EN.md +67 -3
- package/docs/PATTERNS-JA.md +68 -2
- package/docs/PATTERNS-KO.md +70 -7
- package/docs/PATTERNS-ZH.md +67 -3
- package/docs/PATTERNS.md +5 -5
- package/docs/RESEARCH-DOCS-PLATFORM.md +54 -0
- package/docs/ROADMAP.md +46 -66
- package/docs/TRANSLATIONESE-KO.md +51 -0
- package/docs/audits/2026-05-deep-research.md +3 -1
- package/docs/benchmarks/README.md +51 -0
- package/docs/benchmarks/detector-comparison.json +69 -9
- package/docs/benchmarks/detector-comparison.md +10 -5
- package/docs/benchmarks/katfish-ko-latest.json +657 -0
- package/docs/benchmarks/katfish-ko-latest.md +77 -0
- package/docs/benchmarks/latest.json +1183 -108
- package/docs/benchmarks/latest.md +84 -60
- package/docs/benchmarks/lexicon-freshness-en-2026-05-22.json +1121 -0
- package/docs/benchmarks/lexicon-freshness-en-2026-05-22.md +136 -0
- package/docs/benchmarks/rebaseline-latest.json +381 -0
- package/docs/benchmarks/rebaseline-latest.md +121 -0
- package/docs/benchmarks/register-stratified-latest.json +164 -0
- package/docs/benchmarks/register-stratified-latest.md +99 -0
- package/docs/benchmarks/register-stratified.md +43 -0
- package/docs/integrations/github-action.md +44 -11
- package/docs/integrations/playground.md +58 -0
- package/docs/integrations/pre-commit.md +5 -5
- package/docs/integrations/release.md +5 -3
- package/docs/integrations/static-sites.md +83 -0
- package/docs/research/2025-rebaseline-plan.md +71 -2
- package/docs/research/2026-rebaseline.md +102 -0
- package/docs/research/adversarial-mps.md +41 -0
- package/docs/research/ai-human-metrics.md +35 -23
- package/docs/research/human-eval-panel.md +42 -0
- package/docs/research/judge-agreement.md +24 -0
- package/docs/research/ko-2025-corpus-sources.md +135 -0
- package/docs/research/lexicon-freshness-audit.md +64 -0
- package/docs/research/zh-ja-lexicon-calibration.md +60 -0
- package/docs/social/patina-launch-copy.md +173 -100
- package/docs/social/patina-launch-execution.md +94 -0
- package/docs/social/patina-launch-korean-first.md +83 -0
- package/docs/social/signs-of-ai-writing.md +26 -0
- package/docs/social/signs-of-ai-writing_KR.md +26 -0
- package/lexicon/ai-en.md +21 -24
- package/lexicon/ai-ja.md +158 -0
- package/lexicon/ai-ko.md +9 -9
- package/lexicon/ai-zh.md +158 -0
- package/lexicon/provenance/ai-en.json +970 -0
- package/lexicon/provenance/ai-ja.json +542 -0
- package/lexicon/provenance/ai-ko.json +866 -0
- package/lexicon/provenance/ai-zh.json +542 -0
- package/package.json +49 -8
- package/patterns/en-communication.md +5 -0
- package/patterns/en-content.md +5 -0
- package/patterns/en-filler.md +5 -0
- package/patterns/en-language.md +29 -1
- package/patterns/en-structure.md +5 -0
- package/patterns/en-style.md +5 -0
- package/patterns/en-viral-hook.md +42 -2
- package/patterns/ja-communication.md +5 -0
- package/patterns/ja-content.md +5 -0
- package/patterns/ja-filler.md +5 -0
- package/patterns/ja-language.md +33 -1
- package/patterns/ja-structure.md +12 -0
- package/patterns/ja-style.md +5 -0
- package/patterns/ja-viral-hook.md +41 -2
- package/patterns/ko-communication.md +5 -0
- package/patterns/ko-content.md +5 -0
- package/patterns/ko-filler.md +5 -0
- package/patterns/ko-language.md +33 -1
- package/patterns/ko-structure.md +25 -6
- package/patterns/ko-style.md +5 -0
- package/patterns/ko-viral-hook.md +38 -2
- package/patterns/zh-communication.md +5 -0
- package/patterns/zh-content.md +5 -0
- package/patterns/zh-filler.md +5 -0
- package/patterns/zh-language.md +37 -1
- package/patterns/zh-structure.md +12 -0
- package/patterns/zh-style.md +5 -0
- package/patterns/zh-viral-hook.md +38 -2
- package/playground/README.md +55 -0
- package/playground/analytics.js +4 -0
- package/playground/analyzer.js +883 -0
- package/playground/app.js +157 -0
- package/playground/data/lexicons.js +343 -0
- package/playground/index.html +138 -0
- package/playground/styles.css +267 -0
- package/profiles/namuwiki.md +111 -0
- package/scripts/adversarial-mps-report.mjs +201 -0
- package/scripts/badge-json.mjs +79 -0
- package/scripts/benchmark-report.mjs +56 -9
- package/scripts/check-release-metadata.mjs +0 -2
- package/scripts/detector-comparison.mjs +7 -7
- package/scripts/generate-playground-data.mjs +77 -0
- package/scripts/katfish-calibration.mjs +464 -0
- package/scripts/lexicon-freshness.mjs +485 -0
- package/scripts/lint.mjs +1 -1
- package/scripts/precommit-score.mjs +4 -3
- package/scripts/prose-score.mjs +81 -5
- package/scripts/rebaseline-intake.mjs +242 -0
- package/scripts/rebaseline-score.mjs +268 -0
- package/scripts/rebaseline-summary.mjs +773 -0
- package/scripts/rebaseline-web-collect.mjs +410 -0
- package/scripts/update-benchmark-ranges.mjs +1 -0
- package/src/api.js +69 -105
- package/src/auth.js +50 -2
- package/src/backends/claude-cli.js +19 -4
- package/src/backends/codex-cli.js +19 -3
- package/src/backends/contract.js +230 -1
- package/src/backends/gemini-cli.js +18 -5
- package/src/backends/index.js +87 -12
- package/src/backends/kimi-cli.js +161 -0
- package/src/cli.js +577 -567
- package/src/commands/doctor.js +2 -2
- package/src/config.js +29 -0
- package/src/errors.js +53 -1
- package/src/features/discourse-tells.js +68 -0
- package/src/features/index.js +82 -8
- package/src/features/lexicon.js +40 -6
- package/src/features/markup-leakage.js +69 -0
- package/src/features/segment.js +41 -0
- package/src/features/signal-strength.js +81 -0
- package/src/features/stylometry.js +231 -1
- package/src/features/translationese.js +127 -0
- package/src/loader.js +76 -0
- package/src/logger.js +22 -23
- package/src/model-defaults.js +55 -0
- package/src/ouroboros.js +31 -0
- package/src/output.js +102 -90
- package/src/prompt-builder.js +103 -68
- package/src/providers.js +51 -4
- package/src/scoring.js +210 -2
- package/src/security.js +75 -0
- package/tests/fixtures/live-quality/en/public-docs-01.md +26 -0
- package/tests/fixtures/live-quality/ko/public-docs-01.md +26 -0
- package/tests/fixtures/suspect-zones/expected-ranges.json +207 -16
- package/tests/fixtures/suspect-zones/ja/ai/ja-ai-04-lexicon.md +11 -0
- package/tests/fixtures/suspect-zones/ja/natural/ja-nat-04-lexicon-cold.md +11 -0
- package/tests/fixtures/suspect-zones/ko/ai/ko-ai-02.md +4 -5
- package/tests/fixtures/suspect-zones/ko/ai/ko-ai-07-ko-diagnostic.md +11 -0
- package/tests/fixtures/suspect-zones/zh/ai/zh-ai-04-lexicon.md +11 -0
- package/tests/fixtures/suspect-zones/zh/natural/zh-nat-04-lexicon-cold.md +11 -0
- package/tests/quality/README.md +188 -11
- package/tests/quality/adversarial-mps/fixtures.jsonl +10 -0
- package/tests/quality/benchmark.mjs +39 -1
- package/tests/quality/dogfood.mjs +5 -3
- package/tests/quality/live-fixtures.jsonl +2 -0
- package/tests/quality/live-quality.mjs +596 -0
- package/tests/quality/ranking-metrics.mjs +136 -0
- package/tests/quality/rebaseline-manifest.example.jsonl +5 -0
- package/vercel.json +53 -0
- package/SKILL-MAX.md +0 -455
- package/docs/internal/HARNESS.md +0 -14
- package/docs/internal/README.md +0 -14
- package/docs/internal/WARP.md +0 -23
- package/patina-max/SKILL.md +0 -523
- package/patina-max/composite.py +0 -457
- package/src/cache.js +0 -106
- package/src/commands/init.js +0 -208
- package/src/manifest.js +0 -162
- package/src/max-mode.js +0 -207
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"generatedAt": "2026-05-21T16:24:01.336Z",
|
|
4
|
+
"input": "artifacts/rebaseline-2025/human-controls.public.jsonl",
|
|
5
|
+
"targets": {
|
|
6
|
+
"protocolPerLanguageClassRegister": 25,
|
|
7
|
+
"claimPerCell": 100,
|
|
8
|
+
"claimLanguages": 2,
|
|
9
|
+
"claimGeneratorFamilies": 3
|
|
10
|
+
},
|
|
11
|
+
"totalRecords": 250,
|
|
12
|
+
"byLanguage": {
|
|
13
|
+
"ko": 250
|
|
14
|
+
},
|
|
15
|
+
"byClass": {
|
|
16
|
+
"natural-human": 250
|
|
17
|
+
},
|
|
18
|
+
"byRegister": {
|
|
19
|
+
"chat-update": 50,
|
|
20
|
+
"blog": 50,
|
|
21
|
+
"technical-how-to": 50,
|
|
22
|
+
"academic-summary": 50,
|
|
23
|
+
"product-doc": 50
|
|
24
|
+
},
|
|
25
|
+
"byModelFamily": {
|
|
26
|
+
"human-reference": 250
|
|
27
|
+
},
|
|
28
|
+
"protocolCoverage": {
|
|
29
|
+
"totalCells": 80,
|
|
30
|
+
"populatedCells": 5,
|
|
31
|
+
"emptyCells": 75,
|
|
32
|
+
"cellsMeetingTarget": 5,
|
|
33
|
+
"underfilledCells": []
|
|
34
|
+
},
|
|
35
|
+
"claimGate": {
|
|
36
|
+
"ready": false,
|
|
37
|
+
"blockers": [
|
|
38
|
+
"positive corpus has 0/2 languages with n≥100",
|
|
39
|
+
"positive corpus has 0/3 generator families with n≥100",
|
|
40
|
+
"natural/human corpus has 1/2 languages with n≥100"
|
|
41
|
+
],
|
|
42
|
+
"qualifiedPositiveCells": [],
|
|
43
|
+
"qualifiedNaturalCells": [
|
|
44
|
+
{
|
|
45
|
+
"key": "ko",
|
|
46
|
+
"count": 250
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"metrics": {
|
|
51
|
+
"tp": 0,
|
|
52
|
+
"fp": 42,
|
|
53
|
+
"fn": 0,
|
|
54
|
+
"tn": 208,
|
|
55
|
+
"total": 250,
|
|
56
|
+
"accuracy": 0.832,
|
|
57
|
+
"precision": 0,
|
|
58
|
+
"recall": 0,
|
|
59
|
+
"f1": 0,
|
|
60
|
+
"falsePositiveRate": 0.168,
|
|
61
|
+
"falseNegativeRate": 0,
|
|
62
|
+
"accuracyCi": {
|
|
63
|
+
"low": 0.781,
|
|
64
|
+
"high": 0.873,
|
|
65
|
+
"method": "Wilson score interval, 95%"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"metricsByRegister": {
|
|
69
|
+
"academic-summary": {
|
|
70
|
+
"tp": 0,
|
|
71
|
+
"fp": 7,
|
|
72
|
+
"fn": 0,
|
|
73
|
+
"tn": 43,
|
|
74
|
+
"total": 50,
|
|
75
|
+
"accuracy": 0.86,
|
|
76
|
+
"precision": 0,
|
|
77
|
+
"recall": 0,
|
|
78
|
+
"f1": 0,
|
|
79
|
+
"falsePositiveRate": 0.14,
|
|
80
|
+
"falseNegativeRate": 0,
|
|
81
|
+
"accuracyCi": {
|
|
82
|
+
"low": 0.738,
|
|
83
|
+
"high": 0.93,
|
|
84
|
+
"method": "Wilson score interval, 95%"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"blog": {
|
|
88
|
+
"tp": 0,
|
|
89
|
+
"fp": 10,
|
|
90
|
+
"fn": 0,
|
|
91
|
+
"tn": 40,
|
|
92
|
+
"total": 50,
|
|
93
|
+
"accuracy": 0.8,
|
|
94
|
+
"precision": 0,
|
|
95
|
+
"recall": 0,
|
|
96
|
+
"f1": 0,
|
|
97
|
+
"falsePositiveRate": 0.2,
|
|
98
|
+
"falseNegativeRate": 0,
|
|
99
|
+
"accuracyCi": {
|
|
100
|
+
"low": 0.67,
|
|
101
|
+
"high": 0.888,
|
|
102
|
+
"method": "Wilson score interval, 95%"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"chat-update": {
|
|
106
|
+
"tp": 0,
|
|
107
|
+
"fp": 2,
|
|
108
|
+
"fn": 0,
|
|
109
|
+
"tn": 48,
|
|
110
|
+
"total": 50,
|
|
111
|
+
"accuracy": 0.96,
|
|
112
|
+
"precision": 0,
|
|
113
|
+
"recall": 0,
|
|
114
|
+
"f1": 0,
|
|
115
|
+
"falsePositiveRate": 0.04,
|
|
116
|
+
"falseNegativeRate": 0,
|
|
117
|
+
"accuracyCi": {
|
|
118
|
+
"low": 0.865,
|
|
119
|
+
"high": 0.989,
|
|
120
|
+
"method": "Wilson score interval, 95%"
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"product-doc": {
|
|
124
|
+
"tp": 0,
|
|
125
|
+
"fp": 6,
|
|
126
|
+
"fn": 0,
|
|
127
|
+
"tn": 44,
|
|
128
|
+
"total": 50,
|
|
129
|
+
"accuracy": 0.88,
|
|
130
|
+
"precision": 0,
|
|
131
|
+
"recall": 0,
|
|
132
|
+
"f1": 0,
|
|
133
|
+
"falsePositiveRate": 0.12,
|
|
134
|
+
"falseNegativeRate": 0,
|
|
135
|
+
"accuracyCi": {
|
|
136
|
+
"low": 0.762,
|
|
137
|
+
"high": 0.944,
|
|
138
|
+
"method": "Wilson score interval, 95%"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"technical-how-to": {
|
|
142
|
+
"tp": 0,
|
|
143
|
+
"fp": 17,
|
|
144
|
+
"fn": 0,
|
|
145
|
+
"tn": 33,
|
|
146
|
+
"total": 50,
|
|
147
|
+
"accuracy": 0.66,
|
|
148
|
+
"precision": 0,
|
|
149
|
+
"recall": 0,
|
|
150
|
+
"f1": 0,
|
|
151
|
+
"falsePositiveRate": 0.34,
|
|
152
|
+
"falseNegativeRate": 0,
|
|
153
|
+
"accuracyCi": {
|
|
154
|
+
"low": 0.522,
|
|
155
|
+
"high": 0.776,
|
|
156
|
+
"method": "Wilson score interval, 95%"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"validation": {
|
|
161
|
+
"errors": [],
|
|
162
|
+
"warnings": []
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Rebaseline Manifest Summary
|
|
2
|
+
|
|
3
|
+
- Generated at: 2026-05-21T16:24:01.336Z
|
|
4
|
+
- Input: `artifacts/rebaseline-2025/human-controls.public.jsonl`
|
|
5
|
+
- Records: 250
|
|
6
|
+
- Protocol target: 25 samples per language × class × register cell
|
|
7
|
+
- Public claim target: 100 samples per claim cell, 2+ languages, 3+ generator families
|
|
8
|
+
|
|
9
|
+
## Validation
|
|
10
|
+
|
|
11
|
+
Validation: **PASS**
|
|
12
|
+
|
|
13
|
+
## Coverage snapshot
|
|
14
|
+
|
|
15
|
+
### By language
|
|
16
|
+
|
|
17
|
+
| value | n |
|
|
18
|
+
|---|---:|
|
|
19
|
+
| ko | 250 |
|
|
20
|
+
| en | 0 |
|
|
21
|
+
| zh | 0 |
|
|
22
|
+
| ja | 0 |
|
|
23
|
+
|
|
24
|
+
### By class
|
|
25
|
+
|
|
26
|
+
| value | n |
|
|
27
|
+
|---|---:|
|
|
28
|
+
| ai-like | 0 |
|
|
29
|
+
| natural-human | 250 |
|
|
30
|
+
| lightly-edited-ai | 0 |
|
|
31
|
+
| heavily-edited-ai | 0 |
|
|
32
|
+
|
|
33
|
+
### By register
|
|
34
|
+
|
|
35
|
+
| value | n |
|
|
36
|
+
|---|---:|
|
|
37
|
+
| blog | 50 |
|
|
38
|
+
| academic-summary | 50 |
|
|
39
|
+
| product-doc | 50 |
|
|
40
|
+
| chat-update | 50 |
|
|
41
|
+
| technical-how-to | 50 |
|
|
42
|
+
|
|
43
|
+
### By model family
|
|
44
|
+
|
|
45
|
+
| value | n |
|
|
46
|
+
|---|---:|
|
|
47
|
+
| gpt-family | 0 |
|
|
48
|
+
| claude-family | 0 |
|
|
49
|
+
| gemini-family | 0 |
|
|
50
|
+
| open-weight | 0 |
|
|
51
|
+
| human-reference | 250 |
|
|
52
|
+
|
|
53
|
+
## Protocol matrix
|
|
54
|
+
|
|
55
|
+
- Populated language × class × register cells: 5/80
|
|
56
|
+
- Cells meeting 25+ samples: 5
|
|
57
|
+
- Empty cells: 75
|
|
58
|
+
- Underfilled populated cells: 0
|
|
59
|
+
|
|
60
|
+
No underfilled populated protocol cells.
|
|
61
|
+
|
|
62
|
+
## Public performance claim gate
|
|
63
|
+
|
|
64
|
+
Public performance claim: **BLOCKED**
|
|
65
|
+
|
|
66
|
+
| blocker |
|
|
67
|
+
|---|
|
|
68
|
+
| positive corpus has 0/2 languages with n≥100 |
|
|
69
|
+
| positive corpus has 0/3 generator families with n≥100 |
|
|
70
|
+
| natural/human corpus has 1/2 languages with n≥100 |
|
|
71
|
+
|
|
72
|
+
| claim-gate count | value |
|
|
73
|
+
|---|---:|
|
|
74
|
+
| qualified positive cells (language × generator family, n≥100) | 0 |
|
|
75
|
+
| qualified natural-language cells (language, n≥100) | 1 |
|
|
76
|
+
| outcome rows with expected/predicted labels | 250 |
|
|
77
|
+
|
|
78
|
+
## Outcome metrics
|
|
79
|
+
|
|
80
|
+
| metric | value |
|
|
81
|
+
|---|---:|
|
|
82
|
+
| accuracy | 83.2% |
|
|
83
|
+
| accuracy CI | 78.1%–87.3% |
|
|
84
|
+
| precision | 0.0% |
|
|
85
|
+
| recall | 0.0% |
|
|
86
|
+
| F1 | 0.000 |
|
|
87
|
+
| false positive rate | 16.8% |
|
|
88
|
+
| false negative rate | 0.0% |
|
|
89
|
+
| TP/FP/FN/TN | 0/42/0/208 |
|
|
90
|
+
|
|
91
|
+
### By register
|
|
92
|
+
|
|
93
|
+
| register | n | FP rate | FN rate | TP/FP/FN/TN |
|
|
94
|
+
|---|---:|---:|---:|---:|
|
|
95
|
+
| blog | 50 | 20.0% | 0.0% | 0/10/0/40 |
|
|
96
|
+
| academic-summary | 50 | 14.0% | 0.0% | 0/7/0/43 |
|
|
97
|
+
| product-doc | 50 | 12.0% | 0.0% | 0/6/0/44 |
|
|
98
|
+
| chat-update | 50 | 4.0% | 0.0% | 0/2/0/48 |
|
|
99
|
+
| technical-how-to | 50 | 34.0% | 0.0% | 0/17/0/33 |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Korean register-stratified false-positive plan
|
|
2
|
+
|
|
3
|
+
Status: KO diagnostic bands calibrated; public performance claims still blocked on the wider rebaseline gate.
|
|
4
|
+
Related issues: #157, #303, #155.
|
|
5
|
+
|
|
6
|
+
Korean registers do not share one false-positive profile. This page keeps threshold work tied to both register false positives and positive AI-like catch rates.
|
|
7
|
+
|
|
8
|
+
## Current tracked pilot
|
|
9
|
+
|
|
10
|
+
`artifacts/rebaseline-2025/human-controls.public.jsonl` currently contains 250 metadata/hash-only Korean human-control rows. The generated snapshot lives at `docs/benchmarks/register-stratified-latest.md`.
|
|
11
|
+
|
|
12
|
+
| register | current rows | target rows |
|
|
13
|
+
|---|---:|---:|
|
|
14
|
+
| academic / 종결-다 | 50 | 50 |
|
|
15
|
+
| product / technical docs | 50 | 50 |
|
|
16
|
+
| policy / notice / chat update | 50 | 50 |
|
|
17
|
+
| blog / community | 50 | 50 |
|
|
18
|
+
| technical how-to | 50 | 50 |
|
|
19
|
+
|
|
20
|
+
The current pilot has 42 predicted-hot rows and 208 predicted-cold rows, for a 16.8% point false-positive rate on the hash-only human-control sample. The split is uneven by register: chat/update is 4.0%, product-doc is 12.0%, academic-summary is 14.0%, blog is 20.0%, and technical-how-to is 34.0%.
|
|
21
|
+
|
|
22
|
+
That register spread is the main threshold-drift finding: a single Korean threshold would mostly be tuned by technical how-to false positives, while chat/update already looks conservative. Treat this as false-positive evidence for #157, not as a public performance claim.
|
|
23
|
+
|
|
24
|
+
## Gate before threshold changes
|
|
25
|
+
|
|
26
|
+
Do not loosen or tighten Korean scoring thresholds until all gates below pass.
|
|
27
|
+
|
|
28
|
+
| gate | requirement | status |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| coverage | at least five registers have n≥50 reviewed human-control rows each | met: 5/5 registers have n=50 |
|
|
31
|
+
| privacy | no raw private or no-redistribution text is committed | met: public rows are hash-only |
|
|
32
|
+
| review | each row has source review notes or a license field | met: rows carry source/license notes |
|
|
33
|
+
| reporting | the report shows false positives by register, not only in aggregate | met: see `register-stratified-latest.md` |
|
|
34
|
+
| positive controls | threshold change is checked against AI-like and edited-AI rows | met for KO diagnostics via private KatFish aggregate; edited-AI still belongs to #155 |
|
|
35
|
+
| verification | any changed threshold is tested against `npm run benchmark` and `npm run benchmark:rebaseline` | met for the KO diagnostic band update |
|
|
36
|
+
|
|
37
|
+
## Recommendation
|
|
38
|
+
|
|
39
|
+
The KO diagnostic band update uses the KatFish aggregate report, not a public headline claim. It improves KatFish catch rate by +15.9 pp versus Patina without KO diagnostics while keeping the 250-row public-web human-control FP count unchanged at 42/250. Keep broader 2025+ public claims under #155 until multilingual, multi-family claim cells exist.
|
|
40
|
+
|
|
41
|
+
## Why this matters
|
|
42
|
+
|
|
43
|
+
The Korean diagnostic layer already adds spacing, comma, and suffix-class proxies. Those signals can help with generated boilerplate, but they can also flag formal Korean if the register mix is wrong. Register splits protect person-written formal prose before a threshold change ships.
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Patina's standalone Action repository is [`devswha/patina-action`](https://github.com/devswha/patina-action). It runs pull request prose review without a live model call, leaves a sticky comment with file-level hotspot scores, and can fail above an optional score threshold.
|
|
4
4
|
|
|
5
|
-
> The Action defaults to `patina-cli@latest`, so the `@v1` tag should be cut after the npm publish in #203. Until then, pre-release workflows can use `@main` with `patina-package: github:devswha/patina`.
|
|
6
|
-
|
|
7
5
|
```yaml
|
|
8
6
|
name: Patina prose score
|
|
9
7
|
|
|
@@ -23,24 +21,58 @@ jobs:
|
|
|
23
21
|
runs-on: ubuntu-latest
|
|
24
22
|
steps:
|
|
25
23
|
- uses: actions/checkout@v6
|
|
26
|
-
- uses: devswha/patina-action@
|
|
24
|
+
- uses: devswha/patina-action@v1
|
|
27
25
|
with:
|
|
28
|
-
|
|
29
|
-
report-threshold: 30
|
|
26
|
+
score-threshold: 30
|
|
30
27
|
lang: auto
|
|
31
28
|
comment: true
|
|
32
29
|
```
|
|
33
30
|
|
|
34
|
-
|
|
31
|
+
## README score badge
|
|
32
|
+
|
|
33
|
+
Patina can also produce a [Shields.io endpoint](https://shields.io/endpoint) JSON file from the same deterministic prose score used by the PR comment. The endpoint reports the highest scored file (`maxScore`) as an editing-hotspot percentage, not an authorship verdict.
|
|
34
|
+
|
|
35
|
+
Generate the JSON locally:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run badge -- README.md docs/FAQ.md > patina-badge.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Payload shape:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{ "schemaVersion": 1, "label": "patina", "message": "25% · human-ish", "color": "brightgreen" }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use it from a README once `patina-badge.json` is published on a stable branch:
|
|
48
|
+
|
|
49
|
+
```md
|
|
50
|
+
[](https://github.com/devswha/patina)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For the standalone Action, set `badge-branch` to publish `patina-badge.json` after scoring:
|
|
35
54
|
|
|
36
55
|
```yaml
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
permissions:
|
|
57
|
+
contents: write
|
|
58
|
+
pull-requests: read
|
|
59
|
+
issues: write
|
|
60
|
+
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v6
|
|
63
|
+
- uses: devswha/patina-action@v1
|
|
64
|
+
with:
|
|
65
|
+
badge-branch: patina-badge
|
|
42
66
|
```
|
|
43
67
|
|
|
68
|
+
If you do not want a live score endpoint, use the static brand fallback instead:
|
|
69
|
+
|
|
70
|
+
```md
|
|
71
|
+
[](https://github.com/devswha/patina)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
No badge mode performs per-visitor tracking; Shields reads a repository-owned JSON file or a static SVG.
|
|
75
|
+
|
|
44
76
|
## Inputs
|
|
45
77
|
|
|
46
78
|
| Input | Default | Meaning |
|
|
@@ -52,6 +84,7 @@ Once `patina-cli` is on npm and `devswha/patina-action@v1` is tagged, the stable
|
|
|
52
84
|
| `report-threshold` | `30` | Advisory report gate when `score-threshold` is unset. |
|
|
53
85
|
| `max-files` | `50` | Maximum Markdown files to score. |
|
|
54
86
|
| `comment` | `true` | Create/update a sticky PR comment. |
|
|
87
|
+
| `badge-branch` | unset | Optional branch where the Action publishes `patina-badge.json` for Shields.io. Requires `contents: write`. |
|
|
55
88
|
| `patina-package` | `patina-cli@latest` | npm package spec used by `npx`. |
|
|
56
89
|
|
|
57
90
|
## What it measures
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Web Playground
|
|
2
|
+
|
|
3
|
+
The hosted playground is the lowest-friction way to try patina before installing anything:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
https://patina.vibetip.help/
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It is intentionally audit-only. The page runs deterministic string operations in the browser and shows:
|
|
10
|
+
|
|
11
|
+
- a 0-100 editing-hotspot score;
|
|
12
|
+
- paragraph-level burstiness, MATTR, lexicon, and Korean rhythm diagnostic signals;
|
|
13
|
+
- a suspect-zone diff that highlights review zones and lexicon hits;
|
|
14
|
+
- an **Open in CLI** command that copies the pasted input plus `npx patina-cli --score` / `--audit` commands.
|
|
15
|
+
|
|
16
|
+
It does not rewrite text, call an LLM, proxy user API keys, or send pasted text off the page. The hosted Vercel deployment records page-view metadata with Web Analytics so maintainers can watch traffic.
|
|
17
|
+
|
|
18
|
+
## Source files
|
|
19
|
+
|
|
20
|
+
- App shell: [`playground/index.html`](../../playground/index.html)
|
|
21
|
+
- Browser analyzer: [`playground/analyzer.js`](../../playground/analyzer.js)
|
|
22
|
+
- DOM wiring: [`playground/app.js`](../../playground/app.js)
|
|
23
|
+
- Generated lexicons: [`playground/data/lexicons.js`](../../playground/data/lexicons.js)
|
|
24
|
+
- Vercel routes: [`vercel.json`](../../vercel.json)
|
|
25
|
+
- Analytics shim: [`playground/analytics.js`](../../playground/analytics.js)
|
|
26
|
+
- OG image: [`assets/social/patina-og.svg`](../../assets/social/patina-og.svg)
|
|
27
|
+
|
|
28
|
+
## Refreshing lexicon data
|
|
29
|
+
|
|
30
|
+
When `lexicon/ai-*.md` changes, regenerate and check the browser bundle:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run playground:data
|
|
34
|
+
node scripts/generate-playground-data.mjs --check
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Deploy notes
|
|
38
|
+
|
|
39
|
+
Deploy the repository root on Vercel so the root `vercel.json` can rewrite `/` to the playground while keeping brand and social assets under `/assets/`.
|
|
40
|
+
|
|
41
|
+
After a production deploy, verify that the custom domain points at the latest
|
|
42
|
+
deployment and not an older manual alias:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
vercel --prod --yes --scope team_66lsrwOyA36bLnIH2eoEXqry
|
|
46
|
+
vercel alias set <latest-patina-*.vercel.app> patina.vibetip.help --scope team_66lsrwOyA36bLnIH2eoEXqry
|
|
47
|
+
vercel inspect https://patina.vibetip.help --scope team_66lsrwOyA36bLnIH2eoEXqry
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Smoke the live deterministic payloads:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -fsSL https://patina.vibetip.help/docs/benchmarks/latest.json \
|
|
54
|
+
| node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{const j=JSON.parse(d); console.log(j.fixtureCount, !!j.perLanguage?.ko?.byDetector?.koDiagnostics)})"
|
|
55
|
+
|
|
56
|
+
curl -fsSL https://patina.vibetip.help/analyzer.js \
|
|
57
|
+
| grep -E "DEFAULT_KO_DIAGNOSTIC_BANDS|Korean rhythm composite"
|
|
58
|
+
```
|
|
@@ -13,7 +13,7 @@ repos:
|
|
|
13
13
|
rev: main # replace with a release tag after v1 is cut
|
|
14
14
|
hooks:
|
|
15
15
|
- id: patina-score
|
|
16
|
-
args: [--
|
|
16
|
+
args: [--score-threshold, "30", --lang, auto]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Run it locally:
|
|
@@ -33,7 +33,7 @@ npx husky init
|
|
|
33
33
|
// package.json
|
|
34
34
|
{
|
|
35
35
|
"lint-staged": {
|
|
36
|
-
"*.{md,mdx}": "patina-score --
|
|
36
|
+
"*.{md,mdx}": "patina-score --score-threshold 30 --lang auto"
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
```
|
|
@@ -57,7 +57,7 @@ pre-commit:
|
|
|
57
57
|
commands:
|
|
58
58
|
patina-score:
|
|
59
59
|
glob: "*.{md,mdx}"
|
|
60
|
-
run: npx patina-score --
|
|
60
|
+
run: npx patina-score --score-threshold 30 --lang auto {staged_files}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
A repo-local Lefthook command if this repository is vendored or checked out:
|
|
@@ -67,11 +67,11 @@ pre-commit:
|
|
|
67
67
|
commands:
|
|
68
68
|
patina-score:
|
|
69
69
|
glob: "*.{md,mdx}"
|
|
70
|
-
run: node scripts/precommit-score.mjs --
|
|
70
|
+
run: node scripts/precommit-score.mjs --score-threshold 30 --lang auto {staged_files}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## Tuning
|
|
74
74
|
|
|
75
|
-
- `--
|
|
75
|
+
- `--score-threshold 30` means fail when more than 30% of prose paragraphs in a file trip a hot signal.
|
|
76
76
|
- `--lang auto` infers language from filename and Unicode ranges; pass `ko`, `en`, `zh`, or `ja` when a repo is single-language.
|
|
77
77
|
- Use this as a discussion prompt, not as an accusation. See [ETHICS.md](../ETHICS.md).
|
|
@@ -21,8 +21,9 @@ The dry run verifies:
|
|
|
21
21
|
Publishing the npm packages is intended for `v*.*.*` tags:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
25
|
-
git
|
|
24
|
+
VERSION=v4.0.0
|
|
25
|
+
git tag "$VERSION"
|
|
26
|
+
git push origin "$VERSION"
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
Required secret:
|
|
@@ -39,5 +40,6 @@ the container distribution issue is still open. Maintainers can run the
|
|
|
39
40
|
experimental image path manually after npm verification:
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
|
|
43
|
+
VERSION=v4.0.0
|
|
44
|
+
gh workflow run release.yml --ref "$VERSION" -f publish=false -f publish_ghcr=true
|
|
43
45
|
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Static-site Generator Stencils
|
|
2
|
+
|
|
3
|
+
These recipes wire Patina into build-time checks for Markdown sites. They are intentionally in-repo examples, not standalone npm packages.
|
|
4
|
+
|
|
5
|
+
All examples assume one of these authentication paths:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export PATINA_API_KEY=...
|
|
9
|
+
# or tell the example scripts to use a logged-in local CLI backend:
|
|
10
|
+
export PATINA_BACKEND=codex-cli
|
|
11
|
+
# optional: use an installed binary instead of `npx --yes patina-cli`
|
|
12
|
+
export PATINA_BIN=patina
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For CI, prefer score gates over automatic rewrites:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
patina --lang en --backend codex-cli --score --exit-on 30 docs/**/*.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Hosted playground
|
|
22
|
+
|
|
23
|
+
The public audit-only playground is documented in [playground.md](playground.md) and targets [patina.vibetip.help](https://patina.vibetip.help/). It is a no-build static Vercel app, not a rewrite service.
|
|
24
|
+
|
|
25
|
+
## Hugo: JSON data + shortcode
|
|
26
|
+
|
|
27
|
+
Use a small Node helper to score content and write a Hugo data file:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node examples/integrations/hugo/scripts/patina-scores.mjs content/posts data/patina-scores.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then render a badge in a template or post body:
|
|
34
|
+
|
|
35
|
+
```go-html-template
|
|
36
|
+
{{</* patina-score path="content/posts/launch.md" */>}}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Files:
|
|
40
|
+
|
|
41
|
+
- [`examples/integrations/hugo/scripts/patina-scores.mjs`](../../examples/integrations/hugo/scripts/patina-scores.mjs)
|
|
42
|
+
- [`examples/integrations/hugo/layouts/shortcodes/patina-score.html`](../../examples/integrations/hugo/layouts/shortcodes/patina-score.html)
|
|
43
|
+
|
|
44
|
+
## Astro: `astro:build:start` hook
|
|
45
|
+
|
|
46
|
+
Astro integrations can fail the build before pages render. The example integration scans Markdown under `src/content` and runs `patina --score --exit-on 30`.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// astro.config.mjs
|
|
50
|
+
import patinaAudit from './examples/integrations/astro/patina-audit-integration.mjs';
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
integrations: [patinaAudit({ contentDir: 'src/content', lang: 'en', threshold: 30 })],
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
File: [`examples/integrations/astro/patina-audit-integration.mjs`](../../examples/integrations/astro/patina-audit-integration.mjs)
|
|
58
|
+
|
|
59
|
+
## Next.js MDX: remark warning plugin
|
|
60
|
+
|
|
61
|
+
The Next.js example exports a remark plugin that scores the Markdown text during MDX compilation and emits a VFile warning when the threshold is exceeded. Teams can decide whether warnings fail CI.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// next.config.mjs
|
|
65
|
+
import createMDX from '@next/mdx';
|
|
66
|
+
import patinaRemark from './examples/integrations/nextjs/remark-patina-score.mjs';
|
|
67
|
+
|
|
68
|
+
const withMDX = createMDX({
|
|
69
|
+
options: {
|
|
70
|
+
remarkPlugins: [[patinaRemark, { lang: 'en', threshold: 30 }]],
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export default withMDX({ pageExtensions: ['js', 'jsx', 'md', 'mdx'] });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
File: [`examples/integrations/nextjs/remark-patina-score.mjs`](../../examples/integrations/nextjs/remark-patina-score.mjs)
|
|
78
|
+
|
|
79
|
+
## Guardrails
|
|
80
|
+
|
|
81
|
+
- Keep v1 in-repo; do not publish a package until at least one real site uses the stencil.
|
|
82
|
+
- Do not send private drafts to third-party backends from CI unless the project owner opted in.
|
|
83
|
+
- Use `--exit-on` for gating and leave rewrites as a local author action.
|