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

@@ -152,18 +152,33 @@ Until then the repo has the mechanism in source and not in effect.
> **Amendment (2026-09-16) — on a feature branch, the refresh installs `main`, not the branch.**
> Recorded after it happened. The dependencies resolve against the remote default branch, so a
> session opened on a branch that changes `plugins/` loads `main`'s content, refreshed or not — a
> branch's own `.apm/` edits are live only once they are on the remote's `main`. Two visible
> effects follow. Content the branch *removes* comes back in the deployed install: on
> `docs/simplification-audit` a refresh redeployed `main`'s `skill-audit` and `agent-audit` over
> the branch's merged `factory-audit`, and re-materialised `main`'s `plugins/bin/.mcp.json` into
> `apm_modules/`, so the gitignored root `.mcp.json` regained the `obsidian` server the branch
> deleted — invisible to `git status`. And the rewritten `apm.lock.yaml` records `main`'s commit,
> so on a branch it should be discarded (`git checkout -- apm.lock.yaml`, then `apm install` to
> bring the deployed tree back in line with the lock, or `apm pack --check-clean` refuses to run),
> not committed. Skipping the refresh off the default branch was considered and rejected: it would
> not make the branch live, only freeze the session on an older `main` — the silent staleness this
> ADR exists to prevent. Both effects end when the branch merges; apm removes a server that is no
> longer declared on its next update.
> branch's own `.apm/` edits are live only once they are on the remote's `main`. Content the
> branch *removes* comes back in the deployed install: on `docs/simplification-audit` a refresh
> redeployed `main`'s `skill-audit` and `agent-audit` over the branch's merged `factory-audit`, and
> re-materialised `main`'s `plugins/bin/.mcp.json` into `apm_modules/`, so the gitignored root
> `.mcp.json` regained the `obsidian` server the branch deleted — invisible to `git status`.
> Skipping the refresh off the default branch was considered and rejected: it would not make the
> branch live, only freeze the session on an older `main` — the silent staleness this ADR exists to
> prevent. The redeployed content goes away once the branch merges. For the server, the next
> `apm update` or `apm install` that resolves a tree no longer declaring it removes it from
> `.mcp.json`: both commands call `MCPIntegrator.remove_stale` for every server listed under the
> lock's `mcp_servers` that no dependency declares any more (`apm_cli/install/mcp/integration.py`).
>
> The rewritten `apm.lock.yaml` is a separate matter. It records `main`'s current tip, but the
> branch's committed lock records a `main` commit too, just an older one, so committing the
> rewrite would not swap the branch for `main`. On a feature branch, discard it anyway
> (`git checkout -- apm.lock.yaml`, then `apm install`), for two reasons. First, it keeps lock
> churn that has nothing to do with the branch out of the branch's diff. Second, it keeps the
> deployed tree consistent with the committed lock that the `apm-pack-check-clean` pre-push hook
> reads. In this repo `apm pack` builds a bundle from the lock before its `--check-clean` gate
> runs, and it stops with a build error ("deployed files are missing on disk -- run 'apm
> install'") when a file the lock lists is absent (`apm_cli/bundle/packer.py`, `pack_bundle`).
> A refresh leaves the tree in that state whenever the newer `main` dropped a file the older lock
> still lists. Under `--dry-run` it checks only that each file exists, not its content hash. The
> cost of discarding is real: the session then runs the older `main` that the lock records, which
> is the staleness the rejected skip would have caused. That cost is accepted on a feature branch,
> and it does not last. At the next session start the hook finds the restored lock behind `main`
> and refreshes again.
**`.claude/settings.json` stops being `{"hooks": {}}`.** apm merges the hook into it and tracks
ownership in a `.claude/apm-hooks.json` sidecar, with the script copied to