# Rewrite

> Rewrite documents so they are made true against a trusted corpus, then regenerated in a chosen voice without reintroducing factual drift. Use when the user asks to 'rewrite these docs', 'fact-check and rewrite', 'fix and restyle this corpus', 'make these documents accurate then re-voice them', or invokes '/rewrite'.

- Skill: `collinthefarmer/rewrite` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add collinthefarmer/rewrite`
- Raw SKILL.md: https://api.skillmd.com/api/skills/collinthefarmer/rewrite/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: collinthefarmer (https://skillmd.com/u/collinthefarmer)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/collinthefarmer/rewrite

---


# Rewrite — Truth-Preserving Document Re-voicing

Make a set of documents true against a trusted corpus, then regenerate them in a chosen voice — with an outline in between so the voice pass cannot drag the facts off-true.

You ARE the orchestrator. Follow these phases in order. The pipeline is **two Workflow runs with a human checkpoint between them**, because a Workflow cannot pause mid-run for input. Launch the first, do the interactive work while it runs, resolve disputes when it returns, then launch the second.

## Setup

Resolve paths once before starting:

```
PRESETS=~/.claude/skills/rewrite/scripts/presets.sh
RUNDIR="$(mktemp -d)/rewrite"    # frozen corpus spec, resolutions, and per-run artifacts
```

Create `RUNDIR` with `mkdir -p`. Everything you freeze (corpus spec, dispute resolutions) goes there so the run is auditable.

## Argument Parsing

Arguments can appear in any order:

| Argument | Form | Default |
|----------|------|---------|
| targets | Bare path/glob (first non-flag argument) | *(required)* |
| `--sources` | Path/glob **or** a natural-language description | *(required)* |
| `--voice` | `preserve` \| `exemplar:<file>` \| `preset:<name>` \| `design` | `preserve` |
| `--save-preset` | Name to save the resolved style spec under | none |
| `--verifiers` | Integer, refute-verifiers per unique claim | `3` |
| `--dry-run` | Flag (no value) | false |
| `--max-agents` | Integer, concurrency cap | `12` |
| `--output-suffix` | String inserted before the extension | `.rewritten` |

If `targets` or `--sources` is missing, stop and say which — the corpus is the oracle; without it there is nothing to check claims against.

## Phase 0: Frame and resolve the corpus spec

### Step 1: Resolve the corpus spec

The corpus spec is one artifact — `{files: [...], authority: [...], scope: [...]}` — however `--sources` was given.

- **Path/glob:** expand it to a file list with Glob. A bare directory means every text file under it, recursively; skip binaries and anything matched by `.gitignore`. `authority` and `scope` are empty.
- **Description:** dispatch one `Agent` (`subagent_type: Explore`) to resolve the sentence against the real filesystem into the same shape. The description may carry authority ordering ("trust the CHANGELOG over the prose on versions") and per-source scope ("metrics.csv for numbers, the RFC for behavior") — capture these as `authority` and `scope` rules. **Reject any clause that resolves to no file** (e.g. "what you know about HTTP") and report it; it would readmit model priors, which the corpus oracle exists to exclude.

Write the resolved spec to `$RUNDIR/corpus-spec.json` and show the user the file list plus any authority/scope rules. **Wait for confirmation** before verifying. This freeze is what keeps verification deterministic and auditable even when the selection came from a sentence.

### Step 2: Inventory and safety checks

- Expand `targets` to a file list. Count and total the size of targets and corpus files.
- Detect target/corpus overlap. If any file is in both sets, warn and list them — a document cannot be its own oracle — and proceed only on confirmation.
- If the projected dispute volume looks high (a thin corpus against many target claims), say so now rather than at the checkpoint.

### Step 3: Confirm the Workflow opt-in

Estimate scale: roughly `unique_claims × --verifiers + targets × 3` agents. Tell the user the rough agent count and that this runs as an opt-in Workflow. **If the user declines, stop and do nothing.**

**Tools:** Bash, Glob, Read, Agent (description resolution), Write (freeze the corpus spec).

## Phase 1: Launch fact-check (background) + acquire style spec (foreground)

Start both tracks together. Track A runs unattended; Track B is where you spend the wait.

### Track A: Workflow 1 — fact-check

Call `Workflow` with this inline `script`, passing `args: { targets: [...paths], corpus: {files, authority, scope}, verifiers: N }`. It extracts claims, deduplicates them semantically, and verifies each against the corpus.

```js
export const meta = {
  name: 'rewrite-factcheck',
  description: 'Extract, semantically dedup, and adversarially verify claims against a trusted corpus',
  phases: [{ title: 'Extract' }, { title: 'Dedup' }, { title: 'Verify' }],
}

