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
69 KiB
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 a6434e0Live 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>/at9eb8bc7and ata6434e0.)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 — at061bb3d, 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 ata6434e0and the column now names its SHA. The baseline column is confirmed exact against9eb8bc7. Commits after061bb3d(c96ca9c, which deleted the six plugin-root.mcp.jsonfiles) account for most of the remaining drift.
Reviewed (2026-09-14): commits
718c79aandd2480b8were 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 | |
Lines in plugins/ |
|
Of which the 39 SKILL.md files a model actually loads |
|
Generated flat mirror files (byte copies of .apm/) |
~ |
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/ |
|
| 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
718c79aondocs/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.yamltoday has 27 entries and 9stages: [pre-push]. Like for like that is 14 → 9 repo-authored pre-push hooks. The stage reports 11, because the 2 pre-commitmetahooks 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 afterd2480b8/061bb3dcorrected their neighbours, and are now corrected ata6434e0:
- 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.mdbodies: 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-*.shtotalling 9,123 lines, the two runners 490 (run-tests.sh281 +run-bats.sh209), andscripts/3,308.Re-checked and still accurate at
a6434e0, so left alone:docs/research/inside plugins (19,030), repo-leveldocs/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) |
|
check-plugin-content-sync |
|
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 |
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
718c79aondocs/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) andvalidate-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 ofrun-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.
-
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 twoclaude plugin validatehooks plusapm-pack-check-clean.Deletecheck-manifests(282 lines + 771 test lines; itslib/marketplace-plugins.shstays becausesync-plugin-content.shsources it).apm-audit-cispends 12 s confirming that manifestsapm packalready parses do parse; drop or keep on that basis.Move the network-dependentEffort S.apm-marketplace-checkto a release checklist.Done (2026-09-12): see commit
e647f14ondocs/simplification-audit. Deleted thecheck-manifestspre-commit hook entry,scripts/check-manifests.sh(282 lines), andtests/test-check-manifests.sh(771 lines); keptscripts/lib/marketplace-plugins.sh, still sourced bysync-plugin-content.sh. Updated the now-stalecheck-manifests.shmentions inREADME.mdanddocs/spec/gates.md(hook table row and hook counts). Theapm-audit-ciandapm-marketplace-checkdecisions 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 commita155af6, 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 (commit568ca74) already removed the only remote package entry, so everymarketplace.packages[]entry is now a local./plugins/<name>path and the hook is pure overlap withapm-pack-check-clean. Removed the hook entry, and corrected the now-stale "does NOT join apm-marketplace-check ... on the offline SKIP= list" comment onapm-audit-ci(there is no offline skip list any more — every pre-push hook already passes offline perREADME.md). UpdatedREADME.md(tool table, "Offline?" section) anddocs/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 --cistill passes per-plugin, and the pre-push hook count now matches.pre-commit-config.yaml. Corrected and closed (2026-09-14, ata6434e0): two things above went stale within hours of being written, and the finding was never given a marker.- "Keep the two
claude plugin validatehooks" is void.718c79a(ADR-0024) deletedvalidate-plugins— the ADR's own reasoning is thatclaude plugin validatereads 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. Onlyvalidate-marketplacesurvives, 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 withstages: [pre-push], novalidate-pluginsentry. - The gates.md figures above ("13→11 self-authored / 15→13 total") were correct for
0dffff3and are no longer current.718c79aremoved two more pre-push hooks after that commit, anddocs/spec/gates.md:24now 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-manifestsdeleted (e647f14),apm-audit-cikept on the grill above,apm-marketplace-checkremoved (0dffff3). - "Keep the two
-
Fourtwo surviving "keep two copies in sync" gates:1,100 script lines + 1,600 test lines778 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-syncdeleted in0dffff3(2c below) and, though it was never in this finding's own count,check-plugin-content-syncin718c79a. The two that survive arecheck-vale-style-sync(413 script + 797 test) andcheck-scope-walkup-sync(365 + 282);check-executables-allow-syncalso 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 exceptcheck-executables-allow-sync's "474 lines", which 2d already corrects.check-vale-style-sync: 413 lines + 798 test lines guarding a byte-identical 526-linevale-wrap.shand 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 intest-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
coreplugin's threeagentsmd-*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'sdescriptioncarrying 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 ownclaudeoutput) already satisfies that chain's last step, so the dedicated.github/plugin/marketplace.jsonmirror 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 thecheck-marketplace-mirror-syncpre-push hook; removed the dangling references to the deleted script inscripts/sync-plugin-content.shandtests/test-sync-plugin-content.sh(both had comments citing its reasoning by name), and updateddocs/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.
718c79alanded 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 at0dffff3; 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
1b01e25ondocs/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. Shrunkscripts/check-executables-allow-sync.sh231 → 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.
-
Tests of the test harness: 1,090 lines testing 475 lines.
test-run-tests.shandtest-run-bats.shdefend "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 pluginsplus a parallelfind | xargsovertest-*.sh) and delete the meta-tests.lib/batch-run.shstays;both runners source it. Effort M.sync-plugin-content.shsources itNot 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_FLOORhardcode once let deleted test files vanish silently ("155 tests, 0 failures" with 11 tests missing); a missing/brokenrun-bats.shused 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, ata6434e0): the stated reasonlib/batch-run.shsurvives was void —sync-plugin-content.shwas deleted in718c79a. The conclusion is unaffected:batch-run.sh(90 lines) is sourced bytests/run-tests.sh:185andtests/run-bats.sh:138, and copied into fixture trees bytests/test-run-tests.sh:51andtests/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 leavebatch-run.shwith 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 the9eb8bc7baseline. (Both runners andbatch-run.shwere under concurrent edit when this was measured; figures are as ofa6434e0.) -
skill-frontmatteris a 62-line bash script inlined in YAML with its own 366-line test.skill-size-check.shalready parses the same frontmatter with PyYAML. Fold it in (about 15 Python lines), delete the inline hook, its test, and the 79 lines ingates.mdarguing for the split. Effort S.Done (2026-09-12): see commit
c8a7c9eondocs/simplification-audit. Added a ~20-line required-frontmatter check (name,description,metadata.versionas three-part semver) toscripts/skill-size-check.sh, reusing the YAML mappingdescription_value()already parses. Removed the inlineskill-frontmatterhook (~80 lines) from.pre-commit-config.yamland deletedtests/test-skill-frontmatter.sh(366 lines). Removed the 79-line "the other hook on that scope" discussion fromdocs/spec/gates.mdand 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 intests/test-skill-size-check.sh,tests/test-adr0020-body-checks.sh,tests/test-adr0020-targets.sh,tests/test-adr0020-differential.sh, andtests/test-vale-hooks-consumer.shto carry validmetadata.versionso the new check doesn't spuriously fail existing fixtures. -
skill-size-check.shhas six test files totalling 3,589 lines for one 1,497-line script, split by ADR section rather than behaviour.test-adr0020-differential.shis 452 lines for 12 assertions. Merge to two files. Effort M. -
Prose-grep tests.test-governance-layer.shandtest-instructions-and-docs.sh(583 lines) grep markdown for phrases, including a one-shot "issue 0015 refactor incomplete" assertion made permanent and an assertion thatdocs/notes/exists. Delete both.check-apm-agents-valid.sh(161 + 264 test lines) is a loop plus fail-closed guards aroundvalidate.sh; it folds into the merged audit skill's own tests (finding 14). Effort S.Done (2026-09-12): see commit
5f9f2b3ondocs/simplification-audit. Deletedtests/test-governance-layer.sh(270 lines) andtests/test-instructions-and-docs.sh(313 lines); no other file referenced either.check-apm-agents-valid.shwas left untouched — its fate is tied to the separate, out-of-scope skill-merge finding 14. -
check-plugin-content-sync.shis 813 lines wrappingapm 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 baregit diffwould lose: it stripstests/from the mirror, regenerates bothplugin.jsonfiles withmcpServersreinjected, and packs into a scratch copy so--checknever mutates. Even so, 2,100 lines for that is disproportionate; target a third. Effort M.Superseded then done (2026-09-14): see commit
718c79aondocs/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), thecheck-plugin-content-syncpre-push hook, andscripts/lib/marketplace-plugins.sh(86 lines, whose only consumer was the sync script, and which finding 1 had explicitly kept alive for it).validate-pluginswent with them, and the twelve per-pluginplugin.jsonmanifests the script regenerated. The finding's own premise — "the mirror itself must stay" — is what turned out to be wrong. -
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.yamland toscripts/, where 8 of 15 files are 40 to 60% comments. Effort M.Partially done (2026-09-13): see commit
a35f5e8ondocs/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 commita35f5e8. Those two figures describe that commit only, not the file:718c79aand later findings have editedgates.mdagain, 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-pluginsandcheck-plugin-content-syncwere deleted in commit718c79a(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-commitmetahooks that also run at this stage, so 11 are reported at pre-push.validate-marketplacewas kept: the rootmarketplace:block inapm.ymland the root.claude-plugin/marketplace.jsonstay, because apm's own marketplace consumers read that same catalogue and<name>@holocronshort names depend on it. (That manifest is the only tracked file under.claude-plugin/—git ls-files .claude-pluginreturns it alone. The sibling.claude-plugin/plugin.jsonis a localapm packbyproduct, 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 20test-*.shsuites 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.shandtest-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'scheck-astclause 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)
-
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 (Anthropicskill-creatorincluding a 1,325-lineviewer.htmland ten.pyfiles, 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". Everyapm.ymlusesincludes: 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: 32sources.mdfiles 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. -
Delete per-skillREADME.mdandreferences/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 inskill-audit/references/file-structure.mdand the README step innew-skill.sh. Effort S.
Done (2026-09-12): see commit
edcc57condocs/simplification-audit. Deleted the 48 per-skill/reference READMEs plus 2 scaffold templates; dropped the README criterion fromskill-audit'sfile-structure.mdandfinding-criteria.mdand the README-generation step fromnew-skill.sh; updatednew-skill.batsto match. Plugin-root READMEs were kept, not part of this finding.
-
Drop the provenance chain:
sources.md,source_keysfrontmatter,validate-provenance.sh. 32 plugin and skillsources.mdfiles (about 1,300 lines) plus 9 research indexes, 216 source files withsource_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). -
Strip ADR and changelog narration from model-facing files.ADR-0020is 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 recordedlist_issuesas having neither atypenor amilestonesparameter".skill-author/references/retrofit.md(197 lines) is a one-time migration guide; it is loaded fromimprove.mdand listed insources.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
edcc57condocs/simplification-audit. Historical narration stripped from kyberforge (ADR-0020) and git (ADR-0023) skill content;retrofit.mddeleted along with its load-step andsources.mdentries. Caught in review: someADR-0023tags were not narration but thecheck-rtk-prefixhook's required opt-out marker for intentionally-bare git commands — those 12 were restored, not left stripped.
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 isgit-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 5gitea-*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 5binskills. 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 withscripts/skill-size-check.sh(pre/post diff) andclaude plugin validateon both plugins.
4.2 kyberforge (290 files, 44,568 lines incl. mirror; the 7 SKILL.md bodies are 333 lines, under 1%)
-
Merge
skill-audit+agent-auditinto oneauditskill (removes about 3,300 lines and two pre-push hooks).vale-wrap.shis byte-identical in both; five Vale rules byte-identical (agent-audit carries one extra, so it is the superset);validate.shshares 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. -
Merge
skill-author+agent-authorlikewise.contract.mdshares most of its Description section;new-skill.shandnew-agent.shimplement 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. -
Cut the validators by an order of magnitude.
validate.shis 1,677 lines of bash with embedded Python, ported twice;skill-size-check.shis 1,497. Target about 200 lines total: frontmatter present, size ceilings, boundary targets resolve. The 526-linevale-wrap.shexists 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.yamland 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. -
Fold
forgeandapm-install.forgeis 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 inapm-workflow. Effort S. -
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.mdinstructs 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)
-
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 orskill-size-checkfails on the dangling target. Claude Code already routes on descriptions. The chain today isgit-workflowstep 5 invokesgit-orchestrate, whose step 5 invokesgit-commits, which runsrtk 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.mdfiles.git-orchestrateis not a dangling boundary-clause reference; it'sgit-workflowstep 5's actual execution backend (documented both directions), so deleting it breaksgit-workflow's only execution path rather than tidying an orphan.gitea-orchestrateis intentional per ADR-0011 (agent-facing counterpart for agent callers) even thoughgitea-workflowdoesn'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. -
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.mdexplaining fast-forward. Roughly 60% of the plugin is generic. The genuinely house-specific content fits in about 150 lines: thertkrule and ADR-0023 exceptions, main/master refusal,--no-verify, the-i --autosquash2.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_page20 vs 30, 404 means 403) and splits naturally intogitea-tracker(issues, PRs, labels, milestones) andgitea-repo(branches, files, releases). Risk: one description must carry all trigger phrases; keep a dispatch table at the top of the body. Keeppc-authorandpc-run(finding 38). Effort M. -
Deleteconfig.example.json/.claude/plugins/git/config.json. Read by two steps, written by nothing. Default to GitHub Flow with the existingdevelop/release/*inference. Effort S.Done (2026-09-12): see commit
f5e4d0d. Deletedplugins/git/config.example.json(the runtime.claude/plugins/git/config.jsonwas never a tracked file). Removed the config-read step fromgit-orchestrate's Process and fromgit-branches' Step 1, leaving the existing default-inference logic (GitHub Flow, with Gitflow inferred from adevelop/release/*branch) as the sole path; updatedgit-workflow's description of the orchestrator's behaviour to match. Dropped the now-danglingapplied_configfield fromgit-orchestrate's output shape and theconfig.example.jsonexample fromdocs/spec/architecture.md.
4.4 bin, core, lint (88 + 49 + 31 files incl. mirror)
-
bin: strip generic process theatre.
write-docsis 109 lines, mostly form-filling sections plus a 15-line source provenance block; its rules fit in 25 lines.tddis about 70% textbook (RED/GREEN diagram, "good tests are integration-style", five thin references restating textbook design advice).diagnose40%,prototype50% (pixel-level UI switcher spec),grill-with-docs35%. 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. -
bin: merge
grill-meintogrill-with-docs.grill-meis 16 lines and a subset of the docs flow;grill-with-docscreatesCONTEXT.mdwhen missing, so the merged skill needs a no-write opt-out.caveman(50 lines) andzoom-out(9) are hand-invoked prompts rather than workflow skills; they are also the repo'sdisable-model-invocationexemplars inCONTEXT.md,contract.md, ADR-0020, ADR-0021, andgates.md, andinstall.shhas no path for~/.claude/commands/, so moving them means picking a new exemplar.improve-codebase-architecturedefines its glossary twice (inline and inlanguage.md; the README documents the split as intentional). Effort S. -
core:
provider-adapter-authoris 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-authoralready callsagentsmd-auditas mandatory closeout, and both route toprovider-adapter-authorin boundary clauses that must change with it. Target: oneagentsmdskill with an audit mode, adapter conversion as a step, validator about 40 lines. Needs an ADR-0012 revisit. Effort L. -
lint: delete the
lint-runneragent. Its body is "callvale-run, reformat output", which--output=JSONalready gives; it exists for backends that do not exist. It is the example boundary clause in threeagent-authortemplates and ADR-0016, so those need a new example. About 40% ofvale-configis install tables and settings lists the model can fetch from vale.sh. Keep the house-verified matrices (E100/E201,Packagesbelow 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)
-
Move or delete
docs/research/anddocs/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.mdsays "Status: Superseded";factory-integration-decisions.mdsays "Complete" and its decisions already live in ADRs, yetAGENTS.mdtells 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 threeAGENTS.mdpointers. MovingCONTROLS.mdtodocs/spec/means updating its literal path in nine or more files including the deployedgovernance.md. Effort S.Decision (2026-09-12): Keep. Same reasoning as finding 9 — these docs are intentional context for sourced work. Not proceeding.
-
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. Keepgovernance.mdas 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. -
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.mdindex with status. The rules already live ingates.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/adrbefore acting on it. -
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 offlineSKIP=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 offlineSKIP=command (the oneSKIP=-adjacent mention ingates.mdexplicitly says a different opt-out "is notSKIP="), 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 (therm -rfbehavior and theplugins/kyberforge/hooks/README.mdanecdote) staying solely indocs/spec/architecture.md. README's "Keeping the install current" and AGENTS.md's apm.lock bullet trimmed to drop the restatedapm outdated/apm update --yestiming narrative, pointing to ADR-0019 as the canonical mechanism instead. No test greps the trimmed wording (checked). -
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
629320bondocs/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. -
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 outsideCONTEXT.mdand 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
124ce6eand follow-up ondocs/simplification-audit. Independent re-verification found "most used only by gates.md/scripts/tests" overstated: 13 of 28 terms are actually referenced from model-facingreferences/*.mdfiles 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, ata6434e0): 18 terms, not 19. The "28 → 19" above is an accurate record of this finding's own commit (124ce6e) and is left standing.718c79athen 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## Languageand## RelationshipsinCONTEXT.md: 19 at124ce6e, 18 at718c79aand unchanged ata6434e0. 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 bygrill-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). -
Structure is described three ways (README layout table, architecture.md plugin table, AGENTS.md structure bullets), and
VISION.mdcarries 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.
-
Every plugin version lives in four places (five for kyberforge), plus one per skill.
plugins/<name>/apm.yml, two generatedplugin.jsonfiles, the rootapm.ymlpackages list, theexecutables.allowkey (kyberforge#1.6.2), and ametadata.versionin 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 declaredtagPattern: v{version}can never match underper_packageversioning. ADR-0006, ADR-0022,check-executables-allow-sync,skill-frontmatter, andapm pack --check-versionsall exist to police this. Proposal: one version per plugin in itsapm.yml; dropmetadata.versionand ADR-0022; letapm packderive the rest. Effort M.Partially advanced (2026-09-14): see commit
718c79aondocs/simplification-audit. Two of the four locations per plugin are gone: the twelve generatedplugin.jsonmanifests (plugins/*/.claude-plugin/andplugins/*/.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 rootapm.ymlpackages list, theexecutables.allowpin, andmetadata.versionin all 39 SKILL.md files (still unconsumed, still drifting), plus ADR-0022 and thev{version}tagPatternmismatch. -
The SessionStart hook auto-updates the install on every startup.
check-apm-current.shrunsapm outdated(network, 60 s timeout) and thenapm update --yes(300 s timeout) at every session start, rewritingapm.lock.yaml. That is why the lock file is dirty at the start of this session and whyAGENTS.mdhas to explain "commit or discard it deliberately". It is a 60-line script with a 368-line test, an ADR (0019), theexecutables.allowpin, and a sync hook behind it. For a repo that is its own source, the update belongs ininstall.shor a manualapm update, not in session startup. Effort S to remove; the design question is whether auto-update at startup is wanted at all. -
Outputs and packages for consumers that do not exist. Thecodexoutput profile generates.agents/plugins/marketplace.json(95 lines) although Codex is not a supported consumer. Themattpocock-skillsremote package entry is the only reasonapm-marketplace-checkneeds the network, and its pin is advanced by hand (ADR-0015). The.github/plugin/marketplace.jsonmirror is a legacy path (finding 2). Removing all three leaves one generated marketplace manifest (the per-pluginplugin.jsonpairs remain) and no network-dependent hook. Effort S.Done (2026-09-13): see commit
568ca74ondocs/simplification-audit. Removed thecodexoutput profile from rootapm.ymland its compiled.agents/plugins/marketplace.json(95 lines), and themattpocock-skillsremote package entry — the only remote marketplace entry, soapm-marketplace-checkandapm-pack-check-cleanno longer need network access at all. UpdatedREADME.md,AGENTS.md,docs/spec/gates.md, anddocs/spec/architecture.mdaccordingly; added one-line superseded/updated notes to ADR-0015 and ADR-0021. Left.github/plugin/marketplace.jsonuntouched — 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.yamlunaffected (marketplace.packages[]isn't part of the lockfile). Verified viaapm 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 ata6434e0): 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.jsonpairs remain" is void. All twelve were deleted in718c79a(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.jsonbeside it is untracked localapm packoutput, ignored at.gitignore:59.) - "Left
.github/plugin/marketplace.jsonuntouched … out of scope here" is void the same day:0dffff3deleted it under finding 2c, along withscripts/sync-marketplace-mirror.shand its test. The "only re-ran the sync script to keep it consistent" step above refers tosync-plugin-content.sh, itself deleted in718c79a.
- "The per-plugin
-
The release-tag mechanism guards an external contract with no known consumer.
.pre-commit-hooks.yamlexports three hooks for other repos to pin byrev: <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. -
Two.mcp.jsonfiles declare an Obsidian vault server overdocs/(root andplugins/bin/; the other five plugin.mcp.jsonfiles are empty stubs), whileAGENTS.mdforbids using an external memory system for this repo. If the Obsidian tools are unused, drop both and thereinject_mcp_serversexplanation in the bin README; the binplugin.jsonpair 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.jsonfiles deleted (the six plugin-root files and the repo-root one), and the repo-root path added to.gitignoreso a localapmrun cannot recreate it as tracked content. The bin README'sreinject_mcp_serversexplanation goes with it; theplugin.jsonpair the finding expected to regenerate no longer exists (deleted in718c79a, finding 7).What made this urgent is the substantive discovery, not the tidying: deleting the per-plugin
plugin.jsonmanifests in718c79ahad already broken MCP propagation silently.apm_cli/deps/plugin_parser.pymaps 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.ymldeclaresdependencies.mcp: [], so the supported mechanism was never used either. Proved on ref-pinned consumer clones: at the parent commit a consumer gets anobsidianserver, at HEAD it gets none, and on upgrade apm printsRemoved stale MCP server 'obsidian' from .mcp.json— which would in time have stripped the server from this repo's own tracked.mcp.jsononce the lock re-resolved. Deleting the files makes the intent match the behaviour instead of leaving a config that silently does nothing. -
pc-author/pc-run(689 lines) carry generic pre-commit documentation.hooks-by-language.md(128 lines) andfailure-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
a622200ondocs/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.md128 → 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.md133 → 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, thertk git add -u/rtk git commitfix (ADR-0023), and thepre-commit install -fwarning. Combined cut: 60 lines. Flat mirror regenerated and verified byte-identical.
7. Suggested order
- 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.
- Structural changes that need a short discussion: 14, 15, 19, 20, 23, 25, 17, 3, 5, 7, 33, 34, 36.
- 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 nativeclaude 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 commit718c79aondocs/simplification-audit. Nativeclaude plugin installsupport is dropped; the flat mirror, the twelve per-plugin manifests,sync-plugin-content.sh, its test suite,lib/marketplace-plugins.sh, and thecheck-plugin-content-syncandvalidate-pluginshooks are all deleted (245 files changed, −22,602 lines). ADR-0017 carries a superseded banner. Kept deliberately: the rootmarketplace:block and the root.claude-plugin/marketplace.json, which apm's own consumers read. (marketplace.jsonis the only tracked file under.claude-plugin/; the rootplugin.jsonbeside it is untracked localapm packoutput, ignored at.gitignore:59.) This answer is what voided finding 7's recommendation and closed §3'scheck-plugin-content-synctarget.Copilot CLI legacy path. Is.github/plugin/marketplace.jsonstill 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 throughmarketplace.json,.plugin/marketplace.json,.github/plugin/marketplace.json,.claude-plugin/marketplace.jsonin 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 at0dffff3;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, andplugins/*/skills,.../agents,.../hooksdo 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 layout718c79adeleted. 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 ownhooksamendment 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 since0dffff3while this bullet said the opposite. ADR-0012 governs only thecoreplugin's threeagentsmd-*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'sskill-audit/agent-auditandskill-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 — onedescriptioncarrying both skills' trigger phrases — not an ADR supersession. Are you open to superseding ADR-0012, for finding 24? - Granularity of git/gitea skills. One
gitskill 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 updateacceptable? - External hook consumers. Does any other repo pin this repo's
.pre-commit-hooks.yamlby tag today? If not, finding 36 defers the release mechanism entirely. Obsidian MCP. Are the Obsidian tools overdocs/used by anyone? If not, finding 37 is a pure delete.Answered (2026-09-14): not used — remove entirely. All seven
.mcp.jsonfiles are deleted and the repo-root path is gitignored; see finding 37, which also records the functional regression this uncovered (since718c79adeleted 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 thetests/directories the mirror used to strip, so a consumer installing from this branch receives 10.batsfiles across 6 skills, plus those skills' 6tests/README.mdfiles — 16 files. (Repo-wide, 17 tracked paths contain/tests/: the 10.batsand 7README.md, one of which is a template asset underskill-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.batsfiles, 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 sixapm.ymlfiles fromincludes: autoto 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.