docs: source ADR-0029 claims and sync ADRs and hook docs with behaviour

- cite the VS Code prompt-file deprecation and the verbatim apm quote
- add ADR-0029 boundary-clause enforcement and Consequences
- mark superseded ADR-0019 passages; record neutral lock advice, source
  fork and reloadSkills, amend for the hook hardening
- move the ADR-0025 amendment out of the Decision list
- amend ADR-0022 for create keeping 0.1.0
- fix hooks.md merge and event claims, README guard caveat, gates.md Vale
  globs, and pin the research registry URL

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
2026-09-29 08:00:39 +00:00
parent 4a4b598955
commit 641ebcac0e
12 changed files with 171 additions and 76 deletions

View File

@@ -34,7 +34,9 @@ behind and refreshes it in place.
*session* loads, and that is the moment the staleness does damage. It also enables two things a git
hook structurally cannot do: `additionalContext` puts the notice into the agent's context rather
than terminal scrollback nobody reads, and `reloadSkills: true` makes the host re-scan the skill
directories after the hook returns, so a refresh lands in the running session without a restart.
directories after the hook returns, so a refresh lands in the running session without a restart
(`reloadSkills` is a documented `SessionStart` `hookSpecificOutput` field:
code.claude.com/docs/en/hooks, checked 2026-09-29).
apm's own lifecycle events (`pre-/post-install`, `pre-/post-update`, `pre-/post-uninstall`) were
rejected: they fire around apm operations already chosen, so they can announce a refresh but never
@@ -51,8 +53,9 @@ Three sub-decisions:
drift. A hook shipped in a package is written into that file by apm itself, so it is apm's output
and does not drift. `.claude/settings.local.json` also works but is gitignored and machine-local,
which fails the requirement that this travel with the repo.
- **`startup` matcher only.** `resume`, `clear`, `compact` and `fork` would re-run the check on every
compaction, and a compaction is not an event after which the remote can have moved.
- **`startup` matcher only.** `resume`, `clear`, `compact` and `fork` — the other documented
`SessionStart` matchers (code.claude.com/docs/en/hooks, checked 2026-09-29) — would re-run the check
on every compaction, and a compaction is not an event after which the remote can have moved.
The executable-trust gate is switched on at the same time. Root `apm.yml` gains an `executables:`
block allowing kyberforge's hooks and bin.
@@ -142,6 +145,22 @@ value to be larger — so changing either side without the other fails the suite
> Re-measured: ~24–26 s for the same six-behind refresh, warm, on a LAN remote — well inside the
> 380 s above.
> **Amendment (2026-09-29) — the time limits are harder to escape, and portable.** Four changes to
> `check-apm-current.sh` (PR #144):
>
> - Both limits are now `timeout -k 5`: a child that ignores SIGTERM is SIGKILLed 5 s later. The
> worst case is 60 + 5 + 300 + 5 = 370 s, still below the host's 380, and the test now sums each
> limit plus its grace.
> - `timeout` falls back to `gtimeout` (Homebrew coreutils on macOS). With neither on `PATH` the
> hook emits a notice and exits without calling `apm`. Before, the missing binary exited 127, the
> `|| exit 0` swallowed it, and the hook silently never ran.
> - `GIT_TERMINAL_PROMPT=0` is exported, so a remote that wants credentials fails at once instead of
> blocking on an invisible prompt until the timeout fires.
> - `apm update` runs under `flock -n` on `apm_modules/.kyberforge-apm-update.lock` when `flock` exists
> and `apm_modules/` does. A second session starting at the same moment skips its refresh and says
> so, instead of running a second `apm update` over the same tree. Without `flock` (stock macOS) the
> refresh runs unserialised, as before.
**Reading a human-readable CLI for a control decision cost a silent failure, again.** `apm outdated`
has no `--json` or other machine-readable flag (confirmed against 0.28.0), so the hook must match
its prose. The first attempt matched `outdated dependencies found` — plural only. apm emits
@@ -204,9 +223,20 @@ Until then the repo has the mechanism in source and not in effect.
> and it does not last. At the next session start the hook finds the restored lock behind `main`
> and refreshes again.
> **Amendment (2026-09-19) — the advice is neutral when the default branch is unknown.** The hook
> picks its lock advice by comparing the current branch with `refs/remotes/origin/HEAD`. That ref is
> often unset: git writes it on clone, and `git remote add` never does. The fallback used to be
> `main`, which told a checkout whose default branch is `master` to discard a real lock update while
> it stood on its default branch. Now, when `origin/HEAD` is unset, the notice gives neither the
> default-branch nor the feature-branch advice. It says only "commit it or discard it
> deliberately", the same text used outside a git checkout or on a detached HEAD. Learning the
> remote's default would need the network, so the hook asserts nothing and the reader decides
> (`ea119d8`).
**`.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
`.claude/hooks/<pkg>/`. The sidecar and the script directory are gitignored install output; the
`.claude/hooks/<pkg>/`. The sidecar and the script directory are gitignored install output
(*superseded for the sidecar: it is committed, see correction 2026-09-16 below*); the
settings file remains committed, now with apm-generated content in it. ADR-0018's statement that the
committed content is exactly `{"hooks": {}}` is superseded on that point only — the rule it was
protecting, that nothing repo-authored goes in that file, is unchanged.
@@ -223,7 +253,9 @@ protecting, that nothing repo-authored goes in that file, is unchanged.
**Native consumers are protected by a guard, not by the gate.** A host installing holocron through
`claude plugin install` auto-discovers `hooks/hooks.json` and does not consult apm's trust gate at
all. The script therefore exits silently when there is no `apm.lock.yaml` in the working directory,
all. The script therefore exits silently when there is no `apm.lock.yaml` in the working directory
(*superseded: it now checks `${CLAUDE_PROJECT_DIR}/apm.lock.yaml` and has no cwd fallback, see
correction 2026-09-28 below*),
which is what makes it inert in a repo that does not consume packages through apm. Copilot CLI sees
no hook at all, for the reasons already documented in `plugins/kyberforge/docs/hooks.md`.