Product turns your features, architectural decisions, and test criteria into a queryable knowledge graph that Claude can navigate and update directly. The MCP server exposes the full CLI surface: feature lookup, context bundling, drift detection, gap analysis, and atomic writes via the request API. Instead of pasting six files into a chat to explain what changed three weeks ago, you let the agent call product_context with a feature ID and pull the exact ADRs and tests that govern it. The author commands go further, spawning Claude with read access to your entire graph so it can propose new features grounded in existing decisions rather than inventing from scratch. If your project has more than one decision worth remembering and you're tired of manually assembling context for every coding session, this is the missing layer between your markdown files and your agent.
A knowledge graph for LLM-driven development.
You give Claude (or Cursor, or Codex) too much code and not enough decisions, and it builds the wrong thing. Product fixes the context problem at the root: it manages your features, architectural decisions, and test criteria as a structured graph of markdown files, then assembles the exact context bundle an agent needs — feature plus the ADRs that govern it plus the tests that validate it — in one command.
┌──────────────────┐
│ Feature │
│ FT-007 │
└────────┬─────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
ADR-012 ADR-019 TC-031, TC-032
(governs) (governs) (validates)
$ product context FT-007 --depth 2
→ markdown bundle ready to paste into Claude
No database. No service. Just markdown with YAML front-matter, a single Rust binary, and an MCP server so agents can drive the graph themselves.
product drift check catches it; product gap check finds the spec holes.product context FT-XXX gives you the right six, and only those.product mcp exposes the whole tool surface to Claude Code, claude.ai mobile, or any MCP client.If your project has more than one decision worth remembering and more than one feature in flight, this is for you.
Pick whichever fits your environment. None require a Rust toolchain except option 2.
1. Prebuilt binary (recommended) — works on macOS, Linux, Windows:
# macOS / Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Hafeok/product-cli/releases/latest/download/product-installer.sh | sh
# Windows (PowerShell)
irm https://github.com/Hafeok/product-cli/releases/latest/download/product-installer.ps1 | iex
The script drops product into ~/.cargo/bin (or %CARGO_HOME%\bin on Windows). Add that to your PATH if it isn't already.
2. From source (if you have Rust):
cargo install --git https://github.com/Hafeok/product-cli
3. Via Dagger — for hermetic CI use, no install on the runner:
dagger -m github.com/Hafeok/product-cli call binary export --path ./product
Verify any of the above with:
product --version # → product 0.1.0
Product is published to the official Model Context Protocol registry under the namespace io.github.Hafeok/product-cli (per ADR-020 / FT-065). Any MCP-capable client can discover and install it through standard registry tooling — no clone, no cargo install.
Claude Code:
claude mcp install io.github.Hafeok/product-cli
The CLI downloads the matching GitHub Release binary, places it on $PATH, and writes a .mcp.json entry that spawns product mcp over stdio in your repo. The server then discovers .product/config.toml (or the legacy fallback chain — see ADR-048) from the working directory.
Generic stdio .mcp.json — paste into the mcpServers block of any MCP client that consumes the standard configuration shape:
{
"mcpServers": {
"product": {
"command": "product",
"args": ["mcp"],
"cwd": "${workspaceFolder}"
}
}
}
Generic HTTP .mcp.json — for remote agents (claude.ai mobile, Cursor over the network):
{
"mcpServers": {
"product": {
"url": "https://your-tunnel.example.com/mcp",
"headers": { "Authorization": "Bearer $PRODUCT_MCP_TOKEN" }
}
}
}
Architecture not in the release matrix? Fall back to cargo install --git https://github.com/Hafeok/product-cli and configure the same .mcp.json entry — the binary is identical to the one the registry serves.
The on-disk manifest the registry consumes is committed at server.json; a CI smoke test (TC-776) keeps its version in lockstep with product.toml on every push.
# 1. scaffold a project (anywhere)
mkdir my-app && cd my-app
product init -y --name my-app \
--domain api="HTTP surface" \
--domain storage="Persistence"
# 2. create a feature + its ADR + a test, all linked, in one atomic write
cat > /tmp/req.yaml <<'EOF'
type: create
reason: "Rate limit the public API"
artifacts:
- type: feature
ref: ft-rate-limit
title: Rate Limiting
phase: 1
domains: [api]
adrs: [ref:adr-token-bucket]
tests: [ref:tc-100rps]
- type: adr
ref: adr-token-bucket
title: Token bucket for rate limiting
domains: [api]
scope: domain
- type: tc
ref: tc-100rps
title: Enforced at 100 req/s
tc-type: scenario
EOF
product request apply /tmp/req.yaml
# 3. ask the graph what you'd hand to an LLM to implement this
product context FT-001 --depth 2
Step 3 prints a single self-contained markdown document with the feature, the ADR that governs it, and the test that validates it — sized for an LLM context window, deterministic, and free of unrelated noise. That bundle is the entire point of the tool.
Don't want to write the YAML yourself? Skip to Author with Claude below —
product author featuredoes the same thing through a guided conversation.
Everything above tracks work as features, ADRs, and tests. Product also hosts a deeper model — the framework graph — for specifying the product itself as three linked layers: the What (its domain and behaviour), the How (how that's realised), and Delivery (slices and deliverables built to a verifiable 'done'). Same idea, one level up: separate What from How, agree the What first, and check everything instead of asserting it.
See it in seconds:
product init -y --name bookstore --demo # seed a small, conformant example model
product status # What / How / Delivery counts
product guide # where you are + the exact next command
product domain show Order # inspect a node and its links
product guide is the through-line: at any point, it reads your graph and tells
you the next step. Start here:
Once you have artifacts in the graph, the daily flow is:
product status # what's in flight, what's blocked, what's done
product feature next # next feature to pick up (graph-derived)
product context FT-007 # bundle to hand to your agent
product implement FT-007 # or let Product orchestrate the agent itself
product verify FT-007 # run the linked TCs and update status
product implement runs the full pipeline: gap-checks the spec, assembles the bundle, spawns your configured agent (Claude Code by default), then verifies. product verify executes each TC's configured runner (e.g. cargo test) and writes results back into front-matter.
Writing well-formed features by hand is tedious — you have to remember which ADRs are relevant, link the right tests, pick the right phase, and not duplicate something that already exists. product author feature makes that someone else's problem.
product author feature
What this does:
[author] of product.toml) with a versioned authoring system prompt pre-loaded.product_feature_list, product_graph_central, and product_context on related features — so its proposal is grounded in what already exists, not invented.product request apply so the write is atomic.product graph check and product gap check so you don't end up with broken links or untested features.product author feature # open-ended: "I want to add X"
product author feature --feature FT-007 # extend an existing feature; gates on preflight
product author adr # for a pure decision (no new capability)
product author review # spec gardening — fix orphans, weak metrics, missing TCs
Three useful properties:
product mcp --http is running on your dev box or a server, the same authoring flow runs in any claude.ai conversation that has the Product MCP server configured. Author a feature on the train; verify it on a laptop.If you don't have Claude Code installed, point [author].cli at copilot in product.toml, or stick with the product request flow shown in the quickstart.
Authoring gets you a precise spec. product build closes the loop: it assembles the frozen context for a deliverable (the slice it ships, the decisions to apply, the acceptance it must satisfy) and dispatches it to a worker model, then runs deterministic gates — rust-analyzer + clippy, then each acceptance runner — fixing failures in place until the deliverable is done.
The bet behind it: the more precisely a change is specified, the smaller the model that can implement it. So workers are a catalogue of model tiers bound to roles with an escalation ladder. The build tries the smallest model first and climbs only when a gate fails:
product worker init # scaffold .product/capabilities.yaml + role-bindings.yaml
product worker list # the model catalog + each role's escalation ladder
product build my-deliverable --role coder --dry-run # inspect the frozen context + run plan
product build my-deliverable --role coder --lsp # dispatch + LSP + verify gates
# .product/role-bindings.yaml — coder starts small, escalates on failure
- role_id: coder
default_capability: fast-coder # e.g. a 35B model (tier 1)
escalation_steps:
- { capability: code-writer } # 123B (tier 2)
- { capability: code-writer-heavy } # 397B (tier 3)
Two properties make this trustworthy rather than hopeful:
done is computed, not claimed (ADR-071): every in-scope element must conform, every Decider must be sound, every acceptance runner must pass. A worker can't declare victory.The build can even run test-first: a slice's cells become dependency-ordered work units (ADR-075) where one unit writes the test from the spec and a later unit — given that test read-only — implements against it. A deep-enough spec lets two independent small-model runs converge on the same contract.
See docs/spec-depth-substitution.md for the thesis and the experiment behind it.
docs/
features/ FT-001-*.md ← one feature per file, YAML front-matter declares links
adrs/ ADR-001-*.md ← one decision per file
tests/ TC-001-*.md ← one test criterion per file
deps/ DEP-001-*.md ← external dependencies (libs, services, hardware)
product.toml ← repo config (paths, prefixes, domains)
Every artifact has YAML front-matter declaring its identity and edges. The graph is derived on every invocation — there is no separate index to keep in sync, and git diff shows you exactly what the graph changed.
---
id: FT-007
title: Rate Limiting
phase: 1
status: in-progress
domains: [api, security]
adrs: [ADR-012]
tests: [TC-031, TC-032]
---
For anything that touches more than one field or more than one artifact, use a request — a YAML document describing an atomic, validated mutation:
product request create # opens $EDITOR with a template
product request validate FILE # dry-run, reports every finding in one pass
product request diff FILE # show what would change
product request apply FILE # atomic write; assigns IDs; rewrites refs
product request apply FILE --commit # apply and create a git commit
ref: values inside a request are forward references — Product topo-sorts the artifacts, assigns the real IDs (FT-009, ADR-031, TC-050), rewrites every reference on write, and materialises bidirectional cross-links automatically. A failed apply leaves zero files changed, verified by SHA-256 checksum.
For trivial single-field tweaks the granular commands are fine and shorter to type:
product feature new "User Auth" --phase 1
product feature link FT-001 --adr ADR-001 --test TC-001
product adr status ADR-001 --set accepted
product mcp # stdio MCP server — for Claude Code on the desktop
product mcp --http # HTTP MCP server — for claude.ai, including mobile
product init writes .mcp.json so Claude Code picks up the server automatically. From inside an agent session you can ask things like "show me what FT-007 depends on", "create a feature for X with these two ADRs", or "implement FT-007" and the agent calls Product's tools rather than guessing at your code layout.
product graph check # broken links, dangling refs, status invariants
product gap check # specification holes (features without tests, etc.)
product drift check # spec vs implementation divergence
product preflight FT-007 # domain coverage check before implementing
product impact ADR-012 # what does changing this decision affect?
product conformance check # Two Pillars spec conformance (Level 3 clause set)
Wire them into pre-commit or CI and your specs stop rotting.
Product ships as a Dagger module. If you already use Dagger for CI, this gives you a hermetic, no-install path to running graph checks, assembling context bundles, or shipping the binary into a downstream container — without ever putting product on the runner image.
curl. Dagger pulls the prebuilt binary from the GitHub Release and caches it.--version=v0.1.0 locks the binary; the pipeline gives the same result on a laptop and in CI.dag.Product().Container() returns a container with product on PATH that you can chain into your own pipelines.dagger -m github.com/Hafeok/product-cli functions
| Function | What it does |
|---|---|
binary --version --platform | Returns the product binary as a *File. Default version is latest, default platform is linux/amd64. |
container --version --platform | Debian slim with product on PATH and as the entrypoint. |
validate --source --version | Runs product graph check against a directory containing product.toml. Fails the pipeline on any graph error — perfect CI gate. |
context --source --feature --depth --version | Assembles an LLM context bundle for a feature inside a sandbox; returns the markdown as a string. |
# Drop the binary on disk
dagger -m github.com/Hafeok/product-cli call binary export --path ./product
# Specific version + platform
dagger -m github.com/Hafeok/product-cli call binary \
--version=v0.1.0 --platform=darwin/arm64 \
export --path ./product
# Fail CI if the graph is broken
dagger -m github.com/Hafeok/product-cli call validate --source=.
# Pipe a context bundle into a downstream tool
dagger -m github.com/Hafeok/product-cli call context \
--source=. --feature=FT-007 --depth=2 > bundle.md
- uses: dagger/dagger-for-github@v6
with:
verb: call
module: github.com/Hafeok/product-cli
args: validate --source=.
That's the entire CI gate. No setup-rust, no cargo install, no version drift between local and CI.
If you're iterating on the module itself (in dagger/):
cd dagger && dagger develop # regenerate the SDK after editing main.go
dagger -m . functions # list functions
dagger -m . call binary export ... # test against the latest GitHub Release
| Group | What it covers |
|---|---|
init | Scaffold a new Product repository |
request * | Unified atomic write interface — create / change / validate / apply / diff |
feature * | List, show, navigate, link, update features |
adr * | List, show, link, supersede ADRs |
test * | List, show, run test criteria |
dep * | External dependency artifacts |
context FT-XXX | Assemble an LLM context bundle |
graph * | check / rebuild / query / stats / centrality / autolink |
impact ADR-XXX | Change-impact analysis |
status | Project dashboard |
gap *, drift *, preflight | Specification health |
implement FT-XXX | Full agent-orchestration pipeline |
verify [FT-XXX] | Run TC runners and update status |
deliverable *, slice *, cell *, work-unit * | SPMC build artifacts — slices, deliverables, task cells, work units |
build <deliverable> | Assemble frozen context, dispatch a worker model, run LSP + verify gates |
worker * | Model capability catalog — list, resolve a role to a tier, validate |
author * | Graph-aware authoring sessions |
mcp [--http] | Run as MCP server |
metrics *, cycle-times, forecast | Architectural fitness + delivery analytics |
onboard, migrate | Bring an existing codebase into the graph |
Run product <group> --help for the flags on any of them.
product locates the graph it operates on by, in priority order:
--root <path> — top-level flag, accepted before or after the
subcommand. Highest priority; use for one-off scripting.PRODUCT_ROOT env var — session-level override. Use to scope an
entire shell or container at a single graph. Empty values are ignored..product/ directory or
product.toml.When --root and PRODUCT_ROOT are both set, --root wins.
product --root crates/verify-cli feature list # one-off
PRODUCT_ROOT=/workspace/typo-cli product mcp # whole-session scope
Explicit paths are tilde-expanded, resolved against the current directory
when relative, and canonicalized (symlinks followed). Pointing at the
.product/ directory itself (--root foo/.product) is treated as the
parent. The path must exist, be a directory, and contain a .product/
subdirectory; otherwise the binary exits with code 24 and an
error[E024]: graph root not found diagnostic naming the supplied value
and the source (flag or env).
The MCP server reads the same resolution at startup and is fixed to the resolved root for its lifetime — restart the server to point at a different graph.
Single Rust binary, no runtime deps. The graph is rebuilt in memory from front-matter on every invocation (ADR-003), so it can never drift from the files. Oxigraph powers SPARQL queries (ADR-008). Betweenness centrality ranks ADR importance (ADR-012). All file writes go through atomic write + advisory lock (ADR-015). #![deny(clippy::unwrap_used)] — zero panics on user input.
cargo build
cargo t # full suite (alias for --no-fail-fast)
cargo clippy -- -D warnings -D clippy::unwrap_used
cargo bench
Model your product (the framework graph):
product conformance check evaluatesThis tool's own development (the meta graph):
See LICENSE.
csoai-org/pdf-document-mcp
xt765/mcp-document-converter
io.github.xjtlumedia/markdown-formatter
io.github.ai-aviate/better-notion
suekou/mcp-notion-server
meterlong/mcp-doc