| name | specification |
| description | Use when writing or reviewing design specification (design.md), defining interfaces or constraints (SDC/SGDC), or updating from rework feedback; not for RTL implementation or verification. |
Requirements and Specification Freeze
Your sole responsibility: derive a frozen design source of truth from the delivered
<brainstorm>/brainstorm.md. You dispatch and hold no document body: brainstorm.md,
design.md and every <child>.md are read and written only inside sub-Task contexts. The
brainstorm dialogue itself belongs to the pre-pipeline brainstorm skill.
Iron Rule
Write only under {workdir}; never another module's artifacts. Reading templates and upstream inputs outside it is fine.
Artifacts
<skill> is this skill's own base directory, named on the first line of this file.
Read {workdir}/dispatch.json for this round's inputs: its inputs table maps each upstream key to a location, so <key>/<subpath> is how you address one. The only input is <brainstorm>/brainstorm.md (brainstorm is a PIPELINE_INPUT, so <brainstorm> is the module root) — frozen for the run, and read only inside the sub-Tasks that need it.
Everything below is produced under {workdir}. Each JSON sidecar's shape is references/<name>.schema.json.
| Path | What it is |
|---|
design.md | Module overview §1.1–1.6 + §1.7 pointer to the manifest (template: references/design-template.md) |
<child>.md × N | Per-child sub-design (template: references/child-design-template.md) |
manifest.json | The child partition: module + children[] (name / doc / rtl_modules[] / brainstorm_anchor) |
clocks.json, top-io.json, interconnects.json | The boundary: clocks, top-level ports, cut edges |
features.json, check-hints/<child>.json × N | The feature list, and per child the checks that verify it |
ppa.json | PPA targets, verbatim from brainstorm D6 ([] when none) |
constraints/<TOP>.sdc, constraints/<TOP>.sgdc | Generated by derive-constraints |
spec-review/<child>.md × N, spec-review/decisions.md | Wave-3 reviews, and the user's resolution of anything they called blocking |
result.json | The status envelope |
Fan-out
- Dispatch-and-wait: after dispatching, send a brief status and end the turn; a wave's gate begins once every sub-Task in it has reported.
- Sub-Task
STATUS: BLOCKED: a crash, not a fail verdict. Finalize status=fail with a fail_reason listing the failed children, and leave per-child re-dispatch to a repair round.
Workflow
Three waves, each closed by its own gate, then finalize.
Which round is this
Read {workdir}/dispatch.json first. The kernel writes scope / caused_by / reasons only when they carry something, so their presence is what tells you:
caused_by present — failures downstream are waiting on this stage. Each entry is one failing run's own result.json, and a round scheduled for some other reason carries them too: answer them in this round. This stage already shipped, and rtl-design / simulation-plan consumed the manifest, so the partition is not this round's to change. Scope is the union of dispatch.json's scope and what the caused_by envelopes attribute; Read each envelope once. Dispatch one design.md-level rework sub-Task, then pick up at Wave 2's gate and run through to finalize: the wave dispatches are skipped, but the join and the constraints must re-verify against what changed, and Wave 3 re-runs so the promoted review is never stale.
caused_by absent — a first delivery. Run all three waves.
Either way your previous round, if any, is already in {workdir}: edit it in place, touching only what this round requires. Rewriting an artifact this round did not change still changes its bytes, and five stages downstream declare these files as inputs, so a cosmetic rewrite invalidates their proofs and buys a rebuild of everything transitively below for no change in content.
A {workdir} already holding part of a round means the session was compacted or interrupted: that work is yours to continue or redo, and artifacts on disk are not a gate you already passed.
Wave 1 — decompose
Dispatch one Level-1 sub-Task per references/decompose-task-contract.md. In its own context it reads <brainstorm>/brainstorm.md and writes manifest.json, design.md §1.1–1.7, and ppa.json.
Gate, script. Run derive-ports to compute each child's inter-module ports, the interconnects.json wires whose producers or consumers include one of that child's rtl_modules:
python3 <skill>/scripts/spec/__main__.py derive-ports --workdir {workdir}
It also decides the top-partition purity rule, since this is the last moment the partition is still editable. On success the port map is on stdout, and Wave 2 injects it. A non-zero exit names the defect on stderr; every one of them routes a Wave-1 rework sub-Task.
Gate, human. Present an N-child summary from manifest metadata only, Grep manifest.children[].{name,rtl_modules} plus the derive-ports map, and point the user at design.md §1.4 to inspect themselves. They either confirm the partition, or give merge feedback: re-dispatch Wave 1 with the new grouping and re-run this gate.
Wave 2 — child sub-designs (×N)
Dispatch one sub-Task per child, each writing {workdir}/<child>.md per references/child-design-template.md. Inject each child's wire list from Wave 1's gate; the child adds any top-IO ports it drives or reads.
Gate, script. Run check-crossrefs. N children authored their docs and check hints in parallel, so it reports what only a join can see: a name one of them wrote that resolves nowhere, or a target nobody claimed.
python3 <skill>/scripts/spec/__main__.py check-crossrefs --workdir {workdir}
The verdict is structured on stdout and a non-clean one exits non-zero. Fix nothing yourself. Each disagreement names both sides, and which of the two is wrong is a judgment: the child may have mistyped a port, or the boundary may be missing it. Decide that, then route the rework to whoever authored that file, Wave 1 for a sidecar and the affected child for a <child>.md or its check hints.
On a clean gate, immediately derive the constraints:
python3 <skill>/scripts/spec/__main__.py derive-constraints --workdir {workdir}
It generates constraints/<TOP>.{sdc,sgdc} from clocks.json + top-io.json. Running it here, before the human gate, surfaces defects no cross-file join can see (the exactly-one-primary rule, clock-name collisions) while a rework is still cheap. It reads only the Wave-1 sidecars, so a defect it reports on stderr routes to Wave 1.
Wave 3 — semantic review (×N)
Dispatch one Level-1 reviewer per manifest.children[] per references/spec-review-task-contract.md, passing paths. Each writes its own {workdir}/spec-review/<child>.md.
Gate, human. Path-handoff, echoing no body:
- the
design.md (+ per-child) paths and the check-crossrefs verdict;
- one
spec-review/<child>.md path per child;
- the
ppa.json content verbatim — the numeric targets synthesis and power-analysis will gate on, with no other human-visible surface, so the approval has to cover the actual numbers.
What the user is approving is the engineering soundness no script and no reviewer can reach: port roles, reset polarity, clock relationships, and those PPA numbers.
You do not summarize the findings, rank them, or decide which ones matter: a review relayed through your summary is your judgment wearing the reviewer's name.
If the user accepts a finding a reviewer called blocking, write their reason — their words, not yours — to {workdir}/spec-review/decisions.md, so the override travels with the review it overrode instead of living only in this session.
On reject, re-run from wherever their feedback starts: a body change re-enters at Wave 2, a partition change at Wave 1.
Finalize
Every run ends here, an unresolvable failure included:
python3 <skill>/scripts/spec/__main__.py finalize \
--workdir {workdir} --status <pass|fail> [--fail-reason "<one-line reason>"]
You supply only the human-gate outcome; everything else in the envelope is finalize's, including stage_specific.top_module, which it takes from manifest.module. On the pass path it re-runs check-crossrefs and derive-constraints in-process — both were clean at Wave 2, so a failure now means an artifact was edited after that gate, which is BLOCKED rather than a routable fail — and validates the Wave-1-authored {workdir}/ppa.json (missing or invalid is BLOCKED, never a silent default). Exit 0 = result.json written, status pass or fail. A non-zero exit is a program exception: BLOCKED, reason on stderr, never a status=fail.
Return Contract
Control returns to the caller, which decides what runs next from result.json.
Your sole completion signal is {workdir}/result.json present with status=pass.