Documentation quality degrades for a structural reason, not a model reason: teams treat generation as one prompt instead of a pipeline with explicit ownership boundaries. A free-tier model and a free server can automate the mechanical first pass if you encode what the model may draft and what a human must sign. This article walks through a reproducible handoff pipeline that runs entirely on free resources, with a script, an ownership matrix, and a merge-time checklist.
Why generated docs rot
The common failure mode is not bad prose but unowned claims that nobody can trace back to a decision. A model that drafts architecture rationale, security notes, and onboarding paths is making decisions it cannot observe, and those decisions become documentation debt the moment they merge. API reference scaffolding, parameter tables, and changelog drafts are different because they derive directly from code that a human already reviewed. The pipeline below separates those two categories before any token is spent, which keeps the model in the drafting role and the human in the ownership role.
The ownership matrix
Before writing any code, define which artifacts the model may draft and which a human must own. The matrix below is the contract that both the prompt and the review checklist enforce.
| Artifact | Model may draft | Human must own | Reason |
|---|---|---|---|
| API reference scaffolding | Yes | Review | Derives from signatures in the diff |
| Parameter tables | Yes | Review | Derives from function definitions |
| Changelog drafts | Yes | Edit | Needs release context and user impact |
| Getting-started prose | First pass | Rewrite | Needs real user paths and examples |
| Architecture rationale | No | Yes | Decision history is unobservable |
| Security and compliance notes | No | Yes | Liability and accuracy constraints |
| Runnable examples | No | Yes | Must execute against the real code |
This matrix does two jobs at once: it constrains the drafting prompt, and it gives the human reviewer a concrete checklist for the merge gate. Teams that skip this step usually discover the problem in production, when a confident sentence about error handling turns out to be invented.
Step 1: Extract the diff context
The drafting stage should never ask the model to recall your codebase from memory. A small script reads the merged PR diff, collects changed function signatures and public exports, and writes a JSON context file that grounds the model in actual code.
#!/usr/bin/env python3
"""Extract public API changes from a diff for docs drafting."""
import json
import re
import subprocess
import sys
SIGNATURE_RE = re.compile(
r"^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)|^class\s+(\w+)",
re.MULTILINE,
)
def changed_lines(base: str, head: str) -> list[str]:
diff = subprocess.run(
["git", "diff", base, head, "--", "*.py"],
capture_output=True, text=True, check=True,
).stdout
return [ln for ln in diff.splitlines()
if ln.startswith("+") and not ln.startswith("+++")]
def extract_signatures(lines: list[str]) -> list[dict]:
found = []
for ln in lines:
for match in SIGNATURE_RE.finditer(ln):
name, params, _ = match.groups()
found.append({"name": name, "params": params.strip() or "(none)"})
return found
if __name__ == "__main__":
base, head = sys.argv[1], sys.argv[2]
print(json.dumps(extract_signatures(changed_lines(base, head)), indent=2))
The regex is intentionally narrow because it targets public definitions only; private helpers and test code stay out of the context file. If your language is not Python, the same idea applies with a tree-sitter query or a language server instead of a regex.
Step 2: Draft with a constrained contract
The context file feeds a prompt that explicitly forbids the model from making claims beyond the provided symbols. The drafting contract below is the prompt-level enforcement of the ownership matrix.
DRAFTING_CONTRACT = """You are a documentation drafter, not an architect.
Draft only from the JSON context provided. Rules:
1. Describe only functions and classes present in the context.
2. Mark any behavior you cannot verify as TODO(verify).
3. Never invent version numbers, deprecation dates, or performance claims.
4. Output Markdown with one section per symbol.
"""
The request function is a minimal example that assumes an OpenAI-compatible chat completions shape, so verify the response format against your actual endpoint before trusting it.
import json
import os
import urllib.request
def draft(context: dict, endpoint: str, model: str) -> str:
payload = {
"model": model,
"messages": [
{"role": "system", "content": DRAFTING_CONTRACT},
{"role": "user", "content": json.dumps(context)},
],
"temperature": 0.2,
}
req = urllib.request.Request(
endpoint,
data=json.dumps(payload).encode(),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['API_KEY']}",
},
)
with urllib.request.urlopen(req) as resp:
return json.load(resp)["choices"][0]["message"]["content"]
This stage runs on every merge, so its cost must be near zero, and that is where free-tier access changes the economics of the pipeline. MonkeyCode's free model access fits this constraint, and the endpoint variable keeps the provider swappable if your requirements change. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Step 3: Schedule the drafting job on a free server
A cron job or a free CI runner invokes the script after each merge and opens a draft PR with the generated Markdown. The scheduling layer is deliberately boring because the value is in the ownership gate, not in the automation itself.
# Every hour, check for new merges and draft docs for the latest commit.
0 * * * * cd /srv/docs && ./docs_draft.py origin/main HEAD > docs/drafts/latest.md
MonkeyCode's free server option is one way to host this stage without maintaining GPU infrastructure; the script only needs an OpenAI-compatible endpoint, so moving to a different host is a one-line change. Keep the draft in a separate directory and a separate branch so the generated file never masquerades as reviewed documentation.
Step 4: Enforce the human sign-off gate
The draft PR carries a checklist that blocks merge until a human verifies each ownership category. This gate is the actual quality control; the model output is explicitly a starting point and never a mergeable artifact.
## Docs draft review
- [ ] Every symbol described exists in the current source
- [ ] No version, date, or performance claims were added by the model
- [ ] TODO(verify) items are resolved or removed
- [ ] Runnable examples were executed locally
- [ ] Architecture or security content was reviewed by a maintainer
If a reviewer cannot check the first item, the drafting contract failed and the pipeline should be fixed before the next merge. That feedback loop is what turns a free-tier drafting tool into a repeatable process rather than a one-off experiment.
Limitations and who should not use this
Free tiers carry rate limits and concurrency constraints, so batch your drafting jobs instead of streaming them per keystroke. The model can still hallucinate inside a constrained context, which means the checklist is mandatory and not a formality. Teams publishing contractual API documentation, security-sensitive projects, and repositories with no human review capacity should not adopt this workflow, because an unread draft is worse than no documentation at all.
If you want to try this pipeline this week, point the endpoint variable at a free tier and start with one module; the ownership matrix will tell you which docs are safe to automate first.
Top comments (0)