DEV Community

Taylor Wang
Taylor Wang

Posted on

Bound an OSS Patch With Pathspecs and CODEOWNERS

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

--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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)