Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
61ed3c8
ops: measured — number-bearing documents fail the safety gate and Pro…
devswha Jul 30, 2026
954f449
Merge branch 'bot/number-safety-finding' into dev
devswha Jul 30, 2026
699c91b
ops: correct the cost figures — the first measurement bypassed the re…
devswha Jul 30, 2026
3893e6e
Merge branch 'bot/cost-measurement-correction' into dev
devswha Jul 30, 2026
fc39d1f
ops: retract the number-safety alarm — the failures were a probe arti…
devswha Jul 30, 2026
a0eae36
Merge branch 'bot/number-safety-retraction' into dev
devswha Jul 30, 2026
9f4e6c9
ops: establish that the quota machinery actually enforces on production
devswha Jul 30, 2026
14553d0
Merge branch 'bot/limiter-production-evidence' into dev
devswha Jul 30, 2026
9aebbfc
feat!: remove ouroboros mode
devswha Jul 30, 2026
781b140
fix(cli): resolve output config and offline score issues
devswha Jul 30, 2026
8348e2a
fix(cli): harden rewrite and offline score contracts
devswha Jul 30, 2026
2be94df
Merge pull request #665 from devswha/bot/open-issues-662-664-663
devswha Jul 30, 2026
6e32ecb
docs: record resolved CLI issues
devswha Jul 30, 2026
3b64ebc
Merge pull request #666 from devswha/bot/changelog-open-issues
devswha Jul 30, 2026
7b23bd5
ops: rewrite the v6.4 hold and checkout bindings from Lemon Squeezy t…
devswha Aug 2, 2026
6fc6db3
test: shift the pinned changelog historical occurrences for the Unrel…
devswha Aug 2, 2026
f3d860f
docs: address the review lane — gate the enable step, retire the LS r…
devswha Aug 2, 2026
db909c2
Merge pull request #668 from devswha/bot/polar-hold-rewrite
devswha Aug 2, 2026
2d32b4b
ops: record POLAR_APPROVAL and the SECRET_MANAGER presence-only evidence
devswha Aug 2, 2026
c547c1f
Merge pull request #669 from devswha/bot/polar-golive
devswha Aug 2, 2026
d2542c3
chore: retrigger preview build after disabling the stale preview chec…
devswha Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{
"name": "patina",
"source": "./",
"version": "6.3.4",
"version": "7.0.0",
"description": "Strip the AI packaging, keep the meaning. Detect and rewrite AI writing patterns across KO/EN/ZH/JA with MPS/fidelity checks. Runs the root SKILL.md as the /patina skill.",
"homepage": "https://github.com/devswha/patina",
"repository": "https://github.com/devswha/patina",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://www.schemastore.org/claude-code-plugin-manifest.json",
"name": "patina",
"version": "6.3.4",
"version": "7.0.0",
"description": "Detect and rewrite AI writing patterns in Korean, English, Chinese, and Japanese so text reads as if a human wrote it. Meaning-preservation (MPS) verified, audit-friendly. The root SKILL.md is loaded as the /patina skill.",
"author": {
"name": "devswha",
Expand Down
4 changes: 2 additions & 2 deletions .cursor/rules/patina.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The project uses a **plugin architecture**: patterns are plugins (`patterns/{lan
en:
8: amplify
```
4. If the profile needs custom AI/fidelity balance, add to `.patina.default.yaml` under `ouroboros.combined-weights`
4. If the profile needs custom AI/fidelity balance, add to `.patina.default.yaml` under `scoring.combined-weights`
5. Update `README.md` profile table

## When Modifying SKILL.md
Expand Down Expand Up @@ -97,7 +97,7 @@ The skill supports these modes (defined in `.patina.default.yaml`):
- `diff` — shows what changed and why
- `audit` — detects patterns only
- `score` — AI-likeness score 0-100
- `ouroboros` — iterative self-improvement loop
- `strict` — skill-only multi-pass verification flow (`/patina --strict`), not a runtime output mode

## Important Constraints

Expand Down
28 changes: 16 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,20 @@
# PATINA_FREE_MODEL=gemini-3.6-flash
# PATINA_QUOTA_HMAC_SECRET=your-long-random-secret

# --- Pro tier ($9.99/mo USD, Lemon Squeezy license-gated) -------------------
# --- Pro tier ($9.99/mo USD, license-gated; production provider: Polar) -----
# Caller sends `Authorization: Bearer <license_key>`; the server validates it
# against Lemon Squeezy's validate-only endpoint (POST /v1/licenses/validate),
# never stores the raw key, and meters per-license by an HMAC subject.
# against the provider's validate-only endpoint (Polar customer-portal
# license-keys/validate in production), never stores the raw key, and meters
# per-license by an HMAC subject.

# --- License provider ------------------------------------------------------
# Which vendor gates the Pro tier. Defaults to lemonsqueezy so an existing
# deployment cannot change gate behavior by upgrading. Both providers share the
# same fail-closed security core; cache and lock keys are provider-namespaced,
# so switching never serves a decision cached under the other vendor.
# PATINA_LICENSE_PROVIDER=lemonsqueezy # or: polar
# Which vendor gates the Pro tier. The code default stays lemonsqueezy so an
# existing deployment cannot change gate behavior by upgrading, but Lemon
# Squeezy declined the account (docs/operations/payment-provider-reset-20260729.md)
# — production runs polar. Both providers share the same fail-closed security
# core; cache and lock keys are provider-namespaced, so switching never serves
# a decision cached under the other vendor.
# PATINA_LICENSE_PROVIDER=polar # production value; code default: lemonsqueezy
#
# Polar identity (PATINA_LICENSE_PROVIDER=polar). Both are REQUIRED — a missing
# value fails closed rather than skipping the corresponding check.
Expand All @@ -69,7 +72,8 @@
# # seat validates about once per cache TTL.
# PATINA_POLAR_TIMEOUT_MS / _CACHE_TTL_MS / _NEGATIVE_CACHE_TTL_MS / _LOCK_WAIT_MS
# # same meaning as the PATINA_LS_* pair below
# Lemon Squeezy identity — the validate response meta MUST match these.
# Lemon Squeezy identity (retired provider, kept for the code path) — the
# validate response meta MUST match these.
# LS_STORE_ID=425473 # required
# LS_PRO_VARIANT_ID=1875389 # required (Pro subscription variant)
# LS_PRO_PRODUCT_ID=1199625 # optional extra pin (meta.product_id)
Expand Down Expand Up @@ -163,9 +167,9 @@
#
# Enabled checkout additionally requires an exact source-controlled binding in
# scripts/checkout-evidence-bindings.mjs. Environment values never authorize a
# checkout URL. Adding a binding requires reviewed immutable PAY-STG/PAY-B
# evidence and a human-supplied exact Lemon Squeezy URL; keep checkout disabled
# until that review is complete.
# checkout URL. Adding a binding requires reviewed immutable PAY-B evidence and
# a human-supplied exact Polar checkout-link URL (https://buy.polar.sh/polar_cl_*);
# keep checkout disabled until that review is complete.
# PATINA_DEPLOYMENT_CHANNEL=staging
# PATINA_PRO_CHECKOUT_URL=
# PATINA_PRO_GATE_EVIDENCE_ID=
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ body:
id: mode
attributes:
label: Mode
options: [rewrite, audit, score, diff, ouroboros, max, install, other]
options: [rewrite, audit, score, diff, max, install, other]
validations:
required: true
- type: textarea
Expand Down
22 changes: 5 additions & 17 deletions .patina.default.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "6.3.4"
version: "7.0.0"
language: ko # Korean (default) -- auto-loads all ko-*.md patterns
# language: en -- English -- auto-loads all en-*.md patterns
# language: zh -- Chinese -- auto-loads all zh-*.md patterns
Expand All @@ -7,11 +7,6 @@ language: ko # Korean (default) -- auto-loads all ko-*.md patterns
profile: default
output: rewrite # rewrite | diff | audit | score

# Opt-in: request OpenAI-compatible structured output (response_format
# json_object) for ouroboros LLM scoring. Default false; only the openai-http
# backend forwards it (never sent to local CLI backends). Some OpenAI-compatible
# endpoints reject response_format — leave false unless your endpoint supports it.
structured-output: false

# Tone categorization (v3.10): register axis (casual/professional) for ko/en.
# Resolution: explicit --tone > config tone > config profile.
Expand Down Expand Up @@ -104,15 +99,6 @@ scoring:
enabled: true
divergence-threshold: 20 # warn and prefer the more pessimistic score above this delta
combined-weight: 0 # default off for combinedScore; users may opt in (e.g. 0.2)

# Ouroboros: iterative self-improvement loop
# Runs the full humanization pipeline repeatedly until the AI score converges.
# Enable via config or CLI flag: --ouroboros
ouroboros:
enabled: false # opt-in; --ouroboros CLI flag overrides to true
target-score: 30 # stop when overall score <= this value (0-100)
max-iterations: 3 # hard limit on loop iterations
plateau-threshold: 10 # stop if score improves by less than this between iterations
category-weights:
ko:
content: 0.18
Expand Down Expand Up @@ -146,8 +132,6 @@ ouroboros:
filler: 0.08
structure: 0.15
viral-hook: 0.10 # score-only pack (excluded from rewrite)
fidelity-floor: 70 # stop ouroboros if fidelity drops below this; also the --verify fidelity floor
mps-floor: 70 # stop ouroboros if MPS (meaning preservation) drops below this; also the --verify MPS floor
combined-weights:
default:
ai-likeness: 0.60
Expand Down Expand Up @@ -184,6 +168,10 @@ ouroboros:
medium: 2
low: 1

verification:
mps-floor: 70
fidelity-floor: 70

# Stylometry: deterministic statistical preprocessing (SKILL.md Step 4.6)
# Calculates burstiness (sentence-length CV) and MATTR (lexical diversity) per
# paragraph, marks suspect zones, and feeds them into 5a/5b as internal memory.
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,69 @@ All notable changes to patina. Dates are release dates (YYYY-MM-DD).
Semver rationale: patch | minor | major — explain whether this changes patterns, schemas, CLI behavior, or docs only.
```

## Unreleased

### Changed

- The v6.4 preflight hold now certifies the Polar payment route instead of the
declined Lemon Squeezy account: the checkout evidence binding table carries
exactly the Polar production tuple (`PAY-B-20260729-POLAR-ea8385dc-4c9c3f17`,
`https://buy.polar.sh/polar_cl_…`), the hold validates the
`PAY-B-BINDING-POLAR-v1` and `PAY-LIVE-RUNTIME-POLAR-v1` artifacts,
`POLAR_APPROVAL` replaces `LS_APPROVAL`, and the secret-manager blocker names
`PATINA_LICENSE_PROVIDER`/`POLAR_ORGANIZATION_ID`/`POLAR_PRO_BENEFIT_ID`. The
retired staging chain is recorded as superseded by the production zero-amount
purchase evidence, not silently dropped; Lemon Squeezy records stay on disk,
hash-frozen, as history. Checkout remains disabled pending the owner's
env-side enable sequence.

## 7.0.0 — 2026-07-30

**Removes the retired iterative rewrite product contract, gives scoring and verification settings neutral ownership, and preserves the comparator only as unsupported research.**

Semver rationale: major — removes public configuration keys and the obsolete agent-skill mode without compatibility aliases. Existing custom configurations must migrate the shared score and verification settings below.

### Removed

- The retired iterative mode is gone from the agent skill, CLI migration tombstone, default configuration, help/support surfaces, generated API, and current documentation. Node users keep `--verify`; agent-skill users keep the independent `/patina --strict` flow.
- Loop-only `enabled`, `target-score`, `max-iterations`, and `plateau-threshold` settings are no longer product configuration.
- The unused top-level `structured-output` setting is deleted. The scorer's explicit runtime `responseFormat` API remains available to supported callers.

### Changed (breaking)

- Move `category-weights`, `combined-weights`, and `severity-points` from the former top-level loop-settings block to `scoring`.
- Move the shared `mps-floor` and `fidelity-floor` values to `verification`. Their defaults remain 70/70.
- Keep `personas.thresholds.mps_floor` and `personas.thresholds.fidelity_floor` separate; persona gate overrides do not affect `--verify`, and verification overrides do not affect persona gates.
- Rename the opt-in A/B arm to `iterative-baseline` and move its runner under `scripts/`. It remains packaged for `npm run quality:rewrite-ab`, but is unsupported research and is absent from CLI/help/config, hosted APIs, and generated public API docs.
- Persona, profile, and tone behavior is unchanged. Persona remains the reusable voice axis, tone remains the KO/EN register override, and profile-policy consolidation remains follow-up work.

### Added

- `patina --score --offline` runs the deterministic scoring layer without resolving a backend or credential. It supports markdown/JSON output and `--exit-on`; LLM-judged categories are marked unavailable, and a missing numeric local score fails instead of silently passing.

### Fixed

- Rewrite stdout now contains prose only. Plain, fenced, and blockquoted tone footers are stripped, while `--format json` retains structured tone metadata, including the model-resolved value from `--tone auto`.
- The agent skill now uses the CLI's config precedence and canonical project filename: installed `.patina.default.yaml` → `~/.patina.yaml` → project `./.patina.yaml` → command arguments.

### Migration

```yaml
scoring:
category-weights: { ... }
combined-weights: { ... }
severity-points:
high: 3
medium: 2
low: 1

verification:
mps-floor: 70
fidelity-floor: 70
```

No legacy alias is read. Move custom values before upgrading.

## 6.3.4 — 2026-07-29

**Corrects the Pro pricing card, which advertised a burst guard as an entitlement and contradicted the real monthly cap.**
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg"></a>
<a href="#quick-start"><img alt="Skill: Claude Code | Codex | Cursor | OpenCode" src="https://img.shields.io/badge/Skill-Claude%20Code%20%7C%20Codex%20%7C%20Cursor%20%7C%20OpenCode-blueviolet"></a>
<a href="https://github.com/devswha/patina"><img alt="Languages: KO | EN | ZH | JA" src="https://img.shields.io/badge/Languages-KO%20%7C%20EN%20%7C%20ZH%20%7C%20JA-green"></a>
<a href="CHANGELOG.md"><img alt="Version 6.3.4" src="https://img.shields.io/badge/version-6.3.4-blue"></a>
<a href="CHANGELOG.md"><img alt="Version 7.0.0" src="https://img.shields.io/badge/version-7.0.0-blue"></a>
</p>

<p align="center">
Expand Down Expand Up @@ -110,7 +110,7 @@ For large `--batch` runs, prefer an OpenAI-compatible HTTP backend; local CLI ba
| **184 patterns** | 37 rewrite-capable + 9 score-only viral-hook per language (46 each across KO/EN/ZH/JA) — see the full 184-pattern catalog in [PATTERNS.md](docs/PATTERNS.md) |
| **Modes** | rewrite · verify · audit · score · diff |
| **Surfaces** | agent skill · Node CLI · in-place preview · browser playground (rewrite + score) |
| **Voice** | `--persona` (built-in + your own, ko/en/zh/ja) is the sole voice axis · `--tone` register · `--profile` pattern policy — composable with a fixed precedence |
| **Voice controls** | `--persona` is the reusable voice axis · `--tone` overrides register · `--profile` sets Node pattern policy; some skill/core paths still carry legacy profile voice guidance |
| **Free usage** | logged-in `codex`, `claude`, or `gemini` CLI can run rewrites without `PATINA_API_KEY` |
| **Calibration** | 67.3% editing-hotspot catch [63.5–71.0%] across GPT-5.5 / Claude Sonnet 4.6 / Gemini 2.5 Pro (n=600, KO+EN); 16.0% false positives [11.6–21.7%] on KO+EN human controls (n=200) |
| **License** | MIT |
Expand All @@ -127,8 +127,9 @@ patina --lang <ko|en|zh|ja> [mode] [--profile <name>] input.txt
|---|---|
| `patina input.txt` | rewrite with defaults |
| `patina --audit input.txt` | detect patterns only |
| `patina --score input.txt` | output a 0-100 AI-likeness score |
| `patina --score --exit-on 30 input.txt` | CI gate with exit code `3` when `overall > 30` |
| `patina --score input.txt` | output a 0-100 score using the LLM judge plus deterministic signals |
| `patina --score --offline input.txt` | score with deterministic signals only; no backend or API key |
| `patina --score --offline --exit-on 30 input.txt` | offline CI gate with exit code `3` when `overall > 30` |
| `patina --diff input.txt` | show pattern-by-pattern changes |
| `patina --preview page.html` | render rewrites back onto a saved HTML page with toggles and inline diff |
| `patina --verify input.txt` | rewrite, then check MPS/fidelity floors with one retry |
Expand Down Expand Up @@ -158,7 +159,7 @@ patina persona edit my-voice --name "Founder voice" # copy-on-edit into
patina persona rm my-voice # remove a custom persona (--force to skip the confirm)
```

Works on ko/en/zh/ja and composes with `--tone` (register) and `--profile` (pattern policy). The persona is the sole voice owner; register precedence is `--tone` > persona. A persona shapes voice but never lowers the meaning floorsauthored personas are validated on save, and the safety gate still enforces MPS/fidelity + dropped-number checks.
Works on ko/en/zh/ja and composes with `--tone` and `--profile`. In Node, persona owns reusable voice, register precedence is `--tone` > persona, and profile controls pattern policy. Some skill/core-prompt paths still contain legacy profile voice guidance, so that separation is not universal yet. None of these controls lowers the meaning floors: authored personas are validated on save, and rewrites still enforce MPS/fidelity and dropped-number checks.

## CI

Expand Down Expand Up @@ -204,11 +205,11 @@ If meaning drifts, the change is retried or rolled back. Deterministic analysis

```yaml
# .patina.default.yaml
version: "6.3.4"
version: "7.0.0"
language: ko # ko | en | zh | ja
profile: default
output: rewrite # rewrite | diff | audit | score
tone: # casual | professional | auto (register; genre = profile)
tone: # casual | professional | auto (register override; profile = pattern policy)
```

Project `.patina.yaml` overrides defaults. Pattern packs are auto-discovered by language prefix. Additive list keys (`blocklist`, `allowlist`, `skip-patterns`) merge; other arrays replace.
Expand Down
Loading