Ten features at the core, plus what v4.2.0 added on top of them, and nothing beyond that. Each one is stated with the failure it prevents, because “requirement ids are permanent” is a fact and nobody should care about a fact — what matters is that a requirement cannot be quietly renumbered out from under a decision that cited it. At the end of the page is the part most feature lists leave out: what Productizer has not yet proven about itself.
P-numbered principles, checked first.B-ids, order is priority.Claude Code is already very good at the work. What no agent does on its own is remember what you agreed. These three are the memory.
A single document for the whole product at .claude/productizer/spec.md — not a spec per feature, and not a spec per repository. A product may span several repos; exactly one of them is the spec home, so there is one id space and one allocator handing out the next number.
What it prevents. Two spec files, each allocating its own R7, and a year later nobody can say which R7 a test or a review finding was talking about.
Requirements are written in EARS syntax, so each one names a trigger and an observable response a test can assert. Ids are never reused and never renumbered. When a requirement is superseded it keeps its original text rather than being deleted or rewritten, so the record of what was once agreed survives alongside what replaced it.
What it prevents. A requirement quietly renumbered out from under a decision that cited it — every citation silently redirects to something else while the test suite stays green. And the other half: a rewritten requirement that erases the fact anyone ever agreed to the old one, which is exactly the thing you go looking for when the behaviour turns out to have been deliberate.
Since v4.2.0 this rule is checked by a program rather than asked for in prose — the format grammar below, which is also honest about where our own spec fails it.
P-numbered principles that sit above the requirements. They are ratified, never scaffolded — nothing writes you a starter set — and they are checked before classification, not after.
What it prevents. A requirement that reads fine on its own quietly contradicting a standing principle, and being agreed because nobody thought to look one level up. Checking after the fact would mean discovering it once the code exists.
You do not write a specification and hand it over. You describe what you want, and it is worked against the spec that already exists.
Before any work starts, the new intent is classified against everything already agreed: extend (nothing covers it), refine (covered, but imprecise), duplicate (already specified — you get the id) or contradict. Against the whole spec, not against the part of it someone remembered.
What it prevents. Building a thing that already exists, and calling a bug a feature request. If the behaviour is specified and does not work, that is a defect — a far more useful thing to know than a new ticket.
When the new intent conflicts with something already agreed, the run stops. It does not merge and get flagged for later. You get both sides and you rule, and the ruling is recorded and never edited afterward.
What it prevents. The flag nobody clears. A conflict noted in a comment on a merged change is a conflict that has already shipped, and the decision that resolved it — if anyone ever makes one — lands somewhere that is not the record. Stopping is the only mechanism that reliably converts a conflict into a decision.
Plan, Design, Build, Test, Check, Deploy, Document, Announce, Maintain. The state of each is read from files, never from memory or from what was said earlier in the session.
You describe the problem in plain language. You get a labelled issue.
Intake, then the spec delta in EARS. You rule on anything that contradicts.
The plan is interrogated before any code is written, then implemented.
It verifies before you look, and pastes what passed.
The declared checks run, and the result says what was actually examined.
A draft pull request and review findings. You judge intent and risk, not mechanics.
The user guide, regenerated per release from the active spec.
A drafted release post and email. You publish it yourself.
You triage what production surfaced, and it goes back to Plan as a new intent.
What it prevents. An agent that believes it is further along than it is. Read the stage from a file and a fresh session, a resumed session and a scheduled run all agree on where the work stands; read it from memory and each one is confident and different.
Which checks matter is yours to declare, once, in a committed checks.yaml. Every check states what it must have examined. A check that exits clean having examined less than it declared comes back hollow, and hollow blocks exactly like a failure.
What it prevents. The green tick that examined one file of forty-eight. Without a coverage assertion a scanner reporting a perfect grade after reading almost nothing is indistinguishable from one that read everything — and it is the more dangerous of the two, because it ends the conversation. A green result has to have earned it.
v4.2.0 closed the remaining half of that hole: the denominator a check is measured against is now derived from the spec rather than declared by the check.
Deploying and publishing are held behind gates implemented as PreToolUse hooks. The agent drafts, a person approves, then the agent runs. Everything the agent needs for its own work is allowed; the irreversible outward step is not.
What it prevents. A rule the agent is only asked to remember is a rule it eventually reasons past — not through malice, but because a plausible-looking reason to proceed always exists. A hook cannot be reasoned past.
Ask to see the pipeline and it generates one: an Overview with the stats and a kanban, plus Stages, Files, Backlog and Releases. It is generated from the repository’s own files, so if the spec is wrong the page shows it wrong. And it is read-only by construction — a view is output, never an input back into the files.
What it prevents. The moment a view becomes editable, there are two records and the committed one stops being the audit trail. Keeping the page strictly downstream is what lets git log -p on the spec remain the whole story.
Permanent B-ids on the same rule as requirement ids. There is no priority field — the order of the file is the ranking. Five statuses, and read-only links out to Jira. It lives in your repo and diffs like code.
What it prevents. Two representations of one ordering — a rank column and a file order — disagree the first time somebody edits one and not the other, and after that nobody can say which is the real queue. Read-only Jira links keep the same discipline across the boundary: one system owns the state.
The ten above were, until this release, largely enforced by prose the agent was asked to follow. v4.2.0 is the release that moved the load-bearing ones into programs. Each is a script in the plugin, and each is named here so you can go and read it.
scripts/spec-diff.sh fences the diff of the spec and the constitution between a base ref and HEAD into the Build prompt, with the instruction to reconcile the code with the change rather than with the current text.
What it prevents. An invisible removal. Because a superseded requirement keeps its original sentence, a run handed only the current spec reads a document the existing code already satisfies, finds nothing to do, and the dropped behaviour survives. The current spec cannot tell you it changed; the diff is the only place that says so. Nothing here is rendered as an empty success either — “the spec did not change”, “the file is new and has no baseline” and “the base ref does not resolve” are three different answers with three different exit codes, and a diff over the size cap is left out whole rather than truncated, because a cut-off diff reads as a complete one.
references/format-spec.md is the normative grammar and scripts/validate-spec.py enforces it — standard library only, 53 diagnostic codes across two severities, ERROR for a document that cannot be parsed or a permanence invariant that is broken, WARN for one that parses and holds its ids but violates the contract in a way something later half-tests. There is a --self-test over twenty fixtures and a --strict mode that promotes every WARN to a failure. EARS syntax and id permanence are now machine-enforced rather than asked for.
What it prevents. Prose losing to a hurried agent at 2 a.m. A rule nobody can run is a rule that degrades quietly.
And the part that belongs beside it. --strict fails on our own spec. Three requirements — R14, R16 and R21 — each carry two shall clauses, so each states two obligations under one id and each will be half-tested. It is written down as backlog item B9 and it is not fixed. A grammar whose author cannot pass it is worth less than one whose author can, and saying so is cheaper than the alternative.
scripts/drift-reverse.sh walks the code and reports behaviour no current requirement asks for: a requirement id cited in code that the spec now marks superseded or withdrawn, an id cited that the spec never contained, a test whose vocabulary appears nowhere in the spec.
What it prevents. The gap the permanence invariant creates. The Check stage asks whether every requirement is implemented; nothing asked whether every implementation still has a requirement. Code justified by an agreement that has since been replaced keeps running, its tests keep passing, and nothing fails. It is worth being exact about what this is: it gathers signals with file:line anchors and labels them candidates. It concludes nothing, opens nothing and writes nothing into the spec. A shell script cannot read code, and this one does not pretend to — what it can do is find the places worth reading, and refuse to call the rest clean.
Two rules in scripts/run-checks.sh. First: the coverage denominator comes from the spec, not from the check. Every active requirement gets a row, and a row nothing covers is Missing, which refuses. Second: anything deciding what is examined or whether the run blocks is honoured only from the committed config — a local override is ignored with a warning naming it, and a configuration in which every check is enabled: false is a load error, not a quiet clean run.
What it prevents. A check shrinking its own scope and passing. If a check chooses what it is measured against, its coverage assertion is circular and the hollow rule has nothing to bite on. And a policy that changes what gets examined is a decision about everyone who later reads the result, so one person’s untracked file does not get to make it.
scripts/signals.sh collects typed, objective records of what was observed — and records a named absence when there is no pull request, no CI, no review, or no gh at all, because those are four different states of the world. scripts/score.sh computes judgment on top of them and keys it to a hash of the exact signal set, so a cached verdict whose hash no longer matches is refused rather than adjusted. Missing anchors set a ceiling on the score rather than deducting from it: no pull request or no CI caps it at 60, no review of any kind at 70.
What it prevents. A verdict outliving its evidence, and absence reading as a clean bill of health. The null-never-zero rule is enforced here by the wire format rather than by convention: the number never appears at the top level of the document, it exists only nested inside an object that also carries the signals hash and the signal count, and the emitter refuses to write that object when the count is zero. There is nowhere to put a 0 without simultaneously asserting the evidence behind it.
Productizer-Req: commit trailerscripts/req-trailer.sh writes one git trailer into the commit message, so “which commits built R14” is answerable with git log --grep on a machine that has never heard of this tooling. It survives clone, fetch, rebase, cherry-pick and mirroring, because it is part of the commit. The other half is a coverage id, COV_ followed by the requirement id and a slug, which quotes the requirement id verbatim so it cannot be renumbered independently and stops resolving the moment the requirement it names does. Orphans are detectable in both directions: a requirement no commit claims, and a coverage id pointing at a requirement that is gone.
What it prevents. Provenance that needs a database. There is nothing to migrate and nothing that rots when the tool is uninstalled. What it is not: evidence. Nothing checks that a commit citing R14 has anything to do with R14, and the coverage scan is textual — it cannot tell a test from a comment, so a document that merely mentions a coverage id registers as coverage. These are pointers for a reader, in the same spirit as reverse drift. The repository’s KNOWN_LIMITATIONS.md reproduces each of these failures rather than describing them.
scripts/init.sh is the whole of Stage 0: detect, verify the spec path can actually be committed before writing anything, scaffold, survey, and report what a human still has to do. It never overwrites, never prompts, and prints no timestamps, so two runs on an unchanged repo are byte-identical. Where the repo will not say enough, a five-question interview is the fallback rather than a guess. The import survey reads what code states to a machine — CLI surface, public API, config keys, CI job and step names, routes, test names, error paths and the skill and script inventory — and reports its evidence in two tiers, forking to the weaker one rather than refusing outright.
What it prevents. A scaffold that reports success while the spec stays untracked, because .claude/ is routinely gitignored — an audit trail that looks present and is not is worse than none. And an import that aborts on a missing signal is an import nobody can run on the repo that most needs it. Two honest edges: the survey does refuse when neither tier reaches its floor, which is when the interview takes over; and every requirement either path produces is marked inferred, unconfirmed, carrying the file or test it was read from. An answer typed by a human is a statement about a system, not an agreement about it.
Three instruments, each named for what it actually measures. retrieval-budget.sh is a deterministic retrieval regression: fixed prompts, each naming the one requirement it must reach, and a count of how much of the spec has to be read to get there — in characters, called a proxy for a token budget everywhere it appears, because a real token count needs a model call. ab-harness.sh runs one task and one model through two arms, with the process and without. stage-snapshot.sh locks the draft a stage produced, read-only and un-overwritable, before a human opens it, and reports the human delta separately.
What it prevents. Measuring the writer-plus-agent workflow and reporting it as the agent’s output — that contamination runs one way and always flatters. And a benchmark that calls n=1 meaningful: below five complete runs in the smaller arm the A/B comparison is labelled DIRECTIONAL and refuses the conclusion. It still prints the delta, because hiding it would be its own dishonesty, and even above the threshold it reports a difference between two medians and never claims to be a significance test. ab-harness.sh is a scaffold by design: it does not know how to invoke a model and does not try, so what it contributes is the discipline around the comparison rather than the comparison itself.
scripts/graduate.sh harvests the corrections a human gave the agent, clusters them, and walks them up a ladder from prose to skill to hook to CI gate. Counting is by distinct conversation, not by message, and the threshold is three: ten repeats in one session is one lesson badly received, three across three sessions is a rule. Applying a cluster is precondition-checked and all-or-nothing, and the undo is an exact match.
What it prevents. The cheapest signal a repo produces being thrown away fastest, in a transcript nobody reads again. The human decides: there is no auto-apply flag, defaulted on or otherwise, and present writes nothing outside its own working directory.
The generated dashboard now carries an inferred requirements queue — how many sentences an import drafted that no person has yet ratified. It has four distinct renderings, and the difference between them is the feature: there is no spec to read markers out of; the spec exists and could not be read; the spec was read in full and nothing in it came from an import; and an import ran and every sentence has since been ruled on.
What it prevents. An unread import looking like finished work. “There is no spec” and “every one has been promoted” are both quiet screens, and drawing them the same way tells the reader the wrong thing about the one that matters. The tile also says what the number does not mean: nothing in the queue has been judged true, a citation says where a sentence was read from and not that it is right, and none of these ids can trigger the contradiction halt until somebody promotes them — so a large number here is a spec that is not yet defending anything.
The rule on this site is that we never describe a capability the tool does not ship. So, six things:
templates/runners/*.json defines the contract for a scored judgment — the agent, a timeout, a sandbox and permission scope, a prompt with anchored bands, and an output contract that reads the verdict off the last JSON line so surrounding prose cannot change the number. Three ship. Nothing in the repository runs one. A format that ships without an executor is a format, not a gate, and it is not a scored merge gate that re-runs on every push — score.sh runs when you run it.claude plugin eval is early-access gated and no case has been scored by a model. End-to-end recall stays unmeasured. It is backlog item B10.If any of that is a blocker for you, it should be. It is written here so the decision is yours to make before you install it rather than after.