Compare commits
7 Commits
de84d1b677
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 68e08c2413 | |||
| d42f6368fe | |||
| c68e864159 | |||
| c7ba3d2ccf | |||
| 4d336bbf35 | |||
| 36596598ef | |||
| b1ea14df3e |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "holocron",
|
||||
"description": "AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.4.4",
|
||||
"version": "0.4.5",
|
||||
"owner": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
@@ -17,22 +17,22 @@
|
||||
},
|
||||
{
|
||||
"name": "bin",
|
||||
"description": "Cross-cutting workflow skills not yet split into a focused plugin — research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.",
|
||||
"version": "1.1.4",
|
||||
"description": "Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.",
|
||||
"version": "1.1.5",
|
||||
"category": "Utilities",
|
||||
"source": "./plugins/bin"
|
||||
},
|
||||
{
|
||||
"name": "git",
|
||||
"description": "Skills for working with Git — conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.",
|
||||
"version": "1.3.4",
|
||||
"description": "Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.",
|
||||
"version": "1.3.5",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/git"
|
||||
},
|
||||
{
|
||||
"name": "gitea",
|
||||
"description": "Skills for managing Gitea repositories — issues, pull requests, labels, milestones, releases, branches, and files.",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.",
|
||||
"version": "1.3.6",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/gitea"
|
||||
},
|
||||
|
||||
14
.github/plugin/marketplace.json
vendored
14
.github/plugin/marketplace.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "holocron",
|
||||
"description": "AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.4.4",
|
||||
"version": "0.4.5",
|
||||
"owner": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
@@ -17,22 +17,22 @@
|
||||
},
|
||||
{
|
||||
"name": "bin",
|
||||
"description": "Cross-cutting workflow skills not yet split into a focused plugin — research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.",
|
||||
"version": "1.1.4",
|
||||
"description": "Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.",
|
||||
"version": "1.1.5",
|
||||
"category": "Utilities",
|
||||
"source": "./plugins/bin"
|
||||
},
|
||||
{
|
||||
"name": "git",
|
||||
"description": "Skills for working with Git — conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.",
|
||||
"version": "1.3.4",
|
||||
"description": "Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.",
|
||||
"version": "1.3.5",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/git"
|
||||
},
|
||||
{
|
||||
"name": "gitea",
|
||||
"description": "Skills for managing Gitea repositories — issues, pull requests, labels, milestones, releases, branches, and files.",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.",
|
||||
"version": "1.3.6",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/gitea"
|
||||
},
|
||||
|
||||
10
AGENTS.md
10
AGENTS.md
@@ -27,25 +27,25 @@ This repo dogfoods its own plugins. Before shelling out, check whether a skill a
|
||||
- Vale prose linting → `vale-config` / `vale-run`
|
||||
- This repo's own AGENTS.md → `agentsmd-author` / `agentsmd-audit`
|
||||
|
||||
Use the bare, **unnamespaced** names. The `<plugin>:` form (`gitea:gitea-prs`) also still resolves, because user-scope native installs were left enabled on purpose (ADR-0018) — a working namespaced call is not evidence that anything is broken and is not something to "fix". Prefer the bare name anyway: it is what `apm install` deploys, and what survives those user-scope installs eventually being converted.
|
||||
Use the bare, **unnamespaced** names. That is what `apm install` deploys and the only form this repo's own install produces — a project skill has no plugin to prefix (ADR-0018). Whether the `<plugin>:` form (`gitea:gitea-prs`) also resolves depends on native plugin installs at user scope, outside this repo; write the bare name either way.
|
||||
|
||||
Fall back to raw shell only when no skill covers it.
|
||||
|
||||
## Session rules
|
||||
|
||||
- **Do not add repo-owned keys to `.claude/settings.json`.** apm treats it as its own deployed artifact and `apm audit --ci` replays the install and diffs, so anything apm would not have written is permanent drift that fails the `apm-audit-ci` pre-push hook. A hook you want here is authored in `plugins/<name>/.apm/hooks/` and deployed by apm, never hand-written into that file. Machine-specific settings go in the gitignored `.claude/settings.local.json`; shared enforcement goes in `.pre-commit-config.yaml`.
|
||||
- **Do not add repo-owned keys to `.claude/settings.json`.** apm treats it as its own deployed artifact and `apm audit --ci` replays the install and diffs, so anything apm would not have written is permanent drift that fails the `apm-audit-ci` pre-push hook. A hook you want here is authored in `plugins/<name>/.apm/hooks/` and deployed by apm, never hand-written into that file. The `SessionStart` entry already in it is exactly that: kyberforge authors it in `plugins/kyberforge/.apm/hooks/hooks.json` and apm merges it in, so it is apm's own output, it is what the replay expects, and it belongs in the commit — do not strip it (ADR-0019). Machine-specific settings go in the gitignored `.claude/settings.local.json`; shared enforcement goes in `.pre-commit-config.yaml`.
|
||||
- **`apm.lock.yaml` turning up modified is expected, not a bug.** kyberforge's `SessionStart` hook runs `apm outdated` at startup and `apm update --yes` when something is behind, which rewrites the lock. Commit or discard it deliberately.
|
||||
- **A `.apm/` edit is not live in this session until it is pushed.** The six dependencies resolve from the holocron remote, unpinned against the default branch. `apm install` deploys from the lock; `apm update` is what re-resolves refs.
|
||||
- **The ADR-0020 skill gates ship hot, with no baseline.** 26 of 39 descriptions and 9 of 39 bodies exceed their FAIL tier, and the `Kyberforge.CompositionNote` Vale rule fires 10 errors across `gitea-issues`, `gitea-labels-milestones`, `gitea-prs` and `gitea-workflow`. Editing any of those skills *for any reason* means retrofitting it to the contract first — a one-line fix cannot be committed until the skill complies. Deliberate; tracked as Gitea issue #99. `skill-size-check` will not warn you about the Vale half, so check both: `pre-commit run --all-files`.
|
||||
- **Run `bash tests/run-tests.sh` before considering any change done.**
|
||||
- **Before pushing, run the whole gate locally:** `pre-commit run --hook-stage pre-push --all-files`. Pushing runs 14 repo-defined hooks, not just the test suite.
|
||||
- **Run `bash tests/run-tests.sh --strict` before considering any change done.** Keep the flag: without it a suite whose dependency is missing exits 77 and is counted SKIPPED rather than failed, so the run goes green having verified less than it claims.
|
||||
- **Before pushing, rehearse the gate locally:** `pre-commit run --hook-stage pre-push --all-files`. It runs the 14 pre-push hooks this repo authors itself plus pre-commit's 2 `meta` hooks, so it prints 16; `check-release-needed` passes without checking anything, because it needs a real push to `main`. `docs/spec/gates.md` reconciles both.
|
||||
- **Pushing without a network** needs `SKIP=apm-marketplace-check,apm-pack-check-clean git push` — those two resolve a remote marketplace entry via `git ls-remote`. Skip only those two; the rest are real local checks, and adding one to `SKIP` disarms it silently.
|
||||
- **Author commits with `git-commits`** — it validates Conventional Commits, which `commit-msg` enforces.
|
||||
- **This repo and Gitea are the only source of truth.** All project state, decisions, and working conventions live here. Do not use an external memory system for this project — cached state diverges from the repo and you get a split brain. Before answering any design or architecture question, check `docs/adr/` for an existing decision.
|
||||
|
||||
## Key documents
|
||||
|
||||
Read `CONTEXT.md` at the start of every session — it is this repo's domain language, and the terms in it are used unglossed everywhere else.
|
||||
Read `CONTEXT.md` at the start of every session — it is this repo's domain glossary, and the terms it defines are used unglossed everywhere else. It is not exhaustive: terms it does not carry are defined at their point of use, mostly in `docs/spec/`.
|
||||
|
||||
Read these on demand:
|
||||
|
||||
|
||||
41
CONTEXT.md
41
CONTEXT.md
@@ -57,6 +57,24 @@ The deployable unit — one or more skills, agents, hooks, commands, and MCP ser
|
||||
single installable directory under `plugins/<name>/`, compiled from that plugin's `.apm/` source.
|
||||
_Avoid_: package, bundle, module
|
||||
|
||||
**apm package**:
|
||||
The unit apm builds and installs — `plugins/<name>/apm.yml` plus the hand-authored
|
||||
`plugins/<name>/.apm/` tree it compiles from (ADR-0015).
|
||||
_Avoid_: plugin directory, source tree
|
||||
|
||||
**Content mirror**:
|
||||
The generated flat `skills/`, `agents/`, `commands/`, `instructions/`, `extensions/` directories and
|
||||
merged `hooks/hooks.json` at a plugin root — also called the flat mirror — compiled from that
|
||||
plugin's `.apm/` tree so hosts that convention-scan those paths discover the content (ADR-0017).
|
||||
_Avoid_: generated copy, duplicate tree
|
||||
|
||||
**Output profile**:
|
||||
An `apm pack` target format for a generated *marketplace* manifest; apm has `claude`
|
||||
(`.claude-plugin/marketplace.json`) and `codex` (the differently-shaped
|
||||
`.agents/plugins/marketplace.json`), and none for `.github/plugin/marketplace.json` (Copilot CLI's
|
||||
legacy path), which a sync script mirrors instead. Mechanics: `docs/spec/architecture.md`.
|
||||
_Avoid_: build target, export format
|
||||
|
||||
**Plugin marketplace**:
|
||||
A Git repository carrying a `marketplace.json` manifest that lists installable plugins. There is no
|
||||
backend, registry, or SaaS — the Git repo is the marketplace.
|
||||
@@ -135,6 +153,23 @@ The deterministic Vale pass that runs ahead of `skill-audit`/`agent-audit`'s Des
|
||||
so LLM judgment is spent only on what a pattern cannot catch. Mechanics: `docs/spec/gates.md`.
|
||||
_Avoid_: linting, style check
|
||||
|
||||
**Authoring root**:
|
||||
The directory a gate resolves against — the nearest ancestor of the file being checked holding
|
||||
`plugins/*/.apm/skills` or `plugins/*/.apm/agents`, falling back to the nearest ancestor holding
|
||||
`.git`. The walk: `docs/spec/gates.md`.
|
||||
_Avoid_: repo root, project root
|
||||
|
||||
**Near-miss**:
|
||||
A query that shares keywords with this skill but needs a different one — and, by extension, the
|
||||
sibling that would wrongly answer it; boundary clauses exist to exclude genuine near-misses rather
|
||||
than to enumerate siblings. Detail: `skill-audit/references/description-quality.md`.
|
||||
_Avoid_: overlap, similar skill
|
||||
|
||||
**Vacuous green**:
|
||||
A check that reports success because it measured nothing — zero files scanned, an unparsed value read
|
||||
as empty, a conditional branch that never armed.
|
||||
_Avoid_: false pass, clean run
|
||||
|
||||
**Issue**:
|
||||
The cross-provider term for a tracked unit of work. Gitea is this repo's canonical tracker
|
||||
(ADR-0007), but skills say "linked issue" generically rather than naming a provider.
|
||||
@@ -181,9 +216,9 @@ _Avoid_: ticket, card, task
|
||||
- "skill" was used for both the authored `SKILL.md` under `plugins/<name>/.apm/skills/` and the
|
||||
deployed copy under `.claude/skills/` — resolved: the authoring source is the **Skill**; the
|
||||
deployed copy is gitignored `apm install` output and is never edited.
|
||||
- Skills answer to two names, bare (`gitea-prs`) and namespaced (`gitea:gitea-prs`), because
|
||||
user-scope native installs were left enabled deliberately (ADR-0018) — resolved: write the bare
|
||||
name; a working namespaced call is not evidence of a defect.
|
||||
- Skills can answer to two names, bare (`gitea-prs`) and namespaced (`gitea:gitea-prs`), depending on
|
||||
whether a native install exists at user scope alongside the apm one (ADR-0018) — resolved: write
|
||||
the bare name, which is the only form `apm install` produces.
|
||||
- "context" means both the model's live token window (the **Preload tax** sense) and the bounded
|
||||
domain this file describes — resolved: unqualified "context" in this repo means the token window.
|
||||
- "audit" was used for both an author skill's inline closeout and `forge`'s independent
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Lessons
|
||||
|
||||
Patterns observed during development of this repo. Three or more entries on the same pattern → promote to CONTEXT.md (or the relevant instruction file) as a standing rule.
|
||||
Patterns observed during development of this repo. Three or more entries on the same pattern → promote to `docs/spec/architecture.md` (or the relevant instruction file) as a standing rule.
|
||||
|
||||
**Graduation rule:** When three or more entries cover the same pattern, the human reviews and promotes it to the appropriate standing location: `CONTEXT.md` for domain-level principles, `core/instructions/coding.md` for coding conventions, `core/instructions/testing.md` for testing conventions, or `core/instructions/subagent-orchestration.md` for delegation conventions. Those four are the whole set — `core/instructions/` holds `coding.md`, `governance.md`, `subagent-orchestration.md` and `testing.md`, and nothing else. Git conventions have no standing file of their own: promote them to `core/instructions/coding.md`, or create a new instruction file deliberately rather than assuming one exists. The graduated entries are marked `[graduated → target file]` rather than deleted (audit trail).
|
||||
**Graduation rule:** When three or more entries cover the same pattern, the human reviews and promotes it to the appropriate standing location: `docs/spec/architecture.md` for structural and domain-level principles — `CONTEXT.md` is not a destination, its `## Principles` section was deleted and what was there now sits under that file's "AGENTS.md pattern" and "Reference conventions" headings — `core/instructions/coding.md` for coding conventions, `core/instructions/testing.md` for testing conventions, or `core/instructions/subagent-orchestration.md` for delegation conventions. Those four are the whole set — `core/instructions/` holds `coding.md`, `governance.md`, `subagent-orchestration.md` and `testing.md`, and nothing else. Git conventions have no standing file of their own: promote them to `core/instructions/coding.md`, or create a new instruction file deliberately rather than assuming one exists. The graduated entries are marked `[graduated → target file]` rather than deleted (audit trail).
|
||||
|
||||
**Who writes here:** The session-handoff skill (Chunk 3) prompts LESSONS.md extraction before closing a session. The human may also write directly.
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -18,11 +18,11 @@ Content ships as six installable plugins, each an apm (Agent Package Manager) pa
|
||||
The six plugins:
|
||||
|
||||
- **kyberforge** — skills and agents for creating, maintaining, and managing a Claude Code / Copilot CLI plugin marketplace
|
||||
- **git** — conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running (`pc-author` / `pc-run`)
|
||||
- **gitea** — issues, pull requests, labels, milestones, releases, branches, files
|
||||
- **git** — conventional commits, branches, history, submodules, worktrees, remotes, pre-commit hook authoring and running (`pc-author` / `pc-run`), and an interactive router (`git-workflow`)
|
||||
- **gitea** — issues, pull requests, labels, milestones, releases, branches, files, and an interactive router (`gitea-workflow`)
|
||||
- **core** — authoring and auditing a repo's `AGENTS.md` and the provider adapter files that defer to it
|
||||
- **lint** — configuring and running linters
|
||||
- **bin** — cross-cutting workflow skills not yet split into a focused plugin: research, documentation, TDD, prototyping, triage, diagnosis, architecture review, requirement grilling
|
||||
- **bin** — cross-cutting workflow skills not yet split into a focused plugin: research, documentation, TDD, prototyping, triage, diagnosis, architecture review, requirement grilling, compressed output (`caveman`), and re-orienting mid-task (`zoom-out`)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -81,12 +81,17 @@ A suite that exits 77 because a dependency is missing is reported as SKIPPED and
|
||||
|
||||
## Before pushing
|
||||
|
||||
Run the whole pre-push gate locally in one command:
|
||||
Run the pre-push gate locally in one command:
|
||||
|
||||
```bash
|
||||
pre-commit run --hook-stage pre-push --all-files
|
||||
```
|
||||
|
||||
One caveat: `check-release-needed` is a silent no-op under this invocation. It exits 0 unless
|
||||
`PRE_COMMIT_REMOTE_BRANCH` is `refs/heads/main`, and pre-commit exports that only from the real
|
||||
pre-push git hook during an actual `git push` — so the hook reports `Passed` having checked nothing.
|
||||
Every other pre-push hook does run.
|
||||
|
||||
See [`docs/spec/gates.md`](docs/spec/gates.md) for what each hook enforces and why.
|
||||
|
||||
**Offline?** Exactly two pre-push hooks need the network, because root `apm.yml`'s marketplace contains one remote package entry that must be resolved with `git ls-remote`:
|
||||
|
||||
16
apm.yml
16
apm.yml
@@ -1,5 +1,5 @@
|
||||
name: holocron
|
||||
version: 0.4.2
|
||||
version: 0.4.5
|
||||
description: AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.
|
||||
license: MIT
|
||||
|
||||
@@ -52,7 +52,7 @@ marketplace:
|
||||
# top-level apm.yml description:/version: above are NOT inherited into the
|
||||
# compiled output despite being used elsewhere (e.g. by `apm audit`).
|
||||
description: AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.
|
||||
version: 0.4.4
|
||||
version: 0.4.5
|
||||
owner:
|
||||
name: Defame1297
|
||||
email: defame1297@rkdr.net
|
||||
@@ -83,21 +83,21 @@ marketplace:
|
||||
category: Developer Tools
|
||||
|
||||
- name: bin
|
||||
description: Cross-cutting workflow skills not yet split into a focused plugin — research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.
|
||||
description: Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.
|
||||
source: ./plugins/bin
|
||||
version: 1.1.4
|
||||
version: 1.1.5
|
||||
category: Utilities
|
||||
|
||||
- name: git
|
||||
description: Skills for working with Git — conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.
|
||||
description: Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.
|
||||
source: ./plugins/git
|
||||
version: 1.3.4
|
||||
version: 1.3.5
|
||||
category: Version Control
|
||||
|
||||
- name: gitea
|
||||
description: Skills for managing Gitea repositories — issues, pull requests, labels, milestones, releases, branches, and files.
|
||||
description: Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.
|
||||
source: ./plugins/gitea
|
||||
version: 1.3.5
|
||||
version: 1.3.6
|
||||
category: Version Control
|
||||
|
||||
- name: core
|
||||
|
||||
@@ -59,7 +59,9 @@ answers to `git-commits` and `kyberforge:skill-audit` to `skill-audit`. This is
|
||||
a project skill has no plugin to prefix. `AGENTS.md` and `CONTEXT.md` are updated to name the bare
|
||||
form, which is what apm deploys and the only form a repo consuming holocron through apm gets.
|
||||
|
||||
**Correction (2026-08-14): the namespaced form did not stop resolving.** An earlier revision of
|
||||
**Correction (2026-08-14): the namespaced form did not stop resolving.** *Superseded by the
|
||||
2026-08-17 correction below: the machine state this cites is no longer present. Both are kept
|
||||
because the pair is the finding — read neither as current.* An earlier revision of
|
||||
this consequence said every `<plugin>:<skill>` reference "was stale the moment the switch landed",
|
||||
and `AGENTS.md`/`CONTEXT.md` were written to match. That contradicts the "User scope is untouched,
|
||||
deliberately" consequence below, and the contradiction resolves against it: `~/.claude.json` still
|
||||
@@ -72,6 +74,20 @@ survives those user-scope installs eventually being converted, and the namespace
|
||||
resolves for anyone installing holocron natively, so skill bodies written for both audiences
|
||||
should name the bare skill.
|
||||
|
||||
**Correction (2026-08-17): the evidence under the correction above is gone, and the claim goes with
|
||||
it — not to its opposite.** Observed on this machine: `~/.claude/plugins/installed_plugins.json` is
|
||||
`{"version": 2, "plugins": {}}`; there is no `enabledPlugins` key anywhere in `~/.claude.json`
|
||||
(`grep -c enabledPlugins` returns 0); `~/.apm/marketplaces.json` is `{"marketplaces": []}`. The
|
||||
`holocron` entry in `~/.claude/plugins/known_marketplaces.json` survives, but a registered
|
||||
marketplace is not an installed plugin. So the user-scope installs the 2026-08-14 correction cited
|
||||
are not there, and neither is the state the *original* consequence described before it. The claim
|
||||
about the namespaced form has now been written twice off two different observations of the same
|
||||
machine, and this ADR has already reversed itself once on it. That is the finding: the fact is
|
||||
machine state, not a property of this decision, and it changes without any commit. No instruction
|
||||
file — `AGENTS.md`, `CONTEXT.md`, or a skill body — should assert either way whether
|
||||
`<plugin>:<skill>` resolves. The rule that survives every observation is the one that was always the
|
||||
actionable half: write the bare name, because it is the only form `apm install` produces.
|
||||
|
||||
**apm owns `.claude/settings.json`.** (ADR-0019 supersedes the "exactly `{"hooks": {}}`" claim
|
||||
below — once a package ships a hook, apm merges it into that file and the merged entry is apm's own
|
||||
output. The rule that nothing repo-authored goes in the file is unchanged.) `apm audit --ci` replays the install into a scratch tree and
|
||||
@@ -117,10 +133,11 @@ pinned `resolved_commit` in `apm.lock.yaml` and does not re-resolve refs (`apm i
|
||||
documents this explicitly — "does NOT refresh refs; use 'apm update' for that"). Running it after a
|
||||
merge redeploys the same content and reports success.
|
||||
|
||||
**User scope is untouched, deliberately.** `bin@holocron`, `gitea@holocron`, and a stale
|
||||
`hello-world@holocron` remain natively installed at user scope, and every project other than this
|
||||
one still resolves its skills that way. Converting them is a separate decision with a blast radius
|
||||
beyond this repo.
|
||||
**User scope is untouched, deliberately.** This decision changed project scope only; whatever is
|
||||
natively installed at user scope was left alone, and converting it is a separate decision with a
|
||||
blast radius beyond this repo. The specific inventory this paragraph used to name
|
||||
(`bin@holocron`, `gitea@holocron`, a stale `hello-world@holocron`) is machine state and is stale —
|
||||
see the 2026-08-17 correction above. The decision recorded here is unaffected by what that state is.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
227
docs/adr/0021-plugin-descriptions-state-a-domain-boundary.md
Normal file
227
docs/adr/0021-plugin-descriptions-state-a-domain-boundary.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# A plugin's published description states its domain boundary and never enumerates its skills
|
||||
|
||||
Three of this repo's six plugins publish a `description` that lists the skills they ship. That style
|
||||
has now failed three times in four days, the third time inside the correction for the second. It is
|
||||
enforced by nothing, it obliges a marketplace release on every skill addition, and it was never
|
||||
applied to the other three plugins. This ADR retires it: a published description says what the
|
||||
plugin is *for*, and the inventory lives where an inventory can be read off the tree.
|
||||
|
||||
**Status: accepted (2026-08-17).**
|
||||
|
||||
## Context
|
||||
|
||||
A plugin's published description is one string authored twice — in `plugins/<name>/apm.yml` and in
|
||||
the matching `marketplace.packages[]` entry of the root `apm.yml` — and compiled into four generated
|
||||
files per plugin edit: the plugin's `.claude-plugin/plugin.json` and `.github/plugin/plugin.json`,
|
||||
plus the repo-wide `.claude-plugin/marketplace.json` and its `.github/plugin/marketplace.json`
|
||||
mirror. (`.agents/plugins/marketplace.json`, apm's codex profile, carries no per-package
|
||||
`description` or `version` at all and is unaffected.) It is the only text a consumer sees in a marketplace listing before
|
||||
installing. It is **not** a SKILL.md `description`: it is never preloaded into an agent's context and
|
||||
routes nothing at runtime. ADR-0020 governs that other artifact; this one governs this one. The
|
||||
overlap is a finding, not a scope: ADR-0020 established that capability enumeration in a description
|
||||
is "a correctness hazard, not only a token cost". The hazard at this layer is different — staleness
|
||||
in published metadata rather than an agent shortcutting the body — but the enumeration is the same
|
||||
construct and it fails the same way.
|
||||
|
||||
Measured at `de84d1b`, the branch tip before this change. Each figure is reproducible from the tree:
|
||||
skill counts are `ls plugins/<name>/.apm/skills/ | wc -l`, description text is
|
||||
`plugins/<name>/apm.yml`.
|
||||
|
||||
| Plugin | Style | Skills | Items enumerated | Skills named | Unnamed |
|
||||
|---|---|---|---|---|---|
|
||||
| `bin` | enumeration | 11 | 8 | 9 | `caveman`, `zoom-out` |
|
||||
| `git` | enumeration | 9 | 8 | 8 | `git-workflow` |
|
||||
| `gitea` | enumeration | 7 | 7 | 6 | `gitea-workflow` |
|
||||
| `core` | boundary | 3 | — | — | — |
|
||||
| `kyberforge` | boundary | 7 | — | — | — |
|
||||
| `lint` | boundary | 2 | — | — | — |
|
||||
|
||||
Three failures, in order.
|
||||
|
||||
**`bb9158d` (2026-08-14) — `core`'s description described `bin`.** The text it deleted read
|
||||
"Cross-cutting utility skills for everyday AI-assisted coding — triage, diagnosis, architecture
|
||||
review, and session navigation." All four items are real skills and not one of them is `core`'s:
|
||||
they are `bin`'s `triage`, `diagnose`, `improve-codebase-architecture` and `zoom-out`. `core` ships
|
||||
`agentsmd-author`, `agentsmd-audit` and `provider-adapter-author`, and the published description
|
||||
named none of them.
|
||||
|
||||
This is the failure the whole style was later adopted against, and it is worth being exact about
|
||||
what it was, because the record has been read the other way twice since. It was **wrong content**,
|
||||
not an incomplete list. The description was a syntactically perfect, complete, four-item enumeration
|
||||
of a real skill set; it just belonged to a different plugin. Enumerating harder could not have caught
|
||||
it, and a gate that asked "does every enumerated item exist as a skill?" would have passed it — all
|
||||
four did exist. `bb9158d`'s own fix went the other direction: it replaced the enumeration with a
|
||||
domain boundary, and `core` has needed no correction since. The precedent set by that commit was
|
||||
therefore *boundary*, and the two commits below cite it while doing the opposite.
|
||||
|
||||
**`65bac15` (2026-08-17) — `git` advertised `gitea`'s domain, `gitea` advertised a skill that does
|
||||
not exist.** `git` read "conventional commits, branch management, pull requests, and feature flow";
|
||||
pull requests reach the forge over HTTP and are `gitea`'s, which is the exact boundary
|
||||
`docs/spec/architecture.md` draws between the two plugins. `gitea` read "issues, pull requests,
|
||||
milestones, releases, and wikis"; `grep -ri wiki plugins/gitea/.apm/` returns nothing and no wiki
|
||||
skill has ever existed. Both were repaired by re-enumerating.
|
||||
|
||||
**`de84d1b` (2026-08-17) — the re-enumeration was itself incomplete.** `bin`'s "A place for things to
|
||||
be binned" was replaced with an eight-item list over eleven skills; `caveman` and `zoom-out` are
|
||||
absent. `zoom-out` is the same skill `bb9158d` had called "session navigation" three days earlier
|
||||
while deleting it from the wrong plugin's description — named when it was in the wrong place,
|
||||
unnamed once it was in the right one. And the miss is not confined to `bin`: `git-workflow` is
|
||||
unnamed in `git`'s corrected description, though `65bac15`'s own commit message states it was added
|
||||
("omitting pc-author/pc-run, git-submodules and git-workflow"), and `gitea-workflow` is unnamed in
|
||||
`gitea`'s. Across the three plugins, 23 of 27 skills are named at the third attempt.
|
||||
|
||||
**Nothing checks any of this.** `scripts/check-manifests.sh` does not contain the string
|
||||
`description`. The three ADR-0020 validators (`scripts/skill-size-check.sh` and skill-audit's and
|
||||
agent-audit's `validate.sh`) gate on SKILL.md and agent frontmatter; they do open `apm.yml`, but only
|
||||
to read `dependencies.apm` when resolving the boundary-target universe — none of them reads the
|
||||
`description:` key, and their hook globs match `SKILL.md` and `*.agent.md` only. `apm audit --ci`,
|
||||
`apm pack --check-clean` and `scripts/sync-plugin-content.sh --check --all` all compare compiled
|
||||
output against `apm.yml`, so their entire job is to propagate whatever the description says into
|
||||
those four files byte-for-byte and confirm they match. The `wiki` claim passed every one of the fourteen pre-push hooks, every day it
|
||||
was published.
|
||||
|
||||
**And the obligation is unbounded.** Under enumeration, adding one skill to `bin`, `git` or `gitea`
|
||||
means editing two copies of a prose string on top of the version bumps and regeneration any skill
|
||||
addition already owes under this repo's release policy
|
||||
(`plugins/kyberforge/.apm/skills/apm-workflow/references/marketplace.md`). The bumps are not the
|
||||
marginal cost — the prose edit is, and it is the half nothing checks. A skill *rename* triggers the
|
||||
same, for a string no consumer can tell went stale. 27 of the repo's 39 skills sat behind
|
||||
a description carrying that obligation; the other 12 did not, and their three plugins have generated
|
||||
no defect of this class.
|
||||
|
||||
### Scope
|
||||
|
||||
This decision covers the six plugins this repo authors. The root marketplace also lists
|
||||
`mattpocock-skills`, a third-party package whose description is not this repo's to write; its entry
|
||||
is out of scope and is left as published upstream.
|
||||
|
||||
## Decision
|
||||
|
||||
**A plugin's published `description` states the plugin's domain boundary. It does not enumerate the
|
||||
skills the plugin ships, by name or by paraphrase.**
|
||||
|
||||
- The boundary answers "what kind of work belongs to this plugin, and where is its edge against its
|
||||
nearest sibling" — the question a consumer deciding whether to install is actually asking. It is
|
||||
stable under skill addition, rename and removal, which is the entire point: an artifact that does
|
||||
not change when the tree changes cannot go stale against it.
|
||||
- **The boundary must cover everything the plugin actually ships.** A boundary drawn narrower than
|
||||
the contents is the same defect as an incomplete enumeration, one level up, and it is the specific
|
||||
risk in this change. `git` carries `pc-author` and `pc-run`, which are not git operations at all;
|
||||
"Skills for working with Git" silently drops them, so the boundary names the pre-commit hooks
|
||||
explicitly rather than trusting a reader to file them under Git.
|
||||
- The two copies — package `apm.yml` and the root `marketplace.packages[]` entry — stay identical.
|
||||
This is already the rule in practice and both prior corrections state why: the root entry is what
|
||||
reaches the compiled marketplace, so fixing only the package manifest leaves it half-propagated.
|
||||
- The three descriptions, rewritten here, with `core`/`kyberforge`/`lint` shown for register:
|
||||
|
||||
| Plugin | Published description | Chars |
|
||||
|---|---|---|
|
||||
| `bin` | Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin. | 152 |
|
||||
| `git` | Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it. | 146 |
|
||||
| `gitea` | Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone. | 134 |
|
||||
| `core` | *(unchanged)* Skills for authoring and auditing a repo's AGENTS.md and the provider adapter files that defer to it. | 101 |
|
||||
| `kyberforge` | *(unchanged)* Skills and agents for creating, maintaining, and managing a Claude Code / Copilot CLI plugin marketplace. | 105 |
|
||||
| `lint` | *(unchanged)* Skills and agents for configuring and running linters. | 54 |
|
||||
|
||||
- **No gate is added.** This is a deliberate omission and the reasoning is below, not an item left
|
||||
for later.
|
||||
|
||||
### Why no gate
|
||||
|
||||
The check enumeration would need — "every skill directory appears in the description" — was writable
|
||||
in principle and was never written, including by the two commits that corrected an enumeration by
|
||||
enumerating again and had every reason to. It is also only half a check: it
|
||||
catches a skill missing from the list, and it cannot catch `wiki`, because "this noun does not name
|
||||
any skill" requires a vocabulary of permissible non-skill nouns that no one is going to maintain.
|
||||
Under a boundary there is no correspondence left to check, which is the property being bought.
|
||||
|
||||
What survives un-gated is `bb9158d`'s actual failure: a boundary that is simply wrong about its
|
||||
plugin. That was never machine-checkable in either style — the text was a well-formed description of
|
||||
a real plugin — and it is caught by the same review that has to happen when a published,
|
||||
consumer-facing string is edited at all. A gate that would catch it needs a declared per-plugin
|
||||
skill-to-boundary mapping for the description to be checked against, which is a second artifact
|
||||
requiring exactly the per-skill maintenance this ADR exists to delete, relocated one file over.
|
||||
|
||||
Two cheap partial gates were considered and rejected in the same breath. Forbidding a comma-separated
|
||||
run of three or more noun phrases is a prose heuristic that fires on `lint`'s perfectly good
|
||||
"configuring and running linters" class of sentence. Forbidding any string matching a skill directory
|
||||
name under `plugins/<name>/.apm/skills/` bans legitimate boundary vocabulary — `git-branches` exists,
|
||||
and a `git` boundary has every right to say "branches". Both would be believed, and both would be
|
||||
wrong, which ADR-0020 already records as worse than no gate.
|
||||
|
||||
## Considered options
|
||||
|
||||
**Keep enumeration and gate it.** The only option that makes the current style safe. Rejected on the
|
||||
three grounds above: the check is one-directional, it cannot see an invented capability, and it makes
|
||||
a marketplace release the consequence of adding a directory. It also hard-couples published consumer
|
||||
copy to internal directory names, so a skill rename becomes a version bump on the plugin and on the
|
||||
marketplace.
|
||||
|
||||
**Enumerate consistently across all six plugins**, on the grounds that the real defect is the split
|
||||
style. Rejected: it takes an obligation that has produced three failures on three plugins and applies
|
||||
it to six. The measured outcome of the most recent attempt to enumerate carefully, with the defect
|
||||
fresh and two prior commits as precedent, is four skills unnamed.
|
||||
|
||||
**Cap the description length**, mirroring ADR-0020's 250/400-character tiers, on the theory that a
|
||||
short description has no room to enumerate. Rejected because length does not measure correspondence:
|
||||
`gitea`'s failing description was 96 characters and asserted a skill that has never existed, while
|
||||
`bin`'s 176-character enumeration is under the same cap. All six descriptions here, before and after,
|
||||
sit inside ADR-0020's tiers; the tier would have been silent through all three failures.
|
||||
|
||||
**Delete the description to a bare name.** Rejected: apm's Claude marketplace mapper emits
|
||||
`description` into `marketplace.json`, and it is the only prose a consumer sees before installing.
|
||||
|
||||
**Point the description at the plugin's `README.md`.** Rejected: a marketplace listing renders a
|
||||
string, not a link — and the README's own plugin list carries the same enumeration with the same
|
||||
staleness, so this relocates the defect rather than fixing it.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Three descriptions are rewritten and the compiled output regenerated.** Eight generated files
|
||||
change: `plugins/{bin,git,gitea}/.claude-plugin/plugin.json`,
|
||||
`plugins/{bin,git,gitea}/.github/plugin/plugin.json`, `.claude-plugin/marketplace.json` and its
|
||||
byte-identical `.github/plugin/marketplace.json` mirror. `.agents/plugins/marketplace.json` (the
|
||||
codex profile) is unchanged and correctly so — it carries no per-package `description` or `version`
|
||||
field at all, only `name`, `source`, `policy` and `category`.
|
||||
|
||||
**Version bumps, all PATCH under the `per_package` strategy:** `bin` 1.1.4 → 1.1.5, `git` 1.3.4 →
|
||||
1.3.5, `gitea` 1.3.5 → 1.3.6, `marketplace.version` 0.4.4 → 0.4.5.
|
||||
|
||||
**The root `apm.yml` top-level `version:` is restored to lockstep with `marketplace.version`,
|
||||
0.4.2 → 0.4.5.** These two fields have moved together in every commit that has ever touched root
|
||||
`apm.yml` — 0.3.2, 0.3.3, 0.3.4, 0.4.0, 0.4.1, 0.4.2 in both — until `65bac15` and
|
||||
`de84d1b` on this branch bumped `marketplace.version` to 0.4.3 and then 0.4.4 while leaving the
|
||||
top-level field at 0.4.2. Lockstep is not folklore: it is stated at
|
||||
`plugins/kyberforge/.apm/skills/apm-workflow/references/marketplace.md`. This is a defect, not a
|
||||
style: `apm.yml`'s comment inside the marketplace block records that the top-level `version:` is not inherited into the compiled output
|
||||
"despite being used elsewhere (e.g. by `apm audit`)", so the field is live and was silently two
|
||||
releases behind what the marketplace published. Closed here rather than tracked, because the
|
||||
correction is one line and the drift is three days old.
|
||||
|
||||
**`docs/spec/architecture.md`'s plugin table is unchanged and stays a routing table.** It answers
|
||||
"where does a new skill go" for someone working *inside* this repo; the published description answers
|
||||
"should I install this" for someone outside it. The two now read similarly, and that is not
|
||||
duplication to collapse — they have different readers and different lifecycles, and the table already
|
||||
says so in its own preamble ("These are routing boundaries, not inventories"). One caveat for whoever
|
||||
next edits that page: its closing sentence sends a reader to the published description "for what a
|
||||
consumer actually gets", which was true against an enumeration and is now a pointer to a second
|
||||
boundary statement. Neither artifact carries an inventory after this change, so that sentence was
|
||||
rewritten in the same branch to point at `plugins/<name>/.apm/skills/` and `README.md` instead.
|
||||
|
||||
**`README.md`'s plugin bullet list becomes the only place an inventory lives, and it still
|
||||
enumerates.** That is deliberate, but it makes the list load-bearing in a way it was not before, so
|
||||
its `bin`, `git` and `gitea` bullets were completed in the same branch to name every skill those
|
||||
plugins ship. This ADR does not otherwise extend to it: a README is a hand-read document where a
|
||||
list of what you get is the useful thing, it is not compiled into four files, and a stale line in it
|
||||
costs a reader a moment rather than misrepresenting a published package. The tradeoff that makes
|
||||
enumeration wrong in a marketplace manifest is precisely the one that makes it fine there.
|
||||
|
||||
**Nothing in the ADR-0020 gate set changes.** Its character and word tiers, its Vale rules and its
|
||||
three validators all read `SKILL.md` and `*.agent.md` frontmatter; none of them opens an `apm.yml`.
|
||||
The two contracts are adjacent and independent, and a future author retrofitting a skill under
|
||||
issue #99 is not touched by this ADR.
|
||||
|
||||
**The failure mode this leaves open is a wrong boundary, and it is un-gated by design.** If a fourth
|
||||
failure of this class occurs it will be a description that describes the wrong plugin — `bb9158d`'s
|
||||
shape, the one enumeration never addressed. That is the trigger to revisit, and the thing to build
|
||||
then is a declared skill-to-boundary mapping, not a return to enumeration.
|
||||
@@ -21,7 +21,7 @@ project repo (local overrides)
|
||||
|
||||
Skills are **not** deployed by `install.sh`. They are distributed as plugins and installed separately — in this repo by `apm install` against the `dependencies.apm` entries in the root `apm.yml`, which lands them in `.claude/skills/` and `.claude/agents/` (ADR-0018); elsewhere by `claude plugin install <name>@holocron`.
|
||||
|
||||
`~/.claude/CLAUDE.md` is a thin adapter, not a content source. It imports `~/.agents/AGENTS.md` (always-on rules) and `governance.md` (always-on governance) and carries nothing else — the content index of on-demand instruction files sits in `core/AGENTS.md`, deployed beside it. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication.
|
||||
`~/.claude/CLAUDE.md` is a thin adapter, not a content source. It imports `~/.agents/AGENTS.md` (always-on rules) and `governance.md` (always-on governance) and carries nothing else — the content index of on-demand instruction files sits in `core/AGENTS.md`, deployed to `~/.agents/AGENTS.md` and imported by it. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication.
|
||||
|
||||
## Plugin model
|
||||
|
||||
@@ -31,7 +31,7 @@ Which plugin a new skill belongs in follows from what each one is scoped to. The
|
||||
|
||||
The second boundary worth stating is `git` vs `gitea`, because both own things called branches and both touch pull requests: `git` is whatever works over the git wire protocol against a local clone, `gitea` is whatever goes through the forge's HTTP API. That is why `git-branches` and `gitea-branches` both exist and are not duplicates.
|
||||
|
||||
These are routing boundaries, not inventories — they answer "where does a new skill go", so they deliberately do not enumerate what each plugin ships today. For what a consumer actually gets, read the plugin's published `description` in its `apm.yml`.
|
||||
These are routing boundaries, not inventories — they answer "where does a new skill go", so they deliberately do not enumerate what each plugin ships today. The plugin's published `description` in its `apm.yml` states the same boundary for a consumer deciding whether to install (ADR-0021); neither carries an inventory. For what a plugin ships today, read `plugins/<name>/.apm/skills/` or the plugin list in `README.md`.
|
||||
|
||||
| Plugin | Scope |
|
||||
|---|---|
|
||||
@@ -76,7 +76,7 @@ This repo also has a `CLAUDE.md` at its root — the Claude Code entry point for
|
||||
|
||||
## Reference conventions
|
||||
|
||||
The stated convention is that files referencing other files declare those references explicitly: the referencing file carries the forward reference (the content index in `core/AGENTS.md`, `references:` in frontmatter), the referenced file carries a `when:` field describing when it is loaded, and divergence between the two signals staleness. It is aspirational, not a description of the repo today — no file under `core/instructions/` carries frontmatter at all, `when:` appears in two of the 39 skill sources under `plugins/*/.apm/skills/`, and the reference scanner script meant to derive the reverse map ("what files reference this file?") does not exist; `docs/notes/skill-implementation-workflow.md` still lists it as unbuilt work. Treat it as intent for instruction files, skills, and workflow documents, not as a rule the repo enforces.
|
||||
The stated convention is that files referencing other files declare those references explicitly: the referencing file carries the forward reference (the content index in `core/AGENTS.md`, `references:` in frontmatter), the referenced file carries a `when:` field describing when it is loaded, and divergence between the two signals staleness. It is aspirational, not a description of the repo today — no file under `core/instructions/` carries frontmatter at all, `when:` appears in exactly one of the 39 `SKILL.md` sources under `plugins/*/.apm/skills/`, and the reference scanner script meant to derive the reverse map ("what files reference this file?") does not exist; `docs/notes/skill-implementation-workflow.md` still lists it as unbuilt work. Treat it as intent for instruction files, skills, and workflow documents, not as a rule the repo enforces.
|
||||
|
||||
## Provider model
|
||||
|
||||
@@ -84,4 +84,4 @@ The stated convention is that files referencing other files declare those refere
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
Key hard-to-reverse decisions are recorded as ADRs in `docs/adr/`. There is no index file — the directory holds 20 numbered ADRs whose filenames state their decision, so `ls docs/adr/` is the index. Read a superseding ADR before the one it supersedes: ADR-0015 (apm as the authoring source of truth) supersedes ADR-0001 and moots ADR-0006, ADR-0017 corrects ADR-0015's host-discovery gap, and ADR-0019 supersedes one claim in ADR-0018 (that `.claude/settings.json`'s committed content is exactly `{"hooks": {}}`) while keeping the rule behind it. Entry points for the structure described on this page: ADR-0002 (two-tier CLAUDE.md), ADR-0003 (AGENTS.md as the provider-agnostic entry point), ADR-0015 and ADR-0017 (the two compilers behind the plugin roots).
|
||||
Key hard-to-reverse decisions are recorded as ADRs in `docs/adr/`. There is no index file — the directory holds numbered ADRs whose filenames state their decision, so `ls docs/adr/` is the index. Read a superseding ADR before the one it supersedes: ADR-0015 (apm as the authoring source of truth) supersedes ADR-0001 and moots ADR-0006, ADR-0017 corrects ADR-0015's host-discovery gap, and ADR-0019 supersedes one claim in ADR-0018 (that `.claude/settings.json`'s committed content is exactly `{"hooks": {}}`) while keeping the rule behind it. Entry points for the structure described on this page: ADR-0002 (two-tier CLAUDE.md), ADR-0003 (AGENTS.md as the provider-agnostic entry point), ADR-0015 and ADR-0017 (the two compilers behind the plugin roots).
|
||||
|
||||
@@ -14,7 +14,7 @@ of the oddities documented here are load-bearing and have already been re-litiga
|
||||
| Command | Scope |
|
||||
|---|---|
|
||||
| `pre-commit run --all-files` | the commit-stage hooks |
|
||||
| `pre-commit run --hook-stage pre-push --all-files` | the whole push gate, one command |
|
||||
| `pre-commit run --hook-stage pre-push --all-files` | the push gate, one command — with one caveat below |
|
||||
| `pre-commit run skill-size-check --all-files` | just the ADR-0020 size/context gates |
|
||||
|
||||
Install hooks via `pc-run`, wiring **all three stages**. This repo's `.pre-commit-config.yaml` has no
|
||||
@@ -23,7 +23,18 @@ and `pre-push` (everything below).
|
||||
|
||||
The pre-push command reports **16** hooks, not 14. The extra two are pre-commit's own `meta` hooks,
|
||||
`check-hooks-apply` and `check-useless-excludes`: they declare no `stages:`, so they run at every
|
||||
stage including this one. Fourteen is the count of repo-defined pre-push hooks.
|
||||
stage including this one. Both are declared in this repo's `.pre-commit-config.yaml` like everything
|
||||
else — what separates them is `repo: meta` (pre-commit's own built-ins) from `repo: local`. Fourteen
|
||||
is the count of hooks this repo authors itself.
|
||||
|
||||
**The caveat: one of those 14 is a silent no-op under that invocation.**
|
||||
`check-release-needed` exits 0 immediately unless `PRE_COMMIT_REMOTE_BRANCH` equals
|
||||
`refs/heads/main`, and pre-commit exports that variable only from the real pre-push git hook during
|
||||
an actual `git push`. Running the stage by hand — or from a CI runner — therefore reports it
|
||||
`Passed` having checked nothing. That is by design for feature branches — pushing WIP must not be
|
||||
blocked on cutting a premature tag — but it means `--hook-stage pre-push --all-files` is a full
|
||||
rehearsal of 13 hooks and a skip of the fourteenth. The script's own header records the same gap for
|
||||
a PR merged through Gitea's merge button, where no local push happens at all.
|
||||
|
||||
## The pre-push gate
|
||||
|
||||
@@ -74,7 +85,7 @@ drift in generated text.
|
||||
|
||||
| Hook | Guards |
|
||||
|---|---|
|
||||
| `check-release-needed` | on push to `main` only — fails if files exposed via `.pre-commit-hooks.yaml` changed since the last tag |
|
||||
| `check-release-needed` | on a real `git push` to `main` only — fails if files exposed via `.pre-commit-hooks.yaml` changed since the last tag. A no-op everywhere else, including under `pre-commit run --hook-stage pre-push` (see [the caveat above](#running-the-gates)) |
|
||||
|
||||
Four of these shell out to `apm`: `apm-marketplace-check`, `apm-audit-ci`, `apm-pack-check-clean`,
|
||||
and `check-plugin-content-sync` (via `scripts/sync-plugin-content.sh`, which wraps `apm pack`). The
|
||||
@@ -87,8 +98,9 @@ loudly (`Error: jq is required but not installed`).
|
||||
## Skill and agent context gates (ADR-0020)
|
||||
|
||||
The `skill-size-check` pre-commit hook, scoped to `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$`,
|
||||
runs `scripts/skill-size-check.sh`. That scope means it never lints `docs/research/examples/`
|
||||
reference skills. It is also shipped to external repos as `kyberforge-skill-size-check` (see
|
||||
runs `scripts/skill-size-check.sh`. That scope means it never lints the
|
||||
`plugins/kyberforge/docs/research/examples/` reference skills. It is also shipped to external repos
|
||||
as `kyberforge-skill-size-check` (see
|
||||
[External consumers](#external-consumers-the-root-pre-commit-hooksyaml)).
|
||||
|
||||
### Two independent gate families, neither replaced the other
|
||||
@@ -107,14 +119,13 @@ reference skills. It is also shipped to external repos as `kyberforge-skill-size
|
||||
| `description` characters | 250 | 400 | the YAML-**folded** value |
|
||||
| body words | 600 | 900 | **body only** — everything after the frontmatter's closing `---` |
|
||||
|
||||
Plus three hard FAILs with no suggestion tier:
|
||||
Plus two hard FAILs with no suggestion tier:
|
||||
|
||||
- **A missing, valueless or `null` `description:`.** Not a skip. The description is the one field
|
||||
preloaded into every session, so a gate that declines to measure it reports green. (This is not
|
||||
hypothetical: `description:` with no value followed by `model: sonnet` let a line regex capture the
|
||||
*next* key, which looked non-empty, so the "missing or empty" branch never fired and every gate
|
||||
below early-returned on the genuinely empty folded value — exit 0, zero output, on a blocking gate.)
|
||||
- **Every boundary-clause routing target must resolve** to a real skill or agent.
|
||||
- **Every `references/<file>.md` a body names must exist** on disk. A dispatch table pointing at a
|
||||
file that was never written is a silently dead branch, and nothing else in the gate/audit/vale
|
||||
stack notices it.
|
||||
@@ -122,6 +133,35 @@ Plus three hard FAILs with no suggestion tier:
|
||||
A file can sit well inside one family and fail the other. 2,770 whole-file words is a conformance
|
||||
backstop; 900 body-only words is a quality gate. Conflating them is what produced the current state.
|
||||
|
||||
### An unresolved routing target is not automatically a FAIL
|
||||
|
||||
A boundary-clause target that resolves to no skill or agent has **three** possible verdicts, not one
|
||||
(`unresolved_targets()` in `scripts/skill-size-check.sh`):
|
||||
|
||||
| Verdict | When |
|
||||
|---|---|
|
||||
| **SUGGESTION** — the default | the target does not resolve and neither promotion condition below holds |
|
||||
| **blocking ERROR** | the target is **terminal** (not a compound modifier) **and** either written in route notation (`/name` for any name; `-> name` only when the name is hyphenated — see the gap below) **or** corroborated by another target in the same sentence that *does* resolve |
|
||||
| **INFO, "DID NOT RUN"** | no skill universe could be determined for the path at all — the targets are named and left unchecked, exit 0 |
|
||||
|
||||
The default is deliberately soft because a hyphenated word in a boundary clause is as likely to be a
|
||||
tool, a file format or an English compound as a route: "pre-commit hooks" is prose about a tool and
|
||||
never reaches the check at all, being a compound modifier rather than a terminal name. The
|
||||
SUGGESTION text says how to opt in — write it as `/name` or `-> name` and it gets checked properly.
|
||||
|
||||
**Known gap: the arrow form only works for hyphenated names.** Target extraction is built on
|
||||
`NAME_HYPH` (`scripts/skill-size-check.sh:543`), which requires at least one hyphen, and
|
||||
`ARROW_BOUNDARY` (`:561`) inherits that. So `-> gitea-prs` is extracted and checked, while
|
||||
`-> triage` is not extracted at all — no ERROR, no SUGGESTION, exit 0. The unicode arrow `→` is not
|
||||
recognised in either case. This makes the SUGGESTION's own advice unsafe for a single-word skill:
|
||||
taking it silences the finding rather than checking it. `/name` has no such restriction and is the
|
||||
form to prefer. Tracked as a defect; `tests/test-adr0020-targets.sh` has one arrow case and its
|
||||
target happens to be hyphenated, so nothing currently covers this.
|
||||
|
||||
Corroboration is what makes the soft default safe: a sentence whose *other* target resolves is
|
||||
demonstrably a routing sentence, so a sibling that does not resolve is a typo rather than a noun, and
|
||||
gets promoted.
|
||||
|
||||
### Target resolution walk
|
||||
|
||||
Resolution walks up **from the file being checked** — never from the script's own location. Deriving
|
||||
@@ -176,10 +216,13 @@ Three more, deterministic to measure but judgment to act on:
|
||||
The hook is declared `verbose: true` so the SUGGESTION tier is audible. pre-commit prints nothing at
|
||||
all for a passing hook, and a SUGGESTION deliberately does not fail — without verbose every
|
||||
suggestion is swallowed, which is exactly the invisibility ADR-0013 records for Vale warnings.
|
||||
ADR-0020's arithmetic depends on it: writing to the 400-char FAIL lands the preload at 39 × 400 =
|
||||
15,600 chars (a 33% cut off 23,427); writing to the 250-char SUGGESTION lands at 9,750 (58%). The
|
||||
halving depends entirely on that tier being visible. It costs nothing on a clean file — the script
|
||||
prints only findings.
|
||||
ADR-0020's preload arithmetic depends on it: writing to the 400-char FAIL delivers roughly half the
|
||||
cut that writing to the 250-char SUGGESTION does, so the intended saving depends entirely on that
|
||||
tier being visible. The numbers, and the measurement method behind them, are not restated here —
|
||||
they live in ADR-0020's Consequences section, under "A ceiling does not produce an average", whose
|
||||
figures are pinned to the base commit the decision was taken on (`f9b919d`). Quoting them here would
|
||||
just create a second copy to go stale. It costs nothing on a clean file — the script prints only
|
||||
findings.
|
||||
|
||||
### Duplicated constants
|
||||
|
||||
@@ -637,8 +680,10 @@ remote *before* any network call, so it does not join the pair above.
|
||||
enforcement table (deterministic vs. auditor judgment), and every rejected alternative
|
||||
- `docs/adr/0019-session-start-hook-keeps-the-apm-install-current.md` — the `SessionStart` hook, the
|
||||
executable-trust gate, and the version-pinned allow key
|
||||
- `docs/adr/0017` / `0015` / `0014` — plugin content sync, apm-generated manifests, committed Vale
|
||||
styles
|
||||
- `docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md`,
|
||||
`docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md`,
|
||||
`docs/adr/0014-vale-prefilter-ships-from-the-plugin.md` — plugin content sync, apm-generated
|
||||
manifests, committed Vale styles
|
||||
- `docs/spec/architecture.md` — directory structure, install pipeline, what is generated and what is
|
||||
hand-authored
|
||||
- `.pre-commit-config.yaml` — the hooks themselves, with inline rationale comments
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bin",
|
||||
"version": "1.1.4",
|
||||
"description": "Cross-cutting workflow skills not yet split into a focused plugin \u2014 research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.",
|
||||
"version": "1.1.5",
|
||||
"description": "Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
4
plugins/bin/.github/plugin/plugin.json
vendored
4
plugins/bin/.github/plugin/plugin.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bin",
|
||||
"version": "1.1.4",
|
||||
"description": "Cross-cutting workflow skills not yet split into a focused plugin — research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.",
|
||||
"version": "1.1.5",
|
||||
"description": "Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: bin
|
||||
version: 1.1.4
|
||||
description: Cross-cutting workflow skills not yet split into a focused plugin — research, documentation, TDD, prototyping, triage, diagnosis, architecture review, and requirement grilling.
|
||||
version: 1.1.5
|
||||
description: Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: defame1297@rkdr.net
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "git",
|
||||
"version": "1.3.4",
|
||||
"description": "Skills for working with Git \u2014 conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
4
plugins/git/.github/plugin/plugin.json
vendored
4
plugins/git/.github/plugin/plugin.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "git",
|
||||
"version": "1.3.4",
|
||||
"description": "Skills for working with Git \u2014 conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: git
|
||||
version: 1.3.4
|
||||
description: Skills for working with Git — conventional commits, branches, history, submodules, worktrees, remotes, and pre-commit hook authoring and running.
|
||||
version: 1.3.5
|
||||
description: Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: defame1297@rkdr.net
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills for managing Gitea repositories \u2014 issues, pull requests, labels, milestones, releases, branches, and files.",
|
||||
"version": "1.3.6",
|
||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API \u2014 the forge's own objects, as distinct from the local git clone.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
4
plugins/gitea/.github/plugin/plugin.json
vendored
4
plugins/gitea/.github/plugin/plugin.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea",
|
||||
"version": "1.3.5",
|
||||
"description": "Skills for managing Gitea repositories \u2014 issues, pull requests, labels, milestones, releases, branches, and files.",
|
||||
"version": "1.3.6",
|
||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API \u2014 the forge's own objects, as distinct from the local git clone.",
|
||||
"author": {
|
||||
"name": "Defame1297",
|
||||
"email": "defame1297@rkdr.net",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: gitea
|
||||
version: 1.3.5
|
||||
description: Skills for managing Gitea repositories — issues, pull requests, labels, milestones, releases, branches, and files.
|
||||
version: 1.3.6
|
||||
description: Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: defame1297@rkdr.net
|
||||
|
||||
@@ -60,7 +60,19 @@ fi
|
||||
if [[ "$CHECK" -eq 1 ]]; then
|
||||
if [[ ! -f "$DST" ]] || ! diff -q "$SRC" "$DST" >/dev/null 2>&1; then
|
||||
echo "DRIFT $DST: out of sync with .claude-plugin/marketplace.json" >&2
|
||||
echo "Fix: bash scripts/sync-marketplace-mirror.sh -- apm ships no output profile targeting this path, so \`apm pack\` does not refresh it. Expecting it to is exactly the drift this script and its pre-push hook exist to prevent." >&2
|
||||
# The runnable command gets a line to ITSELF, and the rationale gets its own
|
||||
# echo. It was one line -- `Fix: bash scripts/sync-marketplace-mirror.sh --
|
||||
# apm ships no output profile...` -- which put the prose after `--`, the
|
||||
# POSIX end-of-options marker, so copy-pasting the Fix line ran this script
|
||||
# with ~24 stray argv entries: `${1:-}` was `--` (so CHECK stayed 0 and no
|
||||
# shift happened), `[[ $# -eq 0 ]]` failed, and the tool meant to fix the
|
||||
# drift answered with its own usage error and exit 1. The backticks around
|
||||
# `apm pack` made it worse: the paste also command-substituted a real
|
||||
# `apm pack` run before the script was even reached. Hence plain quotes
|
||||
# below too. Keep the command alone on its line.
|
||||
echo "Fix: run, from the repository root:" >&2
|
||||
echo " bash scripts/sync-marketplace-mirror.sh" >&2
|
||||
echo "Note: apm ships no output profile targeting this path, so 'apm pack' does not refresh it. Expecting it to is exactly the drift this script and its pre-push hook exist to prevent." >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
# dev binary, the other 15 suites still tell you something, and turning that
|
||||
# into a red run would just train people to ignore red.
|
||||
# * as a GATE (the run-tests pre-push hook): a skip is a SETUP ERROR, not a
|
||||
# legitimate state. AGENTS.md documents vale, apm and jq as required pre-push
|
||||
# dependencies, so a suite that cannot run on the machine doing the pushing
|
||||
# means the machine is misconfigured -- and pre-commit prints NOTHING for a
|
||||
# passing hook, so the skip list below is swallowed entirely. On a vale-less
|
||||
# PATH that silently shipped a green gate having verified 15 of 17 suites.
|
||||
# legitimate state. README.md's Prerequisites table documents vale, apm and
|
||||
# python3/PyYAML -- the dependencies these suites actually guard on -- as
|
||||
# required pre-push, so a suite that cannot run on the machine doing the
|
||||
# pushing means the machine is misconfigured -- and
|
||||
# pre-commit prints NOTHING for a passing hook, so the skip list below is
|
||||
# swallowed entirely. On a vale-less PATH that once silently shipped a
|
||||
# green gate having verified 15 of the 17 suites that existed then.
|
||||
# Exactly the vacuous-pass class the rest of this file exists to close.
|
||||
#
|
||||
# Deliberately its own switch, NOT folded into
|
||||
@@ -258,7 +260,7 @@ fi
|
||||
# here (not just referenced) because this block goes to stderr and is what a
|
||||
# pre-push reader actually gets handed.
|
||||
if [[ "$STRICT" == true && ${#SKIPPED[@]} -gt 0 ]]; then
|
||||
echo "Error: --strict and ${#SKIPPED[@]} suite(s) skipped. Run as a gate, a skip is a SETUP ERROR on this machine, not a legitimate state: AGENTS.md documents vale, apm and jq as required pre-push dependencies, so every suite is expected to be runnable here. Install what each suite names below and re-run; do not skip the hook." >&2
|
||||
echo "Error: --strict and ${#SKIPPED[@]} suite(s) skipped. Run as a gate, a skip is a SETUP ERROR on this machine, not a legitimate state: README.md's Prerequisites table documents vale, apm and python3/PyYAML — what these suites guard on — as required pre-push dependencies, so every suite is expected to be runnable here. Install what each suite names below and re-run; do not skip the hook." >&2
|
||||
sidx=0
|
||||
for s in ${SKIPPED[@]+"${SKIPPED[@]}"}; do
|
||||
echo " $s" >&2
|
||||
|
||||
@@ -392,10 +392,11 @@ fi
|
||||
|
||||
# --- 10. --strict turns a skip into a failure, and names the suite AND the reason ---
|
||||
# Graceful skipping is right for an ad-hoc run and wrong for a gate. At pre-push a
|
||||
# suite exiting 77 means a dependency AGENTS.md documents as required is missing
|
||||
# on the pushing machine -- and pre-commit prints nothing at all for a passing
|
||||
# hook, so the skip list this script writes to stdout was swallowed whole. A
|
||||
# vale-less PATH shipped a green gate having verified 15 of 17 suites.
|
||||
# suite exiting 77 means a dependency README.md's Prerequisites table documents
|
||||
# as required is missing on the pushing machine -- and pre-commit prints nothing
|
||||
# at all for a passing hook, so the skip list this script writes to stdout was
|
||||
# swallowed whole. A vale-less PATH once shipped a green gate having verified
|
||||
# 15 of the 17 suites that existed then.
|
||||
#
|
||||
# The reason is asserted, not just the name: "something was skipped" leaves the
|
||||
# reader with no idea which binary to install, which is most of why the swallowed
|
||||
|
||||
@@ -451,9 +451,11 @@ fi
|
||||
# cover anything new: it omitted scripts/lib/batch-run.sh — the shared runner
|
||||
# this branch introduced, whose own header (batch-run.sh:9-11) documents it as
|
||||
# bash-3.2-safe — along with four other scripts/*.sh. Deriving the list means a
|
||||
# new script is covered the moment it lands. LESSONS.md and the script headers
|
||||
# name bash 3.2 as an explicit repo target, so the scope is four globs, each
|
||||
# floor-asserted below:
|
||||
# new script is covered the moment it lands. The script headers name bash 3.2 as
|
||||
# an explicit repo target -- scripts/lib/batch-run.sh:10 ("all three callers are
|
||||
# explicitly bash-3.2-safe") and providers/claude-code/statusline-command.sh:100
|
||||
# ("macOS's system bash, and an explicit repo target") -- so the scope is four
|
||||
# globs, each floor-asserted below:
|
||||
# - scripts/**/*.sh — repo tooling and pre-commit hook scripts
|
||||
# - tests/*.sh — the runners and every regression test
|
||||
# - plugins/*/.apm/**/*.sh — the scripts plugins ship to users
|
||||
|
||||
Reference in New Issue
Block a user