Turn a product idea into a grounded, buildable SRD suite — a Software Requirements Document whose requirements and decisions rest on real research (competitors, open-source prior art, technology docs, known pitfalls), not the model's memory. A skills.sh agent skill.
construct is the companion to reconstruct
(rebuild a repo into PRDs) and ultradoc
(answer questions grounded in a repo). Same engineering: a single committed,
zero-dependency Node bundle + a thick agent playbook, fully tested, released
by Conventional Commits.
- Interview the user about the product (one question at a time) →
brief.json. - Research the idea across keyless angles → an evidence dossier with
[E#]ids:- market — competitors & positioning (SearXNG → DuckDuckGo → your WebSearch; pages cleaned through a local Firecrawl when its stack is up),
- oss — comparable open-source projects + their issues/PRs (GitHub/GitLab),
- tech — candidate-technology docs + StackOverflow pitfalls,
- semantic (optional) — a local-embedding relevance pass (Qdrant + Ollama).
- Analyze the dossier: name every feature/competitor/tech/seed that would render ungrounded, with the drill command that fixes each gap.
- Render a complete SRD tree (vision, scope, numbered functional requirements
with Given/When/Then, non-functional requirements, system context, an
inferred data model and interfaces, ADRs, competitive landscape, build plan,
traceability) +
SRD.json+ a machine-readableBUILD-PLAN.jsontask DAG. At--level complexit also renders a design system (design/: principles, design tokens, components, screens/flows, an accessibility contract); pass--no-designto skip it. - Check it: a hard structural-completeness gate, an advisory
grounding-coverage report (opt-in
--min-groundingthreshold), and — viareview+check --semantic— an opt-in claim-support gate that fails on any cited evidence that doesn't actually back its claim. - Verify the build (optional): the agent implements the app task-by-task
from
BUILD-PLAN.json(status --jsonlists the buildable task frontier so independent same-milestone tasks can be built in parallel);construct verifyreferees it against the SRD — declared files exist, every requirement is referenced by a test, and (with--run-tests) the declared test commands pass.
No API keys. No npm install at skill-use time.
npx skills add maxgfr/construct
node scripts/construct.mjs init --idea "a self-hosted read-it-later app" --out ./readpile
# optional: diverge first, then fold kept ideas into the brief
node scripts/construct.mjs brainstorm --out ./readpile # scaffold BRAINSTORM.md
node scripts/construct.mjs brainstorm --out ./readpile --merge # kept → brief.json
# …fill ./readpile/brief.json via the interview…
node scripts/construct.mjs research --out ./readpile --angles market,oss,tech
node scripts/construct.mjs analyze --out ./readpile # what's thin? drill it
node scripts/construct.mjs render --out ./readpile --level complex
node scripts/construct.mjs check --out ./readpile # add --min-grounding 70 to enforce
node scripts/construct.mjs review --out ./readpile # adjudicate each cited [E#] → verdicts.json
node scripts/construct.mjs check --out ./readpile --semantic # gate refuted/unsupported claims
node scripts/construct.mjs verify --out ./readpile --app ./readpile-app --run-tests --strict
Add --merge to also emit a single-file SRD.md. Run --help for the full
surface, or see SKILL.md for the agent playbook and
DOCUMENTATION.md for internals.
00-overview/ VISION.md · SCOPE.md
requirements/ FUNCTIONAL.md · NON-FUNCTIONAL.md
architecture/ SYSTEM-CONTEXT.md · DATA-MODEL.md · INTERFACES.md · decisions/NNNN-*.md
design/ PRINCIPLES.md · DESIGN-TOKENS.md (+ design-tokens.json) · COMPONENTS.md · SCREENS.md · ACCESSIBILITY.md (complex; --no-design to skip)
competitive/ LANDSCAPE.md
BUILD-PLAN.md · BUILD-PLAN.json (task DAG for the build phase) · TRACEABILITY.md
evidence/ EVIDENCE.md · evidence.json · meta.json · brief.json · SRD.json
VERIFY.md · VERIFY.todo.json · VERIFY.json (claim-support review, from `review`)
construct check separates the two axes. The structural gate fails the build
on an incomplete SRD (unresolved 🧠 decisions, an FR with no acceptance
criteria, a dangling reference, a missing required NFR category, a malformed
ADR). The grounding coverage is a report — it tells you how well-cited the
SRD is so you can invest research where it matters, but it never fails the build
by default. When you do want it enforced, --min-grounding <0-100> opts into
a second gate that fails below the threshold.
Coverage counts citations; it does not check they hold. construct review
builds a claim↔evidence worklist (one pair per cited [E#]); an agent (or a
fan-out of skeptic subagents — see references/orchestration.md) adjudicates
each as supported | partial | refuted | unsupported, and check --semantic
turns that into a third opt-in gate that fails on a refuted or unsupported
claim.
The skill shells out to the CLI and parses its output. An MCP server skips both: your agent calls construct as typed tools, with JSON schemas in and structured results out. Same engine, same page cache, no wrapper.
# stdio — the default, and what Claude Code / Claude Desktop / Cursor expect
claude mcp add construct -- node /abs/path/to/scripts/construct.mjs mcp
# or over HTTP, on loopback
node scripts/construct.mjs mcp --transport http --port 7342
claude mcp add --transport http construct http://127.0.0.1:7342/mcpIt serves all three MCP primitives, because a skill is three things: the engine
(tools), the method (prompts), and the documentation the method refers
to (resources). Here that matters more than usual: check gates structure
and grounding is advisory, so a client given only the tools produces a
well-shaped SRD full of decisions nobody researched — and every gate goes green.
| Tool | What it does |
|---|---|
construct_status |
What exists in the run, and the exact next command |
construct_research |
The only command that grounds anything — market, OSS, tech → a dossier |
construct_research_angle |
Probe ONE angle (web/oss/tech/so), persist nothing |
construct_analyze |
What is thin, and the command that fills each gap |
construct_render |
The SRD suite: requirements, ACs, NFRs, ADRs, build plan, traceability |
construct_check |
The structural gate (grounding coverage is advisory) |
construct_review |
Claim↔evidence worklist — where advisory grounding becomes real |
construct_verify |
Referee a built app against its SRD |
construct_cache |
What the page cache holds |
construct_read |
A file, or a line range, from the run |
--allow-write additionally exposes construct_init, which scaffolds a run
folder on disk. Pass --out <run> at startup to dedicate the server to one SRD
— run then becomes optional on every tool.
| Prompt | Arguments | What it drives |
|---|---|---|
interview_idea |
idea |
The questions whose answers change what gets built — one at a time, following the surprising answer |
enrich_srd |
run |
evidence → testable requirements → check → review |
judge_adr |
run, decision? |
Ground a technology choice in its docs, its open issues, and what people hit in production |
Each states the thing the gates cannot: the rigor is yours. A green
construct_check means the SRD is well-formed, not well-researched.
SKILL.md and all 19 references/*.md are served under skill://, read off
disk at request time — so a documentation fix reaches every client without a
rebuild.
Two things worth knowing:
construct_researchis the slow one — a network fan-out across angles, minutes on a first run. Re-runs are nearly free thanks to the page cache.- The HTTP transport binds
127.0.0.1and refuses anything else unless you pass--allow-remote. This server fetches arbitrary URLs and reads local files; an exposed port is a fetch-anything primitive for whoever finds it.
node scripts/construct.mjs semantic up # Qdrant + Ollama + SearXNG, fully local, no key
node scripts/construct.mjs firecrawl up # Firecrawl, fully local, no key (~3 GB, own profile)
The first adds embedding-based re-ranking (--semantic) and keyless web
discovery. The second swaps the built-in regex HTML stripper for browser-based
main-content extraction on every page fetch — the only way a JS-rendered page
yields evidence at all. Both are optional and degrade quietly: when a stack is
down, research runs exactly as it did before and says so in the dossier notes.
See references/semantic-setup.md.
MIT © maxgfr
A .pdf URL or an application/pdf response goes through an extractor
ladder (src/research/pdf/): npx @firecrawl/pdf-inspector@1 → npx @firecrawl/anydoc@0.1 (the PDF on
stdin, in a child process) → the self-hosted Firecrawl → pdftotext → a
built-in dependency-free reader — stopping at the first rung whose output passes
a quality gate, and REFUSING rather than quoting a PDF none of them could read.
Office documents — .docx/.doc/.odt/.rtf, .pptx/.ppt/.odp,
.xlsx/.xls/.ods, .epub, .csv — go through their own two-rung ladder
(src/research/doc/): npx @firecrawl/anydoc@0.1 (the bytes on stdin, converted to
GitHub-Flavored Markdown) → the self-hosted Firecrawl. Same gate, same refusal.
The refusal is the point: these are ZIP and OLE containers, so the fall-through
this replaced did not degrade the evidence, it fabricated it — a .docx was
quoted into requirements as if it were prose, as kilobytes of replacement characters, silently. anydoc needs
Node 20+, so an unavailable converter is a normal outcome rather than a
misconfiguration; CONSTRUCT_DOC_ENGINE=none disables the ladder.
Scanned PDFs — no text layer at all, so every rung above fails — are
rescued by a final OCR rung: copyable-pdf
tesseract, when both are installed. It is last because it is the only expensive one (~2.7s per page at 300 DPI) and is budgeted per process (CONSTRUCT_OCR_MAX, default 3,0disables). Both binaries are checked before the tool is spawned: asked for a missing tesseract, copyable-pdf offers to runbrew install/sudo apt-get install -yand waits on stdin, and a research run must never install a system package as a side effect.
Without it a PDF body was returned verbatim: its bytes decoded as UTF-8, cached, and quoted into requirements as if it were prose. The gate rejects text laced with C0/C1 control bytes or U+FFFD at ANY length — the built-in reader can emit 16 MB of image-stream garbage for a 12 MB paper, which every length-limited check waves through.
CONSTRUCT_NO_NPX=1 drops the npx rung; CONSTRUCT_PDF_ENGINE=<rung> pins one.
The stack is shared with the sibling skills (ultrasearch, construct, ultradoc): one compose project, one set of containers, one set of volumes. They used to define three separate projects on the same host ports, so only one could be up at a time — starting a second failed on the port after leaving its sidecars running. Bringing it up from any of them now targets the same containers, so the second is a no-op and the RAM is paid once.
Upgrading from a version with per-skill container names? Remove the old ones once — this file can no longer stop them, and they still hold the ports:
docker rm -f $(docker ps -aq --filter name='^(ultrasearch|construct|ultradoc)-')