fix(kyberforge): give branch-aware advice for the refreshed apm lock

Why: the docs said to discard a refreshed apm.lock.yaml on a feature
branch because the refresh records main's commit, but the branch's own
lock records a (older) main commit too, and the SessionStart notice gave
the same "commit or discard" advice on every branch.

Implementation Notes:
- check-apm-current.sh picks fixed advice by branch: commit or discard
  deliberately on the default branch (origin/HEAD, else main), discard and
  reinstall on a feature branch; the branch name is never interpolated.
- README, AGENTS.md and ADR-0019 give the real reasons (no lock churn in
  the branch diff, deployed tree matches the committed lock), the cost
  (the session runs the older main) and that the next session start
  refreshes again.
- ADR-0019's check-clean and stale-server claims restated to match apm's
  source.

ADR: 0019
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 11:24:32 +00:00
parent 398515bcad
commit 807caf22ee
5 changed files with 101 additions and 15 deletions

View File

@@ -21,6 +21,11 @@ command -v python3 > /dev/null 2>&1 || { echo "python3 required"; exit 77; }
FAKE_BIN="$(mktemp -d)"
WORK="$(mktemp -d)"
trap 'rm -rf "$FAKE_BIN" "$WORK"' EXIT
# The hook asks git which branch it is on. Stop git's discovery at the temp
# root so a $TMPDIR that happens to sit inside a checkout cannot leak a branch
# into the fixtures that are meant to be outside one.
export GIT_CEILING_DIRECTORIES
GIT_CEILING_DIRECTORIES="$(dirname "$WORK")"
# Mock `apm`. $1 chooses what `apm outdated` reports; $2 the exit code of
# `apm update`. Sentinel files record whether update was actually invoked and
@@ -124,6 +129,55 @@ else
fail "emits valid JSON"
fi
# ---------------------------------------------------------------------------
echo ""
echo "--- lock advice follows the branch ---"
# ---------------------------------------------------------------------------
# ADR-0019: on the default branch the rewritten lock is a real update to commit or
# discard; on a feature branch it is unrelated churn to discard. Outside a git
# checkout (the fixture above) the neutral advice stands.
advice_of() { json_field additionalContext <<< "$1"; }
grep -q "commit it or discard it deliberately" <<< "$(advice_of "$out")" \
&& pass "gives neutral lock advice outside a git checkout" \
|| fail "outside a git checkout the advice should stay neutral"
if command -v git > /dev/null 2>&1; then
REPO="$WORK/repo"
mkdir -p "$REPO"
git -C "$REPO" init -q -b main
git -C "$REPO" -c user.email=probe@example.invalid -c user.name=probe \
commit -q --allow-empty -m init
touch "$REPO/apm.lock.yaml"
out="$(run_hook_in "$REPO" "$REPO")"
grep -q "default branch, so commit it or discard it deliberately" <<< "$(advice_of "$out")" \
&& pass "on main, says to commit or discard the lock deliberately" \
|| fail "on main the advice should be commit-or-discard: $(advice_of "$out")"
git -C "$REPO" checkout -q -b feature/x
out="$(run_hook_in "$REPO" "$REPO")"
advice="$(advice_of "$out")"
grep -qF "feature branch, so discard it: git checkout -- apm.lock.yaml && apm install" <<< "$advice" \
&& pass "on a feature branch, says to discard the lock and reinstall" \
|| fail "on a feature branch the advice should be discard-and-install: $advice"
grep -q "commit it" <<< "$advice" \
&& fail "on a feature branch the advice must not suggest committing the lock" \
|| pass "on a feature branch, does not suggest committing the lock"
echo "$out" | python3 -m json.tool > /dev/null 2>&1 \
&& pass "feature-branch notice is valid JSON" || fail "feature-branch notice broke the JSON"
# A remote whose default branch is not `main` is honoured via origin/HEAD.
git -C "$REPO" update-ref refs/remotes/origin/feature/x HEAD
git -C "$REPO" symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/feature/x
out="$(run_hook_in "$REPO" "$REPO")"
grep -q "default branch, so commit it" <<< "$(advice_of "$out")" \
&& pass "reads the default branch from origin/HEAD when it is set" \
|| fail "should treat origin/HEAD's branch as the default: $(advice_of "$out")"
else
echo " (git not on PATH — branch-specific advice cases not run)"
fi
# ---------------------------------------------------------------------------
echo ""
echo "--- stale, refresh fails ---"