Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

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

## [0.24.2] — 2026-08-08

### Fixed
- **KSF rejected skill names the Agent Skills spec allows, and quietly renamed the skill instead of saying so.** The name rule was `^[a-z][a-z0-9-]{1,40}$`: it required a leading letter and capped names at 41 characters, while [the spec](https://agentskills.io/specification) allows any lowercase alphanumeric start and up to 64. So `2fa-setup` — a perfectly legal Agent Skill — failed the rule, fell through to the directory-name fallback, had its leading digit stripped by a slug that dropped everything before the first letter, and installed as **`fa-setup`**: a different skill from the one its author published, pinned under that name in `kitbash.lock` and compiled under it into every target. The rule now accepts a leading digit and the spec's full 64-character length, and the slug keeps leading digits instead of eating them.
- The loosening is deliberate in one direction only. `tidy--commits` and `tidy-` still **load**, because tightening a value constraint on a frozen manifest field would turn valid existing skills into load failures (RFC 0002); they are reported by the `name-convention` lint added in 0.24.1, which is where a host-compatibility problem belongs. A frontmatter name KSF cannot load still falls back to the directory name, which is what the spec's name-matches-directory rule wants — that invariant is unchanged and still tested.

## [0.24.1] — 2026-08-12

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Already have skills? A plain SKILL.md folder — the skills.sh / Claude Skills c

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.24.1, 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`, `import`, `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.
**Status.** v0.24.2, 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`, `import`, `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.

<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>
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.24.1",
"version": "0.24.2",
"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
30 changes: 30 additions & 0 deletions packages/cli/scripts/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,36 @@ try {
writeFileSync(join(ok, "skill.toml"), '[skill]\nname = "tidy-commits"\nversion = "1.0.0"\ndescription = "A skill with a spec-clean name"\n[context]\nbudget = 1500\n');
writeFileSync(join(ok, "SKILL.md"), "# Tidy\n\nBody.\n");
check("spec-name: a clean name is silent", !run(["lint", `file:${ok}`], nmTmp).out.includes("name-convention"));

// Interop the other way: names the Agent Skills spec allows that KSF used to
// refuse. A leading digit is legal, and the old rule both rejected it and then
// silently installed the skill under a DIFFERENT name than its author declared.
const digit = join(nmTmp, "2fa-setup");
mkdirSync(digit, { recursive: true });
writeFileSync(join(digit, "SKILL.md"), "---\nname: 2fa-setup\ndescription: Set up two-factor authentication. Use when configuring 2FA for a service.\n---\nBody.\n");
check("spec-name: a leading digit loads under its declared name", loadSkill(digit).manifest.skill.name === "2fa-setup");

const proj = mkdtempSync(join(tmpdir(), "kitbash-specname-proj-"));
try {
run(["init"], proj);
const ins = run(["install", `file:${digit}`, "--yes"], proj);
check("spec-name: and installs under it, not a mangled slug", ins.status === 0 && ins.out.includes("installed 2fa-setup@"), ins.out);
const lock = readFileSync(join(proj, "kitbash.lock"), "utf8");
check("spec-name: the lockfile pins the declared name, not the mangled one", lock.includes('name = "2fa-setup"') && !lock.includes('name = "fa-setup"'), lock);
} finally {
rmSync(proj, { recursive: true, force: true });
}

// An unloadable declared name still falls back to the directory, which is what
// the spec's name-matches-directory rule wants — asserted by the name-invariant
// test above. What must NOT happen is that behaviour swallowing a name KSF can load.

// A 64-character name is legal per the spec; the old 41-char cap rejected it.
const long = "a".repeat(64);
const longDir = join(nmTmp, long);
mkdirSync(longDir, { recursive: true });
writeFileSync(join(longDir, "SKILL.md"), `---\nname: ${long}\ndescription: A skill with the longest name the Agent Skills spec permits.\n---\nBody.\n`);
check("spec-name: a 64-character name loads", loadSkill(longDir).manifest.skill.name === long);
} finally {
rmSync(nmTmp, { recursive: true, force: true });
}
Expand Down
26 changes: 24 additions & 2 deletions packages/cli/src/ksf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,21 @@ export interface LoadedSkill {
}

export const SKILLS_DIR = ".kitbash/skills";
export const NAME_RE = /^[a-z][a-z0-9-]{1,40}$/;
/**
* What KSF will LOAD as a skill name: 1–64 characters, starting alphanumeric,
* then alphanumerics and hyphens.
*
* Deliberately looser than the Agent Skills spec, in both directions and for
* different reasons. It accepts `tidy--commits` and `tidy-`, which the spec
* forbids, because tightening a value constraint on a frozen manifest field
* would turn valid existing skills into load failures (RFC 0002); those names
* are reported by the `skills-spec` lint instead, which is where a
* host-compatibility problem belongs. And it accepts a leading digit and names
* up to 64 characters, which the old rule rejected outright — `2fa-setup` is a
* perfectly legal Agent Skill, and refusing to load one the whole ecosystem
* accepts was interop breakage, not strictness.
*/
export const NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
/** spec/schema/skill.schema.json: a trigger command is a slash plus a lowercase name — never a path. */
export const COMMAND_RE = /^\/[a-z][a-z0-9-]*$/;

Expand Down Expand Up @@ -92,7 +106,15 @@ function loadBareSkill(dir: string, bodyPath: string, nameHint?: string): Loaded
const fm = parseFrontmatter(raw);
const body = raw.replace(FRONTMATTER_RE, "").trimStart();

const slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 40);
// Spec-shaped slug: collapse runs of non-alphanumerics to single hyphens and trim
// them from both ends. Leading digits are kept — `2fa-setup` is a legal skill name.
const slug = (s: string) =>
s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64).replace(/-+$/, "");

