Files
holocron/SIMPLIFICATION-AUDIT.md
Defame1297 062ca47a18 docs: correct claims left stale by today's apm-only commits
A five-agent review of today's seven commits found no executable
regressions and no dangling references, but a set of documents still
asserting, in present tense, machinery that ADR-0024 and its commits
removed. This corrects them in place, keeping the original text as the
historical record wherever the repo's amendment convention applies.

LESSONS.md: the 2026-06-21 entry prescribed a `claude plugin validate`
sweep that now fails on every plugin, so it is marked superseded with
the surviving gates named. The 2026-08-09 entry gained a recurrence
note: today's manifest deletion broke apm's MCP propagation exactly as
that lesson describes, and its prescribed repo-local grep could not
have caught it, because `plugin_parser.py` ships in the apm toolchain
installed outside this repository.

ADR-0019, ADR-0011 and ADR-0021: amendments extended to passages the
earlier correction passes stepped over -- a dead native-consumer guard,
Consequences bullets still calling for a `plugins/gitea/.mcp.json` that
must not be recreated, and a drift-gate list naming a deleted script.
ADR-0021's list is down to one gate, not two: `apm audit --ci` never
read `description` and was never a drift gate.

architecture.md and enrichments.md: the self-containment constraint is
restated on its live source, the agentskills.io APM package-mode spec,
rather than on Claude Code's plugin cache-install, which ADR-0024
consequence 6 pins as a superseded rationale. releasing.md's pointer to
the deleted sync script is rewritten as history.

tests/run-bats.sh and scripts/lib/batch-run.sh: comment-only. The
`.claude/skills/` exclusion comment claimed a duplication that is not
live yet; apm does not strip `tests/`, and the deployed tree is empty
of them only because the lockfile still resolves the six dependencies
to a pre-ADR-0024 commit carrying the flat mirror. The exclusion is
correct but forward-looking, and now says so.

SIMPLIFICATION-AUDIT.md: reconciled against what the commits actually
did. Two closed findings recorded conclusions that ADR-0024 reversed
hours later; findings 1, 3, 31 and 35 carried prescriptions voided the
same day; finding 28 is now recorded as having moved backwards, with
docs/adr/ measured at +336 lines over the day. The section 1 headline
table is re-measured at a6434e0 and labelled with its basis. The
ADR-0012 contradiction between finding 2b and section 8 is resolved in
2b's favour after reading the ADR: only finding 24 is governed by it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD
2026-09-14 19:50:23 +00:00

69 KiB
Raw Blame History

Simplification audit

Date: 2026-09-10. Read-only analysis; nothing has been changed. Purpose: a hand-off for deciding what to remove, merge, and shrink. Findings are ranked by payoff within each area; effort is S/M/L. Claims were independently re-verified against the repo by a clean reviewer; corrections have been applied.

Assumptions agreed before analysis: anything is on the table, Claude Code and Copilot CLI both stay supported, findings are ranked with effort.

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. Measured against the 2026-09-10 baseline (9eb8bc7), what remains live varies by plugin — 44% to 71%, not a uniform ~70%:

Plugin Baseline (incl. mirror) At a6434e0 Live
kyberforge 44,568 31,432 70.5%
git 9,889 6,045 61.1%
gitea 6,047 3,471 57.4%
core 3,873 2,355 60.8%
lint 1,558 923 59.2%
bin 4,704 2,075 44.1%

Across all six the baseline was 70,639 lines and 46,301 remain (65.5%). The mirror was 20,061 of those lines, so mirror deletion alone would have left ~71.6%; everything below that line is source the other findings cut, which is why bin — where findings 10, 12, and 13 landed hardest — is the outlier. (Counted as tracked lines under plugins/<name>/ at 9eb8bc7 and at a6434e0.)

Re-measured (2026-09-14, at a6434e0): the right-hand column originally read 31,473 / 6,050 / 3,471 / 2,360 / 923 / 2,083 = 46,360 and was labelled "Today" against "the current working tree". It did not reconcile to its own commit's tree — at 061bb3d, where it was written, the six plugins measured 31,435 / 6,048 / 3,474 / 2,358 / 926 / 2,087 = 46,328 — and "the current working tree" is a basis that goes stale silently. Re-counted at a6434e0 and the column now names its SHA. The baseline column is confirmed exact against 9eb8bc7. Commits after 061bb3d (c96ca9c, which deleted the six plugin-root .mcp.json files) account for most of the remaining drift.

Reviewed (2026-09-14): commits 718c79a and d2480b8 were put through a five-agent review. Result: zero skill, agent or hook regressions — 39 skills before and after, all gates passing, and both hook removals (validate-plugins, check-plugin-content-sync) genuinely moot rather than merely unenforced. One real functional regression was found — MCP propagation to consumers, broken by the same commit's manifest deletion; see finding 37 — along with the numeric and bookkeeping drift in this document's own 2026-09-14 notes, corrected in place above and below.

1. The shape of the problem

