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.
Files changed (193) hide show
  1. package/.patina.default.yaml +29 -29
  2. package/CHANGELOG.md +53 -0
  3. package/NOTICE +21 -0
  4. package/README.md +117 -224
  5. package/README_JA.md +134 -77
  6. package/README_KR.md +132 -74
  7. package/README_ZH.md +137 -80
  8. package/SKILL.md +11 -20
  9. package/artifacts/rebaseline-2025/README.md +147 -0
  10. package/artifacts/rebaseline-2025/human-controls.public.jsonl +250 -0
  11. package/artifacts/rebaseline-2025/intake.example.jsonl +2 -0
  12. package/artifacts/rebaseline-2025/intake.local.example.jsonl +25 -0
  13. package/artifacts/rebaseline-2025/prompts.template.jsonl +7 -0
  14. package/artifacts/rebaseline-2025/sources.ko-public.jsonl +39 -0
  15. package/assets/brand/patina-badge.svg +18 -0
  16. package/assets/brand/patina-mark.svg +8 -0
  17. package/assets/demo/README.md +79 -0
  18. package/core/scoring.md +12 -12
  19. package/core/standalone-prompt.md +3 -1
  20. package/core/stylometry.md +93 -22
  21. package/docs/API.md +1554 -0
  22. package/docs/AUTHENTICATION.md +50 -26
  23. package/docs/AUTHENTICATION_KR.md +54 -29
  24. package/docs/BRANDING.md +9 -8
  25. package/docs/CLI.md +55 -14
  26. package/docs/COOKBOOK.md +8 -21
  27. package/docs/DEMO.md +32 -5
  28. package/docs/EXIT-CODES.md +2 -3
  29. package/docs/FALSE-POSITIVES.md +63 -0
  30. package/docs/FAQ.md +9 -1
  31. package/docs/FAQ_KR.md +3 -1
  32. package/docs/FLAG-PARITY.md +33 -47
  33. package/docs/ISSUE-WAVES.md +57 -0
  34. package/docs/PATTERNS-EN.md +67 -3
  35. package/docs/PATTERNS-JA.md +68 -2
  36. package/docs/PATTERNS-KO.md +70 -7
  37. package/docs/PATTERNS-ZH.md +67 -3
  38. package/docs/PATTERNS.md +5 -5
  39. package/docs/RESEARCH-DOCS-PLATFORM.md +54 -0
  40. package/docs/ROADMAP.md +46 -66
  41. package/docs/TRANSLATIONESE-KO.md +51 -0
  42. package/docs/audits/2026-05-deep-research.md +3 -1
  43. package/docs/benchmarks/README.md +51 -0
  44. package/docs/benchmarks/detector-comparison.json +69 -9
  45. package/docs/benchmarks/detector-comparison.md +10 -5
  46. package/docs/benchmarks/katfish-ko-latest.json +657 -0
  47. package/docs/benchmarks/katfish-ko-latest.md +77 -0
  48. package/docs/benchmarks/latest.json +1183 -108
  49. package/docs/benchmarks/latest.md +84 -60
  50. package/docs/benchmarks/lexicon-freshness-en-2026-05-22.json +1121 -0
  51. package/docs/benchmarks/lexicon-freshness-en-2026-05-22.md +136 -0
  52. package/docs/benchmarks/rebaseline-latest.json +381 -0
  53. package/docs/benchmarks/rebaseline-latest.md +121 -0
  54. package/docs/benchmarks/register-stratified-latest.json +164 -0
  55. package/docs/benchmarks/register-stratified-latest.md +99 -0
  56. package/docs/benchmarks/register-stratified.md +43 -0
  57. package/docs/integrations/github-action.md +44 -11
  58. package/docs/integrations/playground.md +58 -0
  59. package/docs/integrations/pre-commit.md +5 -5
  60. package/docs/integrations/release.md +5 -3
  61. package/docs/integrations/static-sites.md +83 -0
  62. package/docs/research/2025-rebaseline-plan.md +71 -2
  63. package/docs/research/2026-rebaseline.md +102 -0
  64. package/docs/research/adversarial-mps.md +41 -0
  65. package/docs/research/ai-human-metrics.md +35 -23
  66. package/docs/research/human-eval-panel.md +42 -0
  67. package/docs/research/judge-agreement.md +24 -0
  68. package/docs/research/ko-2025-corpus-sources.md +135 -0
  69. package/docs/research/lexicon-freshness-audit.md +64 -0
  70. package/docs/research/zh-ja-lexicon-calibration.md +60 -0
  71. package/docs/social/patina-launch-copy.md +173 -100
  72. package/docs/social/patina-launch-execution.md +94 -0
  73. package/docs/social/patina-launch-korean-first.md +83 -0
  74. package/docs/social/signs-of-ai-writing.md +26 -0
  75. package/docs/social/signs-of-ai-writing_KR.md +26 -0
  76. package/lexicon/ai-en.md +21 -24
  77. package/lexicon/ai-ja.md +158 -0
  78. package/lexicon/ai-ko.md +9 -9
  79. package/lexicon/ai-zh.md +158 -0
  80. package/lexicon/provenance/ai-en.json +970 -0
  81. package/lexicon/provenance/ai-ja.json +542 -0
  82. package/lexicon/provenance/ai-ko.json +866 -0
  83. package/lexicon/provenance/ai-zh.json +542 -0
  84. package/package.json +49 -8
  85. package/patterns/en-communication.md +5 -0
  86. package/patterns/en-content.md +5 -0
  87. package/patterns/en-filler.md +5 -0
  88. package/patterns/en-language.md +29 -1
  89. package/patterns/en-structure.md +5 -0
  90. package/patterns/en-style.md +5 -0
  91. package/patterns/en-viral-hook.md +42 -2
  92. package/patterns/ja-communication.md +5 -0
  93. package/patterns/ja-content.md +5 -0
  94. package/patterns/ja-filler.md +5 -0
  95. package/patterns/ja-language.md +33 -1
  96. package/patterns/ja-structure.md +12 -0
  97. package/patterns/ja-style.md +5 -0
  98. package/patterns/ja-viral-hook.md +41 -2
  99. package/patterns/ko-communication.md +5 -0
  100. package/patterns/ko-content.md +5 -0
  101. package/patterns/ko-filler.md +5 -0
  102. package/patterns/ko-language.md +33 -1
  103. package/patterns/ko-structure.md +25 -6
  104. package/patterns/ko-style.md +5 -0
  105. package/patterns/ko-viral-hook.md +38 -2
  106. package/patterns/zh-communication.md +5 -0
  107. package/patterns/zh-content.md +5 -0
  108. package/patterns/zh-filler.md +5 -0
  109. package/patterns/zh-language.md +37 -1
  110. package/patterns/zh-structure.md +12 -0
  111. package/patterns/zh-style.md +5 -0
  112. package/patterns/zh-viral-hook.md +38 -2
  113. package/playground/README.md +55 -0
  114. package/playground/analytics.js +4 -0
  115. package/playground/analyzer.js +883 -0
  116. package/playground/app.js +157 -0
  117. package/playground/data/lexicons.js +343 -0
  118. package/playground/index.html +138 -0
  119. package/playground/styles.css +267 -0
  120. package/profiles/namuwiki.md +111 -0
  121. package/scripts/adversarial-mps-report.mjs +201 -0
  122. package/scripts/badge-json.mjs +79 -0
  123. package/scripts/benchmark-report.mjs +56 -9
  124. package/scripts/check-release-metadata.mjs +0 -2
  125. package/scripts/detector-comparison.mjs +7 -7
  126. package/scripts/generate-playground-data.mjs +77 -0
  127. package/scripts/katfish-calibration.mjs +464 -0
  128. package/scripts/lexicon-freshness.mjs +485 -0
  129. package/scripts/lint.mjs +1 -1
  130. package/scripts/precommit-score.mjs +4 -3
  131. package/scripts/prose-score.mjs +81 -5
  132. package/scripts/rebaseline-intake.mjs +242 -0
  133. package/scripts/rebaseline-score.mjs +268 -0
  134. package/scripts/rebaseline-summary.mjs +773 -0
  135. package/scripts/rebaseline-web-collect.mjs +410 -0
  136. package/scripts/update-benchmark-ranges.mjs +1 -0
  137. package/src/api.js +69 -105
  138. package/src/auth.js +50 -2
  139. package/src/backends/claude-cli.js +19 -4
  140. package/src/backends/codex-cli.js +19 -3
  141. package/src/backends/contract.js +230 -1
  142. package/src/backends/gemini-cli.js +18 -5
  143. package/src/backends/index.js +87 -12
  144. package/src/backends/kimi-cli.js +161 -0
  145. package/src/cli.js +577 -567
  146. package/src/commands/doctor.js +2 -2
  147. package/src/config.js +29 -0
  148. package/src/errors.js +53 -1
  149. package/src/features/discourse-tells.js +68 -0
  150. package/src/features/index.js +82 -8
  151. package/src/features/lexicon.js +40 -6
  152. package/src/features/markup-leakage.js +69 -0
  153. package/src/features/segment.js +41 -0
  154. package/src/features/signal-strength.js +81 -0
  155. package/src/features/stylometry.js +231 -1
  156. package/src/features/translationese.js +127 -0
  157. package/src/loader.js +76 -0
  158. package/src/logger.js +22 -23
  159. package/src/model-defaults.js +55 -0
  160. package/src/ouroboros.js +31 -0
  161. package/src/output.js +102 -90
  162. package/src/prompt-builder.js +103 -68
  163. package/src/providers.js +51 -4
  164. package/src/scoring.js +210 -2
  165. package/src/security.js +75 -0
  166. package/tests/fixtures/live-quality/en/public-docs-01.md +26 -0
  167. package/tests/fixtures/live-quality/ko/public-docs-01.md +26 -0
  168. package/tests/fixtures/suspect-zones/expected-ranges.json +207 -16
  169. package/tests/fixtures/suspect-zones/ja/ai/ja-ai-04-lexicon.md +11 -0
  170. package/tests/fixtures/suspect-zones/ja/natural/ja-nat-04-lexicon-cold.md +11 -0
  171. package/tests/fixtures/suspect-zones/ko/ai/ko-ai-02.md +4 -5
  172. package/tests/fixtures/suspect-zones/ko/ai/ko-ai-07-ko-diagnostic.md +11 -0
  173. package/tests/fixtures/suspect-zones/zh/ai/zh-ai-04-lexicon.md +11 -0
  174. package/tests/fixtures/suspect-zones/zh/natural/zh-nat-04-lexicon-cold.md +11 -0
  175. package/tests/quality/README.md +188 -11
  176. package/tests/quality/adversarial-mps/fixtures.jsonl +10 -0
  177. package/tests/quality/benchmark.mjs +39 -1
  178. package/tests/quality/dogfood.mjs +5 -3
  179. package/tests/quality/live-fixtures.jsonl +2 -0
  180. package/tests/quality/live-quality.mjs +596 -0
  181. package/tests/quality/ranking-metrics.mjs +136 -0
  182. package/tests/quality/rebaseline-manifest.example.jsonl +5 -0
  183. package/vercel.json +53 -0
  184. package/SKILL-MAX.md +0 -455
  185. package/docs/internal/HARNESS.md +0 -14
  186. package/docs/internal/README.md +0 -14
  187. package/docs/internal/WARP.md +0 -23
  188. package/patina-max/SKILL.md +0 -523
  189. package/patina-max/composite.py +0 -457
  190. package/src/cache.js +0 -106
  191. package/src/commands/init.js +0 -208
  192. package/src/manifest.js +0 -162
  193. 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@main # replace with @v1 after npm publish + action tag
