openwriter 0.35.1 → 0.36.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 (32) hide show
  1. package/dist/client/assets/{index-Be_l2OOL.css → index-B5p6e-z0.css} +1 -1
  2. package/dist/client/assets/{index-BPDt3Psd.js → index-BMhKsQ_t.js} +53 -53
  3. package/dist/client/index.html +2 -2
  4. package/dist/plugins/authors-voice/skill/LICENSE +21 -0
  5. package/dist/plugins/authors-voice/skill/README.md +126 -0
  6. package/dist/plugins/authors-voice/skill/SKILL.md +151 -0
  7. package/dist/plugins/authors-voice/skill/catalog/ai-tells.md +144 -0
  8. package/dist/plugins/authors-voice/skill/catalog/anchor-prompt.md +189 -0
  9. package/dist/plugins/authors-voice/skill/catalog/author-hints.md +119 -0
  10. package/dist/plugins/authors-voice/skill/catalog/fingerprints.md +175 -0
  11. package/dist/plugins/authors-voice/skill/catalog/hurdle.md +76 -0
  12. package/dist/plugins/authors-voice/skill/catalog/post-write-audit.md +105 -0
  13. package/dist/plugins/authors-voice/skill/docs/analysis.md +31 -0
  14. package/dist/plugins/authors-voice/skill/docs/anchor-iteration.md +176 -0
  15. package/dist/plugins/authors-voice/skill/docs/api/import.md +78 -0
  16. package/dist/plugins/authors-voice/skill/docs/api/protocol.md +140 -0
  17. package/dist/plugins/authors-voice/skill/docs/api/setup.md +37 -0
  18. package/dist/plugins/authors-voice/skill/docs/api/tools.md +102 -0
  19. package/dist/plugins/authors-voice/skill/docs/api/troubleshooting.md +7 -0
  20. package/dist/plugins/authors-voice/skill/docs/apply-protocol-deep.md +191 -0
  21. package/dist/plugins/authors-voice/skill/docs/context-hygiene.md +33 -0
  22. package/dist/plugins/authors-voice/skill/docs/setup.md +74 -0
  23. package/dist/plugins/authors-voice/skill/docs/tiers.md +13 -0
  24. package/dist/plugins/authors-voice/skill/package.json +35 -0
  25. package/dist/plugins/authors-voice/skill/prompts/skeleton.md +29 -0
  26. package/dist/plugins/authors-voice/skill/voice/README.md +51 -0
  27. package/dist/plugins/authors-voice/skill/voice/corpus/.gitkeep +0 -0
  28. package/dist/server/documents.js +7 -10
  29. package/dist/server/state.js +27 -7
  30. package/dist/server/title-resolve.js +87 -0
  31. package/dist/server/workspaces.js +10 -4
  32. package/package.json +1 -1
