fix(pre-commit): keep key order in pretty-format-json so apm-owned JSON survives

pretty-format-json sorts object keys by default, but apm emits insertion
order and `apm audit --ci` diffs its output byte-for-byte. A file in the
formatter's scope therefore drifts on every commit with an empty git diff.

Pass --no-sort-keys so `.claude/settings.json` and `.claude/apm-hooks.json`
round-trip untouched and drop them from the exclude. marketplace.json stays
excluded: it carries literal em dashes the formatter re-escapes to —.

tests/test-pretty-json-tool-owned.sh runs the repo's real autofix hooks over
copies of the four tracked apm-owned files and fails if any is rewritten, so
losing the flag fails a test instead of surfacing as drift.

Refs: #102

Co-Authored-By: Claude Code <[email protected]>
Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
Defame1297andClaude Code committed 2026-09-30 16:02:21 +00:00
1 parent 3ff0741857
commit c2c56ff948
4 files changed
+156 -50

No files matched your search

+18 -34
View File
@@ -27,41 +27,25 @@ repos:
stages: ['pre-commit']
- id: pretty-format-json
stages: ['pre-commit']
args: [--autofix]
# Every generated manifest lives at a KNOWN path, so every alternative is
# root-anchored and spells that path out. This was five `(^|/)`
# any-depth alternatives plus one `^` root-only one -- a mixture with no
# rationale, under which a fixture or vendored tree containing
# `.../.claude-plugin/marketplace.json` would have been silently excluded
# from formatting while an equivalent
# `.../.agents/plugins/marketplace.json` would not. Only the one root
# marketplace manifest matches now; anything else is hand-authored and
# gets formatted. The twelve per-plugin `plugin.json` alternatives were
# dropped with the plugin manifests themselves when native
# `claude plugin install` support was removed (ADR-0024) -- apm probes
# `apm.yml` and never reached them. The `.agents/plugins/` and
# `.github/plugin/` marketplace mirrors went the same way, and their
# alternations went with them: `check-useless-excludes` fails on a
# pattern that matches no file.
args: [--autofix, --no-sort-keys]
# `--no-sort-keys` is load-bearing. apm OWNS `.claude/settings.json` and its
# `.claude/apm-hooks.json` sidecar (ADR-0018, ADR-0019), and
# `apm audit --ci` replays the install and diffs the result byte-for-byte.
# apm emits insertion order (`matcher` before `hooks`); the formatter's
# default sorts keys, rewrites that into a form apm would never produce,
# and the `apm-audit-ci` pre-push hook then reports drift on a file with
# no git diff (#102, first hit at 2e395a4). Keeping insertion order means
# those two files need no exclude:
# tests/test-pretty-json-tool-owned.sh pins that they round-trip
# untouched.
#
# `.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
# unless `--no-sort-keys` is passed, while apm's hook integrator emits
# insertion order (`matcher` before `hooks`, `type` before `command`).
# Formatting the file therefore rewrites apm's output into a form apm
# would never produce, and the `apm-audit-ci` pre-push hook reports it
# 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. 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)$'
# `.claude-plugin/marketplace.json` is the one remaining exclude. It
# round-trips except for non-ASCII: it carries literal em dashes and the
# formatter re-escapes them to `\u2014` (`--no-ensure-ascii` would fix that,
# but it changes the output for every JSON file). Root-anchored because it
# is one known path; `check-useless-excludes` fails on a pattern that
# matches no file.
exclude: '^\.claude-plugin/marketplace\.json$'
- id: check-yaml
stages: ['pre-commit']
- id: trailing-whitespace