Backstitch
Spec-to-code traceability and invariant checks for Python repositories.
$ uv tool install backstitch
$ cd your-project
$ mkdir -p docs/specs docs/plans src
$ backstitch check --spec-root docs/specs --code-root src
Backstitch verifies that spec requirements point to real implementation owners,
code points back to the requirements it implements, and declared invariants are
bound to real tests. Deterministic checks run without a model. An optional
semantic lane reviews bounded packets through the llm ecosystem without
giving a model open-ended repository access.
Backstitch is a standalone tool. Weft is a reference target and eventual consumer, not a package dependency.
Recommended For
- Spec-driven Python projects. Stable requirement IDs and reciprocal links make intended behavior navigable from docs to code and back.
- Repositories maintained by coding agents. Deterministic ownership, diagnostics, and acceptance gates replace prose-only compliance claims.
- Critical behavior that needs explicit test bindings. Required and draft invariants make missing, malformed, duplicate, or misplaced bindings visible.
- Teams that want bounded semantic review. Backstitch generates finite, reproducible packets before any model call and validates returned evidence.
Table of Contents
- Backstitch
Features
- Bidirectional traceability - Resolves spec mappings and Python backlinks
- Invariant checking - Connects required or draft guarantees to real tests
- Stable diagnostics - Canonical names, short aliases, contexts, and levels
- Configurable policy - Ordered selectors,
fail_on, and suppressibility - Auditable suppression - Hidden findings remain available with reasons
- Deterministic reports - Stable JSON, ordering, locators, and content hashes
- Bounded semantic review - Section and invariant packets with evidence limits
- Runtime-independent parsing - CommonMark and modern Python syntax support
- Self-application - Backstitch's own repository is a strict acceptance corpus
Installation
# Install the CLI globally with uv (recommended)
uv tool install backstitch
# Or with pipx
pipx install backstitch
# Or add it to a project environment
uv add --dev backstitch
pip install backstitch
For a source checkout:
$ uv sync --extra dev
$ uv run backstitch --version
backstitch 0.3.0
Requirements:
- Python 3.11+
llm,markdown-it-py,tree-sitter, andtree-sitter-python(installed automatically)
Quick Start
For a project with application code under src, create the scan roots and add
the repository profile:
$ mkdir -p docs/specs docs/plans src tests
# pyproject.toml
[tool.backstitch.profile]
name = "backstitch-style-v1"
spec_roots = ["docs/specs"]
plan_roots = ["docs/plans"]
code_roots = ["src", "tests"]
test_roots = ["tests"]
Give a requirement a stable ID and map it to an exact repository-relative implementation path:
## Authentication boundary [AUTH-1]
The request path must reject unauthenticated callers.
_Implementation mapping_:
- `src/auth.py`
Add the reciprocal backlink in the owning Python docstring or a nearby comment:
def authenticate(request: Request) -> User:
"""Authenticate one request.
Spec: docs/specs/auth.md [AUTH-1]
"""
Run the deterministic checker from the repository root:
$ backstitch check
$ backstitch check --format json --output spec-trace.json
$ backstitch check --show-suppressions
The built-in backstitch-style-v1 profile defaults to docs/specs for specs
and backstitch plus tests for code. Override roots in configuration or with
repeatable --spec-root, --code-root, and --test-root options.
Command Reference
Global Options
Global configuration options may appear before a command. Command-specific forms are also available where relevant.
--config PATH- Use exactly one configuration file; skip discovery--no-config- Skip repository configuration; packaged defaults still load--version- Show the installed Backstitch version--help- Show help
Commands
| Command | Description |
|---|---|
check |
Build the deterministic trace graph and report findings |
packets |
Generate bounded section or invariant review packets; no model calls |
analyze |
Run llm semantic review over a packet JSONL file |
summarize-analysis |
Combine a deterministic report with semantic results |
doctor |
Diagnose model, credential, decoding, and endpoint readiness |
config show |
Print effective settings, layers, and diagnostic policy as JSON |
config path |
Print the discovered repository configuration path |
Common deterministic examples:
$ backstitch check --repo-root . --warnings-as-errors
$ backstitch check --repo-root . --format json --output spec-trace.json
$ backstitch packets --repo-root . --kind invariant --output invariants.jsonl
$ backstitch packets --repo-root . --kind all --output packets.jsonl
Run backstitch <command> --help for the full option set.
Exit Codes
0- Command completed and no effectivefail_onlevel was present1- The target repository has a finding at an effective failing level2- Arguments, configuration, input artifacts, output, or the tool failed
Exit 1 describes the target repository. Exit 2 describes the invocation or
tool. Backstitch contains per-file failures where possible and never exposes a
Python traceback as normal CLI output.
Core Concepts
Spec and Code Traceability
Backstitch parses Markdown sections with IDs such as [AUTH-1], implementation
mapping blocks, and Python references in module, class, function, and method
docstrings or comments. Mappings use exact repository-relative paths. Bare IDs
resolve only when unique; Backstitch reports ambiguity instead of guessing.
The deterministic report contains normalized sections, code references, mappings, resolved edges, invariants, binds, issues, and summary counts. Identical inputs produce stable JSON ordering.
Invariant Bindings
Declare behavior in an owning Python docstring or inside an ID-bearing spec section:
def resolve() -> Report:
"""Build the deterministic report.
Invariant: [INV.RES.1] Identical inputs produce byte-identical JSON.
"""
Bind the invariant inside a real test definition:
def test_report_is_stable() -> None:
"""Tests-invariant: [INV.RES.1]"""
Use Invariant (draft): for an advisory declaration. Under packaged defaults,
an untested required invariant is an error and an untested draft invariant is a
warning. packets --kind invariant includes bounded target and binding-test
snippets for advisory semantic review.
Diagnostic Policy
Diagnostics have canonical names such as CODE_REF_UNMAPPED_FROM_SPEC and
stable short aliases such as BSC008. Rules are ordered and the last matching
rule wins:
[tool.backstitch.diagnostics]
fail_on = ["error", "warning"]
suppressible_levels = ["warning", "info"]
[[tool.backstitch.diagnostics.levels]]
select = ["BSC*", "INVARIANT_UNTESTED:draft"]
level = "warning"
Selectors accept canonical codes, short codes, *, family prefixes, and
supported contexts. A diagnostic set to off is omitted from normal findings
but remains available in suppressed_issues under --show-suppressions.
Configuration Discovery
Backstitch always starts with packaged defaults, then applies repository
configuration, supported environment values, and explicit CLI options. It
searches upward for the nearest .backstitch.toml or pyproject.toml with a
[tool.backstitch] table. A standalone file uses the same key layout without
the tool.backstitch prefix.
[tool.backstitch.profile]
name = "backstitch-style-v1"
spec_roots = ["docs/specs"]
code_roots = ["src", "tests"]
test_roots = ["tests"]
[tool.backstitch.check]
format = "text"
warnings_as_errors = false
Test roots classify paths within code roots. Replacing code_roots without
also supplying test_roots resets test roots for that configuration layer;
every nonempty final test root must be contained by a final code root.
Semantic Review
Deterministic checks never call a model. Generate packets explicitly, then analyze and summarize them:
$ backstitch check --format json --output spec-trace.json
$ backstitch packets --kind all --output packets.jsonl
$ backstitch analyze --packets packets.jsonl --output analysis.jsonl
$ backstitch summarize-analysis \
--deterministic-report spec-trace.json \
--analysis-results analysis.jsonl
Packets bound the spec text, code snippets, tests, deterministic findings, and evidence ranges shown to the model. Model output is untrusted: Backstitch owns packet identity, validates structured rows and evidence locality, and contains malformed output per packet.
Use backstitch doctor --probe before semantic analysis to check model
registration, credentials, constrained decoding, and endpoint reachability.
Development and Contributing
Runtime
Backstitch uses markdown-it-py for CommonMark block structure and
tree-sitter-python for Python ownership, comments, and docstrings. Running on
Python 3.11 can therefore analyze newer target syntax such as PEP 695 generics
and PEP 701 f-strings without relying on the host interpreter's ast grammar.
Testing
The repository pytest configuration runs the live cloud-provider contract test
in the default local suite. A normal local run needs a working llm model and
credential:
$ uv run pytest -q
For an intentionally hermetic run:
$ uv run pytest -q -m "not live_llm"
$ uv run pytest tests/live/test_live_llm.py -q -o run_live_llm=false
The completion gate also includes:
$ uv run pytest tests/acceptance -q
$ uv run ruff check backstitch tests bin
$ uv run ruff format --check backstitch bin .github/scripts tests
$ uv run mypy backstitch bin/release.py tests
$ uv run backstitch check --repo-root . --show-suppressions
Live LLM Tests
tests/live/test_live_llm.py drives the real packets to analyze to check
to summarize-analysis path. It asserts structured contracts, transport, and
model success for cloud runs, not exact wording or classification.
# Store a provider key once, then run the local-default live test
$ uv run llm keys set openai
$ LLM_MODEL=gpt-5.4-mini uv run pytest tests/live/test_live_llm.py -q
# Or use a provider environment variable
$ OPENAI_API_KEY=... LLM_MODEL=gpt-5.4-mini uv run pytest -m live_llm -q
The same test supports a loopback OpenAI-compatible endpoint such as Ollama:
$ docker run -d --name backstitch-llm \
-p 127.0.0.1:11434:11434 \
-v "$PWD/.ollama-cache:/root/.ollama" \
ollama/ollama
$ docker exec backstitch-llm ollama pull llama3.2:3b
$ BACKSTITCH_LOCAL_LLM_UPSTREAM=http://127.0.0.1:11434/v1 \
BACKSTITCH_LOCAL_LLM_SERVED_MODEL=llama3.2:3b \
BACKSTITCH_LIVE_LLM_KIND=local \
uv run pytest -m live_llm -q
Cloud tests cost money and can fail because of provider outages, rate limits,
or model retirement. Local endpoints avoid provider credentials but add model
startup and output-quality variance. See
docs/implementation/06-choosing-a-local-model.md for measured local-model
guidance.
The hermetic CI matrix always deselects live tests. The cloud CI job requires
BACKSTITCH_CI_LIVE_LLM=1 plus a main-branch push or manual run on main, and
uses read-only repository permissions. It runs the provider probe only when the
OPENAI_API_KEY repository secret is configured; otherwise that job exits
successfully with a skip notice. The separate local-llm workflow owns the
Ollama canary.
Releases
The local release helper runs regular, cloud-live, and local-live checks before creating and pushing a version tag. The tag-triggered workflow publishes to PyPI through Trusted Publishing and creates the GitHub Release.
$ bin/release.py --version X.Y.Z --dry-run
$ bin/release.py --version X.Y.Z
# When version files and CHANGELOG.md are already prepared
$ bin/release.py all --dry-run
$ bin/release.py all
See docs/implementation/05-release-publishing.md for release setup, rollback,
and verification.
License
Backstitch is released under the MIT License. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file backstitch-0.3.0.tar.gz.
File metadata
- Download URL: backstitch-0.3.0.tar.gz
- Upload date:
- Size: 567.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0f8a18e0443cd4ac7726fff5429955c60fdfbb704c3062127e8c5fcac054573
|
|
| MD5 |
5d21a0f9ef25c1b9dd8fd89469ab75c7
|
|
| BLAKE2b-256 |
d30803e64ce272570f5baa9ccacde6d57742e17f46823fc273167e528f3d9a3f
|
Provenance
The following attestation bundles were made for backstitch-0.3.0.tar.gz:
Publisher:
release-gate.yml on VanL/backstitch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
backstitch-0.3.0.tar.gz -
Subject digest:
c0f8a18e0443cd4ac7726fff5429955c60fdfbb704c3062127e8c5fcac054573 - Sigstore transparency entry: 2140853799
- Sigstore integration time:
-
Permalink:
VanL/backstitch@60f4d2c16e3fbf910ff2a521e7ea07b60f17f80b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/VanL
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-gate.yml@60f4d2c16e3fbf910ff2a521e7ea07b60f17f80b -
Trigger Event:
push
-
Statement type:
File details
Details for the file backstitch-0.3.0-py3-none-any.whl.
File metadata
- Download URL: backstitch-0.3.0-py3-none-any.whl
- Upload date:
- Size: 93.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
412909f9c28bd3ef502c6c294c67059f33614036bb58bfdeff1a3f886d060f99
|
|
| MD5 |
8d3128d66cae20ba2fd802966cfbbccf
|
|
| BLAKE2b-256 |
d4f35e7e7299e32e3f62f5751d5bc9808f7b8ac06542fc0d160df69fb02380e8
|
Provenance
The following attestation bundles were made for backstitch-0.3.0-py3-none-any.whl:
Publisher:
release-gate.yml on VanL/backstitch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
backstitch-0.3.0-py3-none-any.whl -
Subject digest:
412909f9c28bd3ef502c6c294c67059f33614036bb58bfdeff1a3f886d060f99 - Sigstore transparency entry: 2140854109
- Sigstore integration time:
-
Permalink:
VanL/backstitch@60f4d2c16e3fbf910ff2a521e7ea07b60f17f80b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/VanL
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-gate.yml@60f4d2c16e3fbf910ff2a521e7ea07b60f17f80b -
Trigger Event:
push
-
Statement type: