diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2873df..27c3bed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index a420ca9..d36cacf 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json
index 3760e86..3848c43 100644
--- a/packages/cli/package-lock.json
+++ b/packages/cli/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kitbash",
- "version": "0.16.0",
+ "version": "0.24.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kitbash",
- "version": "0.16.0",
+ "version": "0.24.2",
"license": "Apache-2.0",
"bin": {
"kitbash": "dist/index.js"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index b24e262..1b7058c 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -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",
diff --git a/packages/cli/scripts/test.mjs b/packages/cli/scripts/test.mjs
index e5f5288..0bae8ca 100644
--- a/packages/cli/scripts/test.mjs
+++ b/packages/cli/scripts/test.mjs
@@ -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 });
}
diff --git a/packages/cli/src/ksf.ts b/packages/cli/src/ksf.ts
index d6c5613..edd2cd8 100644
--- a/packages/cli/src/ksf.ts
+++ b/packages/cli/src/ksf.ts
@@ -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-]*$/;
@@ -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}")`);
diff --git a/site/changelog.html b/site/changelog.html
index 213f8a0..63079a3 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.24.1Current CLI version
+
v0.24.2Current CLI version
8Compile targets
Apache-2.0License
@@ -105,10 +105,19 @@ Changelog
Confirm with kitbash --version, which reads the installed package.json. Install and uninstall routes are covered on the installation page.
+
+
+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 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.
+
+
-
2026-08-12latest
+
2026-08-12
Fixed
- A skill name with a doubled or trailing hyphen was only ever flagged for Zed. KSF's own name rule permits
tidy--commits and tidy-, but the Agent Skills spec — 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 install, lint and test, naming the spec rather than a single target. A warning, not a failure: the name is valid KSF and still compiles, and --strict escalates it like any other warning.
diff --git a/site/index.html b/site/index.html
index 54b3c85..85cde00 100644
--- a/site/index.html
+++ b/site/index.html
@@ -151,7 +151,7 @@
- Open format for AI agent skills · v0.24.1 · stable spec (RFC 0002)
+ Open format for AI agent skills · v0.24.2 · stable spec (RFC 0002)
Write an agent skill once. Run it everywhere.