Skip to content
Back to skills

Tiny Spec Build

ASecurity

Build the spec — run the per-task loop plan→implement→review→commit, one task at a time. Implements with a fresh executor, grades with an independent reviewer running the real gate, commits per passed task, keeps a lean memory. Can be handed several lanes (one worktree + slug per independent story) and runs them concurrently, one task at a time within each. Halts on a blocker, a spent convergence budget, a task's pause: point, or a genuine fork, and names which. Resumes from the checkbox state.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
ai-agentsgodockergitapi

Works with

  • terminal
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 2, 2026

npx -y skills add GrayMa77er/tiny-spec --skill tiny-spec-build --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tiny Spec Build?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tiny Spec Build
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/grayma77er-tiny-spec-build/badge)](https://www.skillsdirectory.com/skills/grayma77er-tiny-spec-build)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tiny-spec-build
description: Build the spec — run the per-task loop plan→implement→review→commit, one task at a time. Implements with a fresh executor, grades with an independent reviewer running the real gate, commits per passed task, keeps a lean memory. Can be handed several lanes (one worktree + slug per independent story) and runs them concurrently, one task at a time within each. Halts on a blocker, a spent convergence budget, a task's pause: point, or a genuine fork, and names which. Resumes from the checkbox state.
---

# tiny-spec-build

The heart of the flow. Walks the active ticket's `tasks.md` top to bottom and runs each task through a
tight loop: **plan → implement → review → commit**. The constitution
(`constitution.md`) anchors every step; the thing that writes the code is never the
thing that grades it.

Artifacts live under `.spec/`: the **shared** constitution and memory at the root
(`.spec/constitution.md`, `.spec/memory.md`), the per-ticket `tasks.md`/`SPEC.md`/
`decisions.md` under `.spec/<ticket-id>/`. The `memory.md` skeleton is inline below —
write it from there, no file to read. The two subagents dispatched below
(`tiny-spec-build-executor`, `tiny-spec-build-reviewer`) are referenced by name — install them
alongside this skill (see the suite README).

## Inputs

1. **Resolve the active ticket dir** (call it `<active>`), in order: the `.spec/<slug>/`
   whose slug matches the current git branch (one branch per ticket); else the sole
   ticket dir if exactly one exists; else ask. **Ask instead** when more than one dir
   matches the branch, or when ticket dirs exist while you are on `main`/`master` with no
   name match — neither has a safe tie-break. Detached HEAD or no git repo is
   **degraded**, not an ask: fall through to sole-dir and ask as written.
   In **multi-lane mode** (below) this step does not run: each lane is handed its
   worktree and slug explicitly.
2. Read `.spec/constitution.md` (**the shared constitution**), `.spec/memory.md` if
   it exists (**shared**), and `.spec/<active>/tasks.md`. The constitution + memory
   get injected **whole** into every executor and reviewer. Also note the `ticket`
   binding in `.spec/<active>/SPEC.md` — it supplies the commit `Refs:` footer.
3. Refuse to start if `tasks.md` is `status: stale` — tell the user to re-run
   `tiny-spec-plan` to reconcile first.
4. Pick the **first unchecked `[ ]`** task. If all are `[x]`, jump to **Completion**.

## Multi-lane mode (optional — several stories at once)

A caller may hand you **lanes** instead of a single active dir: one or more
`(worktree path, slug)` pairs, each an independent story in its own git worktree.
`tiny-spec-run` does this for a batch of stories whose `needs:` say they don't depend on
each other. **With one lane, or none, everything below is inert and this skill behaves
exactly as it always has** — that is the common case and it must not drift.

Given lanes, run them **in rounds**:

1. For every live lane, pick its first `[ ]` task and build the executor brief exactly as
   step 2 describes — resolved against **that lane's worktree**, not the main checkout.
2. **Dispatch every lane's executor in one message**, so they run concurrently.
3. **Dispatch every lane's reviewer in one message**, once the executors are back.
4. Commit and tick per passing lane, in that lane's worktree.

Then start the next round with whichever lanes are still live. A fast lane waits at the
round boundary for a slow one; that barrier is deliberate, and much simpler to follow than
letting lanes free-run.

**Within a lane nothing changes.** Tasks still run one at a time, top to bottom, with the
same convergence bound, the same gate scope rules, and the same halting states. There is
no parallelism *inside* a story and no `owns:` contract — tasks in one story share files
and assume their predecessors landed, which is exactly why they stay sequential.

**A halt stops that lane only.** The other lanes run to completion; you report each lane's
terminal state separately. The stories were declared independent, so killing working lanes
because one failed throws away finished work for nothing.

**Every dispatched agent is told its working directory**, and that all paths resolve
against it. An executor or reviewer must never read or write another lane's worktree.

### `memory.md` in multi-lane mode

`.spec/memory.md` is **shared at the `.spec/` root**, and step 6 below writes to it. Two
lanes appending to their own copy in their own worktree would conflict on *every* parallel
batch — a guaranteed merge failure that has nothing to do with the code.

So: **in multi-lane mode, lanes do not write `memory.md` at all.** Collect each lane's
distilled lessons as it goes, and write them **once, in the main checkout on the
integration branch, after the batch has merged** — pruning superseded entries there, as
step 6 describes. Single-lane runs are unaffected and write it in place as before.

The other artifacts are safe and need no special handling: `tasks.md` and `decisions.md`
are per-story, and `constitution.md` is read-only for the whole build.

## The per-task loop

For the selected task, run these steps in order. **Do not tick a task until its
reviewer passes.**

**Before step 1, check the task's `pause:` field.** If it has one and the user hasn't
waived it for this run, **halt `paused`** (see **Halting** below): leave the task
`[ ]`, dispatch nothing, and don't touch `tasks.md`. A pause stops *before* the work so
the approach gets reviewed while redirecting it is still cheap.

A pause can be waived or added for the current run only, conversationally — "skip the
pause on T5", "also stop before T9". Honor it for this run and **never write it back
into `tasks.md`**: the file records the standing intent, not one run's override.

### 1. PLAN (inline, brief)
Restate the task as a 2–4 step micro-plan against the constitution: which
**invariants** apply, which files it touches, which **Definition of Done** items
and **Verification commands** it must satisfy. This is the executor's brief — keep
it short and concrete.

### 2. IMPLEMENT (dispatch `tiny-spec-build-executor`)
Spawn one **`tiny-spec-build-executor`** with a fresh, self-contained prompt:

- **the working directory** it must operate in — this lane's worktree, or the project
  root in a single-lane run. Every path resolves against it, and it must never read or
  write another lane's worktree;
- the task id, description, and **acceptance**;
- the `files:` hint;
- the **whole** `.spec/constitution.md`;
- the **whole** `.spec/memory.md` if it exists;
- **if the task has a `design:` field** — that `D<n>` entry copied verbatim from
  `.spec/<active>/SPEC.md` plus its `export:` path, so the executor can look at the
  design instead of guessing at it;
- only the specific existing files the task starts from, named explicitly (so it
  edits with the real current contents, not blind);
- the **accumulated `CHANGES` paths from every passed task in this story so far**,
  labelled as the ground this story has already moved. Every executor starts cold and
  re-derives the codebase from scratch — that re-derivation is the **largest single cost
  in the loop**, far larger than the prompt text around it — and naming the files this
  story has already touched is the cheapest way to cut it. You already have the lists in
  hand. **Paths only**, deduplicated, most-recently-touched first — never the earlier
  tasks' descriptions, findings, or reports. (The reviewer does *not* get this list: it
  is scoped to the task's own changed files on purpose, and widening it would dilute the
  independence that makes its verdict worth anything.)

