From b9c7762463805049ad49b29b8d8d083cb6b4625f Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Mon, 17 Aug 2026 09:28:01 +0000 Subject: [PATCH] docs: trim CONTEXT.md to a glossary and demote VISION.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AGENTS.md | 5 +- CONTEXT.md | 222 ++++++++++++------ ...013-vale-harness-scope-and-rule-sources.md | 6 + ...m-replaces-plugin-marketplace-authoring.md | 2 + ...nt-mirror-bridges-apm-to-host-discovery.md | 4 +- docs/spec/architecture.md | 19 +- docs/spec/gates.md | 175 +++++++++++++- 7 files changed, 361 insertions(+), 72 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fbc7ee4..b7d9e16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,14 +41,17 @@ Fall back to raw shell only when no skill covers it. - **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. - **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. +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 these on demand: - `README.md` — prerequisites, install, and test commands +- `docs/VISION.md` — the phased roadmap and where this is going; read when a decision turns on product direction +- `LESSONS.md` — patterns that went wrong once; read before repeating a class of change that has burned the repo before - `docs/spec/gates.md` — what each pre-commit and pre-push hook enforces and why; read when a gate fails or before changing hook config - `docs/spec/architecture.md` — directory structure, install pipeline, provider model - `docs/adr/` — architectural decisions; read before answering design questions or proposing structural changes diff --git a/CONTEXT.md b/CONTEXT.md index 2a0dc12..ba247c5 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,106 +1,192 @@ --- name: AI Development Repo -description: Domain language and decisions for the global AI development config repository +description: The domain language of the global AI development config repository --- -# Context +# AI Development Repo -## Principles +The bounded context of this repo is **how agent instructions are authored, packaged, distributed, and +kept small**. Terms here name concepts specific to that problem. Mechanics live elsewhere: +`docs/spec/architecture.md` for structure, `docs/spec/gates.md` for enforcement, `docs/adr/` for +decisions. -### CLAUDE.md index model -`AGENTS.md` is the source of always-on universal rules (provider-agnostic). `providers/claude-code/CLAUDE.md` is a thin adapter: it imports `~/.agents/AGENTS.md` via `@~/.agents/AGENTS.md` and appends Claude Code-specific additions (`@import` for governance.md, content index). Deployed to `~/.claude/CLAUDE.md` via `install.sh`. Context size is kept minimal — only what is needed every session is loaded upfront; detailed content is pulled on demand. See ADR-0003. +## Language -### Instruction file format -`core/instructions/.md` files are plain markdown — no frontmatter, no schema. The agent decides when to read each file based on task context and the content index label in `providers/claude-code/CLAUDE.md`. Frontmatter is deferred until there is evidence that agents are loading the wrong files in practice. +### Context cost -### Repo/gitea as source of truth -All project state, decisions, context, and working conventions live in this repo or Gitea. External memory systems should not be used for this project — they create a split-brain risk where cached state diverges from the repo. At the start of every session, read `CLAUDE.md`, `CONTEXT.md`, and `docs/VISION.md`. Everything needed to orient is here. +**Preload tax**: +The always-on context cost of every installed skill's `name` and `description`, charged from the +first token of every session whether the skill is invoked or not. Measured 2026-08-14 at ~5,900 +tokens across 39 skills; method in `docs/spec/gates.md`. +_Avoid_: context cost, token overhead -Before answering any design or architecture question, check for existing decisions: `docs/adr/` (hard architectural decisions). +**Skill context contract**: +The ADR-0020 authoring rules that hold the preload tax and body size down — a description carries a +trigger clause, at most one capability clause, and a boundary clause, and nothing else. Thresholds +and the target-resolution walk: `docs/spec/gates.md`. +_Avoid_: skill budget, size limit -## Glossary +**Dispatch body**: +The body pattern a skill with two or more mutually exclusive flows must use — the body carries only +the dispatch table and the gates common to every branch, and each flow lives in its own +self-contained `references/` file. Exemplar: `apm-workflow`. +_Avoid_: router body, thin body -### Management Application -A separate product (separate repo) for browsing, editing, and configuring AI development configs through a proper product UI. Git is the persistence layer, invisible to the user. The app is repo-agnostic — it works with any git repo that follows these conventions. This repo is the canonical default content (the official starter). See `docs/VISION.md` for the phased roadmap. +**Hand-invoked skill**: +A skill reached only by typing its slash command, declared `disable-model-invocation: true`. The host +withholds it from the model-visible listing entirely, so it pays no preload tax and its description +becomes human-facing text. Exemplar: `zoom-out`. +_Avoid_: manual skill, disabled skill -### Skills -Reusable slash commands for AI coding tools, defined as `SKILL.md` files following the [Agent Skills open standard](https://agentskills.io). Authored at `plugins//.apm/skills//SKILL.md` and reaching a host by one of two install paths: `apm install`, which deploys the skill directory to `.claude/skills//` (this repo's own path — see "apm-consumed install"), or `claude plugin install @`, which caches the whole plugin (still supported for external consumers). Skills are self-contained — they cannot reference files outside the plugin directory after install-time caching. The two paths name skills differently: apm deploys a plain project skill (`skill-audit`), a plugin install namespaces it (`kyberforge:skill-audit`). +**Delegation discipline**: +The agent-side counterpart to the dispatch body. A plugin-scope agent is a single `.agent.md` file +with no sibling `references/` directory, so it cannot disclose to itself — it can only delegate to +skills. Its characteristic defect is therefore restatement, not length. +_Avoid_: agent hygiene -### Preload tax -The always-on context cost of every installed skill's `name` + `description`, which sit in the agent's context from the first token of every session whether or not the skill is invoked. Measured 2026-08-14 against base commit `f9b919d` at 23,427 chars (~5,900 tokens) across 39 skills, plus 1,325 chars for 4 agents. Method, so it can be re-run: sum `len(name) + len(description)` over each `plugins/*/.apm/skills/*/SKILL.md` frontmatter with `>` block scalars folded to the value the host loads, at ~4 characters per token. Non-routing frontmatter (`metadata.source_keys`, `category`, `version`) is **not** part of it — the model-visible skill listing carries only `name` and `description`, which supersedes `LESSONS.md:63` on this host. Bodies are not part of it either; they are charged on invocation. +### Distribution -### Skill context contract -The authoring rules that hold the preload tax and body size down, set by ADR-0020. A description carries a trigger clause, at most one capability clause, and a boundary clause of the form `Not → ` naming a resolvable target — nothing else. "Resolvable" is decided by walking up *from the file being checked* to an **authoring root** — the nearest ancestor holding `plugins/*/.apm/{skills,agents}`, falling back to the nearest `.git`, in two passes so a nested `.git` cannot outrank a real monorepo root. The universe is then every skill and agent under `/plugins/*/` (sibling plugins resolve against each other, which is what a monorepo means), plus the checked file's own apm package and that package's own declared `dependencies.apm`. The **root** manifest's dependency list is never consulted, and no plugin here declares a cross-plugin apm dependency. Deployed `.claude/`/`.agents/` trees count only when there is no authoring root at all — the consumer case. The property this buys is that one commit gets one verdict: those trees are gitignored `apm install` output, so resolving through them made the same commit report 2 dangling targets on a developer machine and 6 on a fresh clone, which a gate shipping hot with no baseline cannot do. A `${BASH_SOURCE}`-relative repo root is the other half of the same defect and is gone — it leaked this repo's 39-skill universe into consumer repos running the hook through pre-commit. A *missing* boundary clause is a SUGGESTION rather than a failure, for skills and agents alike — some skills genuinely have no near-miss sibling. A *missing or empty description* is the opposite: a hard FAIL in all three validators, because a gate that merely declines to measure the one preloaded field reports green. Capability enumeration, output formats, and composition notes ("composes X rather than duplicating Y") belong in the body or `README.md`; a description that summarises workflow is a correctness hazard, not just a cost, because agents act on it instead of reading the body. Sizes are two-tier and sit *below* the agentskills.io spec limits, which stay unchanged as conformance backstops: description 250 SUGGESTION / 400 FAIL (spec 1,024); body 600 SUGGESTION / 900 FAIL (spec 2,770 words / 500 lines). Conflating the quality gate with the spec ceiling is what let `skill-author` and `agent-author` grow to within twelve words of 2,770. +**Skill**: +A reusable slash command defined as a `SKILL.md` file following the +[Agent Skills open standard](https://agentskills.io), authored at +`plugins//.apm/skills//SKILL.md`. +_Avoid_: command, prompt, macro -### Dispatch body -The body pattern a skill with two or more mutually exclusive flows must use: the body carries only the dispatch table and the gates common to every branch, and each flow lives in its own self-contained `references/` file. Named for `apm-workflow` (421-word body, 3,006 words of references), which arrived at it independently and is the repo's exemplar. Its absence was the characteristic defect at the time ADR-0020 was written: `skill-author` inlined both its create and improve flows, and `agent-author` carried 50-60 lines marked inapplicable by their own headers on any single run. Both were retrofitted to dispatch tables in the change that carries the ADR — `skill-author` went 2,623 body words to 595 and `agent-author` 2,582 to 616 — so they are now worked examples of the pattern rather than counter-examples of it. The 39-skill corpus at large is not: 9 bodies still exceed the 900-word FAIL (issue #99). +**Plugin**: +The deployable unit — one or more skills, agents, hooks, commands, and MCP servers bundled into a +single installable directory under `plugins//`, compiled from that plugin's `.apm/` source. +_Avoid_: package, bundle, module -### Hand-invoked skill -A skill reached only by typing its slash command, declared with `disable-model-invocation: true`. The host withholds it from the model-visible skill listing entirely, so it pays no preload tax and its `description` becomes human-facing text rather than a trigger list. `zoom-out` is the worked example: apm passes the flag through verbatim to both install paths, and the skill is absent from the router while `/zoom-out` still works. Choosing model-invoked vs. hand-invoked is the first question `skill-author` asks, because it determines whether a description needs triggers at all. +**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. +_Avoid_: registry, store, catalogue -### Delegation discipline -The agent-side counterpart to the dispatch body. A plugin-scope agent is a single `.apm/agents/.agent.md` file with no sibling `references/` directory, so it cannot disclose to itself — it can only delegate to skills. Its characteristic defect is therefore restatement, not length: an agent body that spells out a procedure a skill it can invoke already owns creates a second copy that drifts. `agent-audit` fails that, with the fix being "invoke `` instead". Agents take the same description gates as skills but no body word gate — a skill body competes with the caller's live conversation, an agent body becomes the system prompt of a fresh context. +**holocron**: +This repository, in its role as a plugin marketplace and as the remote the six plugin dependencies +resolve against. +_Avoid_: the marketplace, upstream -### Plugin -The deployable unit in the plugin marketplace. A plugin bundles one or more skills, agents, hooks, prompts, MCP servers, and optionally a `bin/` directory into a single installable directory. In this repo, plugins live under `plugins//`, each with its own `apm.yml` + `.apm/{skills,agents,hooks,...}` — this is the authoring source of truth for the plugin's content (ADR-0015). Two categories of tracked output are compiled from that source, never hand-edited: `.claude-plugin/plugin.json` (Claude Code) and `.github/plugin/plugin.json` (Copilot CLI) via `apm pack`/`apm compile`; and, alongside them, a flat `agents/`, `skills/`, `commands/`, `instructions/`, `extensions/` directory mirror at the plugin root plus a merged hooks file at `hooks/hooks.json`, generated by `scripts/sync-plugin-content.sh` — Claude Code's and Copilot's installers convention-scan only these flat paths (`hooks/hooks.json` is the convention path for hooks specifically; a root-level `hooks.json` is scanned by nothing and is deleted as stale by a sync — see ADR-0017's 2026-08-14 amendment) and have no awareness of `.apm/` nesting at all, so this mirror is what actually makes `.apm/` content discoverable at install time (ADR-0017). Plugins are copied to a cache on install — they cannot reference files outside their own directory. Install a plugin with `claude plugin install @`, or consume it as an apm dependency (see "apm-consumed install"). +**apm-consumed install**: +How this repo installs its own plugins as of 2026-08-14 — six `dependencies.apm` entries in the root +`apm.yml` deployed by `apm install`, rather than `claude plugin install @holocron`. Its +consequences: ADR-0018. +_Avoid_: apm install, dependency install -### Plugin marketplace -A Git repository with a `marketplace.json` manifest listing installable plugins. No backend, registry, or SaaS required — the Git repo is the marketplace. This repo is the `holocron` marketplace. The manifest at `.claude-plugin/marketplace.json` (read by both Claude Code and Copilot CLI) is **compiled output** of `apm pack`, generated from the root `apm.yml`'s `marketplace:` block (owner, build/output config, versioning strategy, and the `packages:` list of installable plugins) — it is not hand-edited. See ADR-0015. `.github/plugin/marketplace.json` is Copilot CLI's legacy manifest path; apm has no output profile for it (only `claude` and `codex`, and `codex`'s is a differently-shaped file at `.agents/plugins/marketplace.json`), so `scripts/sync-marketplace-mirror.sh` keeps it byte-identical to `.claude-plugin/marketplace.json`, checked at pre-push. Each listed package's `source:` still points at that plugin's own `plugins//` root, not at an `apm pack` build artifact — which is why that root also carries the flat `agents/`/`skills/`/`commands/`/`hooks/hooks.json` content mirror described under "Plugin" (ADR-0017): without it, an install from this marketplace finds a valid manifest but no discoverable content. +**Provenance chain**: +The three-stage traceability record linking a skill back to its research inputs: `/research` produces +topic docs and a `sources.md`; the author skill records which sources informed which files in +`references/sources.md` and `source_keys` frontmatter; `skill-audit` validates the chain is complete +and internally consistent. +_Avoid_: sources, citations, attribution -### apm-consumed install -How this repo installs its own plugins, as of 2026-08-14: not `claude plugin install @holocron`, but six `dependencies.apm` entries in the root `apm.yml`, each a `git:`/`path:` object against the holocron remote, deployed by `apm install` into `.claude/skills/` and `.claude/agents/`. Project scope only — apm installs nothing at user scope, so the switch is contained to this repo and any other repo opts in by declaring its own dependencies. The git+path object form is deliberate over the shorter `@holocron` marketplace alias: an alias must first be registered with `apm marketplace add`, which writes to `~/.apm/marketplaces.json` (user scope, outside the repo), whereas the object form needs nothing beyond the committed manifest and so survives a fresh clone. +### Governance -Four consequences, each load-bearing: -- **Skills gain an unnamespaced name.** apm deploys plain project skills, so `git:git-commits` also answers to `git-commits`. The `:` form has not stopped resolving here: `~/.claude.json` still enables `core`, `git`, `gitea`, `kyberforge`, and `lint` at user scope, which ADR-0018 left in place deliberately — converting them is a separate decision with a blast radius beyond this repo. Until it is taken, every skill is live under two names, which is the same "present twice under two names" outcome ADR-0018's own "Alternatives considered" rejected for *keeping both install paths* — reached here by leaving user scope alone rather than by adopting it as the install model. Write the bare name regardless: apm deploys it, and a repo consuming holocron through apm gets only that form. The namespaced form still resolves wherever holocron is installed natively, so cross-audience skill bodies should use the bare name. -- **apm owns `.claude/settings.json`.** `apm audit --ci` (an `apm-audit-ci` pre-push hook) replays the install into a scratch tree and diffs it against the worktree, so any key apm would not have written is permanent drift. Committed content is exactly `{"hooks": {}}`; repo-owned settings have nowhere to live in that file. -- **Install output is gitignored.** `.claude/skills/`, `.claude/agents/`, and `apm_modules/` are all regenerated by `apm install`. `apm.lock.yaml` and the generated `.mcp.json` are committed. Committing the deployed skills would add a third mirror of the same content to the two ADR-0017 already governs. -- **Test discovery must skip `apm_modules/`.** It holds a full copy of every plugin, `.bats` files included; both `tests/run-bats.sh` and `tests/run-tests.sh` exclude it. +**HITL** (human-in-the-loop): +The agent pauses before a consequential action and a human approves before execution. Required for +irreversible or high-stakes actions — architecture changes, production deployments, security +configuration. +_Avoid_: manual approval, gated action -Dependencies are unpinned against the default branch, matching the `autoUpdate: true` the native marketplace install had. The practical cost is a round trip: an edit to `plugins//.apm/` is invisible locally until it is pushed and `apm install` re-runs, because the dependency resolves from the remote rather than from the working tree beside it. +**HOTL** (human-on-the-loop): +The agent acts and a human monitors, able to intervene after the fact. Acceptable only for +low-stakes, bounded, reversible actions where the cost of pausing exceeds the blast radius of an +error. +_Avoid_: autonomous, unsupervised -### HITL (human-in-the-loop) -Agent pauses before a consequential action; human approves before execution. Required for irreversible or high-stakes actions (architecture changes, production deployments, security configuration). The agent drafts the change plan and waits — it does not proceed autonomously. Contrast with HOTL. +**Sycophancy**: +The failure mode where an RLHF-trained model prioritises approval over accuracy — changing a correct +answer to a wrong one under user pressure, then persisting in the wrong answer. Treated here as a +first-class reliability risk, not a quality-of-life concern. +_Avoid_: agreeableness, people-pleasing -### HOTL (human-on-the-loop) -Agent acts; human monitors and can intervene after the fact. Acceptable for low-stakes, bounded, reversible actions where the cost of pausing for approval exceeds the blast radius of an error. The distinction between HITL and HOTL must be explicit and documented — defaulting to HOTL for convenience is not acceptable. +### Documents -### Sycophancy -The failure mode where RLHF-trained models prioritise approval over accuracy. Treated as a first-class reliability risk: models change correct answers to wrong ones under user pressure in a majority of observed cases, then persist in the wrong answer. Designing against sycophancy is an explicit obligation, not a quality-of-life concern. Countermeasures: explicit pushback resistance instructions, prompting for dissent, cross-validating against independent sources. Never interpret AI agreement as AI accuracy. +**AGENTS.md**: +The provider-agnostic always-on instruction file, in plain markdown with no provider-specific syntax +(ADR-0003). Two exist: repo-level, and the global `core/AGENTS.md` deployed to `~/.agents/AGENTS.md`. +_Avoid_: instructions file, system prompt -### AGENTS.md -The provider-agnostic always-on instruction entry point. Two files: -- **Repo-level `AGENTS.md`** — instructions for agents working inside this repo (structure, key rules); imported by repo `CLAUDE.md` via `@AGENTS.md`. -- **Global `core/AGENTS.md`** — Communication and Behavior rules that apply across all projects; deployed to `~/.agents/AGENTS.md`; imported by `~/.claude/CLAUDE.md` via `@~/.agents/AGENTS.md`. +**Thin adapter**: +A provider-specific instruction file (`CLAUDE.md`, `.cursor/rules/*.mdc`, `copilot-instructions.md`) +that imports its `AGENTS.md` and adds only that provider's syntax, carrying no original always-on +content of its own (ADR-0002, ADR-0003). +_Avoid_: wrapper, shim, provider file -Contains always-on rules in plain markdown with no provider-specific syntax (no `@import`). Provider-specific files (`CLAUDE.md`) are thin adapters that import the relevant `AGENTS.md` and add only Claude Code-specific syntax. This pattern means a single source of truth can serve multiple providers without duplication. See ADR-0003. +**LESSONS.md**: +The long-loop feedback log for patterns observed across sessions, at the repo root. Written by the +session-handoff skill or directly by the human. +_Avoid_: changelog, retro, postmortem -### Skill composition -A skill calling another skill by name to delegate a sub-task. The calling skill focuses on the orchestration decision ("when to do X"); the called skill owns the mechanics ("how to do X"). Established compositions: `grill-me` calls `write-adr` when a decision crystallises; `implement-feature` calls `tdd` as its implementation methodology; `forge` calls `grill-with-docs` to refine intent, classifies the target artifact type (skill / agent / plugin / marketplace entry), then routes to the matching `*-author` skill — which owns its own create/improve logic and, where applicable, its own inline audit closeout (`skill-author` runs `/skill-audit`, `agent-author` runs `kyberforge:agent-audit`, both in the same context as the authoring work). Reserve `forge` for genuinely undecided "which artifact type is this" questions — an already-fully-specified corrective edit (exact file, line, and fix already known) should call the target author skill directly instead (`skill-author`, `apm-workflow`, `agentsmd-author`, etc.); routing a known fix through `forge`'s grill-and-classify layer adds unnecessary indirection and, in practice, has been observed to lose track of hard constraints handed down the chain (e.g. "don't commit yet," "edit in this worktree") because each hop re-derives instructions from a shorter brief. `forge` additionally runs its own independent recheck after a skill/agent route finishes: a clean-context subagent (not forked, no inherited context) re-runs the same audit skill against the finished artifact, as a distinct verification layer from the author skill's inline audit — the two can share blind spots since the inline audit runs in the same context as the work it checks. If the clean audit surfaces any unresolved finding, `forge` loops — re-invoke the author skill to resolve it, re-run the clean audit — until the clean audit comes back with nothing unresolved; only then is the route done. `plugin-author` and `marketplace-author` had no audit counterpart and got no recheck; their terminal check was `claude plugin validate`. Both were deprecated per ADR-0015, superseded by `apm-workflow`, and deleted entirely once issue #90 landed. +**Management Application**: +A separate product in a separate repo for browsing, editing, and configuring AI development configs +through a product UI, with Git as an invisible persistence layer. Repo-agnostic; this repo is its +canonical default content. Roadmap: `docs/VISION.md`. +_Avoid_: the UI, the dashboard, the app -### Provider-agnostic issue tracker -Skills and workflows reference "linked issue" generically rather than a specific provider. Gitea is the canonical issue tracker for this repo (see ADR-0007). "Issue" is the cross-provider term (GitHub, GitLab, Gitea all use it). +### Quality -### Provenance chain -The three-stage traceability record linking a skill back to its research inputs: (1) `/research` produces topic docs and a `sources.md` in `plugins//docs/research/docs//`; (2) `/skill-author` reads those docs and records which sources informed which skill files in `references/sources.md` (including a `Research doc:` back-pointer to the upstream research file) and `source_keys` frontmatter on `SKILL.md` and `references/*.md`; (3) `skill-audit` validates the chain is complete and internally consistent via `validate-provenance.sh`. A skill with research input but no `references/sources.md`, or with `source_keys` that don't match `references/sources.md` slugs, has a broken provenance chain. +**Skill composition**: +A skill calling another skill by name to delegate a sub-task — the caller owns the orchestration +decision ("when to do X"), the callee owns the mechanics ("how to do X"). +_Avoid_: chaining, nesting, sub-skill -### Bidirectional reference principle -Files that reference other files should declare those references explicitly. The referencing file carries the forward reference (e.g. content index in `CLAUDE.md`, `references:` in frontmatter). The referenced file carries a `when:` field describing when it is loaded. Both sides should agree — divergence signals staleness. The reverse map ("what files reference this file?") is derived by a reference scanner script, not maintained manually. This principle applies to instruction files, skills, and workflow documents. +**Vale audit prefilter**: +The deterministic Vale pass that runs ahead of `skill-audit`/`agent-audit`'s Description dimension, +so LLM judgment is spent only on what a pattern cannot catch. Mechanics: `docs/spec/gates.md`. +_Avoid_: linting, style check -### agentsmd-author / agentsmd-audit -A skill pair in the `core` plugin for writing, updating, and reviewing a repo's `AGENTS.md` file(s) — the generic open-standard file (see the `AGENTS.md` entry above), including this repo's own. `agentsmd-author` creates/updates AGENTS.md content, supports nested monorepo placement (per the standard's nearest-file-wins precedence), and closes out by invoking `agentsmd-audit` inline. `agentsmd-audit` runs a single combined pass checking three mandatory baselines: secrets/credentials (governance.md hard prohibition — AGENTS.md is committed content), structural completeness (common-sections checklist from the agents.md spec), and accuracy/drift (do referenced commands and paths actually resolve against the repo). `agentsmd-audit` never inspects provider adapter files (see `provider-adapter-author`) — its scope is AGENTS.md content only. Chosen over folding this into `kyberforge` because kyberforge's scope is meta-tooling for the holocron marketplace itself, not generic target-repo documentation; `core` is the intended home for cross-cutting, repo-agnostic utility skills. +**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. +_Avoid_: ticket, card, task -### provider-adapter-author -A companion skill (`core` plugin) that detects a target repo's provider-specific instruction file (`CLAUDE.md`, `.cursor/rules/*.mdc`, `copilot-instructions.md`, etc.) and, where it duplicates content AGENTS.md should own, converts it into a thin adapter that imports AGENTS.md — mirroring this repo's own ADR-0002/ADR-0003 two-tier adapter pattern. Self-validates via its own bundled deterministic script (`scripts/validate-adapter.sh`: checks for an import reference, no duplicated headings, size threshold) rather than a separate paired audit skill — the check is mechanical, so a script suffices per governance.md's "prefer deterministic code for repeatable tasks." `agentsmd-author` calls this skill via skill composition when it detects an existing provider file with overlapping content. +## Relationships -### lint plugin -A standalone, repo-agnostic plugin (`plugins/lint/`) for configuring and running linters — not scoped to kyberforge's own meta-tooling. First linter is Vale (prose style linting), split into two skills per the git/gitea per-concern pattern: `vale-config` (setup — `.vale.ini`, `StylesPath`, styles) and `vale-run` (invoke Vale, interpret/report findings). A `lint-runner` agent composes these for isolated-context lint sweeps; it is report-only **by instruction, not by capability** — its body states "You never edit files" and "Do not edit, fix, or rewrite any flagged content", but nothing enforces that. It previously carried `tools: Bash, Read, Grep, Glob`, which withheld `Edit` outright; plugin-scope APM agents cannot express a `tools:` field at all (ADR-0016 — `apm compile` copies frontmatter verbatim to both Claude Code and Copilot, whose `tools:` vocabularies are incompatible, so a value correct for one harness is wrong for the other), so `plugins/lint/.apm/agents/lint-runner.agent.md` now declares only `name`/`description`/`source_keys` and inherits every tool, `Edit` included. ADR-0016 accepted this loss of enforcement knowingly; the restriction survives as prose the agent is expected to follow. Vale's research docs (`docs/research/docs/vale/`) moved from `plugins/kyberforge/` to `plugins/lint/` to keep the provenance chain same-plugin. +- A **Plugin** bundles one or more **Skills** and agents; a **Plugin marketplace** lists **Plugins**; + **holocron** is this repo wearing that hat. +- Every model-invocable **Skill** pays the **Preload tax**. A **Hand-invoked skill** does not — which + is the first question to settle when authoring one. +- The **Skill context contract** bounds both the **Preload tax** (description) and the body. + A **Dispatch body** is how a skill stays inside it; **Delegation discipline** is how an agent does. +- **AGENTS.md** is the source of always-on rules; a **Thin adapter** imports it and originates + nothing. +- **Skill composition** is the caller/callee split. `forge` routes a genuinely *undecided* artifact + type to the matching author skill — an already-specified fix (file, line, and change known) calls + that author skill directly, because each routing hop re-derives instructions from a shorter brief + and has been observed to drop hard constraints handed down the chain. +- **HITL** and **HOTL** are exclusive per action class, and the choice must be explicit and + documented. **Sycophancy** is why HOTL is not the safe default. +- A **Skill** built on research carries a **Provenance chain**; `skill-audit` fails it when broken. +- **LESSONS.md** feeds the standing files: three or more entries on one pattern graduate the pattern + into the relevant standing document. -### Vale audit prefilter (skill-audit / agent-audit) -Wiring Vale as a deterministic prefilter for `skill-audit`/`agent-audit`'s Description dimension (ADR motivation: issue #84) is repo-specific, not part of the generic `lint` plugin, so it doesn't live in `plugins/lint/` — but per ADR-0014 it also doesn't live at the repo root anymore. Two copies live inside `plugins/kyberforge/`, one per skill, since a plugin's cache-install only copies each skill's own files (no cross-skill sharing): `plugins/kyberforge/.apm/skills/agent-audit/assets/vale/` is canonical (`.vale.ini` plus a custom `Kyberforge` style covering description-opener banning ("This skill/agent..."), vague-capability wording ("helps with", "utilize", ...), and generic "see references/ for details" padding — and a `KyberforgeCopilot` style scoped only to `.agent.md` files for the Copilot-only "Use proactively has no effect" check), and `plugins/kyberforge/.apm/skills/skill-audit/assets/vale/` is a smaller duplicate (`Kyberforge` only, scoped to `SKILL.md`) kept in sync by `scripts/check-vale-style-sync.sh` (pre-push). A root-level `.pre-commit-hooks.yaml` exposes both copies (plus `skill-size-check`) so any external repo can enforce the same rules via `repo: , rev: ` in its own `.pre-commit-config.yaml` — pre-commit clones the pinned rev into its own cache, independent of whether Claude Code or the `kyberforge` plugin is installed at all, and the same mechanism covers CI (`pre-commit run --all-files`). This repo's own `vale-audit-prefilter-skill`/`-agent` pre-commit hooks consume the identical plugin-bundled copies via `repo: local` (not a third root copy, and not a pinned self-reference — a pinned self-reference would lint working-tree edits against the last tagged release rather than the change being made). Every rule is `level: error` and every alert is a FAIL — no ignorable tier, same as shellcheck, the test suite, and conventional-pre-commit. Graded severities do not work here: Vale's exit code keys on `error` alerts alone, so `warning`/`suggestion` rules exit 0 and pre-commit swallows the output of a passing hook, leaving them invisible and blocking nothing. `MinAlertLevel` and `--minAlertLevel` are correspondingly absent from `.vale.ini` and the hook, being no-ops under this model. Vale covers the pattern-matchable sub-checks named in issue #84 (imperative opener, vague filler, `Use proactively`, generic reference-pointer padding) plus, per ADR-0013, one body-wide prose-pattern check ("There is/are" sentence openers) — everything else about body discipline (defaults-vs-menus, why-rationale, non-pattern-matchable judgment calls), near-miss exclusion strength, and control calibration stays LLM judgment. +## Example dialogue -Both skills' Step 1, and the `vale-audit-prefilter-skill`/`-agent` pre-commit hooks, call each copy's own `scripts/vale-wrap.sh` rather than `vale` directly — a workaround for a confirmed Vale 3.15.2 limitation (see `vale-config`'s Gotchas): `text.frontmatter.description` silently stops matching on most — not all — multi-line descriptions. Verified by reproduction, not assumed: `>` folded scalars, plain (unquoted) continuation lines, and single- or double-quoted multi-line scalars all yield 0 alerts and exit 0 on a deliberately-bad fixture, while a `|` literal block spanning the same 2+ lines lints normally (alerts fire, exit 1). The wrapper flattens those three broken forms to one physical line in a scratch copy (padding with blank lines so every other line number is unchanged) before handing off to real `vale`; `|` literal blocks and single-line descriptions pass through untouched, already linting correctly. The plain and quoted forms previously passed silently — unflattened and unmatched — so a bad description in either sailed through the prefilter. Handed no `--config` at all, the wrapper falls back to its own sibling `assets/vale/.vale.ini`, located from `${BASH_SOURCE[0]}` rather than from the cwd — which is why both manifests' `entry:` is now the bare script path with no argument after it. pre-commit prefixes only `entry[0]` with the hook-repo clone path (`cmd = (prefix.path(cmd[0]), *cmd[1:])`), so every later argument resolves against the *consuming* repo's root: a `--config` in `.pre-commit-hooks.yaml` pointed at a path no consumer has and hard-failed every external run with `E100 [--config] Runtime error`. `.pre-commit-config.yaml` drops the argument too, deliberately keeping the two entries identical — the local `repo: local` hook resolved its `--config` correctly only because the consuming repo *was* this repo, and that divergence is why three review rounds exercised a path no external consumer takes and missed the defect. An explicit `--config` still wins, in all three argv forms (`--config X`, `--config=/abs`, `--config=rel`), and a relative one still resolves against the caller's cwd, matching bare `vale`, not the repo root. Both audit skills' Step 1 now passes no `--config` either: it resolves the script relative to the skill's own directory so the call works from an installed plugin cache, but a relative `--config` alongside it would still resolve against the cwd, yielding `E100 Runtime error ... does not exist` and exit 2 — which both skills' fallback misreads as "vale unavailable" and silently downgrades to full LLM judgment. `tests/test-vale-wrap.sh` regression-tests this against skill-audit's copy specifically (its fixtures are all `SKILL.md`-shaped, and only skill-audit's `.vale.ini` has that glob section). Each `.vale.ini`'s section globs are path-agnostic (`[**/SKILL.md]` for skill-audit's copy; `[**/agents/*.md]`/`[**/*.agent.md]` for agent-audit's) and do no scoping on their own: Vale's `*` crosses `/`. Scoping comes from each pre-commit hook's own `files:` regex and from the audit skills passing one explicit file per invocation. The two manifests scope differently on purpose: this repo's `.pre-commit-config.yaml` pins its own layout — `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$` for `-skill`, `^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$` for `-agent` — while the shipped `.pre-commit-hooks.yaml` stays layout-agnostic for external consumers whose skills live anywhere, using `(^|/)SKILL\.md$` and `(^|/)agents/[^/]+\.md$|\.agent\.md$`. Both manifests split the prefilter into two hooks precisely because one combined hook pointed at only one copy would silently 0-file-skip the other file type. A `SKILL.md` outside `plugins/` (e.g. project-scope `.claude/skills/foo/SKILL.md`) still matches `[**/SKILL.md]` and gets linted normally — the globs constrain filename shape, not location. Vale reports 0 files only when the path it is handed matches no glob section at all: a differently-named file, or a directory argument holding nothing that matches. That run prints `✔ 0 errors ... in 0 files.` and exits 0, indistinguishable from a clean pass, so both audits treat a 0-file Vale run as NOT RUN and fall back to full LLM judgment. +> **Dev:** "This one only fires when someone types the slash command. Does its description still need +> trigger words?" +> **Maintainer:** "No — that's a **hand-invoked skill**. The host withholds it from the model-visible +> listing, so it pays no **preload tax** at all and the description is human-facing text." +> **Dev:** "Then the body can be as long as it needs to be?" +> **Maintainer:** "Different budget. The **skill context contract** gates the body whether or not the +> skill is model-invoked — the description competes with every other skill's description, the body +> competes with the caller's live conversation. Four mutually exclusive flows means a **dispatch +> body**: table in `SKILL.md`, one `references/` file per flow." +> **Dev:** "And if I split it into an agent instead?" +> **Maintainer:** "Then you're in **delegation discipline** territory. An agent has no `references/` +> to disclose to, so the failure mode flips — it stops being length and starts being restatement of +> a procedure some skill already owns." -This scope expands per ADR-0013: one cherry-picked low-noise `write-good`/`alex` rule landed in `styles/Kyberforge`, `Kyberforge.SentenceOpenerThereIs` (22 held-out hits, both in-corpus hits clean rewrites, zero suppressions). A second, `Kyberforge.VagueQualifier`, was cherry-picked and then deleted: 2 hits across the skill/agent corpus as it stood at the time of that measurement (2026-08-08, before the `.apm/` restructure), one marginal and one an unfixable false positive (`caveman/SKILL.md` quotes `of course` as an example of filler — a mention, not a use) that forced the repo's only Vale suppression comments. A third, `Kyberforge.CompositionNote`, landed with ADR-0020 and bans architecture and composition prose from a description; it is `level: error` like the rest, and it currently fires 10 times across `gitea-issues`, `gitea-labels-milestones`, `gitea-prs` and `gitea-workflow`, so `pre-commit run --all-files` is red on prose as well as on size until issue #99 lands. Also new is a sibling pre-commit hook, `skill-size-check` (`scripts/skill-size-check.sh`), which carries **two independent gate families that must not be conflated** (see "Skill context contract"). The agentskills.io spec backstop is `MAX_LINES=500` and `MAX_WORDS=2770`, both inclusive and both counting the **whole file including frontmatter** (2,770 is a word-count proxy for the 5,000-token limit, calibrated to the densest prose measured in this repo — 1.81 tokens per word — so even a worst-case `SKILL.md` at the ceiling stays under 5,000 tokens; it is not a percentile of the corpus). ADR-0020 adds a context budget measured differently: description characters 250 SUGGESTION / 400 FAIL, **body-only** words 600 SUGGESTION / 900 FAIL, plus deterministic checks that every boundary routing target resolves, that a body's named `references/.md` all exist, and — SUGGESTION-tier — that a boundary clause is present at all, that `## Gotchas` holds at most five entries, and that it stays under 25% of the body. `skill-audit/scripts/validate.sh` and `agent-audit/scripts/validate.sh` hold their own copies of the shared constants and `tests/test-skill-size-check.sh` asserts the copies agree, so a `SKILL.md` can no longer pass its own audit yet be blocked by the commit hook. Agents take the description gates and no body word gate. `python3` **and PyYAML** are hard requirements — the earlier hand-rolled frontmatter fallback is gone, because a fallback that silently mis-parses a scalar shape reports a vacuous pass. Scoped to `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$` only, same as `vale-audit-prefilter-skill`, so it never lints `docs/research/examples/` reference skills. It's also exposed in the root-level `.pre-commit-hooks.yaml` as `kyberforge-skill-size-check` — it has no external asset dependency, so it needed no relocation, only exposure to external consumers. File scope (`SKILL.md` + agent files) and enforcement model (rules land directly in `styles/Kyberforge`, blocking immediately, no trial tier) stay unchanged; governance.md/CONTROLS.md were evaluated and excluded as rule sources (nothing prose-pattern-matchable to mine). House convention: banned phrasing that must be mentioned rather than used goes in backticks or a fenced code block — Vale skips code spans and fences, so no suppression is needed; inline `` (HTML-comment form; the MDX `{/* */}` form does not work in plain Markdown) is the fallback only where backticking is impossible. +## Flagged ambiguities -### LESSONS.md -Long-loop feedback log for patterns observed across sessions. Three or more entries on the same pattern graduate to the relevant standing file (e.g. a coding convention, a governance rule). Updated by the session-handoff skill or directly by the human. Lives at the repo root. +- "skill" was used for both the authored `SKILL.md` under `plugins//.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. +- "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 + clean-context recheck — resolved: these are two distinct layers, kept separate precisely because + an audit running in the same context as the work it checks shares that work's blind spots. diff --git a/docs/adr/0013-vale-harness-scope-and-rule-sources.md b/docs/adr/0013-vale-harness-scope-and-rule-sources.md index 2dfb67d..f35932d 100644 --- a/docs/adr/0013-vale-harness-scope-and-rule-sources.md +++ b/docs/adr/0013-vale-harness-scope-and-rule-sources.md @@ -9,6 +9,12 @@ deferred PR #85 review item to broaden that coverage, retroactively captures #84 (since it was never recorded as a decision in its own right), and layers the expansion on top without reversing or weakening the original four rules. +**2026-08-17 amendment.** The CONTEXT.md section named above no longer holds that documentation. +CONTEXT.md was cut back to a glossary and the prefilter's mechanics — the two-copy style layout, +`vale-wrap.sh`, the `--config` argv defect, the rule inventory, and the 0-files-means-NOT-RUN +fallback — moved to `docs/spec/gates.md`. Read that file, not CONTEXT.md, for the harness itself; +this ADR still owns the scope decision. + **File scope stays the same.** `SKILL.md` plus agent files (`**/agents/*.md`, `**/*.agent.md`) only — matching the existing prefilter's globs. Skill-level `README.md` files and `plugin.json` manifests are not added: README.md files are navigational, not diff --git a/docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md b/docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md index 77b8a0e..d8fb9d3 100644 --- a/docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md +++ b/docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md @@ -56,6 +56,8 @@ new hand-maintained manifest format. that work through to merge. - `CONTEXT.md`'s "Plugin"/"Plugin marketplace" glossary entries were rewritten in issue #90 to describe the compiled-output model directly, rather than carrying a forward-pointer to this ADR. + Superseded 2026-08-17: CONTEXT.md was cut back to one-line definitions, and the compiled-output + model is now described in `docs/spec/architecture.md`. ## Considered options diff --git a/docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md b/docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md index ad7856a..35b8c2d 100644 --- a/docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md +++ b/docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md @@ -329,7 +329,9 @@ mirror does carry, and is reported. stands; this ADR fixes the second, previously-unverified half. - `CONTEXT.md`'s "Plugin" and "Plugin marketplace" glossary entries are updated to describe the flat mirror as a second compiled-output category, alongside the existing - `.claude-plugin/plugin.json`/`marketplace.json` description. + `.claude-plugin/plugin.json`/`marketplace.json` description. Superseded 2026-08-17: CONTEXT.md was + cut back to one-line definitions and no longer describes either compiled-output category; + `docs/spec/architecture.md` is where the mirror is documented. - A future apm release that ships a native `.apm/`-aware plugin.json compiler (closing this gap upstream) would let `sync-plugin-content.sh` and its drift gate be deleted outright — nothing in this ADR's decision depends on the flat mirror existing beyond satisfying the current installer's diff --git a/docs/spec/architecture.md b/docs/spec/architecture.md index 3e6307a..137ad93 100644 --- a/docs/spec/architecture.md +++ b/docs/spec/architecture.md @@ -21,12 +21,23 @@ 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 @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), then lists the content index. 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 beside it. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication. ## Plugin model Skills, agents, MCP servers, and hooks are distributed as self-contained plugin units under `plugins/`, installed independently — via `apm install` here, or `claude plugin install @holocron` for a host consuming the marketplace natively (ADR-0018). Each plugin is an **apm package**: `plugins//apm.yml` plus a hand-authored `plugins//.apm/{skills,agents,hooks,commands,instructions,extensions}/` tree (ADR-0015). There is no hand-maintained `plugin.json` — every manifest and every host-visible content directory is compiled from that source. +Which plugin a new skill belongs in follows from what each one is scoped to. The boundary that matters most in practice is `core` vs `kyberforge`: `core` is the home for cross-cutting, repo-agnostic utility skills that a consumer would want against *their* repo, while `kyberforge` is meta-tooling for the holocron marketplace itself. A skill that authors a target repo's `AGENTS.md` is `core`; a skill that audits a `SKILL.md` against this marketplace's contract is `kyberforge`. + +| Plugin | Scope | +|---|---| +| `core` | Authoring and auditing a repo's `AGENTS.md` and the provider adapter files that defer to it | +| `git` | Conventional commits, branch management, history, worktrees, remotes | +| `gitea` | Issues, pull requests, labels, milestones, releases, wikis | +| `kyberforge` | Creating and maintaining a Claude Code / Copilot CLI plugin marketplace — this repo's own meta-tooling | +| `lint` | Configuring and running linters; repo-agnostic, first linter is Vale (`vale-config` / `vale-run`, plus the `lint-runner` agent) | +| `bin` | Unsorted skills that have not earned a home yet | + Two compilers produce the plugin roots you see in the tree: - **`apm pack` compiles the manifests** (ADR-0015). Per plugin: `.claude-plugin/plugin.json` and `.github/plugin/plugin.json`, both generated from `plugins//apm.yml`. Repo-wide, from the root `apm.yml`'s `marketplace:` block: `.claude-plugin/marketplace.json` (apm's `claude` output profile) and `.agents/plugins/marketplace.json` (its `codex` profile, a differently-shaped file). Those two are the only marketplace outputs apm has profiles for — the third root manifest, `.github/plugin/marketplace.json` (Copilot CLI's legacy path), is a byte-identical mirror of the Claude one maintained by `scripts/sync-marketplace-mirror.sh` and gated by the `check-marketplace-mirror-sync` pre-push hook. @@ -40,6 +51,8 @@ That immunity is positional, not by filename. Anything placed *inside* a mirrore `core/instructions/governance.md` is the always-on governance instruction file. Unlike the on-demand instruction files in the content index, governance.md is loaded into every Claude session via `@import` in `providers/claude-code/CLAUDE.md`. This is a technical guarantee, not a behavioural instruction — `@import` causes Claude Code to expand and load the file at launch, before any interaction begins. +Those on-demand files are plain markdown — no frontmatter, no schema. The agent decides when to read each one from task context and the content index label alone. Frontmatter is deferred until there is evidence that agents are loading the wrong files in practice; it is a deliberate deferral, not an oversight to close. + The governance layer has two phases: - **Phase 1** (complete): instruction and documentation layer — `governance.md` loaded via `@import`; `docs/ai-constitution.md` and `docs/wiki/HUMANS.md` as human-facing reference; `CONTEXT.md` extended with governance domain language. - **Phase 2** (planned): deterministic enforcement layer — pre-commit hooks, CI gates, secret scanning, licence scanning. Specified in `docs/research/governance_principles/CONTROLS.md`. @@ -57,6 +70,10 @@ This repo also has a `CLAUDE.md` at its root — the Claude Code entry point for `CONTEXT.md` is therefore **not** always-loaded. `AGENTS.md` instructs agents to read it at session start, which is a behavioural instruction, not an `@import` guarantee — `LESSONS.md`'s 2026-05-17 entry proposed adding the import and it was never applied. Treat that entry as open work rather than a record of a landed change. +## 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. + ## Provider model `core/` is never tool-specific. `providers/` is never shared. When adding a new provider, write an adapter in `providers//` that translates core content into the tool's expected format and location. The core content itself does not change. diff --git a/docs/spec/gates.md b/docs/spec/gates.md index ef83e09..1df8f64 100644 --- a/docs/spec/gates.md +++ b/docs/spec/gates.md @@ -87,7 +87,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`. +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 +[External consumers](#external-consumers-the-root-pre-commit-hooksyaml)). ### Two independent gate families, neither replaced the other @@ -279,6 +281,81 @@ is needed: the `Kyberforge` styles are **committed** under `plugins/kyberforge/.apm/skills/{skill-audit,agent-audit}/assets/vale/styles/`, not downloaded packages (ADR-0014). +### Two copies, one canonical + +Wiring Vale as a deterministic prefilter for `skill-audit`/`agent-audit`'s Description dimension +(motivation: issue #84) is repo-specific, not part of the generic `lint` plugin, so it does not live +in `plugins/lint/` — and per ADR-0014 it no longer lives at the repo root either. It lives **twice**, +one copy per skill, both under `plugins/kyberforge/.apm/skills/`: + +| Copy | Styles | `.vale.ini` sections | +|---|---|---| +| `agent-audit/assets/vale/` — **canonical** | `Kyberforge`, `KyberforgeCopilot` | `[**/agents/*.md]`, `[**/*.agent.md]` | +| `skill-audit/assets/vale/` — smaller duplicate | `Kyberforge` | `[**/SKILL.md]` | + +Duplicated rather than shared because a plugin's cache-install copies only each skill's own files — +there is no cross-skill sharing to point at. `check-vale-style-sync` at pre-push is what keeps them +from drifting; `KyberforgeCopilot` is the one deliberate inequality, being scoped only to `.agent.md` +files for the Copilot-only "`Use proactively` has no effect" check. + +### What Vale owns, and what stays LLM judgment + +Eleven rule files across the two copies, six distinct rules: + +| Rule | Vale scope | Bans | From | +|---|---|---|---| +| `Kyberforge.DescriptionOpener` | `text.frontmatter.description` | non-imperative openers ("This skill/agent…") | issue #84 | +| `Kyberforge.VagueWording` | `text.frontmatter.description` | vague capability wording ("helps with", "utilize", …) | issue #84 | +| `Kyberforge.PaddingPhrase` | `text` | generic "see `references/` for details" padding | issue #84 | +| `KyberforgeCopilot.ProactivePhrase` | `text.frontmatter.description` | `Use proactively` (no effect in Copilot) | issue #84 | +| `Kyberforge.SentenceOpenerThereIs` | `sentence` | "There is/are" sentence openers | ADR-0013 | +| `Kyberforge.CompositionNote` | `text.frontmatter.description` | architecture and composition prose in a description | ADR-0020 | + +Vale covers the **pattern-matchable** sub-checks named in issue #84 plus, per ADR-0013, one +cherry-picked body-wide prose-pattern rule. Everything else stays LLM judgment: defaults-vs-menus, +why-rationale, the non-pattern-matchable body-discipline calls, near-miss exclusion strength, and +control calibration. New rules land directly in `styles/Kyberforge` and block immediately — there is +no trial tier. + +The cherry-pick record, so it is not re-litigated: + +- `Kyberforge.SentenceOpenerThereIs` **landed** — 22 held-out hits, both in-corpus hits clean + rewrites, zero suppressions needed. +- `Kyberforge.VagueQualifier` was cherry-picked and then **deleted**. 2 hits across the corpus as it + stood on 2026-08-08 (before the `.apm/` restructure): one marginal, and one unfixable false + positive — `caveman/SKILL.md` quotes `of course` as an example of filler, a mention rather than a + use — which forced the repo's only Vale suppression comments. +- `governance.md` and `CONTROLS.md` were evaluated as rule sources and **excluded**: nothing + prose-pattern-matchable to mine. + +### Why every rule is `level: error` + +Every alert is a FAIL, with no ignorable tier — same all-or-nothing model as shellcheck, the test +suite, and conventional-pre-commit. Graded severities do not work here: **Vale's exit code keys on +`error` alerts alone**, so a `warning` or `suggestion` rule exits 0, and pre-commit swallows a +passing hook's output. Such a rule would be invisible and would block nothing. + +`MinAlertLevel` and `--minAlertLevel` are correspondingly **absent** from both `.vale.ini` files and +from the hook definitions. Under this model they are no-ops; adding one is not a missing knob. + +The `verbose: true` escape hatch that makes `skill-size-check`'s SUGGESTION tier audible has no +analogue here — Vale has no tier to make audible. + +### External consumers: the root `.pre-commit-hooks.yaml` + +The root `.pre-commit-hooks.yaml` exposes both Vale copies (`kyberforge-vale-audit-skill`, +`kyberforge-vale-audit-agent`) plus `kyberforge-skill-size-check`, so any external repo can enforce +the same rules with `repo: , rev: ` in its own `.pre-commit-config.yaml`. +pre-commit clones the pinned rev into its own cache, independent of whether Claude Code or the +`kyberforge` plugin is installed at all; the same mechanism covers CI via `pre-commit run +--all-files`. `skill-size-check` has no external asset dependency, so it needed no relocation under +ADR-0014 — only exposure. + +This repo's own `vale-audit-prefilter-skill` / `-agent` hooks consume the **identical** +plugin-bundled copies via `repo: local`. Deliberately not a third root copy, and deliberately **not a +pinned self-reference** — a pinned self-reference would lint working-tree edits against the last +tagged release rather than against the change being made. + ### Pre-commit Two prefilter hooks, with `.apm/`-scoped `files:` patterns: @@ -292,6 +369,92 @@ Only the **authoring source** triggers them. A `SKILL.md` in the generated flat neither pattern, so prose findings surface only when you edit the file you are supposed to be editing. Without the binary the hooks fail with a bare "command not found" and no install pointer. +**Two hooks, not one combined hook.** Both manifests split the prefilter in two precisely because a +single hook can point at only one copy, and that copy would silently 0-file-skip the other file +shape (see [A 0-file Vale run is NOT RUN](#a-0-file-vale-run-is-not-run)). + +### The `.vale.ini` globs do no scoping + +Each `.vale.ini`'s section globs are **path-agnostic** — `[**/SKILL.md]` for skill-audit's copy, +`[**/agents/*.md]` and `[**/*.agent.md]` for agent-audit's — and constrain filename *shape*, not +location: Vale's `*` crosses `/`. A `SKILL.md` outside `plugins/` (a project-scope +`.claude/skills/foo/SKILL.md`, say) still matches `[**/SKILL.md]` and gets linted normally. + +All scoping therefore comes from the pre-commit hook's own `files:` regex and from the audit skills +passing one explicit file per invocation. The two manifests scope **differently on purpose**: + +| Manifest | `-skill` | `-agent` | +|---|---|---| +| `.pre-commit-config.yaml` (pins this repo's layout) | `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$` | `^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$` | +| `.pre-commit-hooks.yaml` (layout-agnostic for consumers) | `(^\|/)SKILL\.md$` | `(^\|/)agents/[^/]+\.md$\|\.agent\.md$` | + +Narrowing a `.vale.ini` glob to a `plugins/`-shaped path to "tighten" it breaks the consumer case, +and `check-vale-style-sync`'s probe set is built to catch exactly that. + +### `vale-wrap.sh`, never bare `vale` + +Both audit skills' Step 1 and both pre-commit hooks call **each copy's own** +`scripts/vale-wrap.sh`, not `vale`. It works around a confirmed **Vale 3.15.2** limitation: +`text.frontmatter.description` silently stops matching on most — not all — multi-line descriptions. + +Verified by reproduction on a deliberately-bad fixture, not assumed: + +| Description scalar spanning 2+ lines | Vale's behaviour | +|---|---| +| `>` folded block | 0 alerts, exit 0 — **broken** | +| plain (unquoted) continuation lines | 0 alerts, exit 0 — **broken** | +| single- or double-quoted, wrapped | 0 alerts, exit 0 — **broken** | +| `\|` literal block | alerts fire, exit 1 — lints normally | + +The wrapper flattens the three broken forms to a single-line scalar in a scratch copy — or, for the +rare value no inline scalar can spell verbatim, a `|-` block with one content line — padding with +blank lines so **every other line number is unchanged**. `|` literal blocks and single-line +descriptions pass through untouched. Most descriptions in this repo are `>` blocks, so before the +wrapper a bad description in any of the three broken forms sailed straight through the prefilter. + +### The `--config` argv defect + +Handed **no `--config` at all**, the wrapper falls back to its own sibling `assets/vale/.vale.ini`, +located from `${BASH_SOURCE[0]}` rather than from the cwd. That is why both manifests' `entry:` is +now the bare script path with **no argument after it**. + +pre-commit prefixes only `entry[0]` with the hook-repo clone path (`cmd = (prefix.path(cmd[0]), +*cmd[1:])`), so every later argument resolves against the **consuming** repo's root. A `--config` in +`.pre-commit-hooks.yaml` therefore pointed at a path no consumer has and hard-failed every external +run with `E100 [--config] Runtime error`. + +`.pre-commit-config.yaml` drops the argument too, deliberately keeping the two entries identical. +The local `repo: local` hook resolved its `--config` correctly only because the consuming repo *was* +this repo — and that divergence is why three review rounds exercised a path no external consumer +takes and missed the defect. **Do not reintroduce a `--config` to either manifest to make the local +run "explicit".** + +An explicit `--config` from any other caller still wins, in all three argv forms (`--config X`, +`--config=/abs`, `--config=rel`), and a relative one resolves against the caller's cwd — matching +bare `vale`, not the repo root. + +Both audit skills' Step 1 passes no `--config` either. Step 1 resolves the script relative to the +skill's own directory so the call works from an installed plugin cache; a relative `--config` +alongside it would resolve against the cwd instead, yielding `E100 Runtime error … does not exist` +and exit 2 — which both skills' fallback misreads as "vale unavailable" and silently downgrades to +full LLM judgment. + +`tests/test-vale-wrap.sh` regression-tests this against **skill-audit's** copy specifically: its +fixtures are all `SKILL.md`-shaped, and only skill-audit's `.vale.ini` carries that glob section. + +### A 0-file Vale run is NOT RUN + +Vale reports 0 files only when the path it is handed matches **no glob section at all** — a +differently-named file, or a directory argument holding nothing that matches. That run prints + +``` +✔ 0 errors ... in 0 files. +``` + +and exits 0, indistinguishable from a clean pass. Both audits therefore treat a 0-file Vale run as +**NOT RUN** and fall back to full LLM judgment rather than reporting the Description dimension +clean. + ### Pre-push `vale` is a **pre-push** dependency too, not only pre-commit. `check-vale-style-sync` runs **six @@ -314,6 +477,16 @@ Use it only on a machine that genuinely cannot install `vale`, and read that lin was not checked", not as a pass. The hook is `verbose: true` for exactly that reason — its clean output is a single line, so it costs one line per push. +### Mentioning banned phrasing without tripping the rule + +House convention: banned phrasing that must be **mentioned** rather than used goes in backticks or a +fenced code block. Vale skips code spans and fences, so no suppression is needed — which is why this +document quotes `Use proactively` and "There is/are" the way it does. + +Inline `` is the fallback **only** where backticking is impossible. Use the +HTML-comment form; the MDX `{/* */}` form does not work in plain Markdown. The one time a rule forced +suppression comments, the rule was deleted instead (see the `VagueQualifier` entry above). + ## Tests ```