DEV Community

Avery Lin
Avery Lin

Posted on

Draft the Stable, Own the Moving: A Change-Velocity Contract for AI-Generated Docs

Most AI documentation workflows assign ownership by document type: tutorials go to the model, API references go to the engineering team, and migration notes go to platform owners. In a real repository, that split collapses within two release cycles, because a tutorial can carry one fragile version pin while a reference page quietly drifts every sprint. Responsibility ends up assigned by the name of the document rather than by how often its content actually changes or breaks. A better split replaces document type with change velocity, and the model drafts exactly the sections whose cost of going stale is lowest.

This article walks through a reproducible workflow: measure per-section churn from git history, tag each section with risk markers, and translate the two signals into a drafting contract the model must honor. The contract is not a promise made in a prompt, but a section-level permission list generated from measurable repository data.

A Cheaper Signal: Per-Section Churn

Most teams have no budget to measure how often each documentation sentence goes stale, but git history already provides a reasonable proxy. A section edited in every release needs a human who understands why that churn happens; a section untouched for two years may need no drafting budget at all. Git's -L line-range option gives a per-section commit count even when files are renamed, and the following script converts one Markdown document into a per-section scoring table.

#!/usr/bin/env bash
# section_churn.sh — approximate per-section commit counts for a Markdown doc.
# Usage: ./section_churn.sh docs/user-guide.md
set -euo pipefail
doc="$1"
python3 - "$doc" <<'PY'
import re, subprocess, sys

path = sys.argv[1]
lines = open(path, encoding="utf-8").read().splitlines()
heads = [(i + 1, ln.strip("# ").strip())
         for i, ln in enumerate(lines)
         if ln.startswith(("# ", "## "))]
if not heads:
    heads = [(1, path)]
heads.append((len(lines) + 1, None))

commit_re = re.compile(r"^[0-9a-f]{7,40} ")

for idx in range(len(heads) - 1):
    start, name = heads[idx]
    end = heads[idx + 1][0] - 1
    proc = subprocess.run(
        ["git", "log", "--oneline", "-L", f"{start},{end}:{path}", "--", path],
        capture_output=True, text=True, check=False,
    )
    n = sum(1 for l in proc.stdout.splitlines() if commit_re.match(l))
    print(f"{n}\t{name}")
PY
Enter fullscreen mode Exit fullscreen mode

The output is a tab-separated table of commit counts per heading, which you can sort to find the sections that actually move. This is an approximation rather than a measurement, because large refactors or renames can flatten line history into one opaque chunk. Treat a low churn score as the beginning of a question, not as proof that a section is safe to automate, and check whether a quiet section is quiet because nobody reads it.

Turning the Numbers into a Drafting Contract

Churn alone is not enough; a stable section can still hold version pins, example URLs, or authentication notes that are expensive when wrong. Pair the commit count with content risk markers, and the decision table below decides which sections a model may draft.

Section churn Risk markers Drafting decision
Low None: pure prose, stable references Model drafts freely
Low Version numbers, URLs, CLI syntax Model drafts; a second pass verifies the pins
High Config keys, local paths, failure modes Human owns; the model may reformat the draft
High API contracts, access control, migrations Human writes; the model is not invoked

When you cannot explain why a section changes, move it one row to the right, because unexplained churn usually hides a contract that no one explicitly owns. Write the outcomes of that table into a small contract file that the drafting step reads before it starts:

# drafting_budget.yaml — section-level permissions for model drafting
drafting_budget:
  doc: docs/user-guide.md
  churn_baseline: 2026-08-31
  sections:
    - heading: "Authentication basics"
      decision: agent
    - heading: "Token renewal"
      decision: verify
      checks: ["grep for version pins", "confirm curl flags"]
    - heading: "Breaking changes"
      decision: human
Enter fullscreen mode Exit fullscreen mode

The contract is the artifact that separates this approach from document-type heuristics: a section marked agent is a candidate for model drafting, a section marked verify needs a deterministic check, and a section marked human is excluded from the model context entirely. Re-run the churn script before each drafting cycle, because the contract is a snapshot of repository behavior, not a permanent constitution.

Making the Model Honor the Section Boundaries

A contract only works if the drafting step receives it, so extract the permitted headings into the model prompt with a small command rather than trusting the model to remember the file:

python3 -c "
import yaml
budget = yaml.safe_load(open('drafting_budget.yaml'))['drafting_budget']
allowed = [s['heading'] for s in budget['sections'] if s['decision'] == 'agent' or s['decision'] == 'verify']
print('\n'.join(allowed))
"
Enter fullscreen mode Exit fullscreen mode

Put that list in the system context, and add one explicit rule: sections not listed are off-limits, and the model should say why it refused instead of editing them silently. When the draft comes back, run the verify checks, diff only the allowed sections, and reject any change that crosses a boundary defined in the file.

This stage is also where the cost model matters: the contract deliberately labels most sections as low risk, so the drafting run does not need an always-on or expensive pipeline. In my workflow, I use MonkeyCode's free model access and free server option to run the low-risk drafting steps, which keeps the per-cycle cost near zero while the human-owned sections stay untouched by the generation stage. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The workflow itself lives entirely in git history and the contract file, so the process still works without the product; the free options just make it cheap to iterate many times.

Limitations and Who Should Not Use This

This method assumes a repository with meaningful history, honest commit discipline, and sections that actually distinguish themselves in git. A repo with three large commits, a single monolithic deployment doc, or generated changelogs will produce a churn table that is noise; the script does not measure author intent, reviewer experience, or whether the churn was caused by a script renaming every heading. Teams that still manually approve every documentation change have nothing to optimize yet, and teams working on regulated or contractual text should keep the human gate on every word, regardless of what the numbers say.

The approach also breaks down when a file is large enough that git log -L becomes slow, so scope the script to one document at a time and accept that one giant refactor can inflate the count for every section touched. If the churn table consistently contradicts what maintainers feel about the doc, trust the maintainers and fix the history signal instead of rewriting the table.

Next time you split drafting responsibility, do not ask what category a document belongs to; ask how often each section changes and which string inside it is the most expensive to get wrong. When those two answers drive the split, a model drafts sections that are cheap to regress, and a human reviews the section that actually expresses the system's contract.

Top comments (0)