// A frontmatter name is used only when KSF can load it. Otherwise the directory
// name wins: the Agent Skills spec requires `name` to match the parent directory,
// so deriving from the directory produces output every host accepts, where
// propagating a mismatched name would produce a skill they silently drop.
const fallback = nameHint ? slug(nameHint) : slug(basename(dir));
const name = fm["name"] && NAME_RE.test(fm["name"]) ? fm["name"] : fallback;
if (!NAME_RE.test(name)) throw new Error(`${dir}: cannot derive a valid skill name (got "${name}")`);
Expand Down
13 changes: 11 additions & 2 deletions site/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h1>Changelog</h1>
<p>Releases follow <a href="https://keepachangelog.com" target="_blank" rel="noopener">Keep a Changelog</a> and semver — for skills <em>and</em> for this CLI, breaking prompt changes are breaking changes. The CLI is published to npm as <a href="https://www.npmjs.com/package/kitbash" target="_blank" rel="noopener"><code>kitbash</code></a> and to Homebrew via <code>singhharsh1708/tap</code>. Tagged builds are on the <a href="https://github.com/singhharsh1708/kitbash/releases" target="_blank" rel="noopener">GitHub releases page</a>.</p>

<div class="stat-row">
<div class="stat"><b><span data-version>v0.24.1</span></b><span>Current CLI version</span></div>
<div class="stat"><b><span data-version>v0.24.2</span></b><span>Current CLI version</span></div>
<div class="stat"><b>8</b><span>Compile targets</span></div>
<div class="stat"><b>Apache-2.0</b><span>License</span></div>
</div>
Expand All @@ -105,10 +105,19 @@ <h1>Changelog</h1>
<p>Confirm with <code>kitbash --version</code>, which reads the installed package.json. Install and uninstall routes are covered on the <a href="docs/install">installation page</a>.</p>

<!-- changelog:begin -->
<article class="release" id="v0.24.2">
<div class="release-head">
<h2><a href="#v0.24.2">v0.24.2</a></h2>
<span class="release-date">2026-08-08</span><span class="release-tag">latest</span>
</div>
<h3 class="group">Fixed</h3>
<ul><li><strong>KSF rejected skill names the Agent Skills spec allows, and quietly renamed the skill instead of saying so.</strong> The name rule was <code>^[a-z][a-z0-9-]{1,40}$</code>: it required a leading letter and capped names at 41 characters, while <a href="https://agentskills.io/specification" target="_blank" rel="noopener">the spec</a> allows any lowercase alphanumeric start and up to 64. So <code>2fa-setup</code> — a perfectly legal Agent Skill — failed the rule, fell through to the directory-name fallback, had its leading digit stripped by a slug that dropped everything before the first letter, and installed as <strong><code>fa-setup</code></strong>: a different skill from the one its author published, pinned under that name in <code>kitbash.lock</code> and compiled under it into every target. The rule now accepts a leading digit and the spec's full 64-character length, and the slug keeps leading digits instead of eating them.</li><li>The loosening is deliberate in one direction only. <code>tidy--commits</code> and <code>tidy-</code> still <strong>load</strong>, because tightening a value constraint on a frozen manifest field would turn valid existing skills into load failures (RFC 0002); they are reported by the <code>name-convention</code> lint added in 0.24.1, which is where a host-compatibility problem belongs. A frontmatter name KSF cannot load still falls back to the directory name, which is what the spec's name-matches-directory rule wants — that invariant is unchanged and still tested.</li></ul>
</article>

<article class="release" id="v0.24.1">
<div class="release-head">
<h2><a href="#v0.24.1">v0.24.1</a></h2>
<span class="release-date">2026-08-12</span><span class="release-tag">latest</span>
<span class="release-date">2026-08-12</span>
</div>
<h3 class="group">Fixed</h3>
<ul><li><strong>A skill name with a doubled or trailing hyphen was only ever flagged for Zed.</strong> KSF's own name rule permits <code>tidy--commits</code> and <code>tidy-</code>, but the <a href="https://agentskills.io/specification" target="_blank" rel="noopener">Agent Skills spec</a> — the shared authority every skills-directory target defers to, Claude Code, Codex, Copilot, Cline, Zed, Gemini CLI and Agent Plugins alike — forbids a leading, trailing or consecutive hyphen outright. So a name that only Zed complained about could in fact be refused by all of them, and several refuse without saying anything. It is now reported once against the skill, at <code>install</code>, <code>lint</code> and <code>test</code>, naming the spec rather than a single target. A warning, not a failure: the name is valid KSF and still compiles, and <code>--strict</code> escalates it like any other warning.</li></ul>
Expand Down
2 changes: 1 addition & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<circle cx="256" cy="256" r="238" fill="none" stroke="#ffb454" stroke-width="5"/>
</svg>
</div>
<p class="eyebrow">Open format for AI agent skills · <span data-version>v0.24.1</span> · stable spec (RFC 0002)</p>
<p class="eyebrow">Open format for AI agent skills · <span data-version>v0.24.2</span> · stable spec (RFC 0002)</p>
<h1>Write an agent skill once. Run it <em>everywhere</em>.</h1>
<div class="actions">
<a class="button" href="docs/quickstart">Get started</a>
Expand Down
Loading