Contract branch
Evidence-backed, deterministic breaking-change review of a git branch's changes in whatever repository the skill is invoked from. Whether a change "breaks callers" is a judgement; this skill makes the EVIDENCE deterministic: a bundled Rust tool (ctk), built on tree-sitter, extracts the public contract surface (every public function, method, type, field, variant, constant, and alias, with normalized, formatter-proof signatures and visibility) across Rust, TypeScript/JavaScript, Python, and Go at the base and at the branch head, diffs them, and emits reason-coded candidates — REMOVED, REMOVED_DEPRECATED, SIGNATURE_CHANGED, VISIBILITY_REDUCED, ADDED — each with a semver impact and the before/after signature as proof, plus an optional CI gate. Every finding cites a signature change at file:line. No measurement, no finding. Use whenever the user asks whether a branch/PR breaks the public API or ABI, is backward-compatible, needs a major/minor semver bump, changed an exported contract, removed or renamed public functions/types/fields, or asks "will this break callers", "is this a breaking change", "what semver bump does this need". Produces a ranked, signature-cited breaking-change report with a semver call — it assesses, it does not fix.From its SKILL.md
npx -y skills add iaserrat/krafter --skill contract-branchAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.5 KB, as published. Nobody here has run it
Contract-Branch
You measure the contract first, then judge the blast radius. Whether a change breaks downstream callers is a judgement — but the evidence you judge from does not have to be. This skill grounds every compatibility claim in a deterministic signature diff, so the review is reproducible in a PR instead of a wall of "this might break something".
The bundled Rust tool ctk computes the facts: the public contract surface at the base ref and at the
branch head, and the reason-coded diff between them. Your job is to turn those into a small set of
high-conviction findings — each anchored to a before → after signature at file:line — and a single
semver call. No measurement, no finding — anything you cannot tie to a ctk candidate is a "consider".
This skill is repo-agnostic. ctk reads a public contract from Rust, TypeScript/JavaScript, Python,
and Go (parsing each with tree-sitter); it reports anything else as unmeasured rather than guessing.
Core directive
Hold this the entire time:
Diff the branch's public contract against its base before you judge compatibility. Report only what a signature change backs, decide the true blast radius per finding (source-breaking? ABI? cosmetic?), give one defensible semver call, and keep it to the changes that matter. The determinism is in the evidence, not in the verdict.
Rules of engagement (the determinism discipline)
- No measurement, no finding. Every finding cites a
ctkcandidate: a reason code +file:line+ thebefore/aftersignature. If you cannot, it is a "consider", filed separately. - Codes, not verdicts.
SIGNATURE_CHANGEDmeans the signature moved — you decide whether it is genuinely source-breaking (a removed param) or compatible (a param rename for positional callers, a widened type). The tool surfaces candidates deterministically; the breaking call is yours. - Reproducible over clever. Same branch + same base → same findings.
ctkis deterministic and formatter-proof (reformatting, reordering, body rewrites never flag). State the base ref you measured. - Honour the honesty flags.
ctkreportsunmeasuredfiles (a language it has no contract rule for, e.g. Go/C/C++) andparse_ok=false. Never present "no breaking changes" as a guarantee over files the tool could not read — say what was and wasn't measured. - Mind the scope limits.
ctkmeasures functions, methods, types, fields, variants, constants, and aliases across the four languages. It does not resolve types (a changed param type is flagged asSIGNATURE_CHANGED; you judge source-compatibility), and a few constructs are known gaps — Rust trait-impl methods, apubmember of a private type, Go multi-name const specs, TSexport { x }re-exports. If the diff turns on one of those, inspect by hand and file findings asCONSIDER. - No nit floods. A wall of additive (
ADDED) entries buries the oneREMOVED. Lead with the breaking set; additive surface is context, not a problem. - Assess; do not fix. Produce the finding, the signature diff, and a migration pointer. Do not edit application code.
What is deterministic vs what is judgement
- Deterministic (the tool's job): what public symbols exist at each ref, their normalized signatures, which were removed / changed / narrowed / added, and the semver impact in isolation.
- Judgement (your job): whether a flagged change actually breaks a real caller here (positional vs keyword args, an overload, an internal-only "public" symbol), whether a removal has a deprecation path, what migration the caller needs, and the one overall semver call. The determinism is in what gets surfaced, not in the verdict.
Phase 0 — Recon: learn the target and what "public" means here
Default the target to the repo you were invoked from and its current branch; if the user named another repo/branch/PR, target that.
REPO=$(git rev-parse --show-toplevel)
git -C "$REPO" branch --show-current
BASE=$(git -C "$REPO" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
BASE=${BASE:-$(git -C "$REPO" rev-parse --verify -q main >/dev/null && echo main || echo master)}
Discover, in roughly this order:
- Stack — languages of the diff (
ctkreads contracts from Rust, TS/JS, Python, Go; note any the diff touches that it cannot, e.g. Go/C/C++ — those need manual review). - What counts as public here — is this a published library/crate/package with external consumers
(every exported symbol is a contract), an internal service (only the wire/API boundary matters), or an
app (maybe nothing is a hard contract)? Read
CONTRIBUTING.md, the package manifest, any#[deprecated]/@deprecatedconventions, and whether the project documents a semver policy. This decides how much aREMOVEDactually costs. - Existing API surface — where the public API lives (a
lib.rs, anindex.ts, a package's__init__), so you can tell a genuine contract from an incidentally-pubhelper.
Build the toolkit once (needs Rust; see toolkit/README.md):
test -x toolkit/target/release/ctk || (cd toolkit && make setup) # idempotent: skips the rebuild if already built
Record the target/scope and the "what is public here" call in two or three lines — it becomes the report header and frames every severity.
Phase 1 — Scope the diff
git -C "$REPO" fetch -q origin "$BASE" 2>/dev/null || true
git -C "$REPO" diff --stat "origin/$BASE...HEAD" 2>/dev/null || git -C "$REPO" diff --stat "$BASE...HEAD"
If the branch touches no source in a language ctk reads a contract from, say so and fall back to a manual
contract review (or stop). Otherwise, note the changed source files and languages.
Phase 2 — Measure with ctk (the deterministic core)
Run from the repo root. ctk discovers the repo and base itself; override with --repo / --base.
assess is the spine. It diffs the branch's public contract against the base and emits the ranked,
reason-coded candidates plus the overall semver impact:
toolkit/target/release/ctk --base "$BASE" assess
Read the result top to bottom:
semver_impact— the headline (major/minor/none). This is your starting semver call; you confirm or downgrade it with judgement.candidates— each is an evidence bundle:path,symbol(qualified, e.g.Config::portfor a field orService::handlefor a method),kind,line,reason,semver,breaking, and thebefore/aftersignatures. Reason codes map to the break catalog:REMOVED— a public symbol is gone (or the file was deleted). The clearest break.REMOVED_DEPRECATED— a removed symbol that was already#[deprecated]/@deprecatedat base; still breaking, but it had a migration window, so it sorts below fresh removals.SIGNATURE_CHANGED— same public symbol, the normalized signature moved. Readbefore/afterto see what (a param added/removed/retyped, a field's type changed, the return changed, a rename).VISIBILITY_REDUCED—pub→ private and similar. Breaks any caller that reached it.ADDED— new public surface (a function, type, field, variant, const). Additive; credit it.
gate—{ impact, tripped, suppressed }.trippedis only ever true under--fail-on; otherwise the run is report-only.suppressedcounts candidates excluded by a--baseline.unmeasured— changed files the tool could not read a contract from (an unsupported language). Review these by hand; they are coverage gaps, not clean bills of health.
For a CI gate, add --fail-on major (exit 2 on any breaking change) or --fail-on minor (any contract
change), and --baseline <file> to accept known breaks (REASON path symbol per line). The headline
semver_impact always reflects the unfiltered truth even when the gate is suppressed.
Drill into a specific file's contract on either side with the building block when you need to see the full
surface (e.g. to confirm an incidentally-pub helper vs a real API symbol):
toolkit/target/release/ctk surface --paths path/to/api.rs # working-tree (head) surface
git show "$BASE:path/to/api.rs" > /tmp/base.rs && ctk surface --paths /tmp/base.rs # base surface
Phase 3 — Judge: turn candidates into findings and one semver call
assess surfaced and ranked the candidates. Your job is the judgement the tool can't make:
- For each breaking candidate, decide whether it breaks a real caller here. A
SIGNATURE_CHANGEDthat only renamed a positional parameter is cosmetic in Rust/Go but breaking in Python (keyword args). AREMOVEDon a symbol that ispubonly for tests is not a real contract break. A widened parameter type may be source-compatible. Downgrade or confirm with a reason. - For each confirmed break, write the migration: what the caller must change, and whether a deprecation shim is possible instead of a hard removal.
- Make one overall semver call. Start from
semver_impact; downgrade it only with an explicit justification per candidate ("the onlyREMOVEDis a test-only symbol"). If anything genuinely breaks a real caller, it is a major.
A candidate becomes a finding only with its signature diff attached. Use references/break-catalog.md for
the taxonomy (each reason code → what to check → typical migration).
Phase 4 — Report
Write to contract-<branch>-<date>.md in the repo root (offer the path; do not commit). Follow
references/report-template.md. Lead with the semver call. Every finding carries:
- The measurement — the
ctkcandidate: reason code,before → aftersignature,file:line. The spine. - The judgement — does this break a real caller here, and why (2-3 sentences).
- Migration — what a caller changes, or the deprecation path instead of a hard break.
- Confidence —
MEASURED(ctk-backed) vsCONSIDER(judgement-only: an unmeasured-language file, or one of the known gaps — a Rust trait-impl method, apubmember of a private type) — keep these visibly separate.
End with a clear semver verdict and a migration punch list. State the bump (major/minor/none) and why
in terms of the candidates, then the ordered list: what breaks callers (the confirmed REMOVED /
SIGNATURE_CHANGED / VISIBILITY_REDUCED), what is merely additive, and what is unmeasured and needs a
manual look. If the contract is unchanged, say "no breaking changes, no bump needed" and stop.
Then open it in the browser
After writing the Markdown report, render it as a single self-contained HTML file and open it so the reader sees the semver call and findings immediately — same content, presented for reading:
- Minimal editorial style. One readable column (
max-w-3xl mx-auto, generous vertical rhythm), a clear type hierarchy, a restrained palette — a well-typeset article, not a dashboard. Use Tailwind via the Play CDN (<script src="https://cdn.tailwindcss.com"></script>); monospace forfile:lineand thebefore → aftersignatures; reserve color for the semver verdict and theMEASURED/CONSIDERdistinction only. No cards, no chrome, no logos. - Save to
/tmpand overwrite on rerun. Write to the stable path/tmp/contract-<branch>.html(sanitize any/in the branch name to-); overwrite it if it exists, so a rerun refreshes the same file instead of piling up renders. - Open it:
open /tmp/contract-<branch>.html(macOS) /xdg-open(Linux).
The Markdown report in the repo root stays the source of record; the HTML is the read-only view.
Calibrate to the ask
Scale to the request. "Did this branch break the API?" → assess, confirm the breaking candidates, give the
semver call. "Full backward-compat audit" / "be strict" → assess plus surface on every changed public
file, manually cover the scope gaps (unmeasured languages and the known type-resolution gaps), and
write the full report with migrations. For a merge gate, wire assess --fail-on major into CI. When
unsure, lean thorough — but never inflate the finding count past what the candidates support, and never
present an unmeasured file as a clean bill of health.