@@ -0,0 +1,105 @@
1
+ # Post-Write Audit
2
+
3
+ > Distribution-level statistical checks the orchestrator runs against the minion's returned prose. Sits between step 6 (NEVER scan) and step 7 (integration) of the Apply Protocol. Catches statistical fingerprints the minion's prompt can't reasonably prevent without cognitively overloading the writing pass.
4
+
5
+ ## When this runs
6
+
7
+ After step 6 (NEVER-violations scan + brief-error patching), before step 7 (integration). The orchestrator reads this file, applies each check to the minion's output, and surgically rewrites the smallest span that brings the failing metric back into range.
8
+
9
+ ## Why this layer exists
10
+
11
+ The minion writes prose. The orchestrator polices distribution and lexicon. Anything mechanically detectable after the fact lives here, not in the writing-pass prompt — the minion's cognitive budget should go to channeling the anchor and hitting the commitments, not tracking 60 micro-bans.
12
+
13
+ Two enforcement points still exist for the bans the minion DOES need to see (contrastive negation, sentence-opener repetition, em-dashes, etc.) — those live in `voice/never-rules.md` and get scanned at step 6. This audit is for the slop that's cheaper to scrub than to prevent.
14
+
15
+ ## Remediation principle
16
+
17
+ For each failing check, rewrite the **smallest local span** that fixes the metric. Do not regenerate. Do not reach for stylistic improvement. The minion's voice IS the result — the audit only nudges the statistics.
18
+
19
+ If a failing span is load-bearing (a specific image, a coined term, a structural beat the brief demanded), leave it. Audit findings are advisory at the boundary case. The minion's intent wins ties.
20
+
21
+ Aim for the lightest touch: 5-10 small substitutions across a typical draft brings rates back in line. Heavier rewrites mean the audit is being misused.
22
+
23
+ ## Distribution checks
24
+
25
+ ### 1. Sentence-opener repetition
26
+
27
+ **What to measure:** walk the output sentence by sentence. For each window of 3 consecutive sentences, check whether all three start with the same first word.
28
+
29
+ **Threshold:** flag if >30% of windows trigger.
30
+
31
+ **Why this number:** human writing sits at ~17% (DFT 2026 — mostly from intentional list structures like "How does X?... How does Y?... How does Z?"). SFT models at T=0.7 hit 53.3%. The 30% line cleanly separates human from AI.
32
+
33
+ **Action:** locate the offending windows. For each, rewrite the second OR third sentence to start with a different word. If the window forms an intentional list, leave it — list structure is the human use case the 17% baseline reflects.
34
+
35
+ ### 2. Sentence-initial "The" frequency
36
+
37
+ **What to measure:** percentage of sentences that begin with the word "The."
38
+
39
+ **Threshold:** flag if >15% of all sentences.
40
+
41
+ **Why this number:** "The" at sentence start is over-used by ~90% in SFT output vs human writing (DFT 2026, 14B SFT model). The +90% inflation puts AI rates well above the natural human range.
42
+
43
+ **Action:** locate sentences starting with "The." Rewrite a portion to start with a different determiner ("A", "An", "These"), a pronoun, a prepositional phrase, or a different subject. Five to seven swaps across a typical paragraph is usually sufficient.
44
+
45
+ ### 3. Function-word over-use
46
+
47
+ **What to read for:** the AI distribution-distance signal lives mostly in function words, not fancy diction. Top-10 tokens account for 87.2% of L2 distribution distance in SFT output (DFT 2026). Watch for:
48
+
49
+ | Token | SFT inflation vs human |
50
+ |---|---|
51
+ | `is` | +44% |
52
+ | `was` | +49% |
53
+ | `are` | +31% |
54
+ | `that` | +25% |
55
+ | `a` | +15% |
56
+ | `to` | +11% |
57
+ | `.` (period) | +19% |
58
+
59
+ **Heuristic check (no exact threshold):** scan the draft for clusters of short copular sentences ("X is Y. Z is W. P is Q.") and high period density (many short sentences in a row). Both are signatures of function-word inflation.
60
+
61
+ **Action:** when noticed, merge two short copular sentences into one with a participial or relative clause; vary sentence structure to use action verbs instead of "is/was"; combine short sentences to drop period count. Three to five rewrites across a paragraph usually levels the distribution.
62
+
63
+ ### 4. Sentence-length variance
64
+
65
+ **What to measure:** compute standard deviation of sentence length (in words) across the output. If the user has a `voice/stats.md`, compare to the user's own σ. Otherwise compare to baseline σ ≥ 8 words.
66
+
67
+ **Threshold:** flag if σ < 6 words (low variance — uniform sentence length is an AI signature).
68
+
69
+ **Action:** locate runs of similar-length sentences. Merge two short ones into a longer compound, or split a medium one. The goal is to restore length variance, not hit a specific number.
70
+
71
+ ## Lexical watch list
72
+
73
+ Mechanical word-level scrubs. The minion doesn't see these — the audit handles them on the way out.
74
+
75
+ ### GPT-5 specific over-used tokens (DFT 2026)
76
+
77
+ When the minion is a GPT-5-class model, these tokens are inflated vs human writing. Scan for them:
78
+
79
+ | Token | Inflation vs human | Human baseline |
80
+ |---|---|---|
81
+ | `corridors` | +45.2% | 0.1% |
82
+ | `norms` | +43.1% | 0.1% |
83
+ | `align` / `aligns` / `alignment` | +36.0% | 0.2% |
84
+ | `metrics` | +27.2% | 0.2% |
85
+ | `engagement` | +26.5% | 0.2% |
86
+ | `targeted` | +5.1% | 1.6% |
87
+ | `identity` | +5.0% | 1.0% |
88
+ | `trust` | +4.9% | 1.2% |
89
+
90
+ **Action:** swap to a context-appropriate alternative when the word appears in surplus (3+ uses in a short piece, OR any use in a context where the word feels generic). If the user's corpus contains the word at signature frequency (in `voice/stats.md` or `voice/never-rules.md` exempts), leave it — they own that word.
91
+
92
+ ### Named-character defaults
93
+
94
+ AI defaults to specific generated names in fiction. Known examples:
95
+
96
+ - `Elara Voss` — documented in OpenAI's "goblin problem"
97
+ - Add new defaults as documented.
98
+
99
+ **Action:** if found in fiction output without explicit user specification, rename to something contextually appropriate or to a name the user has used in their corpus.
100
+
101
+ ## Source
102
+
103
+ Distribution thresholds and over-use rates from "Fixing LLM Writing with Distribution Fine-Tuning," Rosmine 2026 (https://rosmine.ai/2026/05/18/fixing-llm-writing-with-distribution-fine-tuning/). Token over-use rates measured against 14B SFT vs human fineweb baseline. Sentence-opener repetition methodology: percent of texts containing 3+ consecutive sentences starting with the same first word.
104
+
105
+ This file is a living checklist. New research that surfaces measurable thresholds for AI-vs-human writing belongs here, not in `voice/never-rules.md` — the writing pass stays lean.
@@ -0,0 +1,31 @@
1
+ # Analysis Protocol
2
+
3
+ Regenerates the voice files from the corpus. Run any time the corpus changes (new samples added, samples removed, samples revised). Loaded only when triggered — not in context during normal writing sessions.
4
+
5
+ ## Protocol
6
+
7
+ 1. **Read inputs.** Concatenate every file in `voice/corpus/` (strip frontmatter). Count words. Read `catalog/ai-tells.md`, `catalog/fingerprints.md`, `catalog/hurdle.md`.
8
+
9
+ 2. **Compute deterministic tally** (best effort — counts may drift ±1 on long corpora):
10
+ - **Sentence distribution**: split on `[.!?]\s`, compute short/medium/long/very-long percentages, average length. Set `short_max` (25th-pct, clamped [6,12]) and `long_min` (75th-pct, clamped [18,28]). Do NOT emit a sentence-length cap in the apply directive — the corpus distribution carries the right ceiling and an arbitrary cap suppresses signature long sentences.
11
+ - **Punctuation density per 1k words** for em/en dash, colon, semicolon, question, exclamation, ellipsis, paren, bracket, straight/curly quotes. Categorize as `never` / `rare` / `low` / `strong`.
12
+ - **AI-tell tally**: count each item from `catalog/ai-tells.md`. Apply hurdle from `catalog/hurdle.md`: passes hurdle → preserve; fails → emit NEVER rule; below-hurdle but present → log to `below_hurdle`.
13
+ - **Fingerprints**: apply each detector from `catalog/fingerprints.md` with its decision rule.
14
+
15
+ 3. **Determine tier** by word count: <300 = 0 Empty; 300-999 = 1 Anchor; 1000-4999 = 2 Preliminary; 5000-19999 = 3 Full Coverage; ≥20000 = 4 AV-Grade. See `docs/tiers.md` for what unlocks at each tier.
16
+
17
+ 4. **Write `voice/stats.md`** — corpus stats, sentence distribution table, punctuation density table.
18
+
19
+ 5. **Write `voice/never-rules.md`** — preserve `## Manual Additions` section verbatim (anchored to start-of-line; the literal also appears in the intro blockquote — naive search will mis-grab it).
20
+
21
+ 6. **Write `voice/fingerprints.md`** — preserve `## Manual Overrides` section, same caution.
22
+
23
+ 7. **Write `voice/status.md`** — tier, words, active features, locked features, next milestone, file list, below-hurdle detections.
24
+
25
+ 8. **Report** — new tier, what changed in NEVER rules, what's locked next.
26
+
27
+ For corpora >10k words, count in passes (words → phrases → transitions) rather than tracking 60 counters at once.
28
+
29
+ ## Adding Samples Later
30
+
31
+ User says "add this to my voice profile" or pastes new writing. Append to next `voice/corpus/sample-NNN.md`, re-run Analysis Protocol, report tier change if any.
@@ -0,0 +1,176 @@
1
+ # Anchor Iteration
2
+
3
+ Final-polish minion. Channels the user's voice anchors as a panel; iterates critique → rewrite → re-score until 90/100. Single minion conversation, visible iteration history. Mandatory anti-ai cleanup follow-up.
4
+
5
+ Specialization of `/polish`'s pattern for writers-voice: channels the user's specific voice anchors (dynamically loaded from `voice/anchor.md` or `voice/anchor-<context>.md`), not generic advertising practitioners.
6
+
7
+ Replaced the prior single-pass Anchor Critique tool. Single-pass scoring is now just "stop after iteration 1" of Anchor Iteration — same architecture, parameter difference.
8
+
9
+ ## Why this works
10
+
11
+ AI cannot judge beats subjectively from generic prompts — no dopamine system to consult. But channeled anchors carry beat-judgment encoded in their training-data representations. Channeling Peterson reading prose surfaces Peterson's beat-trained sensibility. Multiplied across the panel, the collective weighted score reflects how the prose lands across the writer's actual voice ambition.
12
+
13
+ This is the ONLY AI critic tool that can do beat-level judgment, and it works only because the anchors are real humans whose dopamine-trained sensibilities are encoded in training data. Judgment is collective and writer-specific.
14
+
15
+ ## Architecture: one minion, visible iteration
16
+
17
+ ONE minion conversation. The minion runs the entire iteration loop internally with full visible history — each iteration's anchor critiques are part of the minion's context for the next iteration. Anchors see how their previous critiques were addressed (or weren't), which sharpens subsequent critiques.
18
+
19
+ Mirrors `/polish` exactly. NOT extracted-and-rerun-cold per iteration. The loop has memory.
20
+
21
+ Optional fallback: if visible iteration converges on a local optimum (rewriter anchored to original framing through visibility), retry with blind iteration (no prior history shown across iterations). Default = visible.
22
+
23
+ ## Inputs (only)
24
+
25
+ - The prose to polish
26
+ - The voice anchor blend (dynamically loaded from `voice/anchor.md` or `voice/anchor-<context>.md`)
27
+
28
+ That's the complete input. No commitments. No beat sheet. No project context. Anchors read the prose AS-IS, like a reader encountering it cold.
29
+
30
+ **Why no context:** anchors must judge the prose AS IT LANDS, not as it was intended to land. Briefing them on the project would have them judge against the brief, not against the prose. Cold reading is the point.
31
+
32
+ ## Personas (dynamic, inferred)
33
+
34
+ Pulled from `voice/anchor.md` (or context-specific variant). Each anchor listed with a blend percentage that serves as both voice influence weight and panel vote weight.
35
+
36
+ The minion infers each anchor's persona from training data — named writers are known entities to opus. System prompt does NOT enumerate per-persona profiles. `/polish` works the same way ("top 10 advertising practitioners" — no profiles needed; opus knows Hopkins, Ogilvy, Sugarman, etc.).
37
+
38
+ Example book-project anchor file:
39
+
40
+ ```
41
+ - 26% Jordan Peterson
42
+ - 22% Robert Sapolsky
43
+ - 20% Nassim Taleb
44
+ - 18% Bryan Caplan
45
+ - 14% Naval Ravikant
46
+ ```
47
+
48
+ Minion channels each with their characteristic sensibility — Peterson for moral weight and structural rigor, Sapolsky for biological grounding and dry mechanism, Taleb for skin-in-the-game and aphoristic hardness, Caplan for clear thesis with evidence, Naval for aphoristic screenshot-worthy compression.
49
+
50
+ ## Process (per iteration)
51
+
52
+ 1. Each anchor reads the current prose AS-IS, with their characteristic sensibility
53
+ 2. Each anchor produces:
54
+ - SCORE 0-100 (honest read of how this lands for them)
55
+ - TOP CRITIQUE (ONE thing they would cut, sharpen, or restructure)
56
+ - STRENGTH (what's working that must be preserved)
57
+ 3. Compute collective weighted score using anchor blend percentages
58
+ 4. If collective ≥ 90/100: STOP. Mark as FINAL ITERATION. Return converged prose.
59
+ 5. If collective < 90/100: synthesize panel's critiques into a FULL REWRITE of the prose (not a sentence-level patch). Preserve named strengths.
60
+ 6. Begin next iteration with rewritten prose. Each iteration's anchors see all previous iterations and critiques in their context.
61
+
62
+ ITERATION CAP: 6 iterations. If still <90 after 6, return highest-scoring iteration with note about non-convergence.
63
+
64
+ ## Output format
65
+
66
+ Per iteration:
67
+
68
+ ```
69
+ ====== ITERATION N ======
70
+
71
+ PROSE (current state):
72
+ [the prose being read this iteration]
73
+
74
+ ANCHOR READINGS:
75
+
76
+ {Anchor Name} ({weight}%): SCORE: X/100
77
+ TOP CRITIQUE: ...
78
+ STRENGTH: ...
79
+
80
+ [repeat per anchor]
81
+
82
+ COLLECTIVE WEIGHTED SCORE: X/100
83
+
84
+ [if < 90:]
85
+ SYNTHESIS — what the rewrite must address:
86
+ - ...
87
+
88
+ REWRITE:
89
+ [next iteration's prose, full text]
90
+
91
+ [if ≥ 90:]
92
+ CONVERGED. Final prose ready below.
93
+ ```
94
+
95
+ After convergence (or cap):
96
+
97
+ ```
98
+ ====== FINAL ======
99
+ Iterations: N
100
+ Final score: X/100
101
+ Convergence: YES / NO
102
+
103
+ FINAL PROSE:
104
+ [the polished prose, full text]
105
+ ```
106
+
107
+ ## Mandatory anti-ai follow-up
108
+
109
+ Anchor iteration runs no-context. NEVER rules and presentation fingerprints are not in scope during iteration. Rewriter will introduce AI tells the original prose may have avoided.
110
+
111
+ After convergence, editor MUST run an anti-ai cleanup pass against `voice/never-rules.md` and `voice/fingerprints.md`. Common scrubs:
112
+
113
+ - em-dashes → commas, periods, or restructured sentences
114
+ - semicolons → "and" or new sentences
115
+ - contrastive negation patterns → direct positive statements
116
+ - banned diction → plain equivalents
117
+ - inserted parenthetical em-dashes → restructure
118
+
119
+ TWO-STEP pattern. Iteration THEN anti-ai. Two passes do different jobs and should not be conflated. Skipping the anti-ai pass ships AI fingerprints into the published prose.
120
+
121
+ ## When to fire
122
+
123
+ - Final polish of a beat or section before publishing
124
+ - After integration of multi-minion drafts is complete and the Blinder Audit is clean
125
+ - When prose is content-finished and needs to land at ship-level voice quality
126
+
127
+ Do NOT fire on:
128
+
129
+ - Rough first drafts (commitments may still be evolving — polish wastes effort)
130
+ - Sections under structural revision (rewrite the commitments first, then polish the result)
131
+ - Single-paragraph fragments (panel needs prose to evaluate; isolation produces weak critiques)
132
+
133
+ ## Editor's role
134
+
135
+ 1. Identifies a beat ready for final polish
136
+ 2. Fires Anchor Iteration with the prose + dynamically loaded anchor blend
137
+ 3. Receives the converged output
138
+ 4. Runs the mandatory anti-ai cleanup pass
139
+ 5. Posts the result for review or comparison
140
+
141
+ The editor does NOT:
142
+ - Inject project context into the iteration (preserves cold-reader purity)
143
+ - Stop the iteration early (let convergence happen — the loop is the point)
144
+ - Re-judge the panel's collective decisions (panel's authority is the entire point of the tool)
145
+
146
+ ## Failure modes
147
+
148
+ - **Sycophantic clustering**: anchors give 85+ uniformly. Prompt explicitly bans default-middle scoring and names what 90/60/40 means for each anchor (90 = anchor would actually quote / share; 60 = competent but forgettable; 40 = anchor would put it down).
149
+ - **Persona drift**: anchors all sound like generic helpful AI. Combat by instructing channel-faithfully — each anchor should sound like the actual writer, hostile to AI flattening.
150
+ - **Iteration plateau**: scores stop rising after iteration 3-4. Panel has done what it can. Ship the highest iteration even if below 90, or fall back to blind iteration.
151
+ - **Manufactured content**: rewriter may invent details to address critiques (fabricated autobiographical claims, invented statistics, manufactured anecdotes). Editor MUST scan output for invented content during anti-ai pass and verify or cut as appropriate.
152
+
153
+ ## Model
154
+
155
+ **Opus required.** Sonnet drifts to default-helpful behavior, scores uniformly high, produces weak rewrites that don't actually address critiques. Opus channels personas with discipline and produces rewrites worth re-scoring.
156
+
157
+ ## Cost
158
+
159
+ Single conversation, multiple turns. Per iteration: ~3-5k input tokens (prose + previous iteration history) + ~3-5k output (critiques + rewrite). Three iterations ≈ 30k total tokens. Acceptable for chapter-scale work.
160
+
161
+ For short pieces (tweets, single paragraphs), Anchor Iteration is overkill. Use `/anti-ai` alone or a single Apply call with strong commitments.
162
+
163
+ ## Validation
164
+
165
+ Tested 2026-05-18 on a 1000-word chapter beat. Three iterations:
166
+
167
+ - **Iteration 1**: 77.20/100. Panel flagged: thesis buried, mechanism overclaim, no skin-in-the-game, no screenshot-worthy lines.
168
+ - **Iteration 2**: 86.32/100. Rewrite added thesis paragraph + personal admission + mechanism hedge + sharpened closing teaser.
169
+ - **Iteration 3**: 91.04/100. Strengthened personal admission, fixed determinist phrasing, added specific enemies, gave load-bearing line its own paragraph. Converged.
170
+
171
+ Notable failure modes observed:
172
+
173
+ - Rewriter manufactured an autobiographical detail (Iteration 2 invented a personal admission to satisfy a skin-in-the-game critique). Editor caught and flagged for verification during anti-ai pass.
174
+ - Iteration introduced em-dashes across 8 paragraphs and semicolons in the opening (original prose used "and"). Mandatory anti-ai pass scrubbed all of them.
175
+
176
+ The tool delivered ship-quality prose in 3 iterations. The post-iteration anti-ai pass was non-optional.
@@ -0,0 +1,78 @@
1
+ # Import Workflow
2
+
3
+ ## How It Works
4
+
5
+ The user's writing samples are **not stored locally**. When you import a document, it is uploaded to the Author's Voice API and stored in a cloud database. The API chunks the content, indexes it for search, and uses it for voice matching when `rewrite` or `generate` is called.
6
+
7
+ This means the user's corpus is a **persistent, curated repository** — not a throwaway import. Every document you add stays in the database and directly influences all future voice output. The quality of the corpus IS the quality of the voice.
8
+
9
+ **Think of it like a training set:**
10
+ - Documents tagged `Human` are the ground truth. They define the voice.
11
+ - Documents tagged `AI` or `AI-Assisted` are penalized in retrieval — they exist for reference but don't shape the voice.
12
+ - Wrongly tagged documents (AI content marked as Human) **corrupt the voice profile**. The system will learn AI patterns as if they were the author's patterns. This is the single worst thing that can happen to voice quality.
13
+ - Wrong categories cause cross-contamination — email samples polluting blog voice, tweets diluting long-form style.
14
+
15
+ **The agent's job during import is curation, not bulk ingestion.** Every document must be verified with the user before it enters the corpus.
16
+
17
+ **Corpus size guidelines:**
18
+ - **Minimum**: 3-5 documents, ~5,000 words — enough for basic pattern detection
19
+ - **Good**: 10+ documents, ~15,000+ words — reliable voice profile
20
+ - **Per category**: At least 3 documents in each category you plan to use for voice output
21
+ - More is better, but only if it's genuinely human-written. 5 authentic documents beat 50 mixed ones.
22
+
23
+ If the user doesn't have enough samples yet, tell them. A thin corpus produces a weak profile — the agent should set expectations rather than generate from insufficient data.
24
+
25
+ ---
26
+
27
+ ## Critical Rules
28
+
29
+ These rules are non-negotiable. Violating them degrades voice quality.
30
+
31
+ ### 1. Always Tag Authenticity
32
+
33
+ Voice profiles are ONLY built from Human-written content. AI-generated docs pollute the voice fingerprint.
34
+
35
+ **The agent MUST follow this process:**
36
+ 1. Gather candidate documents (URLs the user provides, or raw content)
37
+ 2. Present the list to the user and ask:
38
+ - "Which of these did YOU write? (Human)"
39
+ - "Which were AI-generated or AI-assisted?"
40
+ 3. Only import docs the user confirms as Human with `"authenticity": "Human"`
41
+ 4. Tag AI/uncertain docs as `"AI"` or `"AI-Assisted"`, or skip them
42
+ 5. Ask about categories for each doc
43
+
44
+ **NEVER assume a document is human-written.** Many users have a mix.
45
+ **NEVER bulk-import without user confirmation per document.**
46
+
47
+ ### 2. Authenticity Labels
48
+ - `Human` — written entirely by a human (default)
49
+ - `AI-Assisted` — human-written with AI help
50
+ - `AI` — generated by AI
51
+ - `Untagged` — unknown origin
52
+
53
+ ### 3. Category Pollution Destroys Voice Quality
54
+
55
+ Categories control which writing samples the retrieval layer pulls back. Wrong category = wrong examples = wrong voice.
56
+
57
+ **Impact**: Blog posts tagged as "email" → retrieval pulls email-style samples → output sounds corporate, not blog-voice. Tweets tagged as "blog" → retrieval pulls long-form samples → output loses punchy fragments.
58
+
59
+ **The agent MUST:**
60
+ 1. Ask the user what category each document belongs to during import
61
+ 2. Present built-in categories: `email`, `x`, `linkedin`, `blog`, `fiction`, `technical`, `business`, `academic`, `newsletter`
62
+ 3. If uncertain, ask the user — NEVER guess categories
63
+ 4. When calling `rewrite`, ALWAYS pass `category` to scope retrieval
64
+ 5. If a document doesn't fit any category, use the most stylistically similar one
65
+
66
+ **NEVER leave category empty** when the user has categorized content. Empty category retrieves from all categories, diluting the voice signal with stylistically mixed samples.
67
+
68
+ ---
69
+
70
+ ## Import Tools Quick Reference
71
+
72
+ | Tool | Use |
73
+ |------|-----|
74
+ | `import_from_url` | Import from any public URL (`url`, `categories[]`, `authenticity`) |
75
+ | `bulk_import` | Import multiple docs, max 50 (global `authenticity`) |
76
+ | `upload_content` | Upload raw markdown. Minimal payload `{docId, content}` — `profileId` optional (auto-resolves a default profile), `content` server-chunked on blank lines. |
77
+
78
+ After import: run `setup_voice` to create/update the voice profile. Update `local/state.md`.
@@ -0,0 +1,140 @@
1
+ # Voice Emulation Protocol
2
+
3
+ Two API endpoints do the voice work. The agent's job is to gather inputs,
4
+ call the endpoint, and return the result. The server handles profile loading,
5
+ sample retrieval, voice-guided generation, and anti-AI passes.
6
+
7
+ **Do not attempt to emulate the voice yourself** — the API is the only path
8
+ that produces reliable voice quality.
9
+
10
+ - **Rewrite existing text** → `rewrite`
11
+ - **Generate new content** → `generate` (see `/voice-generate`)
12
+
13
+ ## API Base
14
+
15
+ ```
16
+ BASE_URL=https://api.authors-voice.com
17
+ ```
18
+
19
+ All endpoints require `Authorization: Bearer $AV_API_KEY`.
20
+
21
+ ## rewrite — Rewrite Existing Text
22
+
23
+ ```bash
24
+ curl -s -N -X POST "${BASE_URL}/api/voice/mcp" \
25
+ -H "Authorization: Bearer $AV_API_KEY" \
26
+ -H "Content-Type: application/json" \
27
+ -H "Accept: application/json, text/event-stream" \
28
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
29
+ "name":"rewrite","arguments":{
30
+ "content":"<text to rewrite>",
31
+ "mode":"rewrite",
32
+ "category":"<x|blog|email|...>",
33
+ "inputType":"ai-assisted",
34
+ "contextBefore":"<optional>",
35
+ "contextAfter":"<optional>",
36
+ "format":"plaintext"
37
+ }}}'
38
+ ```
39
+
40
+ **Required**: `content`, `category`.
41
+ **Defaults**: `mode=rewrite`, `inputType=ai-assisted`, `format=plaintext`.
42
+
43
+ ### Modes
44
+
45
+ - `rewrite` — standard voice rewrite (default)
46
+ - `shrink` — compress while keeping voice
47
+ - `expand` — lengthen while keeping voice
48
+ - `custom` — pass `customInstruction` with specific directives
49
+
50
+ ### inputType
51
+
52
+ - `human` — author's own writing. Preserve word choices and quirks; only polish flow.
53
+ - `ai` — generic AI content. Discard phrasing entirely; rewrite from scratch using voice samples.
54
+ - `ai-assisted` (default) — mixed. Preserve author-sounding passages; rewrite generic parts.
55
+
56
+ ## generate — Create New Content
57
+
58
+ ```bash
59
+ curl -s -N -X POST "${BASE_URL}/api/voice/mcp" \
60
+ -H "Authorization: Bearer $AV_API_KEY" \
61
+ -H "Content-Type: application/json" \
62
+ -H "Accept: application/json, text/event-stream" \
63
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
64
+ "name":"generate","arguments":{
65
+ "instruction":"<what to write>",
66
+ "category":"<x|blog|email|...>",
67
+ "query":"<optional topic keywords>",
68
+ "targetWords":500,
69
+ "contextBefore":"<optional>",
70
+ "contextAfter":"<optional>",
71
+ "format":"plaintext"
72
+ }}}'
73
+ ```
74
+
75
+ **Required**: `instruction`, `category`.
76
+ **`query`**: optional — use when the retrieval topic differs from the instruction wording. If omitted, instruction + context are used for retrieval.
77
+ **`targetWords`**: optional; max 2000.
78
+
79
+ ## Voice Anchor (V1 lead signal)
80
+
81
+ An anchor is reference prose the author wants to sound like. When set, it's
82
+ injected **ahead of** sample retrieval in both `rewrite` and `generate` (and the
83
+ OpenWriter editor path), so it's the strongest single voice lever available.
84
+
85
+ Derive + persist from pasted prose:
86
+
87
+ ```bash
88
+ curl -s -X POST "${BASE_URL}/api/voice/anchor/derive" \
89
+ -H "Authorization: Bearer $AV_API_KEY" \
90
+ -H "Content-Type: application/json" \
91
+ -d '{"content":"<reference prose>","profileId":"<optional>"}'
92
+ ```
93
+
94
+ Read / replace / clear the persisted anchor:
95
+
96
+ ```bash
97
+ curl -s "${BASE_URL}/api/voice/anchor" -H "Authorization: Bearer $AV_API_KEY" # GET
98
+ curl -s -X PUT "${BASE_URL}/api/voice/anchor" ... # replace
99
+ curl -s -X DELETE "${BASE_URL}/api/voice/anchor" ... # clear
100
+ ```
101
+
102
+ MCP equivalent: the `set_voice_anchor` tool. `list_profiles` reports `hasAnchor`
103
+ and `anchorAuthors` per profile so you can check anchor state without a GET.
104
+
105
+ ## Response Parsing
106
+
107
+ The response is Server-Sent Events. Find the line starting `data: `, parse
108
+ the JSON, and extract the text:
109
+
110
+ ```
111
+ result.content[0].text
112
+ ```
113
+
114
+ That text is the final voice-matched output. Return it verbatim — no
115
+ post-processing needed.
116
+
117
+ **Defensive fallback**: if the text itself parses as JSON, extract `.content`
118
+ or `.text` from the parsed object. Most responses are plain text, but the
119
+ wire format permits JSON envelopes.
120
+
121
+ ## Categories
122
+
123
+ Always pass `category` — scopes retrieval to the right writing style.
124
+
125
+ Built-in: `x`, `blog`, `email`, `newsletter`, `linkedin`, `technical`,
126
+ `business`, `academic`, `fiction`.
127
+
128
+ ## Context
129
+
130
+ When editing inside existing text, pass `contextBefore` and `contextAfter`
131
+ as the surrounding paragraphs. Context guides flow but is **never included
132
+ in the output**.
133
+
134
+ ## Errors
135
+
136
+ - `401` → API key invalid. User should run `/voice-setup`.
137
+ - `404 profile not found` → profile not built. User should run `/voice-setup`.
138
+ - `429` → rate limit. Wait and retry.
139
+ - Timeout (>30s) → retry once; then surface the error.
140
+
@@ -0,0 +1,37 @@
1
+ # Setup — One-Time Configuration
2
+
3
+ ## API Key (Email OTP — Primary Method)
4
+
5
+ The agent handles signup directly. No website visit needed.
6
+
7
+ 1. Ask the user for their email address
8
+ 2. Send `POST https://api.authors-voice.com/auth/request-code` with `{ "email": "<user's email>" }`
9
+ 3. Tell user: "Check your email for a 6-digit verification code from Author's Voice."
10
+ 4. User provides the code
11
+ 5. Send `POST https://api.authors-voice.com/auth/verify-code` with `{ "email": "<user's email>", "code": "<6 digits>" }`
12
+ 6. Response: `{ "apiKey": "av_live_...", "tenantId": "email|..." }`
13
+ 7. Save the API key to `~/.claude/skills/authors-voice/local/config.md` and set `AV_API_KEY`
14
+
15
+ **Rate limits**: 5 requests/min per IP, 60s cooldown between sends, max 3 attempts per code, code expires in 10 minutes.
16
+
17
+ **If email OTP fails**: Ask the user to get a key manually at [authors-voice.com/voice?tab=api-keys](https://authors-voice.com/voice?tab=api-keys).
18
+
19
+ ## OpenWriter Plugin
20
+
21
+ Author's Voice also works inside [OpenWriter](https://openwriter.io). The plugin auto-resolves the API key from `~/.openwriter/config.json` if configured.
22
+
23
+ ## Base URL
24
+
25
+ Defaults to production. Override with `AV_BASE_URL` env var.
26
+
27
+ ```bash
28
+ AV_BASE_URL="https://api.authors-voice.com/api/voice/mcp"
29
+ ```
30
+
31
+ ## Seeding writing samples
32
+
33
+ Import samples with `import_from_url` (any public URL), `bulk_import` (up to 50
34
+ at once), or `upload_content` (raw markdown — minimal payload `{docId, content}`).
35
+ Inside OpenWriter, right-click a doc in the filetree to ingest it directly
36
+ (doc-level, manual re-sync). The Google Drive / Notion connectors were removed in
37
+ June 2026.