Measure Value
Tracked files / lines 820 / 102,000 → 475 / 73,073
Lines in plugins/ 70,600 (69% of repo) → 46,301 (63% of repo)
Of which the 39 SKILL.md files a model actually loads about 2,600 lines (under 4% of plugin lines) → 2,509 lines (5.4% of plugin 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
Enforcement: tests/*.sh + runners + scripts/ 12,400 + 475 + 4,500 lines → 9,123 + 490 + 3,308 lines
Validator scripts inside kyberforge (+ their bats tests) 6,800 + 5,300 lines
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 is stale on both halves — it was correct at the 2026-09-10 baseline (9eb8bc7: 33 - id: entries, 14 repo-authored pre-push hooks), but .pre-commit-config.yaml today has 27 entries and 9 stages: [pre-push]. Like for like that is 14 → 9 repo-authored pre-push hooks. The stage reports 11, because the 2 pre-commit meta hooks also run there — a different counting basis; see the corrected §3 target, which states it the same way.

Re-measured (2026-09-14, at a6434e0): this table is a dated snapshot corrected in place, not a live figure — every arrow above reads "baseline (2026-09-10, 9eb8bc7) → value at the stated commit". Three further rows were still carrying baseline values after d2480b8/061bb3d corrected their neighbours, and are now corrected at a6434e0:

  • Tracked files / lines: 475 / 73,073 (475 is git ls-files | wc -l: 471 regular files plus 4 submodule gitlinks — docs/wiki, tests/bats, tests/test_helper/bats-assert, tests/test_helper/bats-support, whose own contents are not counted).
  • Lines in plugins/: 46,301, which is 63% of 73,073, not 69%.
  • The 39 SKILL.md bodies: 2,509 lines. That is now 5.4% of plugin lines rather than "under 4%" — the share rose because the denominator shrank faster than the payload, which is the whole point of the audit.
  • Enforcement scripts and tests: 20 tests/test-*.sh totalling 9,123 lines, the two runners 490 (run-tests.sh 281 + run-bats.sh 209), and scripts/ 3,308.

Re-checked and still accurate at a6434e0, so left alone: docs/research/ inside plugins (19,030), repo-level docs/research/ + docs/notes/ (4,488), kyberforge validators + their bats tests (6,849 + 5,256). The hook-entry row is superseded by the paragraph above. Not re-measured: the preload-tax row (10,987 chars) and the commit-share row — the commit count alone has moved to 476 since 2026-05-10, and that row was always a moving figure.

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. 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) 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

run-tests is 90% of the wall time. Every push pays it in full: the runner has no change detection and the config sets always_run: true. apm-audit-ci is the second-slowest hook; per its own comment block its earlier description overclaimed, and what it verifies today is that seven manifests parse and the lockfile exists.

Where the 276 s goes (each suite run alone, sequential):

Suite Time Note
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
test-vale-wrap.sh 14 s
test-adr0020-frontmatter.sh + -targets.sh 25 s
Remaining 20 suites 36 s 12 of them run in under 2 s each

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 92 s comes off every push (83 + 4.5 + 4.9 = 92.4 s) (~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.

  1. Six hooks validate overlapping sets of the same manifests. check-manifests, validate-plugins, validate-marketplace, apm-pack-check-clean, apm-marketplace-check, apm-audit-ci. Keep the two claude plugin validate hooks plus apm-pack-check-clean. Delete check-manifests (282 lines + 771 test lines; its lib/marketplace-plugins.sh stays because sync-plugin-content.sh sources it). apm-audit-ci spends 12 s confirming that manifests apm pack already parses do parse; drop or keep on that basis. Move the network-dependent apm-marketplace-check to a release checklist. Effort S.

    Done (2026-09-12): see commit e647f14 on docs/simplification-audit. Deleted the check-manifests pre-commit hook entry, scripts/check-manifests.sh (282 lines), and tests/test-check-manifests.sh (771 lines); kept scripts/lib/marketplace-plugins.sh, still sourced by sync-plugin-content.sh. Updated the now-stale check-manifests.sh mentions in README.md and docs/spec/gates.md (hook table row and hook counts). The apm-audit-ci and apm-marketplace-check decisions in this finding remain open — out of scope for this change. Grilled and closed (2026-09-14): apm-audit-ci — already resolved before this audit was written: .pre-commit-config.yaml's own comment block (added in commit a155af6, months before this audit) already rebuts the "overclaimed description" complaint and gives a dated, verified justification for what the hook still checks. Keep, no action. apm-marketplace-check — its stated purpose ("the only hook that checks remote package references rather than local-source paths") is void: finding 35 (commit 568ca74) already removed the only remote package entry, so every marketplace.packages[] entry is now a local ./plugins/<name> path and the hook is pure overlap with apm-pack-check-clean. Removed the hook entry, and corrected the now-stale "does NOT join apm-marketplace-check ... on the offline SKIP= list" comment on apm-audit-ci (there is no offline skip list any more — every pre-push hook already passes offline per README.md). Updated README.md (tool table, "Offline?" section) and docs/spec/gates.md (hook table, hook counts 13→11 self-authored / 15→13 total, the "Three of these shell out to apm" paragraph, and the "Pushing without a network" section) accordingly. Verified: apm audit --ci still passes per-plugin, and the pre-push hook count now matches .pre-commit-config.yaml. Corrected and closed (2026-09-14, at a6434e0): two things above went stale within hours of being written, and the finding was never given a marker.

    • "Keep the two claude plugin validate hooks" is void. 718c79a (ADR-0024) deleted validate-plugins — the ADR's own reasoning is that claude plugin validate reads manifests only and could never detect the empty-content defect it was credited with guarding, and with the per-plugin manifests gone it has nothing left to read. Only validate-marketplace survives, over the one manifest this repo still ships (.claude-plugin/marketplace.json). Of the six hooks this finding named, three now exist: validate-marketplace, apm-pack-check-clean, apm-audit-ci. Verified against .pre-commit-config.yaml: 27 - id: entries, 9 with stages: [pre-push], no validate-plugins entry.
    • The gates.md figures above ("13→11 self-authored / 15→13 total") were correct for 0dffff3 and are no longer current. 718c79a removed two more pre-push hooks after that commit, and docs/spec/gates.md:24 now reads 11 reported / 9 self-authored. Read the count from that file, not from this note.

    Marked [x]: all three of this finding's decisions are resolved — check-manifests deleted (e647f14), apm-audit-ci kept on the grill above, apm-marketplace-check removed (0dffff3).

  2. Four two surviving "keep two copies in sync" gates: 1,100 script lines + 1,600 test lines 778 script lines + 1,079 test lines. Each one is a symptom of duplication that could be removed instead of guarded:

    Re-measured (2026-09-14, at a6434e0): two of the four are gone — check-marketplace-mirror-sync deleted in 0dffff3 (2c below) and, though it was never in this finding's own count, check-plugin-content-sync in 718c79a. The two that survive are check-vale-style-sync (413 script + 797 test) and check-scope-walkup-sync (365 + 282); check-executables-allow-sync also survives, shrunk to 222 + 243 (2d below), and counts as the third if that gate is read as part of this group rather than as its own item. Two-gate total 778 + 1,079; three-gate total 1,000 + 1,322. The per-bullet script and test figures below are all still exact at this commit except check-executables-allow-sync's "474 lines", which 2d already corrects.

    • check-vale-style-sync: 413 lines + 798 test lines guarding a byte-identical 526-line vale-wrap.sh and style directory copied between skill-audit and agent-audit. About 350 of its lines run Vale glob probes against the hook file patterns. Disappears if the two audit skills merge (finding 14); the probes belong in test-vale-wrap.sh.
    • check-scope-walkup-sync: 365 lines cross-checking four independent ports of the same package-root walk-up. Disappears if the ports share one script or the skills merge.

      Grilled, held (2026-09-14): both of the above are gated on findings 14/15 (merging skill-audit+agent-audit and skill-author+agent-author), deliberately held for a separate session rather than decided here. Correction for that session: the audit's §8 grouping is wrong — these merges don't need ADR-0012 revisited (that ADR governs the unrelated core plugin's three agentsmd-* skills). The actual constraint is ADR-0014 (no-cross-skill file sharing on plugin cache-install), and merging sidesteps it rather than requiring it be reversed. The open question for that session is a design one — a shared skill's description carrying both skill- and agent-audit trigger phrases — not an ADR supersession. ADR-0012 revisit is needed only for finding 24.

    • check-marketplace-mirror-sync: guards .github/plugin/marketplace.json. The script header calls it Copilot's legacy convention path and says Copilot also accepts the Claude path; the vendored Copilot docs list it as primary. Verify against current Copilot CLI before deleting hook, script, test, and mirror file.

      Grilled and done (2026-09-14): verified against GitHub's current Copilot CLI plugin docs (not the vendored copy, which risked drift). Copilot CLI's marketplace discovery checks paths in order — marketplace.json, .plugin/marketplace.json, .github/plugin/marketplace.json, .claude-plugin/marketplace.json — falling through to whichever exists first. .claude-plugin/marketplace.json (apm's own claude output) already satisfies that chain's last step, so the dedicated .github/plugin/marketplace.json mirror bought Copilot users its preferred discovery path rather than a required one. Decided against reopening ADR-0018 (native install for both Claude Code and Copilot CLI stays supported) to justify this — the deletion holds either way, since Copilot's own fallback covers it. Deleted .github/plugin/marketplace.json, scripts/sync-marketplace-mirror.sh (81 lines), tests/test-sync-marketplace-mirror.sh (304 lines), and the check-marketplace-mirror-sync pre-push hook; removed the dangling references to the deleted script in scripts/sync-plugin-content.sh and tests/test-sync-plugin-content.sh (both had comments citing its reasoning by name), and updated docs/spec/architecture.md's description of the marketplace-manifest compile step. tests/test-sync-plugin-content.sh (92 cases) still passes in full.

      Correction (2026-09-14, later the same day): the parenthetical "Decided against reopening ADR-0018 (native install for both Claude Code and Copilot CLI stays supported) to justify this" was true when written and is now the opposite of the repo's decision. 718c79a landed ADR-0024 hours later and dropped native install support outright, for both hosts. The note is left standing as the record of what was decided at 0dffff3; read the parenthetical as historical. The deletion itself still holds, and holds more strongly — the file was removed on the grounds that Copilot's own fallback covers it, and ADR-0024 removed the content that fallback would have led to, so the mirror file would now be a discovery path to nothing. See the §8 Copilot bullet, corrected on the same point.

    • check-executables-allow-sync: 474 lines to assert one string equals kyberforge's version. A six-line grep, or drop it (the failure mode is visible and recoverable).

      Corrected then partially done (2026-09-13): see commit 1b01e25 on docs/simplification-audit. Independent re-verification found "drop it" unsafe — ADR-0019's own Consequences section calls this failure mode silent and says a silent-staleness failure here is worse than the duplication the other gates catch, directly contradicting the finding's "visible and recoverable" claim. The hook stays. Shrunk scripts/check-executables-allow-sync.sh 231 → 222 lines by deduplicating two comment blocks that re-derived ADR-0019's own reasoning inline, replacing them with a pointer at the ADR. The dual-reader design (PyYAML plus a hand-rolled fallback, so a missing PyYAML can't silently skip the check) was found to be load-bearing, not redundant, and left intact; test file unchanged (behavior unaffected). All 23 test cases and the live pre-push hook run still pass. Effort S each, M for the walk-up.

  3. Tests of the test harness: 1,090 lines testing 475 lines. test-run-tests.sh and test-run-bats.sh defend "green either way" holes that exist only because the runners hand-roll TAP parsing and set-equality checks. Replace both runners with about 40 lines (bats -r plugins plus a parallel find | xargs over test-*.sh) and delete the meta-tests. lib/batch-run.sh stays; sync-plugin-content.sh sources it both runners source it. Effort M.

    Not proceeding (2026-09-13): premise doesn't hold. A full read of both runners and both meta-tests found the "TAP-parsing/set-equality" logic is regression coverage for specific past incidents — a BATS_FILE_FLOOR hardcode once let deleted test files vanish silently ("155 tests, 0 failures" with 11 tests missing); a missing/broken run-bats.sh used to make the whole bats suite disappear with a green summary; a formatter change once reported "0 tests, 0 failures" as a pass. Replacing the runners as specified would delete exactly the guards against that failure class. No changes made. Re-scoping this would mean deciding, guard by guard, which are still worth keeping — a design decision, not a mechanical cleanup. Rationale corrected, decision unchanged (2026-09-14, at a6434e0): the stated reason lib/batch-run.sh survives was void — sync-plugin-content.sh was deleted in 718c79a. The conclusion is unaffected: batch-run.sh (90 lines) is sourced by tests/run-tests.sh:185 and tests/run-bats.sh:138, and copied into fixture trees by tests/test-run-tests.sh:51 and tests/test-run-bats.sh:49. Since the finding is not proceeding, both runners stay and keep sourcing it, so nothing is orphaned. Note the knock-on if this is ever re-scoped: with the sync script gone, "replace both runners" would leave batch-run.sh with no caller at all, which the original wording assumed it could not. Headline figures re-measured: the meta-tests are 1,090 lines (675 + 415, as stated) against 490 runner lines, not 475 — the runners grew from 273 + 202 at the 9eb8bc7 baseline. (Both runners and batch-run.sh were under concurrent edit when this was measured; figures are as of a6434e0.)

  4. skill-frontmatter is a 62-line bash script inlined in YAML with its own 366-line test. skill-size-check.sh already parses the same frontmatter with PyYAML. Fold it in (about 15 Python lines), delete the inline hook, its test, and the 79 lines in gates.md arguing for the split. Effort S.

    Done (2026-09-12): see commit c8a7c9e on docs/simplification-audit. Added a ~20-line required-frontmatter check (name, description, metadata.version as three-part semver) to scripts/skill-size-check.sh, reusing the YAML mapping description_value() already parses. Removed the inline skill-frontmatter hook (~80 lines) from .pre-commit-config.yaml and deleted tests/test-skill-frontmatter.sh (366 lines). Removed the 79-line "the other hook on that scope" discussion from docs/spec/gates.md and its now-dangling cross-reference, replacing both with a one-line note of the fold; updated the pre-push hook counts there. Updated fixture builders in tests/test-skill-size-check.sh, tests/test-adr0020-body-checks.sh, tests/test-adr0020-targets.sh, tests/test-adr0020-differential.sh, and tests/test-vale-hooks-consumer.sh to carry valid metadata.version so the new check doesn't spuriously fail existing fixtures.

  5. skill-size-check.sh has six test files totalling 3,589 lines for one 1,497-line script, split by ADR section rather than behaviour. test-adr0020-differential.sh is 452 lines for 12 assertions. Merge to two files. Effort M.

  6. 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.

    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 — a 36-line cut, 966 → 930 as measured at commit a35f5e8. Those two figures describe that commit only, not the file: 718c79a and later findings have edited gates.md again, so read its current length from the file rather than quoting a number here. .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.

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 stay, because apm's own marketplace consumers read that same catalogue and <name>@holocron short names depend on it. (That manifest is the only tracked file under .claude-plugin/ — git ls-files .claude-plugin returns it alone. The sibling .claude-plugin/plugin.json is a local apm pack byproduct, has never been tracked on any branch, and is ignored at .gitignore:59; it was not "kept", because it was never there.)

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.

Re-measured (2026-09-14, at a6434e0): the tests target was stated against the 2026-09-10 baseline and both its numbers are stale. tests/ now holds 20 test-*.sh suites totalling 9,123 lines (plus the two runners, 490). Six suites have gone since the baseline: test-check-manifests.sh (e647f14), test-skill-frontmatter.sh (c8a7c9e), test-governance-layer.sh and test-instructions-and-docs.sh (5f9f2b3), test-sync-marketplace-mirror.sh (0dffff3), test-sync-plugin-content.sh (718c79a). Restated on the same basis the target is 20 files to about 10, 9,123 to about 5,000 lines — the file half of the target is now the closer half, and finding 9's check-ast clause is moot anyway, since finding 9 is not proceeding.

4. Plugins

The shared pattern: per-skill README.md files no model reads, a docs/research/ dump per plugin, a sources.md provenance chain with its own validator, and reference files that restate man pages.

4.1 Cross-plugin (apply everywhere)

  1. Delete docs/research/ from every plugin (~19,000 lines). kyberforge's alone is 14,143 lines, 32% of the plugin, and about 8,900 of those are vendored third-party content (Anthropic skill-creator including a 1,325-line viewer.html and ten .py files, obra/superpowers, mattpocock). The rest is copied tool documentation. The gitea references explicitly say the research doc "has a known history of drifting from the deployed server". Every apm.yml uses includes: auto; whether the directory ships to consumers needs one check. Keep upstream URLs in one line per plugin README; git history keeps the rest. Check obra/superpowers licence if anything is retained. Goes together with finding 11: 32 sources.md files carry "Research doc" paths into these directories. Effort S.

    Decision (2026-09-12): Keep. docs/research/ is retained on purpose — it's read by agents doing work sourced from those docs. Not proceeding.

  2. Delete per-skill README.md and references/README.md (48 files, 1,574 lines). They restate the SKILL.md in narrative form. The pre-commit config itself notes a skill README "is consumer-facing prose that no agent ever loads". Keep one plugin-level README with one line per skill. Requires dropping the README criterion in skill-audit/references/file-structure.md and the README step in new-skill.sh. Effort S.

Done (2026-09-12): see commit edcc57c on docs/simplification-audit. Deleted the 48 per-skill/reference READMEs plus 2 scaffold templates; dropped the README criterion from skill-audit's file-structure.md and finding-criteria.md and the README-generation step from new-skill.sh; updated new-skill.bats to match. Plugin-root READMEs were kept, not part of this finding.

  1. Drop the provenance chain: sources.md, source_keys frontmatter, validate-provenance.sh. 32 plugin and skill sources.md files (about 1,300 lines) plus 9 research indexes, 216 source files with source_keys, two copies of the validator (1,198 and 632 lines) with ten checks, and 125 bats tests exist to track which upstream informed which file. Git blame and a URL in the README do the same job. This is more code than the content it tracks. Effort M (touches skill-audit, both validator copies, two repo tests, and every skill's frontmatter).

  2. Strip ADR and changelog narration from model-facing files. ADR-0020 is cited in 3 of 7 kyberforge SKILL.md files and 16 references; ADR-0023 is cited inline 21 times in the git plugin. Examples: "was the old house rule and ADR-0020 deleted it", "were removed per ADR-0015 once issue #90 landed", "this file previously recorded list_issues as having neither a type nor a milestones parameter". skill-author/references/retrofit.md (197 lines) is a one-time migration guide; it is loaded from improve.md and listed in sources.md, so remove those in the same change. These belong in git history or the ADR, not in context. Effort S.

Done (2026-09-12): see commit edcc57c on docs/simplification-audit. Historical narration stripped from kyberforge (ADR-0020) and git (ADR-0023) skill content; retrofit.md deleted along with its load-step and sources.md entries. Caught in review: some ADR-0023 tags were not narration but the check-rtk-prefix hook's required opt-out marker for intentionally-bare git commands — those 12 were restored, not left stripped.

  1. State repeated boilerplate once or delete it. A near-identical "Resolve owner and repo" block in 5 of 7 gitea skills; 404-masks-403 in 6 files; manual pagination in 7; main/master refusal in 9 git files; the "use the project's domain glossary, respect ADRs" paragraph in 5 bin skills. Three git skills define three different structured-result JSON shapes whose only consumer is git-orchestrate (finding 19). Effort S.

    Done (2026-09-12): see commit 6cfc357. Trimmed each repeated instance in place — same meaning, fewer words — rather than extracting to a shared file (blocked by the one-file-per-skill install constraint, ADR-0014): the "Resolve owner and repo" block across 5 gitea-* skills, the 404-masks-403 note across 6 gitea files, the manual-pagination explanation across 8 gitea files, the main/master force-push refusal across 7 git plugin files (some with multiple internal restatements), and the domain-glossary/ADR paragraph across 5 bin skills. This was a trim-in-place pass, not a merge: the cross-skill duplication itself remains and is coupled to the (out-of-scope) skill-merge findings 19/20. Left the three git skills' structured-result JSON shapes untouched, as directed. Verified no regressions with scripts/skill-size-check.sh (pre/post diff) and claude plugin validate on both plugins.

4.2 kyberforge (290 files, 44,568 lines incl. mirror; the 7 SKILL.md bodies are 333 lines, under 1%)

  1. Merge skill-audit + agent-audit into one audit skill (removes about 3,300 lines and two pre-push hooks). vale-wrap.sh is byte-identical in both; five Vale rules byte-identical (agent-audit carries one extra, so it is the superset); validate.sh shares a 1,061-line boundary-target resolver block that diffs as zero lines; SKILL.md steps 1, 3, 4 and the gotchas are the same text. Each copy is hard-wired to one mode, so the merged script needs a path switch. The duplication exists because a plugin-cache install copies only each skill's own files (the rule ADR-0014 follows), so a script cannot be shared across skills; merging the skills is the only way to remove the copy. Effort M.

  2. Merge skill-author + agent-author likewise. contract.md shares most of its Description section; new-skill.sh and new-agent.sh implement the same package-root walk-up with different mode names; step 1 dispatch tables and step 3 gates are near-identical. Keep the agent scope logic (plugin vs project/user) as its own reference. Effort M.

  3. Cut the validators by an order of magnitude. validate.sh is 1,677 lines of bash with embedded Python, ported twice; skill-size-check.sh is 1,497. Target about 200 lines total: frontmatter present, size ceilings, boundary targets resolve. The 526-line vale-wrap.sh exists to work around folded > scalars in descriptions; writing descriptions as | literal blocks removes the folding problem, but the wrapper is also the exported hook entry in .pre-commit-hooks.yaml and carries the NOT RUN guard the audits depend on, so it shrinks rather than disappears. This is where the real complexity lives and is the item most worth discussing. Effort L.

  4. Fold forge and apm-install. forge is a four-row routing table plus 207 lines of references explaining fork vs inline; it should be 25 lines with no references. apm-install (53 lines + 17-line sources) becomes a sixth dispatch row in apm-workflow. Effort S.

  5. Delete prose the model already knows. "Valid characters: lowercase letters, numbers, hyphens"; what pipx does and PEP 668; "code blocks carry a language tag"; "data to stdout, diagnostics to stderr". Ironically body-discipline.md instructs auditors not to include "concepts the agent already knows". Effort S.

4.3 git and gitea (153 + 93 files, 9,889 + 6,047 lines incl. mirror; source 3,288 + 2,286)

  1. Delete the two router skills and two orchestrate agents (309 lines + 195 reference lines). No skill invokes them as a step; they appear only in boundary clauses (AGENTS.md, git-worktrees, gitea-issues, gitea-prs) and as worked examples in agent-audit references, all of which must change in the same commit or skill-size-check fails on the dangling target. Claude Code already routes on descriptions. The chain today is git-workflow step 5 invokes git-orchestrate, whose step 5 invokes git-commits, which runs rtk git commit: three hops. Both agents exceed 900 words; ADR-0020 deliberately sets no agent body gate. Effort S.

    Not proceeding (2026-09-13): premise doesn't hold. There are no separate "router skills" — only two .agent.md files. git-orchestrate is not a dangling boundary-clause reference; it's git-workflow step 5's actual execution backend (documented both directions), so deleting it breaks git-workflow's only execution path rather than tidying an orphan. gitea-orchestrate is intentional per ADR-0011 (agent-facing counterpart for agent callers) even though gitea-workflow doesn't call it. A third, undocumented instance of the same pattern (apm-orchestrate) exists and isn't addressed by this finding. The four boundary-clause locations named above don't actually reference either agent. No changes made. This needs the "short discussion" §7 bucket 2 implies, not a mechanical delete.

  2. Collapse git 7 skills to 1; gitea 7 to 2. Git references are man-page restatement: git-log-format.md (242 lines listing %H, %ar), conventional-commits-spec.md (170 lines), worktrees.md (178), merging.md explaining fast-forward. Roughly 60% of the plugin is generic. The genuinely house-specific content fits in about 150 lines: the rtk rule and ADR-0023 exceptions, main/master refusal, --no-verify, the -i --autosquash 2.39.5 trap, --force-with-lease --force-if-includes, bisect exit codes, submodule push ordering, the detached-HEAD worktree trap. Gitea is more legitimately specific (MCP schema quirks: tree_sha, withLines, silent drops on PR create, per_page 20 vs 30, 404 means 403) and splits naturally into gitea-tracker (issues, PRs, labels, milestones) and gitea-repo (branches, files, releases). Risk: one description must carry all trigger phrases; keep a dispatch table at the top of the body. Keep pc-author and pc-run (finding 38). Effort M.

  3. Delete config.example.json / .claude/plugins/git/config.json. Read by two steps, written by nothing. Default to GitHub Flow with the existing develop / release/* inference. Effort S.

    Done (2026-09-12): see commit f5e4d0d. Deleted plugins/git/config.example.json (the runtime .claude/plugins/git/config.json was never a tracked file). Removed the config-read step from git-orchestrate's Process and from git-branches' Step 1, leaving the existing default-inference logic (GitHub Flow, with Gitflow inferred from a develop/release/* branch) as the sole path; updated git-workflow's description of the orchestrator's behaviour to match. Dropped the now-dangling applied_config field from git-orchestrate's output shape and the config.example.json example from docs/spec/architecture.md.

4.4 bin, core, lint (88 + 49 + 31 files incl. mirror)

  1. bin: strip generic process theatre. write-docs is 109 lines, mostly form-filling sections plus a 15-line source provenance block; its rules fit in 25 lines. tdd is about 70% textbook (RED/GREEN diagram, "good tests are integration-style", five thin references restating textbook design advice). diagnose 40%, prototype 50% (pixel-level UI switcher spec), grill-with-docs 35%. Keep the opinionated parts: "no horizontal slicing", "no phase 2 without a loop", [DEBUG-xxxx] tags, "never infer the output path", the triage state machine. Effort M.

  2. bin: merge grill-me into grill-with-docs. grill-me is 16 lines and a subset of the docs flow; grill-with-docs creates CONTEXT.md when missing, so the merged skill needs a no-write opt-out. caveman (50 lines) and zoom-out (9) are hand-invoked prompts rather than workflow skills; they are also the repo's disable-model-invocation exemplars in CONTEXT.md, contract.md, ADR-0020, ADR-0021, and gates.md, and install.sh has no path for ~/.claude/commands/, so moving them means picking a new exemplar. improve-codebase-architecture defines its glossary twice (inline and in language.md; the README documents the split as intentional). Effort S.

  3. core: provider-adapter-author is a 1,200-line wrapper around one instruction ("replace duplicated lines with @AGENTS.md, keep provider-specific lines"): a 496-line validator with a 519-line bats suite for a check that is a grep. agentsmd-author already calls agentsmd-audit as mandatory closeout, and both route to provider-adapter-author in boundary clauses that must change with it. Target: one agentsmd skill with an audit mode, adapter conversion as a step, validator about 40 lines. Needs an ADR-0012 revisit. Effort L.

  4. lint: delete the lint-runner agent. Its body is "call vale-run, reformat output", which --output=JSON already gives; it exists for backends that do not exist. It is the example boundary clause in three agent-author templates and ADR-0016, so those need a new example. About 40% of vale-config is install tables and settings lists the model can fetch from vale.sh. Keep the house-verified matrices (E100/E201, Packages below glob, frontmatter, ignore paths). lint/docs/research/docs/vale/ overlaps the skill's own references by about two thirds. Effort S.

5. Prose and docs (9,600 lines, 109,000 words outside plugins)

  1. Move or delete docs/research/ and docs/notes/ (4,500 lines, 47% of prose words). Six of eleven research files are linked only from each other; they are self-described session audit trails, agendas, and a "temporary build reference". docs/notes/factory-research-gaps-conflicts.md says "Status: Superseded"; factory-integration-decisions.md says "Complete" and its decisions already live in ADRs, yet AGENTS.md tells every session to read it. archive/team-self-organisation-sprint-brief.md (3,400 words) is unrelated to this repo. Archive or delete; drop the three AGENTS.md pointers. Moving CONTROLS.md to docs/spec/ means updating its literal path in nine or more files including the deployed governance.md. Effort S.

    Decision (2026-09-12): Keep. Same reasoning as finding 9 — these docs are intentional context for sourced work. Not proceeding.

  2. Four governance documents say one thing. core/instructions/governance.md (949 words, always-on), docs/ai-constitution.md (2,906), docs/wiki/HUMANS.md (1,413), CONTROLS.md (1,224), with near-identical preambles and, in three of the four, a "what this file does not govern" block pointing at the others. The constitution repeats one of its own principle lead sentences. Keep governance.md as the operative file, trimmed to about 50 lines (drop the classification table that repeats the bullets above it, the footer, the non-governance block). Dedupe the constitution by about 20%. Effort M.

  3. ADRs: 2,740 lines, 72% in eight ADRs over 150 lines. ADR-0020 is 513 lines with a 71-line measurement log as Context; ADR-0017 has 173 lines of amendments against 45 of decision. ADR-0001 is superseded and ADR-0006 moot, both keeping full text below the banner. ADR-0002 is three lines. Truncate superseded ones to the banner, fold amendments into the decision, cap Context at 20 lines, add a 25-line docs/adr/README.md index with status. The rules already live in gates.md; the ADRs need only decision and consequences. Effort M.

    Moved backwards (measured 2026-09-14 over afa7187^..a6434e0): today's ADR-0024 work did the opposite of this finding on every axis, and that is recorded here so it is a known trade rather than a surprise. docs/adr/ went from 23 files / 2,748 lines to 24 / 3,084 — one new ADR (0024, 259 lines) plus amendment and banner text across eleven existing ADRs (0001, 0006, 0011, 0013, 0014, 0015, 0017, 0018, 0019, 0020, 0021 — 87 lines added, 10 removed, net +77), for a total of net +336 lines (+12%). The two ADRs this finding names for truncation both grew below their banners instead: ADR-0001 26 → 27 lines and ADR-0006 22 → 27, each gaining a fresh "as of ADR-0024" paragraph rather than losing the historical body beneath it. ADR-0017 gained a supersession banner while keeping its four amendments in full — the exact shape this finding proposes to fold.

    Not a defect in that work: a supersession has to be recorded somewhere, and an unread stale ADR is worse than a long one. But it does mean the finding's estimate is now conservative and its "truncate superseded ones to the banner" step has more to remove than when it was written — ADR-0001, ADR-0006 and ADR-0017 are all superseded-with-full-body today. State the basis when re-measuring: this is a two-SHA measurement, not a standing count, and further ADR amendments were being written by other sessions while it was taken. Re-derive with git ls-tree -r --name-only <sha> docs/adr before acting on it.

  4. The same facts are stated in full three or four times. "Edit .apm/, never the mirror": README (2 paragraphs), AGENTS.md (2 paragraphs), architecture.md (2 paragraphs plus the lost-README anecdote), ADR-0017. The apm.lock / SessionStart story: README (11 lines), AGENTS.md, ADR-0018, ADR-0019, gates.md. The offline SKIP= command and the three-stage install each appear three times. Rule: README has the how-to, AGENTS.md has one-line rules with links, architecture.md has mechanics. Effort S.

    Corrected then partially done (2026-09-14): independent re-verification found the "edit .apm/, never the mirror" and apm.lock/SessionStart clusters confirmed but the third overstated — no file documents an offline SKIP= command (the one SKIP=-adjacent mention in gates.md explicitly says a different opt-out "is not SKIP="), and "three-stage install" appears twice, not three times, with no restatement worth trimming. Trimmed the two confirmed clusters: README's "Editing plugin content" and AGENTS.md's "Edit .apm/, never the flat mirror" sections cut to the how-to/one-line-plus-link split the finding itself proposed, full mechanics (the rm -rf behavior and the plugins/kyberforge/hooks/README.md anecdote) staying solely in docs/spec/architecture.md. README's "Keeping the install current" and AGENTS.md's apm.lock bullet trimmed to drop the restated apm outdated/apm update --yes timing narrative, pointing to ADR-0019 as the canonical mechanism instead. No test greps the trimmed wording (checked).

  5. LESSONS.md: 41 entries, 2 graduated, about 12 stale. Twelve entries from 2026-05-17 describe a write-skill / write-eval workflow whose skills no longer exist. One entry is open work labelled "Status: neither part landed". The longest eight are 200 to 550-word incident reports. Delete the stale entries, move open work to an issue, cap entries at about 60 words, target 100 lines. Effort S.

    Done (2026-09-12): see commit 629320b on docs/simplification-audit. 255→131 lines, 41→30 entries. Kept 3 of the same-dated entries (RLHF defaults, secrets-rule gap, HITL gap) — judged unrelated to the defunct write-skill/write-eval workflow and still applicable, so 10 deleted rather than 12. The "neither part landed" open-work entry (CONTEXT.md not @imported at session start) was removed rather than filed as an issue — full text preserved in this session's transcript if wanted later.

  6. CONTEXT.md: 28 terms, most used only by gates.md, scripts, or tests rather than by skills; two (Preload tax, Skill context contract) are never used outside CONTEXT.md and ADR-0020. The preload-tax entry quotes two dated numbers then says not to quote them. The example dialogue and flagged-ambiguities sections are grill residue. Cut to about 20 one-line terms. Effort S.

    Corrected then done (2026-09-13): see commits 124ce6e and follow-up on docs/simplification-audit. Independent re-verification found "most used only by gates.md/scripts/tests" overstated: 13 of 28 terms are actually referenced from model-facing references/*.md files skills load in normal use (Routing target, Hand-invoked skill, Dispatch body, Near-miss, Thin adapter, Provenance chain, Output profile, apm package, Plugin marketplace, HITL, Skill composition, Delegation discipline, holocron) and were kept untouched. Only the 9 terms confirmed as true orphans were removed after a fresh independent grep: Content mirror, apm-consumed install, Vale audit prefilter, Vacuous green, Management Application, Sycophancy, HOTL, Preload tax, Skill context contract — 28 → 19 terms. Re-counted (2026-09-14, at a6434e0): 18 terms, not 19. The "28 → 19" above is an accurate record of this finding's own commit (124ce6e) and is left standing. 718c79a then removed a twentieth-to-nineteenth entry this finding never touched: the standalone Plugin term, folded into apm package when ADR-0024 made "plugin" and "apm package" the same thing. Counted as bolded term entries between ## Language and ## Relationships in CONTEXT.md: 19 at 124ce6e, 18 at 718c79a and unchanged at a6434e0. The finding's own target ("about 20 one-line terms") is met either way. The preload-tax self-contradiction (quotes 23,427/10,478-char figures then says not to quote either) was confirmed verbatim and resolved by the entry's own deletion. The "example dialogue" and "flagged ambiguities" sections were found to be mandated by grill-with-docs/references/context-format.md's template spec, not grill residue — left untouched, except one dangling bolded cross-reference to the now-deleted "Preload tax" term in a Flagged-ambiguities line, which was unbolded/de-referenced in place (the ambiguity resolution itself still holds without a defined glossary entry to point at).

  7. Structure is described three ways (README layout table, architecture.md plugin table, AGENTS.md structure bullets), and VISION.md carries a 35-line stack spec for a product that lives in another repo. One layout table in README; architecture.md keeps mechanics only; VISION drops the stack detail. Effort S.

6. Distribution, versioning, and session startup

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.

  1. 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.

  2. 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.

  3. Outputs and packages for consumers that do not exist. The codex output profile generates .agents/plugins/marketplace.json (95 lines) although Codex is not a supported consumer. The mattpocock-skills remote package entry is the only reason apm-marketplace-check needs the network, and its pin is advanced by hand (ADR-0015). The .github/plugin/marketplace.json mirror is a legacy path (finding 2). Removing all three leaves one generated marketplace manifest (the per-plugin plugin.json pairs remain) and no network-dependent hook. Effort S.

    Done (2026-09-13): see commit 568ca74 on docs/simplification-audit. Removed the codex output profile from root apm.yml and its compiled .agents/plugins/marketplace.json (95 lines), and the mattpocock-skills remote package entry — the only remote marketplace entry, so apm-marketplace-check and apm-pack-check-clean no longer need network access at all. Updated README.md, AGENTS.md, docs/spec/gates.md, and docs/spec/architecture.md accordingly; added one-line superseded/updated notes to ADR-0015 and ADR-0021. Left .github/plugin/marketplace.json untouched — that's the Copilot legacy-path question in finding 2/§8, out of scope here; only re-ran the sync script to keep it consistent. apm.lock.yaml unaffected (marketplace.packages[] isn't part of the lockfile). Verified via apm install, apm pack --marketplace=claude --check-versions, and all four affected pre-push hooks. Both carve-outs overtaken the next day (2026-09-14, verified at a6434e0): neither survives, and the finding's headline outcome — "one generated marketplace manifest, no network-dependent hook" — is now literally true rather than approximately so.

    • "The per-plugin plugin.json pairs remain" is void. All twelve were deleted in 718c79a (ADR-0024); git ls-files '*plugin.json' returns nothing. The only tracked manifest left anywhere is the root .claude-plugin/marketplace.json. (The root .claude-plugin/plugin.json beside it is untracked local apm pack output, ignored at .gitignore:59.)
    • "Left .github/plugin/marketplace.json untouched … out of scope here" is void the same day: 0dffff3 deleted it under finding 2c, along with scripts/sync-marketplace-mirror.sh and its test. The "only re-ran the sync script to keep it consistent" step above refers to sync-plugin-content.sh, itself deleted in 718c79a.
  4. The release-tag mechanism guards an external contract with no known consumer. .pre-commit-hooks.yaml exports three hooks for other repos to pin by rev: <tag>. check-release-needed (242 lines + 442 test), test-vale-hooks-consumer (270 lines), ADR-0014, and three tags exist to serve that. If no other repo pins these hooks today, the whole mechanism can be deferred until one does. Effort S.

  5. Two .mcp.json files declare an Obsidian vault server over docs/ (root and plugins/bin/; the other five plugin .mcp.json files are empty stubs), while AGENTS.md forbids using an external memory system for this repo. If the Obsidian tools are unused, drop both and the reinject_mcp_servers explanation in the bin README; the bin plugin.json pair regenerates. Effort S.

    Not proceeding (2026-09-13): premise doesn't hold. The server exposes the repo's own git-tracked docs/ folder — not an external/off-repo store — so it isn't the "external memory system" AGENTS.md's rule targets. It was deliberately added and versioned (3 commits), is documented as current intended behavior in both READMEs, and ADR-0018 uses it as its only concrete worked example of apm's MCP-dependency propagation mechanism actually working. No skill invokes the Obsidian tools as a workflow step, but that alone doesn't make the config dead. No changes made; recommend a human confirm whether the vault tooling is still wanted before removing it. Confirmed and done (2026-09-14): the human confirmed the vault tooling is not wanted — remove it entirely. All seven .mcp.json files deleted (the six plugin-root files and the repo-root one), and the repo-root path added to .gitignore so a local apm run cannot recreate it as tracked content. The bin README's reinject_mcp_servers explanation goes with it; the plugin.json pair the finding expected to regenerate no longer exists (deleted in 718c79a, finding 7).

    What made this urgent is the substantive discovery, not the tidying: deleting the per-plugin plugin.json manifests in 718c79a had already broken MCP propagation silently. apm_cli/deps/plugin_parser.py maps a plugin-root .mcp.json → .apm/.mcp.json, and that code path runs only for marketplace plugins — with no manifest, apm never reads the file. plugins/bin/apm.yml declares dependencies.mcp: [], so the supported mechanism was never used either. Proved on ref-pinned consumer clones: at the parent commit a consumer gets an obsidian server, at HEAD it gets none, and on upgrade apm prints Removed stale MCP server 'obsidian' from .mcp.json — which would in time have stripped the server from this repo's own tracked .mcp.json once the lock re-resolved. Deleting the files makes the intent match the behaviour instead of leaving a config that silently does nothing.

  6. pc-author / pc-run (689 lines) carry generic pre-commit documentation. hooks-by-language.md (128 lines) and failure-patterns.md (133) restate pre-commit.com. Keep the skills, trim to the house-specific rules. Effort S.

    Corrected then done (2026-09-13): see commit a622200 on docs/simplification-audit. Independent re-verification found the 689-line figure overstated (actual combined size 598 lines) and the realistic cut smaller than a rewrite (~60-85 lines, concentrated in the two named reference files, not the SKILL.md files or the four short flow files, which are house-specific gates rather than restatement). Landed within that range: hooks-by-language.md 128 → 92 lines (collapsed six per-language tables repeating the same repo/rev/rationale into one shared-repo table plus a small other-repos table); failure-patterns.md 133 → 109 lines (removed generic SSH/proxy and shellcheck SC-code restatement, compressed generic schema-error bullets). Kept verbatim: both "Unverified — not in research corpus" flags, the rev-freshness caveat, the rtk git add -u/rtk git commit fix (ADR-0023), and the pre-commit install -f warning. Combined cut: 60 lines. Flat mirror regenerated and verified byte-identical.

7. Suggested order

  1. Quick wins, all S, no design decisions needed: findings 9, 10, 26, 30, 31, 29, 12, 13, 1, 6, 4, 35, 37, 38, and the mirror-sync and executables-allow halves of 2. Removes roughly 25,000 to 30,000 lines and 6 hooks.
  2. Structural changes that need a short discussion: 14, 15, 19, 20, 23, 25, 17, 3, 5, 7, 33, 34, 36.
  3. The real complexity: 16 (validators), 11 (provenance), 24 (core), 8 and 28 (gates.md and ADRs).

Findings 9, 10, 11, and 12 are coupled through the provenance validator and the audit criteria; land them together or the audit gates start reporting the removals.

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?

    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 the root .claude-plugin/marketplace.json, which apm's own consumers read. (marketplace.json is the only tracked file under .claude-plugin/; the root plugin.json beside it is untracked local apm pack output, ignored at .gitignore:59.) 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.

    Answered (2026-09-14): yes, but only as a preferred path, not a required one — so the delete holds. Settled under finding 2c above and executed in commit 0dffff3; this bullet was left open by oversight when that finding closed. Copilot CLI falls through marketplace.json, .plugin/marketplace.json, .github/plugin/marketplace.json, .claude-plugin/marketplace.json in order, and the .claude-plugin/ file apm already emits satisfies the last step. What was lost is discovery-order preference, not Copilot consumability.

    Corrected (2026-09-14, later the same day, verified at a6434e0): "not Copilot consumability" no longer holds. It was true at 0dffff3; 718c79a (ADR-0024) then removed native install support for both hosts, and Copilot consumability went with it. The fallback still resolves — that part stands — but it now resolves to a catalogue of six packages whose roots contain no content: ls plugins/*/ shows .apm/, apm.yml, docs/ and a README, and plugins/*/skills, .../agents, .../hooks do not exist at all.

    The mechanism is host-independent, which is why this bullet had to change. ADR-0024 consequence 1 and §9's first residual both state it for Claude Code: a native registration succeeds and installs six plugins containing zero skills, silently. Nothing in that chain is Claude-specific. The catalogue is a list of plugin roots; discovery of content inside a root is a convention-scan of flat skills//agents//hooks/ directories, and that is the layout 718c79a deleted. Whichever of the four paths a host resolves the catalogue through, it lands on the same empty roots. Copilot was in fact always the weaker case — ADR-0017's own hooks amendment records that the mirror only ever partially served it.

    The delete still holds, for a stronger reason than the one given: the file was a preferred discovery path to content that no longer exists. What changed is the accepted cost — this is no longer "preference lost", it is the same accepted silent-empty-install residual §9 records, now known to apply to Copilot as well.

  • 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. Corrected (2026-09-14): this grouping was wrong, and finding 2b's note has said so since 0dffff3 while this bullet said the opposite. ADR-0012 governs only the core plugin's three agentsmd-* skills (agentsmd-author, agentsmd-audit, provider-adapter-author) — read it: it names those three and nothing else. Only finding 24 touches them, so only finding 24 needs ADR-0012 revisited. Findings 14 and 15 merge kyberforge's skill-audit/agent-audit and skill-author/agent-author, which ADR-0012 does not govern; what constrains them is the self-containment rule, and merging is the way around it rather than a reason to reverse anything. That rule survives ADR-0024 — see §9's negative result and ADR-0024 consequence 6, which also correct its source: it is the agentskills.io spec for APM package mode, not a property of Claude Code's plugin cache-install as finding 2b's note assumed. The open question for 14/15 is a design one — one description carrying both skills' trigger phrases — not an ADR supersession. Are you open to superseding ADR-0012, for finding 24?
  • Granularity of git/gitea skills. One git skill vs seven trades routing precision for size. Is one broad description acceptable?
  • 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.

    Answered (2026-09-14): not used — remove entirely. All seven .mcp.json files are deleted and the repo-root path is gitignored; see finding 37, which also records the functional regression this uncovered (since 718c79a deleted the per-plugin manifests, apm no longer propagated the server to consumers at all).

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 a consumer installing from this branch receives 10 .bats files across 6 skills, plus those skills' 6 tests/README.md files — 16 files. (Repo-wide, 17 tracked paths contain /tests/: the 10 .bats and 7 README.md, one of which is a template asset under skill-author/assets/templates/tests/ and is not a test fixture.) This is what consumers receive, not what this checkout shows: .claude/skills/ here currently holds zero .bats files, because that deployed tree is stale and predates this branch. The mechanism was confirmed empirically on a ref-pinned consumer clone — the 16 files are absent at the parent commit and present at HEAD. 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.

Accepted gap — symlinks under .apm/. ADR-0017's check_apm_symlinks() was the only thing reporting that symlinks under .apm/ do not survive to a consumer. It is gone, and no replacement guard is being added — the human decided to accept the gap.

The mechanism is not the bundle exporter, as ADR-0017 assumed; it is the install path, and it has since been verified. apm_cli/security/gate.py's ignore_non_content() is a shutil.copytree ignore callback whose docstring says "Excludes symlinks (security)"; it is used at apm_cli/integration/skill_integrator.py:424, :791 and :1152. Materialization into apm_modules/ dereferences first, so symlinked content survives there and is dropped when skills are deployed out of it. ADR-0024 flagged the prediction as unverified; it holds, with that corrected attribution. No symlinks exist under any .apm/ today, so nothing is broken now — but the next one added there will silently not reach consumers, and nothing will say so.