docs: record the apm-only decision and its carried-forward consequences

Marks §8's install-path question answered and finding 7 superseded-then-done
(deleted rather than shrunk, since the mirror it guarded is gone). Corrects
two stale figures: the mirror was 213 files / 20,061 lines, not 263 / ~22,000,
and the pre-push stage now reports 11 hooks, not 14.

Adds §9 for what the decision carries forward rather than resolves: the two
accepted residuals, the self-containment negative result (findings 14/15 still
need skill merges, not file sharing), and the now-unreported symlink drop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD
This commit is contained in:
2026-09-14 17:02:35 +00:00
parent 718c79af70
commit d2480b8191

View File

@@ -6,6 +6,8 @@ Assumptions agreed before analysis: anything is on the table, Claude Code and Co
Counting convention: line counts are hand-edited `.apm/` source unless marked "incl. mirror". Every `.apm/` file has a byte-identical generated copy at the plugin root, so plugin cuts count double in the repo total.
> **Superseded (2026-09-14):** the mirror is gone (commit `718c79a`, ADR-0024). "incl. mirror" totals below are historical; roughly 70% of each remains live (kyberforge 44,568 → ~33,000).
## 1. The shape of the problem
| Measure | Value |
@@ -13,7 +15,7 @@ Counting convention: line counts are hand-edited `.apm/` source unless marked "i
| Tracked files / lines | 820 / 102,000 |
| Lines in `plugins/` | 70,600 (69% of repo) |
| Of which the 39 `SKILL.md` files a model actually loads | ~2,600 lines (under 4% of plugin lines) |
| Generated flat mirror files (byte copies of `.apm/`) | 263 files, ~22,000 lines |
| Generated flat mirror files (byte copies of `.apm/`) | ~~263 files, ~22,000 lines~~ → 0 (deleted 2026-09-14, see below) |
| `docs/research/` vendored inside plugins | ~19,000 lines, nothing executable reads it |
| Repo-level `docs/research/` + `docs/notes/` | 4,500 lines, 47% of all prose words, 6 of 11 research files linked only from each other |
| Enforcement: hook entries in `.pre-commit-config.yaml` / pre-push hooks | 33 / 14 |
@@ -22,19 +24,21 @@ Counting convention: line counts are hand-edited `.apm/` source unless marked "i
| Preload tax (39 skill names + descriptions) | 10,987 chars, ~2,750 tokens per session |
| Commits since 2026-05-10 / share touching hook, test, gate, vale, or sync | 447 / ~25% |
> **Corrected then done (2026-09-14):** the mirror row's figure was wrong. The true mirror was **213 files / 20,061 lines**, not 263 / ~22,000 — the original count swept in files that were never mirror output. All 213 were deleted in commit `718c79a` on `docs/simplification-audit` (245 files changed, 298 insertions, 22,602 deletions across the whole change), so the row is now zero. The enforcement row's pre-push figure of 14 is also stale: with `validate-plugins` and `check-plugin-content-sync` gone the stage now reports 11 (9 repo-authored plus 2 pre-commit `meta` hooks); see the corrected §3 target.
The pattern across every area is the same: the payload (skill bodies, rules, decisions) is small and the scaffolding around it (mirrors, research dumps, sync gates, tests of tests, justification prose) is 10 to 30 times larger. A quarter of all commits have gone into maintaining the scaffolding.
## 2. Measured baseline: hooks and tests
Measured on this machine, clean tree, all hooks passing. `pre-commit run --all-files` per stage.
Measured on this machine, clean tree, all hooks passing. `pre-commit run --all-files` per stage. Figures below are the 2026-09-10 measurement; rows struck through were deleted on 2026-09-14 (commit `718c79a`) and their times no longer apply.
| Gate | Wall time |
|---|---|
| **Full pre-push stage (everything below, sequential)** | **~5 min 10 s** |
| `run-tests` (26 bash suites + 351 bats tests) | 276 s |
| `apm-audit-ci` (7 manifests) | 12.2 s |
| `validate-plugins` (6 × `claude plugin validate`) | 4.9 s |
| `check-plugin-content-sync` | 4.5 s |
| ~~`validate-plugins` (6 × `claude plugin validate`)~~ deleted | ~~4.9 s~~ |
| ~~`check-plugin-content-sync`~~ deleted | ~~4.5 s~~ |
| `apm-pack-check-clean` | 3.1 s |
| Other 9 pre-push hooks combined | 7.6 s |
| **Full pre-commit stage, all files** | **18.2 s** |
@@ -45,7 +49,7 @@ Where the 276 s goes (each suite run alone, sequential):
| Suite | Time | Note |
|---|---|---|
| `test-sync-plugin-content.sh` | 83 s | 14 temp trees, 2 `git init`, repeated `apm pack` |
| ~~`test-sync-plugin-content.sh`~~ deleted | ~~83 s~~ | 14 temp trees, 2 `git init`, repeated `apm pack` |
| all 351 bats tests (10 files, kyberforge and core validators) | 64 s | mostly `validate.sh` / `validate-provenance.sh` fixtures |
| `test-adr0020-differential.sh` | 29 s | 12 assertions; re-runs two validators over the live corpus and a fixture tree |
| `test-check-vale-style-sync.sh` | 25 s | guards a byte-identical copy |
@@ -55,6 +59,8 @@ Where the 276 s goes (each suite run alone, sequential):
Five suites account for 215 s of 276 s. Three of those five (sync-plugin-content, vale-style-sync, adr0020-differential) test tooling that findings 2, 7, and 14 propose to delete or shrink, so the fastest path to a quick pre-push is removing the duplication those tests guard rather than optimising the tests.
> **Done (2026-09-14):** see commit `718c79a` on `docs/simplification-audit`. The three struck-through rows are gone: `test-sync-plugin-content.sh` (83 s, 1,289 lines, 92 cases), `check-plugin-content-sync` (4.5 s) and `validate-plugins` (4.9 s). Expected, not re-measured: roughly 88 s comes off every push (~83 s of it out of `run-tests`, which loses its single slowest suite), on the arithmetic of the 2026-09-10 figures alone. The remaining rows have not been re-timed since, so treat every number in this section as the 2026-09-10 baseline minus those three, not as a fresh measurement.
## 3. Enforcement layer: hooks, tests, scripts
This is the area you named as hardest to understand and slowest. Root cause: most pre-push hooks exist to keep two copies of something in sync, or to re-validate what another hook already validates.
@@ -84,12 +90,17 @@ This is the area you named as hardest to understand and slowest. Root cause: mos
6. [x] ~~**Prose-grep tests.** `test-governance-layer.sh` and `test-instructions-and-docs.sh` (583 lines) grep markdown for phrases, including a one-shot "issue 0015 refactor incomplete" assertion made permanent and an assertion that `docs/notes/` exists. Delete both.~~ `check-apm-agents-valid.sh` (161 + 264 test lines) is a loop plus fail-closed guards around `validate.sh`; it folds into the merged audit skill's own tests (finding 14). Effort S.
> **Done (2026-09-12):** see commit `5f9f2b3` on `docs/simplification-audit`. Deleted `tests/test-governance-layer.sh` (270 lines) and `tests/test-instructions-and-docs.sh` (313 lines); no other file referenced either. `check-apm-agents-valid.sh` was left untouched — its fate is tied to the separate, out-of-scope skill-merge finding 14.
7. **`check-plugin-content-sync.sh` is 813 lines wrapping `apm pack`, with a 1,291-line test.** The mirror itself must stay (Claude Code marketplace installs need flat directories), and the script does real work a bare `git diff` would lose: it strips `tests/` from the mirror, regenerates both `plugin.json` files with `mcpServers` reinjected, and packs into a scratch copy so `--check` never mutates. Even so, 2,100 lines for that is disproportionate; target a third. Effort M.
7. [x] ~~**`check-plugin-content-sync.sh` is 813 lines wrapping `apm pack`, with a 1,291-line test.** The mirror itself must stay (Claude Code marketplace installs need flat directories), and the script does real work a bare `git diff` would lose: it strips `tests/` from the mirror, regenerates both `plugin.json` files with `mcpServers` reinjected, and packs into a scratch copy so `--check` never mutates. Even so, 2,100 lines for that is disproportionate; target a third. Effort M.~~
> **Superseded then done (2026-09-14):** see commit `718c79a` on `docs/simplification-audit`. The recommendation ("target a third") is void, not met — the §8 question it depended on was settled the other way. Answering "apm-only" (ADR-0024) removed the mirror's reason to exist, and with the mirror gone the script guarded nothing, so the whole thing was deleted rather than shrunk: `scripts/sync-plugin-content.sh` (813 lines), `tests/test-sync-plugin-content.sh` (1,289 lines — the finding said 1,291), the `check-plugin-content-sync` pre-push hook, and `scripts/lib/marketplace-plugins.sh` (86 lines, whose only consumer was the sync script, and which finding 1 had explicitly kept alive for it). `validate-plugins` went with them, and the twelve per-plugin `plugin.json` manifests the script regenerated. The finding's own premise — "the mirror itself must stay" — is what turned out to be wrong.
8. **`docs/spec/gates.md` (1,048 lines) is roughly 15% "what is enforced" and 85% post-mortems** of defects already fixed and pinned by tests. The 60-line hook table is the useful part. Target 200 lines. The same applies to the 106 comment lines in `.pre-commit-config.yaml` and to `scripts/`, where 8 of 15 files are 40 to 60% comments. Effort M.
> **Partially done (2026-09-13):** see commit `a35f5e8` on `docs/simplification-audit`. The 85%-post-mortem characterization was stale — the file had already shrunk to 966 lines by other findings, and most of what remained is load-bearing "why this design" rationale cited by ADRs and tests, not dead incident narration. Cut only the two genuinely stale passages: a reproduction paragraph carrying explicitly outdated numbers, and a retrofit-process narrative superseded by current state — 966 → 930 lines. `.pre-commit-config.yaml`'s comments were left untouched; on inspection they're compact constraint notes, not filler. Target of 200 lines not reached and not recommended — would require deleting content the file itself flags as load-bearing.
**Proposed target.** Pre-push 14 hooks to 6: `run-tests`, `validate-plugins`, `validate-marketplace`, `apm-pack-check-clean`, `check-plugin-content-sync`, `check-release-needed`. Pre-commit stays roughly as is minus `skill-frontmatter`, and minus `check-ast` once finding 9 removes the only `.py` files. Tests 26 files to about 10 (12,400 to about 5,000 lines). Keep bats and its three submodules; the 351 bats tests ship inside plugins and are the right tool there. Do not port the bash suites to bats; delete them instead.
**Proposed target.** ~~Pre-push 14 hooks to 6: `run-tests`, `validate-plugins`, `validate-marketplace`, `apm-pack-check-clean`, `check-plugin-content-sync`, `check-release-needed`.~~
> **Corrected (2026-09-14):** two of the six named targets no longer exist — `validate-plugins` and `check-plugin-content-sync` were deleted in commit `718c79a` (finding 7, ADR-0024). Actual state today: **9 repo-authored pre-push hooks** — `run-tests`, `check-executables-allow-sync`, `apm-audit-ci`, `check-apm-agents-valid`, `apm-pack-check-clean`, `check-vale-style-sync`, `check-scope-walkup-sync`, `check-release-needed`, `validate-marketplace` — plus the 2 pre-commit `meta` hooks that also run at this stage, so 11 are reported at pre-push. `validate-marketplace` was kept: the root `marketplace:` block in `apm.yml` and the root `.claude-plugin/marketplace.json` + `plugin.json` stay, because apm's own marketplace consumers read that same catalogue and `<name>@holocron` short names depend on it.
Pre-commit stays roughly as is minus `skill-frontmatter`, and minus `check-ast` once finding 9 removes the only `.py` files. Tests 26 files to about 10 (12,400 to about 5,000 lines). Keep bats and its three submodules; the 351 bats tests ship inside plugins and are the right tool there. Do not port the bash suites to bats; delete them instead.
## 4. Plugins
@@ -168,6 +179,7 @@ The shared pattern: per-skill `README.md` files no model reads, a `docs/research
Not covered by the area audits above; found on a final sweep of the root config and install pipeline. The install pipeline itself (`scripts/install.sh` 55 lines, `deploy-manifest.sh` 24, statusline 109) is fine and needs nothing.
33. **Every plugin version lives in four places (five for kyberforge), plus one per skill.** `plugins/<name>/apm.yml`, two generated `plugin.json` files, the root `apm.yml` packages list, the `executables.allow` key (`kyberforge#1.6.2`), and a `metadata.version` in all 39 SKILL.md files (ADR-0022) that nothing consumes and that drifts freely (gitea skills sit at five different values). Repo tags (`v2.0.1`) follow a third scheme that the declared `tagPattern: v{version}` can never match under `per_package` versioning. ADR-0006, ADR-0022, `check-executables-allow-sync`, `skill-frontmatter`, and `apm pack --check-versions` all exist to police this. Proposal: one version per plugin in its `apm.yml`; drop `metadata.version` and ADR-0022; let `apm pack` derive the rest. Effort M.
> **Partially advanced (2026-09-14):** see commit `718c79a` on `docs/simplification-audit`. Two of the four locations per plugin are gone: the twelve generated `plugin.json` manifests (`plugins/*/.claude-plugin/` and `plugins/*/.github/plugin/`) were deleted with the mirror. ADR-0006 needed no action — it was already moot and governed only those two now-deleted manifests, so no version bumps were required by the change. **Not closed.** Still outstanding: `plugins/<name>/apm.yml`, the root `apm.yml` packages list, the `executables.allow` pin, and `metadata.version` in all 39 SKILL.md files (still unconsumed, still drifting), plus ADR-0022 and the `v{version}` `tagPattern` mismatch.
34. **The SessionStart hook auto-updates the install on every startup.** `check-apm-current.sh` runs `apm outdated` (network, 60 s timeout) and then `apm update --yes` (300 s timeout) at every session start, rewriting `apm.lock.yaml`. That is why the lock file is dirty at the start of this session and why `AGENTS.md` has to explain "commit or discard it deliberately". It is a 60-line script with a 368-line test, an ADR (0019), the `executables.allow` pin, and a sync hook behind it. For a repo that is its own source, the update belongs in `install.sh` or a manual `apm update`, not in session startup. Effort S to remove; the design question is whether auto-update at startup is wanted at all.
@@ -192,7 +204,8 @@ Findings 9, 10, 11, and 12 are coupled through the provenance validator and the
## 8. Questions to settle before starting
- **Native Claude Code marketplace install vs apm-only.** The flat mirror, `check-plugin-content-sync`, and ADR-0017 exist only for native `claude plugin install`. If apm install is the only supported path, the mirror and its 2,100 lines of tooling go away. Which install paths must work for consumers?
- [x] ~~**Native Claude Code marketplace install vs apm-only.** The flat mirror, `check-plugin-content-sync`, and ADR-0017 exist only for native `claude plugin install`. If apm install is the only supported path, the mirror and its 2,100 lines of tooling go away. Which install paths must work for consumers?~~
> **Answered (2026-09-14):** apm-only. See ADR-0024 (`docs/adr/0024-apm-is-the-only-supported-install-path.md`) and commit `718c79a` on `docs/simplification-audit`. Native `claude plugin install` support is dropped; the flat mirror, the twelve per-plugin manifests, `sync-plugin-content.sh`, its test suite, `lib/marketplace-plugins.sh`, and the `check-plugin-content-sync` and `validate-plugins` hooks are all deleted (245 files changed, −22,602 lines). ADR-0017 carries a superseded banner. Kept deliberately: the root `marketplace:` block and root `.claude-plugin/` manifests, which apm's own consumers read. This answer is what voided finding 7's recommendation and closed §3's `check-plugin-content-sync` target.
- **Copilot CLI legacy path.** Is `.github/plugin/marketplace.json` still read by any Copilot version you target? If not, finding 2c is a pure delete.
- **Provenance chain.** Is "which upstream informed this file" a requirement you still want, or was it a governance experiment? Finding 11 hinges on this.
- **ADR-0012 (three core skills) and the one-script-per-skill install constraint.** The merges in 14, 15, and 24 need the first revisited and are the only way around the second. Are you open to superseding ADR-0012?
@@ -200,3 +213,16 @@ Findings 9, 10, 11, and 12 are coupled through the provenance validator and the
- **Auto-update at session start.** Do you want the install refreshed from the remote every time a session opens (finding 34), or is a manual `apm update` acceptable?
- **External hook consumers.** Does any other repo pin this repo's `.pre-commit-hooks.yaml` by tag today? If not, finding 36 defers the release mechanism entirely.
- **Obsidian MCP.** Are the Obsidian tools over `docs/` used by anyone? If not, finding 37 is a pure delete.
## 9. Carried forward from the apm-only decision (2026-09-14)
Recorded here so they are not rediscovered as defects. All follow from commit `718c79a` / ADR-0024.
**Two accepted residuals.**
- **Native install still half-works, and cannot be prevented.** apm reuses Claude's catalogue format by design, so a Claude Code user can still register holocron natively and will install six plugins containing zero skills. Accepted, not overlooked: no schema change closes this, because the format that makes it possible is the format apm's own consumers need.
- **Consumers now receive test fixtures.** apm installs from `.apm/`, which carries the `tests/` directories the mirror used to strip, so 17 `.bats` fixture files across 6 skills now ship. Suppressing them means switching all six `apm.yml` files from `includes: auto` to explicit lists, where a wrong list silently drops content — worse failure mode than the noise. Deferred deliberately.
**Negative result — do not re-litigate.** Deleting native install does *not* relax the self-containment constraint. `plugins/kyberforge/.apm/skills/skill-author/references/deployment-modes.md`, sourced from the agentskills.io spec, states it independently for APM package mode: the spec defines no cross-skill sharing. So findings 14 and 15 still require *merging* skills; sharing one file between two skills remains impossible, and §8's "one-script-per-skill install constraint" bullet is unchanged by this decision.
**Latent regression.** ADR-0017's `check_apm_symlinks()` was the only thing reporting that apm's bundle exporter silently drops symlinks under `.apm/`. It is gone. No symlinks exist under any `.apm/` today, so this is latent rather than live — but the next symlink added there will be dropped from the install with no error.