24
+ - uses: devswha/patina-action@v1
27
25
  with:
28
- patina-package: github:devswha/patina # remove after patina-cli@latest is on npm
29
- report-threshold: 30
26
+ score-threshold: 30
30
27
  lang: auto
31
28
  comment: true
32
29
  ```
33
30
 
34
- Once `patina-cli` is on npm and `devswha/patina-action@v1` is tagged, the stable form is:
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
+ [![patina](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/<owner>/<repo>/<badge-branch>/patina-badge.json)](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
- - uses: actions/checkout@v6
38
- - uses: devswha/patina-action@v1
39
- with:
40
- score-threshold: 30
41
- comment: true
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
+ [![patina](https://raw.githubusercontent.com/devswha/patina/main/assets/brand/patina-badge.svg)](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: [--gate, "30", --lang, auto]
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 --gate 30 --lang auto"
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 --gate 30 --lang auto {staged_files}
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 --gate 30 --lang auto {staged_files}
70
+ run: node scripts/precommit-score.mjs --score-threshold 30 --lang auto {staged_files}
71
71
  ```
72
72
 
73
73
  ## Tuning
74
74
 
75
- - `--gate 30` means fail when more than 30% of prose paragraphs in a file trip a hot signal.
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
- git tag v3.11.0
25
- git push origin v3.11.0
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
- gh workflow run release.yml --ref v3.11.0 -f publish=false -f publish_ghcr=true
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.