DEV Community

Avery Lin
Avery Lin

Posted on

Measure the Rewrite, Not the Prompt: Revoke Doc Drafting Rights from Post-Merge Receipts

Most teams argue about whether a model should have been allowed to draft a documentation section at all. The cheaper question arrives a week later, when you can measure how much of that draft a human actually had to rewrite. Sections that survive review with light edits have earned more drafting leeway; sections that get rebuilt from scratch have demonstrated the opposite. This article builds the measurement: a post-merge receipt, a rewrite-cost number, and a small ledger that revokes drafting rights when the cost stays high.

The permission gate is the wrong place to learn

Pre-draft policy is necessary, but it is written before you have evidence. A section can pass every ownership check and still cost a reviewer two hours of restructuring, and nothing in the policy file records that. The evidence you want lives in the gap between the draft text and the merged text, which is already sitting in your repository history. Turning that gap into a number converts a recurring argument into a ledger entry.

What the receipt measures

Rewrite cost is the fraction of the draft's words that did not survive, in order, into the merged section. I use word-level matching rather than line diffs because documentation drafts are usually reflowed, and line-level noise would swamp the signal. The metric is intentionally blunt: it answers "how much of this did a human keep" and nothing else. Structural quality, factual accuracy, and tone still need a human reader.

Step 1: Persist the draft before anyone touches it

A receipt needs two inputs, and the first one disappears the moment someone edits in place. Commit the raw draft to a predictable path on its own branch, or attach it as a CI artifact, before the review starts.

# drafting job output, committed on a branch, never edited
git checkout -b draft/architecture-retries
mkdir -p .drafts/architecture
cp /tmp/model-output.md .drafts/architecture/retries.md
git add .drafts/architecture/retries.md
git commit -m 'draft: architecture/retries.md (raw model output)'
Enter fullscreen mode Exit fullscreen mode

The rule that makes the whole workflow work is simple: .drafts/ is append-only, and humans never fix text there. Reviewer edits land in docs/ only, so the two files stay comparable.

Step 2: Compute rewrite cost with a dependency-free script

The script below uses only the standard library, prints one JSON receipt, and exits with a code your pipeline can branch on. It also reports added words, because a draft that is 40 percent rewritten but triples the section length is a different problem than one that was simply deleted.

#!/usr/bin/env python3
"""draft_receipt.py - rewrite cost for one model-drafted section."""
import argparse, difflib, json, re, sys
from pathlib import Path

WORD = re.compile(r"[A-Za-z0-9_]+")

def tokens(path):
    return WORD.findall(Path(path).read_text(encoding="utf-8").lower())

def rewrite_cost(draft_path, final_path):
    draft, final = tokens(draft_path), tokens(final_path)
    if not draft:
        return 1.0, len(draft), len(final), len(final)
    sm = difflib.SequenceMatcher(a=draft, b=final, autojunk=False)
    kept = sum(block.size for block in sm.get_matching_blocks())
    added = max(len(final) - kept, 0)
    return round(1 - kept / len(draft), 4), len(draft), len(final), added

def main():
    p = argparse.ArgumentParser()
    p.add_argument("--section", required=True)
    p.add_argument("--draft", required=True)
    p.add_argument("--final", required=True)
    p.add_argument("--max-cost", type=float, default=0.55)
    a = p.parse_args()
    cost, d_words, f_words, added = rewrite_cost(a.draft, a.final)
    receipt = {
        "section": a.section,
        "rewrite_cost": cost,
        "draft_words": d_words,
        "final_words": f_words,
        "added_words": added,
        "verdict": "revoke" if cost >= a.max_cost else "keep",
    }
    print(json.dumps(receipt, indent=2))
    sys.exit(1 if receipt["verdict"] == "revoke" else 0)

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it against the merged file after the review PR lands:

python3 tools/draft_receipt.py \
  --section architecture/retries.md \
  --draft .drafts/architecture/retries.md \
  --final docs/architecture/retries.md > receipt.json
Enter fullscreen mode Exit fullscreen mode

Step 3: Band the cost and pick an action

The number matters less than the band, because bands map to decisions a reviewer can defend. Start with the table below, then replace the cutoffs with values from your own history as described in the calibration step.

