From 0edbb783e4f37cc85d0df805cc4607d73e890e9e Mon Sep 17 00:00:00 2001 From: Harsh Singh Date: Tue, 28 Jul 2026 18:07:07 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20zed=20target=20=E2=80=94=20.zed=20detec?= =?UTF-8?q?tion=20and=20Zed's=20silent=20frontmatter=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zed reads the vendor-neutral .agents/skills//SKILL.md path, but nothing in a Zed-only repo announces that: the `agents` adapter probes .agents/ and .codex/, neither of which Zed creates. A repo whose only agent marker was .zed/ therefore compiled to nothing but the eager AGENTS.md floor — paying a skill's whole body every session on an agent that can lazy-load it for free. The new target detects .zed/ and emits the byte-identical file `agents` does, so a repo with both compiles that skill once, not twice. Ten targets, nine output files. It also surfaces two constraints Zed enforces and KSF does not, both of which Zed applies silently — the skill is dropped at load with no diagnostic in the UI, which is the capability loss spec §2 forbids: - name must match ^[a-z0-9]+(-[a-z0-9]+)*$. KSF's NAME_RE is looser and permits a doubled or trailing hyphen, so `tidy--commits` is a legal skill name Zed will not load. - description must be non-empty and at most 1024 UTF-8 *bytes*, not characters — a trap for non-ASCII text, where 600 characters can be 1,200 bytes. Manifested skills are bounded at 200 characters, so only an unmanifested SKILL.md, whose frontmatter is copied through unvalidated, can breach either bound. No disable-model-invocation key is emitted: KSF has no field meaning "never auto-invoke" (triggers.auto is a hint *for* auto-invocation), so synthesizing a value would guess at the author's intent. Fixes a related honesty bug found while wiring it up. `explain zed` answered "no capability degradation" for a skill Zed discards entirely — true, but it reads as false. Adapters can now declare target-specific constraints via an optional lint() separate from emit(), and explain reads them directly. --- CHANGELOG.md | 9 ++++ README.md | 6 +-- docs/benchmarks/README.md | 2 + docs/roadmap.md | 2 +- packages/cli/package-lock.json | 4 +- packages/cli/package.json | 2 +- packages/cli/scripts/benchmark.mjs | 10 ++-- packages/cli/scripts/test.mjs | 47 +++++++++++++++++++ packages/cli/src/adapters.ts | 74 ++++++++++++++++++++++++++++-- packages/cli/src/commands.ts | 5 ++ site/404.html | 2 +- site/benchmark.html | 12 +++-- site/changelog.html | 15 +++++- site/docs/adapters.html | 35 ++++++++++---- site/docs/authoring.html | 4 +- site/docs/cli.html | 6 +-- site/docs/config.html | 7 +-- site/docs/format.html | 2 +- site/docs/index.html | 2 +- site/docs/quickstart.html | 2 +- site/index.html | 20 +++++++- site/skills.html | 2 +- 22 files changed, 227 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee49d4..2e0a429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Format: [Keep a Changelog](https://keepachangelog.com). Versioning: semver — for skills *and* for this CLI, breaking prompt changes are breaking changes. +## [0.12.0] — 2026-07-28 + +### Added +- **`zed` adapter — the tenth target.** Zed reads the vendor-neutral `.agents/skills//SKILL.md` path, but nothing in a Zed-only repo announces that: the `agents` adapter probes `.agents/` and `.codex/`, neither of which Zed creates. A repo whose only agent marker was `.zed/` therefore compiled to nothing but the eager `AGENTS.md` floor — paying a skill's whole body every session on an agent that can lazy-load it for free. The new target detects `.zed/` and emits the byte-identical file `agents` does, so a repo with both compiles that skill once, not twice. **Ten targets, nine output files.** +- **Zed's silent frontmatter rejections are now visible.** Zed's skill loader is stricter than KSF and drops a non-conforming skill at load with no diagnostic in the UI — the silent capability loss spec §2 forbids. Two constraints are checked and warned about at `compile` (so `--strict` fails on them) and in `explain`: `name` must match `^[a-z0-9]+(-[a-z0-9]+)*$`, which rejects the doubled and trailing hyphens KSF's own name rule permits (`tidy--commits` is a legal skill name Zed will not load); and `description` must be non-empty and at most **1024 UTF-8 bytes, not characters** — a trap for any non-ASCII description, where 600 characters can be 1,200 bytes. A manifested skill is bounded at 200 characters, so only an unmanifested `SKILL.md`, whose frontmatter is copied through unvalidated, can breach either bound. No `disable-model-invocation` key is emitted: KSF has no field meaning "never auto-invoke", so synthesizing one would guess at the author's intent. + +### Fixed +- **`explain` no longer answers "no capability degradation" about a skill the target refuses to load.** Adapters can now declare target-specific constraints separately from `emit()`, and `explain` reads them directly — a true statement that read as a false one. + ## [0.11.0] — 2026-07-28 Credibility pass. A staff-level review of the shipped product found the docs, site, and CLI contradicting each other — fatal for a tool whose whole pitch is honest measurement. Every fix here aligns the surface with reality; none is a new feature. diff --git a/README.md b/README.md index 65b8b83..7b4060a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ npm version npm downloads CI - 9 agent targets + 10 agent targets Apache-2.0

@@ -39,7 +39,7 @@ kitbash install gh:singhharsh1708/kitbash/examples/skills/prereview kitbash compile ``` -What's working right now: `init`, `install` (via `gh:`, `owner/repo`, or `file:`), and `compile` to nine targets — Claude Code, Cursor, the vendor-neutral `.agents/skills/` path, Copilot, Cline, Devin (ex-Windsurf), Gemini CLI, Aider's CONVENTIONS.md, and the AGENTS.md floor. Declared `/commands` compile down to native slash commands. You also get `doctor`, `list`, `remove`, budget enforcement, a content-hash lockfile with drift detection, stale-output pruning, and `--strict`. Evals, update diffs, and everything else are on the [roadmap](docs/roadmap.md). +What's working right now: `init`, `install` (via `gh:`, `owner/repo`, or `file:`), and `compile` to ten targets — Claude Code, Cursor, the vendor-neutral `.agents/skills/` path, Zed, Copilot, Cline, Devin (ex-Windsurf), Gemini CLI, Aider's CONVENTIONS.md, and the AGENTS.md floor. Declared `/commands` compile down to native slash commands. You also get `doctor`, `list`, `remove`, budget enforcement, a content-hash lockfile with drift detection, stale-output pruning, and `--strict`. Evals, update diffs, and everything else are on the [roadmap](docs/roadmap.md). Already have skills? A plain SKILL.md folder — the [skills.sh](https://www.skills.sh) / Claude Skills convention — installs directly with `kitbash install owner/repo`. It's basically KSF without the manifest, so Kitbash fills in defaults and marks it `unmanifested` since nobody declared a budget or permissions for it. skills.sh is good at distributing skills; Kitbash is about treating them like real engineering artifacts. @@ -172,7 +172,7 @@ v0.1 is intentionally a thin slice: KSF, `compile`, three adapters, and one skil No. It's a compiler, a package manager, and a format spec. Prompt collections are the thing that gets compiled. **I already use skills.sh / Claude skills.** -Keep them. They install directly with `kitbash install owner/repo`. You pick up nine targets, a lockfile, and a token-cost report, and you don't give anything up. +Keep them. They install directly with `kitbash install owner/repo`. You pick up ten targets, a lockfile, and a token-cost report, and you don't give anything up. **What if I stop using Kitbash?** Nothing breaks. The compiled output is plain files in your repo. Delete `kitbash.toml` and everything keeps working the way it does now. diff --git a/docs/benchmarks/README.md b/docs/benchmarks/README.md index bfc5ac8..cae35bc 100644 --- a/docs/benchmarks/README.md +++ b/docs/benchmarks/README.md @@ -18,6 +18,7 @@ Token counts are estimates (~4 chars/token), the same estimator the compiler enf | claude-code | 567 | 40 | lazy | | cursor | 568 | 40 | lazy | | agents | 567 | 40 | lazy | +| zed | 567 | 40 | lazy | | copilot | 567 | 40 | lazy | | cline | 539 | 539 | eager | | windsurf | 569 | 40 | lazy | @@ -34,6 +35,7 @@ Token counts are estimates (~4 chars/token), the same estimator the compiler enf | claude-code | 880 | 19 | lazy | | cursor | 879 | 19 | lazy | | agents | 880 | 19 | lazy | +| zed | 880 | 19 | lazy | | copilot | 880 | 19 | lazy | | cline | 859 | 859 | eager | | windsurf | 881 | 19 | lazy | diff --git a/docs/roadmap.md b/docs/roadmap.md index 39a3034..5f28862 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -48,7 +48,7 @@ Deferred out of v0.1 on purpose: more adapters, more skills, index, evals tier 2 - Community index (registry repo, Homebrew-tap model): `kitbash install prereview` short names, `kitbash search`. - `kitbash publish` (validates, tags, points the index). - Loadouts: `kitbash install loadout:oss-maintainer`. -- ✅ Remaining adapters, landed early: `windsurf` (now Devin Desktop), `cline`, `aider`, and `agents` — the vendor-neutral `.agents/skills/` path that Codex, Cursor, Copilot, Gemini CLI, Roo, Amp, OpenCode, Zed and Antigravity all read — 9 targets total. +- ✅ Remaining adapters, landed early: `windsurf` (now Devin Desktop), `cline`, `aider`, `agents` — the vendor-neutral `.agents/skills/` path that Codex, Cursor, Copilot, Gemini CLI, Roo, Amp, OpenCode and Antigravity all read — and `zed`, which shares that path but detects `.zed/` and enforces Zed's own frontmatter rules. 10 targets total. - Docs site with the skill catalog + measured eval results per skill. - Skill badges, measurement-only: eval pass rate, compiled token cost, auto-derived compatibility matrix, signed status. No star ratings — measurement over popularity, by design. - **Launch moment.** The demo is one command turning a bare repo into a four-assistant, team-standard setup. Ponytail proved a single good skill can pull 75k stars; ours ride on infrastructure others can build on, which is the durable version of that story. diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index 8ba9a20..af4ce7d 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "kitbash", - "version": "0.11.0", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kitbash", - "version": "0.11.0", + "version": "0.12.0", "license": "Apache-2.0", "bin": { "kitbash": "dist/index.js" diff --git a/packages/cli/package.json b/packages/cli/package.json index 22fc758..9b3f4a9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "kitbash", - "version": "0.11.0", + "version": "0.12.0", "description": "The package manager and compiler for AI agent skills — write once, run in every coding agent", "license": "Apache-2.0", "author": "Harsh Singh", diff --git a/packages/cli/scripts/benchmark.mjs b/packages/cli/scripts/benchmark.mjs index 6555a19..3dc68bd 100644 --- a/packages/cli/scripts/benchmark.mjs +++ b/packages/cli/scripts/benchmark.mjs @@ -1,5 +1,5 @@ /** - * Measured benchmark: one source skill → 8 targets, real token cost per target. + * Measured benchmark: one source skill → every target, real token cost per target. * * Runs the actual compile pipeline on committed fixtures (no network), then * measures the emitted output the way an agent pays for it: standing cost @@ -44,7 +44,11 @@ function measure(tmp, target, skillName) { return estimateTokens(read(`.claude/skills/${skillName}/SKILL.md`)); case "cursor": return estimateTokens(read(`.cursor/rules/${skillName}.mdc`)); + // zed reads the same vendor-neutral path and emits the same bytes, so it + // measures identically — listed rather than folded in, because a reader + // looking up "what does Zed cost" must find a row. case "agents": + case "zed": return estimateTokens(read(`.agents/skills/${skillName}/SKILL.md`)); case "copilot": return estimateTokens(read(`.github/skills/${skillName}/SKILL.md`)); @@ -68,8 +72,8 @@ function measure(tmp, target, skillName) { const tmp = mkdtempSync(join(tmpdir(), "kitbash-bench-")); const skills = []; try { - // Every target present so all eight adapters fire. - for (const d of [".claude", ".cursor", ".agents", ".clinerules", ".windsurf", ".github"]) mkdirSync(join(tmp, d)); + // Every target present so every adapter fires. + for (const d of [".claude", ".cursor", ".agents", ".zed", ".clinerules", ".windsurf", ".github"]) mkdirSync(join(tmp, d)); writeFileSync(join(tmp, "GEMINI.md"), ""); writeFileSync(join(tmp, "CONVENTIONS.md"), ""); diff --git a/packages/cli/scripts/test.mjs b/packages/cli/scripts/test.mjs index 53adf9c..37f4f72 100644 --- a/packages/cli/scripts/test.mjs +++ b/packages/cli/scripts/test.mjs @@ -699,6 +699,53 @@ try { rmSync(devin, { recursive: true, force: true }); } +// --- zed: .zed detection, and the frontmatter constraints Zed enforces silently --- + +const zed = mkdtempSync(join(tmpdir(), "kitbash-zed-")); +try { + mkdirSync(join(zed, ".zed")); + run(["init"], zed); + run(["install", `file:${fixture}`, "--yes"], zed); + const c = run(["compile"], zed); + + // The gap this target closes: before it, a Zed-only repo compiled to nothing + // but the eager AGENTS.md floor and paid the whole body every session. + const zedSkill = join(zed, ".agents/skills/prereview/SKILL.md"); + check("zed: .zed alone emits the vendor-neutral skills path", existsSync(zedSkill), c.out); + check("zed: emits Zed's frontmatter", /^---\nname: prereview\ndescription: "/.test(readFileSync(zedSkill, "utf8")), c.out); + check("zed: a conforming skill draws no constraint warning", !c.out.includes("→ zed:"), c.out); + + // zed and agents write the same path; a repo with both must compile it once. + const both = run(["compile"], zed); // .agents/ now exists, so the agents adapter fires too + const writes = (both.out.match(/→ \.agents\/skills\/prereview\/SKILL\.md/g) ?? []).length; + check("zed + agents write the shared path once, no conflict", writes === 1 && !both.out.includes("conflict:"), both.out); + + // Zed's own loader is stricter than KSF and rejects without a diagnostic, so + // an unmanifested SKILL.md — frontmatter copied through unvalidated — is the + // one input that can reach it malformed. + const badZed = (name, description) => { + const d = join(zed, `${name}-src`); + mkdirSync(d); + writeFileSync(join(d, "SKILL.md"), `---\nname: ${name}\ndescription: ${description}\n---\n\nBody one.\n\nBody two.\n`); + run(["install", `file:${d}`, "--yes"], zed); + }; + badZed("tidy--commits", "Doubled hyphen is legal KSF, illegal in Zed"); + badZed("empty-desc", '""'); + badZed("wide-desc", "é".repeat(600)); // 600 chars, 1200 bytes — the byte-vs-char trap + + const bad = run(["compile"], zed); + check("zed: doubled hyphen in name warned", bad.out.includes("tidy--commits → zed: name must match"), bad.out); + check("zed: empty description warned", bad.out.includes("empty-desc → zed: description is empty"), bad.out); + check("zed: description measured in bytes, not characters", bad.out.includes("wide-desc → zed: description is 1200 bytes"), bad.out); + check("zed: constraint breaches are warnings, so --strict fails", run(["compile", "--strict"], zed).status === 1); + + // explain must not answer "no capability degradation" about a skill the target discards + const ex = run(["explain", "tidy--commits", "zed"], zed); + check("zed: explain surfaces the rejection", ex.status === 0 && ex.out.includes("✗ tidy--commits → zed: name must match"), ex.out); +} finally { + rmSync(zed, { recursive: true, force: true }); +} + // --- trust & review: pre-install review, --yes, [policy] allowlists --- const trust = mkdtempSync(join(tmpdir(), "kitbash-trust-")); diff --git a/packages/cli/src/adapters.ts b/packages/cli/src/adapters.ts index f08e457..a41a676 100644 --- a/packages/cli/src/adapters.ts +++ b/packages/cli/src/adapters.ts @@ -39,6 +39,12 @@ export interface Adapter { loading: "eager" | "lazy"; detect(root: string): boolean; emit(skill: LoadedSkill, body: string, root: string): AdapterOutput; + /** + * Constraints this target enforces on a skill beyond what KSF validates, as + * warnings. emit() folds these into its own; `explain` reads them directly, so + * it can't answer "no degradation" about a skill the target silently discards. + */ + lint?(skill: LoadedSkill): string[]; } export const GENERATED_MARK = "generated by kitbash"; @@ -173,14 +179,23 @@ const copilot = skillDirAdapter("copilot", ".github/skills", (root) => existsSyn * Agent Skills directories (agentskills.io): a folder per skill whose SKILL.md * carries `name` and `description` in frontmatter. Hosts inject only that * metadata at session start and load the body on demand, so these are lazy. + * + * `lint` reports constraints a specific host enforces on that frontmatter beyond + * what KSF itself validates. */ -function skillDirAdapter(id: string, dir: string, detect: (root: string) => boolean): Adapter { +function skillDirAdapter( + id: string, + dir: string, + detect: (root: string) => boolean, + lint?: (skill: LoadedSkill) => string[], +): Adapter { return { id, // Writes only SKILL.md — no scripts/ copied. Claim nothing emit() doesn't deliver. capabilities: [], loading: "lazy", detect, + ...(lint ? { lint } : {}), emit(skill, body) { const { name, description } = skill.manifest.skill; return { @@ -190,7 +205,7 @@ function skillDirAdapter(id: string, dir: string, detect: (root: string) => bool content: `---\nname: ${name}\ndescription: ${yamlString(description)}\n---\n${header(skill)}\n\n${body}`, }, ], - warnings: degradationWarnings(skill, this), + warnings: [...degradationWarnings(skill, this), ...(lint?.(skill) ?? [])], }; }, }; @@ -210,6 +225,59 @@ const agents = skillDirAdapter( (root) => existsSync(join(root, ".agents")) || existsSync(join(root, ".codex")), ); +/** + * Zed's skill loader (`crates/agent_skills/agent_skills.rs`) is stricter than + * KSF about frontmatter, and it fails *silently* — a skill that violates either + * constraint is dropped with no diagnostic in the UI, which is the silent + * capability loss spec §2 forbids. So the adapter says what Zed will not: + * + * - `name` must match ^[a-z0-9]+(-[a-z0-9]+)*$. KSF's NAME_RE is looser and + * permits a doubled or trailing hyphen (`tidy--commits`, `tidy-`), both of + * which Zed rejects. Its 64-character cap is unreachable from here: KSF names + * are capped at 41. `name` must also equal the containing directory name, + * which holds by construction — the directory IS the name. + * - `description` must be non-empty and at most 1024 UTF-8 *bytes*, not + * characters. A manifested skill is bounded at 200 characters (600 bytes at + * worst), so only an unmanifested SKILL.md — whose frontmatter is copied + * through unvalidated — can breach either bound. + * + * No `disable-model-invocation` key is emitted. Zed accepts one, but KSF has no + * field that means "never auto-invoke" (`triggers.auto` is a hint *for* + * auto-invocation), so synthesizing a value would be guessing at the author's + * intent and suppressing a skill they expect to fire. + */ +const ZED_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/; +const ZED_DESCRIPTION_MAX_BYTES = 1024; +const SILENTLY_DROPPED = "Zed drops the skill at load with no diagnostic"; + +function zedConstraints(skill: LoadedSkill): string[] { + const { name, description } = skill.manifest.skill; + const out: string[] = []; + if (!ZED_NAME_RE.test(name)) { + out.push(`${name} → zed: name must match ${ZED_NAME_RE.source} — no leading, trailing or doubled hyphen; ${SILENTLY_DROPPED}`); + } + const bytes = new TextEncoder().encode(description).length; + if (!description.trim()) { + out.push(`${name} → zed: description is empty; ${SILENTLY_DROPPED}`); + } else if (bytes > ZED_DESCRIPTION_MAX_BYTES) { + out.push( + `${name} → zed: description is ${bytes} bytes, over Zed's ${ZED_DESCRIPTION_MAX_BYTES}-byte limit (the limit is bytes, not characters); ${SILENTLY_DROPPED}`, + ); + } + return out; +} + +/** + * Zed reads the same vendor-neutral path, so this writes the byte-identical file + * the `agents` adapter does — a repo with both `.zed/` and `.agents/` compiles + * that skill once, not twice. What makes it its own target is detection and the + * constraint check: `.zed/` is the only marker a Zed-only repo has, and without + * this adapter such a repo compiles to nothing but the eager AGENTS.md floor — + * paying a skill's whole body every session on an agent that can lazy-load it + * for free. + */ +const zed = skillDirAdapter("zed", ".agents/skills", (root) => existsSync(join(root, ".zed")), zedConstraints); + // Cline rule files are always loaded — eager. const cline = fileAdapter( "cline", @@ -260,7 +328,7 @@ const aider = mergedFileAdapter( /** The floor: everything that reads AGENTS.md (Codex and many others). */ const agentsmd = mergedFileAdapter("agentsmd", "AGENTS.md", () => true); -export const ADAPTERS: Adapter[] = [claudeCode, cursor, agents, copilot, cline, windsurf, gemini, aider, agentsmd]; +export const ADAPTERS: Adapter[] = [claudeCode, cursor, agents, zed, copilot, cline, windsurf, gemini, aider, agentsmd]; /** Replace or append a skill's marker-delimited section in shared-file content. */ export function mergeSection(existing: string, name: string, section: string): string { diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index a0dc15c..c36005d 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -959,6 +959,11 @@ export async function cmdExplain(args: string[]): Promise { } const skillName = skill.manifest.skill.name; + // Target-specific constraints first: a skill the target refuses to load at all + // makes "no capability degradation" a true statement that reads as a false one. + const rejected = adapter.lint?.(skill) ?? []; + for (const r of rejected) console.log(`✗ ${r}`); + const missing = skill.manifest.targets.requires.filter((r) => !adapter.capabilities.includes(r)); if (!missing.length) { console.log(`${skillName} → ${adapterName}: no capability degradation`); diff --git a/site/404.html b/site/404.html index 6e9cce1..d469bb8 100644 --- a/site/404.html +++ b/site/404.html @@ -101,7 +101,7 @@

CLI reference

Adapters & targets

-

All nine outputs and what each one supports, from Claude Code down to the AGENTS.md fallback.

+

All ten targets and what each one supports, from Claude Code down to the AGENTS.md fallback.

Benchmark

diff --git a/site/benchmark.html b/site/benchmark.html index e6d7a2a..3d4af1f 100644 --- a/site/benchmark.html +++ b/site/benchmark.html @@ -4,17 +4,17 @@ The standing token tax, measured — Kitbash - + - + - + @@ -138,7 +138,7 @@

#What is measured

#prereview — manifested, budget 1500, authored lazy

-

The first-party example skill, compiled to all nine targets.

+

The first-party example skill, compiled to all ten targets.

@@ -146,6 +146,7 @@

#prereview — manifested, budget 1500, authored l

+ @@ -170,6 +171,7 @@

#review-checklist — bare, no manifest

+ @@ -239,7 +241,7 @@

#Keep reading

Adapters & targets

-

Which of the nine targets are lazy, which are eager, and what each one supports.

+

Which of the ten targets are lazy, which are eager, and what each one supports.

Budgets in the format

diff --git a/site/changelog.html b/site/changelog.html index 8c2c01e..8ff5b67 100644 --- a/site/changelog.html +++ b/site/changelog.html @@ -91,7 +91,7 @@

Changelog

Releases follow Keep a Changelog and semver — for skills and for this CLI, breaking prompt changes are breaking changes. The CLI is published to npm as kitbash and to Homebrew via singhharsh1708/tap. Tagged builds are on the GitHub releases page.

-
v0.11.0Current CLI version
+
v0.12.0Current CLI version
8Compile targets
Apache-2.0License
@@ -105,10 +105,21 @@

Changelog

Confirm with kitbash --version, which reads the installed package.json. Install and uninstall routes are covered on the installation page.

+
+
+

v0.12.0

+ 2026-07-28latest +
+

Added

+
  • zed adapter — the tenth target. Zed reads the vendor-neutral .agents/skills/<name>/SKILL.md path, but nothing in a Zed-only repo announces that: the agents adapter probes .agents/ and .codex/, neither of which Zed creates. A repo whose only agent marker was .zed/ therefore compiled to nothing but the eager AGENTS.md floor — paying a skill's whole body every session on an agent that can lazy-load it for free. The new target detects .zed/ and emits the byte-identical file agents does, so a repo with both compiles that skill once, not twice. Ten targets, nine output files.
  • Zed's silent frontmatter rejections are now visible. Zed's skill loader is stricter than KSF and drops a non-conforming skill at load with no diagnostic in the UI — the silent capability loss spec §2 forbids. Two constraints are checked and warned about at compile (so --strict fails on them) and in explain: name must match ^[a-z0-9]+(-[a-z0-9]+)*$, which rejects the doubled and trailing hyphens KSF's own name rule permits (tidy--commits is a legal skill name Zed will not load); and description must be non-empty and at most 1024 UTF-8 bytes, not characters — a trap for any non-ASCII description, where 600 characters can be 1,200 bytes. A manifested skill is bounded at 200 characters, so only an unmanifested SKILL.md, whose frontmatter is copied through unvalidated, can breach either bound. No disable-model-invocation key is emitted: KSF has no field meaning "never auto-invoke", so synthesizing one would guess at the author's intent.
+

Fixed

+
  • explain no longer answers "no capability degradation" about a skill the target refuses to load. Adapters can now declare target-specific constraints separately from emit(), and explain reads them directly — a true statement that read as a false one.
+
+

v0.11.0

- 2026-07-28latest + 2026-07-28

Credibility pass. A staff-level review of the shipped product found the docs, site, and CLI contradicting each other — fatal for a tool whose whole pitch is honest measurement. Every fix here aligns the surface with reality; none is a new feature.

Fixed

diff --git a/site/docs/adapters.html b/site/docs/adapters.html index 81cde14..8b9aa43 100644 --- a/site/docs/adapters.html +++ b/site/docs/adapters.html @@ -4,16 +4,16 @@ Adapters & targets — Kitbash docs - + - + - + @@ -105,7 +105,7 @@

Project

kitbash / docs / adapters

Adapters & targets

-

Nine compile targets, each with its own output path, detection rule, loading mode, and capability set. This page is the reference for what every one of them actually writes.

+

Ten compile targets, each with its own output path, detection rule, loading mode, and capability set. This page is the reference for what every one of them actually writes.

@@ -118,7 +118,7 @@

Lazy vs eager

  • lazy The target keeps the skill out of the context window until it is invoked. Only a standing stub — a name and description the agent can match against — is resident. Claude Code SKILL.md files, Cursor agent-requested rules, the vendor-neutral .agents/skills/ path, the .github/skills/ and .gemini/skills/ directories Copilot and Gemini CLI read, and Devin rules with a model_decision trigger all work this way.
  • eager The target has no mechanism for deferred loading, so the entire compiled body sits in context every session. AGENTS.md, CONVENTIONS.md, and Cline rule files all work this way.
  • -

    Kitbash compiles to the cheapest loading mode each target actually supports, so six of the nine carry only a stub. The standing token tax is what a skill costs on the targets whose only mode is eager: a skill authored with disclosure = "lazy" cannot lazy-load there — the target simply has nowhere to put a stub. Kitbash refuses to let that happen quietly: on every eager target, a lazy-authored skill produces a warning that names the real cost and the limit the author declared.

    +

    Kitbash compiles to the cheapest loading mode each target actually supports, so seven of the ten carry only a stub. The standing token tax is what a skill costs on the targets whose only mode is eager: a skill authored with disclosure = "lazy" cannot lazy-load there — the target simply has nowhere to put a stub. Kitbash refuses to let that happen quietly: on every eager target, a lazy-authored skill produces a warning that names the real cost and the limit the author declared.

     prereview → agentsmd: agentsmd is eager and cannot lazy-load;
       this skill costs ~560 tokens standing every session (declared limit: 60)

    Multiply that by the number of installed skills and it is the whole context budget problem. The benchmark page quantifies the gap between lazy and eager delivery of the same skill.

    @@ -150,6 +150,13 @@

    Capability matrix

    + + + + + + + @@ -195,10 +202,10 @@

    Capability matrix

    claude-code56740lazy
    cursor56840lazy
    agents56740lazy
    zed56740lazy
    copilot56740lazy
    cline539539eager
    windsurf56940lazy
    claude-code88019lazy
    cursor87919lazy
    agents88019lazy
    zed88019lazy
    copilot88019lazy
    cline859859eager
    windsurf88119lazy
    lazy none
    zed.agents/skills/<name>/SKILL.md.zed/ existslazynone
    copilot .github/skills/<name>/SKILL.md
    -

    No adapter declares any capability today. emit() writes a SKILL.md — plus slash-command shims on Claude Code — but it does not copy a skill's scripts/, install a hook, or wire a subagent, so claiming any of those would report full support for output referencing files the compiler never produced. Because every capability set is empty, any non-empty targets.requiresscripts, hooks, subagents, or network — degrades on every one of the nine targets, Claude Code included. A capability will be re-added only alongside the emit code that delivers its primitive.

    +

    No adapter declares any capability today. emit() writes a SKILL.md — plus slash-command shims on Claude Code — but it does not copy a skill's scripts/, install a hook, or wire a subagent, so claiming any of those would report full support for output referencing files the compiler never produced. Because every capability set is empty, any non-empty targets.requiresscripts, hooks, subagents, or network — degrades on every one of the ten targets, Claude Code included. A capability will be re-added only alongside the emit code that delivers its primitive.

    Detection can be overridden. Set targets under [project] in kitbash.toml and Kitbash compiles exactly that list instead of probing the filesystem; an unknown id there is a hard error. See project config.

    -

    The nine adapters

    +

    The ten adapters

    Every emitted file carries a generated-file header naming the source skill and version, which is what makes stale output safe to delete on the next compile:

    <!-- generated by kitbash — do not edit; source: .kitbash/skills/prereview @ 0.1.0 -->
    @@ -214,6 +221,18 @@

    agents

    Writes .agents/skills/<name>/SKILL.md with the same name and quoted description frontmatter as Claude Code. This is the vendor-neutral Agent Skills path: Codex reads it as its only repo path, and Cursor, Copilot, Gemini CLI, Roo, Amp, OpenCode, Zed and Antigravity read it too — one target instead of nine adapters. Only the metadata is preloaded, so it is lazy.

    Detection is deliberately narrow: .agents/ or .codex/. Agents that also have a native path already get their own adapter, and emitting both would duplicate the skill for no benefit. It writes only SKILL.md and declares no capabilities.

    +

    zed

    +

    Zed reads the same vendor-neutral path, so this target writes the byte-identical file agents does — a repo with both .zed/ and .agents/ compiles that skill once, not twice. Ten targets, nine output files.

    +

    What makes it its own target is detection and a constraint check. .zed/ is the only marker a Zed-only repo carries, and without this adapter such a repo compiled to nothing but the eager AGENTS.md floor — paying a skill's whole body every session on an agent that can lazy-load it for free.

    +

    Zed's skill loader is stricter about frontmatter than KSF is, and it fails silently: a skill that breaches either rule is dropped at load with no diagnostic in the UI. Kitbash says what Zed will not, at compile and in explain:

    +
      +
    • name must match ^[a-z0-9]+(-[a-z0-9]+)*$. KSF's own name rule is looser and permits a doubled or trailing hyphen — tidy--commits is a legal skill name that Zed will not load. Its 64-character cap is unreachable from here: KSF caps names at 41.
    • +
    • description must be non-empty and at most 1024 UTF-8 bytes, not characters — a trap for any non-ASCII description, where 600 characters can be 1,200 bytes. A manifested skill is bounded at 200 characters, so only an unmanifested SKILL.md, whose frontmatter is copied through unvalidated, can breach either bound.
    • +
    +
     tidy--commits → zed: name must match ^[a-z0-9]+(-[a-z0-9]+)*$ — no leading,
    +  trailing or doubled hyphen; Zed drops the skill at load with no diagnostic
    +

    Both are warnings, so compile --strict fails on them. Zed also accepts a disable-model-invocation key, which Kitbash does not emit: KSF has no field meaning "never auto-invoke" — triggers.auto is a hint for auto-invocation — so synthesizing a value would guess at the author's intent and could suppress a skill they expect to fire.

    +

    copilot

    Writes .github/skills/<name>/SKILL.md with name and a quoted description in frontmatter — the agentskills.io layout. Copilot loads only that frontmatter until the skill is needed, so the target is lazy. Detection is the presence of .github/, which most repositories have — expect this target to fire more often than the others.

    Before 0.8.0 this adapter wrote .github/instructions/<name>.instructions.md with applyTo: "**", a glob that matched every single request and made the target eager.

    @@ -257,7 +276,7 @@

    Degradation

    mode = "skill"

    At compile time each required capability is checked against the adapter's capability list. Anything missing produces a warning naming the skill, the target, and the capability:

     verify → cursor: target lacks "scripts"; compiled instruction-only (degraded)
    -

    Because every adapter's capability set is currently empty, this is not a per-target quirk: any value in requires degrades on every one of the nine targets. Kitbash's emit() writes instructions — a SKILL.md, and slash-command shims on Claude Code — but it does not yet deliver the underlying primitives: it does not copy a skill's scripts/, install a hook, or wire a subagent, and no adapter reaches the network. Declaring the capability the compiler cannot honor would report full support for output that references files it never produced, which is exactly the silent capability loss the spec forbids.

    +

    Because every adapter's capability set is currently empty, this is not a per-target quirk: any value in requires degrades on every one of the ten targets. Kitbash's emit() writes instructions — a SKILL.md, and slash-command shims on Claude Code — but it does not yet deliver the underlying primitives: it does not copy a skill's scripts/, install a hook, or wire a subagent, and no adapter reaches the network. Declaring the capability the compiler cannot honor would report full support for output that references files it never produced, which is exactly the silent capability loss the spec forbids.

    Degraded means the instructions still compile and still ship — the agent gets the prose, it just does not get the deterministic helper the skill wanted to run. That is often acceptable. What is never acceptable is not being told.

    Spec §2. A compiler lacking a required capability MUST either emit a degraded variant with a visible warning, or fail under --strict. Silent degradation is a conformance violation.

    diff --git a/site/docs/authoring.html b/site/docs/authoring.html index 94594ae..85e8cf7 100644 --- a/site/docs/authoring.html +++ b/site/docs/authoring.html @@ -190,7 +190,7 @@

    targets

    [targets]
     requires = []
     mode = "skill"
    -

    requires declares capabilities the skill needs: scripts, hooks, subagents, network. No adapter delivers any of them today — emit() writes instructions but does not copy a skill's scripts/, install a hook, or wire a subagent — so any value here degrades on every one of the nine targets, and each produces a visible warning at compile:

    +

    requires declares capabilities the skill needs: scripts, hooks, subagents, network. No adapter delivers any of them today — emit() writes instructions but does not copy a skill's scripts/, install a hook, or wire a subagent — so any value here degrades on every one of the ten targets, and each produces a visible warning at compile:

     test-gaps → cursor: target lacks "scripts"; compiled instruction-only (degraded)

    Under --strict that warning becomes a build failure. Silent degradation is a conformance violation — leave requires empty unless the skill genuinely cannot function without the capability.

    mode is skill or gate. A gate-mode skill must route its verdict through a script exit code or a schema-validated artifact. A bare model opinion is not a gate verdict.

    @@ -397,7 +397,7 @@

    Where to go next

    diff --git a/site/docs/cli.html b/site/docs/cli.html index ce31b7e..d85f5ed 100644 --- a/site/docs/cli.html +++ b/site/docs/cli.html @@ -321,7 +321,7 @@

    compile

    doctor

    kitbash doctor

    -

    The repo health check. It reports which of the nine adapters were detected, how many skills are installed, the total standing context cost of their stubs, the worst-case active cost if every skill fired in one session, and then verifies the lockfile.

    +

    The repo health check. It reports which of the ten adapters were detected, how many skills are installed, the total standing context cost of their stubs, the worst-case active cost if every skill fired in one session, and then verifies the lockfile.

    Three integrity conditions are checked: skills installed with no kitbash.lock at all, a locked skill whose files on disk no longer match their recorded hash, and a skill present on disk but absent from the lockfile. All three mean the code your agents load is not the code somebody reviewed.

    If [policy] is configured, doctor rechecks it against everything already installed — using each skill's recorded source from the lockfile. That catches skills that predate the policy or were copied in without going through kitbash install.

      @@ -446,12 +446,12 @@

      explain

      Explain reports two independent kinds of loss. Capability degradation: each entry in the skill's targets.requires that the adapter does not support, which compiles down to instruction-only. And loading degradation: a skill authored for lazy disclosure on an eager adapter, which pays its full body as standing context in every session.

      • <target> Required, first positional. Path, installed name, or uninstalled source.
      • -
      • <adapter> Required, second positional. One of the nine adapter ids.
      • +
      • <adapter> Required, second positional. One of the ten adapter ids.
      $ kitbash explain prereview agentsmd
       prereview → agentsmd: no capability degradation
          loading: agentsmd is eager — skill costs ~560 tokens standing every session (declared limit: 60)
      -

      No adapter declares a capability today — emit() writes instructions but does not copy scripts/, install a hook, or wire a subagent — so a skill that requires any of scripts, hooks, subagents, or network reports degraded on every one of the nine targets, Claude Code included:

      +

      No adapter declares a capability today — emit() writes instructions but does not copy scripts/, install a hook, or wire a subagent — so a skill that requires any of scripts, hooks, subagents, or network reports degraded on every one of the ten targets, Claude Code included:

      $ kitbash explain prereview cursor
       prereview → cursor: degraded
          requires "scripts" — not supported by cursor; compiled instruction-only
      diff --git a/site/docs/config.html b/site/docs/config.html index 43a3cb3..59faffe 100644 --- a/site/docs/config.html +++ b/site/docs/config.html @@ -139,7 +139,7 @@

      targets

      An array of adapter ids. Omit the key and Kitbash autodetects — every adapter whose detection probe finds evidence of that agent in the repo. Set it and detection is bypassed entirely: the listed targets are compiled whether or not the agent's directory exists.

      [project]
       targets = ["claude-code", "cursor", "agentsmd"]
      -

      The nine valid ids, and what autodetection looks for:

      +

      The ten valid ids, and what autodetection looks for:

      @@ -147,6 +147,7 @@

      targets

      + @@ -158,7 +159,7 @@

      targets

      An unrecognised id is a hard error, not a warning. kitbash compile writes nothing and exits 1:

      unknown target(s) in kitbash.toml: claude (known: claude-code, cursor,
      -agents, copilot, cline, windsurf, gemini, aider, agentsmd)
      +agents, zed, copilot, cline, windsurf, gemini, aider, agentsmd)

      That is deliberate. A typo'd target silently dropping an agent from every compile is worse than a failed build.

      Note that kitbash doctor reports detection, not configuration — its "detected targets" list runs the probes regardless of what targets says. If the two disagree, targets is what compile obeys.

      What each adapter emits, which capabilities it supports, and whether it loads lazily or eagerly is covered in adapters & targets.

      @@ -333,7 +334,7 @@

      What to commit, what to ignore

      Where to go next

        -
      • Adapters & targets — what each of the nine ids actually emits, and what degrades.
      • +
      • Adapters & targets — what each of the ten ids actually emits, and what degrades.
      • Trust & review — designing a [policy] your team can live with.
      • CLI reference — every command, flag, and exit code.
      • Skill format (KSF) — the manifest keys [policy] gates on.
      • diff --git a/site/docs/format.html b/site/docs/format.html index 6096195..f60bb76 100644 --- a/site/docs/format.html +++ b/site/docs/format.html @@ -217,7 +217,7 @@

        [targets]

      idDetected when the repo contains
      claude-code.claude/
      cursor.cursor/
      agents.agents/ or .codex/
      zed.zed/
      copilot.github/
      cline.clinerules/
      windsurf.devin/ or .windsurf/
      -

      requires is the input to the compatibility matrix, which is derived and never hand-written: targets.requires crossed with each adapter's capability set yields full, degraded, or unsupported per assistant. A compiler missing a required capability must either emit a degraded variant with a visible warning or fail under --strict. Silent degradation is a conformance violation. In the reference compiler today every adapter's capability set is empty — emit() writes instructions but does not yet copy scripts/, install a hook, or wire a subagent — so any value in requires degrades on every one of the nine targets, Claude Code included.

      +

      requires is the input to the compatibility matrix, which is derived and never hand-written: targets.requires crossed with each adapter's capability set yields full, degraded, or unsupported per assistant. A compiler missing a required capability must either emit a degraded variant with a visible warning or fail under --strict. Silent degradation is a conformance violation. In the reference compiler today every adapter's capability set is empty — emit() writes instructions but does not yet copy scripts/, install a hook, or wire a subagent — so any value in requires degrades on every one of the ten targets, Claude Code included.

      mode = "gate" means the skill produces a verdict that can block — a pre-push check, a CI step. Gates must ground that verdict in a script exit code or a schema-validated artifact. A gate-mode skill with no scripts/ directory and no artifacts.produces has nothing to produce a verdict from, and fails the gate-verdict check under lint and test.

      [lore]

      diff --git a/site/docs/index.html b/site/docs/index.html index 9765696..43244de 100644 --- a/site/docs/index.html +++ b/site/docs/index.html @@ -151,7 +151,7 @@

      Project config

      Adapters & targets

      -

      All nine outputs, which are lazy or eager, and where compilation degrades.

      +

      All ten targets, which are lazy or eager, and where compilation degrades.

      Trust & review

      diff --git a/site/docs/quickstart.html b/site/docs/quickstart.html index b79f538..d4bff90 100644 --- a/site/docs/quickstart.html +++ b/site/docs/quickstart.html @@ -173,7 +173,7 @@

      Where to go next

      diff --git a/site/index.html b/site/index.html index f4bf4fe..57df703 100644 --- a/site/index.html +++ b/site/index.html @@ -151,7 +151,7 @@
    -

    Open format for AI agent skills · v0.11.0 · stable spec (RFC 0002)

    +

    Open format for AI agent skills · v0.12.0 · stable spec (RFC 0002)

    Write an agent skill once. Run it everywhere.

    Get started @@ -252,6 +252,7 @@

    #Pick a target

    + @@ -309,6 +310,21 @@

    #Pick a target

    The vendor-neutral Agent Skills path, detected from .agents/ or .codex/ — read by Codex, Cursor, Copilot, Gemini CLI, Roo, Amp, OpenCode, Zed and Antigravity. Only the metadata is preloaded, so the body costs nothing until it is needed.

    + +