Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Format: [Keep a Changelog](https://keepachangelog.com). Versioning: semver — for skills *and* for this CLI, breaking prompt changes are breaking changes.

## [0.17.0] — 2026-08-08

The eleventh target — and it's the one the whole industry just agreed on. On 2026-08-06 OpenAI, Amazon, Microsoft, Cursor and Vercel published **Agent Plugins v1.0** (agent-plugins.org, Google core-maintaining): a vendor-neutral package format — a `plugin.json` manifest plus a `skills/<name>/SKILL.md` folder — that ChatGPT/Codex, Cursor, Copilot, Kiro and VS Code all read. It is a *packaging* standard by design: it defines no permission model, no trust or provenance, no sandboxing, and no measurement. That absence is exactly the layer Kitbash already is. So rather than treat the standard as a competitor, Kitbash compiles to it.

### Added
- **`agent-plugins` compile target** — the eleventh adapter. `kitbash compile` emits a spec-shaped Agent Plugin: `agent-plugin/plugin.json` (the `$schema` + package name the standard requires) and one `agent-plugin/skills/<name>/SKILL.md` per skill, with `name` + `description` frontmatter so clients inject only the metadata and lazy-load the body. The skill drops into any Agent-Plugins client having *already* passed Kitbash's install gate, its declared token budget, and its drift check — the trust and measurement the format itself leaves out. Unlike the ten auto-detected targets this one is **opt-in**: it does not fire on a fresh repo, because publishing a plugin is a choice. Turn it on with `agent-plugins` under `[project].targets`, or once an `agent-plugin/plugin.json` exists it is detected on its own. The compiler owns `plugin.json` (one repo-level manifest, not per-skill), writes it only when it changes, and prunes a removed skill's `SKILL.md` from the plugin's `skills/` folder on the next compile.

## [0.16.0] — 2026-08-07

The on-ramp for repos that already have the copy-per-agent mess — and an honesty correction. Grounded in a landscape scan: the strongest, most-cited pain for teams running several coding agents is config drift and painful onboarding across agents (named verbatim by five-plus independently-built sync tools). Kitbash made you author a fresh skill; now it can start from what you already have.
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Kitbash is a compiler for AI agent skills that measures what a skill costs your context window every session — before you install it.

You write the skill once in one open format ([KSF](spec/SPEC.md)) and compile it to the native format of ten coding agents — Claude Code, Cursor, Copilot, Zed, Cline, Devin, Gemini CLI, Aider, the vendor-neutral `.agents/skills/` path, and the `AGENTS.md` floor. While compiling, it measures each output's **standing** cost: the tokens the skill parks in context on every request, whether or not it ever gets used.
You write the skill once in one open format ([KSF](spec/SPEC.md)) and compile it to the native format of ten coding agents — Claude Code, Cursor, Copilot, Zed, Cline, Devin, Gemini CLI, Aider, the vendor-neutral `.agents/skills/` path, and the `AGENTS.md` floor — plus an opt-in eleventh target, the [Agent Plugins](https://agent-plugins.org) v1.0 package format. While compiling, it measures each output's **standing** cost: the tokens the skill parks in context on every request, whether or not it ever gets used.

```bash
npm install -g kitbash # or: brew install singhharsh1708/tap/kitbash
Expand All @@ -23,7 +23,7 @@ In a repo that already has `.claude/` and `.cursor/`, that last command prints:
→ AGENTS.md
ℹ prereview → agentsmd: agentsmd is eager and cannot lazy-load, so this skill adds ~560 tokens standing every session (a lazy target pays 0; declared limit 60)
compiled 1 skill for 3 targets
7 more target(s) available — add agents, zed, copilot, … under [project].targets in kitbash.toml, or create their agent dirs.
8 more target(s) available — add agents, zed, copilot, … under [project].targets in kitbash.toml, or create their agent dirs.
```

### The `ℹ` line is why this is a compiler, not a converter
Expand All @@ -32,7 +32,7 @@ The identical instructions cost ~40 standing tokens on a target that lazy-loads

Those numbers are measured, not asserted: the method and the full per-target table are in [docs/benchmarks/README.md](docs/benchmarks/README.md), and `npm run bench` inside `packages/cli` regenerates them. A converter would translate the format and stop. Kitbash reads the skill and tells you what it will cost you. I have not found another tool that surfaces that number.

Kitbash always compiles to the cheapest loading mode a target actually supports — eight of the ten lazy-load; Aider's `CONVENTIONS.md` and the `AGENTS.md` floor cannot, and carry the whole body every session. `--strict` turns budget overruns and degradation warnings into build failures.
Kitbash always compiles to the cheapest loading mode a target actually supports — nine of the eleven lazy-load; Aider's `CONVENTIONS.md` and the `AGENTS.md` floor cannot, and carry the whole body every session. `--strict` turns budget overruns and degradation warnings into build failures.

### Why not a sync script?

Expand Down Expand Up @@ -62,7 +62,7 @@ Already carrying a hand-written `CLAUDE.md`, `.cursor/rules/`, `AGENTS.md`, and
<p align="center">
<a href="https://www.npmjs.com/package/kitbash"><img src="https://img.shields.io/npm/v/kitbash?color=ffb454" alt="npm version"></a>
<a href="https://github.com/singhharsh1708/kitbash/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/singhharsh1708/kitbash/ci.yml?branch=main" alt="CI"></a>
<img src="https://img.shields.io/badge/agent_targets-10-ffb454" alt="10 agent targets">
<img src="https://img.shields.io/badge/agent_targets-11-ffb454" alt="11 agent targets">
<img src="https://img.shields.io/badge/runtime_deps-0-ffb454" alt="zero runtime dependencies">
<a href="LICENSE"><img src="https://img.shields.io/github/license/singhharsh1708/kitbash?color=8b96ab" alt="Apache-2.0"></a>
</p>
Expand Down Expand Up @@ -194,7 +194,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 ten 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 eleven 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, `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.
- ✅ 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. Plus `agent-plugins`, the opt-in eleventh target that emits the industry-standard Agent Plugins package (agent-plugins.org). 11 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.
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kitbash",
"version": "0.16.0",
"version": "0.17.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",
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/scripts/benchmark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ const repoRoot = resolve(here, "../../..");
const cli = join(here, "../dist/index.js");
const fixture = join(repoRoot, "examples/skills/prereview");

// agent-plugins is an opt-in publishing target (agent-plugins.org): it is not part
// of a repo's auto-detected fan-out and would not fire in this fixture's compile
// (no plugin.json). It is lazy, so it carries the same stub cost as every other
// lazy target and adds no standing-tax story — this benchmark measures the
// always-on tax across the targets a repo compiles to by default, so it is excluded.
const BENCH_ADAPTERS = ADAPTERS.filter((a) => a.id !== "agent-plugins");

// How each target loads a skill, read from the adapters themselves rather than
// restated here — a second copy of this map is exactly how the published numbers
// drift away from what the compiler actually emits.
const LOADING = Object.fromEntries(ADAPTERS.map((a) => [a.id, a.loading]));
const LOADING = Object.fromEntries(BENCH_ADAPTERS.map((a) => [a.id, a.loading]));

function run(args, cwd) {
const r = spawnSync("node", [cli, ...args], { cwd, encoding: "utf8" });
Expand Down
57 changes: 57 additions & 0 deletions packages/cli/scripts/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,63 @@ try {
rmSync(imp, { recursive: true, force: true });
}

// ── Agent Plugins target (agent-plugins.org package format) ──────────────────
// One KSF skill → a spec-shaped plugin (plugin.json + skills/<n>/SKILL.md), with
// the trust/budget/drift layer the standard omits living in the KSF source.
const ap = mkdtempSync(join(tmpdir(), "kitbash-agentplugins-"));
try {
const apSrc = join(ap, "greet-src");
mkdirSync(apSrc, { recursive: true });
writeFileSync(
join(apSrc, "skill.toml"),
'[skill]\nname = "greet"\nversion = "0.1.0"\ndescription = "Greet the user warmly and concisely"\n[context]\nbudget = 500\nstanding = 80\n',
);
writeFileSync(join(apSrc, "SKILL.md"), "# Greet\n\nSay hello. Be brief.\n");

// Not detected by default: no plugin.json, not in [project].targets. The other
// adapters must still fan out; agent-plugins must NOT force itself onto a repo.
writeFileSync(join(ap, "kitbash.toml"), '[project]\ntargets = ["claude-code"]\n');
run(["install", `file:${apSrc}`, "--yes"], ap);
const noAp = run(["compile"], ap);
check("agent-plugins: opt-out repo does not emit a plugin", !existsSync(join(ap, "agent-plugin")), noAp.out);

// Opt in via [project].targets and recompile.
writeFileSync(join(ap, "kitbash.toml"), '[project]\ntargets = ["claude-code", "agent-plugins"]\n');
const apc = run(["compile"], ap);
check("agent-plugins: compile exits 0", apc.status === 0, apc.out);
check("agent-plugins: emits the skill under skills/<name>/SKILL.md", existsSync(join(ap, "agent-plugin/skills/greet/SKILL.md")), apc.out);
check("agent-plugins: emits plugin.json", existsSync(join(ap, "agent-plugin/plugin.json")), apc.out);

const manifest = readFileSync(join(ap, "agent-plugin/plugin.json"), "utf8");
let parsed = null;
try {
parsed = JSON.parse(manifest);
} catch {
parsed = null;
}
check("agent-plugins: plugin.json is valid JSON", parsed !== null, manifest);
check("agent-plugins: manifest declares the spec $schema + a name", !!parsed && typeof parsed.name === "string" && parsed.name.length > 0 && String(parsed.$schema).includes("agent-plugins.org"), manifest);

const skillMd = readFileSync(join(ap, "agent-plugin/skills/greet/SKILL.md"), "utf8");
check("agent-plugins: SKILL.md carries name + description frontmatter", skillMd.startsWith("---\nname: greet\n") && skillMd.includes("description:"), skillMd);

// Once a plugin.json exists the target is sticky: detection fires even with no
// targets list, so a follow-up compile keeps regenerating the plugin.
writeFileSync(join(ap, "kitbash.toml"), "");
const sticky = run(["compile"], ap);
check("agent-plugins: auto-detected once a plugin.json exists", existsSync(join(ap, "agent-plugin/skills/greet/SKILL.md")) && sticky.out.includes("agent-plugin/skills/greet/SKILL.md"), sticky.out);

// plugin.json is not rewritten when unchanged (no needless churn/diff).
check("agent-plugins: plugin.json not rewritten when unchanged", !sticky.out.includes("→ agent-plugin/plugin.json"), sticky.out);

// Removing the skill prunes its SKILL.md from the plugin's skills/ folder.
run(["remove", "greet"], ap);
const pruned = run(["compile"], ap);
check("agent-plugins: removed skill is pruned from the plugin", !existsSync(join(ap, "agent-plugin/skills/greet/SKILL.md")), pruned.out);
} finally {
rmSync(ap, { recursive: true, force: true });
}

if (failures) {
console.error(`\n${failures} test(s) failed`);
process.exit(1);
Expand Down
43 changes: 41 additions & 2 deletions packages/cli/src/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { basename, join } from "node:path";
import { estimateTokens, type LoadedSkill } from "./ksf.js";

export interface CompiledFile {
Expand Down Expand Up @@ -366,7 +366,46 @@ 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, zed, copilot, cline, windsurf, gemini, aider, agentsmd];
/**
* Agent Plugins (agent-plugins.org) — the vendor-neutral package format ratified
* 2026-08-06 by OpenAI, Amazon, Microsoft, Cursor, Vercel, with Google as core
* maintainer. A plugin is a directory with a `plugin.json` manifest and a
* `skills/` folder whose subdirectories each hold a SKILL.md. First-wave clients:
* ChatGPT/Codex, Cursor, Copilot, Kiro, VS Code.
*
* The spec is a package format ONLY — it explicitly defines no permission model,
* trust, provenance, sandboxing, or measurement. That is exactly the layer
* Kitbash adds around it: compile here and the skill drops into any Agent-Plugins
* client, having already passed the install gate, the token budget, and the
* drift check that the standard leaves out. `plugin.json` is written by the
* compiler (see cmdCompile). Opt in via [project].targets; auto-detected once a
* plugin.json exists.
*/
export const AGENT_PLUGIN_DIR = "agent-plugin";
const agentPlugins = skillDirAdapter(
"agent-plugins",
`${AGENT_PLUGIN_DIR}/skills`,
(root) => existsSync(join(root, "plugin.json")) || existsSync(join(root, AGENT_PLUGIN_DIR)),
);

/** The Agent Plugins package manifest (spec §plugin.json). Minimal + valid; skills/ is auto-discovered. */
export function agentPluginManifest(root: string): string {
const name = basename(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent-plugin";
return (
JSON.stringify(
{
$schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
name,
version: "1.0.0",
description: "Agent Plugin compiled by kitbash — install-gate, token budget, and drift checks live in the KSF source.",
},
null,
2,
) + "\n"
);
}

export const ADAPTERS: Adapter[] = [claudeCode, cursor, agents, zed, copilot, cline, windsurf, gemini, aider, agentsmd, agentPlugins];

/** Replace or append a skill's marker-delimited section in shared-file content. */
export function mergeSection(existing: string, name: string, section: string): string {
Expand Down
17 changes: 16 additions & 1 deletion packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { execFileSync } from "node:child_process";
import { createInterface } from "node:readline";
import { tmpdir } from "node:os";
import { basename, dirname, join, resolve, sep } from "node:path";
import { ADAPTERS, GENERATED_MARK, mergeSection, pruneSections, readFileIfExists, type CompiledFile } from "./adapters.js";
import { ADAPTERS, AGENT_PLUGIN_DIR, agentPluginManifest, GENERATED_MARK, mergeSection, pruneSections, readFileIfExists, type CompiledFile } from "./adapters.js";
import { dropLock, integrityOf, readLock, upsertLock, walk, LOCK_FILE } from "./lock.js";
import { fileChanges, manifestDelta, textOf, unifiedDiff } from "./diff.js";
import { collectImports, driftGroups, type ImportedSource } from "./importers.js";
Expand Down Expand Up @@ -902,6 +902,20 @@ export async function cmdCompile(args: string[]): Promise<number> {
writeFileSync(abs, f.content.endsWith("\n") ? f.content : `${f.content}\n`);
console.log(`→ ${f.path}`);
}
// Agent Plugins: the skills/ folder is written per-skill by its adapter above.
// The plugin.json manifest that makes that folder a valid plugin is one
// repo-level file describing the whole package, so the compiler — not any one
// skill — owns it. Written only when the target is active and something compiled.
if (adapters.some((a) => a.id === "agent-plugins") && skills.length) {
const rel = `${AGENT_PLUGIN_DIR}/plugin.json`;
const manifest = agentPluginManifest(root);
if (readFileIfExists(root, rel) !== manifest) {
const abs = join(root, rel);
mkdirSync(dirname(abs), { recursive: true });
writeFileSync(abs, manifest);
console.log(`→ ${rel}`);
}
}
// Shared marker files not rewritten this compile still need stale sections pruned.
// Nothing wrote to this file, so none of its kitbash sections are current — whether
// the last skill writing there was removed, the target was dropped from kitbash.toml,
Expand Down Expand Up @@ -1474,6 +1488,7 @@ const MANAGED_DIRS: { dir: string; suffix: string; wholeDir?: boolean }[] = [
{ dir: ".agents/skills", suffix: "/SKILL.md", wholeDir: true },
{ dir: ".gemini/skills", suffix: "/SKILL.md", wholeDir: true },
{ dir: ".github/skills", suffix: "/SKILL.md", wholeDir: true },
{ dir: `${AGENT_PLUGIN_DIR}/skills`, suffix: "/SKILL.md", wholeDir: true },
{ dir: ".cursor/rules", suffix: ".mdc" },
{ dir: ".clinerules", suffix: ".md" },
{ dir: ".windsurf/rules", suffix: ".md" },
Expand Down
2 changes: 1 addition & 1 deletion site/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ <h3>CLI reference</h3>
</a>
<a class="doc-link" href="docs/adapters">
<h3>Adapters &amp; targets</h3>
<p>All ten targets and what each one supports, from Claude Code down to the <code>AGENTS.md</code> fallback.</p>
<p>All eleven targets and what each one supports, from Claude Code down to the <code>AGENTS.md</code> fallback.</p>
</a>
<a class="doc-link" href="benchmark">
<h3>Benchmark</h3>
Expand Down
2 changes: 1 addition & 1 deletion site/benchmark.html
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ <h2><span class="hash">#</span>Keep reading</h2>
<div class="docs-grid">
<a class="doc-link" href="docs/adapters">
<h3>Adapters &amp; targets</h3>
<p>Which of the ten targets are lazy, which are eager, and what each one supports.</p>
<p>Which of the eleven targets are lazy, which are eager, and what each one supports.</p>
</a>
<a class="doc-link" href="docs/format">
<h3>Budgets in the format</h3>
Expand Down
Loading