Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 20 additions & 23 deletions .cursor/rules/patina.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

Patina is a **Claude Code skill** that detects and removes AI writing patterns from Korean, English, Chinese, and Japanese text. It rewrites AI-sounding text into natural, human-like prose while preserving meaning through the Meaning Preservation System (MPS).

The project uses a **plugin architecture**: patterns are plugins (`patterns/{lang}-*.md`), profiles are themes (`profiles/*.md`). Inspired by oh-my-zsh.
The project uses a **plugin architecture**: pattern packs (`patterns/{lang}-*.md`), Document Type policies (`document-types/*.md`), and optional Persona v2 voice definitions (`personas/<lang>/*.md`).

## File Structure Conventions

- **Pattern packs**: `patterns/{lang}-{category}.md` (e.g., `en-content.md`, `ko-language.md`)
- Must have valid YAML frontmatter: `pack`, `language`, `name`, `version`, `patterns`
- Pattern definitions use `### N. Pattern Name` headings
- **Profiles**: `profiles/{name}.md` (e.g., `blog.md`, `academic.md`)
- Define `voice-overrides` and `pattern-overrides`
- **Document Types**: `document-types/{name}.md` — genre, structure, and `pattern-overrides`
- **Personas**: `personas/{lang}/{name}.md` — optional voice-only Persona v2 frontmatter
- **Core definitions**: `core/voice.md`, `core/scoring.md`
- **Examples**: `examples/{number}-success-01.md`, `{number}-failure-01.md`
- English examples: `examples/en-{number}-success-01.md`
Expand All @@ -39,21 +39,19 @@ The project uses a **plugin architecture**: patterns are plugins (`patterns/{lan
6. Update `README.md` pattern tables
7. Bump the pack's `version:` frontmatter

## When Adding a Profile

1. Copy `profiles/default.md` as a template
2. Define `voice-overrides` (amplify/allow/suppress per voice dimension)
3. Define `pattern-overrides` (amplify/normal/reduce/suppress per pattern number)
- Use language-scoped overrides to avoid cross-language number collisions:
```yaml
pattern-overrides:
ko:
8: amplify
en:
8: amplify
```
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 Adding a Document Type

1. Copy `document-types/default.md` as a template.
2. Set `document-type:` to the filename stem.
3. Define `purpose`, `audience`, `structure`, `style`, `avoid`, and
language-scoped `pattern-overrides` in frontmatter.
4. Keep Persona voice, Register, and verification fields out of the policy.
5. Add a matching key under `.patina.default.yaml`
`scoring.combined-weights.document-type` only when scoring evidence justifies
a non-default balance.

Create reusable voice through Persona v2 (`patina persona new`). Persona schema
validation rejects Document Type, Register, pattern-policy, and safety fields.

## When Modifying SKILL.md

Expand All @@ -68,14 +66,13 @@ The project uses a **plugin architecture**: patterns are plugins (`patterns/{lan

## Version Management

- `.patina.default.yaml` `version:` is the **source of truth**
- `SKILL.md` and `README.md` must match it
- Pattern pack versions are independent — bump when patterns change
- Profile versions are independent
- `package.json` `version` is the **source of truth**
- `SKILL.md`, `.patina.default.yaml`, and `README.md` must match it
- Pattern pack and Document Type versions are independent

## Language Synchronization Rule

All 4 languages (ko, en, zh, ja) must maintain **29 patterns each** (116 total). When adding a universal pattern:
All 4 languages (ko, en, zh, ja) currently maintain **46 patterns each** (184 total). When adding a universal pattern:
- Same pattern number across all 4 languages
- Same semantic category
- Language-specific watch words and examples
Expand Down
45 changes: 15 additions & 30 deletions .patina.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,26 @@ language: ko # Korean (default) -- auto-loads all ko-*.md patterns
# language: zh -- Chinese -- auto-loads all zh-*.md patterns
# language: ja -- Japanese -- auto-loads all ja-*.md patterns
# Override with CLI: --lang en / --lang zh / --lang ja
profile: default
output: rewrite # rewrite | diff | audit | score
document-type: default # pattern policy: default, blog, academic, technical,
# formal, social, email, legal, medical, marketing,
# narrative, instructional, casual-conversation,
# code-comment, commit-message, release-notes, namuwiki
# `formal` means structured documents; `casual-conversation` means chat/message
# structure. Neither selects casual/professional delivery — use `register`.
output: rewrite # rewrite | diff | audit | score


# Tone categorization (v3.10): register axis (casual/professional) for ko/en.
# Resolution: explicit --tone > config tone > config profile.
# auto = heuristic single-tone detection at SKILL.md Phase 4.5b.
# Unset = profile-only legacy behavior (regression-safe default).
tone: # one of: casual, professional, auto (register only)
# Override with CLI: --tone <name>
# zh/ja with explicit tone: warns and falls back to profile.

# Persona harness (v1): Korean rewrite defaults to the conservative preserve
# persona. Non-rewrite and non-Korean surfaces do not apply persona implicitly.
persona: preserve
# Optional rewrite axes. Omit persona to preserve source voice; omit register
# to preserve the source register.
register: # casual | professional
# --persona applies a reusable voice fingerprint from personas/{lang}/.
persona:
personas:
library: personas/ko
thresholds:
schema: patina.persona.thresholds.v1
# source stays `placeholder` because the advisory tuning below is informed by
# observation, not a formal 2-round promotion. The ENFORCING floors, however,
# are the product's core safety contract and do not depend on calibration.
# Persona match and surface churn are advisory voice-quality signals.
# Meaning, numbers, and fidelity are enforced by the global rewrite and
# --verify paths whether or not a persona is active.
source: placeholder
calibration_rounds_required: 2
# --- ENFORCING (safety gate; a miss sets a non-zero exit, non-destructive) ---
# Meaning + facts only. Enforced when evaluated (mps/fidelity need --verify or a
# backend-reported score); dropped source numbers always enforce deterministically.
mps_floor: 70
fidelity_floor: 70
# --- ADVISORY (warn only; never blocks or changes the exit code) ---
# persona_match: voice-quality signal. Observed real KO persona-match runs ~35-56,
# so 45 flags the low end without firing on every rewrite.
persona_match_min: 45
# churn: surface token change, NOT a meaning signal. Live KO humanizing rewrites
# churn ~0.5-0.85 while preserving meaning, so 0.9 flags only near-total rewrites.
churn_max: 0.9

# Supported languages: ko, en, zh, ja
Expand Down
32 changes: 14 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,7 @@ 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
## 7.0.0 — 2026-08-04

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

Expand All @@ -39,23 +23,35 @@ Semver rationale: major — removes public configuration keys and the obsolete a
- 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.
- **`--profile`, `--tone`, and `--formality` are retired** along with the `profiles/` and `examples/tones` catalogs; the CLI rejects them with migration errors pointing at the replacement axes below. The `preserve` persona files are gone — omitting `--persona` now preserves the source voice by construction.

### 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.
- The rewrite-axis model is rebuilt (see Added): Document Type owns document policy, Persona v2 stays the optional reusable voice axis, and Register owns delivery. Meaning preservation and verification remain global; no axis can weaken them.

### 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.
- Three independent rewrite axes replace the overloaded profile/tone/formality model: 17 Document Types own document policy, 12 language-scoped Persona v2 files optionally supply reusable voice, and Register independently selects `casual` or `professional` delivery. Omitted Persona and Register preserve the source.
- Document Type files now use a validated structured frontmatter contract (`purpose`, `audience`, `structure`, `style`, `avoid`, language-scoped `pattern-overrides`). Invalid or cross-axis custom policies fail before prompt assembly.
- Rewrite prompts state axis ownership and precedence explicitly. Document Type cannot manufacture voice, Persona cannot choose document policy or Register, and Register cannot alter structure or claims.
- The example catalog now includes comparable KO/EN rewrite-axis fixtures for academic, instructional, marketing, narrative, casual, and professional behavior.
- Server-paid free-tier rewrites on DeepSeek get a measured reasoning cut (`reasoning_effort: low`, ~44s instead of ~90s at gate-parity quality), env-tunable via `PATINA_FREE_REWRITE_REASONING=low|medium|high|off`; the MPS/fidelity judges carry the same cut on gemini and deepseek. BYOK and Pro rewrites never receive the field. The streaming client gains `extraBody` passthrough with protocol fields protected.

### 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.
- A model cached as rejecting `temperature` can no longer recover the field from `extraBody` in either LLM client.
- The BYOK pricing card no longer claims the key "stays in your browser": it is sent only with your requests, relayed by the server, and never stored or logged.

### Operations

- The v6.4 preflight hold now certifies the Polar payment route instead of the declined Lemon Squeezy account: the binding table carries exactly the Polar production tuple (`PAY-B-20260729-POLAR-ea8385dc-4c9c3f17`), `POLAR_APPROVAL` replaces `LS_APPROVAL`, and the retired staging chain is recorded as superseded by production zero-amount purchase evidence. Checkout was opened by the owner on 2026-08-04 after the full gate sequence; the hosted free tier now serves on `deepseek-v4-flash`.

### Migration

Expand Down
25 changes: 16 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Contributing to Patina

Thanks for considering a contribution. Patina is a pattern-based tool, so the most impactful contributions are usually new patterns, better examples, or profile refinements.
Thanks for considering a contribution. Patina is a pattern-based tool, so the most impactful contributions are usually new patterns, better examples, Document Type policies, or Persona refinements.

## Public vs. Internal Docs

User-facing documentation lives in `README*.md`, `docs/`, `examples/`, `patterns/`, `profiles/`, and the skill entrypoints. Maintainer or agent notes (backlogs, runbooks, launch playbooks) are kept out of this repository entirely — they live in the maintainer's private workspace, and `docs/internal/` is gitignored as a guard. Only real user-facing facts get promoted into the public docs list.
User-facing documentation lives in `README*.md`, `docs/`, `examples/`, `patterns/`, `document-types/`, `personas/`, and the skill entrypoints. Maintainer or agent notes (backlogs, runbooks, launch playbooks) are kept out of this repository entirely — they live in the maintainer's private workspace, and `docs/internal/` is gitignored as a guard. Only real user-facing facts get promoted into the public docs list.

When moving a root-level Markdown file, link it from `README.md` if it is public; if it is maintainer-internal, it does not belong in the repository.

Expand Down Expand Up @@ -73,7 +73,7 @@ False positives are expected, especially for academic, encyclopedic, legal, corp
1. Use the false-positive issue template.
2. Include language, genre/register, score/audit excerpt, and the specific pattern that over-fired.
3. Remove private text or replace it with a minimal redistributable excerpt.
4. Suggest whether the fix should be an exclusion rule, lower severity, profile override, or benchmark fixture.
4. Suggest whether the fix should be an exclusion rule, lower severity, Document Type `pattern-overrides` change, or benchmark fixture.

Maintainers should prefer tightening exclusions over deleting patterns outright.

Expand Down Expand Up @@ -160,11 +160,18 @@ See [docs/HARNESS.md](docs/HARNESS.md) for the full measurement-tool map.
- Add a target-language false-positive note when a phrase is normal in that register.
- Keep examples redistributable; do not paste private user text.

## Adding a Profile
## Adding a Document Type

Profiles live in `profiles/{name}.md`. Copy an existing one (e.g. `blog.md`) and adjust:
- `voice-overrides`: which voice dimensions to amplify/suppress
- `pattern-overrides`: per-language, per-pattern-id actions. `suppress` is applied deterministically (the pattern is dropped from the prompt for that language); `reduce`/`amplify` are advisory and reinforced only by the prose body.
Document Types live in `document-types/{name}.md`. Copy an existing policy
(for example `blog.md`), set `document-type:` to the filename stem, and define
`scope`, `purpose`, `audience`, `structure`, `style`, `avoid`, and
language-scoped `pattern-overrides`. `suppress` is applied deterministically;
`reduce`/`amplify` document policy intent but do not currently change a runtime
weight. Keep Persona voice, casual/professional Register markers, and
verification thresholds out of this axis.

For reusable voice, create a Persona v2 with `patina persona new`. Persona
frontmatter is validated to reject document policy, Register, and safety fields.

## Pattern Staleness

Expand All @@ -176,14 +183,14 @@ How we handle this:
- **Quarterly review:** Maintainers follow [`process/pattern-freshness.md`](process/pattern-freshness.md) for corpus freeze windows, promotion thresholds, and frontmatter metadata
- **Lexicon provenance:** Newly mined or re-mined lexicon entries need `added`, `source`, and `last_validated` provenance before changing shipped behavior; run `npm run lexicon:freshness` to verify sidecars match the shipped entries
- **Version notes:** Each pattern pack has a `version` field — bump it when patterns change
- **No deletion without replacement:** We don't remove patterns outright; we mark them as `low` severity or move them to `reduce` in profiles
- **No deletion without replacement:** We don't remove patterns outright; we mark them as `low` severity or move them to `reduce` in a Document Type policy

## Versioning Policy

Patina uses semantic versioning for both CLI behavior and pattern-pack compatibility.

- **Major:** remove or renumber patterns, break config/result schemas, change public CLI semantics, or make existing pattern packs incompatible.
- **Minor:** add a pattern, language, profile, mode, backend, benchmark schema field, or contributor-facing workflow.
- **Minor:** add a pattern, language, Document Type, Persona, mode, backend, benchmark schema field, or contributor-facing workflow.
- **Patch:** fix bugs, adjust severity/exclusions, clarify examples, update docs, refresh benchmark fixtures without changing schemas.

Every changelog entry should include a short semver rationale line so downstream users know whether to pin, test, or upgrade normally.
Expand Down
Loading