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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "personawriter",
"source": "./",
"description": "Capture any writing style from a sample and turn it into a reusable skill that writes new text in that voice.",
"version": "1.1.0",
"version": "1.2.0",
"author": {
"name": "Moustafa Farhat"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "personawriter",
"version": "1.1.0",
"version": "1.2.0",
"description": "Capture any writing style from a sample and turn it into a reusable skill that writes new text in that voice. English, German, Arabic (MSA and dialects), French, Spanish and more.",
"author": {
"name": "Moustafa Farhat",
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 1.2.0

- Merges the Style Forge line of work into PersonaWriter, so PersonaWriter is the one current version.
- `style_stats.py compare` now prints a 0-100 similarity score and the three most important fixes,
and exits with 1 below 70. Per-metric tolerances and noise floors from 1.1 are kept.
- New `style_stats.py check`: counts signature-move dosage and never-list hits defined in the
generated skill's `references/markers.json` (Arabic matched through diacritics and alef/ya variants).
- New `style_stats.py overlap`: finds passages copied verbatim from the source or anchors
(Chinese/Japanese measured in characters).
- `--json` output for `compare`, `check` and `overlap`.
- Text cleaning before measuring: Markdown, code, HTML, URLs and front matter are removed, and
hard-wrapped PDF/e-book text is re-joined into paragraphs.
- New `validate.py` (quality gate for a generated skill, including a copy check against the
source) and `package.py` (validates and zips a skill into a `.skill` file).
- `scaffold.py` writes a starter `markers.json` (never overwritten on re-runs), checks that the
samples exist, and requires an ASCII slug of at most 64 characters, as skill names must be.
- New **Profile only** mode; Update mode also refreshes markers and anchors and works on style
skills built elsewhere.
- Generated skills get `markers.json`, the three-step drift check, and a "Rewriting a draft" section.
- SKILL.md: intake strips non-author material, one voice per skill, a manual path when code
cannot run, a 70+ score target in the proving step, and a "drift watch-list" in the guide.

## 1.1.0

- **Quick mode:** "write X like this" now produces the text right away and offers to save the
Expand Down
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ Claude: Measured 6,200 words. Your load-bearing traits: you open on a small con

<!-- DEMO: add a real blind test here (one original paragraph + two generated, answer in a <details> block) or a GIF of the full flow. -->

### Example: a skill built with PersonaWriter

**[Arabic Critic Essay Writer](https://github.com/moustafarhat/arabic-critic-essay-writer)** was
generated from a single 2,500-word Arabic music-criticism essay. It writes reviews (albums, films,
books, series, exhibitions, games), cultural essays and opinion pieces in formal Arabic, in a
reflective critic's voice: long qualified sentences landed by a short blunt one, "not X, but Y" as
the engine of every thesis, one fair concession, and an ending that leaves the tension open.

Look inside to see what PersonaWriter actually produces: a
[style guide](https://github.com/moustafarhat/arabic-critic-essay-writer/blob/main/skills/arabic-critic-essay-writer/references/style-guide.md)
built from decisions, a never-list, a drift watch-list learned while testing, a
[markers.json](https://github.com/moustafarhat/arabic-critic-essay-writer/blob/main/skills/arabic-critic-essay-writer/references/markers.json)
with measured dosage targets, and an
[example essay](https://github.com/moustafarhat/arabic-critic-essay-writer/blob/main/examples/the-photos-we-never-look-at.md)
on a topic the source never touched, with its check results. The example page also shows the first
draft failing the dosage check for overusing the voice's signature moves, and what was cut to fix it.

## Install

**Claude Code** (plugin marketplace):
Expand Down Expand Up @@ -84,6 +101,7 @@ Just talk to Claude. No special syntax needed.
| "Write a LinkedIn post about our launch, in the style of this article: …" | **Quick mode:** analyzes the sample and writes the post right away, then offers to save the voice as a skill. |
| "Turn my old blog posts into a skill that writes like me." | **Build mode:** full analysis, builds `<name>-writer`, tests it on unrelated topics, and refines it with your feedback. |
| "Here are three more of my essays. Update my voice skill." | **Update mode:** re-measures, revises the style guide, and tells you what changed. |
| "Just analyze how this author writes, I don't need a skill." | **Profile only:** delivers the style guide as a document. |
| "اكتب مقالاً عن العمل عن بعد بأسلوب هذا النص" | Works the same in Arabic (MSA or dialect). |
| "Schreib eine Produktankündigung im Stil dieses Textes." | …and in German, French, Spanish, and more. |

Expand All @@ -98,9 +116,10 @@ my-newsletter-writer/
├── references/
│ ├── style-guide.md # load-bearing traits, rhythm, diction, structure, never-list, dosage
│ ├── anchors.md # 5-10 short labelled excerpts, to tune the ear, never to copy
│ └── profile.json # the measured fingerprint
│ ├── profile.json # the measured fingerprint
│ └── markers.json # signature-move dosage + never-list as checkable patterns
└── scripts/
└── style_stats.py # drift check: compares every new draft to the fingerprint
└── style_stats.py # drift, dosage and copy checks for every new draft
```

## Why it works
Expand All @@ -113,8 +132,12 @@ my-newsletter-writer/
explaining the joke) is often the strongest signal, and the thing generic imitations miss.
- **Dosage.** Signature moves get a frequency ("one aside per section, at most"). Without it you
get a caricature.
- **Measured drift.** Every draft can be checked against the source's numbers: sentence rhythm,
paragraph shape, punctuation habits, person, openers.
- **Measured drift.** Every draft gets a similarity score against the source's numbers (sentence
rhythm, paragraph shape, punctuation habits, person, openers) plus the three fixes that matter most.
- **Checked dosage and never-list.** `markers.json` turns signature moves and forbidden habits into
patterns, so overuse and violations are counted, not guessed.
- **No copying.** An overlap check flags any passage shared verbatim with the source, and a
validator refuses to package a skill that reproduces it.
- **Proven on new topics.** Each skill is tested on subjects the source never touched before it is
handed over, with an optional blind test: can *you* tell which paragraph is real?

Expand All @@ -137,13 +160,26 @@ The scripts work on their own too:
# fingerprint one or more texts
python skills/personawriter/scripts/style_stats.py analyze essay1.txt essay2.txt --json profile.json

# how far is a draft from that fingerprint?
# how far is a draft from that fingerprint? (score 0-100 + top fixes)
python skills/personawriter/scripts/style_stats.py compare profile.json draft.txt

# dosage of signature moves and never-list hits
python skills/personawriter/scripts/style_stats.py check markers.json draft.txt

# did the draft copy from the source? (8+ shared words)
python skills/personawriter/scripts/style_stats.py overlap draft.txt source.txt

# scaffold a new style skill folder from samples
python skills/personawriter/scripts/scaffold.py my-style-writer ./out sample.txt

# check a finished skill, then zip it into an installable .skill file
python skills/personawriter/scripts/validate.py ./out/my-style-writer --source sample.txt
python skills/personawriter/scripts/package.py ./out/my-style-writer ./dist
```

Add `--json` to `compare`, `check` or `overlap` for machine-readable output. Exit code 1 means
problems were found (score under 70, violations, or copied text), so the tools work in CI.

## Ethics & fair use

PersonaWriter learns **patterns**: rhythm, structure, diction habits. Studying and imitating
Expand All @@ -165,7 +201,7 @@ case, and there is no ambiguity there.
├── skills/personawriter/ # the skill itself (this folder is what gets installed)
│ ├── SKILL.md
│ ├── references/ # analysis lens + templates for generated skills
│ └── scripts/ # style_stats.py, scaffold.py
│ └── scripts/ # style_stats.py, scaffold.py, validate.py, package.py
└── tests/ # python -m unittest discover tests
```

Expand Down
81 changes: 58 additions & 23 deletions skills/personawriter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ This skill runs anywhere (Claude Code, claude.ai, Claude Desktop, the API, other
reusable skill?" If yes, continue with Build mode from stage 2.
- **Build mode** - the user asks for a skill, agent, reusable voice, or brand voice, or accepts
the offer above. Do all stages.
- **Update mode** - the user has an existing style skill (built by PersonaWriter) and gives more
samples or feedback. Re-run stage 2 into the same folder on all samples you have (the originals
are usually not stored in the skill - that is fine, measure the new ones; the scaffold keeps the
old numbers as `references/profile.previous.json` so you can weigh the two). Re-read against the existing style guide and revise it: keep what
still holds, fix what the new material contradicts, and tell the user what changed.
- **Update mode** - the user has an existing style skill (from PersonaWriter or elsewhere) and gives
more samples or feedback ("too formal", "overuses questions"). Re-run stage 2 into the same folder
on all samples you have (the originals are usually not stored in the skill - that is fine, measure
the new ones; the scaffold keeps the old numbers as `references/profile.previous.json` so you can
weigh the two, and never overwrites `markers.json`). Re-read against the existing style guide and
revise it, plus markers and anchors: keep what still holds and what the user liked, fix what the
new material contradicts. Re-run stage 6 on one topic, repackage, and tell the user what changed.
- **Profile only** - the user wants the analysis, not a text or a skill. Do stages 1-4 and deliver
the style guide as a document.

When unsure, use Quick mode - a good first text is the best argument for building the skill.

Expand All @@ -50,14 +54,22 @@ When unsure, use Quick mode - a good first text is the best argument for buildin
Work through these stages in order. Keep the user informed in plain language at each stage,
but do not ask for permission at every step - this is a build task.

If you cannot run code, do every stage by hand: estimate the metrics by counting a few
paragraphs, write the files as text, and give the user the folder contents. The scripts make
it faster and more reliable; they are not required.

### 1. Intake

- **Get the sample** (pasted or uploaded) and save the plain text to `<work>/sample.txt`
(several files are fine).
(several files are fine). Strip front matter, footnotes, reader comments, and quotations that
are not by the writer; they pollute the profile. (Markdown, code, URLs and hard line wraps are
cleaned out by the scripts.)
- **Judge if it is enough.** Under ~1,000 words gives only a sketch; 3,000-10,000 words across
different parts is the sweet spot. For a book, choose a spread (opening, middle, a dialogue-heavy
stretch, a reflective stretch) rather than only chapter one. If the sample is thin, say so, build
anyway, and mark the profile as provisional.
- **One voice per skill.** If the samples come from several writers, or one writer across very
different registers, say so and ask whether to build one skill per voice or a deliberate blend.
- **Build mode only: ask only what you cannot infer**, at most three short questions, in one message:
1. What will the finished skill write (essays, chapters, posts, emails...)? This sets the genres in its description.
2. Whose writing is it - the user's own, or someone else's? (Affects how many excerpts to keep.)
Expand All @@ -66,13 +78,17 @@ but do not ask for permission at every step - this is a build task.

### 2. Measure

Run the scaffold script. It measures the sample, writes `references/profile.json`, and
copies the stats script into the new skill so it is self-contained:
Run the scaffold script. It measures the sample, writes `references/profile.json` and a
starter `references/markers.json`, and copies the stats script into the new skill so it is
self-contained:

```bash
python <skill-dir>/scripts/scaffold.py <style-slug>-writer <out> <work>/sample.txt
```

The slug is lowercase ASCII words joined by hyphens, ending in `-writer`; transliterate
non-Latin names (e.g. `nahj-hikayat-writer`).

In Quick mode, measure without scaffolding: `python <skill-dir>/scripts/style_stats.py analyze <work>/sample.txt`.

Read the printed report: sentence-length distribution, rhythm variation, paragraph size,
Expand Down Expand Up @@ -110,6 +126,9 @@ Create, inside the scaffolded folder, using the templates in `references/generat
- `SKILL.md` - essence, writing procedure, dials, guardrails. Keep it short (under ~100 lines).
- `references/style-guide.md` - the full analysis from stage 4.
- `references/anchors.md` - 5-10 short excerpts (one or two sentences each), each labelled with the trait it shows. If the source is a third party's published work, keep quoted material minimal and lean on paraphrased patterns; if it is the user's own writing you can be more generous while still keeping each anchor short.
- `references/markers.json` - the machine-checkable version of the dosage and never-list (template
section 4b). Measure each dosage target on the sample with `style_stats.py check` and set the
range around it.

Make the generated skill's `description` pushy and concrete: name the genres, the phrasings
users will actually say (in the user's languages), and the "rewrite my draft in this voice" case.
Expand All @@ -122,31 +141,45 @@ conventions explicitly when the sample uses them.

A style skill is only as good as what it writes on topics the source never touched.
1. Choose two test topics unrelated to the source, one close to the source's genre and one further away (e.g. a product update, a short story scene).
2. Following the generated skill's own procedure, write ~300-500 words for each, then run its drift check (`python scripts/style_stats.py compare references/profile.json draft.txt`).
3. Read both drafts critically against the never-list. Fix the guide (not just the draft) where something felt off.
2. Following the generated skill's own procedure, write ~300-500 words for each, then run from the skill folder:
```bash
python scripts/style_stats.py compare references/profile.json draft.txt # score, top fixes
python scripts/style_stats.py check references/markers.json draft.txt # dosage, never-list
python scripts/style_stats.py overlap draft.txt <work>/sample.txt # copying check
```
Aim for a compare score of 70+, no never-list hits and no overlap. Numbers are hints; your reading decides.
3. Read both drafts critically against the never-list. Fix the guide (not just the draft) where
something felt off, and add what you learned as a "drift watch-list" in the guide.
4. Show the user one draft and ask what feels right and what feels off. For a sharper test, offer a blind check: three short paragraphs on the same topic, one original and two generated - can they tell which is real?
5. Revise the style guide once or twice based on feedback. Stop when the user is satisfied; subjective work needs human judgment, not a benchmark.

### 7. Package and deliver
### 7. Validate, package, deliver

```bash
python <skill-dir>/scripts/validate.py <out>/<slug> --source <work>/sample.txt # must PASS
python <skill-dir>/scripts/package.py <out>/<slug> <out> # writes <slug>.skill
```

- If the skill was written straight into the user's skills folder, it is already installed - say so.
- Otherwise zip the folder so the zip contains `<style-slug>-writer/` at its root (use a
skill-creator packager if one is available, else any zip tool or
`python -c "import shutil; shutil.make_archive('<out>/<slug>', 'zip', '<out>', '<slug>')"`),
and hand the user the file with whatever file-sharing mechanism the environment offers.
- If the skill was written straight into the user's skills folder, it is already installed - say
so; packaging is optional then.
- Otherwise hand the user the `.skill` file (a zip with `<slug>/` at its root) with whatever
file-sharing or skill-saving mechanism the environment offers.

Finish with a short message: the style's essence (one or two sentences), what was tested,
where the skill is, and how to use it ("ask for a text and mention the style name" or "paste a
Finish with a short message: the style's essence (one or two sentences), what was tested and
how it scored, where the skill is, and how to use it ("ask for a text and mention the style name" or "paste a
draft and ask me to rewrite it in this voice").

## Guardrails

- **Patterns, not passages.** Learning rhythm, structure and diction habits from a published work
is ordinary stylistic study. Store patterns plus at most a few short anchors - never large passages.
`overlap` and `validate.py --source` enforce this.
- **Original text in a manner, not forgery.** The output is new writing *in a style*. Do not
present it as written by the source's author, invent quotes for real people, or write messages
meant to be taken as coming from a real, identifiable person (e.g. an email "from" a CEO, a
statement "by" a politician) - unless that person is the user themself.
statement "by" a politician) - unless that person is the user themself. If the request is
clearly to impersonate someone to others, decline that part and offer a voice framed as
"inspired by", used openly.
- **No deception at scale.** Decline to build voices meant for fake reviews, astroturfing, scams,
or academic work that the user's institution forbids ghost-writing for.
- **Facts stay facts.** Style never justifies making things up.
Expand All @@ -158,11 +191,13 @@ draft and ask me to rewrite it in this voice").
- **Topic contamination** - encoding the sample's subject vocabulary as "style". The fix is the swap test.
- **Averaging** - smoothing away exactly the odd choices that make the voice recognizable. Keep the oddities, with dosage.
- **Over-fitting one genre** - a novelist's narration rules will not automatically fit a tweet. Flag what is inference.
- **Copying** - reusing the sample's actual sentences. Anchors tune the ear; they are never source material.
- **Copying** - reusing the sample's actual sentences. Anchors tune the ear; they are never source material. `overlap` catches the rest.

## Files in this skill

- `references/style-dimensions.md` - the analysis lens (voice, syntax, diction, rhetoric, structure, fiction, language notes, swap test). Read in stage 3.
- `references/generated-skill-template.md` - folder layout and templates for the generated skill's SKILL.md, style guide and anchors. Read in stages 4-5.
- `scripts/style_stats.py` - `analyze` fingerprints a text; `compare` checks a draft against a saved profile.
- `scripts/scaffold.py` - creates the new skill folder, measures the sample, copies the stats script.
- `references/generated-skill-template.md` - folder layout and templates for the generated skill's SKILL.md, style guide, anchors and markers. Read in stages 4-5.
- `scripts/style_stats.py` - `analyze` fingerprints a text; `compare` scores a draft against a saved profile and lists the top fixes; `check` counts dosage and never-list hits from markers.json; `overlap` finds copied passages.
- `scripts/scaffold.py` - creates the new skill folder, measures the sample, writes starter markers, copies the stats script.
- `scripts/validate.py` - quality gate for a generated skill (structure, placeholders, required sections, anchor size, markers, copying).
- `scripts/package.py` - validates and zips a skill into an installable `.skill` file.
Loading
Loading