An OSS patch should touch only owned files. Unbounded diffs hide regressions and invite drive-by refactors. Freeze pathspecs and CODEOWNERS before any model review.
The failure mode
Assistants expand scope when the whole tree is visible. Neighbor modules receive drive-by cleanups during one-line fixes. Maintainers then review style noise instead of the defect.
Issue comments often describe a single failing function. The opened pull request then rewrites three packages. Review time then shifts to ownership instead of correctness.
This is not a taste argument about generated code. It is a git-radius problem with a checkable gate. The issue must name files before editors open.
Core rule
The issue defines a path allowlist on disk. Git enforces that allowlist on every staging step. A model may comment only after the allowlist holds.
The allowlist is not a chat preference or a remembered glob. Store it beside the worktree as review/paths.allow. Treat a missing file as a hard stop.
Artifact: an ownership map and pathspec gate
The script below is an example workflow, not a live benchmark. Contributors should rename branches and owners for the target repository. Run it from a throwaway clone, never from a secrets-bearing tree.
#!/usr/bin/env bash
# pathspec-gate.sh — example workflow for a bounded OSS patch
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
ALLOW="$ROOT/review/paths.allow"
OWNERS="$ROOT/CODEOWNERS"
MAP="$ROOT/review/ownership-map.txt"
REPORT="$ROOT/review/blast-radius.txt"
mkdir -p "$ROOT/review"
if [[ ! -f "$ALLOW" ]]; then
echo "missing $ALLOW" >&2
exit 2
fi
if [[ ! -f "$OWNERS" ]]; then
echo "missing $OWNERS" >&2
exit 2
fi
BASE="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
mapfile -t CHANGED < <(git diff --name-only "$BASE"...HEAD)
fail=0
{
echo "base $BASE"
echo "changed_count ${#CHANGED[@]}"
} > "$REPORT"
: > "$MAP"
for f in "${CHANGED[@]}"; do
allowed=0
while IFS= read -r spec; do
[[ -z "$spec" || "$spec" =~ ^# ]] && continue
case "$f" in
$spec) allowed=1; break ;;
esac
done < "$ALLOW"
owner_hit=0
while IFS= read -r line; do
[[ -z "$line" || "$line" =~ ^# ]] && continue
own_path="${line%% *}"
case "$f" in
${own_path}*) owner_hit=1; echo "$f :: $line" >> "$MAP"; break ;;
esac
done < "$OWNERS"
if [[ "$allowed" -eq 0 ]]; then
echo "OUT_OF_SCOPE $f" >> "$REPORT"
fail=1
elif [[ "$owner_hit" -eq 0 ]]; then
echo "UNOWNED $f" >> "$REPORT"
fail=1
else
echo "IN_SCOPE $f" >> "$REPORT"
fi
done
exit "$fail"
The script exits non-zero on extra or unowned files. Local CI can invoke it before compiling tests. Reviewers read review/blast-radius.txt and review/ownership-map.txt together.
Numbered workflow
1. Open a linked worktree for the issue
An assistant should not sit on the only local clone. Create a linked worktree pinned to upstream main. Failed experiments then die with that directory.
git fetch origin
git worktree add ../issue-8123 origin/main
cd ../issue-8123
git switch -c fix/8123-null-guard
mkdir -p review tools
Keep production credentials out of this worktree. Copy only the files the build system requires.
2. Build the allowlist from named paths
Read the issue title, labels, and linked file list. Write the smallest glob set that can hold a legal fix. Put those globs in review/paths.allow.
# review/paths.allow — example for a parser crash
src/parser/*.c
src/parser/*.h
tests/parser/**
docs/parser.md
Do not add src/** for staging convenience. Wide globs recreate the original scope failure. A later extra file must reopen the issue thread.
3. Overlay CODEOWNERS on the same paths
Open CODEOWNERS in the same sitting as the allowlist. Every allowlist path needs a named owner line. A path without an owner is already a social conflict.
# CODEOWNERS excerpt — example only
src/parser/ @parser-maintainers
tests/parser/ @parser-maintainers
docs/parser.md @parser-maintainers @docs-team
An allowlist path with no owner stops the workflow. The tracker must name an owner before coding starts. Guessed teams do not belong in the pull request.
4. Limit the editor and the index
Edit files under the allowlist only. Stage with explicit pathspecs on every commit. Refuse git add -A for the entire working tree.
git add -- src/parser/scan.c src/parser/scan.h
git add -- tests/parser/null_guard_test.cc
git diff --cached --stat
git diff --cached --check
git diff --cached --name-only
--check catches whitespace errors before review. --stat shows radius in one screen. --name-only becomes the later model input.
5. Run the gate before any test binary
A green suite on an unbounded diff is still a bad patch. The ownership map must pass first. Only then should unit tests run on the named component.
chmod +x tools/pathspec-gate.sh
./tools/pathspec-gate.sh
cmake --build build --target parser_unittests
./build/parser_unittests --gtest_filter=NullGuard.* \
| tee review/test.log
If tests require a directory outside the allowlist, the allowlist is wrong. Widen it with tracker evidence, not with local instinct. Record the extra path in the issue before restaging.
6. Shape the commit to the same radius
Bound patches still need a bound history. One logical commit should match one allowlist. Docs-only files stay out unless the issue lists them.
git commit -m "parser: guard NULL tokens in scan_ident"
git log -1 --format='%s%n%n%b'
git diff origin/main...HEAD --shortstat
A useful subject names the owned component first. The body should cite the issue number and review/paths.allow. Changelog files join the allowlist before they join the index.
# extra allowlist line only when the project requires it
CHANGELOG.md
7. Hand a model the radius, not the repository
Export names, owners, and the cached diff. Do not paste the whole tree into a prompt. Strip tokens from logs before any upload.
{
echo "## allowlist"
cat review/paths.allow
echo
echo "## ownership-map"
cat review/ownership-map.txt
echo
echo "## names"
git diff --cached --name-only
echo
echo "## stat"
git diff --cached --stat
} > review/radius.md
sed -i 's/ghp_[A-Za-z0-9]*/REDACTED/g' review/radius.md
sed -i 's/Bearer [^ ]*/Bearer REDACTED/g' review/radius.md
git diff --cached > review/cached.diff
The model input is review/radius.md plus review/cached.diff. Anything else is out of scope by construction.
Where a free coding assistant fits
A model is useful after the radius is frozen. It is not a substitute for CODEOWNERS or for git pathspecs.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. That pair can host a bounded diff review when the laptop is already compiling. The assistant should flag missing tests, unsafe API use, and lines that contradict the allowlist.
Do not ask the model to improve surrounding code. That prompt destroys the gate in one sentence. Ask it to list changed functions without tests. Ask it to quote the CODEOWNERS line for each path.
A sample prompt follows as an unexecuted template.
Review one bounded OSS patch and nothing else.
Discuss only files listed under ## names.
If a fix needs another path, answer OUT_OF_SCOPE.
Check tests, NULL handling, and log noise.
Do not propose refactors, renames, or dependency bumps.
The free server option is for redacted radius files, not for embargoed security diffs. Upload review/radius.md and review/cached.diff only after the sed redaction step.
Decision table
| Signal | Action | Forbidden shortcut |
|---|---|---|
| File outside allowlist | Stop and reopen the issue | Quiet git add of the extra path |
| Allowlist path with no CODEOWNER | Ping the tracker for an owner | Invent a team in the pull request |
| Tests need an extra module | Widen the allowlist with evidence | Keep the old file and ship |
| Model suggests a rename | Record OUT_OF_SCOPE | Apply the rename to look complete |
| Tests pass, gate fails | Reject the patch | Ship because the suite is green |
| Radius file contains tokens | Redact and rotate credentials | Upload the log anyway |
Limitations
This gate does not prove functional correctness. It only proves the diff stayed inside declared paths. A wrong allowlist will certify the wrong radius with great confidence.
CODEOWNERS files are often stale in long-lived trees. A listed team may no longer merge that directory. The example script does not check GitHub or GitLab membership.
Pathspec matching here uses bash case globbing. That is not identical to gitignore semantics. Leading slashes and nested globs need per-repo tests.
The merge-base line assumes origin/main or main. Repositories on master or trunk must edit that lookup. Monorepos usually need one allowlist per package, not one file at the root.
Model output remains advisory text. It cannot accept a CLA or DCO. It cannot judge whether a public API change needs a major version.
Free model access and a free server option are availability claims only. This article does not state quotas, model names, uptime, or hardware. Those product details change and must be read from current primary docs.
Who should not use this
Skip this gate for a one-word documentation typo. A single README fix does not need a linked worktree ritual.
Skip it when the issue is an explicit module refactor. Broad scope work needs a design review, not a glob file. The allowlist would only hide a missing architecture thread.
Do not point any assistant at a clone that holds production secrets. The radius files are for redacted logs and cached hunks.
Embargoed security patches need private review channels. A free server is the wrong place for those diffs.
Close
Keep the allowlist smaller than the first instinct. Run the ownership map before the test binary. Let a model read the radius files only after both gates are green.
If a bounded review loop would help a current issue, try free model access on a throwaway worktree and keep the production clone offline.
Top comments (0)