c5d43b4d63878cb233bd1cad3b2982c02be3d512
125 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 1d40544075 |
fix(gates): hold skill versions above main's tip as well as the merge-base
Why: two branches that both bump a skill 1.0.0 -> 1.0.1 with different content merge without a conflict, and each passed the gate against its own merge-base, so main could ship two changes under one version. Implementation Notes: - check-skill-version-bump requires the pushed version to exceed both the merge-base and the main tip; failures name the baseline they missed. - Presence is read from the tree, so a blob missing from a partial clone is a read failure instead of a silently exempt "new" skill. - A leading UTF-8 BOM no longer reads as a missing version. - Version parts reject leading zeros in all three validators (check-skill-version-bump, skill-size-check, factory-audit). - New tests cover equal bumps, moved files, major/minor ordering, bad refs, unreadable blobs, mode-only changes, symlinks and tag peeling. Impact: ADR-0022 amended (reverses "not main's current tip"); gates.md updated to match, including pre-commit 4.6.1's exact ref selection. ADR: 0022 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| dd0b9233e6 |
docs: correct the version-bump gate and branch-refresh documentation
ADR-0022 and gates.md now state the merge-base baseline, the fail-closed cases, the PyYAML requirement and the multi-ref push gap (shared with check-release-needed); the new hook gets its own gates.md group. Both ADR additions follow each file's amendment format. README and AGENTS.md now say to discard a refreshed apm.lock.yaml on a feature branch, and that an .apm/ edit is live only once it is on the remote's main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| afcf477ede |
docs(adr): record the feature-branch refresh hazard in ADR-0019
The session-start refresh resolves against main, so on a branch it redeploys content the branch removed and rewrites the lock to main's commit. Documented as a consequence rather than skipped in code, since a skip would only freeze the session on an older main. Also records the re-measured refresh time (~24-26 s). Simplification audit finding 34. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| 8451169d2b |
feat(gates): enforce metadata.version bumps on changed skills at pre-push
check-skill-version-bump fails a push when a skill directory changed against its merge-base with main (tests/ excluded) without a strictly higher metadata.version than main. New, renamed and deleted skills are exempt; every plugin is covered. Recorded as a dated section in ADR-0022 and documented in gates.md. Patch-bumps the 17 skills that changed on this branch without a bump, so the branch passes its own gate. Simplification audit finding 33. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| 69119f4754 |
docs: reconcile the ADRs, gates and audit log with the shipped behaviour
Why A six-agent review of the two preceding commits found their code sound -- the differential claim holds, the published hook contract is byte-unchanged -- but their prose drifted from it in three ways: statements of fact the code contradicts, markers in a convention this repo does not use, and figures that went stale when the merge changed what they counted. Implementation Notes ADR-0025's edge-path table is rewritten around one stated doctrine: exit 0 is audited and clean, exit 1 is audited with findings or a target present but unreadable, exit 2 is that nothing was audited. Its old row 1 promised "one generic matches-neither message" for three different inputs; there are three distinct messages, and the missing-path case exited 1 until the preceding commit fixed it. Rows are added for the preflight and CDPATH changes, because a table claiming to enumerate every entry-point behaviour change reproduces its own "an earlier revision of this ADR said they were behaviour-neutral" failure if it omits any. ADR-0025 also gains a Consequences supersession record in ADR-0016's form: partially-superseded entries for 0008, 0014, 0020 and 0021, and explicit "is not superseded" entries with reasoning for the rest. Twelve ADRs are amended and it previously listed none. ADR-0008 moves from an amendment note to partially superseded. Its contract genuinely narrowed -- an agent .md outside an agents/ directory was audited before the merge and is refused now -- and ADR-0020 already recorded that the merge "reopens ADR-0008". Its detector description said "a path under .apm/agents/", the phrasing ADR-0025 rejects as wider than the script and circular; the shipped rule is a .md whose immediate parent is named agents/, at any scope. ADR-0020's amendment claimed the boundary resolver is sourced by validate-provenance.sh. It is not, and never was; only validate.sh sources it, once per mode branch. Three Home-column entries pointed at reference filenames the merge renamed, one of which now resolves to two files because its row covers skills and agents. Five ADRs opened with "Skill renamed per ADR-0025", a form this repo does not use, in the same commit that used the conventional "Amended by ADR-0025" twice. They are normalized. "Renamed" was also wrong: the BREAKING-CHANGE trailer says the skills were removed and their flows merged. SIMPLIFICATION-AUDIT.md had 2026-09-15 notes attached to headlines that were never updated, against its own convention of correcting in place with strikethrough. Every figure here was re-derived at HEAD by command, and several differed from the review's own numbers, so the notes record the basis rather than the result alone. LESSONS.md asserted the two review-time suite failures were the SIGPIPE race. The commit that fixed that race explicitly declined to claim it -- the suite was running while agents edited live config files -- so the hedge is restored. Impact No code, test or configuration change; documentation only. Suites stay 20/20 strict with 0 skipped and 374/374 bats. No gate parses ADR or gates.md content, so nothing here is load-bearing for a hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| ffcbed6c41 |
fix(tests): replace pipefail-racy echo | grep -q with here-strings
Why Two suites failed intermittently — tests/test-vale-wrap.sh case 21 and tests/test-check-release-needed.sh cases 4 and 15 — on correct output, and never when run alone. The cause is the `echo "$OUT" | grep -q P` idiom under `set -o pipefail`: grep -q exits as soon as it has an answer, bash's echo can hand a multi-line value to the pipe one line at a time, and a write after the reader is gone kills echo with SIGPIPE. pipefail then reports the writer's death, so output that DID match reads as "no match". Every observed failure had lines after its match; case 15's match is on line 1 of 6, the widest window in that file. Forced with a pause before the writer's last line, the pipe form failed 50 of 50 runs; a here-string, a match on the last line, and the same pipe without pipefail each passed 50 of 50. Unforced the rate is about 1 per 670 suite runs, which is why it read as a flaky gate rather than a bug. The failures at review time are consistent with this, but were not proven to be it: the suite was running while agents edited live config files in place, and a brief change to .vale.ini or .pre-commit-hooks.yaml would produce the same two failures. The race is real and fixed either way. Implementation Notes `grep -q P <<< "$VAR"` has no separate writer process, so there is nothing to race. It is not a retry or a sleep. 121 sites converted across 9 files, three of them scripts rather than tests: new-agent.sh, new-skill.sh and check-executables-allow-sync.sh. None ships via .pre-commit-hooks.yaml, so no external consumer pins them, and all three are single-pipeline checks whose verdict cannot change. Left alone deliberately: 14 sites whose writer is a command, not a shell builtin — they either absorb the writer's status with `|| true` or are python3 and awk, which write once at exit — and one file with no pipefail. `printf '%s'` sites differ from a here-string only by a trailing newline, which no -q verdict on a non-empty pattern depends on. tests/test-no-pipefail-early-exit-grep.sh is a static guard against new occurrences, discovered automatically by run-tests.sh. It only scans files that set pipefail, joins continuation lines, skips comments, and flags only echo/printf writers. Its first case proves the scanner can fail before its second trusts a clean verdict on the tree. A guard covers exactly the spellings its regex models, so the miss surface was measured rather than assumed. Four were found and closed: pipefail declared as `set -o errexit -o pipefail` (where the old pattern required pipefail to follow the FIRST -o, and a file-level miss skips every site in that file); a writer separated from grep by an intermediate stage; a pipeline wrapped on a trailing `|` rather than a backslash; and readers spelled egrep, fgrep, /bin/grep, `command grep` or with an env-var prefix. Segment characters exclude a bare `&` so `echo ok && other | grep -q x`, whose writer is `other`, does not false-fire. Widening surfaced 5 live sites invisible to the original scanner, all in tests/test-apm-current-hook.sh, all `echo "$out" | json_field ... | grep -q`; they are safe today only because json_field is python3, which reads to EOF and writes once. Fixtures go 4 to 12 vulnerable spellings plus near-miss negatives. Two `grep ... | head -1` sites (test-vale-wrap.sh) are the same race with a different early-exiting reader, and are fixed by absorbing the writer. The scanner deliberately does not model `head`, `sed -n 1p` or a bare `read`: most legitimate uses in this tree are already absorbed with `|| true` and the scanner cannot see absorption from pipeline text, so a high false-positive rate would be how this guard gets weakened. Heredoc bodies are scanned as code; none in the tree trips it today. Impact The bug predates the factory-audit merge: every converted site in check-release-needed and case 21 dates to |
|||
| 620f20b0fd |
refactor(kyberforge)!: merge skill-audit and agent-audit into factory-audit
Why The two audit skills carried 1,724 lines of byte-identical duplication: the ADR-0020 boundary resolver (1,061), vale-wrap.sh (526), the Vale style rules (44) and the Contributing-files parser (93). Nothing shared them — they were held in sync by a 413-line pre-push gate and its 797-line test suite. Sync-by-gate had already failed once: at |
|||
| a5962ba773 |
fix: point the HUMANS.md counterpart references at the file that exists
Why: three governance documents cited `docs/HUMANS.md`, which has never existed -- the human practitioner rules live at `docs/wiki/HUMANS.md`. One of the three is `core/instructions/governance.md`, which is `@`-imported into every session in every project, so an agent following its "read it when making decisions not covered here" pointer hit a dead path. That file was self-inconsistent: line 73 already cited the correct path while line 82 did not. Implementation notes: five occurrences corrected across three files -- `core/instructions/governance.md:82`, `docs/research/governance_principles/CONTROLS.md:5,101,106`, and `docs/ai-constitution.md:238`. Text is otherwise untouched; this is a path correction only, not a change to any governance rule. Marked the defect fixed in SIMPLIFICATION-AUDIT.md, which recorded it in two places as outstanding. Impact: no rule, gate or behaviour changes. The deployed copy at `~/.claude/core/instructions/governance.md` no longer matches the repo and stays stale until `scripts/install.sh` re-runs; it was byte-identical before this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| f91babcacd |
docs: dedupe structure and stack descriptions (finding 32)
Why: AGENTS.md's Structure bullet restated apm-install mechanics already owned by docs/spec/architecture.md:24 and README.md:55, and docs/VISION.md carried stack, framework and deployment choices for a product that lives in a separate repo. Implementation notes: AGENTS.md keeps two actionable one-liners plus pointers to the README layout table and architecture.md, preserving the session rule that .claude/skills/ and .claude/agents/ are install output and must not be edited. VISION.md's Phase 1 Architecture block becomes a one-line scope statement; the "Mobile/desktop (Phase 3)" line is dropped as an intra-file duplicate of the Phase 3 section. Impact: no behaviour change. README.md and docs/spec/architecture.md are untouched -- the finding's premise was inflated, and architecture.md had already been differentiated in a way it documents in the file itself. Refs: SIMPLIFICATION-AUDIT.md finding 32 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| 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
|
|||
| a6434e0cdb |
docs: amend ADR-0021 and close the loose ends ADR-0024 left behind
A six-agent review of today's commits found three documentation defects that the apm-only change left behind. All three are records that describe deleted machinery in the present tense; no code or gate is affected. ADR-0021 was the one ADR that ADR-0024 invalidates without carrying an amendment note -- 0001, 0006, 0011, 0013, 0014, 0015, 0017, 0018 and 0019 all got one. Its Context section still compiles a plugin description into four generated files and its Consequences section still names eight, but |
|||
| c96ca9ca0d |
fix: remove the obsidian MCP server and the unreadable .mcp.json files
|
|||
| 061bb3d5b4 |
docs: correct the apm-only change after a five-agent review
A five-agent review of |
|||
| 718c79af70 |
chore: drop the flat content mirror and native install support (ADR-0024)
apm becomes the only supported install path. The flat mirror at each plugin root existed solely so Claude Code's native `claude plugin install` could convention-scan plugin content (ADR-0017). With no native consumers, it cost ~20,000 tracked lines plus ~2,100 lines of sync tooling and ~88s of every push to guard content apm never reads — and its only automated gate, `claude plugin validate --strict`, passes on a plugin with zero content, so it could not detect the defect ADR-0017 was created to fix. Removes the mirror (213 files), the six per-plugin manifest pairs, sync-plugin-content.sh, its 1,289-line test, the orphaned marketplace-plugins.sh, and the check-plugin-content-sync and validate-plugins pre-push hooks. The root `marketplace:` block and .claude-plugin/ catalogue stay: apm's own marketplace consumers read that same file, so `<name>@holocron` short names keep working. tests/run-bats.sh now excludes .claude/skills/. apm installs from .apm/, which carries the tests/ dirs the mirror stripped, so deployed .bats files would otherwise be discovered and double-run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| 0dffff3c21 |
chore: drop redundant apm-marketplace-check and Copilot marketplace mirror (findings 1, 2)
apm-marketplace-check checked network reachability of remote marketplace refs, but finding 35 already removed the only remote package entry, so every marketplace.packages[] source is local and the hook is pure overlap with apm-pack-check-clean. apm-audit-ci was re-examined and kept as-is -- its pre-commit-config.yaml comment already carries a dated, verified justification the audit had missed. check-marketplace-mirror-sync guarded .github/plugin/marketplace.json against drift from .claude-plugin/marketplace.json. Verified against current GitHub Copilot CLI docs: Copilot's marketplace discovery already falls back through .github/plugin/marketplace.json to .claude-plugin/marketplace.json, which this repo generates anyway -- the dedicated mirror bought a discovery-order preference, not a capability. Deleted the mirror file, its sync script, its test, and the hook. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| af80d27b9b |
docs(adr): flag stale codex-profile mention in ADR-0021 context
The Context section still described .agents/plugins/marketplace.json (apm's codex profile) as an existing, unaffected generated file. It was removed today in 568ca74; point to the removal instead of leaving the text describing a file that no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| e21a5fb24c |
chore: merge dead-distribution-surface cleanup (finding 35)
Removes the codex marketplace output profile and the mattpocock-skills remote package entry per SIMPLIFICATION-AUDIT.md finding 35. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| 568ca749f0 |
chore: remove codex output profile and mattpocock-skills remote entry
Neither has a real consumer: Codex is not a supported target, and mattpocock-skills was the sole remote marketplace.packages[] entry forcing apm-marketplace-check and apm-pack-check-clean to git ls-remote on every push. Removing both drops .agents/plugins/marketplace.json (the codex output artifact) and makes every pre-push hook resolve fully offline. Updates README, AGENTS.md, gates.md, architecture.md, and ADR-0015/ADR-0021 to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| a35f5e889e |
docs(gates): trim two stale post-mortem passages from gates.md
Cut the "Verified fixed" reproduction paragraph carrying explicitly stale pre-retrofit figures, and condensed the "Current retrofit status" section's issue-#99 process narrative to the current-state facts and the commands to check them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| c8a7c9ea87 |
chore: fold skill-frontmatter into skill-size-check
skill-frontmatter was a 62-line bash script inlined in .pre-commit-config.yaml, re-parsing SKILL.md frontmatter with grep and awk to check for name/description/metadata.version fields. skill-size-check.sh already parses the same frontmatter block with PyYAML for its ADR-0020 checks, so the two checks belonged in one script. Adds a ~20-line required-frontmatter check (name, description, metadata.version as three-part semver) to scripts/skill-size-check.sh. Removes the inline skill-frontmatter hook from .pre-commit-config.yaml and deletes tests/test-skill-frontmatter.sh (366 lines). Removes 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, and updates the pre-push hook counts there. Updates fixture builders in test-skill-size-check.sh, test-adr0020-body-checks.sh, test-adr0020-targets.sh, test-adr0020-differential.sh, and test-vale-hooks-consumer.sh to carry valid metadata.version so the new check doesn't spuriously fail existing fixtures that predate it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| e647f14535 |
chore: delete the check-manifests pre-commit hook
Six pre-push hooks were validating overlapping sets of the same manifests. check-manifests (marketplace.json/plugin.json path checks) is redundant with validate-plugins (claude plugin validate) and apm-pack-check-clean, which already cover the same ground. Deletes the check-manifests hook entry, scripts/check-manifests.sh (282 lines), and tests/test-check-manifests.sh (771 lines). scripts/lib/marketplace-plugins.sh is kept — it is still sourced by sync-plugin-content.sh. Updates the now-stale check-manifests.sh mentions and hook counts in README.md and docs/spec/gates.md. The apm-audit-ci and apm-marketplace-check hooks named in the same finding are left untouched — the audit flags them as needing a separate decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| f5e4d0d082 |
docs(git): delete unused git plugin config file and its read steps
Finding 21: `config.example.json` (and the never-tracked `.claude/plugins/git/config.json` it documented) was read by git-orchestrate and git-branches but written by nothing, and the default-inference fallback (GitHub Flow, with Gitflow inferred from a `develop`/`release/*` branch) already covered the no-config case. Removed the config-read step from both, updated git-workflow's description of the orchestrator to match, dropped the now-dangling `applied_config` field from git-orchestrate's output shape, and deleted the config file and its stale example reference in docs/spec/architecture.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD |
|||
| a3e721e937 |
docs: retire the META.md guidance ADR-0022 overruled, bump touched plugins
Why: ADR-0022 made `metadata.version` mandatory in SKILL.md frontmatter, but three documents still instructed the opposite — that `version:`, `source:`, `references:` and `when:` belong in a separate META.md. That recommendation was never implemented: META.md exists exactly once in this repo, inside a vendored third-party research example, and all 39 skills now contradict it. A stale instruction that outranks nothing is worse than no instruction, because an author following it undoes the ADR. Implementation notes: - Two LESSONS.md entries deleted outright — their entire payload was the rejected fix. Two kept and rewritten: the copy-fill entry loses only its META-TEMPLATE clause, and the `model:` entry keeps the provider-extension fact and the invocation-time boundary rule, which stand on their own. - One factual error corrected in passing: the `extracted` slug entry claimed provenance is recorded in META.md. It lives in `references/sources.md` keyed by `source_keys:`, verified against validate-provenance.sh. - Both docs/notes files gain `metadata.version` in their required-field lists. Deleting the stale paragraph while leaving those lists silent would have re-created the gap. - `bin/write-docs` carried `metadata.version: "1.0"` — the only non-semver value in the corpus, and the result of relocating its old top-level `version:` without normalising it. Now `1.0.0`. ADR-0022 records the relocation it previously omitted, which issue #127 had asked it to decide. Impact: patch bumps for the four plugins whose `.apm/` content changed — bin, git, gitea, kyberforge. core and lint are untouched and stay put. Root apm.yml's `executables.allow` key and marketplace package versions move in lockstep; the marketplace release version is unchanged. Refs: #127 ADR: 0022 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeH8SCbcrCAQrtymkNuhKP |
|||
| ed8c99efbd |
fix(git): stop prefixing rtk where it rewrites the output skills parse
The #113 sweep rested on CLAUDE.md's premise that rtk either filters or passes through unchanged, so prefixing is always safe. Measured against rtk 0.42.4, that premise is false for several of the commands the sweep prefixed, and two skills were left giving wrong answers silently. Why: - `rtk git worktree list --porcelain -z` discards both flags and renders its own format. The `locked`/`lock_reason` fields git-worktrees Step 2 must emit are absent entirely, and paths under $HOME are abbreviated to `~/`. - `rtk git branch --list <name>` prints a phantom `* ` line even when nothing matches, so git-branches' stated ambiguity test — "output from both means the name is ambiguous" — reported every name as ambiguous. `tag --list` is a clean passthrough, so only one half broke. - `rtk git diff --name-only`/`--name-status` append a `Changes:` trailer to output documented as "one per line"; `--word-diff` emits none of the `[-removed-] {+added+}` markers its table describes; `rtk git log -L` truncates each line at ~72 chars, on the one command whose purpose is showing line content. - `rtk git stash pop` prints only `FAILED: git stash pop`, swallowing the conflict diagnostic and retained-entry message the surrounding prose tells the agent to rely on. Implementation notes: - Eleven sites reverted to bare `git`, each carrying its reason inline so the next sweep does not undo it. `mergetool` and `rebase -i` are reverted on clause 3's interactive limb only: the TTY defect does not reproduce — rtk filters exactly twelve subcommands and execs the rest — and ADR-0023 records that measurement rather than a convenient one. - ADR-0023 states the rule repo-wide with a third clause: a command whose output the skill parses, or which is interactive, stays bare. `plugins/git/README.md` is reduced to a pointer; its claim that gitea skills "contain no git/rtk mentions at all" was false, and its citation of `hard-rules.md` pointed at a file containing no occurrence of "rtk". - Eight gitea sites swept, all verified byte-identical passthroughs first. - `scripts/check-rtk-prefix.sh` gates clause 1. Run against main's pre-sweep corpus it reports 99 findings including every gitea site, so it would have caught the drift #113 was filed about. Impact: the gate covers clause 1 only, in shell-tagged fences and the opening span of Run cells. Clause 2 is not gateable — "Run `git switch`" and "`git switch` refuses" are the same tokens — and prose bullets are invisible to it. Both limits are recorded in gates.md rather than left implied. Refs: #113 ADR: 0023 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeH8SCbcrCAQrtymkNuhKP |
|||
| a6eedacfd8 |
fix(skill-frontmatter): check every file, scope checks to frontmatter
The hook is `entry: bash` with `args: ['-c', <script>]`. pre-commit appends filenames after the script string, so the first becomes `$0` and never enters `"$@"` — on a single-file commit, the common case, the loop body never ran and the hook reported Passed having measured nothing. ADR-0022 leans on this hook as the enforcement for a mandatory `metadata.version`, so the vacuous green was the whole gate. Implementation notes: - An arg0 placeholder absorbs `$0` so every filename lands in `"$@"`. - Checks now run against the YAML frontmatter block only, extracted with awk. The old `grep -A10 "^metadata:"` matched a `metadata:` inside a body code fence, spanned past the block into a following `source:` entry's `version:`, accepted any indentation, and missed a `version:` more than ten lines in. An unreadable frontmatter block is now an error, never a pass. - The value is asserted against three-part semver. `write-docs` carried "1.0" through the entire ADR-0022 retrofit undetected, which a presence-only check cannot catch. Impact: `tests/test-skill-frontmatter.sh` is the first test this hook has ever had. It drives the real `entry`/`args` composition read out of the config rather than a copy of the script, which is the only shape that catches the arg0 bug; against the pre-fix hook it scores 7/20. gates.md described the hook wrongly in both directions and is rewritten, with a carve-out explaining why this one stays a shell parser next to the "python3 and PyYAML are hard requirements" reasoning that argues otherwise. Refs: #127 ADR: 0022 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeH8SCbcrCAQrtymkNuhKP |
|||
| 60be7b3232 |
refactor(skills): mandate metadata.version on every skill's frontmatter
Only 12 of 39 skills carried metadata.version, and adoption tracked which plugin a skill lived in rather than any stated rule: core, gitea and lint were consistent adopters, bin and kyberforge were consistent non-adopters, git was split with one outlier. There was no documented convention, and skill-author's own bump logic was already written as if presence were conditional. metadata.version is now required on every skill. The 19 skills here that never carried one (bin, kyberforge, gitea-files) are seeded at 1.0.0, not 0.1.0 -- that value stays reserved for a skill's actual creation point under skill-author's existing convention. The skill-frontmatter pre-commit hook now fails a SKILL.md missing the field, the same class of failure as a missing name/description. Full rationale in the new ADR. The git-plugin skills that also need this field follow in the next commit, bundled with issue #113's rtk normalization since both touch the same files. Refs: #127 ADR: 0022 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeH8SCbcrCAQrtymkNuhKP |
|||
|
|
598a7c326a |
refactor(skills): retrofit the corpus to the ADR-0020 context contract (#129)
Retrofits all 39 skills to ADR-0020's description/body context contract, then fixes what six rounds of independent review found in that retrofit — including four ways the hot gate itself failed open. Closes #99, #107, #108, #110, #111, #114, #115, #120. ## The retrofit (waves 1-5) | | Start | Now | |---|---|---| | Description FAILs (>400 chars) | 26 | **0** | | Body FAILs (>900 words, body-only) | 9 | **0** | | Dangling routing targets | 2 | **0** | | `Kyberforge.CompositionNote` | 10 | **0** | | Preload tax | 21,005 chars | **~10,500** | Under the 12,000-char success criterion. Per-wave detail is on #99. ## The review fixes **The gate failed open four ways, three of them found after the retrofit shipped.** An unrecognised follower token made a dangling target vanish. A skill directory with no `SKILL.md` resolved as a valid target, so a commit could be green locally and red in a fresh clone — three existing fixtures were relying on that, one of which made the install-leak A/B pass vacuously. Then the free-standing `/name` sweep turned out to be gated on the sentence carrying a boundary marker, so route notation in any other sentence was invisible — not an ERROR, not a SUGGESTION, not an INFO — which left the documented "`/name` always blocks" promise false from a second direction. All four fixed and pinned. **Two checks were silently not running.** `validate-provenance.sh` checks 7-8 were dead across nine skills. Waking them exposed a deeper problem: they assume `Research doc:` names a source index, but 30 of 121 entries point at topic content documents, so every new check-7 INFO was a false positive and check 8 was saved from a false-FAIL flood only by an *unannounced* skip. Checks 7/8 are now scoped to source indexes and every skip announces itself (#121). **The retrofit's own anti-goal, four times.** ADR-0020 warns that a blunt gate gets satisfied by deleting content rather than relocating it. `diagnose` and `skill-audit` relocated prose and then read it unconditionally; `prototype` and `vale-config` deleted rules outright that survived nowhere. All four addressed. ## Verification - `bash tests/run-tests.sh --strict` — 24 suites, 0 skipped, 0 failed - `bash tests/run-bats.sh` — 325 tests, 0 failures - `pre-commit run --all-files` — 17/17 - `pre-commit run --hook-stage pre-push --all-files` — 16/16, with `apm marketplace check` and `apm pack --check-clean` run against the remote, not skipped - `scripts/skill-size-check.sh` over all 39 skills — rc 0, 0 ERROR/FAIL, SUGGESTION-only - Preload tax measured at **10,498 chars**, max description 390 — both inside budget - Every new test proven non-vacuous by a deliberate mutation of the behaviour it covers **Per-commit sync, stated accurately:** the ten commits from the latest review round each pass `check-plugin-content-sync` in isolation, verified by checking each out in a detached worktree with a clean between. The earlier gitea window (`dfacf05..bedbd1d`, nine commits) does **not** — its mirror was regenerated in one batch at `bbc7300`. An earlier revision of this description claimed the property held for every commit; it does not, and a bisect through that window lands on a red commit. **Squash-merge** to collapse it, or accept that this range is not bisectable. ## Version bump Six plugins and the catalog take a **patch**, not a minor. The branch is **89 commits — 40 `fix` / 30 `refactor` / 12 `docs` / 5 `chore` / 2 `test` — zero `feat`, zero `!`, zero `BREAKING CHANGE`** — and adds no skill, agent, command or hook. (Two earlier revisions of this section cited a stale histogram, most recently 78 commits; the figures above are measured at HEAD.) Both rules this repo ships (`forge/references/version-bump.md`, landing in this PR, and `git-commits/references/conventional-commits-spec.md`) make that a patch, and the catalog set is unchanged at 7 entries. Not settled by that: four published files were removed from the installed tree, three moved, and `caveman` gained `disable-model-invocation`, retiring its old triggers. Under a strict reading those are major-class and currently ship under `refactor:` with no marker. Whether the deployed skill surface is a public contract is written down nowhere — worth deciding, but it outlives this PR. ## Deliberately not in scope #112 (cherry-pick ownership, now resolved in favour of `git-commits`), #113 (`rtk git` normalisation), #116 (research fan-out), #101 (audit-skill merge), #122 (non-spec skill-root files), #123 (no PRD producer) stay open. #117 is the one worth reading: the contract's remedy is to move prose into `references/`, which is exactly where neither the size gate nor Vale looks — and the blind spot is wider than #117 currently records, since there is no root `.vale.ini` at all, so every ADR, `CONTEXT.md` and `README.md` is unlinted too. That blind spot let this branch carry two `level: error` `Kyberforge.SentenceOpenerThereIs` violations into `references/` files it created — `provider-adapter-author/references/provider-matrix.md:31` and `agent-audit/references/finding-criteria.md:95`. Both are reworded in `afadaae`, confirmed by routing each file through the audit's own `vale-wrap.sh` (1 error each before, 0 after). Five further occurrences sit in `references/` files already on `main`; those are the pre-existing corpus and stay with #117, which is the real fix. Also unfixed and not this PR's: `apm install` appends a duplicate `SessionStart` entry to `.claude/settings.json`, so a fresh clone cannot get pre-push green without an edit AGENTS.md warns against. Reproduces identically on `main`. Co-authored-by: Defame1297 <gitea@rkdr.net> Reviewed-on: https://git.dev.rkdr.net/Defame1297/holocron/pulls/129 Co-authored-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net> Co-committed-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net> |
||
| d42f6368fe |
fix(docs): correct claims the first fix round asserted without measuring
Why: two blind verifiers re-ran the five preceding commits and found four defects of the same class this branch exists to close -- a confidently stated measured claim that does not survive re-measurement -- this time inside the fixes themselves. - AGENTS.md:41 still carried both phrasings |
|||
| c68e864159 |
docs(spec): correct eight claims the trim carried into the new files
Why: the relocation into gates.md and architecture.md moved text faster than it
verified it, and a review found eight assertions that do not survive contact
with the repo.
- gates.md cited docs/research/examples/, which does not exist; the tree is at
plugins/kyberforge/docs/research/examples/. The same wrong string survives at
ADR-0013:98, where it originated, and is tracked separately.
- gates.md's description-budget arithmetic was built on 23,427, a figure
ADR-0020 measured at
|
|||
| c7ba3d2ccf |
chore(apm): retire skill enumeration from published descriptions
Why: enumerating a plugin's skills in its published description has now failed three times in four days, the third time inside the correction for the second. |
|||
| 4d336bbf35 |
docs: stop the preloaded instruction set asserting machine state
Why: four defects in the files every session pays for, all introduced or left behind by the trim. AGENTS.md told agents the `<plugin>:` form still resolves "because user-scope native installs were left enabled on purpose", and that a working namespaced call "is not something to fix". That premise is false on this machine: installed_plugins.json is empty, no enabledPlugins key exists in ~/.claude.json, and ~/.apm/marketplaces.json is empty. ADR-0018 already reversed itself once on this exact claim (Correction 2026-08-14) using that same enablement as its evidence, so flipping the assertion again would be the third revision in three. Both files now assert nothing about install state at all, which removes the flip-flop surface instead of re-aiming it. The other three are guard-rails whose instruction survived the trim while the caveat that made it safe did not: - The run-tests.sh line omitted --strict, so it named the one invocation that reports SKIPPED rather than failed when a dependency is missing. gates.md records this gate going green having verified 15 of 17 suites on a vale-less PATH. .pre-commit-config.yaml:70 already uses --strict for that reason. - The .claude/settings.json prohibition lost its ADR-0019 exception, so an agent applying it literally would strip apm's own merged SessionStart entry and create the drift the rule exists to prevent. - LESSONS.md still routed graduated rules to CONTEXT.md's Principles section, which this branch deleted. Implementation notes: the six terms the trim dropped while AGENTS.md still claimed CONTEXT.md glosses everything -- authoring root, content mirror, apm package, output profile, near-miss, vacuous green -- are restored as one-line entries per CONTEXT-FORMAT.md, sourced from architecture.md, gates.md and skill-audit's description-quality.md rather than reworded. ADR-0018 gets a third dated note recording the observation and the fact that the state has now been described two ways, and its stale user-scope inventory is replaced by a pointer to it; the decision it records is untouched. LESSONS.md:3 carried the identical stale claim as :5 and is fixed with it. Impact: preloaded context is now free of assertions about machine state. Refs: #105 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmFqzpuExLJv3m114XVE9w |
|||
| b0ef503485 |
docs: correct plugin scope, hook ordering and the setup gap
Why: the review found architecture.md's plugin table and README's plugin list
were both written by enumerating what happened to be in each plugin, so both
went stale immediately — README credited `git` with pull requests it has no
skill for, and both omitted `pc-author`/`pc-run`.
|
|||
| bd2bf667c5 |
docs: restore two guard-rails the trim dropped
Why: |
|||
| ba7cec7672 |
docs(adr): amend ADR-0007 and ADR-0015 for the CONTEXT.md trim
Why:
|
|||
| b9c7762463 |
docs: trim CONTEXT.md to a glossary and demote VISION.md
Why: CONTEXT.md is an instructed every-session read at 33,799 bytes (~8,450
tokens), and CONTEXT.md:17 layered docs/VISION.md on top. After the AGENTS.md
trim, that left ~9,650 tokens of instructed reads sitting behind a file whose
own format contract — grill-with-docs/CONTEXT-FORMAT.md:43, "Keep definitions
tight. One sentence max." — it was not following. 22 glossary entries averaged
190 words; the top four were 55% of the file, and one ("Vale audit prefilter")
was 1,444 words of pre-commit forensics filed as a definition.
Implementation notes:
- CONTEXT.md rewritten to CONTEXT-FORMAT.md's prescribed template: ## Language
(five clusters, 22 terms with _Avoid_ aliases), ## Relationships,
## Example dialogue, ## Flagged ambiguities. 33,799 -> 9,762 bytes.
- Vale prefilter forensics moved to docs/spec/gates.md under nine subsections:
the two-copy style layout, vale-wrap.sh and the Vale 3.15.2
text.frontmatter.description limitation, the --config argv defect (E100),
glob scoping, the rule inventory, and 0-files-means-NOT-RUN.
- Instruction-file format and the bidirectional-reference principle moved to
docs/spec/architecture.md, the latter flagged as aspirational: no instruction
file carries a `when:` field, only 2 of 39 skills do, and the reference
scanner script it names does not exist.
- Added a per-plugin scope table to architecture.md so "which plugin owns this
skill" survives the trim, and corrected its claim that the content index
lives in providers/claude-code/CLAUDE.md — that file is now two import lines
and the index sits in core/AGENTS.md.
- "Repo and Gitea are the only source of truth" moved from CONTEXT.md's
Principles into AGENTS.md session rules; it was operative guidance in a
glossary. docs/VISION.md and LESSONS.md added to the on-demand list.
- The apm-consumed install entry (432 words) was deleted outright: all four
consequences are already carried by README.md, ADR-0018, gates.md and
AGENTS.md.
- ADR-0013, ADR-0015 and ADR-0017 each pointed at CONTEXT.md sections that
moved; amended with dated supersession notes rather than left dangling.
Impact: the every-session set drops from ~15,700 to ~4,140 tokens across both
passes (-74%). The deleted "lint plugin" entry was also stale on a safety
claim — it said lint-runner inherits Edit, but ADR-0016 restored
`disallowedTools: Edit, Write, NotebookEdit` and CONTEXT.md was never updated.
Refs: #99
|
|||
| 1929ffd2da |
docs: slim AGENTS.md to per-session context only
Why: AGENTS.md is preloaded into every session via CLAUDE.md's @AGENTS.md import, so every word is a tax paid whether or not it is relevant. It had grown to 3,371 words (~4,550 tokens), 75% of it a single "Setup and testing" section that was mostly neither: roughly 1,600 words were gate forensics — why a hook is shaped the way it is, what was tried and rejected, verification numbers — and only ~390 words were genuine one-time setup. Implementation notes: split three ways by audience rather than by heading. One-time setup, prerequisites and test commands move to a new root README.md, which did not previously exist. The gate forensics move to a new docs/spec/gates.md, preserving every constant, hook ID, error string and tried-and-rejected note (policy.fetch_failure_default, the removed fallback frontmatter reader, extending skill-size-check's files: pattern to agents, tidying .claude/settings.json out of the pretty-format-json exclude list) — these exist to stop settled decisions being re-litigated, so none were dropped. AGENTS.md keeps only what applies every session, at one clause of rationale per rule. The .apm/-mirror section collapses to a pointer because docs/spec/architecture.md already covers it in full. The hot ADR-0020 gate counts stay in AGENTS.md deliberately: they block any skill edit today. Also corrects docs/spec/architecture.md's stale claim of 19 ADRs; ADR-0020 landed without updating the count. Impact: per-session preload drops from ~4,550 to ~1,116 tokens, a 75% cut. agentsmd-audit passes: secrets clean, drift clean, 4 informational findings for sections deliberately relocated to the README. |
|||
| 54d7bd80ba |
docs: rule host built-ins out of the routing target universe
Closes the second open design decision on PR #103. The `/compact` finding was recorded as a false positive needing an allowlist or a suppression mechanism. It is neither: the routing universe is the apm marketplace, so a target either resolves to a skill or an agent or it does not resolve, and `/compact`, `/clear` and `/init` are Claude Code slash commands with no counterpart in Copilot CLI or Codex. `.apm/` source compiles for all three, so a vendor-neutral description routing to one is a portability defect and the hard FAIL is a true positive. An allowlist was rejected for a concrete reason, not a stylistic one: it answers a different question ("does this exist on some host?"), it cannot answer that portably from a single source file, and it goes stale the next time a host ships a command — reintroducing the same-commit-two-verdicts failure ADR-0020 already closed for deployed trees. Nothing is blocked today: zero of the 43 descriptions name a host built-in, and an author who needs to mention one writes it un-slashed, which is not route notation and carries no routing claim. Recorded in ADR-0020 and in both author-facing contract references, so the next agent reads the decision rather than "fixing" the gate. ADR: 0020 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| 75a13c82f6 |
fix(kyberforge): scope corroboration to a real sentence boundary
A prose-form routing target blocks a commit only when its own sentence names another target that resolves. That makes the sentence splitter part of the ADR-0020 contract rather than an implementation detail, and the naive "period, space, capital" rule got it wrong in both directions: - OVER-SPLIT: `e.g. "..."` is not a sentence end, but the quote looks like a start. The clause was cut in half and the corroborator stranded on the far side, so a genuinely dangling target silently demoted to SUGGESTION — a measurement taken and then discarded, the vacuous-green shape this gate exists to prevent. Seven such splits are live in the current corpus. - UNDER-SPLIT: a sentence opening with a code span or a lowercase skill name was not seen as a start, so two sentences merged and a resolving target vouched for an unresolvable one it never stood beside — a hard FAIL with no escape hatch, which is the exact failure corroboration was added to prevent. The splitter now excludes the five abbreviations that occur in routing prose and admits a backtick or lowercase letter as a sentence opener. Applied byte-identically to all three copies of the shared resolver. Verified zero-delta against the corpus: 37 ERROR / 58 SUGGESTION / 2 dangling before and after, findings byte-identical. The exposure this closes is to the descriptions #99 is about to rewrite, not to the ones already measured — which is why the deferral reason recorded on PR #103 ("can move the documented corpus counts") does not hold and the fix lands here rather than after the retrofit. Three regression tests, one per direction plus the backtick opener, each proven non-vacuous by reverting the splitter alone and watching it go red. Refs: #99 ADR: 0020 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
|||
| 79c9089122 |
docs: make the resolution contract match what the gate actually does
Both AGENTS.md and ADR-0020 said deployed .claude/.agents trees are consulted "only
when no authoring root exists". That stopped being true in
|
|||
| 64ffb9f35a |
docs: make ADR-0020 match what actually shipped, and record what did not
The ADR was written against base commit `f9b919d` and then not updated as the implementation moved, so several of its numbers were measuring one thing and being read as another — the exact conflation the ADR exists to stop, reproduced inside it. Corrections, all reproducible now that each figure states its method: - The preload tax is 23,427 chars / ~5,900 tokens, not 23,612 / ~6,200. - `MAX_WORDS=2770` is a density proxy for the agentskills.io ~5,000-token ceiling, not "2× p90". Neither percentile reaches it: 2× the body-only p90 is 2,698 and 2× the whole-file p90 is 3,052. Reading it as a percentile pairs a whole-file gate against a body-only distribution. - `apm-workflow` is a 421-word body; 554 is its whole-file count. `skill-author` and `agent-author` were 2,623 and 2,582 body words — 2,760 and 2,758 whole-file, which is where "within twelve words of the gate" comes from. Two numbers for one file is the point, and only one of them is what either gate measures. - Every `file:line` citation now says it resolves against `f9b919d`, since this change rewrites most of the cited files. Three things the ADR asserted that no validator implemented are now filed by tier in an exhaustive enforcement table — deterministic, prose-pattern, or auditor judgment — because a rule filed under "Enforcement" that nothing enforces is the failure mode this ADR is most exposed to. The Gotchas entry count moves to SUGGESTION to match the script; the paraphrase FAIL is marked as an auditor's, since semantic equivalence is not pattern-matchable. Two gaps recorded rather than quietly left: - The agent body-gate exemption lives in `agent-audit`'s validator and in the `skill-size-check` hook's `SKILL.md`-only `files:` pattern — *not* in `scripts/skill-size-check.sh`, which measures whatever path it is handed and today reports 900-word body FAILs on `git-orchestrate` (933), `gitea-orchestrate` (1,199) and `apm-orchestrate` (1,080). Agents escape by file pattern, not because the script knows the difference, so widening that pattern would silently enforce a gate this ADR declines to set. - The `skill-audit`/`agent-audit` merge is deferred to #101. This change made the split deeper, not shallower: the dispatch retrofit took them from 3 and 4 reference files to 7 and 8, and their two same-named `description-quality.md` files now differ on 100 of ~120 lines after normalising skill/agent. The merge reopens ADR-0008 and touches every call site in `skill-author`, `agent-author` and `forge`, so it is its own change. #100 carries the dangling-target fixes. AGENTS.md and CONTEXT.md take the same corrections plus the two live setup changes: PyYAML is now a hard requirement rather than an optional accelerator (a fallback that mis-parses an unfamiliar scalar shape reports a clean pass on a file it never measured), and `.claude/settings.json`'s `pretty-format-json` exclusion is documented as load-bearing rather than as a tidy-up candidate. LESSONS.md's autofix entry is corrected on its own provenance, which it got wrong in both directions. `git log --date=iso` puts the introducing commit at 18:47 and the fix at 21:54 — three hours, not "weeks" — and `git branch -a --contains` puts the introducing commit on this branch only, not on main. It was manufactured inside the same PR that diagnosed it. The added lesson is that "pre-existing" is a claim about history and history is queryable: a defect found while working on a branch feels inherited, and the feeling is not evidence. Refs: ADR-0020, #99, #100, #101 |
|||
| 4a5c3c0cff |
feat(kyberforge): enforce the ADR-0020 context contract for skills and agents
Skill name+description pairs are preloaded into every session, costing ~6,200 tokens across 39 skills before any skill is invoked. The authoring rules mandated that growth: skill-author:104 and description-quality.md:21 both required padding, while skill-author:102 (the deflating rule) had no FAIL condition behind it. Gates (blocking, no baseline file): - description 250 chars SUGGESTION / 400 FAIL, measured on the folded YAML value - body-only 600 words SUGGESTION / 900 FAIL, independent of the unchanged whole-file 2770-word / 500-line spec backstop - every boundary-clause routing target must resolve to a real skill or agent; catches skill-improve, neuledge-context and gitea-labels - agents take the description gates but deliberately no body gate; a test pins that absence Vale: DescriptionOpener widened to ^This\b, new CompositionNote rule banning architecture notes from descriptions. 10 hits, 0 false positives. Kyberforge's own four skills retrofitted: descriptions 3,364 -> 938 chars (-72%), bodies 8,306 -> 2,487 words (-70%), all via the apm-workflow dispatch pattern. Fixes the skill-improve dangling route and the agent-author misroute to manual review. Also fixes a pre-existing false positive where any line-initial 'read ' was flagged as interactive input, which had already caused two scripts to be rewritten around it. Refs: ADR-0020 |
|||
| 1c6eababb0 |
docs(adr): set a context contract for skill and agent authoring
Every installed skill's name+description is preloaded every session - 23,612 chars (~6,200 tokens) across 39 skills. The authoring rules optimised for triggering reliability with no counter-pressure on size: skill-author:104 and description-quality.md:21 both mandate padding, while skill-author:102 (the rule that would deflate it) is judgment-only and absent from description-quality.md's FAIL conditions. ADR-0020 sets the description shape (trigger + one capability + boundary), two-tier size gates sitting below the unchanged agentskills.io ceilings, a mandatory dispatch pattern for multi-branch bodies, a Gotchas constraint, an agent-side delegation check, and invocation as a design axis. Gates ship blocking with no baseline. Adds five CONTEXT.md glossary entries: preload tax, skill context contract, dispatch body, hand-invoked skill, delegation discipline. Refs: ADR-0020 |
|||
| c9fe2e8ab2 |
docs: correct the namespace claim and record the hook fixes
AGENTS.md and CONTEXT.md asserted that the `<plugin>:<skill>` form "no longer resolves here". It does: ~/.claude.json still enables core, git, gitea, kyberforge and lint at user scope, which ADR-0018 left in place deliberately. Both names are live at once, so a working `gitea:gitea-prs` is the user-scope copy answering — not evidence that the apm install is broken and not something to "fix". ADR-0018 contradicted itself on this, claiming every namespaced reference went stale while its own "User scope is untouched" consequence said otherwise; recorded as a dated correction alongside the existing one. Bare names stay the documented default. Five stale pre-push hook counts updated for the new check-executables-allow-sync gate: 13 -> 14 repo-defined hooks, 15 -> 16 reported by pre-commit, eleven -> twelve passing offline. The gate reads two local manifests and makes no network call, so the SKIP pair for offline pushes stays exactly two. "Four pre-push hooks shell out to apm" is unchanged and still correct — the new hook parses YAML directly. ADR-0019 gains the timeout arithmetic, the singular/plural failure and why mocking every apm call hid it, and a consequence recording that the trust gate is keyed on version rather than content: an edit to a hook script landing on main deploys and executes unattended on the next session start, since the dependency is unpinned and the hook runs `apm update --yes`. That is accepted, not overlooked, but it is why the gate should not be read as a supply-chain control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT |
|||
| 3bfdf58960 |
chore(release): kyberforge 1.5.0, marketplace 0.4.0
Why
---
The SessionStart freshness hook is a new kyberforge feature and was committed
without a version bump. It also cannot ship unbumped: apm's executable-trust
allow key is version-pinned, so `kyberforge#1.4.1` would have stopped matching
the moment the package version moved, silently blocking the very hook the entry
exists to authorise.
Implementation notes
-----------------
kyberforge 1.4.1 -> 1.5.0 (MINOR, new feature); marketplace and root manifest
0.3.4 -> 0.4.0, following the convention in
|
|||
| dee56c506a |
feat(kyberforge): refresh the apm install at SessionStart, not at push
Why --- ADR-0018 left deployed skills tracking the remote default branch with nothing watching for drift. The mechanism that was supposed to cover this, scripts/git-hooks/post-push, could never have worked: git has no client-side post-push hook. install.sh copied it into .git/hooks/ so it looked installed, and it had never once fired. Issue #78 reported it as skipping the gitea plugin; it was skipping everything. Refreshing on push was also the wrong shape. Your install goes stale when someone else merges, so a push of your own is neither necessary nor sufficient for staleness to have occurred. Implementation notes -------------------- kyberforge ships a SessionStart hook (startup matcher only) that runs `apm outdated`, and when anything is behind runs `apm update --yes` and returns reloadSkills:true so the running session picks up redeployed content. It exits silently with no apm.lock.yaml present, which keeps it inert for hosts that installed this plugin natively rather than through apm. Two findings drove the wiring, both verified rather than assumed: - apm resolves ${CLAUDE_PLUGIN_ROOT} against the installed package root, and `apm pack` keeps only *.json from .apm/hooks/. A .../hooks/<script> reference therefore points into the generated mirror where the script does not exist — apm reports "Hook script not found" and deploys a hook aimed at nothing. The reference must be .apm/-relative, and a test pins it. - apm's executable-trust gate is OFF unless apm.yml carries an `executables:` block; until now every hook, bin and MCP primitive a dependency shipped would have deployed unprompted. Root apm.yml now enables it. The allow key is version-pinned by apm's design, so a kyberforge version bump silently blocks the hook until the key is bumped too — called out in the block and the ADR. Also corrects ADR-0018 and AGENTS.md, which named `apm install` as the refresh command. It is not: `apm install` deploys from apm.lock.yaml's pinned commit and does not re-resolve refs. `apm update` does. Impact ------ Session startup costs ~0.7s when current and ~10.4s when six packages are behind. Auto-refresh rewrites apm.lock.yaml, so an unexplained modification to it after opening a session is expected; the emitted notice says so. .claude/settings.json stops being exactly {"hooks": {}} once the hook lands there — the merged entry is apm's own output, and the rule that nothing repo-authored goes in that file is unchanged. .claude/hooks/ and the .claude/apm-hooks.json sidecar are gitignored install output. The hook cannot install itself: dependencies resolve from the remote, so it takes effect only after this merges and `apm update` runs once against the new default branch. scripts/git-hooks/ is now empty. install.sh's copy block is kept and test-git-hooks-install.sh synthesizes its own fixture, so the mechanism stays tested without requiring a dead hook to exist. ADR: 0019 Refs: #78 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT |
|||
| 2e8732a8e5 |
build(apm): consume holocron plugins through apm instead of plugin install
Why:
The repo published apm packages but consumed them the old way — `claude plugin install
<name>@holocron`, six plugins enabled per project. Dogfooding stopped one layer short of the
install tooling kyberforge itself ships.
Implementation notes:
- Root apm.yml declares the six packages as dependencies.apm git+path objects against the
holocron remote. Object form over `<name>@holocron` aliases on purpose: an alias first needs
`apm marketplace add`, which writes to ~/.apm/marketplaces.json — user scope, absent on a fresh
clone. Unpinned against the default branch, matching the autoUpdate the native install had.
- apm.lock.yaml is committed; .claude/skills/, .claude/agents/ and apm_modules/ are gitignored
regenerable install output. Committing the deployed skills would add a third mirror of content
ADR-0017 already governs two copies of.
- .mcp.json is generated by apm from plugins/bin/.mcp.json, so the obsidian MCP server survives
the switch.
- .claude/settings.json is reduced to {"hooks": {}}. apm replays the install into a scratch tree
and diffs, so any repo-owned key there is permanent drift that fails apm-audit-ci. Nothing was
lost: enabledPlugins was empty after the uninstall and the only hooks entry was PreToolUse: [].
- tests/run-bats.sh and tests/run-tests.sh exclude apm_modules/. It holds a full copy of every
plugin, and a copied .bats file resolves its helpers against the dependency root rather than
this repo — 334 tests, 167 failures before the exclusion.
Impact:
Skills are now unnamespaced — `git-commits`, not `git:git-commits` — because apm deploys plain
project skills with no plugin to prefix. AGENTS.md, CONTEXT.md and docs/spec/architecture.md are
updated accordingly. Root apm.yml now declares dependencies, which arms apm-audit-ci's
lockfile-exists check for the root manifest. External consumers are unaffected: the marketplace
manifests are untouched and `apm pack --check-clean` stays clean. Project scope only.
ADR: 0018
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
|
|||
| cf625229f7 |
fix(scripts): detect the .apm/ content the mirror loses silently
apm's bundle exporter drops symlinks entirely, so a symlink under .apm/ never
reaches the mirror -- and no gate could see it, because every existing check
diffs the live mirror against a bundle-derived copy and both sides lack the
file. It is an absence with nothing left to mismatch against, the only class of
.apm/ content that vanishes without a trace. check_apm_symlinks reads the .apm/
source tree, where the loss is visible, and fails both modes. Reported rather
than resolved: dereferencing would make a real sync emit content the bundle does
not contain, which is the reimplementation ADR-0017 rejects.
--check --all could also pass having verified fewer plugins than the marketplace
lists: a plugin whose .apm/ had gone was SKIPped rather than counted, and the
earlier floor only caught zero. The count is now checked against the marketplace's
own local-package list. There is no exempt state -- ADR-0015 makes .apm/ the sole
authoring source for every local plugin, so a listed plugin without one is drift.
On the Copilot hooks gap, the decision is to document, not implement. Copilot
declares no hooks path and apm emits none, which looks like the mcpServers case
-- but that exception holds because .mcp.json is one host-agnostic format both
ecosystems read, so a pointer to it is true whatever it contains. Hooks have no
shared format: Claude expects nested matcher groups under PascalCase events,
Copilot requires version: 1, camelCase, and a bash/powershell split. apm merges
.apm/hooks/*.json into exactly one file, at Claude's convention path. A pointer
would assert a Claude-shaped file is Copilot-shaped -- an incomplete manifest
traded for a wrong one -- and it is not inert today either, since {"hooks": {}}
lacks Copilot's mandatory version key. A test pins the decision, so restoring the
pointer fails until someone confronts the schema mismatch.
Tests: 77 -> 92 assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
|
|||
| f4bb1cf4e5 |
docs: finish reconciling the agent write fence across the toolchain
The previous round taught agent-audit's validator to permit disallowedTools but left the skill that writes agents still forbidding it, in six places. Running agent-author on any of the three fenced orchestrators would have stripped the fence, and nothing would have caught it: the validator's allowlist is a permit list, so an absent field passes. The template was the worst of them, since its comment is copied verbatim into every new plugin-scope agent. Where a list had to be restated it is now a pointer to field-inventory.md's apm-agent-allowlist instead -- the same data validate.sh reads -- because a roster copied into a template goes stale one step further out than the roster itself. Where the text has to teach something it teaches the shape rule rather than the exception: tools is an allowlist whose vocabulary differs per harness, so verbatim copy makes one value wrong on one target; disallowedTools is a denylist, where an unrecognised name denies nothing, so the worst case is a missing fence rather than a wrongly granted capability. ADR-0016's amendment claimed an unrecognised key is inert on Copilot while the same ADR's Context says that behaviour is unconfirmed by research -- asserting as settled the exact thing it flags as unknown, and justifying it with apm's compile-time behaviour, which says nothing about Copilot's runtime. It is rewritten into labelled tiers: confirmed for Claude Code with citations, inferred by analogy for Copilot with the analogy's limits stated, unverified where it is unverified, and the residual risk accepted explicitly with its blast radius. It also no longer claims to restore a write sandbox: the denylist does not deny Bash, which these agents inherit and legitimately need. docs/hooks.md called the old root hooks.json a stale sync artifact -- it was added in the plugin's creating commit and pointed at by main's Copilot manifest -- and claimed both ecosystems now resolve hooks/hooks.json. Copilot does not: its hooks field has no default and no compiled manifest declares one, so it resolves nothing. Recorded as the gap it is, with re-injection noted as a follow-up rather than asserted away. Its event list is marked partial. Also: new-agent.bats asserted a hardcoded four-field allowlist and would have rejected a scaffolded agent carrying the field the ADR now blesses; it reads field-inventory.md too. And ADR-0016's premise that Claude's tools: is space-separated was wrong -- it takes a comma-separated string or a YAML list. The incompatibility with Copilot is the vocabulary, not the punctuation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT |
|||
| 430f46b8e8 |
docs: correct the claims this review found false
AGENTS.md told an offline agent to push with SKIP=apm-marketplace-check and asserted that hook was "the only one whose failure mode is 'no network'". Running all 12 pre-push hooks under a network namespace shows two fail, for one shared cause: apm-pack-check-clean resolves the same remote entry. An exact pin does not remove the ls-remote, so both hooks are named now. AGENTS.md also said everything in a plugin root except .apm/ is generated. Plugin roots carry hand-authored README.md, docs/, bin/, sources.md and .mcp.json, so an agent would hunt for an .apm/ source that does not exist or refuse the edit. The rule is positional: immunity belongs to the plugin root, and anything inside a mirrored directory is still rm -rf'd. ADR-0017 said apm strips a hooks field. The real loop is (agents, skills, commands, instructions) -- hooks absent, instructions never mentioned -- and it can never fire, because synthesize_plugin_json_from_apm_yml only emits the eight identity fields. The decision stands; the mechanism was overstated. Its mcpServers amendment is rewritten for the pointer payload and now records the real reason: inlining bypassed apm's credential sanitizer. ADR-0015's owner.email and version-pin passages are corrected against the apm source, and ADR-0016 gains the disallowedTools amendment. agent-audit's allowlist is data, so it gains disallowedTools too -- the ADR and the validator that enforces it had come apart. architecture.md described a root CLAUDE.md that imports two files (it imports one, plus an RTK block) and pointed at an ADR index that does not exist. Seven skill READMEs listed tests/ files the mirror strips, promising installed users files their install lacks; those rows are marked source-only, with the depth-4 template tests explicitly called out as surviving. And plugins/kyberforge/hooks/README.md, deleted during the conversion and preserved nowhere, is restored to a path the mirror does not own -- verified by running a sync against a scratch copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT |
|||
| 013b913bd4 |
docs: correct claims the apm conversion left false
Documentation asserted things the code no longer did, which is how several of this round's defects survived three review passes. - `docs/spec/architecture.md` still described the pre-APM model — "each plugin has a `plugin.json` manifest" — when no such file exists. AGENTS.md routes agents here for the current structure, so it was live drift, not archive. Also cited ADR-0012 where ADR-0003 is meant. - AGENTS.md never said `.apm/` is the sole hand-edited source, while the generated mirror is byte-identical with no marker — an agent reading only AGENTS.md would edit the mirror and find out at pre-push. It also omitted that `apm` and `jq` are now required to push, unlike the `vale` bullet that exists for exactly that reason, and understated the pre-push gate. `apm marketplace check` hard-fails offline and `--offline` does not help, so the `SKIP=` escape hatch is documented rather than new machinery built. - ADR-0017 carried hardcoded line numbers that drifted twice within one PR, and said `mcpServers` reinjection was real-syncs-only where the script's own header says both modes. Line numbers dropped rather than corrected. - ADR-0015 asserted issue #90 was closed. It is open. Reworded to defer to the issue's own state rather than depend on someone remembering to close it. - ADR-0014 said both audit skills pass `--config`; both SKILL.md files say pass none, and passing one fails with E100. The ADR was wrong. - ADR-0007 cross-referenced an archived ADR-0011 whose number was later reused. Disambiguated with a note — renumbering a published ADR rewrites an immutable record and breaks every existing citation. - CONTEXT.md claimed `lint-runner` is report-only via a missing `Edit` tool. Plugin-scope APM agents cannot express `tools:` (ADR-0016), so it is report-only by instruction now; ADR-0016 accepted that regression but CONTEXT.md was not updated. - Recorded two undocumented consequences of the conversion: `displayName` dropped from all six compiled manifests and `owner.email` from the marketplace, and `mattpocock-skills` silently version-pinned and manually maintained. - Wrote the version-bump policy issue #90 specified but never delivered. Its previous carrier, `marketplace-author`, was deleted in this PR, leaving the per-package rule documented nowhere. - README fixes: kyberforge listed skills belonging to the `git` plugin and a deleted hooks path, `bin` advertised an agents directory it never had, and the docs index listed files that do not exist. Refs: #90 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT |