Ten things Productizer actually does
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.
- One living spec per product — one document, not one per feature.
- Permanent requirement ids — never reused, never renumbered.
- A constitution above the requirements —
P-numbered principles, checked first. - Every intent classified against the whole spec — before any work starts.
- A contradiction halts the work — and waits for a human ruling.
- Nine linear stages — with the state of each read from files.
- Declared checks that cannot go hollow — a green result has to have earned it.
- Human gates on deploy and publish — as PreToolUse hooks, not conventions.
- A generated dashboard — read-only by construction.
- A backlog that is just a file — permanent
B-ids, order is priority.
The record — one file, and ids that hold
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.
One living spec per product
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.
Permanent requirement ids
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.
A constitution above the requirements
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.
When something new arrives — it is judged before it is worked on
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.
Every intent classified against the whole spec
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.
A contradiction halts the work
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.
How the work runs — from files, not from memory
Nine linear stages
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.
Plan
You describe the problem in plain language. You get a labelled issue.
Design
Intake, then the spec delta in EARS. You rule on anything that contradicts.
Build
The plan is interrogated before any code is written, then implemented.
Test
It verifies before you look, and pastes what passed.
Check
The declared checks run, and the result says what was actually examined.
Deploy
A draft pull request and review findings. You judge intent and risk, not mechanics.
Document
The user guide, regenerated per release from the active spec.
Announce
A drafted release post and email. You publish it yourself.
Maintain
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.
Declared checks that cannot go hollow
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.
Human gates on deploy and publish
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.
What you can see, and what is waiting
A generated dashboard
Ask to see the pipeline and it generates one, in nine tabs: Dashboard, Board, Stages, Files, Backlog, Releases, Limitations, Visualizer and Useful commands. 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.
A backlog that is just a file
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.
What v4.2.0 added — the machine-checked half
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.
The spec diff is handed to Build, not just the spec
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.
An executable format grammar
references/format-spec.md is the normative grammar and scripts/validate-spec.py enforces it — standard library only, 65 diagnostic codes (53 when it shipped in v4.2.0) 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 42 fixtures and a --strict mode that promotes every WARN to a failure. CI runs the self-test and the validator on every push and PR. 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 used to fail on our own spec. Three requirements — R14, R16 and R21 — each carried two shall clauses, so each stated two obligations under one id and each would have been half-tested. It was backlog item B9, and on 2026-08-29 it split all three; --strict now passes on the repository’s own spec with 0 errors and 0 warnings. What is still short: CI runs the validator without --strict, so nothing on a push holds the spec to the bar it currently clears.
Reverse drift
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.
Coverage denominators derived from the spec, and settings that are a team decision
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.
Evidence and judgment, split
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.
A Productizer-Req: commit trailer
scripts/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.
One command to start, and an import that does not refuse
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.
A measurement suite
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.
Skill graduation
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.
A promotion queue on the dashboard
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.
What it does not do — plainly
The rule on this site is that we never describe a capability the tool does not ship. So, seven things:
- Claude Code only, today. It is a Claude Code plugin. There is no other host, no editor integration and no hosted service.
- The lifecycle has not yet been exercised end to end through CI. CI runs the validator, the solver gates and the declared checks on every push and PR, but no agent runs in CI and nothing opens a PR. The stages have been run; a single automated pass from intent through to release, in CI, has not been demonstrated. It is backlog item B1.
- Runners run by hand, never on a push.
templates/runners/*.jsondefines 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.run-runner.shexecutes one andscore.shscores it, but both run when you run them. That is not a scored merge gate that re-runs on every push; what re-runs on every push is the declared-check suite. - The graded evals show no benefit over the bare model. On a pre-registered 14-case hard corpus, 840 graded runs on 2026-09-26, the halt decision was correct in 235 of 280 runs with the plugin against 239 of 280 bare, Fisher p = 0.725: no effect, and the point estimate favours bare. Eleven of the fourteen cases were at the bare model’s ceiling. The skill fired in 48 of 280 plugin runs, and reference files were read 0 times in 840 runs, so the mechanism was never exercised. One case, H11, reached p = 0.0471 with the plugin worse (5/20 → 0/20); it is one of 14 uncorrected tests, so read it as descriptive. The 26-case corpus scored 16/16 recall and 0.94 precision with the plugin against 13/16 without on 2026-08-29, then tied at 30/42 each, p = 1.0, when re-run over every case the bare model had ever failed. Grading is not run at publish time; it has only been done in one-off runs. The contradiction halt is a design claim, and the graded runs show halt correctness no better than the bare model.
- The halt rarely asks for the ruling. In the graded runs the model halts but seldom asks a person to rule:
06-ruling-requestedscored 0.22 with the plugin against 0.13 bare. A 14-case mechanism corpus is committed and has never been run. - The best accuracy figure we have is narrow, in-sample, and not the system’s. It covers the symbolic cross-check that runs alongside the model classifier: 1.00 precision and 0.94 recall on its 33-case self-test, and 1.00 / 0.90 on the 26-case corpus — 9 caught, 1 missed, 6 escalated as undecided, 0 false halts. It was tuned against that same corpus, so the figure is in-sample, and it is a self-authored set, not a benchmark. So the honest sentence is not “Productizer has 0.90 recall”, which would overstate it considerably, but that one deterministic component of the classification path scores that on cases of our own that it was tuned on.
- It is a free, open-source, one-person project. MIT licensed. Not a supported platform, no SLA, and no roadmap commitment beyond what is in the repository.
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.