kitbash · diff

git:20260911.0796fe3 to git:20260912.673d228

50 added, 17 removed. Audit A to A.

---
name: kitbash
- description: Decide what to borrow, adapt, or write before building something. Use when the user describes an app, tool, or feature they want to build and needs to know which parts already exist as open-source GitHub repos (BORROW), which are worth reading and adapting (KITBASH), and which to just write (WRITE). Every repo named is checked against the live GitHub API before the user sees it.
+ description: Decide what to borrow, adapt, or write before building something. Use when the user describes an app, tool, CLI, or feature they want to build and needs to know which parts already exist as open-source GitHub repos (BORROW), which are worth reading and adapting (KITBASH), and which to just write (WRITE). Every repository named is checked against the live GitHub API, so invented ones never reach the user.
---
<!-- Generated by `npm run skill` from src/rubric.ts. Edit the rubric, not this file. -->
- # kitbash
+ # Kitbash
- If the `kitbash` and `kitbash_verify` MCP tools are connected, call `kitbash` instead and
- follow what it returns. Everything below is the same flow for when they are not.
+ Most of what someone is about to build already exists. This skill breaks an idea into
+ components, gives each one a verdict — **BORROW**, **KITBASH**, or **WRITE** — and checks
+ every repository it names against the live GitHub API, so the invented ones are gone
+ before the user sees the answer.
- Follow the rubric, then run the verification step at the end. It is not optional.
+ ## When to Use This Skill
+ Use this skill when the user:
+
+ - Describes an app, tool, CLI, or feature they want to build
+ - Asks "is there a library for this" or "should I use a package or write it myself"
+ - Is about to hand-roll something in a domain where correctness was hard-won (PDF parsing, timezone math, OAuth, codecs, Unicode, crypto)
+ - Wants a build plan that names real, maintained dependencies instead of plausible-sounding ones
+ - Asks which parts of a project are worth writing themselves
+
+ ## The Three Verdicts
+
+ | Verdict | Meaning |
+ | --- | --- |
+ | **BORROW** | A maintained library exists and the correctness was hard-won. Rewriting it is the mistake — take the dependency. |
+ | **KITBASH** | A good reference exists but is not a clean fit. Read it, adapt the approach, leave the dependency. |
+ | **WRITE** | Generic enough that you should just write it. No repository needed. |
+
+ ## How It Works
+
+ 1. Decompose the idea into 3-6 functionally distinct components.
+ 2. Give every component exactly one verdict, and recall 1-3 candidate repositories for the BORROW and KITBASH ones.
+ 3. Verify the whole slate — every repository is resolved against the GitHub API, and the ones that do not exist are dropped and counted.
+ 4. Present the verified report, and only that.
+
+ If the `kitbash` and `kitbash_verify` MCP tools are connected, call `kitbash` instead:
+ same flow, with verification as a tool call rather than a shell command.
+
+ ## The Rubric
+
+ Follow it exactly, then run the verification step below.
+
```text
KITBASH — COMPONENT SLATE
You are deciding, component by component, what this product should borrow, what it
should adapt, and what it should just write.
IDEA
<the user's idea, in their own words>
CONSTRAINTS
- TARGET STACK: <the stack the user named — drop this constraint if they did not name one>
Candidates must be usable from this stack. A brilliant Rust crate is not a
BORROW for a TypeScript project unless it ships real bindings — say so if it does,
and downgrade to KITBASH (read the algorithm, port it) if it does not.
- TARGET LICENSE: <the SPDX id they ship under — drop this constraint if unstated>
Prefer candidates compatible with this. Do not adjudicate compatibility in prose —
just prefer permissive licenses for BORROW, and note in the rationale when the only
good fit is copyleft. A copyleft repo is often still a fine KITBASH reference.
The claim is not "ship faster." It is "ship less." Four components not built beats
four components built quickly. Everything below serves that.
================================================================================
1. DECOMPOSE
================================================================================
Break the idea into 3-6 discrete functional components. Each gets a stable slug id
(kebab-case), a short name, and a one-sentence role describing its job IN THIS
PRODUCT — not in general.
Components must be FUNCTIONALLY DISTINCT. The failure mode is over-decomposition:
splitting one library's job into three components so the slate looks thorough.
The canonical mistake: "video transcoder" decomposed into transcode +
compress + format-convert. Those are not three components. They are three
flags on ffmpeg. A domain-aware reader spots this instantly and stops
trusting everything else on the page.
Test each boundary: would a working implementation plausibly use a DIFFERENT
library here than the component next to it? If no, it is one component. Merge it.
Under-decomposing is the cheaper error. Prefer 4 real components to 6 padded ones.
================================================================================
2. VERDICT — the heart of this
================================================================================
Every component gets exactly one:
BORROW A real, maintained dependency exists AND the problem has hard-won
correctness. Rewriting it is the mistake. Take the dependency.
KITBASH A good reference exists but is not a clean fit — wrong runtime, too
heavy, half the surface you need, abandoned but instructive. Read it,
adapt the approach into your own code, do NOT take the dependency.
WRITE Generic enough that the agent should just write it. No repo needed.
BORROW requires BOTH halves. A maintained library for something trivial is not a
BORROW — that is a dependency you are taking to save twenty lines. Hard-won
correctness with no maintained library is a KITBASH, not a BORROW.
================================================================================
3. HARD-WON-CORRECTNESS HAZARD LIST
================================================================================
These are domains where correctness was earned over years of edge cases that
appear in NO SPEC. An LLM rewrite of any of them looks correct, passes the tests
you thought to write, and is subtly wrong in production:
- PDF parsing and generation (malformed xref tables, encodings, embedded fonts)
- Video/audio container muxing and demuxing; codec handling
- Timezone and calendar math (DST transitions, leap seconds, historical offsets)
- Character encoding, Unicode normalization, grapheme segmentation, collation
- OAuth / OIDC / SAML flows and token lifecycle
- Cryptography of any kind, and TLS
- Rate limiting under contention; distributed locks
- Retry and backoff semantics (jitter, budget, idempotency)
- Money as floating point — decimal arithmetic and rounding rules
- Natural-language date parsing
- Image format decoding (chunk-level malformed input, EXIF orientation)
- Compression codecs
- Network protocol implementations (HTTP/2, WebSocket framing, DNS, SMTP)
- Text search relevance scoring and tokenization
- Charset/CSV/Excel dialect sniffing
If a component touches this list, that is a STRONG BORROW signal, and the
rationale should name the specific edge case a rewrite would miss. Not "PDFs are
hard" — "PDFs in the wild have broken xref tables and this recovers from them."
================================================================================
4. YOU ARE REQUIRED TO SAY WRITE
================================================================================
This is a requirement, not a suggestion.
In a typical decomposition AT LEAST ONE component must be WRITE. A tool that
recommends a repo for everything is a tool nobody believes, and the reader will
correctly conclude the verdicts are decoration.
Default to WRITE for:
- glue and orchestration between the other components — the queue, the pipeline,
the retry-this-one-file loop
- config loading and validation
- file watching, polling, directory walking (fs.watch + debounce + a seen-set)
- CRUD and straightforward persistence
- HTTP handlers, routing, request validation
- CLI argument parsing and output formatting
- anything whose whole implementation is under ~100 obvious lines
The tell: if you can describe the implementation in one sentence and a number ("an
array with concurrency 2 and a retry counter, about thirty lines"), it is WRITE. If
you cannot, ask what edge case you are afraid of — and if you cannot name one, it is
still WRITE.
For WRITE, the rationale must say plainly why writing it is CORRECT — the shape is
"this is N lines of obvious code and a dependency here costs more than it saves."
WRITE is a positive result. Do not phrase it as a search failure or an apology.
If your slate has zero WRITE components, you have over-borrowed. Go back and find
the glue.
================================================================================
5. RECALL — the reason this tool exists
================================================================================
For BORROW and KITBASH, propose 1-3 ranked candidate repos from your own knowledge.
DO NOT propose the repo GitHub search would return first. GitHub ranks by
popularity, which buries the correct answer under bigger, more general,
wrong-shaped projects. Correcting that ranking is the entire product.
Prefer the under-starred EXACT fit over the popular general-purpose one.
Aim for the repo the developer would never find on their own: the 400-star library
that does precisely this one thing, the maintained fork that fixed the thing the
original never merged, the tool from an adjacent ecosystem that happens to solve
this exactly. Rank it FIRST when it genuinely fits better. Put the obvious popular
option second as the safe alternate if it is legitimately viable.
Every rationale must give a fit reason SPECIFIC TO THIS COMPONENT. It must be a
sentence you could not have written from the README alone.
Bad: "A fast, modern, well-maintained library for working with PDFs."
Good: "Keeps byte offsets for every extracted span, so highlight positions
survive back to the original page — the search index needs that and
most extractors throw it away."
Use "owner/name" exactly as you believe it is spelled. Do not invent a plausible
owner for a package name you half-remember; a wrong guess is caught and dropped,
but you lose the slot. If you are unsure, propose fewer candidates.
================================================================================
6. OUTPUT CONTRACT
================================================================================
Produce ComponentCandidate[] — a JSON array, this exact shape:
{
"id": string, // stable kebab-case slug, e.g. "pdf-text-extraction"
"name": string, // short human name
"role": string, // one sentence: its job IN THIS product
"verdict": "BORROW" | "KITBASH" | "WRITE",
"rationale": string, // why THIS verdict, specific. Not a README summary.
"candidates": [ // [] for WRITE. 1-3 ranked for BORROW/KITBASH.
{ "owner": string, "name": string }
]
}
Worked example (idea: "watch a folder, OCR new PDFs, make them searchable"):
[
{
"id": "inbox-watcher",
"name": "Inbox watcher",
"role": "Notices new PDFs landing in the watched directory and enqueues them.",
"verdict": "WRITE",
"rationale": "fs.watch plus a 200ms debounce and a seen-set is about forty lines. The only real edge case is a file still being written, solved by waiting for size to stabilize. A watcher dependency here buys nothing and adds a native build step.",
"candidates": []
},
{
"id": "pdf-text-extraction",
"name": "PDF text extraction",
"role": "Pulls the existing text layer out of a PDF before falling back to OCR.",
"verdict": "BORROW",
"rationale": "Real-world PDFs carry broken xref tables, mixed encodings and CID fonts with no ToUnicode map. This recovers text from documents that strictly fail the spec, which is precisely the correctness no rewrite will reproduce. It also preserves per-span byte offsets, so the search index can highlight back into the original page.",
"candidates": [
{ "owner": "pdfminer", "name": "pdfminer.six" },
{ "owner": "jsvine", "name": "pdfplumber" }
]
},
{
"id": "ocr-engine",
"name": "OCR engine",
"role": "Reads text off scanned pages that carry no text layer.",
"verdict": "BORROW",
"rationale": "Decades of trained language data and page-segmentation heuristics. Nothing about this is reimplementable, and the failure mode of a weak OCR pass is silently wrong text that still indexes cleanly.",
"candidates": [
{ "owner": "tesseract-ocr", "name": "tesseract" }
]
},
{
"id": "search-index",
"name": "Search index",
"role": "Makes extracted page text queryable with ranked results.",
"verdict": "KITBASH",
"rationale": "A full search server is the wrong shape for a single-user self-hosted tool — it doubles the deployment surface. SQLite FTS5 gets you tokenization and BM25 in-process; take the schema and ranking approach from a small embedded-search project and drop the server.",
"candidates": [
{ "owner": "nalgeon", "name": "sqlean" }
]
}
]
Note the ordering in pdf-text-extraction: pdfminer.six is the lower-level, tighter
fit and has FEWER stars than pdfplumber, the more popular library built on top of it.
The tighter fit still goes first. That is the required shape, not a stylistic
preference — if your first-ranked candidate is always the most popular one, you are
doing GitHub search's job instead of this one.
Note also that both are permissively licensed. When two candidates fit comparably,
prefer the permissive one for a BORROW — you are recommending a dependency, and a
copyleft obligation is a real cost you are asking the developer to take on.
Return the array and nothing else — no prose wrapper, no markdown fence commentary.
```
- ## 7. Verify. Do not present any of this to the user yet.
+ ## Verify Before You Answer
- Nothing above has been checked, and some of the repos you just named do not exist. Do not
- summarize the slate, preview the verdicts, or paste the JSON.
+ The slate is unverified recall, and some of the repositories in it do not exist. Verification
+ is what makes the answer trustworthy, so it runs before the user sees anything.
- Pipe the whole slate, WRITE components included, into the verifier in one call
- (needs Node 18+; picks up a GitHub token from `GITHUB_TOKEN`, `GH_TOKEN`, or
- `gh auth token`, otherwise runs at 60 checks an hour):
+ Pipe the whole slate, WRITE components included, into the verifier in one call. It needs
+ Node 18+, and picks up a GitHub token from `GITHUB_TOKEN`, `GH_TOKEN`, or `gh auth token`
+ (without one it runs at 60 checks an hour):
```bash
npx -y github:Open-Dev-Society/kitbash verify <<'EOF'
{
"idea": "<the idea, verbatim>",
"stack": "<omit if unstated>",
"target_license": "<omit if unstated>",
"components": [ <the full ComponentCandidate[] array> ]
}
EOF
```
- It resolves every repo against the GitHub API, drops the ones that do not exist, flags
+ It resolves every repository against the GitHub API, drops the ones that do not exist, flags
archived, unlicensed and abandoned ones, ranks the survivors, and prints a markdown report
followed by a `<kitbash_result_json>` block. Read `next_action` in that JSON:
- - `DONE`: show the user the markdown report as-is. Do not add repos to it, re-rank it,
- or restate it from memory.
+ - `DONE`: show the user the markdown report as-is. Do not add repositories to it, re-rank
+ it, or restate it from memory — every fact in it was checked and yours were not.
- `RECALL_REPLACEMENTS`: every candidate died for one or more components. Propose new
- repos for exactly those components and run the verifier again with the full slate.
+ repositories for exactly those components and run the verifier again with the full slate.
- If the verifier cannot run (no Node, no network), say so plainly and present nothing as
- verified. An unchecked repo list is the one failure this skill exists to prevent.
+ If the verifier cannot run (no Node, no network), say so plainly and present the slate as
+ unverified recall. Repositories that were never checked are the one failure this skill
+ exists to prevent.