diff --git a/CHANGELOG.md b/CHANGELOG.md
index 82f89da..632335f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
Format: [Keep a Changelog](https://keepachangelog.com). Versioning: semver — for skills *and* for this CLI, breaking prompt changes are breaking changes.
+## [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.
+
+### Added
+- **`kitbash import`** — reverse-compile. Reads a repo's existing agent instruction files (CLAUDE.md, AGENTS.md, GEMINI.md, CONVENTIONS.md, `.cursorrules`, `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, `.clinerules`, `.windsurf`/`.devin/rules/*`), measures what each costs, **detects drift** — where the copies have silently diverged — and synthesizes one KSF skill from the version the most agents agree on. `--write` saves it and pins it, so `kitbash compile` regenerates every target from one source and ends the drift. `--name` sets the skill name. Non-destructive: your original files are left in place until you remove them. Purely kitbash-generated files are skipped, not re-imported.
+
+### Fixed
+- Corrected an overstated claim on the benchmark page ("the number nobody measures"). Other tools do estimate context-file token cost; what is distinct about Kitbash is measuring it **per target, from one source, and enforcing it as a declared budget at compile time** — the page now says that instead.
+
## [0.15.0] — 2026-08-07
Security and integrity pass. A multi-agent audit of the shipped code — five independent review passes, every finding independently reproduced before it was accepted — turned up four ways to walk a hostile skill straight past the install gate, plus five ways the tool corrupted or silently discarded its own output. Everything here was reachable in 0.13.0. Nothing here is a new feature.
diff --git a/README.md b/README.md
index 7580b51..cae7aa5 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,8 @@ A syncer multiplies your review surface; a compiler divides it. You review one s
Already have skills? A plain SKILL.md folder — the skills.sh / Claude Skills convention — installs directly with `kitbash install owner/repo`. It is basically KSF without the manifest, so Kitbash fills in defaults and marks it `unmanifested`, since nobody declared a budget or permissions for it.
+Already carrying a hand-written `CLAUDE.md`, `.cursor/rules/`, `AGENTS.md`, and the rest of the copy-per-agent set? `kitbash import` reads them back into a single skill, measures what each one costs in standing context, and reports where the copies have drifted apart — so `kitbash compile` can regenerate them all from that one source. It touches nothing on disk until you remove the originals yourself.
+
**Status.** v0.15.0, on npm and Homebrew, zero runtime dependencies, Node 20+. The KSF core is frozen and additive-only within the major version ([RFC 0002](rfcs/0002-ksf-1.0-stabilization.md)). Everything around it is early and labeled as such: `init`, `install`, `remove`, `list`, `compile`, `doctor`, `update`, `diff`, `lint`, `preview`, `explain`, and `test` work today; `audit`, `gate`, `search`, `publish`, `lore`, and `run` exit `7` and are on the [roadmap](docs/roadmap.md). One first-party skill ships (`prereview`); six more are specified but not built. Adoption is single-digit stars — if the measurement above is what you want, you are early.
diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json
index 7147bc3..3760e86 100644
--- a/packages/cli/package-lock.json
+++ b/packages/cli/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kitbash",
- "version": "0.15.0",
+ "version": "0.16.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kitbash",
- "version": "0.15.0",
+ "version": "0.16.0",
"license": "Apache-2.0",
"bin": {
"kitbash": "dist/index.js"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0cfd82d..d504a7f 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "kitbash",
- "version": "0.15.0",
+ "version": "0.16.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/test.mjs b/packages/cli/scripts/test.mjs
index 9dd87ae..9a0693f 100644
--- a/packages/cli/scripts/test.mjs
+++ b/packages/cli/scripts/test.mjs
@@ -1278,6 +1278,58 @@ try {
}
}
+// --- import: reverse-compile a repo's existing agent config files ---
+const imp = mkdtempSync(join(tmpdir(), "kitbash-import-"));
+try {
+ // empty repo: nothing to import, exits 0
+ const empty = run(["import"], imp);
+ check("import: empty repo exits 0 with nothing to import", empty.status === 0 && empty.out.includes("nothing to import"), empty.out);
+
+ // agreeing configs across two agents → detected, no drift
+ writeFileSync(join(imp, "CLAUDE.md"), "# Rules\n\nUse strict mode. Write tests.\n");
+ writeFileSync(join(imp, "AGENTS.md"), "# Rules\n\nUse strict mode. Write tests.\n");
+ const agree = run(["import"], imp);
+ check("import: detects both config files", agree.out.includes("CLAUDE.md") && agree.out.includes("AGENTS.md"), agree.out);
+ check("import: reports token cost", /~\d+ tok/.test(agree.out), agree.out);
+ check("import: no drift when identical", agree.out.includes("no drift"), agree.out);
+ check("import: dry run does not write", !existsSync(join(imp, ".kitbash/skills")), agree.out);
+
+ // introduce a drifted third source
+ mkdirSync(join(imp, ".cursor/rules"), { recursive: true });
+ writeFileSync(join(imp, ".cursor/rules/main.mdc"), "---\ndescription: r\n---\nUse strict mode. Always lint first.\n");
+ const drift = run(["import"], imp);
+ check("import: detects drift", drift.out.includes("drifted into 2 different versions"), drift.out);
+ check("import: groups agreeing files together", /version 1: (CLAUDE\.md, AGENTS\.md|AGENTS\.md, CLAUDE\.md)/.test(drift.out), drift.out);
+
+ // --write persists a synthesized skill
+ const written = run(["import", "--write", "--name", "myrules"], imp);
+ check("import --write exits 0", written.status === 0, written.out);
+ check("import --write creates the skill", existsSync(join(imp, ".kitbash/skills/myrules/skill.toml")) && existsSync(join(imp, ".kitbash/skills/myrules/SKILL.md")), written.out);
+ check("import --write pins the skill", readFileSync(join(imp, "kitbash.lock"), "utf8").includes("myrules"), "");
+ const importedToml = readFileSync(join(imp, ".kitbash/skills/myrules/skill.toml"), "utf8");
+ check("import: synthesized manifest is valid-shaped", importedToml.includes('name = "myrules"') && /budget = \d+/.test(importedToml) && importedToml.includes('disclosure = "lazy"'), importedToml.slice(0, 120));
+
+ // the imported skill actually compiles (closes the loop)
+ const importedCompile = run(["compile"], imp);
+ check("import: the imported skill compiles", importedCompile.status === 0 && importedCompile.out.includes("compiled 1 skill"), importedCompile.out);
+
+ // re-writing the same name is refused
+ const dup = run(["import", "--write", "--name", "myrules"], imp);
+ check("import --write refuses an existing name", dup.status === 1 && dup.out.includes("already exists"), dup.out);
+
+ // a purely kitbash-generated file is NOT re-imported as a source
+ const gen = mkdtempSync(join(tmpdir(), "kitbash-import-gen-"));
+ try {
+ writeFileSync(join(gen, "AGENTS.md"), "\n\n\n## Skill: x\n\nbody\n\n");
+ const genImp = run(["import"], gen);
+ check("import: skips a purely kitbash-generated file", genImp.status === 0 && genImp.out.includes("nothing to import"), genImp.out);
+ } finally {
+ rmSync(gen, { recursive: true, force: true });
+ }
+} finally {
+ rmSync(imp, { recursive: true, force: true });
+}
+
if (failures) {
console.error(`\n${failures} test(s) failed`);
process.exit(1);
diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts
index afeabdc..8d9bfa1 100644
--- a/packages/cli/src/commands.ts
+++ b/packages/cli/src/commands.ts
@@ -8,6 +8,7 @@ import { basename, dirname, join, resolve, sep } from "node:path";
import { ADAPTERS, 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";
import { estimateTokens, loadInstalledSkills, loadInstalledSkillsSafe, loadSkill, resolveBody, schemaLints, standingStub, COMMAND_RE, NAME_RE, SKILLS_DIR, type LoadedSkill } from "./ksf.js";
import { parseToml } from "./toml.js";
@@ -48,6 +49,106 @@ export async function cmdInit(): Promise {
return 0;
}
+/**
+ * Reverse compile: read the agent instruction/rule files a repo already has,
+ * measure what each costs, show where they have drifted apart, and synthesize a
+ * single KSF skill so `kitbash compile` can regenerate them all from one source.
+ * This is the on-ramp for a repo already carrying the copy-per-agent mess.
+ */
+export async function cmdImport(args: string[]): Promise {
+ const root = process.cwd();
+ const write = args.includes("--write");
+ const nameArg = flagValue(args, "--name");
+
+ const sources = collectImports(root);
+ if (!sources.length) {
+ console.log("no existing agent instruction files found (CLAUDE.md, AGENTS.md, .cursor/rules/, .clinerules, …).");
+ console.log(" nothing to import — author a skill instead: kitbash init && kitbash install ");
+ return 0;
+ }
+
+ console.log(`found ${plural(sources.length, "agent config file")}:`);
+ for (const s of sources) {
+ console.log(` ${s.file} → ${s.agent} (~${s.tokens} tok, ${s.loading})`);
+ }
+ const eager = sources.filter((s) => s.loading === "eager").reduce((sum, s) => sum + s.tokens, 0);
+ console.log(`standing cost of the always-on files: ~${eager} tokens every session`);
+
+ // Drift is the hook: do the copies actually say the same thing?
+ const groups = driftGroups(sources);
+ if (groups.length === 1) {
+ console.log(`\n✓ all ${sources.length} carry the same rules — no drift.`);
+ } else {
+ console.log(`\n⚠ these ${sources.length} files have drifted into ${groups.length} different versions:`);
+ groups.forEach((g, i) => console.log(` version ${i + 1}: ${g.files.join(", ")}`));
+ console.log(" the canonical version below is the one the most agents agree on.");
+ }
+
+ // Synthesize one skill from the de-facto canonical body (largest drift group).
+ const canonical = groups[0]!;
+ const name = deriveImportName(nameArg, root);
+ if (!NAME_RE.test(name)) {
+ console.error(`invalid skill name "${name}" — use --name `);
+ return 1;
+ }
+ const bodyTokens = estimateTokens(canonical.body);
+ const budget = Math.min(20000, Math.max(500, Math.ceil((bodyTokens * 1.2) / 100) * 100));
+ const desc = `Imported from ${sources.length} existing agent config file${sources.length === 1 ? "" : "s"} (${sources.map((s) => s.agent).filter((a, i, arr) => arr.indexOf(a) === i).slice(0, 4).join(", ")})`;
+ const manifest = [
+ `[skill]`,
+ `name = "${name}"`,
+ `version = "0.1.0"`,
+ `description = ${JSON.stringify(desc.slice(0, 200))}`,
+ ``,
+ `[context]`,
+ `budget = ${budget}`,
+ `standing = 100`,
+ `disclosure = "lazy"`,
+ ``,
+ ].join("\n");
+ const skillMd = groups.length > 1
+ ? `\n\n${canonical.body}\n`
+ : `${canonical.body}\n`;
+
+ if (!write) {
+ console.log(`\n— proposed skill "${name}" (budget ${budget}) —\n`);
+ console.log(manifest);
+ console.log(`# SKILL.md (${bodyTokens} tok, first lines):`);
+ console.log(canonical.body.split("\n").slice(0, 8).join("\n"));
+ console.log(`\nre-run with --write to save it to ${SKILLS_DIR}/${name}/, then: kitbash compile`);
+ return 0;
+ }
+
+ const dest = join(root, SKILLS_DIR, name);
+ if (existsSync(dest)) {
+ console.error(`${SKILLS_DIR}/${name}/ already exists — pass --name or remove it first.`);
+ return 1;
+ }
+ mkdirSync(dest, { recursive: true });
+ writeFileSync(join(dest, "skill.toml"), manifest);
+ writeFileSync(join(dest, "SKILL.md"), skillMd);
+ if (!existsSync(join(root, CONFIG))) writeFileSync(join(root, CONFIG), INIT_CONFIG);
+ upsertLock(root, { name, version: "0.1.0", source: "import:local", integrity: integrityOf(dest) });
+ console.log(`\nwrote ${SKILLS_DIR}/${name}/ (skill.toml + SKILL.md), pinned in ${LOCK_FILE}`);
+ console.log(`next: kitbash preview ${name} (see it per agent + the token cost)`);
+ console.log(`then: kitbash compile (regenerate every target from this one source — ends the drift)`);
+ return 0;
+}
+
+/** Derive a valid skill name from --name or the repo directory. */
+function deriveImportName(nameArg: string | undefined, root: string): string {
+ if (nameArg) return nameArg;
+ const base = basename(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
+ const candidate = `${base || "project"}-rules`.slice(0, 41);
+ return NAME_RE.test(candidate) ? candidate : "project-rules";
+}
+
+/** Value following a `--flag` token, or undefined. */
+function flagValue(args: string[], flag: string): string | undefined {
+ const i = args.indexOf(flag);
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
+}
+
/**
* Confine an install subpath to the cloned repo. Returns the resolved absolute
* path, or null if it escapes `base` (e.g. "../../etc") — a directory-traversal guard.
diff --git a/packages/cli/src/importers.ts b/packages/cli/src/importers.ts
new file mode 100644
index 0000000..dc5d5b9
--- /dev/null
+++ b/packages/cli/src/importers.ts
@@ -0,0 +1,126 @@
+/**
+ * Reverse compile: read the agent instruction/rule files a repo ALREADY has —
+ * the copy-per-agent mess — so `kitbash import` can turn them into one KSF source
+ * and show where they have drifted apart. This is the inverse of the adapters:
+ * adapters WRITE a format; here we READ the common ones back.
+ */
+
+import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
+import { join } from "node:path";
+import { GENERATED_MARK, pruneSections } from "./adapters.js";
+import { estimateTokens } from "./ksf.js";
+
+/** One imported instruction source found in a repo. */
+export interface ImportedSource {
+ agent: string; // the coding agent this file feeds
+ file: string; // repo-relative path
+ body: string; // human-authored instruction text (frontmatter + kitbash sections stripped)
+ tokens: number; // estimated standing cost of that body
+ loading: "eager" | "lazy";
+}
+
+/** Known hand-authored instruction files, by exact path. */
+const FILE_SOURCES: { path: string; agent: string; loading: "eager" | "lazy" }[] = [
+ { path: "CLAUDE.md", agent: "claude-code", loading: "eager" },
+ { path: "AGENTS.md", agent: "agentsmd", loading: "eager" },
+ { path: "GEMINI.md", agent: "gemini", loading: "eager" },
+ { path: "CONVENTIONS.md", agent: "aider", loading: "eager" },
+ { path: ".cursorrules", agent: "cursor", loading: "eager" },
+ { path: ".windsurfrules", agent: "windsurf", loading: "eager" },
+ { path: ".clinerules", agent: "cline", loading: "eager" }, // may also be a directory (handled below)
+ { path: ".github/copilot-instructions.md", agent: "copilot", loading: "eager" },
+];
+
+/** Known rule directories whose *.md/*.mdc files are each an instruction source. */
+const DIR_SOURCES: { dir: string; ext: string; agent: string; loading: "eager" | "lazy" }[] = [
+ { dir: ".cursor/rules", ext: ".mdc", agent: "cursor", loading: "lazy" },
+ { dir: ".github/instructions", ext: ".instructions.md", agent: "copilot", loading: "eager" },
+ { dir: ".clinerules", ext: ".md", agent: "cline", loading: "eager" },
+ { dir: ".windsurf/rules", ext: ".md", agent: "windsurf", loading: "lazy" },
+ { dir: ".devin/rules", ext: ".md", agent: "windsurf", loading: "lazy" },
+];
+
+/**
+ * Strip a file down to its human-authored instruction text: drop leading YAML
+ * frontmatter, remove any kitbash-generated marker sections and header comment,
+ * and trim. Returns "" for a file that is entirely kitbash output (nothing to import).
+ */
+export function stripToBody(raw: string): string {
+ let s = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw; // BOM
+ s = s.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, ""); // leading frontmatter
+ s = pruneSections(s, new Set()); // remove all sections
+ s = s
+ .split(/\r?\n/)
+ .filter((line) => !line.includes(GENERATED_MARK)) // drop generated-header comments
+ .join("\n");
+ return s.trim();
+}
+
+function readSource(root: string, rel: string, agent: string, loading: "eager" | "lazy"): ImportedSource | null {
+ const body = stripToBody(readFileSync(join(root, rel), "utf8"));
+ if (!body) return null; // empty or purely kitbash-generated — not a real existing config
+ return { agent, file: rel, body, tokens: estimateTokens(body), loading };
+}
+
+/** Every hand-authored agent instruction source present in the repo. */
+export function collectImports(root: string): ImportedSource[] {
+ const out: ImportedSource[] = [];
+ const seen = new Set();
+
+ for (const s of FILE_SOURCES) {
+ const abs = join(root, s.path);
+ if (!existsSync(abs) || !statSync(abs).isFile()) continue; // .clinerules may be a dir
+ const src = readSource(root, s.path, s.agent, s.loading);
+ if (src) {
+ out.push(src);
+ seen.add(s.path);
+ }
+ }
+
+ for (const d of DIR_SOURCES) {
+ const abs = join(root, d.dir);
+ if (!existsSync(abs) || !statSync(abs).isDirectory()) continue;
+ for (const name of readdirSync(abs).sort()) {
+ if (!name.endsWith(d.ext)) continue;
+ const rel = `${d.dir}/${name}`;
+ if (seen.has(rel)) continue;
+ const src = readSource(root, rel, d.agent, d.loading);
+ if (src) {
+ out.push(src);
+ seen.add(rel);
+ }
+ }
+ }
+ return out;
+}
+
+/** A set of sources sharing byte-identical instruction text (after whitespace normalization). */
+export interface DriftGroup {
+ body: string;
+ files: string[];
+}
+
+/** Normalize for drift comparison: collapse runs of whitespace, trim each line. */
+function normalize(body: string): string {
+ return body
+ .split(/\r?\n/)
+ .map((l) => l.replace(/\s+/g, " ").trim())
+ .filter(Boolean)
+ .join("\n");
+}
+
+/**
+ * Group sources by whether they carry the same rules. One group means every agent
+ * agrees; more than one means the copies have drifted apart — the pain to surface.
+ */
+export function driftGroups(sources: ImportedSource[]): DriftGroup[] {
+ const groups = new Map();
+ for (const s of sources) {
+ const key = normalize(s.body);
+ const g = groups.get(key);
+ if (g) g.files.push(s.file);
+ else groups.set(key, { body: s.body, files: [s.file] });
+ }
+ // Largest group first (the de-facto canonical version), then by first file for determinism.
+ return [...groups.values()].sort((a, b) => b.files.length - a.files.length || (a.files[0]! < b.files[0]! ? -1 : 1));
+}
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index b05c4ca..7e94389 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -8,7 +8,7 @@
*/
import { createRequire } from "node:module";
-import { cmdCompile, cmdDiff, cmdDoctor, cmdInit, cmdInstall, cmdList, cmdRemove, cmdTest, cmdLint, cmdExplain, cmdPreview, cmdUpdate } from "./commands.js";
+import { cmdCompile, cmdDiff, cmdDoctor, cmdImport, cmdInit, cmdInstall, cmdList, cmdRemove, cmdTest, cmdLint, cmdExplain, cmdPreview, cmdUpdate } from "./commands.js";
const VERSION: string = createRequire(import.meta.url)("../package.json").version;
@@ -30,6 +30,7 @@ function todo(name: string) {
const commands: Command[] = [
{ name: "init", summary: "Set up kitbash in this repository (kitbash.toml)", run: cmdInit },
+ { name: "import", summary: "Turn a repo's existing agent config files (CLAUDE.md, .cursor/rules, …) into one skill + a drift report (--write; --name)", run: cmdImport },
{ name: "install", summary: "Install a skill with pre-install review: gh:owner/repo[/path][@ref], owner/repo, or file:path (--yes; [policy] enforced)", run: cmdInstall },
{ name: "remove", summary: "Remove an installed skill", run: cmdRemove },
{ name: "list", summary: "List installed skills with versions and context cost", run: cmdList },
diff --git a/site/benchmark.html b/site/benchmark.html
index 8951594..c381678 100644
--- a/site/benchmark.html
+++ b/site/benchmark.html
@@ -128,7 +128,7 @@
#What is measured
Two costs matter, and conflating them is how the expensive one stays invisible:
Loaded — tokens the agent reads when the skill is actually in play. Roughly the same everywhere; this is the number people assume they are paying.
-
Standing — tokens sitting in the context window every session, before the skill is invoked. Lazy targets keep only a short stub. Eager targets keep the whole body. This is the number nobody measures.
+
Standing — tokens sitting in the context window every session, before the skill is invoked. Lazy targets keep only a short stub. Eager targets keep the whole body. It is the number the copy-per-agent workflow never sees — and the one Kitbash measures per target, from one source, and enforces as a declared budget at compile time.
Token counts are estimates at roughly four characters per token — the same estimator the compiler enforces context.budget and context.standing with, so the benchmark and the build agree by construction. Loading modes are read from the adapters themselves, not restated here, so these tables cannot drift from what the compiler emits.
Kitbash compiles to the cheapest loading mode each target actually supports. Claude Code, Cursor, Copilot, Devin (ex-Windsurf), Gemini CLI and the vendor-neutral .agents/skills/ path all load on demand, so a skill there costs only its stub. What the tables below price is the rest: Cline, Aider and the AGENTS.md floor, whose only mode is eager — they carry the whole body, every session, before the skill is invoked.
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.15.0Current CLI version
+
v0.16.0Current CLI version
8Compile targets
Apache-2.0License
@@ -105,10 +105,22 @@
Changelog
Confirm with kitbash --version, which reads the installed package.json. Install and uninstall routes are covered on the installation page.
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.
+
Added
+
kitbash import — reverse-compile. Reads a repo's existing agent instruction files (CLAUDE.md, AGENTS.md, GEMINI.md, CONVENTIONS.md, .cursorrules, .cursor/rules/*.mdc, .github/copilot-instructions.md, .clinerules, .windsurf/.devin/rules/*), measures what each costs, detects drift — where the copies have silently diverged — and synthesizes one KSF skill from the version the most agents agree on. --write saves it and pins it, so kitbash compile regenerates every target from one source and ends the drift. --name sets the skill name. Non-destructive: your original files are left in place until you remove them. Purely kitbash-generated files are skipped, not re-imported.
+
Fixed
+
Corrected an overstated claim on the benchmark page ("the number nobody measures"). Other tools do estimate context-file token cost; what is distinct about Kitbash is measuring it per target, from one source, and enforcing it as a declared budget at compile time — the page now says that instead.
Security and integrity pass. A multi-agent audit of the shipped code — five independent review passes, every finding independently reproduced before it was accepted — turned up four ways to walk a hostile skill straight past the install gate, plus five ways the tool corrupted or silently discarded its own output. Everything here was reachable in 0.13.0. Nothing here is a new feature.
Usage: kitbash <command> [args]
init Set up kitbash in this repository (kitbash.toml)
+ import Turn a repo's existing agent config files (CLAUDE.md, .cursor/rules, …) into one skill + a drift report (--write; --name)
install Install a skill with pre-install review: gh:owner/repo[/path][@ref], owner/repo, or file:path (--yes; [policy] enforced)
remove Remove an installed skill
list List installed skills with versions and context cost
@@ -157,12 +158,13 @@
Synopsis
Every command runs against the current working directory as the project root. Skills live in .kitbash/skills/, config in kitbash.toml, pins in kitbash.lock.
Command summary
-
Twelve commands are implemented. The other six are wired to a placeholder that prints kitbash <name>: not implemented yet — planned, see the roadmap on stderr and returns exit code 7. They occupy the command surface deliberately, so docs, spec, and implementation grow against one interface.
+
Thirteen commands are implemented. The other six are wired to a placeholder that prints kitbash <name>: not implemented yet — planned, see the roadmap on stderr and returns exit code 7. They occupy the command surface deliberately, so docs, spec, and implementation grow against one interface.
List installed skills with versions and context cost
Available
@@ -206,6 +208,65 @@
init
Exit codes:0 always — both the created and the already-exists path succeed.
+
+
import
+
kitbash import [--write] [--name <name>]
+
Reads the agent instruction and rule files a repo already has, measures what each one costs in standing context, reports where the copies have drifted apart, and synthesizes one KSF skill from the version the most files agree on — so compile can regenerate every target from that single source. It is the reverse of compile, and the on-ramp for a repo already carrying a hand-maintained copy per agent. Nothing is deleted: the files it reads stay in place until you remove them yourself.
+
It reads back the formats the adapters write: CLAUDE.md, AGENTS.md, GEMINI.md, CONVENTIONS.md, .cursorrules, .windsurfrules, .clinerules (file or directory), .github/copilot-instructions.md, and the rule directories .cursor/rules/*.mdc, .github/instructions/*.instructions.md, .windsurf/rules/*.md, and .devin/rules/*.md. YAML frontmatter and any kitbash-generated marker sections are stripped first, and a file that is entirely kitbash output is skipped — so import never re-imports its own generated copies.
+
Drift is the point. Files carrying the same rules after whitespace normalization are one version; more than one version means the copies have diverged, and the report lists which files fell into which. The largest group is treated as canonical — the version the most agents agree on — and that is what the skill is built from. When a skill is written from drifted sources, its SKILL.md is prefixed with a comment saying so, to be reviewed before you compile.
+
+
--writeSave the synthesized skill to .kitbash/skills/<name>/ (as skill.toml + SKILL.md) and pin it in kitbash.lock. Without it, import is a dry run that prints the proposed skill and writes nothing.
+
--name <name>Name the skill. Must satisfy the KSF name rule — lowercase, starting with a letter, 2–41 characters. Defaults to <dir>-rules, derived from the repository directory name.
+
+
$ kitbash import
+found 4 agent config files:
+ CLAUDE.md → claude-code (~638 tok, eager)
+ AGENTS.md → agentsmd (~638 tok, eager)
+ .windsurfrules → windsurf (~638 tok, eager)
+ .cursor/rules/conventions.mdc → cursor (~602 tok, lazy)
+standing cost of the always-on files: ~1914 tokens every session
+
+⚠ these 4 files have drifted into 2 different versions:
+ version 1: CLAUDE.md, AGENTS.md, .windsurfrules
+ version 2: .cursor/rules/conventions.mdc
+ the canonical version below is the one the most agents agree on.
+
+— proposed skill "acme-api-rules" (budget 800) —
+
+[skill]
+name = "acme-api-rules"
+version = "0.1.0"
+description = "Imported from 4 existing agent config files (claude-code, agentsmd, windsurf, cursor)"
+
+[context]
+budget = 800
+standing = 100
+disclosure = "lazy"
+
+# SKILL.md (638 tok, first lines):
+# Engineering conventions
+
+- Write tests for every bug fix — a regression test that fails before the fix lands.
+- Never widen a public type to `any` to silence the type checker.
+- Run `pnpm lint && pnpm test` before opening a pull request.
+- Commit messages use the imperative mood with a scope prefix and no trailing period.
+- Prefer editing an existing module over adding a new one.
+- Ask before adding a runtime dependency; justify it in the PR description.
+
+re-run with --write to save it to .kitbash/skills/acme-api-rules/, then: kitbash compile
+
When every file already carries the same rules there is no drift to report, and that block reads ✓ all 4 carry the same rules — no drift. instead. Re-running with --write saves the skill and pins it:
+
$ kitbash import --write
+…
+wrote .kitbash/skills/acme-api-rules/ (skill.toml + SKILL.md), pinned in kitbash.lock
+next: kitbash preview acme-api-rules (see it per agent + the token cost)
+then: kitbash compile (regenerate every target from this one source — ends the drift)
+
On a repo with no agent instruction files at all there is nothing to reverse-compile, and import says so:
+
$ kitbash import
+no existing agent instruction files found (CLAUDE.md, AGENTS.md, .cursor/rules/, .clinerules, …).
+ nothing to import — author a skill instead: kitbash init && kitbash install <source>
+
Exit codes:0 on a successful dry run or write, and on a repo with nothing to import. 1 when --name is not a valid KSF name, or when --write would overwrite an existing .kitbash/skills/<name>/ — pass --name <other> or remove it first.
Already have agent config files? If your repo already carries CLAUDE.md, .cursor/rules/, AGENTS.md and their kin, you don't have to author or install a skill to start — kitbash import reads the files you already have into one skill and shows you where the copies have drifted apart, ready for kitbash compile. See import.
+
kitbash import
+
+
1. Install the CLI
Kitbash is a zero-dependency CLI. The npm route needs Node 20 or newer; the Homebrew route brings its own.