Do **not** pass the plan, sibling tasks, or other chatter. It returns a structured
report (`STATUS`, `CHANGES`, `DECISIONS`, `BLOCKER`). A `STATUS: blocked` →
**Blockers** below; do not proceed with this task.

### 3. REVIEW (dispatch `tiny-spec-build-reviewer` — independent)
Spawn one **`tiny-spec-build-reviewer`**, **blind to step 2**, with:

- **the working directory** it must operate in — this lane's worktree, or the project
  root in a single-lane run. It runs the gate there and touches no other lane;
- the task id, description, and **acceptance**;
- the **whole** `.spec/constitution.md`;
- the list of changed files (from the executor's `CHANGES`) to read;
- the **Verification commands** from the constitution to run, and the **gate scope**
  for this task — `full` or `scoped` (decide it as below; you have the run context,
  the reviewer does not);
- **if the task has a `design:` field** — the same `D<n>` entry and `export:` path
  you gave the executor, so it grades against the contract rather than its taste.

**Gate scope.** A clean `install → build` proves the same thing on task 7 that it proved
on task 1 unless a task changed what it installs or builds, so running it every time buys
little and costs the most wall-clock in the loop. Name the scope in the brief:

- **`full`** — the whole gate from a clean state (install → lint → test → build → run).
  Use it on: the **first** task of this run; any task whose `CHANGES` touch dependency or
  build configuration (`package.json`/lockfiles, `pyproject.toml`, `go.mod`, `Cargo.toml`,
  `Dockerfile`, CI config, build scripts); any task carrying `design:`; and the **last**
  task in `tasks.md`.
- **`scoped`** — otherwise. `lint:` + `test:` plus the acceptance exercised end-to-end,
  black-box, exactly as on a full gate. The acceptance exercise is never scoped away; it
  is the whole reason the reviewer exists.

The reviewer may **escalate** `scoped` to `full` on its own and will say so — that
direction is always safe. It may never go the other way.

It runs the gate at the scope you named, checks the code against the constitution's
**Definition of Done** and **invariants**, confirms the **acceptance** actually
holds (exercised, not inferred), and returns `VERDICT: PASS | FAIL` + findings.
On a `design:` task it also runs the constitution's `visual:` command, measures the
selectors the `D<n>` names against the Design system tokens, exercises the states it
names, and finally **looks at the render beside the design export** — catching what
numbers can't reach, like an element that measures perfectly and shows up invisible.
It fails on what it measured or saw, and merely flags what is taste.

> Why independent: unit-green ≠ working, and the author is the worst judge of its
> own blind spots. The reviewer exercising the acceptance black-box — on every task,
> at either scope — is the safeguard that keeps scope and quality honest without an
> ownership contract.

### 4. CONVERGE (on FAIL)
Re-dispatch the **executor** with the reviewer's findings appended to its brief, then
review again — recomputing the **gate scope** from the fix's `CHANGES`, and using `full`
if the failure was in the gate itself or the reviewer escalated last time. A task that
already failed once has earned the wider check.
Bound this to **2 fix attempts**. If it still fails after that, stop and **halt
`exhausted`** (see **Halting** below) — don't keep grinding or hand-fix past the loop
silently. `exhausted` is recorded as a blocker, because a task that three attempts
couldn't turn green is a signal the upstream document is wrong — but name it
`exhausted`, not `blocked`: the executor never said it couldn't proceed, it just
never got there.

### 5. COMMIT + TICK (on PASS)
Two commits, in order (keeps code history clean of planning churn), both in
**Conventional Commits** format:

1. **Code commit** — stage only the source/test files the executor produced;
   message `<type>(<scope>): <task description>`. The **type** is the task's `type:`
   field, defaulting to `feat`; **scope** is optional (a component, or the ticket
   id). A breaking change uses `!` and/or a `BREAKING CHANGE:` footer.
2. **Bookkeeping commit** — tick the task `[x]` in `.spec/<active>/tasks.md` (bump
   `updated`), add any `decisions.md` entry; message `chore(spec): tick T<n>`.

**`Refs:` footer (ticket linking).** If `.spec/<active>/SPEC.md` has a `ticket`
binding, append a `Refs:` footer to **both** commits so the platform auto-links the
work (omit it entirely if there's no ticket). Always end with the `Co-Authored-By`
trailer:

```
<type>(<scope>): <description>

Refs: <ticket-ref>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
```

Build `<ticket-ref>` from the binding's provider: Jira → `PROJ-123`,
GitHub → `#123`, ADO → `AB#123`, Monday → the item URL. The **closing** keyword
(`Closes #123`, `Fixes AB#123`) is reserved for the final / PR commit, **not** the
per-task commits.

Never commit a red gate. Never tick a task the reviewer did not pass.

### 6. DISTILL (memory)
If steps 2–4 surfaced a **forward-acting operational lesson** (a toolchain quirk,
a flaky/precondition gate, an abandoned approach, a fragile area), append a curated
entry to the **shared** `.spec/memory.md` (the root — lessons are project-wide) —
pruning any entry the new one supersedes. Skip code-style rules (→ shared
`constitution.md`) and one-off history (→ the ticket's `decisions.md`). Keep it lean.

**In multi-lane mode, do not write the file here.** Hold the entry and write it once
after the batch merges — see **Multi-lane mode** above for why.

On first use, create the file with this structure:

```markdown
# Memory — operational lessons

> Curated, forward-acting lessons that should survive across runs so the blind
> executor and reviewer don't re-learn them. **Project-wide** — lives at the
> `.spec/` root and is shared across every ticket. NOT a changelog. Prune superseded
> entries. Code-style rules belong in `constitution.md`; one-off history in the
> ticket's `decisions.md`.

<!-- Each entry:
- type: environment | pitfall | tried-rejected | hotspot
  lesson: <one line — the operational fact>
  apply:  <one line — why it matters / what to do about it>
-->

- type: environment
  lesson: <e.g. the test suite needs the package installed (`pip install -e .`) first>
  apply:  <run the documented setup before the gate; a bare `pytest` gives a false red>
```

### 7. NEXT
Report the task outcome (built, reviewed, committed). Then:
- **Interactive default:** continue to the next unchecked task. Pausing for the
  user between tasks is fine and expected.
- If the user asked to **run it through** ("do it all", "build everything"), or
  `tiny-spec-run` invoked you, keep looping until one of the five terminal states
  below is reached — committing per passed task as you go.

**The task list is the budget.** The loop ends when the tasks end. There is no turn
ceiling, no token cap, and no max-tasks knob to set — adding one would put a second,
weaker stopping rule next to the real one.

There is **no** separate autonomous mode and **no** checkpoint config: one commit
per passed task, always, on the current branch. (If the user wants a feature
branch, create it once up front — that's their call, not a knob here.) Running
through is a caller's instruction, not a mode — every step above is identical either
way, which is why a loop and a hand-driven build can't drift apart.

## Halting

Every build run ends in exactly **one** of five terminal states. Name it out loud in
the closing report — **per lane**, when there is more than one; a lane's halt never
rounds up into another lane's `done`, and the run's own state is the worst of them:

| State | Cause |
|---|---|
| `done` | every task `[x]` and the final smoke passed |
| `blocked` | the executor reported a `BLOCKER`, or the reviewer found a missing `visual:` command |
| `exhausted` | convergence (step 4) spent its 2 fix attempts and the task is still red |
| `paused` | a task's `pause:` point was reached |
| `fork` | a genuine either/or the plan doesn't pin down |

**Never round up.** Only `done` may report the work as built. A run that ended at
`blocked`, `exhausted`, `paused`, or `fork` has unbuilt work in it, and a closing
report that calls it done is worse than the halt itself — it converts a stop the user
could act on into a false completion they won't check. Say the state, then say what's
left and which command resolves it.

**`blocked` and `exhausted`** log **one** entry with `type: blocker` — an upstream doc
must change; see **Blockers** below. **`fork`** logs `type: halt`:

```
## D-NNN — <short title>
- type: halt
- state: fork
- date: <ISO date>
- affects: REQ-N, T<n>
- note: <the options, your recommendation, and what the user has to decide>
```

Write one entry per halt, not one per cause — a blocker that halts the loop is a single
event and reads as two records if you log it twice.

**`done` and `paused` log nothing.** A green tree with every box ticked already says
`done`. And a `paused` run stopped at a task whose `pause:` line is sitting right there
in `tasks.md` saying why — an entry would restate what the file already tells you, and
the log is for things that aren't otherwise visible. Report it, don't record it.

The task the loop stopped at stays `[ ]`. That is the whole resume mechanism:
re-running `tiny-spec-build` (or `tiny-spec-run`) picks up at exactly that task with no
conversational context needed.

## Blockers (never hack around)

When the executor reports `BLOCKER`, when the reviewer reports a **missing `visual:`
command** for a `design:` task, or when convergence (step 4) exhausts its attempts:

1. Leave the task `[ ]`.
2. Log it to `.spec/<active>/decisions.md` using the fixed skeleton (`type: blocker`,
   the terminal state in `state:`, the upstream doc to fix named in `note:`, and the
   affected `REQ-N`/`T<n>` in `affects:`). Create the file if absent:

   ```
   ## D-NNN — <short title>
   - type: blocker
   - state: blocked
   - date: <ISO date>
   - affects: REQ-N, T<n>
   - note: <what stopped you; which upstream doc must change>
   ```

   `state:` is `blocked` for an executor blocker or a missing `visual:`, and
   `exhausted` when convergence ran out. Both route upstream the same way; the
   distinction is what the user reads to know whether the task was impossible or just
   never converged.
3. **Surface and route upstream:** `tiny-spec-plan` (a design gap, a missing `visual:`
   command, or a token the Design system doesn't define) or `tiny-spec-create` (a
   requirement is wrong/impossible), in update mode. Never resolve a missing `visual:`
   command by dropping the task's `design:` field — that turns a gap in the gate into
   a task that quietly claims a check it never got. After the upstream fix and a
   `tiny-spec-plan` reconcile, re-run `tiny-spec-build` — it resumes from the checkbox state.

A genuine fork the plan doesn't pin down → don't guess: present the options + your
recommendation, get the user's call, record it in `decisions.md`, then continue.

**When running through, a fork is a halt** (`type: halt`, `state: fork`) — record the
options and your recommendation in the `note:` and stop, rather than blocking on an
answer nobody may be present to give. Ask only when the user is in the conversation
with you; a question asked into an empty room stalls the run without ending it, and
the halt is what makes the same fork readable hours later.

## Completion

When every task in `tasks.md` is `[x]`:

1. **Final smoke** — run the constitution's **Verification commands** once more
   against the whole project, exercised the way a user would (after the documented
   setup — install/build, not a test-runner shortcut). There is no separate
   verify skill — this final smoke confirms the requirements actually work end-to-end, not
   just that tasks are ticked.

   **This is the authoritative clean-state run for the whole build**, and it is never
   scoped or skipped. Per-task gates run at the scope step 3 named; this one always runs
   everything from clean. A build or install regression that a `scoped` task gate did not
   re-prove surfaces here — which is why a red final smoke is a gap to fix or route
   upstream, never a pass and never `done`.
2. **Report** — the terminal state by name (`done` here, since every task is `[x]`
   and the smoke passed), what was built, the commits made (with the branch), and any
   open `decisions.md` items (blockers, tasks unchecked by a reconcile). If the final
   smoke reveals a gap, it's a bug to fix now (new/edited task) or a blocker to
   route upstream — **not** a pass, and not `done`.
3. **Close the ticket (reference-only).** If a ticket is bound, this is where the
   **closing** keyword belongs — on the final / PR commit, not the per-task ones:
   GitHub `Closes #123`, ADO `Fixes AB#123`. Jira/Monday have no closing keyword,
   so move the ticket's status manually (active auto-transitions are an opt-in layer
   — see [INTEGRATIONS.md](INTEGRATIONS.md)). The suite makes **no API calls**.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.