| name | update |
| description | Sync user-project files with the plugin templates after an update.
Uses a baseline-hash mechanism to detect user edits: unmodified files
are auto-applied, modified files are skipped with a diff notice
(preserved). The `_harness_version` marker is bumped after a successful
sync.
Usage: /harness:update [--dry-run]
|
| allowed-tools | ["Bash","Read"] |
update — Sync user files after a plugin upgrade
A plugin upgrade (/plugin marketplace update harness@tkstardev) refreshes
the templates themselves, but user-project files (CLAUDE.md,
.claude/config.json, .claude/rules/*.md) live outside the plugin's
control. This skill closes that gap. Preserving user edits is the
first-class invariant.
Step 1 — Path + flag resolution
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$PROJECT_DIR/.claude}"
TEMPLATES_DIR="$PLUGIN_ROOT/templates"
source "$PLUGIN_ROOT/hooks/lib/baseline.sh"
DRY=0
for arg in "$@"; do
case "$arg" in
--dry-run) DRY=1 ;;
*) echo "unknown flag: $arg" >&2; exit 1 ;;
esac
done
Step 2 — Target file mapping
| Template | Target |
|---|
templates/CLAUDE.md.tmpl | CLAUDE.md |
templates/config.json.tmpl | .claude/config.json |
templates/rules/code-style.md.tmpl | .claude/rules/code-style.md |
templates/rules/file-conventions.md.tmpl | .claude/rules/file-conventions.md |
templates/rules/review-finding-format.md.tmpl | .claude/rules/review-finding-format.md |
Step 3 — Per-file decision
For each (src, target, key):
update_one() {
local src="$1" target="$2" key="$3"
if [[ -L "$target" ]]; then
echo " skipped $target (symlink — sync refused)"
return 0
fi
if [[ ! -f "$target" ]]; then
echo " missing $target (run /harness:init first)"
return 0
fi
if cmp -s "$src" "$target"; then
echo " = up-to-date $target"
(( DRY )) || baseline_record "$key" "$target"
return 0
fi
local stored current
stored=$(baseline_get "$key")
current=$(baseline_compute "$target")
if [[ -n "$stored" && "$stored" == "$current" ]]; then
if (( DRY )); then
echo " -> would sync $target (no user modification)"
else
if [[ -e "$target.bak" ]]; then
echo " skipped $target ($target.bak already exists — move/remove it and retry)"
return 0
fi
cp "$target" "$target.bak"
cp "$src" "$target"
baseline_record "$key" "$target"
echo " synced $target (backup: $target.bak)"
fi
return 0
fi
echo " user-modified $target"
echo " Templates: $src"
echo " Inspect diff: diff -u $target $src"
echo " After manual sync, call 'baseline_record \"$key\" \"$target\"' or"
echo " run /harness:init --force $target to overwrite (creates .bak)"
}
Step 4 — Driver loop (executed by the agent)
SYNCED=0; UPTODATE=0; USER_MOD=0; MISSING=0
run_one() {
local out
out=$(update_one "$1" "$2" "$3")
echo "$out"
case "$out" in
*"synced"*) SYNCED=$((SYNCED+1)) ;;
*"= up-to-date"*) UPTODATE=$((UPTODATE+1)) ;;
*"user-modified"*) USER_MOD=$((USER_MOD+1)) ;;
*"missing"*) MISSING=$((MISSING+1)) ;;
esac
}
run_one "$TEMPLATES_DIR/CLAUDE.md.tmpl" \
"$PROJECT_DIR/CLAUDE.md" \
"CLAUDE.md"
run_one "$TEMPLATES_DIR/config.json.tmpl" \
"$PROJECT_DIR/.claude/config.json" \
".claude/config.json"
for tmpl in "$TEMPLATES_DIR/rules/"*.tmpl; do
[[ -f "$tmpl" ]] || continue
base="$(basename "$tmpl" .tmpl)"
run_one "$tmpl" \
"$PROJECT_DIR/.claude/rules/$base" \
".claude/rules/$base"
done
Step 5 — Summary + version marker bump
echo ""
echo "--- harness:update complete ---"
echo "synced: $SYNCED, up-to-date: $UPTODATE, user-modified: $USER_MOD, missing: $MISSING"
if [[ "$USER_MOD" -gt 0 ]]; then
echo ""
echo "$USER_MOD file(s) were skipped because user modifications were detected."
echo "Review them manually, then use /harness:init --force to overwrite."
fi
The _harness_version marker bumps automatically as a side-effect of cp (the templates already carry the new value) — no extra step required.
Operational guarantees
- User-edit protection: a baseline-hash mismatch always blocks the auto-overwrite path.
- Automatic backup: even the auto-sync path writes
<file>.bak.
- Baseline consistency: every successful
cp immediately refreshes the baseline so the next update judges correctly.
- Dry-run:
--dry-run modifies nothing; it only prints the would-be actions.
Baseline-missing scenario
When harness-baseline.json is absent or a specific key is empty (stored=""):
- The file may predate the plugin upgrade. The agent has no way to verify
whether it matches the previous template version.
- For safety the file is treated as user-modified (Step 3 case 4) -> auto-sync
refused, diff guidance printed.
- After the user inspects and applies the sync manually (or via
/harness:init --force), the baseline is recorded at that point, and
subsequent updates judge correctly.
Sample output
$ /harness:update
[harness:update] templates: /Users/x/.claude/plugins/harness@tkstardev/templates v0.2.0
synced /Users/x/myproject/.claude/config.json (backup: .bak)
synced /Users/x/myproject/.claude/rules/code-style.md (backup: .bak)
user-modified /Users/x/myproject/CLAUDE.md
Templates: .../templates/CLAUDE.md.tmpl
Inspect diff: diff -u /Users/x/myproject/CLAUDE.md .../templates/CLAUDE.md.tmpl
...
= up-to-date /Users/x/myproject/.claude/rules/file-conventions.md
missing /Users/x/myproject/.claude/rules/review-finding-format.md
--- harness:update complete ---
synced: 2, up-to-date: 1, user-modified: 1, missing: 1
1 file(s) were skipped because user modifications were detected.