An SDLC for coding agents that installs into any project, for any stack, without editing a single core file. Skills, subagents, and machine-checkable gates live here; everything project-specific is data.
~/.config/ai-tooling/
├── install.sh install · list · doctor · uninstall
├── skills/core/<id>/ 17 stack-agnostic skills
├── agents/<name>.md 9 subagents, each defined by what it may NOT touch
├── scripts/ gate.sh · guard.sh · intake.sh · engines.sh · review.sh · worktree-*.sh
├── stacks/<name>.stack command profiles: generic · node-ts · python · go · roblox
├── templates/ _STACK.md · dev-prompt · GROOM_LOG · BOARD · PROPOSALS · hooks · AGENTS block
└── tests/ the gates' own test suite — bash + git, no other dependency
| Layer | What | Changes per project? |
|---|---|---|
| Core — loop, gates, agents | skills/core/, agents/, scripts/ |
Never. Copied byte-for-byte |
| Adapter — how to lint/test/run here, which engines work, where tasks come from | .tasks/_STACK.md |
Data, not code |
| Domain — economy rules, compliance, product canon | separate skills/agents, registered in _STACK.md |
A separate problem |
Test of correctness: if adopting this in a new project requires editing a file under skills/core/, the
design is broken. Anything that differs must be expressible as data or as a separate skill.
Interactively (recommended) — a session that works out this project's real commands, intake and engines, verifies the gates run, and offers to open a PR adding the harness:
bash ~/.config/ai-tooling/install.sh self-install # once per machine: adds the /adw-install skill
cd /path/to/project && claude
/adw-installA stack profile guesses ruff check .; the interactive install reads the Makefile, the CI workflow and
the dependency manager, runs each candidate command, and records only the ones that work — a gate
configured with a command that errors reports SKIPPED while the loop looks green.
Mechanically:
bash ~/.config/ai-tooling/install.sh install /path/to/project # stack auto-detected
bash ~/.config/ai-tooling/install.sh install . --stack python
bash ~/.config/ai-tooling/install.sh install . --no-hooks # skip the on-edit format hook
bash ~/.config/ai-tooling/install.sh doctor . # version delta, drift, unset commands
bash ~/.config/ai-tooling/install.sh uninstall . # removes generated copies, keeps .tasks/The core is copied into each project, so a change here does not reach them on its own. upgrade takes
them forward:
bash ~/.config/ai-tooling/install.sh upgrade /path/to/project # one project
bash ~/.config/ai-tooling/install.sh upgrade /path/to/project --dry-run # classify, write nothing
bash ~/.config/ai-tooling/install.sh upgrade --all # every project ever installed
bash ~/.config/ai-tooling/install.sh installs # which ones, and at what versionIt re-applies the shape the project was installed with — stack, tools, skills, hooks — read back from
.ai-tooling.json. Changing that shape is a re-install, deliberately.
The distinction that makes it safe: the manifest records a hash per installed file, so an upgrade
can tell a copy that is merely out of date from one this project edited. Untouched copies are replaced
silently; an edited one is reported and kept, and stays kept across later upgrades, until you pass
--force. An edited core file is either a local fix worth upstreaming or drift worth discarding, and
only you know which — so the exit code is non-zero while any are outstanding.
.tasks/_STACK.md is never regenerated. VERSION in install.sh moves with every core change, which
is what lets doctor and installs report which projects are behind.
Then, once per project:
cd /path/to/project
$EDITOR .tasks/_STACK.md # check kit, paths, canon, shared resources
bash scripts/ai/engines.sh probe --write # which engine CLIs actually work (installed ≠ usable)
bash scripts/ai/gate.sh static # confirm the gates runGenerated copies are never clobbered when locally modified — the installer prints SKIP (differs) unless
--force.
intake ─► triage ─► scout ─► plan + validation ─► groom passes (fresh context, rotating lens, ledger)
│ two quiet passes
▼
G2 · operator approves the plan
▼
┌──── worktree (always, resources allocated) ────┐
│ test-author → RED gate → builder → GREEN gate │
│ (may not touch src) (may not touch tests)│
│ validator → evidence gate │
└──────────────────────┬──────────────────────────┘
▼
lens reviewers ∥ wildcard ─► judge (dedupe + refute + rank)
▼
G7 · operator merges ─► harness-improver
Two human gates. Everything else is autonomous — except a blocker, which always stops the run.
bash scripts/ai/gate.sh plan <id> # plan + validation complete, acceptance mapped, no placeholders
bash scripts/ai/gate.sh groom <id> # no open blocker + two consecutive quiet groom passes
bash scripts/ai/gate.sh red [path] # tests MUST fail before the implementation exists
bash scripts/ai/gate.sh green # static + tests pass
bash scripts/ai/gate.sh evidence <id> # every validation check has evidence on disk
bash scripts/ai/guard.sh builder # the implementer's diff may not touch tests
bash scripts/ai/review.sh 1 .tasks/<id>/VALIDATION.md --profile deepAn on-edit hook runs the project's formatter after every Edit/Write (--no-hooks to opt out).
Instructions are advisory; hooks and exit codes are not.
Three outcomes, not two. 0 passed · 1 failed · 2 misuse · 3 DEGRADED: the gate ran no
check at all. A command left empty in _STACK.md is reported as SKIPPED, and a gate where nothing
ran exits 3 rather than 0 — otherwise a project with an empty adapter finishes green having verified
nothing, which is the same disease as an unauthenticated review engine returning silence. Leave a reason
next to a genuinely absent command (LINT_CMD= # none: no linter for Luau) and the gate quotes it back
instead of reporting a missing config; the verdict is unchanged, because no linter is still no coverage.
An exit code nobody checks is an opinion with better posture, so the gates are themselves tested —
against throwaway git repos, with no network, no gh and no engine CLI:
bash tests/run.sh # every case
bash tests/run.sh gate-plan # one
ADW_TEST_SHELL=/bin/bash bash tests/run.sh # under macOS's bash 3.2CI runs both on Linux and macOS, plus shellcheck -S warning over every script (0 errors, 0 warnings;
the 58 info and 58 style findings below that floor are backlog, not a silenced rule). shellcheck is not
part of tests/run.sh — the suite needs only bash and git. A case may declare a known gap — the behaviour we want, asserted
where the code does not deliver it yet. A known gap keeps the suite green and reports itself; the day
it starts passing, the suite fails so the test gets promoted instead of quietly rotting. The current
three are printed by every run, and tests/run.sh ends by naming the surface it does not cover
(anything needing a remote, gh, or a live engine).
workflow-triage picks from blast radius, not diff size, and can escalate mid-run. The operator can
name one instead — /adw-run superlight, /adw-run deep SM-12 — and then it is a decision, not a hint.
superlight |
light |
standard |
deep |
|
|---|---|---|---|---|
| Scout | skip | skip | agent | agent, multi-angle |
| Groom | none | 1 lens, inline | 2 lenses | all lenses |
| Review | 1 reviewer | 1 reviewer | 3 lenses + wildcard + judge | all lenses + wildcard + judge |
| Human gates | G7 | G7 | G2 + G7 | G2 + G7 |
The profile is the phase list — adw-run runs one column, not the whole loop with cheaper settings.
What never scales down: the worktree, the draft PR, gate.sh green, gate.sh evidence, and one
reviewer. A run with no independent look at the diff is a solo run awarding itself a green tick.
superlight still writes PLAN.md and VALIDATION.md, because the gates read them — around a dozen
lines, three of the four required headers one word long. No gate is skipped or special-cased for it.
And there is a floor. A typo, a version bump, a comment fix: do it in the session, run
gate.sh green, show the diff. Blast radius says superlight for a one-liner; that is not a reason to
spend a worktree and a PR on it. Below the floor the cost is the operator's attention, not the tokens.
| Agent | Forbidden |
|---|---|
context-scout |
writes nothing but the provenance index |
planner |
anything outside .tasks/ |
groom-hardener |
product code (one lens per pass, ledger-aware) |
test-author |
source paths |
builder |
test files |
integration-verifier |
source and tests |
slice-reviewer, wildcard-reviewer, review-judge |
everything (read-only) |
guard.sh enforces this on the diff. A prompt that says "please don't" is not a boundary.
The core knows a five-field contract (id, title, body, url, status) and runs your command
to satisfy it:
INTAKE=linear
INTAKE_CMD=linear-kit issue show <ref> --json
INTAKE_SKILL=linear-tasks
INTAKE_STATUS_CMD=linear-kit issue update <ref> --state "<value>"
INTAKE_COMMENT_CMD=linear-kit issue comment <ref> --message "<value>"bash scripts/ai/intake.sh fetch SM-12 # → normalized JSON + extra_context/ticket.md
bash scripts/ai/intake.sh writeback SM-12 --comment "PR: <url>"JSON output is mapped automatically (identifier|key|id, title|name|summary, description|body, …).
Anything else is handed to the agent with INTAKE_SKILL named. No INTAKE_CMD → manual paste. A new
tracker is one config line; a failed fetch is a stop, never an invented ticket. Writeback is off by
default and, when on, makes exactly two writes: in-progress at the start, PR link at the end.
A CLI on PATH can be unauthenticated, unpaid, or rate-limited; it then fails silently and a missing review lens looks exactly like a clean one. So availability is earned:
bash scripts/ai/engines.sh candidates # on PATH
bash scripts/ai/engines.sh probe --write # actually call each; record the ones that answerEntries can pin a model (claude:opus, claude:sonnet) — with one paid vendor, model rotation is still
real independence. Reviews are labelled CROSS-ENGINE > CROSS-MODEL > DEGRADED, and DEGRADED is
never presented as an independent verdict.
| Skill | Phase |
|---|---|
adw-run |
drives the whole loop, stops at G2 and G7 |
task-intake |
ticket / prompt / bug report → .tasks/<id>/ |
workflow-triage |
picks the profile from blast radius |
task-explore · task-plan · task-open-questions · task-checklist |
grooming artifacts |
slice-verify |
VALIDATION.md: acceptance → check → expected → evidence |
groom-harden |
adversarial passes with lens rotation + GROOM_LOG.md ledger |
dev-prompt |
gate check → self-contained implementation prompt |
test-author · slice-implement |
RED then GREEN, under mechanical guards |
verification-before-completion |
prove it before saying done |
slice-review |
rubric registry: lens blocks + judge rubric |
harness-improver |
proposals for improving this harness (never applies them) |
orchestrate · handoff |
parallel tasks; session compaction |
harness-improver writes proposals; you decide. When a proposal is project-independent, edit the
canonical skill here, re-install with --force, and doctor will show which projects still drift.