A runtime-agnostic Spec-Driven Development harness that any AI can operate. Drop it into a repo and the team — humans and AIs alike — follow one disciplined loop: spec first, code second, verify against spec. The pre-commit hook makes it non-optional.
- Why
- Install
- How it works
- Using it day to day
- What you get
- Proven in a real adoption
- Roadmap
- License · Read more
Most "AI in your repo" setups lock you into one assistant, drop ungoverned context, and let code drift from intent until nobody can explain why anything is the way it is.
sdd-harness fixes three things at once:
- Spec before code, enforced. A pre-commit hook blocks feature commits that change code without touching a spec or ADR. The discipline is structural, not a guideline people forget.
- Runtime-agnostic. The brain lives in
.ai/. Any AI (Claude Code, Codex, Cursor, Copilot, Gemini, …) operates the project by reading it. Swapping assistants is a 5-line pointer file, not a migration. (See ADR 0008.) - Cold-start onboarding. Its highest-value scenario is the codebase nobody currently understands — a legacy or inherited repo.
initbootstraps vision, decisions, and vocabulary from the repo itself, so a contributor (or an AI) can make a safe change without tribal knowledge.
It is zero-dependency (bash + git only) and ships nothing stack-specific — your ADRs declare your architecture; the harness only ensures you follow them. It was extracted from a .ai/ layer iterated through many phases in a real production codebase under non-trivial constraints; only the generic discipline is kept.
No global installation needed. In any repository:
curl -sSL https://raw.githubusercontent.com/iMark21/sdd-harness/main/bootstrap.sh | bash -s -- --stack pythonReplace python with your stack: swift, android, node, go, rust, or generic.
This downloads all templates, tools, and hooks directly into your repo. Then:
- With AI:
bash -s -- --stack python --ai-setupdownloads.ai/BOOTSTRAP-AI.md. Open it with any AI that has repo access. - Manual:
cat .ai/BOOTSTRAP.mdto fillPRODUCT.md/BACKLOG.md/CONTEXT.md/ glossary by hand.
If you want sdd-harness CLI on PATH:
git clone https://github.com/iMark21/sdd-harness.git ~/.sdd-harness
bash ~/.sdd-harness/install.sh
sdd-harness init /path/to/repo --stack pythonOr one-command clone + init:
bash ~/.sdd-harness/quick-start.sh user/repo --stack pythonEvery feature on a feat/* or feature/* branch goes through one loop:
spec → review (human + agent) → implement → verify against spec → merge
.ai/hooks/pre-commit-spec-check.sh enforces the first arrow: touch implementation surface on a feat/* or feature/* branch without touching .ai/specs/ or .ai/adrs/, and the commit is refused. By default, implementation surface means every tracked non-documentation path, so iOS, web, backend, monorepos, and unusual layouts are protected without stack-specific setup. Branches chore/*, docs/*, fix/hotfix/* are exempt. The full primer is in .ai/notes/spec-driven-development.md.
Walkthrough: installing into a real, unmaintained Android repo
The subject is deliberately iMark21/marvel-android — last commit October 2021, no active maintainer, a repo the runner does not have in head. That is the point: the harness earns its keep on code nobody is currently the expert on.
1. Install in the clean repo
$ cd marvel-android # last commit Oct 2021, no AI files
$ sdd-harness init . --yes
Fresh repo detected. Routing to 'install'.
Project name: marvel-android
Branch: develop
[install.sh] linked pre-commit -> .ai/hooks/pre-commit-spec-check.sh
...
Done. Next steps:
1. Open .ai/BOOTSTRAP.md — it tells you (or your AI) how to fill
PRODUCT/CONTEXT/BACKLOG/glossary from this repo.2. Start a feature
$ git checkout -b feat/marvel-login3. Commit code without a spec — the hook refuses
$ git add src/LoginViewModel.kt && git commit -m 'feat: login view-model'
[sdd-check] Spec-Driven Development violation.
This feature commit changes code but does not touch .ai/specs/ or .ai/adrs/.
1. Update the relevant spec and re-stage it, or
2. Add an ADR, or
3. SH_SDD_SKIP=1 git commit ... (documented exceptions only)4. Write the spec first — by hand, or paste .ai/BOOTSTRAP.md's prompt into your AI:
Read
.ai/commands/spec.md, then draft a spec for the login screen (Marvel public API, token in EncryptedSharedPreferences, 24h expiry, comics list on HTTP 200, clear error on 401). Save it to.ai/specs/SH-001-marvel-login.mdfollowing the structurespec.mddocuments. Read.ai/PRODUCT.mdand.ai/specs/glossary.mdfirst.
5. Commit spec and code together — passes
$ git add .ai/specs/SH-001-marvel-login.md src/LoginViewModel.kt
$ git commit -m 'feat: login spec + view-model'
[feat/marvel-login 95d194c] feat: login spec + view-modelA short GIF of the same flow: docs/demo.gif.
Branches and commits:
- Branch from
develop, never frommain. - Branch naming:
type/short-description(e.g.,feat/login-pager,fix/timeout-race). - Commit message:
[branch_name] type: "title"(e.g.,[feat/login-pager] feat: "add infinite scroll"). - Merge back to
develop: squash merge, always. - Promote to
main: once per release, fromdeveloponly. Squash merge the entire release.
Pre-commit hook:
.ai/hooks/pre-commit-spec-check.sh enforces spec-first on feat/* and feature/* branches:
- Touch code without touching
.ai/specs/or.ai/adrs/→ commit blocked. - Update a spec, then commit code → commit passes.
chore/*,docs/*,fix/hotfix/*branches are exempt.- Override (documented exceptions only):
SH_SDD_SKIP=1 git commit ...
Versioning and release:
Release process lives in .ai/commands/release.md. Typical flow:
- Propose the release:
git checkout -b release/vX.Y.Z develop - Ask AI to follow
.ai/commands/release.md— updates version numbers, CHANGELOG, etc. - Merge to main:
git checkout main && git merge --squash release/vX.Y.Z && git commit -m "release: vX.Y.Z" - Tag:
git tag vX.Y.Z && git push origin main --tags - Back-merge to develop:
git checkout develop && git merge main && git push
Use semantic versioning (MAJOR.MINOR.PATCH). Releases are tagged on main only.
The loop is driven by seven Markdown procedures in .ai/commands/. You don't
"execute" them like binaries — you (or your AI) ask it to follow that file.
Each file has Usage, Inputs, Procedure, Done criteria.
| Command | When you run it | Example (what you tell your AI) |
|---|---|---|
spec <id> |
Turn a backlog story into a spec + acceptance Gherkin | "Follow .ai/commands/spec.md for SH-002" |
story <id> |
Expand the spec into a file-level implementation plan | "Follow .ai/commands/story.md for SH-002" |
implement <id> |
Execute the plan, tests-first, respecting your ADR layering | "Follow .ai/commands/implement.md for SH-002" |
verify <id> |
Check the code against every acceptance scenario | "Follow .ai/commands/verify.md for SH-002" |
review |
Security + architecture review of the current diff | "Follow .ai/commands/review.md" |
release [lane] |
Drive your project's own release toolchain | "Follow .ai/commands/release.md beta" |
phase-close <next> |
Close the phase: update CONTEXT.md + BACKLOG.md |
"Follow .ai/commands/phase-close.md F2" |
.ai/hooks/pre-commit-spec-check.sh runs on every commit:
| Situation | Result |
|---|---|
feat/* or feature/* branch, touches implementation surface and a spec/ADR |
✅ allowed |
feat/* or feature/* branch, touches implementation surface without any .ai/specs/ or .ai/adrs/ change |
⛔ blocked |
feat/* or feature/* branch, touches only specs/docs |
✅ allowed |
chore/*, docs/*, fix/hotfix/* branch |
✅ exempt — never blocked |
| Any branch, no code touched | ✅ allowed |
"Implementation surface" is whatever SH_CODE_GLOBS includes and
SH_CODE_EXCLUDE_GLOBS does not exclude in .ai/hooks/config.sh.
The default is deliberately broad: include *, then exclude .ai/, runtime
metadata, docs, and common text-only files. Narrow it only for known
repo-specific exceptions. init warns at install time if nothing is protected.
Prohibitions (by design):
- No feature implementation on
feat/*orfeature/*without its spec — the whole point. - No silent bypass: the override is explicit and logged in your shell history.
- The hook never edits your files or auto-writes a spec — it only refuses.
Documented-exception override:
SH_SDD_SKIP=1 git commit -m 'fix: typo in log message'Use it for typo fixes, pure renames, generated files — not for "I'll write the spec later". If you're skipping because the change is real, write the spec.
./.ai/hooks/install.sh wires two hooks (re-run it after editing config.sh):
| Git hook | Script | Does |
|---|---|---|
pre-commit |
pre-commit-spec-check.sh |
The gate above. Exit non-zero blocks the commit. |
post-commit |
post-edit-trace.sh |
Prints which spec/ADR and code files the commit touched — a passive trace for end-of-day reflection. Never blocks. |
1. Greenfield repo
sdd-harness init . --yes
# fill PRODUCT.md + the first BACKLOG row (or paste .ai/BOOTSTRAP.md to your AI)
git checkout -b feat/first-feature
# ask AI: follow .ai/commands/spec.md for FOO-001
# ask AI: follow .ai/commands/implement.md for FOO-001
git add .ai/specs/FOO-001*.md src/... # spec + code together
git commit -m 'feat: FOO-001 ...' # hook passes2. Legacy repo nobody remembers (cold-start)
Paste the AI-assisted install line.
The AI audits the repo and fills the context. Then pick a real TODO from the
migrated backlog and run the loop. This is exactly what was done on
marvel-android (MAR-002 pager).
3. Swapping or adding an AI runtime
sdd-harness init . --runtimes all # Claude, Codex, Cursor, Copilot, Gemini.ai/ is unchanged — only the 5-line root pointer files differ. Switching
assistants is never a migration. See ADR 0008.
4. A genuine one-line exception
# feat/* or feature/* branch, fixing a log typo, no behavior change:
SH_SDD_SKIP=1 git commit -m 'fix: correct typo in error string'The skip is visible in history; reviewers can question it.
5. Closing a milestone
# ask AI: follow .ai/commands/phase-close.md F2
git commit -m 'chore: close F1, open F2' # chore/* is exemptCONTEXT.md becomes the single place a new contributor (or AI) reads to know
where the project stands — no git archaeology. See
.ai/notes/governance-mirror.md.
A .ai/ directory that is the single source of truth for any AI runtime. Canonical files ship ready (leave them alone); you fill files are templates waiting for your project:
.ai/
├── ROUTING.md — canonical: how any AI operates the project
├── PRODUCT.md — you fill: vision and non-goals
├── CONTEXT.md — you fill: phase, branch, decisions, risks
├── BACKLOG.md — you fill: your stories
├── BOOTSTRAP.md — generated: how to fill the above (delete when done)
├── adrs/ — ships ADR 0008; you add 0009+ as decisions land
├── agents/ — ships spec-writer; you add stack-specific reviewers
├── commands/ — canonical: spec, story, implement, verify, review, release, phase-close
├── hooks/ — canonical scripts; you can tune config.sh include/exclude globs
├── notes/ — canonical: SDD primer, governance mirror
└── specs/ — you fill: PRD, glossary, acceptance Gherkin, contracts
Plus 5-line bootloaders at the repo root — CLAUDE.md, AGENTS.md, and one per runtime you enable.
Concrete example: what the "you fill" files look like after bootstrap
.ai/PRODUCT.md
# marvel-android — Product Vision
## Tagline
Native Android client for browsing Marvel comics, characters, and stories
against the public Marvel API.
## Non-Goals
- No in-app comic reader in v1 (link out to marvel.com).
- No offline mode beyond image-thumbnail cache.
## Audience
Marvel fans, primary; Android developers reviewing the codebase, secondary..ai/BACKLOG.md
| ID | Title | Status | Spec | Phase |
|--------|---------------------------------------------|--------|---------------------------------|-------|
| MA-001 | Marvel API login (email + password) | done | acceptance/MA-001-login.feature | F0 |
| MA-002 | Pager — infinite scroll for the heroes list | done | specs/MA-002-pager.md | F1 |
| MA-003 | Hero detail screen | todo | TBD | F2 |Start with PRODUCT.md and the first BACKLOG.md row — the SDD loop drives the rest.
Not a staged demo. sdd-harness was cold-started for real on the legacy iMark21/marvel-android: context bootstrapped from source, the README TODO list migrated into a backlog, and a genuine feature — infinite-scroll pagination (MAR-002) — shipped through the full spec → implement → verify loop with the hook enforcing spec-first.
See
marvel-android@develop:.ai/specs/MAR-002-pager.md·.ai/CONTEXT.md
That adoption surfaced a real product gap — the original default hook globs didn't match the repo's nested Gradle module, so the hook would have passed code-only commits silently. The default now protects every tracked non-documentation path, and the install dry-run still warns if a repo has no protected implementation surface. Adoption driving the product is the intended feedback loop.
- v1.0.0-alpha (shipped) — framework core, CLI rewrite (
bin/sdd-harness), multi-runtime bootloaders, governance mirror (phase-close), install completeness (glob dry-run, git seed,BOOTSTRAP.md), one proven external adoption. - v1.0.0-beta (shipped) — stack-aware bootstrap, AI-assisted setup, README → backlog migration, universal hook surface, multi-runtime bootloaders, deterministic CI plugins, and the generic CI fallback.
- v1.0.0 — stability window with no breaking changes, full
MANUAL.md, contributor playbook, explicit SemVer/deprecation policy. - v1.1.0+ — deterministic CI stack-plugins (ADR 0009), additional reviewer agents, optional integrations.
Coming from iMark21/agentlayer v0.5.0? v1.0 is a documented rupture — the old agent-explore → plan → code → verify flow is gone. See CHANGELOG.md.
MIT. See LICENSE.
.ai/ROUTING.md— start here for any AI.ai/PRODUCT.md— vision and non-goals.ai/notes/spec-driven-development.md— the SDD primer.ai/notes/governance-mirror.md— whyCONTEXT.mdis updated only at phase close.ai/adrs/0008-runtime-agnostic-ai-layer.md— why.ai/and not.claude/- CHANGELOG.md — version history