A documentation pipeline should give a model the sentences between the code blocks and nothing else, because the code blocks already have an owner: your repository. If a model can type a fence, it can retype an example from memory, and the copy your reader pastes will drift from the function your tests exercise. The workflow below makes fences un-writable by the model, splices them from marked source regions, and rejects any draft that tries to introduce one. Everything here is a design you can implement; the script is illustrative and was not executed for this article.
The failure this prevents
Hand-typed examples fail quietly because nobody diffs them against source on the day the source changes. A renamed parameter or reordered argument leaves the page syntactically valid and semantically wrong, which is worse than an obvious break. The usual mitigation is review attention, and review attention is exactly what a fluent draft consumes. Removing the model's write access to fences converts a judgement problem into a build failure.
The second half is masking. If the model never sees the fence bodies, it cannot paraphrase them, and if the draft arrives without fences, reinsertion is mechanical. You keep the model's useful contribution, which is connective prose, and you delete its riskiest one.
Step 1: Mark the canonical regions in source
A line range in a fence rots the first time someone adds an import, so I prefer named comment regions that survive edits above them. The markers sit in the file your tests already cover, which means the example cannot be a special case that only documentation compiles.
# src/retry.py # region: backoff for attempt in range(max_attempts): delay = base_delay * (2 ** attempt) time.sleep(delay) # endregion: backoff Keep regions short and self-contained, because a reader copies a region and expects it to mean something on its own. If a region needs five surrounding definitions to run, it is the wrong region. Pick the smallest fragment that still teaches the point.
Step 2: Splice fences from regions and lock the result
The gate reads each fence's info string, resolves the region, and compares the body byte for byte. A fence without a source= attribute is a build error, not a warning, because a warning becomes baseline noise within a month.
#!/usr/bin/env python3 # docs_gate.py - ILLUSTRATIVE. Written for this article; not executed here. import pathlib import re import sys ROOT = pathlib.Path(__file__).parent OPEN = re.compile(r"^``` (?P<info>[^`]*)$") SPEC = re.compile(r"^\w+ source=(?P<path>[^\s#]+)#region=(?P<region>[\w.-]+)$") def read_fences(text: str): """Yield (line_number, info_string, body) for each top-level fence.""" lines, found, i = text.splitlines(), [], 0 while i < len(lines): m = OPEN.match(lines[i]) if not m: i += 1 continue start, i = i, i + 1 body = [] while i < len(lines) and not lines[i].startswith("```"): body.append(lines[i]) i += 1 if i >= len(lines): sys.exit(f"unclosed fence opened at line {start + 1}") found.append((start + 1, m.group("info").strip(), "\n".join(body))) i += 1 return found def slice_region(path: pathlib.Path, region: str) -> str: begin, end = f"# region: {region}", f"# endregion: {region}" lines = path.read_text(encoding="utf-8").splitlines() lo = next((n for n, l in enumerate(lines) if l.strip() == begin), None) hi = next((n for n, l in enumerate(lines) if l.strip() == end), None) if lo is None or hi is None: sys.exit(f"{path}: missing marker for region {region!r}") if hi <= lo: sys.exit(f"{path}: region {region!r} ends before it starts") return "\n".join(lines[lo + 1:hi]) def expected_body(info: str) -> str: m = SPEC.match(info) if not m: sys.exit(f"fence needs 'lang source=path#region=name', got {info!r}") return slice_region(ROOT / m.group("path"), m.group("region")) def check(doc: pathlib.Path) -> None: for line, info, body in read_fences(doc.read_text(encoding="utf-8")): want = expected_body(info) if body.strip() != want.strip(): sys.exit(f"{doc}:{line} has drifted from its source region") print(f"ok {doc}") if __name__ == "__main__": for target in sys.argv[1:]: check(pathlib.Path(target)) ``{% endraw %}{% raw %}` Run it as `{% endraw %}python docs_gate.py docs/retry.md{% raw %}` and treat the non-zero exit as a merge blocker. Note what the check proves: the fence matches the region now. It proves nothing about whether the surrounding paragraph explains that region correctly, and that gap is where the human review below belongs. ## Step 3: Mask the fences and let the model draft only prose Extract every fence, replace it with a stable token, and send the masked page plus the change description. The model returns prose with tokens intact, and the reinsertion step puts the original bytes back. A draft containing a backtick fence fails that step immediately, which is the invariant the whole design exists to enforce. `{% endraw %}{% raw %}``python # draft_docs.py - ILLUSTRATIVE, and deliberately endpoint-agnostic. import re import sys FENCE = re.compile(r"^```[^\n]*\n.*?^```$", re.S | re.M) TOKEN = re.compile(r"\[\[FENCE_(\d+)\]\]") def mask(text: str): parts = [] return FENCE.sub(lambda m: parts.append(m.group(0)) and f"[[FENCE_{len(parts)}]]", text), parts def unmask(draft: str, parts: list) -> str: if "```" in draft: sys.exit("draft introduced a fence; reject it and re-prompt") referenced = {int(n) for n in TOKEN.findall(draft)} if referenced != set(range(1, len(parts) + 1)): sys.exit("draft dropped or invented a fence token") for n, fence in enumerate(parts, start=1): draft = draft.replace(f"[[FENCE_{n}]]", fence) return draft `{% endraw %}{% raw %}`` Masking costs the model context, so pass a one-line purpose for each token instead of a bare placeholder, and expect slightly more generic transitions as a result. This is where MonkeyCode fits my own setup: the masking and check steps run on the operator-supplied free server option, and the prose request goes to the operator-supplied free model access, so the whole loop stays inside a budget I can justify for internal docs. Availability is the only thing I am claiming there; verify current limits and model options on your side before wiring it in. Disclosure: This article was prepared as part of MonkeyCode's product outreach. ## Step 4: Decide what the model may draft and what a human must own The table is the part I would copy into a policy file, because the interesting decisions happen before any prompt is written. Rows are doc elements, not sections, since a single page mixes both kinds. | Doc element | Drafted by | Reason | | --- | --- | --- | | Transitions between two verified facts | Model | Cheap to review, low blast radius | | The body of a code fence | Build step | Must be byte-identical to what tests exercise | | Which region a reader should copy | Named human | Defines the example's scope and prerequisites | | Version numbers, defaults, limits | Named human, with a source link | Age fastest and are the costliest to get wrong | | Caveats and "do not do this" notes | Named human | Omission is the failure mode that hurts readers | | Titles and headings | Human, model may suggest | Headings become anchors and links elsewhere | Two rules matter more than the rest. A model may never assert a number it cannot point at in the supplied context, and a model may never be the only place a limitation is mentioned. Both rules are reviewable in a diff without reading the whole page. ## Step 5: Verify the gate with three deliberate breakages A gate nobody has seen fail is a gate you do not know. Introduce each of these on a branch, run the check, and confirm the failure message names the file and line. 1. Edit one line inside a source region without updating the doc, then run `{% endraw %}python docs_gate.py docs/retry.md{% raw %}` and expect a drift error. 2. Delete a fence's `source=` attribute and expect a specification error rather than a pass. 3. Feed the reinsertion step a draft containing a fenced block and expect rejection before any file is written. Record the three outputs in the pull request that introduces the gate, so the next maintainer can see the failure modes without reconstructing them. ## Limitations and who should not use this Region markers add comments to production files, and some teams will reasonably refuse that trade. Masking also removes the model's ability to notice that an example is misleading, since it never reads the example. The gate checks identity between page and source, and it cannot check that the explanation is useful or that the region is the right teaching size. Skip this approach for a single small README where a build script is more machinery than the page deserves. Skip it for tutorials whose examples must be trimmed for readability, because a splice cannot shorten a region for you. And skip it if you have no CI that runs on documentation changes, since a check nobody runs is documentation of an intention. ## Closing note The durable rule is narrow: the model owns prose that describes, the build owns code that executes, and a named human owns the claims a reader might act on. If you want to test the masking loop cheaply before committing to it, run step three against a free model endpoint you already trust and keep the fence check local. 
