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
package/docs/API.md ADDED
@@ -0,0 +1,1554 @@
1
+ # API Reference
2
+
3
+ This file is generated from JSDoc comments in `src/*.js`.
4
+ Run `npm run docs:api` after changing public exports or their JSDoc.
5
+
6
+ ## Worked example: programmatic scoring
7
+
8
+ The package currently publishes these modules for CLI reuse, but deep imports should be treated as unstable until an explicit `exports` map is introduced.
9
+
10
+ Use `scoreText` directly when you want patina's score envelope inside another Node.js tool. This example injects a mock `callLLM` so the snippet is deterministic; replace it with the default HTTP caller plus provider credentials in production.
11
+
12
+ ```js
13
+ import { getRepoRoot, loadConfig } from 'patina-cli/src/config.js';
14
+ import { loadPatterns } from 'patina-cli/src/loader.js';
15
+ import { scoreText } from 'patina-cli/src/scoring.js';
16
+
17
+ const config = loadConfig();
18
+ config.language = 'en';
19
+
20
+ const patterns = loadPatterns(getRepoRoot(), config.language);
21
+ const text = 'Coffee has emerged as a pivotal cultural phenomenon.';
22
+
23
+ const result = await scoreText({
24
+ text,
25
+ config,
26
+ patterns,
27
+ model: 'deterministic-example',
28
+ callLLM: async () => JSON.stringify({
29
+ categories: {
30
+ content: { detected: 1, sum: 1, max: 18, score: 5.6, weighted: 1.7 },
31
+ },
32
+ overall: 24,
33
+ interpretation: 'mostly human',
34
+ }),
35
+ });
36
+
37
+ console.log(result.overall); // 24 unless deterministic shadow scoring reconciles upward
38
+ console.log(result.interpretation); // mostly human
39
+ ```
40
+
41
+ ## Generated reference
42
+
43
+ <!-- Generated by scripts/generate-api-docs.mjs using jsdoc-to-markdown. Do not edit by hand. -->
44
+
45
+ ## Classes
46
+
47
+ <a name="HttpError"></a>
48
+ ### HttpError
49
+
50
+ Error raised for non-2xx HTTP responses from an LLM provider.
51
+
52
+ **Kind**: global class
53
+
54
+ #### Constructor parameters
55
+
56
+ | Param | Type | Description |
57
+ | --- | --- | --- |
58
+ | status | `number` | HTTP status code returned by the provider. |
59
+ | body | `string` | Response body text, truncated in the message. |
60
+ | retryAfter | `string` \| `null` | Raw Retry-After response header, if present. |
61
+
62
+ **Example**
63
+
64
+ ```js
65
+ throw new HttpError(429, 'rate limit', '2');
66
+ ```
67
+
68
+ <a name="PatinaCliError"></a>
69
+ ### PatinaCliError
70
+
71
+ Structured CLI error with separate what/why/action fields and exit code.
72
+
73
+ **Kind**: global class
74
+
75
+ #### Constructor parameters
76
+
77
+ | Param | Type | Description |
78
+ | --- | --- | --- |
79
+ | options | `object` | Error fields. |
80
+ | options.what | `string` | Short failure headline. |
81
+ | options.why | `string` | Explanation of the failure. |
82
+ | options.action | `string` | Suggested user action. |
83
+ | [options.exitCode=1] | `number` | Process exit code. |
84
+
85
+ **Example**
86
+
87
+ ```js
88
+ throw new PatinaCliError({ what: 'missing input', why: 'No file was provided', action: 'Pass a file path.' });
89
+ ```
90
+
91
+ ## Constants
92
+
93
+ <dl>
94
+ <dt><a href="#DEFAULT_TEMPERATURE">DEFAULT_TEMPERATURE</a> : <code>number</code></dt>
95
+ <dd><p>Default sampling temperature for OpenAI-compatible chat completion calls.</p>
96
+ </dd>
97
+ <dt><a href="#HTTP_KEY_ENV_VARS">HTTP_KEY_ENV_VARS</a> : <code>Array.&lt;string&gt;</code></dt>
98
+ <dd><p>Environment variable names checked for HTTP provider authentication.</p>
99
+ </dd>
100
+ <dt><a href="#DEFAULT_HTTP_KEY_ENV_VARS">DEFAULT_HTTP_KEY_ENV_VARS</a> : <code>Array.&lt;string&gt;</code></dt>
101
+ <dd><p>Default key lookup order for the OpenAI-compatible HTTP provider.</p>
102
+ </dd>
103
+ <dt><a href="#defaultLogger">defaultLogger</a> : <code>Object</code></dt>
104
+ <dd><p>Default stderr logger used by simple callers.</p>
105
+ </dd>
106
+ <dt><a href="#PROVIDERS">PROVIDERS</a> : <code>Record.&lt;string, {name: string, baseURL: string, apiKeyEnv: string, defaultModel: string, freeTier: boolean, note: string}&gt;</code></dt>
107
+ <dd><p>Built-in OpenAI-compatible provider presets.</p>
108
+ </dd>
109
+ <dt><a href="#DEFAULT_DETERMINISTIC_DIVERGENCE_THRESHOLD">DEFAULT_DETERMINISTIC_DIVERGENCE_THRESHOLD</a> : <code>number</code></dt>
110
+ <dd><p>Default maximum delta before deterministic and LLM scores are reconciled upward.</p>
111
+ </dd>
112
+ <dt><a href="#LEAKAGE_SCORE_FLOOR">LEAKAGE_SCORE_FLOOR</a> : <code>number</code></dt>
113
+ <dd><p>Score floor applied when deterministic markup-leakage is detected.</p>
114
+ <p>Model-output leakage (issue #332) is near-proof-grade: a single token that
115
+ LLM tooling injects and humans never type. Unlike the stylometric/lexical
116
+ signals it is decisive on its own, so any hit short-circuits the deterministic
117
+ <code>overall</code> into the &#39;heavily AI&#39; band (&gt;70) regardless of the per-paragraph
118
+ hot ratio. It is a floor, not a hard 100, because the surrounding prose may
119
+ still be genuinely human and we avoid claiming absolute proof.</p>
120
+ </dd>
121
+ </dl>
122
+
123
+ ## Functions
124
+
125
+ <dl>
126
+ <dt><a href="#isRetryable">isRetryable(err)</a> ⇒ <code>boolean</code></dt>
127
+ <dd><p>Decide whether an LLM call failure should be retried.</p>
128
+ </dd>
129
+ <dt><a href="#computeBackoffMs">computeBackoffMs(attempt, retryAfter, [opts])</a> ⇒ <code>number</code></dt>
130
+ <dd><p>Compute retry delay from Retry-After or exponential backoff with jitter.</p>
131
+ </dd>
132
+ <dt><a href="#callLLM">callLLM(options)</a> ⇒ <code>Promise.&lt;string&gt;</code></dt>
133
+ <dd><p>Call an OpenAI-compatible chat completions endpoint with retries, timeout, and abort support.</p>
134
+ </dd>
135
+ <dt><a href="#providerHttpKeyEnvVars">providerHttpKeyEnvVars([providerApiKeyEnv])</a> ⇒ <code>Array.&lt;string&gt;</code></dt>
136
+ <dd><p>Build the key lookup order for a selected provider.</p>
137
+ </dd>
138
+ <dt><a href="#inspectHttpApiKeySource">inspectHttpApiKeySource([options])</a> ⇒ <code>Object</code></dt>
139
+ <dd><p>Inspect where an HTTP API key would be read from without exposing the secret.</p>
140
+ </dd>
141
+ <dt><a href="#resolveHttpApiKey">resolveHttpApiKey([options])</a> ⇒ <code>string</code> | <code>undefined</code></dt>
142
+ <dd><p>Resolve the HTTP API key from a key file or environment.</p>
143
+ </dd>
144
+ <dt><a href="#main">main(args)</a> ⇒ <code>Promise.&lt;void&gt;</code></dt>
145
+ <dd><p>Run the patina CLI command dispatcher.</p>
146
+ </dd>
147
+ <dt><a href="#createCancellationController">createCancellationController([options])</a> ⇒ <code>Object</code></dt>
148
+ <dd><p>Create a SIGINT-aware cancellation controller for long-running CLI operations.</p>
149
+ </dd>
150
+ <dt><a href="#resolveProfileForLanguage">resolveProfileForLanguage(profileName, lang, [logger])</a> ⇒ <code>string</code></dt>
151
+ <dd><p>Resolve a profile name against language-specific profile limits.</p>
152
+ </dd>
153
+ <dt><a href="#loadConfig">loadConfig([path])</a> ⇒ <code>object</code></dt>
154
+ <dd><p>Load default config and merge global/project .patina.yaml overrides.</p>
155
+ </dd>
156
+ <dt><a href="#getRepoRoot">getRepoRoot()</a> ⇒ <code>string</code></dt>
157
+ <dd><p>Return the repository root inferred from this source file location.</p>
158
+ </dd>
159
+ <dt><a href="#resolveTone">resolveTone(options)</a> ⇒ <code>Object</code></dt>
160
+ <dd><p>Resolve CLI/config tone settings into prompt-ready tone metadata.</p>
161
+ </dd>
162
+ <dt><a href="#inputError">inputError(what, why, action)</a> ⇒ <code>PatinaCliError</code></dt>
163
+ <dd><p>Create a user-input error that should exit with code 2.</p>
164
+ </dd>
165
+ <dt><a href="#runtimeError">runtimeError(what, why, action)</a> ⇒ <code>PatinaCliError</code></dt>
166
+ <dd><p>Create a runtime error that should exit with code 1.</p>
167
+ </dd>
168
+ <dt><a href="#renderCliError">renderCliError(err)</a> ⇒ <code>string</code></dt>
169
+ <dd><p>Render any thrown value into the patina CLI error format.</p>
170
+ </dd>
171
+ <dt><a href="#getExitCode">getExitCode(err, [fallback])</a> ⇒ <code>number</code></dt>
172
+ <dd><p>Extract a safe process exit code from an error-like value.</p>
173
+ </dd>
174
+ <dt><a href="#loadFile">loadFile(path)</a> ⇒ <code>string</code></dt>
175
+ <dd><p>Read a UTF-8 text file.</p>
176
+ </dd>
177
+ <dt><a href="#splitFrontmatter">splitFrontmatter(content)</a> ⇒ <code>Object</code></dt>
178
+ <dd><p>Split Markdown-style YAML frontmatter from a document body.</p>
179
+ </dd>
180
+ <dt><a href="#loadPatterns">loadPatterns(repoRoot, lang, [skipPatterns])</a> ⇒ <code>Array.&lt;{file: string, frontmatter: (object|null), body: string, isStructure: boolean, isScoreOnly: boolean}&gt;</code></dt>
181
+ <dd><p>Load language-specific pattern packs from patterns/{lang}-*.md.</p>
182
+ </dd>
183
+ <dt><a href="#loadProfile">loadProfile(repoRoot, profileName)</a> ⇒ <code>Object</code></dt>
184
+ <dd><p>Load a named profile from profiles/{profileName}.md after path validation.</p>
185
+ </dd>
186
+ <dt><a href="#loadCoreFile">loadCoreFile(repoRoot, filename)</a> ⇒ <code>Object</code></dt>
187
+ <dd><p>Load a Markdown file from the core/ directory.</p>
188
+ </dd>
189
+ <dt><a href="#loadInputText">loadInputText(path)</a> ⇒ <code>string</code></dt>
190
+ <dd><p>Read user input text from disk.</p>
191
+ </dd>
192
+ <dt><a href="#loadVoiceSample">loadVoiceSample(path)</a> ⇒ <code>Object</code></dt>
193
+ <dd><p>Load up to three non-empty paragraphs from a voice sample file.</p>
194
+ </dd>
195
+ <dt><a href="#toneToBackboneProfile">toneToBackboneProfile(tone)</a> ⇒ <code>string</code> | <code>null</code></dt>
196
+ <dd><p>Map a resolved named tone to its primary backbone profile.</p>
197
+ </dd>
198
+ <dt><a href="#createLogger">createLogger([options])</a> ⇒ <code>Object</code></dt>
199
+ <dd><p>Create a small stderr logger with text and progress modes.</p>
200
+ </dd>
201
+ <dt><a href="#runOuroboros">runOuroboros(options)</a> ⇒ <code>Promise.&lt;{finalText: string, finalScore: number, iterations: number, reason: string, log: Array.&lt;object&gt;}&gt;</code></dt>
202
+ <dd><p>Run the iterative Ouroboros rewrite-and-score loop.</p>
203
+ </dd>
204
+ <dt><a href="#formatOutput">formatOutput(result, mode, [parsed], [opts])</a> ⇒ <code>string</code></dt>
205
+ <dd><p>Format a raw backend result for CLI output mode and requested format.</p>
206
+ </dd>
207
+ <dt><a href="#shouldColorDiff">shouldColorDiff([options])</a></dt>
208
+ <dd></dd>
209
+ <dt><a href="#validateScoreWeights">validateScoreWeights(output, configWeights)</a> ⇒ <code>Array.&lt;string&gt;</code></dt>
210
+ <dd><p>Validate that a model-emitted score table used configured category weights.</p>
211
+ </dd>
212
+ <dt><a href="#stripSelfAudit">stripSelfAudit(body, [options])</a> ⇒ <code>string</code></dt>
213
+ <dd><p>Remove SELF_AUDIT blocks and unwrap the BODY block from rewrite output.</p>
214
+ </dd>
215
+ <dt><a href="#buildDeterministicAuditBackstop">buildDeterministicAuditBackstop(text, [opts])</a> ⇒ <code>string</code></dt>
216
+ <dd><p>Build a deterministic &quot;backstop&quot; section for audit mode. The LLM audit is
217
+ model-dependent (a weak model silently drops 번역투/calques); these signals are
218
+ computed deterministically so they appear regardless of which model ran. ko
219
+ translationese rules are listed even below the hot-density gate, because audit
220
+ is a hint surface, not a verdict.</p>
221
+ </dd>
222
+ <dt><a href="#buildPrompt">buildPrompt(options)</a> ⇒ <code>string</code></dt>
223
+ <dd><p>Build the LLM prompt for rewrite, diff, audit, score, or ouroboros mode.</p>
224
+ </dd>
225
+ <dt><a href="#isShortText">isShortText(text)</a> ⇒ <code>boolean</code></dt>
226
+ <dd><p>Classify whether text should use the short-text scoring boost.</p>
227
+ </dd>
228
+ <dt><a href="#selectProvider">selectProvider(name)</a> ⇒ <code>object</code> | <code>null</code></dt>
229
+ <dd><p>Resolve a provider preset by name.</p>
230
+ </dd>
231
+ <dt><a href="#resolveProviderConfig">resolveProviderConfig(options)</a> ⇒ <code>Object</code></dt>
232
+ <dd><p>Resolve effective API key, base URL, and model from explicit values, provider, and env.</p>
233
+ </dd>
234
+ <dt><a href="#scoreText">scoreText(options)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
235
+ <dd><p>Score text for AI-likeness using an LLM JSON scorer plus deterministic shadow signals.</p>
236
+ </dd>
237
+ <dt><a href="#scoreDeterministicSignals">scoreDeterministicSignals([options])</a> ⇒ <code>object</code> | <code>null</code></dt>
238
+ <dd><p>Compute deterministic stylometry/lexicon AI-likeness signals.</p>
239
+ </dd>
240
+ <dt><a href="#withShadowScore">withShadowScore(parsed, [options])</a> ⇒ <code>object</code></dt>
241
+ <dd><p>Merge an LLM score payload with deterministic shadow-score reconciliation.</p>
242
+ </dd>
243
+ <dt><a href="#reconcileScoreOverall">reconcileScoreOverall([options])</a> ⇒ <code>Object</code></dt>
244
+ <dd><p>Reconcile LLM and deterministic overall scores according to config thresholds.</p>
245
+ </dd>
246
+ <dt><a href="#scoreMPS">scoreMPS(options)</a> ⇒ <code>Promise.&lt;Object&gt;</code></dt>
247
+ <dd><p>Score meaning preservation between original and rewritten text.</p>
248
+ </dd>
249
+ <dt><a href="#interpretScore">interpretScore(score)</a> ⇒ <code>string</code></dt>
250
+ <dd><p>Convert a numeric AI-likeness score to a human-readable band.</p>
251
+ </dd>
252
+ <dt><a href="#lengthRatioPoints">lengthRatioPoints(original, rewritten)</a> ⇒ <code>number</code></dt>
253
+ <dd><p>Score rewritten length ratio on the 0-3 fidelity scale.</p>
254
+ </dd>
255
+ <dt><a href="#scoreFidelity">scoreFidelity(options)</a> ⇒ <code>Promise.&lt;Object&gt;</code></dt>
256
+ <dd><p>Score fidelity between original and rewritten text using length plus LLM criteria.</p>
257
+ </dd>
258
+ <dt><a href="#clamp03">clamp03(v)</a> ⇒ <code>number</code></dt>
259
+ <dd><p>Clamp and round a value into the inclusive 0-3 scoring range.</p>
260
+ </dd>
261
+ <dt><a href="#combinedScore">combinedScore(options)</a> ⇒ <code>number</code></dt>
262
+ <dd><p>Combine AI-likeness, inverted fidelity, and optional deterministic score.</p>
263
+ </dd>
264
+ <dt><a href="#validateProfileName">validateProfileName(name)</a> ⇒ <code>void</code></dt>
265
+ <dd><p>Validate a profile name before resolving profiles/{name}.md.</p>
266
+ </dd>
267
+ <dt><a href="#isLoopbackHost">isLoopbackHost(hostname)</a> ⇒ <code>boolean</code></dt>
268
+ <dd><p>Check whether a hostname is localhost or loopback.</p>
269
+ </dd>
270
+ <dt><a href="#isPrivateOrSpecialIP">isPrivateOrSpecialIP(hostname)</a> ⇒ <code>boolean</code></dt>
271
+ <dd><p>Detect literal private, reserved, link-local, metadata, or multicast IP hosts.</p>
272
+ </dd>
273
+ <dt><a href="#validateBaseURL">validateBaseURL(baseURL, [options])</a> ⇒ <code>void</code></dt>
274
+ <dd><p>Validate a provider base URL before sending prompts and bearer tokens.</p>
275
+ </dd>
276
+ <dt><a href="#shouldAllowInsecureBaseURL">shouldAllowInsecureBaseURL([parsed])</a> ⇒ <code>boolean</code></dt>
277
+ <dd><p>Read CLI/env opt-in for non-loopback HTTP base URLs.</p>
278
+ </dd>
279
+ <dt><a href="#applyInsecureBaseURLOptIn">applyInsecureBaseURLOptIn([parsed])</a> ⇒ <code>void</code></dt>
280
+ <dd><p>Persist CLI insecure-base-url opt-in into process.env for downstream calls.</p>
281
+ </dd>
282
+ <dt><a href="#shouldAllowPrivateBaseURL">shouldAllowPrivateBaseURL([parsed])</a> ⇒ <code>boolean</code></dt>
283
+ <dd><p>Read CLI/env opt-in for private or reserved literal IP base URLs.</p>
284
+ </dd>
285
+ <dt><a href="#applyPrivateBaseURLOptIn">applyPrivateBaseURLOptIn([parsed])</a> ⇒ <code>void</code></dt>
286
+ <dd><p>Persist CLI private-base-url opt-in into process.env for downstream calls.</p>
287
+ </dd>
288
+ </dl>
289
+
290
+ <a name="DEFAULT_TEMPERATURE"></a>
291
+
292
+ ## DEFAULT\_TEMPERATURE : <code>number</code>
293
+ Default sampling temperature for OpenAI-compatible chat completion calls.
294
+
295
+ **Kind**: global constant
296
+ **Example**
297
+ ```js
298
+ const temperature = DEFAULT_TEMPERATURE; // 0.7
299
+ ```
300
+ <a name="HTTP_KEY_ENV_VARS"></a>
301
+
302
+ ## HTTP\_KEY\_ENV\_VARS : <code>Array.&lt;string&gt;</code>
303
+ Environment variable names checked for HTTP provider authentication.
304
+
305
+ **Kind**: global constant
306
+ **Example**
307
+ ```js
308
+ const supported = HTTP_KEY_ENV_VARS.includes('OPENAI_API_KEY');
309
+ ```
310
+ <a name="DEFAULT_HTTP_KEY_ENV_VARS"></a>
311
+
312
+ ## DEFAULT\_HTTP\_KEY\_ENV\_VARS : <code>Array.&lt;string&gt;</code>
313
+ Default key lookup order for the OpenAI-compatible HTTP provider.
314
+
315
+ **Kind**: global constant
316
+ **Example**
317
+ ```js
318
+ const first = DEFAULT_HTTP_KEY_ENV_VARS[0]; // PATINA_API_KEY
319
+ ```
320
+ <a name="defaultLogger"></a>
321
+
322
+ ## defaultLogger : <code>Object</code>
323
+ Default stderr logger used by simple callers.
324
+
325
+ **Kind**: global constant
326
+ **Example**
327
+ ```js
328
+ defaultLogger.info('patina.ready', { message: 'ready' });
329
+ ```
330
+ <a name="PROVIDERS"></a>
331
+
332
+ ## PROVIDERS : <code>Record.&lt;string, {name: string, baseURL: string, apiKeyEnv: string, defaultModel: string, freeTier: boolean, note: string}&gt;</code>
333
+ Built-in OpenAI-compatible provider presets.
334
+
335
+ **Kind**: global constant
336
+ **Example**
337
+ ```js
338
+ const openaiBaseURL = PROVIDERS.openai.baseURL;
339
+ ```
340
+ <a name="DEFAULT_DETERMINISTIC_DIVERGENCE_THRESHOLD"></a>
341
+
342
+ ## DEFAULT\_DETERMINISTIC\_DIVERGENCE\_THRESHOLD : <code>number</code>
343
+ Default maximum delta before deterministic and LLM scores are reconciled upward.
344
+
345
+ **Kind**: global constant
346
+ **Example**
347
+ ```js
348
+ const threshold = DEFAULT_DETERMINISTIC_DIVERGENCE_THRESHOLD;
349
+ ```
350
+ <a name="LEAKAGE_SCORE_FLOOR"></a>
351
+
352
+ ## LEAKAGE\_SCORE\_FLOOR : <code>number</code>
353
+ Score floor applied when deterministic markup-leakage is detected.
354
+
355
+ Model-output leakage (issue #332) is near-proof-grade: a single token that
356
+ LLM tooling injects and humans never type. Unlike the stylometric/lexical
357
+ signals it is decisive on its own, so any hit short-circuits the deterministic
358
+ `overall` into the 'heavily AI' band (>70) regardless of the per-paragraph
359
+ hot ratio. It is a floor, not a hard 100, because the surrounding prose may
360
+ still be genuinely human and we avoid claiming absolute proof.
361
+
362
+ **Kind**: global constant
363
+ <a name="isRetryable"></a>
364
+
365
+ ## isRetryable(err) ⇒ <code>boolean</code>
366
+ Decide whether an LLM call failure should be retried.
367
+
368
+ **Kind**: global function
369
+ **Returns**: <code>boolean</code> - True for retryable HTTP statuses, aborts, and common network failures.
370
+ **Throws**:
371
+
372
+ - <code>Error</code> Does not intentionally throw; unexpected Error-like inputs may still propagate JavaScript runtime failures.
373
+
374
+
375
+ | Param | Type | Description |
376
+ | --- | --- | --- |
377
+ | err | <code>Error</code> \| <code>Object</code> | Error thrown by fetch or [HttpError](HttpError). |
378
+
379
+ **Example**
380
+ ```js
381
+ const retry = isRetryable(new HttpError(429, 'rate limit', '1'));
382
+ ```
383
+ <a name="computeBackoffMs"></a>
384
+
385
+ ## computeBackoffMs(attempt, retryAfter, [opts]) ⇒ <code>number</code>
386
+ Compute retry delay from Retry-After or exponential backoff with jitter.
387
+
388
+ **Kind**: global function
389
+ **Returns**: <code>number</code> - Delay in milliseconds, capped at opts.max.
390
+ **Throws**:
391
+
392
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
393
+
394
+
395
+ | Param | Type | Default | Description |
396
+ | --- | --- | --- | --- |
397
+ | attempt | <code>number</code> | | Zero-based retry attempt. |
398
+ | retryAfter | <code>string</code> \| <code>null</code> \| <code>undefined</code> | | Retry-After seconds or HTTP-date header. |
399
+ | [opts] | <code>object</code> | | Backoff tuning and deterministic test hooks. |
400
+ | [opts.base] | <code>number</code> | <code>1000</code> | Initial exponential backoff in milliseconds. |
401
+ | [opts.max] | <code>number</code> | <code>30000</code> | Maximum returned delay in milliseconds. |
402
+ | [opts.now] | <code>function</code> | | Clock returning epoch milliseconds. |
403
+ | [opts.random] | <code>function</code> | | Random number provider used for jitter. |
404
+
405
+ **Example**
406
+ ```js
407
+ const delay = computeBackoffMs(1, '2'); // 2000
408
+ ```
409
+ <a name="callLLM"></a>
410
+
411
+ ## callLLM(options) ⇒ <code>Promise.&lt;string&gt;</code>
412
+ Call an OpenAI-compatible chat completions endpoint with retries, timeout, and abort support.
413
+
414
+ **Kind**: global function
415
+ **Returns**: <code>Promise.&lt;string&gt;</code> - Assistant message content.
416
+ **Throws**:
417
+
418
+ - <code>HttpError</code> When the provider returns a non-2xx response after retries.
419
+ - <code>Error</code> On abort, timeout, malformed provider payload, or base URL validation failure.
420
+
421
+
422
+ | Param | Type | Default | Description |
423
+ | --- | --- | --- | --- |
424
+ | options | <code>object</code> | | LLM request options. |
425
+ | options.prompt | <code>string</code> | | User prompt sent as the single chat message. |
426
+ | [options.apiKey] | <code>string</code> | | Bearer token for the provider. |
427
+ | [options.baseURL] | <code>string</code> | | OpenAI-compatible API base URL. Defaults to https://api.openai.com/v1. |
428
+ | [options.model] | <code>string</code> | | Model id to request. Defaults to gpt-5.5. |
429
+ | [options.temperature] | <code>number</code> | <code>DEFAULT_TEMPERATURE</code> | Sampling temperature. |
430
+ | [options.seed] | <code>number</code> \| <code>string</code> | | Optional deterministic seed forwarded to the provider. |
431
+ | [options.timeout] | <code>number</code> | <code>120000</code> | Per-attempt timeout in milliseconds. |
432
+ | [options.maxRetries] | <code>number</code> | <code>2</code> | Retry count after the first attempt. |
433
+ | [options.deadline] | <code>number</code> | | Absolute epoch-millisecond deadline for all attempts. |
434
+ | [options.signal] | <code>AbortSignal</code> | | External cancellation signal. |
435
+ | [options.allowInsecureBaseURL] | <code>boolean</code> | <code>false</code> | Allow non-loopback HTTP base URLs. |
436
+ | [options.onResponse] | <code>function</code> | | Callback receiving provider metadata. |
437
+ | [options.sleep] | <code>function</code> | | Injectable sleep function for tests. |
438
+ | [options.now] | <code>function</code> | | Clock returning epoch milliseconds. |
439
+
440
+ **Example**
441
+ ```js
442
+ const text = await callLLM({ prompt: 'Rewrite this', apiKey: process.env.OPENAI_API_KEY });
443
+ ```
444
+ <a name="providerHttpKeyEnvVars"></a>
445
+
446
+ ## providerHttpKeyEnvVars([providerApiKeyEnv]) ⇒ <code>Array.&lt;string&gt;</code>
447
+ Build the key lookup order for a selected provider.
448
+
449
+ **Kind**: global function
450
+ **Returns**: <code>Array.&lt;string&gt;</code> - Unique env var names in lookup order.
451
+ **Throws**:
452
+
453
+ - <code>Error</code> Does not intentionally throw; invalid non-string env names can still propagate JavaScript runtime failures.
454
+
455
+
456
+ | Param | Type | Description |
457
+ | --- | --- | --- |
458
+ | [providerApiKeyEnv] | <code>string</code> | Provider-specific key env var, such as GEMINI_API_KEY. |
459
+
460
+ **Example**
461
+ ```js
462
+ const vars = providerHttpKeyEnvVars('GEMINI_API_KEY');
463
+ ```
464
+ <a name="inspectHttpApiKeySource"></a>
465
+
466
+ ## inspectHttpApiKeySource([options]) ⇒ <code>Object</code>
467
+ Inspect where an HTTP API key would be read from without exposing the secret.
468
+
469
+ **Kind**: global function
470
+ **Returns**: <code>Object</code> - Source diagnostics.
471
+ **Throws**:
472
+
473
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
474
+
475
+
476
+ | Param | Type | Default | Description |
477
+ | --- | --- | --- | --- |
478
+ | [options] | <code>object</code> | | Inspection options. |
479
+ | [options.env] | <code>object</code> | <code>process.env</code> | Environment map to inspect. |
480
+ | [options.readFile] | <code>function</code> | | File reader for PATINA_API_KEY_FILE. |
481
+ | [options.envVars] | <code>Array.&lt;string&gt;</code> | <code>DEFAULT_HTTP_KEY_ENV_VARS</code> | Env vars to check. |
482
+
483
+ **Example**
484
+ ```js
485
+ const source = inspectHttpApiKeySource({ env: { PATINA_API_KEY: 'sk-...' } });
486
+ ```
487
+ <a name="resolveHttpApiKey"></a>
488
+
489
+ ## resolveHttpApiKey([options]) ⇒ <code>string</code> \| <code>undefined</code>
490
+ Resolve the HTTP API key from a key file or environment.
491
+
492
+ **Kind**: global function
493
+ **Returns**: <code>string</code> \| <code>undefined</code> - Resolved key value, or undefined when unauthenticated.
494
+ **Throws**:
495
+
496
+ - <code>PatinaCliError</code> When the configured key file cannot be read or is empty.
497
+
498
+
499
+ | Param | Type | Default | Description |
500
+ | --- | --- | --- | --- |
501
+ | [options] | <code>object</code> | | Resolution options. |
502
+ | [options.apiKeyFile] | <code>string</code> | | Explicit key file path. |
503
+ | [options.env] | <code>object</code> | <code>process.env</code> | Environment map. |
504
+ | [options.readFile] | <code>function</code> | | File reader for key files. |
505
+ | [options.envVars] | <code>Array.&lt;string&gt;</code> | <code>DEFAULT_HTTP_KEY_ENV_VARS</code> | Env var lookup order. |
506
+
507
+ **Example**
508
+ ```js
509
+ const key = resolveHttpApiKey({ env: process.env });
510
+ ```
511
+ <a name="main"></a>
512
+
513
+ ## main(args) ⇒ <code>Promise.&lt;void&gt;</code>
514
+ Run the patina CLI command dispatcher.
515
+
516
+ **Kind**: global function
517
+ **Returns**: <code>Promise.&lt;void&gt;</code> - Resolves after command output is written.
518
+ **Throws**:
519
+
520
+ - <code>Error</code> For validation, provider, file, or runtime failures.
521
+
522
+
523
+ | Param | Type | Description |
524
+ | --- | --- | --- |
525
+ | args | <code>Array.&lt;string&gt;</code> | Command-line arguments excluding node and script path. |
526
+
527
+ **Example**
528
+ ```js
529
+ await main(['--help']);
530
+ ```
531
+ <a name="createCancellationController"></a>
532
+
533
+ ## createCancellationController([options]) ⇒ <code>Object</code>
534
+ Create a SIGINT-aware cancellation controller for long-running CLI operations.
535
+
536
+ **Kind**: global function
537
+ **Returns**: <code>Object</code> - Controller facade.
538
+ **Throws**:
539
+
540
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
541
+
542
+
543
+ | Param | Type | Default | Description |
544
+ | --- | --- | --- | --- |
545
+ | [options] | <code>object</code> | | Cancellation integration points. |
546
+ | [options.processObj] | <code>NodeJS.Process</code> | <code>process</code> | Process-like object used for signal listeners. |
547
+ | [options.stderr] | <code>NodeJS.WritableStream</code> | <code>process.stderr</code> | Stream for fallback cancel messages. |
548
+ | [options.logger] | <code>object</code> \| <code>null</code> | | Optional patina logger. |
549
+
550
+ **Example**
551
+ ```js
552
+ const cancellation = createCancellationController();
553
+ cancellation.install();
554
+ ```
555
+ <a name="resolveProfileForLanguage"></a>
556
+
557
+ ## resolveProfileForLanguage(profileName, lang, [logger]) ⇒ <code>string</code>
558
+ Resolve a profile name against language-specific profile limits.
559
+
560
+ **Kind**: global function
561
+ **Returns**: <code>string</code> - Effective profile name.
562
+ **Throws**:
563
+
564
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
565
+
566
+
567
+ | Param | Type | Description |
568
+ | --- | --- | --- |
569
+ | profileName | <code>string</code> | Requested profile name. |
570
+ | lang | <code>string</code> | Active language code. |
571
+ | [logger] | <code>object</code> | Logger with warn(event, payload). |
572
+
573
+ **Example**
574
+ ```js
575
+ resolveProfileForLanguage('namuwiki', 'en') // 'default'
576
+ ```
577
+ <a name="loadConfig"></a>
578
+
579
+ ## loadConfig([path]) ⇒ <code>object</code>
580
+ Load default config and merge global/project .patina.yaml overrides.
581
+
582
+ **Kind**: global function
583
+ **Returns**: <code>object</code> - Merged patina configuration object.
584
+ **Throws**:
585
+
586
+ - <code>Error</code> When a config file is missing, invalid YAML, or not a mapping.
587
+
588
+
589
+ | Param | Type | Description |
590
+ | --- | --- | --- |
591
+ | [path] | <code>string</code> | Base YAML config path. |
592
+
593
+ **Example**
594
+ ```js
595
+ const config = loadConfig();
596
+ ```
597
+ <a name="getRepoRoot"></a>
598
+
599
+ ## getRepoRoot() ⇒ <code>string</code>
600
+ Return the repository root inferred from this source file location.
601
+
602
+ **Kind**: global function
603
+ **Returns**: <code>string</code> - Absolute repository root path.
604
+ **Throws**:
605
+
606
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
607
+
608
+ **Example**
609
+ ```js
610
+ const root = getRepoRoot();
611
+ ```
612
+ <a name="resolveTone"></a>
613
+
614
+ ## resolveTone(options) ⇒ <code>Object</code>
615
+ Resolve CLI/config tone settings into prompt-ready tone metadata.
616
+
617
+ **Kind**: global function
618
+ **Returns**: <code>Object</code> - Tone metadata.
619
+ **Throws**:
620
+
621
+ - <code>Error</code> When cliTone or configTone is not supported.
622
+
623
+
624
+ | Param | Type | Description |
625
+ | --- | --- | --- |
626
+ | options | <code>object</code> | Tone inputs. |
627
+ | [options.cliTone] | <code>string</code> \| <code>null</code> | CLI tone override. |
628
+ | [options.configTone] | <code>string</code> \| <code>null</code> | Configured tone value. |
629
+ | [options.lang] | <code>string</code> | Active language code. |
630
+
631
+ **Example**
632
+ ```js
633
+ const tone = resolveTone({ cliTone: 'casual', lang: 'ko' });
634
+ ```
635
+ <a name="inputError"></a>
636
+
637
+ ## inputError(what, why, action) ⇒ <code>PatinaCliError</code>
638
+ Create a user-input error that should exit with code 2.
639
+
640
+ **Kind**: global function
641
+ **Returns**: <code>PatinaCliError</code> - Structured input error.
642
+ **Throws**:
643
+
644
+ - <code>Error</code> Does not intentionally throw; returns an Error instance for callers to throw.
645
+
646
+
647
+ | Param | Type | Description |
648
+ | --- | --- | --- |
649
+ | what | <code>string</code> | Short failure headline. |
650
+ | why | <code>string</code> | Explanation of the invalid input. |
651
+ | action | <code>string</code> | Suggested user action. |
652
+
653
+ **Example**
654
+ ```js
655
+ throw inputError('missing input', 'No file was provided.', 'Pass a file path.');
656
+ ```
657
+ <a name="runtimeError"></a>
658
+
659
+ ## runtimeError(what, why, action) ⇒ <code>PatinaCliError</code>
660
+ Create a runtime error that should exit with code 1.
661
+
662
+ **Kind**: global function
663
+ **Returns**: <code>PatinaCliError</code> - Structured runtime error.
664
+ **Throws**:
665
+
666
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
667
+
668
+
669
+ | Param | Type | Description |
670
+ | --- | --- | --- |
671
+ | what | <code>string</code> | Short failure headline. |
672
+ | why | <code>string</code> | Explanation of the runtime failure. |
673
+ | action | <code>string</code> | Suggested user action. |
674
+
675
+ **Example**
676
+ ```js
677
+ throw runtimeError('provider failed', 'The API timed out.', 'Retry later.');
678
+ ```
679
+ <a name="renderCliError"></a>
680
+
681
+ ## renderCliError(err) ⇒ <code>string</code>
682
+ Render any thrown value into the patina CLI error format.
683
+
684
+ **Kind**: global function
685
+ **Returns**: <code>string</code> - Multi-line user-facing error text.
686
+ **Throws**:
687
+
688
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
689
+
690
+
691
+ | Param | Type | Description |
692
+ | --- | --- | --- |
693
+ | err | <code>unknown</code> | Error-like value to render. |
694
+
695
+ **Example**
696
+ ```js
697
+ const message = renderCliError(inputError('bad flag', 'Unknown flag.', 'Run --help.'));
698
+ ```
699
+ <a name="getExitCode"></a>
700
+
701
+ ## getExitCode(err, [fallback]) ⇒ <code>number</code>
702
+ Extract a safe process exit code from an error-like value.
703
+
704
+ **Kind**: global function
705
+ **Returns**: <code>number</code> - Non-negative integer exit code.
706
+ **Throws**:
707
+
708
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
709
+
710
+
711
+ | Param | Type | Default | Description |
712
+ | --- | --- | --- | --- |
713
+ | err | <code>unknown</code> | | Error-like value. |
714
+ | [fallback] | <code>number</code> | <code>1</code> | Exit code used when err.exitCode is absent or invalid. |
715
+
716
+ **Example**
717
+ ```js
718
+ const code = getExitCode(inputError('bad', 'why', 'fix')); // 2
719
+ ```
720
+ <a name="loadFile"></a>
721
+
722
+ ## loadFile(path) ⇒ <code>string</code>
723
+ Read a UTF-8 text file.
724
+
725
+ **Kind**: global function
726
+ **Returns**: <code>string</code> - File contents.
727
+ **Throws**:
728
+
729
+ - <code>Error</code> When the file cannot be read.
730
+
731
+
732
+ | Param | Type | Description |
733
+ | --- | --- | --- |
734
+ | path | <code>string</code> | File path to read. |
735
+
736
+ **Example**
737
+ ```js
738
+ const markdown = loadFile('README.md');
739
+ ```
740
+ <a name="splitFrontmatter"></a>
741
+
742
+ ## splitFrontmatter(content) ⇒ <code>Object</code>
743
+ Split Markdown-style YAML frontmatter from a document body.
744
+
745
+ **Kind**: global function
746
+ **Returns**: <code>Object</code> - Parsed frontmatter and trimmed body.
747
+ **Throws**:
748
+
749
+ - <code>Error</code> When YAML frontmatter is invalid.
750
+
751
+
752
+ | Param | Type | Description |
753
+ | --- | --- | --- |
754
+ | content | <code>string</code> | File contents. |
755
+
756
+ **Example**
757
+ ```js
758
+ const { frontmatter, body } = splitFrontmatter('---\ntitle: x\n---\nBody');
759
+ ```
760
+ <a name="loadPatterns"></a>
761
+
762
+ ## loadPatterns(repoRoot, lang, [skipPatterns]) ⇒ <code>Array.&lt;{file: string, frontmatter: (object\|null), body: string, isStructure: boolean, isScoreOnly: boolean}&gt;</code>
763
+ Load language-specific pattern packs from patterns/{lang}-*.md.
764
+
765
+ **Kind**: global function
766
+ **Returns**: <code>Array.&lt;{file: string, frontmatter: (object\|null), body: string, isStructure: boolean, isScoreOnly: boolean}&gt;</code> - Pattern packs.
767
+ **Throws**:
768
+
769
+ - <code>Error</code> When the patterns directory or a pattern file cannot be read.
770
+
771
+
772
+ | Param | Type | Default | Description |
773
+ | --- | --- | --- | --- |
774
+ | repoRoot | <code>string</code> | | Repository root path. |
775
+ | lang | <code>string</code> | | Language code, such as ko, en, zh, or ja. |
776
+ | [skipPatterns] | <code>Array.&lt;string&gt;</code> | <code>[]</code> | Pack names to omit, without .md. |
777
+
778
+ **Example**
779
+ ```js
780
+ const patterns = loadPatterns(getRepoRoot(), 'en');
781
+ ```
782
+ <a name="loadProfile"></a>
783
+
784
+ ## loadProfile(repoRoot, profileName) ⇒ <code>Object</code>
785
+ Load a named profile from profiles/{profileName}.md after path validation.
786
+
787
+ **Kind**: global function
788
+ **Returns**: <code>Object</code> - Parsed profile document.
789
+ **Throws**:
790
+
791
+ - <code>Error</code> When the profile name is invalid or the file cannot be read.
792
+
793
+
794
+ | Param | Type | Description |
795
+ | --- | --- | --- |
796
+ | repoRoot | <code>string</code> | Repository root path. |
797
+ | profileName | <code>string</code> | Profile file stem. |
798
+
799
+ **Example**
800
+ ```js
801
+ const profile = loadProfile(getRepoRoot(), 'default');
802
+ ```
803
+ <a name="loadCoreFile"></a>
804
+
805
+ ## loadCoreFile(repoRoot, filename) ⇒ <code>Object</code>
806
+ Load a Markdown file from the core/ directory.
807
+
808
+ **Kind**: global function
809
+ **Returns**: <code>Object</code> - Parsed core document.
810
+ **Throws**:
811
+
812
+ - <code>Error</code> When the file cannot be read or frontmatter is invalid.
813
+
814
+
815
+ | Param | Type | Description |
816
+ | --- | --- | --- |
817
+ | repoRoot | <code>string</code> | Repository root path. |
818
+ | filename | <code>string</code> | Core filename, such as scoring.md. |
819
+
820
+ **Example**
821
+ ```js
822
+ const scoring = loadCoreFile(getRepoRoot(), 'scoring.md');
823
+ ```
824
+ <a name="loadInputText"></a>
825
+
826
+ ## loadInputText(path) ⇒ <code>string</code>
827
+ Read user input text from disk.
828
+
829
+ **Kind**: global function
830
+ **Returns**: <code>string</code> - UTF-8 input text.
831
+ **Throws**:
832
+
833
+ - <code>Error</code> When the file cannot be read.
834
+
835
+
836
+ | Param | Type | Description |
837
+ | --- | --- | --- |
838
+ | path | <code>string</code> | Input file path. |
839
+
840
+ **Example**
841
+ ```js
842
+ const text = loadInputText('draft.md');
843
+ ```
844
+ <a name="loadVoiceSample"></a>
845
+
846
+ ## loadVoiceSample(path) ⇒ <code>Object</code>
847
+ Load up to three non-empty paragraphs from a voice sample file.
848
+
849
+ **Kind**: global function
850
+ **Returns**: <code>Object</code> - Voice sample payload.
851
+ **Throws**:
852
+
853
+ - <code>Error</code> When the file is unreadable or has no non-empty paragraphs.
854
+
855
+
856
+ | Param | Type | Description |
857
+ | --- | --- | --- |
858
+ | path | <code>string</code> | Voice sample file path. |
859
+
860
+ **Example**
861
+ ```js
862
+ const sample = loadVoiceSample('voice.md');
863
+ ```
864
+ <a name="toneToBackboneProfile"></a>
865
+
866
+ ## toneToBackboneProfile(tone) ⇒ <code>string</code> \| <code>null</code>
867
+ Map a resolved named tone to its primary backbone profile.
868
+
869
+ **Kind**: global function
870
+ **Returns**: <code>string</code> \| <code>null</code> - Profile name, or null when no mapping exists.
871
+ **Throws**:
872
+
873
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
874
+
875
+
876
+ | Param | Type | Description |
877
+ | --- | --- | --- |
878
+ | tone | <code>string</code> | Tone name. |
879
+
880
+ **Example**
881
+ ```js
882
+ const profile = toneToBackboneProfile('casual'); // blog
883
+ ```
884
+ <a name="createLogger"></a>
885
+
886
+ ## createLogger([options]) ⇒ <code>Object</code>
887
+ Create a small stderr logger with text and progress modes.
888
+
889
+ **Kind**: global function
890
+ **Returns**: <code>Object</code> - Logger facade.
891
+ **Throws**:
892
+
893
+ - <code>Error</code> Propagates stream write errors from the configured output stream.
894
+
895
+
896
+ | Param | Type | Default | Description |
897
+ | --- | --- | --- | --- |
898
+ | [options] | <code>object</code> | | Logger options. |
899
+ | [options.level] | <code>string</code> | <code>&quot;info&quot;</code> | Minimum log level. |
900
+ | [options.quiet] | <code>boolean</code> | <code>false</code> | Suppress all log output. |
901
+ | [options.stream] | <code>NodeJS.WritableStream</code> | <code>process.stderr</code> | Progress stream. |
902
+
903
+ **Example**
904
+ ```js
905
+ const logger = createLogger();
906
+ logger.info('event', { message: 'ready' });
907
+ ```
908
+ <a name="runOuroboros"></a>
909
+
910
+ ## runOuroboros(options) ⇒ <code>Promise.&lt;{finalText: string, finalScore: number, iterations: number, reason: string, log: Array.&lt;object&gt;}&gt;</code>
911
+ Run the iterative Ouroboros rewrite-and-score loop.
912
+
913
+ **Kind**: global function
914
+ **Returns**: <code>Promise.&lt;{finalText: string, finalScore: number, iterations: number, reason: string, log: Array.&lt;object&gt;}&gt;</code> - Final text and iteration log.
915
+ **Throws**:
916
+
917
+ - <code>Error</code> When model calls or scoring fail outside handled schema fallbacks.
918
+
919
+
920
+ | Param | Type | Description |
921
+ | --- | --- | --- |
922
+ | options | <code>object</code> | Ouroboros options. |
923
+ | options.config | <code>object</code> | Effective config with ouroboros settings. |
924
+ | options.patterns | <code>Array.&lt;object&gt;</code> | Loaded pattern packs. |
925
+ | options.profile | <code>object</code> \| <code>null</code> | Parsed profile. |
926
+ | options.voice | <code>object</code> \| <code>null</code> | Parsed voice guide. |
927
+ | [options.voiceSample] | <code>object</code> \| <code>null</code> | Optional voice sample payload. |
928
+ | options.scoring | <code>object</code> \| <code>null</code> | Parsed scoring guide. |
929
+ | options.text | <code>string</code> | Source text to improve. |
930
+ | [options.apiKey] | <code>string</code> | Provider API key. |
931
+ | [options.baseURL] | <code>string</code> | Provider base URL. |
932
+ | [options.model] | <code>string</code> | Model id. |
933
+ | [options.callLLM] | <code>function</code> | LLM implementation. |
934
+ | [options.now] | <code>function</code> | Clock returning epoch milliseconds. |
935
+ | [options.sleep] | <code>function</code> | Sleep helper for tests. |
936
+ | [options.signal] | <code>AbortSignal</code> | External cancellation signal. |
937
+ | [options.logger] | <code>object</code> | patina logger. |
938
+
939
+ **Example**
940
+ ```js
941
+ const result = await runOuroboros({ config, patterns, profile, voice, scoring, text });
942
+ ```
943
+ <a name="formatOutput"></a>
944
+
945
+ ## formatOutput(result, mode, [parsed], [opts]) ⇒ <code>string</code>
946
+ Format a raw backend result for CLI output mode and requested format.
947
+
948
+ **Kind**: global function
949
+ **Returns**: <code>string</code> - User-facing formatted output.
950
+ **Throws**:
951
+
952
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
953
+
954
+
955
+ | Param | Type | Default | Description |
956
+ | --- | --- | --- | --- |
957
+ | result | <code>string</code> \| <code>object</code> | | Backend result or structured mode result. |
958
+ | mode | <code>string</code> | | Output mode: rewrite, diff, audit, score, or ouroboros. |
959
+ | [parsed] | <code>object</code> | <code>{}</code> | Parsed CLI options. |
960
+ | [opts] | <code>object</code> | <code>{}</code> | Formatting options. |
961
+ | [opts.tone] | <code>object</code> \| <code>null</code> | | Tone metadata to append. |
962
+ | [opts.logger] | <code>object</code> | | Logger for output warnings. |
963
+ | [opts.env] | <code>object</code> | | Environment map for color decisions. |
964
+ | [opts.stdout] | <code>object</code> | | Stdout-like stream for color decisions. |
965
+ | [opts.auditBackstop] | <code>string</code> | | Deterministic audit-mode section to append before the tone footer. |
966
+
967
+ **Example**
968
+ ```js
969
+ const output = formatOutput('[BODY]Hi[/BODY]', 'rewrite');
970
+ ```
971
+ <a name="shouldColorDiff"></a>
972
+
973
+ ## shouldColorDiff([options])
974
+ **Kind**: global function
975
+
976
+ | Param | Type |
977
+ | --- | --- |
978
+ | [options] | <code>object</code> |
979
+ | [options.parsed] | <code>object</code> |
980
+ | [options.parsed.noColor] | <code>boolean</code> |
981
+ | [options.env] | <code>Record.&lt;string, (string\|undefined)&gt;</code> |
982
+ | [options.stdout] | <code>object</code> |
983
+ | [options.stdout.isTTY] | <code>boolean</code> |
984
+
985
+ <a name="validateScoreWeights"></a>
986
+
987
+ ## validateScoreWeights(output, configWeights) ⇒ <code>Array.&lt;string&gt;</code>
988
+ Validate that a model-emitted score table used configured category weights.
989
+
990
+ **Kind**: global function
991
+ **Returns**: <code>Array.&lt;string&gt;</code> - Human-readable warnings for missing, mismatched, or unexpected categories.
992
+ **Throws**:
993
+
994
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
995
+
996
+
997
+ | Param | Type | Description |
998
+ | --- | --- | --- |
999
+ | output | <code>string</code> | Score-mode markdown output. |
1000
+ | configWeights | <code>object</code> | Expected category weight map. |
1001
+
1002
+ **Example**
1003
+ ```js
1004
+ const warnings = validateScoreWeights('| content | 0.4 | 1 | 10 | 4 |', { content: 0.4 });
1005
+ ```
1006
+ <a name="stripSelfAudit"></a>
1007
+
1008
+ ## stripSelfAudit(body, [options]) ⇒ <code>string</code>
1009
+ Remove SELF_AUDIT blocks and unwrap the BODY block from rewrite output.
1010
+
1011
+ **Kind**: global function
1012
+ **Returns**: <code>string</code> - Clean user-facing body text.
1013
+ **Throws**:
1014
+
1015
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1016
+
1017
+
1018
+ | Param | Type | Description |
1019
+ | --- | --- | --- |
1020
+ | body | <code>string</code> | Raw model response. |
1021
+ | [options] | <code>object</code> | Strip options. |
1022
+ | [options.logger] | <code>object</code> | Logger for malformed output warnings. |
1023
+
1024
+ **Example**
1025
+ ```js
1026
+ const clean = stripSelfAudit('[BODY]Hello[/BODY]\n[SELF_AUDIT]ok[/SELF_AUDIT]');
1027
+ ```
1028
+ <a name="buildDeterministicAuditBackstop"></a>
1029
+
1030
+ ## buildDeterministicAuditBackstop(text, [opts]) ⇒ <code>string</code>
1031
+ Build a deterministic "backstop" section for audit mode. The LLM audit is
1032
+ model-dependent (a weak model silently drops 번역투/calques); these signals are
1033
+ computed deterministically so they appear regardless of which model ran. ko
1034
+ translationese rules are listed even below the hot-density gate, because audit
1035
+ is a hint surface, not a verdict.
1036
+
1037
+ **Kind**: global function
1038
+ **Returns**: <code>string</code> - Markdown section (empty string when nothing fired).
1039
+
1040
+ | Param | Type | Description |
1041
+ | --- | --- | --- |
1042
+ | text | <code>string</code> | Source text. |
1043
+ | [opts] | <code>object</code> | |
1044
+ | [opts.lang] | <code>string</code> | |
1045
+ | [opts.repoRoot] | <code>string</code> | |
1046
+
1047
+ <a name="buildDeterministicAuditBackstop..rows"></a>
1048
+
1049
+ ### buildDeterministicAuditBackstop~rows : <code>Array.&lt;{signal:string, label:string, severity:string, location:string}&gt;</code>
1050
+ **Kind**: inner constant of [<code>buildDeterministicAuditBackstop</code>](#buildDeterministicAuditBackstop)
1051
+ <a name="buildPrompt"></a>
1052
+
1053
+ ## buildPrompt(options) ⇒ <code>string</code>
1054
+ Build the LLM prompt for rewrite, diff, audit, score, or ouroboros mode.
1055
+
1056
+ **Kind**: global function
1057
+ **Returns**: <code>string</code> - Complete prompt text.
1058
+ **Throws**:
1059
+
1060
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1061
+
1062
+
1063
+ | Param | Type | Default | Description |
1064
+ | --- | --- | --- | --- |
1065
+ | options | <code>object</code> | | Prompt inputs. |
1066
+ | options.config | <code>object</code> | | Effective patina config. |
1067
+ | options.patterns | <code>Array.&lt;object&gt;</code> | | Loaded pattern packs. |
1068
+ | options.profile | <code>object</code> \| <code>null</code> | | Parsed profile document. |
1069
+ | options.voice | <code>object</code> \| <code>null</code> | | Parsed voice guide. |
1070
+ | [options.voiceSample] | <code>object</code> \| <code>null</code> | | Optional voice sample payload. |
1071
+ | options.scoring | <code>object</code> \| <code>null</code> | | Parsed scoring guide. |
1072
+ | options.text | <code>string</code> | | Input text. |
1073
+ | [options.mode] | <code>string</code> | <code>&quot;rewrite&quot;</code> | Output mode. |
1074
+ | [options.tone] | <code>object</code> \| <code>null</code> | <code></code> | Tone resolution metadata. |
1075
+
1076
+ **Example**
1077
+ ```js
1078
+ const prompt = buildPrompt({ config, patterns, profile, voice, scoring, text: 'Draft' });
1079
+ ```
1080
+ <a name="isShortText"></a>
1081
+
1082
+ ## isShortText(text) ⇒ <code>boolean</code>
1083
+ Classify whether text should use the short-text scoring boost.
1084
+
1085
+ **Kind**: global function
1086
+ **Returns**: <code>boolean</code> - True when text is <=200 non-whitespace chars or <=3 paragraphs.
1087
+ **Throws**:
1088
+
1089
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1090
+
1091
+
1092
+ | Param | Type | Description |
1093
+ | --- | --- | --- |
1094
+ | text | <code>string</code> | Text to inspect. |
1095
+
1096
+ **Example**
1097
+ ```js
1098
+ const short = isShortText('A short note.');
1099
+ ```
1100
+ <a name="selectProvider"></a>
1101
+
1102
+ ## selectProvider(name) ⇒ <code>object</code> \| <code>null</code>
1103
+ Resolve a provider preset by name.
1104
+
1105
+ **Kind**: global function
1106
+ **Returns**: <code>object</code> \| <code>null</code> - Provider preset or null.
1107
+ **Throws**:
1108
+
1109
+ - <code>PatinaCliError</code> When name is unknown.
1110
+
1111
+
1112
+ | Param | Type | Description |
1113
+ | --- | --- | --- |
1114
+ | name | <code>string</code> \| <code>null</code> \| <code>undefined</code> | Provider name; falsy returns null. |
1115
+
1116
+ **Example**
1117
+ ```js
1118
+ const provider = selectProvider('openai');
1119
+ ```
1120
+ <a name="resolveProviderConfig"></a>
1121
+
1122
+ ## resolveProviderConfig(options) ⇒ <code>Object</code>
1123
+ Resolve effective API key, base URL, and model from explicit values, provider, and env.
1124
+
1125
+ **Kind**: global function
1126
+ **Returns**: <code>Object</code> - Resolved provider config.
1127
+ **Throws**:
1128
+
1129
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1130
+
1131
+
1132
+ | Param | Type | Description |
1133
+ | --- | --- | --- |
1134
+ | options | <code>object</code> | Provider resolution inputs. |
1135
+ | [options.provider] | <code>object</code> \| <code>null</code> | Provider preset from [selectProvider](#selectProvider). |
1136
+ | [options.apiKey] | <code>string</code> | Explicit API key. |
1137
+ | [options.baseURL] | <code>string</code> | Explicit base URL. |
1138
+ | [options.model] | <code>string</code> | Explicit model id. |
1139
+
1140
+ **Example**
1141
+ ```js
1142
+ const resolved = resolveProviderConfig({ provider: selectProvider('openai') });
1143
+ ```
1144
+ <a name="scoreText"></a>
1145
+
1146
+ ## scoreText(options) ⇒ <code>Promise.&lt;object&gt;</code>
1147
+ Score text for AI-likeness using an LLM JSON scorer plus deterministic shadow signals.
1148
+
1149
+ **Kind**: global function
1150
+ **Returns**: <code>Promise.&lt;object&gt;</code> - Score payload with overall, interpretation, llmScore, and deterministicScore.
1151
+ **Throws**:
1152
+
1153
+ - <code>Error</code> When the operation is aborted.
1154
+
1155
+
1156
+ | Param | Type | Description |
1157
+ | --- | --- | --- |
1158
+ | options | <code>object</code> | Scoring options. |
1159
+ | options.text | <code>string</code> | Text to score. |
1160
+ | options.config | <code>object</code> | Effective patina config. |
1161
+ | options.patterns | <code>Array.&lt;object&gt;</code> | Loaded pattern packs, retained for scorer compatibility. |
1162
+ | [options.apiKey] | <code>string</code> | Provider API key. |
1163
+ | [options.baseURL] | <code>string</code> | Provider base URL. |
1164
+ | [options.model] | <code>string</code> | Model id. |
1165
+ | [options.deadline] | <code>number</code> | Absolute epoch-millisecond deadline. |
1166
+ | [options.signal] | <code>AbortSignal</code> | External cancellation signal. |
1167
+ | [options.callLLM] | <code>function</code> | Injectable LLM implementation. |
1168
+ | [options.logger] | <code>object</code> | patina logger. |
1169
+ | [options.now] | <code>function</code> | Clock returning epoch milliseconds. |
1170
+ | [options.sleep] | <code>function</code> | Sleep helper for tests. |
1171
+
1172
+ **Example**
1173
+ ```js
1174
+ const score = await scoreText({ text: 'Draft', config, patterns, callLLM: async () => '{"categories":{},"overall":20,"interpretation":"mostly human"}' });
1175
+ ```
1176
+ <a name="scoreDeterministicSignals"></a>
1177
+
1178
+ ## scoreDeterministicSignals([options]) ⇒ <code>object</code> \| <code>null</code>
1179
+ Compute deterministic stylometry/lexicon AI-likeness signals.
1180
+
1181
+ **Kind**: global function
1182
+ **Returns**: <code>object</code> \| <code>null</code> - Deterministic score payload, skipped payload, or null when disabled.
1183
+ **Throws**:
1184
+
1185
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1186
+
1187
+
1188
+ | Param | Type | Default | Description |
1189
+ | --- | --- | --- | --- |
1190
+ | [options] | <code>object</code> | | Deterministic scoring options. |
1191
+ | [options.text] | <code>string</code> | | Text to analyze. |
1192
+ | [options.config] | <code>object</code> | <code>{}</code> | Effective config. |
1193
+ | [options.repoRoot] | <code>string</code> | | Repository root for analyzer resources. |
1194
+ | [options.analyzer] | <code>function</code> | | Analyzer implementation. |
1195
+
1196
+ **Example**
1197
+ ```js
1198
+ const deterministic = scoreDeterministicSignals({ text: 'Draft', config });
1199
+ ```
1200
+ <a name="withShadowScore"></a>
1201
+
1202
+ ## withShadowScore(parsed, [options]) ⇒ <code>object</code>
1203
+ Merge an LLM score payload with deterministic shadow-score reconciliation.
1204
+
1205
+ **Kind**: global function
1206
+ **Returns**: <code>object</code> - Score payload preserving llmScore and deterministicScore details.
1207
+ **Throws**:
1208
+
1209
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1210
+
1211
+
1212
+ | Param | Type | Default | Description |
1213
+ | --- | --- | --- | --- |
1214
+ | parsed | <code>object</code> | | Parsed LLM scoring JSON. |
1215
+ | [options] | <code>object</code> | | Reconciliation options. |
1216
+ | [options.deterministicScore] | <code>object</code> \| <code>null</code> | | Deterministic score payload. |
1217
+ | [options.config] | <code>object</code> | <code>{}</code> | Effective config. |
1218
+ | [options.logger] | <code>object</code> | | Logger for reconciliation warnings. |
1219
+
1220
+ **Example**
1221
+ ```js
1222
+ const score = withShadowScore({ overall: 20 }, { deterministicScore: { overall: 25 } });
1223
+ ```
1224
+ <a name="reconcileScoreOverall"></a>
1225
+
1226
+ ## reconcileScoreOverall([options]) ⇒ <code>Object</code>
1227
+ Reconcile LLM and deterministic overall scores according to config thresholds.
1228
+
1229
+ **Kind**: global function
1230
+ **Returns**: <code>Object</code> - Reconciled score and preference source.
1231
+ **Throws**:
1232
+
1233
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1234
+
1235
+
1236
+ | Param | Type | Default | Description |
1237
+ | --- | --- | --- | --- |
1238
+ | [options] | <code>object</code> | | Reconciliation inputs. |
1239
+ | [options.llmOverall] | <code>number</code> \| <code>null</code> | | LLM overall score. |
1240
+ | [options.deterministicScore] | <code>object</code> \| <code>null</code> | | Deterministic score payload. |
1241
+ | [options.config] | <code>object</code> | <code>{}</code> | Effective config. |
1242
+ | [options.logger] | <code>object</code> | | Logger for warnings. |
1243
+
1244
+ **Example**
1245
+ ```js
1246
+ const result = reconcileScoreOverall({ llmOverall: 20, deterministicScore: { overall: 60 } });
1247
+ ```
1248
+ <a name="scoreMPS"></a>
1249
+
1250
+ ## scoreMPS(options) ⇒ <code>Promise.&lt;Object&gt;</code>
1251
+ Score meaning preservation between original and rewritten text.
1252
+
1253
+ **Kind**: global function
1254
+ **Returns**: <code>Promise.&lt;Object&gt;</code> - MPS result.
1255
+ **Throws**:
1256
+
1257
+ - <code>Error</code> When the operation is aborted.
1258
+
1259
+
1260
+ | Param | Type | Description |
1261
+ | --- | --- | --- |
1262
+ | options | <code>object</code> | MPS options. |
1263
+ | options.original | <code>string</code> | Original text. |
1264
+ | options.rewritten | <code>string</code> | Rewritten text. |
1265
+ | [options.apiKey] | <code>string</code> | Provider API key. |
1266
+ | [options.baseURL] | <code>string</code> | Provider base URL. |
1267
+ | [options.model] | <code>string</code> | Model id. |
1268
+ | [options.deadline] | <code>number</code> | Absolute epoch-millisecond deadline. |
1269
+ | [options.signal] | <code>AbortSignal</code> | External cancellation signal. |
1270
+ | [options.callLLM] | <code>function</code> | Injectable LLM implementation. |
1271
+ | [options.logger] | <code>object</code> | patina logger. |
1272
+ | [options.now] | <code>function</code> | Clock returning epoch milliseconds. |
1273
+ | [options.sleep] | <code>function</code> | Sleep helper for tests. |
1274
+
1275
+ **Example**
1276
+ ```js
1277
+ const mps = await scoreMPS({ original: 'A', rewritten: 'A', callLLM: async () => '{"mps":100,"anchors":[]}' });
1278
+ ```
1279
+ <a name="interpretScore"></a>
1280
+
1281
+ ## interpretScore(score) ⇒ <code>string</code>
1282
+ Convert a numeric AI-likeness score to a human-readable band.
1283
+
1284
+ **Kind**: global function
1285
+ **Returns**: <code>string</code> - Interpretation band.
1286
+ **Throws**:
1287
+
1288
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1289
+
1290
+
1291
+ | Param | Type | Description |
1292
+ | --- | --- | --- |
1293
+ | score | <code>number</code> | AI-likeness score from 0 to 100. |
1294
+
1295
+ **Example**
1296
+ ```js
1297
+ const label = interpretScore(28); // mostly human
1298
+ ```
1299
+ <a name="lengthRatioPoints"></a>
1300
+
1301
+ ## lengthRatioPoints(original, rewritten) ⇒ <code>number</code>
1302
+ Score rewritten length ratio on the 0-3 fidelity scale.
1303
+
1304
+ **Kind**: global function
1305
+ **Returns**: <code>number</code> - Length-ratio points from 0 to 3.
1306
+ **Throws**:
1307
+
1308
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1309
+
1310
+
1311
+ | Param | Type | Description |
1312
+ | --- | --- | --- |
1313
+ | original | <code>string</code> | Original text. |
1314
+ | rewritten | <code>string</code> | Rewritten text. |
1315
+
1316
+ **Example**
1317
+ ```js
1318
+ const points = lengthRatioPoints('abcd', 'abcde');
1319
+ ```
1320
+ <a name="scoreFidelity"></a>
1321
+
1322
+ ## scoreFidelity(options) ⇒ <code>Promise.&lt;Object&gt;</code>
1323
+ Score fidelity between original and rewritten text using length plus LLM criteria.
1324
+
1325
+ **Kind**: global function
1326
+ **Returns**: <code>Promise.&lt;Object&gt;</code> - Fidelity result.
1327
+ **Throws**:
1328
+
1329
+ - <code>Error</code> When the operation is aborted.
1330
+
1331
+
1332
+ | Param | Type | Description |
1333
+ | --- | --- | --- |
1334
+ | options | <code>object</code> | Fidelity options. |
1335
+ | options.original | <code>string</code> | Original text. |
1336
+ | options.rewritten | <code>string</code> | Rewritten text. |
1337
+ | [options.apiKey] | <code>string</code> | Provider API key. |
1338
+ | [options.baseURL] | <code>string</code> | Provider base URL. |
1339
+ | [options.model] | <code>string</code> | Model id. |
1340
+ | [options.deadline] | <code>number</code> | Absolute epoch-millisecond deadline. |
1341
+ | [options.signal] | <code>AbortSignal</code> | External cancellation signal. |
1342
+ | [options.callLLM] | <code>function</code> | Injectable LLM implementation. |
1343
+ | [options.logger] | <code>object</code> | patina logger. |
1344
+ | [options.now] | <code>function</code> | Clock returning epoch milliseconds. |
1345
+ | [options.sleep] | <code>function</code> | Sleep helper for tests. |
1346
+
1347
+ **Example**
1348
+ ```js
1349
+ const fidelity = await scoreFidelity({ original: 'A', rewritten: 'A', callLLM: async () => '{"criteria":{"meaning":3,"tone":3,"no_unintended_additions":3}}' });
1350
+ ```
1351
+ <a name="clamp03"></a>
1352
+
1353
+ ## clamp03(v) ⇒ <code>number</code>
1354
+ Clamp and round a value into the inclusive 0-3 scoring range.
1355
+
1356
+ **Kind**: global function
1357
+ **Returns**: <code>number</code> - Integer from 0 to 3.
1358
+ **Throws**:
1359
+
1360
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1361
+
1362
+
1363
+ | Param | Type | Description |
1364
+ | --- | --- | --- |
1365
+ | v | <code>number</code> \| <code>string</code> | Value to clamp. |
1366
+
1367
+ **Example**
1368
+ ```js
1369
+ const value = clamp03(4.2); // 3
1370
+ ```
1371
+ <a name="combinedScore"></a>
1372
+
1373
+ ## combinedScore(options) ⇒ <code>number</code>
1374
+ Combine AI-likeness, inverted fidelity, and optional deterministic score.
1375
+
1376
+ **Kind**: global function
1377
+ **Returns**: <code>number</code> - Combined score, lower is better.
1378
+ **Throws**:
1379
+
1380
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1381
+
1382
+
1383
+ | Param | Type | Description |
1384
+ | --- | --- | --- |
1385
+ | options | <code>object</code> | Combined score inputs. |
1386
+ | options.aiLikeness | <code>number</code> | AI-likeness score, lower is better. |
1387
+ | options.fidelity | <code>number</code> | Fidelity score, higher is better. |
1388
+ | [options.profile] | <code>string</code> | Profile name for configured weights. |
1389
+ | [options.config] | <code>object</code> | Effective config. |
1390
+ | [options.deterministicScore] | <code>number</code> \| <code>object</code> \| <code>null</code> | Optional deterministic score. |
1391
+
1392
+ **Example**
1393
+ ```js
1394
+ const score = combinedScore({ aiLikeness: 20, fidelity: 90, profile: 'default', config: {} });
1395
+ ```
1396
+ <a name="validateProfileName"></a>
1397
+
1398
+ ## validateProfileName(name) ⇒ <code>void</code>
1399
+ Validate a profile name before resolving profiles/{name}.md.
1400
+
1401
+ **Kind**: global function
1402
+ **Throws**:
1403
+
1404
+ - <code>PatinaCliError</code> When the name is empty, non-string, or contains unsafe characters.
1405
+
1406
+
1407
+ | Param | Type | Description |
1408
+ | --- | --- | --- |
1409
+ | name | <code>string</code> | Profile name supplied by CLI or config. |
1410
+
1411
+ **Example**
1412
+ ```js
1413
+ validateProfileName('default');
1414
+ ```
1415
+ <a name="isLoopbackHost"></a>
1416
+
1417
+ ## isLoopbackHost(hostname) ⇒ <code>boolean</code>
1418
+ Check whether a hostname is localhost or loopback.
1419
+
1420
+ **Kind**: global function
1421
+ **Returns**: <code>boolean</code> - True for localhost, 127/8, or ::1.
1422
+ **Throws**:
1423
+
1424
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1425
+
1426
+
1427
+ | Param | Type | Description |
1428
+ | --- | --- | --- |
1429
+ | hostname | <code>string</code> | Hostname from a URL. |
1430
+
1431
+ **Example**
1432
+ ```js
1433
+ const local = isLoopbackHost('127.0.0.1');
1434
+ ```
1435
+ <a name="isPrivateOrSpecialIP"></a>
1436
+
1437
+ ## isPrivateOrSpecialIP(hostname) ⇒ <code>boolean</code>
1438
+ Detect literal private, reserved, link-local, metadata, or multicast IP hosts.
1439
+
1440
+ **Kind**: global function
1441
+ **Returns**: <code>boolean</code> - True when the literal IP is private or special-use.
1442
+ **Throws**:
1443
+
1444
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1445
+
1446
+
1447
+ | Param | Type | Description |
1448
+ | --- | --- | --- |
1449
+ | hostname | <code>string</code> | Hostname or bracketed IPv6 literal. |
1450
+
1451
+ **Example**
1452
+ ```js
1453
+ const blocked = isPrivateOrSpecialIP('169.254.169.254');
1454
+ ```
1455
+ <a name="validateBaseURL"></a>
1456
+
1457
+ ## validateBaseURL(baseURL, [options]) ⇒ <code>void</code>
1458
+ Validate a provider base URL before sending prompts and bearer tokens.
1459
+
1460
+ **Kind**: global function
1461
+ **Throws**:
1462
+
1463
+ - <code>PatinaCliError</code> When the URL is invalid, unsupported, insecure, or private without opt-in.
1464
+
1465
+
1466
+ | Param | Type | Default | Description |
1467
+ | --- | --- | --- | --- |
1468
+ | baseURL | <code>string</code> | | URL to validate. |
1469
+ | [options] | <code>object</code> | | Validation opt-ins. |
1470
+ | [options.allowInsecure] | <code>boolean</code> | <code>false</code> | Allow non-loopback HTTP. |
1471
+ | [options.allowPrivate] | <code>boolean</code> | <code>false</code> | Allow private/reserved literal IPs. |
1472
+
1473
+ **Example**
1474
+ ```js
1475
+ validateBaseURL('https://api.openai.com/v1');
1476
+ ```
1477
+ <a name="shouldAllowInsecureBaseURL"></a>
1478
+
1479
+ ## shouldAllowInsecureBaseURL([parsed]) ⇒ <code>boolean</code>
1480
+ Read CLI/env opt-in for non-loopback HTTP base URLs.
1481
+
1482
+ **Kind**: global function
1483
+ **Returns**: <code>boolean</code> - True when insecure base URLs are explicitly allowed.
1484
+ **Throws**:
1485
+
1486
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1487
+
1488
+
1489
+ | Param | Type | Description |
1490
+ | --- | --- | --- |
1491
+ | [parsed] | <code>object</code> | Parsed CLI options. |
1492
+
1493
+ **Example**
1494
+ ```js
1495
+ const allowed = shouldAllowInsecureBaseURL({ allowInsecureBaseURL: true });
1496
+ ```
1497
+ <a name="applyInsecureBaseURLOptIn"></a>
1498
+
1499
+ ## applyInsecureBaseURLOptIn([parsed]) ⇒ <code>void</code>
1500
+ Persist CLI insecure-base-url opt-in into process.env for downstream calls.
1501
+
1502
+ **Kind**: global function
1503
+ **Throws**:
1504
+
1505
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1506
+
1507
+
1508
+ | Param | Type | Description |
1509
+ | --- | --- | --- |
1510
+ | [parsed] | <code>object</code> | Parsed CLI options. |
1511
+
1512
+ **Example**
1513
+ ```js
1514
+ applyInsecureBaseURLOptIn({ allowInsecureBaseURL: true });
1515
+ ```
1516
+ <a name="shouldAllowPrivateBaseURL"></a>
1517
+
1518
+ ## shouldAllowPrivateBaseURL([parsed]) ⇒ <code>boolean</code>
1519
+ Read CLI/env opt-in for private or reserved literal IP base URLs.
1520
+
1521
+ **Kind**: global function
1522
+ **Returns**: <code>boolean</code> - True when private base URLs are explicitly allowed.
1523
+ **Throws**:
1524
+
1525
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1526
+
1527
+
1528
+ | Param | Type | Description |
1529
+ | --- | --- | --- |
1530
+ | [parsed] | <code>object</code> | Parsed CLI options. |
1531
+
1532
+ **Example**
1533
+ ```js
1534
+ const allowed = shouldAllowPrivateBaseURL({ allowPrivateBaseURL: true });
1535
+ ```
1536
+ <a name="applyPrivateBaseURLOptIn"></a>
1537
+
1538
+ ## applyPrivateBaseURLOptIn([parsed]) ⇒ <code>void</code>
1539
+ Persist CLI private-base-url opt-in into process.env for downstream calls.
1540
+
1541
+ **Kind**: global function
1542
+ **Throws**:
1543
+
1544
+ - <code>Error</code> Propagates validation, filesystem, network, or dependency failures when the underlying operation cannot complete.
1545
+
1546
+
1547
+ | Param | Type | Description |
1548
+ | --- | --- | --- |
1549
+ | [parsed] | <code>object</code> | Parsed CLI options. |
1550
+
1551
+ **Example**
1552
+ ```js
1553
+ applyPrivateBaseURLOptIn({ allowPrivateBaseURL: true });
1554
+ ```