Rewrite cost Reading Action on the section
below 0.25 draft was structurally usable keep drafting rights, record the low cost
0.25 to 0.55 normal review friction keep rights, flag for a prompt or template fix
0.55 to 0.80 reviewer rebuilt the argument rights move to probation, one more draft allowed
above 0.80 draft was effectively discarded revoke drafting rights, section becomes human-owned

Two consecutive receipts in the same band are required before a state change, which prevents a single bad week from closing a section permanently. The exception is full revocation, which is sticky by design.

Step 4: Update the ledger with sticky revocation

The ledger is a flat JSON file under version control, keyed by section path, holding the current rights state and the receipts that produced it. JSON is deliberate: it diffs cleanly in review and needs no parser in CI.

{
  "architecture/retries.md": {
    "state": "probation",
    "receipts": [
      {"date": "2026-10-19", "rewrite_cost": 0.61, "verdict": "revoke"},
      {"date": "2026-10-05", "rewrite_cost": 0.34, "verdict": "keep"}
    ],
    "regrant_note": null
  }
}
Enter fullscreen mode Exit fullscreen mode

A revoked section is not re-opened by another low-cost draft, because the draft that would produce it does not exist yet. Restoring rights requires a human commit that sets state back to probation and fills regrant_note with a reason. That single friction point is the entire governance mechanism, and it is enough.

Step 5: Wire the receipt into CI and review

Make the receipt a required check with two behaviours: fail the build when a section crosses into revoke, and post the receipt JSON as a PR comment so reviewers see the number before they argue about the prose. Exit code 1 from the script above is already the signal.

- name: doc draft receipt
  run: |
    python3 tools/draft_receipt.py \
      --section architecture/retries.md \
      --draft .drafts/architecture/retries.md \
      --final docs/architecture/retries.md | tee receipt.json
Enter fullscreen mode Exit fullscreen mode

Keep the drafting job's prompt and template version in the receipt too, otherwise you cannot tell whether your prompt changed or your reviewers did. That single added field turns the ledger into a prompt-regression log for free.

Calibrate the thresholds before you enforce them

Do not adopt the cutoffs above as facts about model drafting; they are a starting point that encodes nothing about your repository. Measure your baseline instead: compute rewrite cost between two human-authored revisions of the same section, several weeks apart, and take the median. That median is your cost of ordinary editorial churn, and any band below it is noise. Teams that skip this step usually set the bar too low and revoke rights on healthy sections within a month.

Two mechanical caveats are worth quoting in your policy. Sections shorter than roughly eighty words are too noisy for word-level matching, since a single sentence rewrite can push cost past 0.6. Sections that are mostly tables, code blocks, or CLI transcripts also need a separate tokenizer, because identifier renames look like full rewrite to the default pattern.

Where the drafting job itself can run

The receipt workflow does not care which runtime produced the draft, as long as the raw output is persisted before review. If you would rather not run the generation step on a developer laptop, MonkeyCode's free server option is a reasonable place to schedule the job, and its free model access keeps re-drafts cheap enough that a probation cycle is not a budget conversation.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

That cheapness matters for the workflow specifically: revocation is only fair if a section can earn its rights back without procurement. Allocate model and server capacity according to your own measurements rather than to any figure quoted in an article.

Limitations and who should skip this

Word-level survival does not detect fluent, confidently wrong prose, which is the failure mode that actually hurts readers. It also penalizes legitimate restructuring, so a section that a reviewer genuinely reorganised will look like a bad draft even when the model supplied most of the final sentences. Neither problem is fatal, but both mean the receipt should gate drafting rights and never serve as a quality score.

Skip this entirely if your repository is small enough that one maintainer reviews everything, and the ledger would cost more than the arguments it prevents. Skip it too if your process edits the model output in place inside a single commit, because there is no draft left to compare against. Teams that already run a factual-claim registry or a section-ownership gate should treat the receipt as the missing input to those gates, not as a replacement for them.

Start with three sections, one drafting job, and a ledger file that contains nothing but receipts. After a month you will have a rewrite-cost distribution for your own documentation, a list of sections that never should have been drafted, and a short prompt-diff history explaining why.

Top comments (0)