Critical invariant: Every file in gad-local-patches/ was backed up because the installer's hash comparison detected it was modified. The workflow must NEVER conclude "no custom content" for any backed-up file — that is a logical contradiction. When in doubt, classify as CONFLICT requiring user review, not SKIP.
Step 1: Detect backed-up patches
Check for local patches directory:
# Global install — detect runtime config directory
if [ -d "$HOME/.config/opencode/gad-local-patches" ]; then
PATCHES_DIR="$HOME/.config/opencode/gad-local-patches"
elif [ -d "$HOME/.opencode/gad-local-patches" ]; then
PATCHES_DIR="$HOME/.opencode/gad-local-patches"
elif [ -d "$HOME/.gemini/gad-local-patches" ]; then
PATCHES_DIR="$HOME/.gemini/gad-local-patches"
else
PATCHES_DIR="${GAD_PATCHES_DIR:-$HOME/.config/gad-local-patches}"
fi
# Local install fallback — check all runtime directories
if [ ! -d "$PATCHES_DIR" ]; then
for dir in .config/opencode .opencode .gemini .claude; do
if [ -d "./$dir/gad-local-patches" ]; then
PATCHES_DIR="./$dir/gad-local-patches"
break
fi
done
fi
Read backup-meta.json from the patches directory.
If no patches found:
No local patches found. Nothing to reapply.
Local patches are automatically saved when you run /gad:update
after modifying any GAD workflow, command, or agent files.
Exit.
Step 2: Determine baseline for three-way comparison
The quality of the merge depends on having a pristine baseline — the original unmodified version of each file from the pre-update GAD release. This enables three-way comparison:
- Pristine baseline (original GAD file before any user edits)
- User's version (backed up in
gad-local-patches/) - New version (freshly installed after update)
Check for baseline sources in priority order:
Option A: Git history (most reliable)
If the config directory is a git repository:
CONFIG_DIR=$(dirname "$PATCHES_DIR")
if git -C "$CONFIG_DIR" rev-parse --git-dir >/dev/null 2>&1; then
HAS_GIT=true
fi
When HAS_GIT=true, use git log to find the commit where GAD was originally installed (before user edits). For each file, the pristine baseline can be extracted with:
git -C "$CONFIG_DIR" log --diff-filter=A --format="%H" -- "{file_path}"
This gives the commit that first added the file (the install commit). Extract the pristine version:
git -C "$CONFIG_DIR" show {install_commit}:{file_path}
Option B: Pristine snapshot directory
Check if a gad-pristine/ directory exists alongside gad-local-patches/:
PRISTINE_DIR="$CONFIG_DIR/gad-pristine"
If it exists, the installer saved pristine copies at install time. Use these as the baseline.
Option C: No baseline available (two-way fallback)
If neither git history nor pristine snapshots are available, fall back to two-way comparison — but with strengthened heuristics (see Step 3).
Step 3: Show patch summary
## Local Patches to Reapply
**Backed up from:** v{from_version}
**Current version:** {read VERSION file}
**Files modified:** {count}
**Merge strategy:** {three-way (git) | three-way (pristine) | two-way (enhanced)}
| # | File | Status |
|---|------|--------|
| 1 | {file_path} | Pending |
| 2 | {file_path} | Pending |
Step 4: Merge each file
For each file in backup-meta.json:
- Read the backed-up version (user's modified copy from
gad-local-patches/) - Read the newly installed version (current file after update)
- If available, read the pristine baseline (from git history or
gad-pristine/)
Three-way merge (when baseline is available)
Compare the three versions to isolate changes:
- User changes = diff(pristine → user's version) — these are the customizations to preserve
- Upstream changes = diff(pristine → new version) — these are version updates to accept
Merge rules:
- Sections changed only by user → apply user's version
- Sections changed only by upstream → accept upstream version
- Sections changed by both → flag as CONFLICT, show both, ask user
- Sections unchanged by either → use new version (identical to all three)
Two-way merge (fallback when no baseline)
When no pristine baseline is available, use these strengthened heuristics:
CRITICAL RULE: Every file in this backup directory was explicitly detected as modified by the installer's SHA-256 hash comparison. "No custom content" is never a valid conclusion.
For each file: a. Read both versions completely b. Identify ALL differences, then classify each as:
- Mechanical drift — path substitutions between runtime config directories, variable additions (
${GSD_WS},${AGENT_SKILLS_*}), error handling additions (|| true) - User customization — added steps/sections, removed sections, reordered content, changed behavior, added frontmatter fields, modified instructions
c. If ANY differences remain after filtering out mechanical drift → those are user customizations. Merge them. d. If ALL differences appear to be mechanical drift → still flag as CONFLICT. The installer's hash check already proved this file was modified. Ask the user: "This file appears to only have path/variable differences. Were there intentional customizations?" Do NOT silently skip.
Git-enhanced two-way merge
When the config directory is a git repo but the pristine install commit can't be found, use commit history to identify user changes:
# Find non-update commits that touched this file
git -C "$CONFIG_DIR" log --oneline --no-merges -- "{file_path}" | grep -vi "update\|gad-install"
Each matching commit represents an intentional user modification. Use the commit messages and diffs to understand what was changed and why.
- Write merged result to the installed location
- Report status per file:
Merged— user modifications applied cleanly (show summary of what was preserved)Conflict— user reviewed and chose resolutionIncorporated— user's modification was already adopted upstream (only valid when pristine baseline confirms this)
Never report Skipped — no custom content. If a file is in the backup, it has custom content.
Step 5: Update manifest
After reapplying, regenerate the file manifest so future updates correctly detect these as user modifications:
# The manifest will be regenerated on next /gad:update
# For now, just note which files were modified
Step 6: Cleanup option
Ask user:
- "Keep patch backups for reference?" → preserve
gad-local-patches/ - "Clean up patch backups?" → remove
gad-local-patches/directory
Step 7: Report
## Patches Reapplied
| # | File | Result | User Changes Preserved |
|---|------|--------|----------------------|
| 1 | {file_path} | Merged | Added step X, modified section Y |
| 2 | {file_path} | Incorporated | Already in upstream v{version} |
| 3 | {file_path} | Conflict resolved | User chose: keep custom section |
{count} file(s) updated. Your local modifications are active again.