const CLAIMS_SCHEMA = {
  type: 'object',
  properties: {
    claims: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          text: { type: 'string' },      // one atomic factual proposition
          location: { type: 'string' },  // section/line hint within the doc
        },
        required: ['text'],
      },
    },
  },
  required: ['claims'],
}

const DEDUP_SCHEMA = {
  type: 'object',
  properties: {
    claims: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          text: { type: 'string' },                 // canonical phrasing
          docs: { type: 'array', items: { type: 'string' } },
          variants_conflict: { type: 'boolean' },    // true if merged variants assert incompatible things
        },
        required: ['id', 'text', 'docs'],
      },
    },
  },
  required: ['claims'],
}

const REFUTE_SCHEMA = {
  type: 'object',
  properties: {
    supported: { type: 'boolean' },   // corpus affirmatively supports the claim
    refuted: { type: 'boolean' },     // corpus contradicts the claim
    correction: { type: 'string' },   // if refuted: what the corpus says instead
    evidence: { type: 'string' },     // corpus file:excerpt cited
  },
  required: ['supported', 'refuted'],
}

const corpusList = args.corpus.files.join('\n')
const rules = [
  args.corpus.authority?.length ? `Authority (higher wins a conflict): ${args.corpus.authority.join('; ')}` : '',
  args.corpus.scope?.length ? `Per-source scope: ${args.corpus.scope.join('; ')}` : '',
].filter(Boolean).join('\n')

// 1. Extract — fan out over target documents.
phase('Extract')
const perDoc = await parallel(args.targets.map(path => () =>
  agent(
    `Read ${path}. Extract every atomic factual proposition it asserts — one checkable fact per claim, no opinions or instructions. Give a short location hint for each.`,
    { label: `extract:${path}`, phase: 'Extract', schema: CLAIMS_SCHEMA },
  ).then(r => r && r.claims.map(c => ({ ...c, doc: path })))
))
const rawClaims = perDoc.filter(Boolean).flat()

// 2. Dedup — barrier: one merge over the whole set (semantic, not string).
phase('Dedup')
const deduped = await agent(
  `Here are ${rawClaims.length} raw claims, each with its source doc:\n${JSON.stringify(rawClaims)}\n\n` +
  `Merge claims that assert the SAME fact even when worded differently ("founded in 1998" == "established 23 years ago"). ` +
  `Give each merged claim a stable id, a canonical phrasing, and the list of docs that asserted it. ` +
  `If merged variants actually assert incompatible things, keep them as one claim but set variants_conflict=true. ` +
  `When unsure whether two claims are the same, keep them separate — false merges lose facts.`,
  { label: 'dedup', phase: 'Dedup', schema: DEDUP_SCHEMA },
)

// 3. Verify — fan out over unique claims; N refuters each.
phase('Verify')
function classify(claim, votes) {
  if (votes.length < Math.ceil(args.verifiers / 2)) {
    return { ...claim, verdict: 'disputed', reason: 'too few verifier votes to decide' }
  }
  const refuted = votes.filter(v => v.refuted)
  const supported = votes.filter(v => v.supported && !v.refuted)
  if (claim.variants_conflict) {
    return { ...claim, verdict: 'disputed', reason: 'target documents assert conflicting versions' }
  }
  if (refuted.length > votes.length / 2) {
    return { ...claim, verdict: 'refuted', correction: refuted.find(v => v.correction)?.correction || '', evidence: refuted[0].evidence }
  }
  if (supported.length > votes.length / 2 && refuted.length === 0) {
    return { ...claim, verdict: 'confirmed', evidence: supported[0].evidence }
  }
  if (supported.length === 0 && refuted.length === 0) {
    return { ...claim, verdict: 'disputed', reason: 'corpus is silent on this claim' }
  }
  return { ...claim, verdict: 'disputed', reason: 'verifiers split' }
}

