CLI reference
Every command the kitbash binary exposes, the flags it actually parses, the lines it actually prints, and the exit code it returns.
Synopsis
kitbash <command> [args]
The binary is a single dispatch table. It takes the first argument as the command name, hands the rest to that command, and exits with whatever number the command returns. There is no global option parser: flags are matched literally by each command, so --strict and --yes only mean something to the commands documented as accepting them.
Three cases are handled before dispatch:
help/--help/-h, or no argument at all โ print the usage listing and exit0.--versionโ print the version and exit0.- Anything else that isn't a known command โ print
kitbash: unknown command "โฆ"and a did-you-mean suggestion to stderr, exit2.
$ kitbash --version
0.15.0
The usage listing is the command table itself:
$ kitbash help
kitbash 0.15.0 โ write a skill once, run it in every coding agent
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
compile Emit native formats for every detected assistant (--strict)
doctor Detect assistants, report total standing context cost
update Refetch each skill's pinned source and apply changes after a full review diff (--yes; safety lints and [policy] re-enforced)
diff Manifest/permission/file diff between a skill and its source, or any two skills (exit: 0 same, 1 differs, 2 trouble)
lint Static checks: schema, budgets, dead refs, safety lints (--strict; name, path, or uninstalled source)
preview Render each target's output with per-agent token counts โ works on uninstalled sources
explain Why a compilation degraded on a given target (name, path, or uninstalled source)
test Run a skill's static evals (schema, budgets, dead refs, safety lints; --strict)
Planned (not yet implemented):
audit Scan installed skills: permission drift, unsigned sources
gate Run a gate-mode skill with a deterministic exit code
search Search the community index
publish Validate and publish a skill to the index
lore Build, query, and curate repo intelligence
run Run a declared pipeline (e.g. kitbash run ship)
Global: kitbash --version ยท kitbash --help ยท kitbash help <command>
Docs: https://kitbash.vercel.app/docs
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
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.
| Command | Summary | Status |
|---|---|---|
init | Set up kitbash in this repository (kitbash.toml) | Available |
import | Turn a repo's existing agent config files into one skill and a drift report | Available |
install | Install a skill with pre-install review | Available |
remove | Remove an installed skill | Available |
list | List installed skills with versions and context cost | Available |
compile | Emit native formats for every detected assistant | Available |
doctor | Detect assistants, report total standing context cost | Available |
update | Refetch pinned sources, apply changes after a full review diff | Available |
diff | Manifest/permission/file diff between a skill and its source, or any two skills | Available |
lint | Schema, context budgets, dead references, injection heuristics | Available |
audit | Scan installed skills: permission drift, unsigned sources, injection heuristics | Planned โ v0.2 |
preview | Render each adapter's output with per-agent token counts | Available |
explain | Why a compilation degraded on a given adapter | Available |
test | Run a skill's static evals | Available |
gate | Run a gate-mode skill with a deterministic exit code | Planned โ v0.3 |
search | Search the community index | Planned โ v0.4 |
publish | Validate and publish a skill to the index | Planned โ v0.4 |
lore | Build, query, and curate repo intelligence | Planned โ v0.5 |
run | Run a declared pipeline (e.g. kitbash run ship) | Planned โ v0.5 |
Do not script against planned commands. They exit 7, not 127 and not 1. If your CI treats any non-zero code as a failure, calling kitbash audit today will fail the build with a message pointing at the roadmap.
Commands
init
kitbash init
Writes kitbash.toml at the current working directory and creates .kitbash/skills/. The generated config carries a [project] table with every setting commented out: an example targets line, and a commented [policy] block covering allow_sources, deny_network, deny_write, and max_budget. Nothing is enabled until you uncomment it.
The command is idempotent in the safest possible way โ if kitbash.toml already exists it touches nothing, including the skills directory.
(none)Takes no arguments and no flags.
$ kitbash init
created kitbash.toml and .kitbash/skills/
next: kitbash install <gh:owner/repo | owner/repo | file:path>, then kitbash compile
On a repo that already has one:
$ kitbash init
kitbash.toml already exists โ nothing to do
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>/(asskill.toml+SKILL.md) and pin it inkitbash.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.
install
kitbash install <gh:owner/repo[/path][@ref] | owner/repo | file:path> [--yes]
Fetches a skill, shows a review block, asks for confirmation, copies it into .kitbash/skills/<name>/, and records a content hash in kitbash.lock.
The source argument is the first argument that does not start with -. Five forms resolve:
| Form | Resolves to |
|---|---|
gh:owner/repo | Shallow git clone of https://github.com/owner/repo.git |
gh:owner/repo/path/to/skill | The same clone, then a subdirectory |
gh:owner/repo@ref | Full clone, then git checkout <ref> โ branch, tag, or commit |
owner/repo | Same as gh:, but only if no local path of that name exists |
file:./path or ./path | A directory on disk, resolved against the project root |
Resolution order matters: a bare owner/repo is checked as a local path first, so a directory named foo/bar in your repo wins over the GitHub repo foo/bar. Subpaths are confined to the clone โ a subpath that resolves outside it is rejected as escaping the repository. GitHub sources require git on PATH; without it the command tells you to install git or use file:. The temporary clone is removed whether the install succeeds or fails.
Before anything is written, the review block prints what the skill declares: budget, standing limit, disclosure mode, target mode, tool permissions, network and write flags, and any required capabilities. A skill with only a SKILL.md and no skill.toml is flagged as unmanifested. Every failing or warning static check is printed too, so you see budget overruns and injection heuristics before you consent.
A failed safety lint โ visible-text, dynamic-context, remote-exec, or secrets โ blocks the install outright: the command prints the reason, exits 1, and writes nothing. secrets refuses a skill that ships a live credential โ an API key, a database password, or a private-key block. This holds with no kitbash.toml present and is not bypassable by --yes. (Through 0.8.0 the first two were printed but did not stop the install; 0.8.1 fixed that, and secrets joined the gate in 0.10.0.) Schema and quality checks do not block install โ they surface at test.
Because install copies the whole skill directory, the four safety lints scan every non-binary file in it, not just SKILL.md: a curl โฆ | sh in scripts/setup.sh, a live credential in a sibling config file, or hidden text in a sibling .md is caught the same way it is in the body. A symlink is itself flagged โ it can point anywhere and the copy follows it verbatim. Each hit reports the offending file, and the block is enforced exactly like a body hit.
--yes,-ySkip the confirmation prompt. Does not skip the review block, the safety lints, or[policy].
The prompt only appears when both stdin and stdout are TTYs. In a pipe, a CI runner, or a non-interactive shell, install proceeds without asking โ --yes is for readability, not for unblocking automation.
$ kitbash install gh:singhharsh1708/kitbash/examples/skills/prereview
review: prereview@0.1.0 โ Review the working diff against this team's real standards before it ships
budget 1500 tokens ยท standing 60 ยท lazy disclosure ยท mode skill
permissions: tools [read, grep, bash:git diff *, bash:git log *] ยท network no ยท write no
install prereview@0.1.0? [y/N] y
installed prereview@0.1.0
pinned in kitbash.lock
next: kitbash compile
Only y or yes, case-insensitive, count as consent. Anything else โ including an empty line โ aborts:
aborted โ nothing installed.
[policy] is a hard gate. If kitbash.toml declares a [policy] table, the source is matched against allow_sources and the manifest against deny_network, deny_write, and max_budget. Violations are printed and the install is refused โ --yes does not bypass it, because the flag suppresses a question and policy is not a question.
$ kitbash install gh:stranger/skills/exfil --yes
review: exfil@1.0.0 โ Summarizes your codebase
budget 4000 tokens ยท standing 90 ยท lazy disclosure ยท mode skill
permissions: tools [read, bash] ยท network YES ยท write no
โ lint: injection โ heuristic match โ review: data-exfiltration shape
โ policy: source "gh:stranger/skills/exfil" is not in allow_sources (gh:your-org/*)
โ policy: exfil declares network permission and deny_network = true
blocked by [policy] in kitbash.toml.
Reinstalling is explicit. If the skill name is already present the command refuses rather than overwriting, and tells you the two-step sequence.
prereview is already installed. To reinstall: kitbash remove prereview && kitbash install gh:singhharsh1708/kitbash/examples/skills/prereview
Exit codes: 0 when installed. 1 when no source was given, the source string is malformed, git is missing, the clone or ref checkout fails, the subpath is missing or escapes the repo, the folder has no SKILL.md, the skill is already installed, a policy rule is violated, or the prompt is declined.
remove
kitbash remove <skill-name>
Deletes .kitbash/skills/<name>/ and drops the skill's entry from kitbash.lock. It takes an installed skill name only โ not a path and not a source โ and the name must match the KSF name rule (lowercase, starting with a letter, 2โ41 characters).
Removing does not clean up compiled output. The next kitbash compile prunes the generated files this skill owned, which is why the command says so.
<skill-name>Required. The installed name, as shown bykitbash list.
$ kitbash remove prereview
removed prereview
next: kitbash compile (prunes this skill's generated outputs)
If the name isn't installed, the error lists what is:
$ kitbash remove prereviw
prereviw is not installed.
installed: prereview, release-notes
Exit codes: 0 when removed. 1 when the name is missing, malformed, or not installed.
list
kitbash list
Prints one line per installed skill, sorted by name: name and version, declared context budget, declared standing limit, target mode, an [unmanifested] marker for SKILL.md-only skills, and the description. Directories under .kitbash/skills/ without a SKILL.md are skipped, so aborted installs and stray folders don't break the listing.
(none)Takes no arguments and no flags.
$ kitbash list
prereview@0.1.0 budget=1500 standing=60 mode=skill โ Review the working diff against this team's real standards before it ships
release-notes@0.2.0 budget=2200 standing=80 mode=skill โ Turns merged PRs into a release note
scratch@0.0.0 budget=6000 standing=250 mode=skill [unmanifested] โ Imported skill (no manifest)
An empty project says so instead of printing nothing:
no skills installed โ kitbash install <source>
Exit codes: 0 always, including when nothing is installed.
compile
kitbash compile [--strict]
Compiles every installed skill for every configured target and writes the results to disk. Targets come from [project] targets in kitbash.toml; with no config or no targets key, Kitbash autodetects by looking for each adapter's marker file or directory. An unknown target name in the config is a hard error before anything is written.
Per-skill files (.claude/skills/, .claude/commands/, .cursor/rules/, .agents/skills/, .github/skills/, .gemini/skills/, .clinerules/, .devin/rules/ or .windsurf/rules/) are written whole. Shared files (AGENTS.md, CONVENTIONS.md) are marker-merged: each skill owns a <!-- kitbash:begin <name> --> โฆ <!-- kitbash:end <name> --> section and your own prose around it is preserved.
Compile is also the garbage collector. Files in managed directories that carry the generated header but were not written by this run are deleted, including the .github/instructions/ files older versions emitted. A managed shared file that nothing wrote to this run โ AGENTS.md, GEMINI.md or CONVENTIONS.md โ has all of its generated sections pruned, since none of them are current. That is how removing a skill eventually removes its output.
Budgets are enforced here, not merely reported. A manifested skill whose compiled body exceeds context.budget, or whose standing stub exceeds context.standing, fails the whole compile before any file is written. Unmanifested skills never declared those numbers, so their overruns are measured and warned about instead.
--strictExit1if the run produced any warnings. Files are still written; the failure is reported after.
$ kitbash compile
โ .claude/skills/prereview/SKILL.md
โ .claude/commands/prereview.md
โ .cursor/rules/prereview.mdc
โ AGENTS.md
โ removed .claude/commands/old-review.md (stale)
โ prereview โ agentsmd: agentsmd is eager and cannot lazy-load; this skill costs ~560 tokens standing every session (declared limit: 60)
compiled 1 skill for 3 targets
Warnings cover eager-target standing cost, capability degradation, unmanifested skills, and output-path conflicts where two skills write the same file. With --strict those same warnings end the run:
$ kitbash compile --strict
โ .claude/skills/prereview/SKILL.md
โ AGENTS.md
โ prereview โ agentsmd: agentsmd is eager and cannot lazy-load; this skill costs ~560 tokens standing every session (declared limit: 60)
compiled 1 skill for 2 targets
--strict: failing on 1 warning
A budget violation stops the run outright:
โ prereview: compiled body is ~1712 tokens, over its budget of 1500
With nothing installed, compile still prunes stale output, then reports and succeeds:
no skills installed โ kitbash install <source> to add one
Exit codes: 0 on a clean compile. 1 on an unknown target in kitbash.toml, a manifested skill over its budget or standing limit, or --strict with at least one warning.
doctor
kitbash doctor
The repo health check. It reports which of the eleven 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.
(none)Takes no arguments and no flags.
$ kitbash doctor
detected targets:
โ claude-code
โ cursor
โ agents
โ zed
โ copilot
โ cline
โ windsurf
โ gemini
โ aider
โ agentsmd (floor: Codex, Gemini CLI, anything reading AGENTS.md)
โ agent-plugins (opt-in: name it in [project].targets)
installed skills: 2
standing context cost: ~98 tokens (stubs); worst-case active: 3700 tokens (budgets)
lock integrity: ok
policy: ok
The agentsmd target is always detected โ AGENTS.md is the floor every repo compiles to. A failing run looks like this:
installed skills: 2
standing context cost: ~98 tokens (stubs); worst-case active: 3700 tokens (budgets)
โ prereview: integrity drift โ installed files differ from kitbash.lock
โ scratch: installed but not pinned in kitbash.lock โ reinstall to pin it.
2 integrity problem(s) โ reinstall or investigate
A lockfile entry for a skill that is not installed is a warning, not a failure โ that is the ordinary state after removing a skill from disk by hand.
Exit codes: 0 when detection, integrity, and policy all pass. 1 on a missing lockfile with skills installed, integrity drift, an unpinned installed skill, or a policy violation.
update
kitbash update [skill-name] [--yes]
Refetches each installed skill's pinned source โ the exact gh:/file: source recorded in kitbash.lock โ and, when anything differs, prints the complete review before touching a byte: manifest field deltas with permission escalations flagged, the changed-file list, then a unified diff of every readable file. Only after that does it ask for confirmation, replace the installed directory, and re-pin the lockfile with the new version and content hash.
$ kitbash update
diff: prereview@0.1.0 โ prereview@0.2.0
manifest:
version: 0.1.0 โ 0.2.0
budget: 500 tok โ 800 tok โ escalation
permissions.network: no โ YES โ escalation
files:
~ SKILL.md
~ skill.toml
+ helper.sh
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,5 +1,6 @@
Do the thing.
Step one: read files.
-Step two: report.
+Step two: fetch the changelog from the network.
+Step three: report.
โฆ
update prereview to 0.2.0? [y/N]
Three properties are deliberate:
- The four safety lints that gate
installโvisible-text,dynamic-context,remote-exec,secretsโ re-run against the new version and block the update regardless of--yes. A skill must clear the same gate to change on disk as to arrive. [policy]is re-enforced. A new version that declares a permission your policy denies, or grows pastmax_budget, cannot arrive by update.- Unlike
install, a non-interactive run never auto-applies. No TTY and no--yesmeans the diff prints, nothing changes, and the exit code is1. The command's whole contract is that a human saw the diff and said yes.
Local edits to an installed skill are detected via the lockfile hash and called out before being overwritten. A source that renames its skill is refused โ remove and reinstall instead of letting one skill silently replace another. An unchanged skill reports up to date.
[skill-name]Optional. Update only this skill. Omit to check everything pinned inkitbash.lock.--yesApply without prompting. Does not bypass safety lints or[policy].
Exit codes: 0 when every checked skill is up to date or was applied. 1 when any skill failed to fetch or load, was blocked by a safety lint or policy, or was left unapplied (declined, or non-interactive without --yes).
diff
kitbash diff <skill-name> [<skill-name | path | source>]
The same review update prints, read-only. With one argument it diffs the installed skill against a fresh fetch of its pinned source โ "what would update do?". With two arguments it diffs any two skills, each resolved as an installed name, a local path, or a fetchable source, so you can compare two versions of a stranger's skill before either touches your repo:
$ kitbash diff prereview gh:owner/repo/skills/prereview@v2
Output is the manifest field delta (escalations flagged with โ ), the changed-file list (+ added, - removed, ~ modified; binaries and symlinks are listed but not line-diffed), then unified diffs. Identical skills print no differences.
<a>Installed name, path, or source. With no second argument, must be an installed skill pinned inkitbash.lock.[b]Optional second target to compare against.
Exit codes: diff(1) semantics โ 0 identical, 1 different, 2 trouble (unresolvable target, missing lock pin, failed fetch). Scriptable as a cheap "is my skill stale?" probe in CI.
test
kitbash test [skill-name] [--strict]
Runs the static-tier evals over installed skills: manifest presence, template and reference resolution, measured body tokens against context.budget, measured stub tokens against context.standing, artifact reference shape (name@version), slash-prefixed trigger commands, a gate-verdict check, schema-conformance lints, prompt-injection heuristics, and four safety checks on the body itself โ visible-text, dynamic-context, remote-exec, and secrets.
The gate-verdict check applies only to mode = "gate" skills: a gate has to be able to produce a deterministic verdict, so it must carry a scripts/ directory to run or a declared artifacts.produces. A gate with neither fails lint and test โ nothing there can ground a pass/fail.
No eval file is required โ these checks always run. Failures print โ, warnings print โ , passing checks print ยท. Injection heuristics and schema lints only ever warn: a security skill may legitimately quote the phrases it defends against, so the tool refuses to hard-fail on a pattern match.
The four safety checks are the exception โ each is a hard failure that exits 1 with or without --strict, and each also blocks install:
visible-textrejects a body containing zero-width characters, bidi overrides, or codepoints from the Unicode Tags block (U+E0000โU+E007F), which encodes plain ASCII invisibly. Those are instructions a human reviewer cannot see while the agent still reads them. The report names each run of hidden characters by codepoint.dynamic-contextrejects!followed by a backtick command substitution in the body. That construct executes at load time, before the model reads anything, so it never gets a pass.remote-execrejects a download-and-execute pipeline in the body prose โcurl โฆ | sh,eval "$(curl โฆ)",base64 -d | sh, PowerShelliex/iwr, save-then-chmod +x-then-run, remote-archive extract-run. This is the payload that hides in a "Prerequisites" section rather than the manifest. A[policy]may exempt it withdeny_remote_exec = false; the other three are never exemptible.secretsrejects a live credential shipped in the body โ an AWS, Anthropic, OpenAI, GitHub, Google, Stripe, Slack, or Linear key, a database URL with an inline password, or a private-key block. Each pattern keys on the real key shape, so a placeholder guard drops documentation values: a body teachingsk-ant-..., an env ref${OPENAI_API_KEY}, or AWS'sAKIAIOSFODNN7EXAMPLEis not flagged.
Kitbash fans one skill out to nine files, several of them always in context, which is why none of these is a warning.
The optional positional argument filters to a single installed skill. Unlike lint, test does not accept a path or a remote source.
[skill-name]Optional. Restrict the run to one installed skill.--strictExit1if any check warned, not just if one failed.
$ kitbash test
โ prereview
ยท manifest โ prereview@0.1.0
ยท references
ยท budget โ body ~464 tok / budget 1500
ยท standing โ stub ~38 tok / limit 60
ยท artifacts โ produces 1, consumes 0
ยท visible-text โ no hidden characters
โ scratch
โ manifest โ unmanifested (SKILL.md only) โ defaults applied
ยท references
ยท budget โ body ~1204 tok / budget 6000
ยท standing โ stub ~61 tok / limit 250
โ injection โ heuristic match โ review: role reassignment
ยท visible-text โ no hidden characters
tested 2 skill(s) ยท 0 failure(s) ยท 2 warning(s) (static tier)
If any skill has an evals/ directory, test says plainly that only the static tier ran:
note: evals/ present โ audit & behavioral tiers need a runner (not in this build); static tier ran
A real failure โ here a dead template reference and a malformed artifact id:
โ release-notes
ยท manifest โ release-notes@0.2.0
โ references โ release-notes: template references missing /repo/.kitbash/skills/release-notes/prompts/summarize.md
โ artifacts โ malformed: changelog (want name@version)
tested 1 skill(s) ยท 2 failure(s) ยท 0 warning(s) (static tier)
Exit codes: 0 when nothing failed (and, under --strict, nothing warned). 1 when the named skill is not installed, no skills are installed, any check failed, or --strict saw a warning.
lint
kitbash lint [skill-name | path | source] [--strict]
Runs the same static checks as test and prints the same report, but resolves its target far more broadly. The argument is tried in this order: an existing path relative to the project root, then an installed skill name, then a fetchable source (gh:owner/repo[/path][@ref], owner/repo, file:path). With no argument it lints everything installed.
That last case is the point: you can lint a stranger's skill before it ever touches your repo. The remote is cloned to a temporary directory, checked, and deleted.
Four of those checks are hard failures rather than warnings, and they apply to an uninstalled source exactly as they do to an installed skill: visible-text, which rejects hidden characters โ zero-width, bidi overrides, the Unicode Tags block; dynamic-context, which rejects ! plus backtick command substitution in the body; remote-exec, which rejects a download-and-execute pipeline; and secrets, which rejects a live credential shipped in the skill. Any one of them exits 1 without --strict:
$ kitbash lint gh:stranger/skills/helper
โ helper
ยท manifest โ helper@1.0.0
ยท references
ยท budget โ body ~318 tok / budget 1500
ยท standing โ stub ~24 tok / limit 60
โ visible-text โ 2 run(s) of invisible characters (U+200B, U+E0041) โ instructions a reviewer cannot see
โ dynamic-context โ command substitution in the skill body executes before the model sees it: !`cat ~/.ssh/id_rsa`
linted 1 skill(s) ยท 2 failure(s) ยท 0 warning(s)
[target]Optional. A path, an installed name, or an uninstalled source. Omit to lint every installed skill.--strictExit1if any check warned, not just if one failed.
$ kitbash lint gh:stranger/skills/summarize
โ summarize
โ manifest โ unmanifested (SKILL.md only) โ defaults applied
ยท references
โ budget โ body ~7310 tok / budget 6000
ยท standing โ stub ~112 tok / limit 250
โ injection โ heuristic match โ review: override of prior instructions, data-exfiltration shape
ยท visible-text โ no hidden characters
linted 1 skill(s) ยท 0 failure(s) ยท 3 warning(s)
The same run under --strict adds one line and fails:
--strict: failing on 3 warning(s)
An unresolvable argument tells you what it tried:
$ kitbash lint prereviw
prereviw: not found as a path or installed skill name (or pass a source: gh:owner/repo, file:path)
installed: prereview, release-notes
Exit codes: 0 when nothing failed (and, under --strict, nothing warned). 1 when the target cannot be resolved or loaded, no argument was given and nothing is installed, any check failed, or --strict saw a warning.
explain
kitbash explain <skill-name | path | source> <adapter>
Answers one question: what does this skill lose on that agent? Both arguments are positional and both are required. The first resolves the same way as lint's target โ path, installed name, or fetchable source. The second names an adapter: claude-code, cursor, agents, zed, copilot, cline, windsurf, gemini, aider, agentsmd, or agent-plugins.
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 eleven 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 eleven targets, Claude Code included:
$ kitbash explain prereview cursor
prereview โ cursor: degraded
โ requires "scripts" โ not supported by cursor; compiled instruction-only
Missing arguments print the usage line and the adapter list:
$ kitbash explain prereview
usage: kitbash explain <skill-name-or-path-or-source> <adapter>
adapters: claude-code, cursor, agents, zed, copilot, cline, windsurf, gemini, aider, agentsmd, agent-plugins
Exit codes: 0 whether or not degradation was found โ explain reports, it does not judge. 1 when an argument is missing, the adapter name is unknown, the target cannot be resolved or loaded, or the body's template references cannot be resolved.
preview
kitbash preview <skill-name | path | source>
Renders, without writing anything, exactly what each adapter would emit โ every output path, every byte of frontmatter, the full compiled body โ plus that adapter's loading mode and what it costs in standing context. Target resolution is identical to lint and explain, so remote sources work.
Adapters come from [project] targets or autodetection, the same as compile. If kitbash.toml names an unknown target, preview errors out with the same message compile prints rather than falling back to every adapter โ previewing output the repo will never generate would be its own kind of lie.
Lazy adapters are labelled lazy (0 tok standing). Eager adapters show the measured token count of their emitted files, because on those targets that is what every session carries.
<target>Required. Path, installed name, or uninstalled source. The first argument not starting with-.
$ kitbash preview gh:singhharsh1708/kitbash/examples/skills/prereview
preview: prereview@0.1.0
โโโ claude-code [lazy] lazy (0 tok standing) โโโ
โ .claude/skills/prereview/SKILL.md
---
name: prereview
description: "Review the working diff against this team's real standards before it ships"
---
<!-- generated by kitbash โ do not edit; source: .kitbash/skills/prereview @ 0.1.0 -->
# prereview
โฆ
โ .claude/commands/prereview.md
<!-- generated by kitbash โ do not edit; source: .kitbash/skills/prereview @ 0.1.0 -->
Load the "prereview" skill: read .claude/skills/prereview/SKILL.md and follow it exactly.
Arguments: $ARGUMENTS
โโโ agentsmd [eager] ~560 tok standing โโโ
โ prereview โ agentsmd: agentsmd is eager and cannot lazy-load; this skill costs ~560 tokens standing every session (declared limit: 60)
โ AGENTS.md
<!-- kitbash:begin prereview -->
<!-- generated by kitbash โ do not edit; source: .kitbash/skills/prereview @ 0.1.0 -->
## Skill: prereview
โฆ
<!-- kitbash:end prereview -->
The shared-file adapters print only the skill's own marker-delimited section, not the merged file โ that section is what compile would splice in.
Exit codes: 0 on a successful render, warnings included. 1 when no target was given, the target cannot be resolved or loaded, [project].targets in kitbash.toml names an unknown adapter, or the body's template references cannot be resolved.
Exit codes
Four codes, used consistently. Wire CI against these rather than parsing output.
| Code | Meaning |
|---|---|
0 | Success. Also returned by --version, help, by read-only commands that found problems worth reporting but not failing on โ explain on a degraded target, list with nothing installed โ and by diff when the skills are identical. |
1 | Failure. Bad or missing arguments, an unresolvable target, a failed fetch, a validation or integrity failure, a policy violation, a declined prompt, a budget overrun, an unapplied update, or --strict tripping on warnings. Uncaught errors also land here. For diff it means the opposite of trouble: differences were found. |
2 | Usage trouble: an unknown command (a did-you-mean suggestion is printed), or diff unable to resolve one of its targets. |
7 | The command exists but is not implemented in this build. Reserved for the roadmap commands listed in the summary table. |
A useful CI shape: doctor to prove the lockfile still matches disk and policy still holds, test --strict to fail on any warning in your own skills, then compile --strict to fail if the generated output would carry a degradation warning nobody has acknowledged.
kitbash doctor
kitbash test --strict
kitbash compile --strict
One caveat for CI. install only prompts when stdin and stdout are both TTYs, so in a pipeline it installs without asking whether or not you passed --yes. The thing that actually stops an unwanted skill in automation is [policy] in kitbash.toml โ see trust & review. update is deliberately stricter: in a pipeline it never applies without an explicit --yes.