Surface assumptions before writing any spec body — and run one
targeted verification check per candidate first. With the
directory scaffolded, stop. The load-bearing rule: one targeted
check per candidate assumption — a repo read, a web lookup, or a
read-only probe script — not a sweep. Then split the result into
what you confirmed and what still needs the user.
Draft candidates covering the three categories below, generated
from this repo's actual context — the template serves multiple
project types, so don't carry assumptions across features:
- Technical — runtime, data model, persistence, deployment
target, transport. Canonical sources: package manifests
(
pyproject.toml, package.json, Cargo.toml, go.mod, etc.),
build / orchestration configs (docker-compose.yml, CI
workflows), and the module the feature touches.
- Product — who this serves and where the feature ends. No
canonical local source; goes straight to Unverified. Don't
fabricate confirmation.
- Process — review cadence, who signs off on Boundaries
(especially the
Never do subsection), how the spec moves Draft
→ Approved. Canonical sources: docs/CHARTER.md,
docs/CONVENTIONS.md, recent docs/specs/<feature>/spec.md for
shape precedent, prior ADRs / RFCs that named the rule.
See the Source of truth table in AGENTS.md for the full repo
map. For assumptions about an external library, standard, service,
or runtime behavior, the right source is a web search (cite
the URL) or a read-only probe script (paste the command and
its output) — e.g. python -c "import x; print(x.__version__)",
a GET on a list endpoint, git --version. Probes must be
side-effect-free against any external service: no writes, no
mutations, no calls that bill or page. If the only way to verify
is to write, the assumption stays Unverified. If web search
isn't available in the harness, mark the assumption Unverified
with (web search unavailable) — never guess a URL.
Emit the result in chat (not into spec.md — the body is
gated below), under this shape:
ASSUMPTIONS I'M MAKING:
## Verified
- <category>: <fact> (<single-line citation: path | URL | command + one-line summary>)
- …
## Unverified
- <category>: <open item or reason it couldn't be settled>
- …
Each Verified bullet stays single-line. If a probe's output is too
long to summarise in one line, paste the full transcript in a
fenced block above the ASSUMPTIONS I'M MAKING: heading and
reference it from the bullet (e.g. (probe #1 above: returned True)).
Example Verified entries:
Technical: runtime is Python 3.12 (pyproject.toml),
Technical: HTTP client is undici 6.x (package.json),
Process: top-level convention changes need an RFC (docs/CONVENTIONS.md §Living-docs).
Three to seven candidate assumptions before verification is the
usual shape; Verified is whatever subset of those candidates passed
the check — no floor, no separate cap. Coverage check is across
the three categories (Technical / Product / Process), not the two
subsections.
Surface the Unverified list and wait for human confirmation or
correction before writing into Objective, Boundaries,
Testing Strategy, or Acceptance Criteria. If Unverified is
empty, surface the Verified list with the highest-stakes item
called out and ask the user to confirm that one specifically — a
vague "looks good" doesn't count when the user may not have read
the list.
Only once Unverified has been signed off (or the highest-stakes
Verified item confirmed, if Unverified was empty):
- Copy the now-confirmed assumption list into the spec's
## Assumptions section as a flat list — one bullet per item,
each citing how it was settled. Verified entries keep their
canonical source (path / URL / probe summary); previously-
Unverified entries cite user confirmation YYYY-MM-DD with
today's date. The chat block was the working surface; the spec
section is the audit trail.
- Write the spec's
Constrained by: header from any Verified
items that name an ADR or RFC the feature must cite. The header
lands before any body section; Verified items don't gate the
Unverified loop but they do gate Constrained by:.
- Stamp the optional
Brief: header only when this spec is
derived from a product brief — i.e. you arrived here from
receive-brief, which decomposes a received brief into specs. Set
it to the brief's slug (docs/product/briefs/<slug>.md); leave it
blank or none for a spec authored directly. It records product
provenance and is distinct from Constrained by: (governance).
A spec without it stays valid — the field is additive.
- Stamp the optional
Discovery: header only when this spec
descended from an upstream discovery artifact (a decision brief /
intent produced by an upstream discovery process — e.g. the
discovery loop's G3 hand-off). Set it to that artifact's stable id;
leave it blank or none otherwise. It is the discovery-side sibling
of Brief: — the spec→discovery up-edge a traceability check walks
— additive, and a spec without it stays valid. (Format only — the
header field; see CONVENTIONS.md § 4 Spec metadata.)