fix(apm): commit the hook ownership sidecar so fresh installs stay idempotent
apm recognises its own settings.json hook entries only through the .claude/apm-hooks.json sidecar. With the sidecar gitignored, apm install in a fresh clone keeps the committed SessionStart entry as user-owned and appends a duplicate, so apm audit --ci reports drift and the apm-audit-ci pre-push hook fails. Reproduced on main and this branch with apm 0.28.0. Commit the sidecar in apm's exact serialisation, exclude it from pretty-format-json alongside settings.json, and record the correction in ADR-0019. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
15
.claude/apm-hooks.json
Normal file
15
.claude/apm-hooks.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/kyberforge/.apm/hooks/check-apm-current.sh\"",
|
||||
"timeout": 380
|
||||
}
|
||||
],
|
||||
"_apm_source": "Defame1297/holocron/plugins/kyberforge"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -40,11 +40,11 @@ apm_modules/
|
||||
/.mcp.json
|
||||
|
||||
# APM hook deployment output — `apm install` copies each package's referenced
|
||||
# hook scripts here and tracks its own settings.json entries in the sidecar.
|
||||
# Regenerated on every install; the authoring source is
|
||||
# plugins/<name>/.apm/hooks/ (ADR-0019).
|
||||
# hook scripts here. Regenerated on every install; the authoring source is
|
||||
# plugins/<name>/.apm/hooks/ (ADR-0019). The .claude/apm-hooks.json ownership
|
||||
# sidecar is committed, not ignored: without it a fresh clone's install cannot
|
||||
# claim the committed settings.json entry and duplicates it (ADR-0019).
|
||||
.claude/hooks/
|
||||
.claude/apm-hooks.json
|
||||
|
||||
# `apm pack` bundle output. The pre-push gate runs pack with --dry-run, so this
|
||||
# only appears after a bare `apm pack` during a release; it is not repo content.
|
||||
|
||||
@@ -44,8 +44,9 @@ repos:
|
||||
# alternations went with them: `check-useless-excludes` fails on a
|
||||
# pattern that matches no file.
|
||||
#
|
||||
# `.claude/settings.json` is the second and last alternation, and it is
|
||||
# the only one here for a reason other than "generated manifest":
|
||||
# `.claude/settings.json` and its `.claude/apm-hooks.json` ownership
|
||||
# sidecar are the last two alternations, and they are the only ones
|
||||
# here for a reason other than "generated manifest":
|
||||
# apm OWNS that file (ADR-0018, ADR-0019), and
|
||||
# `apm audit --ci` replays the install into a scratch tree and diffs
|
||||
# the result byte-for-byte. `pretty-format-json` sorts object keys
|
||||
@@ -56,8 +57,11 @@ repos:
|
||||
# as permanent drift on a file with no git diff -- exactly what
|
||||
# happened when the SessionStart hook first landed in 2e395a4.
|
||||
# Re-running `apm install` fixes the file; leaving it in scope here
|
||||
# would re-break it on the very commit that carries the fix.
|
||||
exclude: '^(\.claude-plugin/marketplace\.json|\.claude/settings\.json)$'
|
||||
# would re-break it on the very commit that carries the fix. The
|
||||
# sidecar is committed so a fresh clone's install can claim the
|
||||
# settings entry instead of duplicating it (ADR-0019, 2026-09-16
|
||||
# correction), and it is apm output under the same byte-for-byte replay.
|
||||
exclude: '^(\.claude-plugin/marketplace\.json|\.claude/(settings|apm-hooks)\.json)$'
|
||||
- id: check-yaml
|
||||
stages: ['pre-commit']
|
||||
- id: trailing-whitespace
|
||||
|
||||
@@ -187,6 +187,16 @@ settings file remains committed, now with apm-generated content in it. ADR-0018'
|
||||
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.
|
||||
|
||||
> **Correction (2026-09-16) — the sidecar is committed, not ignored.** apm keeps no ownership marker
|
||||
> inside `settings.json`; it recognises its own entries by matching them against
|
||||
> `.claude/apm-hooks.json`, then replaces them. With the sidecar gitignored, a fresh clone holds the
|
||||
> committed `SessionStart` entry but no sidecar, so `apm install` treats the entry as user-owned,
|
||||
> keeps it, and adds its own identical copy. `apm audit --ci` then reports `settings.json` drift and
|
||||
> the `apm-audit-ci` pre-push hook fails. Reproduced on `main` (`a712f2c`) and on this branch with
|
||||
> apm 0.28.0; committing the sidecar makes the install idempotent and the audit pass. The sidecar
|
||||
> is apm output like the settings entry it describes, so it is committed for the same reason and
|
||||
> changes only when the owning package is renamed or moved. `.claude/hooks/` stays ignored.
|
||||
|
||||
**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,
|
||||
|
||||
Reference in New Issue
Block a user