const verdicts = await parallel(deduped.claims.map(claim => () =>
  parallel(Array.from({ length: args.verifiers }, (_, i) => () =>
    agent(
      `Corpus files (the ONLY evidence you may cite):\n${corpusList}\n${rules}\n\n` +
      `Claim: "${claim.text}"\n\n` +
      `Try to REFUTE this claim using the corpus. Read the relevant corpus files. ` +
      `Set refuted=true only if the corpus contradicts it (give the correction). ` +
      `Set supported=true only if the corpus affirmatively supports it. ` +
      `If the corpus says nothing either way, set both false. Cite the file and excerpt you relied on.`,
      { label: `verify:${claim.id}#${i}`, phase: 'Verify', schema: REFUTE_SCHEMA },
    )
  )).then(votes => classify(claim, votes.filter(Boolean)))
))

return { verdicts: verdicts.filter(Boolean) }
```

Tell the user briefly: "Fact-checking N documents against the corpus in the background. Meanwhile, let's settle the voice." The call runs in the background — you will get a task notification when it returns.

### Track B: acquire the style spec

Resolve `--voice` now, in the main conversation, while Workflow 1 runs:

| Mode | What you do |
|------|-------------|
| `preserve` (default) | No interaction. The style spec is each target's own voice — regeneration keeps it. Record `{ mode: "preserve" }`. |
| `exemplar:<file>` | Read the file. Derive a style spec from it (voice, format, lexicon, constraints). |
| `preset:<name>` | `bash "$PRESETS" show <name>` and load the returned JSON. |
| `design` | Co-author the spec with the user: propose a draft, generate a short sample paragraph from it, take reactions, refine. This is the interactive track that overlaps Workflow 1. |

A style spec is JSON: `{ name, voice{tone,person,register}, format{structure,headings,length_target}, lexicon{preferred,avoided}, constraints{must,must_not}, exemplar_excerpt? }`.

If `--save-preset <name>` was set, after the spec is settled write it to a temp file and run `bash "$PRESETS" add <name> --tier user --from-json <file>` — after the user confirms.

**Tools:** Workflow (Track A); Read, Write, Bash, Agent (Track B).

## Phase 2: Dispute resolution (checkpoint)

When Workflow 1 returns, partition `verdicts` into `confirmed`, `refuted` (each with its correction), and `disputed`.

If `disputed` is empty, skip this phase silently.

Otherwise present the disputed claims as **one batch** — never one document at a time. For each: the claim, the reason (`corpus silent`, `verifiers split`, `target documents conflict`, too few votes), the documents that assert it, and any corpus excerpts the verifiers cited. Ask the user to resolve each as:

- **true** — keep as asserted;
- **false** — drop it, or correct it to a stated value;
- **rephrase** — restate it as the user dictates.

Write the resolutions to `$RUNDIR/resolutions.json` keyed by claim id. Because claims are deduped corpus-wide, each resolution propagates to every document in that claim's `docs` list.

**Tools:** Write, wait for user input.

## Phase 3: Regenerate (Workflow 2, background)

Assemble the final corrected claim set per document: `confirmed` + `refuted` corrections + resolved disputes, dropping anything the user marked false. Build `args.docs = [{ path, corrections: [...] }, ...]` where `corrections` are the claims (with final text) that apply to that document.

If `--dry-run` was set, stop here: report the correction and dispute counts and the outlines that would be produced, and write nothing.

Otherwise call `Workflow` with `args: { docs, styleSpec, suffix, retries: 2 }`:

```js
export const meta = {
  name: 'rewrite-regenerate',
  description: 'Per document: apply corrections, outline, regenerate under a style spec, gate for fidelity',
  phases: [{ title: 'Apply' }, { title: 'Outline' }, { title: 'Regenerate' }],
}

const OUTLINE_SCHEMA = {
  type: 'object',
  properties: { outline: { type: 'array', items: { type: 'string' } } }, // ordered points, content only
  required: ['outline'],
}
const REGEN_SCHEMA = {
  type: 'object',
  properties: { text: { type: 'string' } },
  required: ['text'],
}
const GATE_SCHEMA = {
  type: 'object',
  properties: {
    unsourced: { type: 'array', items: { type: 'string' } }, // factual assertions not traceable to the outline
  },
  required: ['unsourced'],
}

const styleDesc = args.styleSpec.mode === 'preserve'
  ? 'Preserve THIS document's own voice, register, and format.'
  : `Voice/format spec:\n${JSON.stringify(args.styleSpec)}`

// Pipeline over documents — no barrier; each doc flows through all stages independently.
const results = await pipeline(args.docs,
  // 1. Apply corrections to the source doc.
  (doc) => agent(
    `Read ${doc.path}. Apply these corrections, changing only what they require and leaving everything else intact:\n${JSON.stringify(doc.corrections)}\n\nReturn the full corrected document text.`,
    { label: `apply:${doc.path}`, phase: 'Apply', schema: REGEN_SCHEMA },
  ),
  // 2. Summarize the corrected doc to a content-only outline.
  (applied, doc) => agent(
    `Reduce this corrected document to an ordered outline of its factual points — content and logical order only, no voice or formatting:\n\n${applied.text}`,
    { label: `outline:${doc.path}`, phase: 'Outline', schema: OUTLINE_SCHEMA },
  ),
  // 3. Regenerate from the outline under the style spec, then gate for fidelity (bounded retries).
  async (outlineResult, doc) => {
    const outline = outlineResult.outline
    let attempt = 0, out = null
    while (attempt <= args.retries) {
      const tighten = attempt === 0 ? '' : ' Assert NOTHING that is not in the outline — a previous attempt added unsupported detail.'
      out = await agent(
        `Regenerate a complete document from this outline. ${styleDesc}\n` +
        `Every factual assertion must come from the outline; add no facts of your own.${tighten}\n\nOutline:\n${JSON.stringify(outline)}`,
        { label: `regen:${doc.path}#${attempt}`, phase: 'Regenerate', schema: REGEN_SCHEMA },
      )
      const gate = await agent(
        `Outline:\n${JSON.stringify(outline)}\n\nDocument:\n${out.text}\n\nList every FACTUAL assertion in the document that does not trace to a point in the outline. Ignore pure connective/stylistic phrasing.`,
        { label: `gate:${doc.path}#${attempt}`, phase: 'Regenerate', schema: GATE_SCHEMA },
      )
      if (!gate.unsourced.length) return { path: doc.path, text: out.text, flags: [] }
      attempt++
      if (attempt > args.retries) return { path: doc.path, text: out.text, flags: gate.unsourced }
    }
  },
)

return { results: results.filter(Boolean) }
```

When Workflow 2 returns, write each result to `<path><suffix>.<ext>` with the Write tool (e.g. `report.md` → `report.rewritten.md`). Originals are never touched. If a result has `flags`, note them in the report and mark them inline in the written file so the user can review the assertions the gate could not source.

**Tools:** Workflow, Write.

## Phase 4: Report

Summarize plainly:

- documents rewritten and their output paths;
- unique claims checked, how many corrected, how many disputes and how each was resolved;
- any fidelity-gate flags that survived retries, per document.

Optionally route a completion notice through the `notify` skill.

**Tools:** Read, Write (optional notify).

## Error Recovery

| Situation | Response |
|-----------|----------|
| `targets` or `--sources` missing | Stop; state which is missing and that the corpus is required as the oracle. |
| A `--sources` description clause resolves to no file | Reject that clause in Phase 0 and report it. If nothing resolves, treat as missing sources. |
| Targets and corpus overlap | Warn, list the files, proceed only on confirmation. |
| User declines the Workflow opt-in | Stop after Phase 0; do nothing. |
| Corpus internally contradictory on a claim | If an authority rule covers it, resolve by the rule. Otherwise mark `disputed` with the conflict as its reason. |
| A verifier agent dies | Its vote drops; `classify` sends claims with too few votes to `disputed`, never to `confirmed`. Never re-launch. |
| Dedup uncertain whether two claims match | Keep them separate (the prompt says so) — false merges lose facts. |
| `disputed` empty | Skip Phase 2. |
| No corrections and `--voice preserve` | Nothing would change; report that and skip Phase 3. |
| Fidelity gate cannot converge after retries | Write the document, flag the unsourced assertions inline and in the report. Do not silently drop or keep them. |
| `preset:<name>` not found | `bash "$PRESETS" list` and ask for a valid name or another voice mode. |
| User interrupts while a Workflow is running | Workflow calls run to completion in the background; continue from whatever the run returns once it reports. |

