v0.23.0
The product→architect handoff becomes a contract the code enforces rather than prose it describes. All three plugins (product, architect, scalardb) move to 0.23.0 together.
Added
- The ID prefix namespace is declared, not just described. Each phase in both
skill-dependencies.yamlfiles now carriesid_prefix, making the manifests the registry
of which skill mints which ID prefix. The prefixes previously existed only in each SKILL.md's
prose, where nothing could detect a collision or an omission — and three skills turned out to
declare none at all.tools/lib/pipeline_status_data.test.pynow asserts that every skill
which appends towork/traceability.jsondeclares a prefix, that its SKILL.md actually uses
it, and that no two skills in one manifest claim the same one (NFR-is the single
deliberate cross-manifest claim — the §1.5 carry-over, asserted as such). - Registry phase entries name their pipeline. Every entry in
work/pipeline-progress.jsonnow carries"plugin": "product" | "architect", written by
init-outputand by each orchestrator on itsin_progressstamp. One registry serves both
pipelines and keys phases by bare name, so for the four names both manifests define this
field is the only thing that says whose entry it is.tools/nexus-status.shreads it to
settle the question outright — an entry labelled for the other pipeline is not this phase's
status, whatever it says — and falls back to output corroboration where the field is absent.
Fixed
- A ScalarDB-free project no longer sits at 3/4 outputs forever. The architect manifest listed
scalardb-applicability.mdamongdefine-requirements' unconditional outputs, but the skill
writes it only when ScalarDB is in play — so on a ScalarDB-free project the output bar could
never fill and the phase read as unfinished rather than as one with nothing left to write. The
manifest gainedconditional_outputs("<condition>:<path>"), and the dashboard counts such an
output only when the project's options satisfy its condition. - The validation gate now says whose it is. The gate is the product pipeline's, and it is
deliberately surfaced on the architect tab as well — requirements resting on an unvalidated
premise is exactly what an architect wants to know — but an unlabelledgate: no-goprinted over
the architect tree read as architect's own verdict. It now renders asProduct gate: no-go
everywhere except the product view. - Handoff detection matches files, not directories.
/product:init-outputcreates
reports/01_ux/domain-stories/andreports/02_spec/ui-mocks/empty, so a directory-existence
test reported a product handoff on any initialized product project, whether or not a phase had
ever run. Corrected in/architect:start,/architect:pipeline,define-requirements,
AGENTS.mdandOMNIGENT.md;define-requirementsadditionally states which product artifacts
it found and which were absent, since a partial product run changes what can be carried over. AGENTS.mdandOMNIGENT.mdnow document the handoff they were driving. The repo runs the
same skills under three orchestrators and requires their entry docs to stay in sync, but only
CLAUDE.mdmentioned the product→architect handoff at all — Codex and the omnigent loader were
given no detection rule, no artifact mapping, and crucially no statement that
pipeline-progress.json,traceability.jsonandcontext.mdare shared by both pipelines and
must be written additively. Both now carry the detection glob, a pointer to thedocs/design.md
§1 contract, the per-file additive rules including thepluginstamp and the four ambiguous
phase names, andadapt-change's report-and-stop boundary.CLAUDE.mdgained the same
shared-state paragraph.- Three skills wrote nodes nothing downstream could cite.
research-landscape,
generate-ui-mockandgenerate-frontendall appended to the trace graph without saying
under which ID prefix, which broke two chains for real:/product:adapt-change --type=market
seeds its blast radius from market-landscape nodes that had no ID to seed from, and the
journey → story → screen → feature chain had no screen ID to run through. They now mint
MKT-,SCR-andPG-respectively, anddefine-featurescites theSCR-eachFEAT-
comes from — so aFR-derived downstream traces all the way back. A generated React
component creates no node of its own: it is the implementation of a design-systemCMP-and
is recorded on that node instead of being duplicated under a second ID. - Token cost is no longer merged across the pipeline boundary.
work/token-usage.jsonwas
keyed by bare phase name like the registry, so the product and architect spend on
map-domains(ordesign-api/create-domain-story/report) accumulated in one bucket
that neither view could claim.hooks/record_token_usage.pynow records those four under
<plugin>:<phase>, taken from the registry entry'spluginfield; the dashboard charges a
bucket only to its own pipeline, leaves the neighbour's to its own tab, and reports a legacy
un-namespaced bucket as unassigned rather than to whichever tab happens to be open. Every
other phase name is recorded bare, as before. /product:adapt-changestops at the architect boundary instead of leaving it undefined.
After a handoff the trace graph holds architect's nodes, so the blast-radius closure reaches
them by design — but the skill said nothing about what to do with them. It now splits the
confirmed impact set by node ownership, re-runs only the product side, and writes an
## Architect-Side Impactsection naming each affectedFR-/NFR-, the skill that owns
it and the command to act on it. It never rewrites an architect artifact: a product-side
change is grounds to revise the product spec, not authority to rewrite requirements that
backlog items and shipped code depend on.docs/design.md§7.5 is the new contract, and
§7.2 no longer implies the re-run crosses over./architect:pipelinedetects a product handoff over the same reportsdefine-requirements
reads — the same glob mismatch already fixed in/architect:start.init-outputno longer discards the other pipeline's state. Both/architect:init-output
and/product:init-outputare now explicitly additive: they merge into an existing
work/pipeline-progress.jsoninstead of re-registering every phase aspending, keep the
optionsalready set (notably theoutput_languagethe user chose), and create
work/context.md/work/traceability.jsononly when absent. On the product→architect
handoff/architect:startrunsinit-outputimmediately before
/architect:define-requirements— andinit-outputused to creatework/context.md"as an
empty file", erasing the product-side Open Questions table thatdefine-requirementsreads
in its very next step./product:init-outputlikewise no longer truncates
work/traceability.json, which is the single cross-plugin trace graph architect appends its
FR-/NFR-nodes to (docs/design.md§1.5).- A phase name both pipelines define is no longer read as done on one pipeline's word.
map-domains,design-api,create-domain-storyandreportare defined by both
manifests, and the progress registry keys phases by bare name — so a product phase
recordedcompletedrendered as the architect phase being complete, and
/architect:pipeline --resume-fromwould have skipped it.tools/nexus-status.shnow
trusts such an entry only when the phase's own declared outputs exist to corroborate it,
and otherwise derives the status from the filesystem and reportsshared-namedrift; a
running phase (in_progress) and a skip the project actually asked for are exempt.
skills/common/progress-registry.mdstates the same rule for the orchestrators — confirm
an ambiguous entry against the outputs on disk before treating it as satisfied — plus the
additive-write rule the shared registry requires.init-outputadditionally records each
such entry inwarnings[]. /architect:startdetects a product handoff over the same reportsdefine-requirements
reads. Detection globbed onlyreports/02_spec|03_domain|04_quality, so a product run
that stopped early (--profile=mvpwrites onlyreports/00_core/) could be announced as
having no product artifacts by the skill about to consume them. The two sets are now
identical.
Changed
- Open Questions are asked, not filed. Every skill that could write
TBDnow runs the
protocol in the newrules/open-questions.md: an unknown it cannot resolve from its own
inputs is put to the user withAskUserQuestion— 2–4 candidate answers the skill derived
from context, each described by what it changes downstream — and only what the user defers,
cannot answer in-session, or was never asked (--auto) becomes aTBD. Previously an
unknown went straight to "record it asTBDin Open Questions", so a question the user
could have answered in one click was instead deferred into a report nobody re-opened. - Anything the options cannot express is answered in free text. Skills never author an
"Other" option (the harness appends one, and that is the free-text path) and never round a
free-text answer to the nearest option — it is recorded verbatim and marked as free text,
with only units/IDs normalized and the normalization echoed back. Inherently free-form
answers are asked as representative bands (p95 < 100 ms/< 500 ms/< 1 s) so the
exact figure arrives through "Other", or in prose when no meaningful bands exist — never
skipped toTBDbecause the answer would not fit a menu. - What stays open now says why. Open Questions entries carry an
OQ-ID, a status
(answered/deferred/unasked/external), the answer, the options offered, an
owner and the downstream impact; aTBDin an artifact carries its question ID
(TBD (OQ-012))./product:reportgroups the header by status so a question nobody was
asked is visibly different from one the user consciously deferred, and/product:review
reports an unaskedTBDas a finding.--autoruns record the question and the options
that would have been offered, so a later pass can answer instead of re-deriving. - Questions carry across phases. Each skill picks up the
deferred/unaskedentries in
its own domain at its read-context step, re-asks them in its first question batch, and
updates them in place under the sameOQ-ID — no duplicates, and nothing already answered
is re-asked./product:init-outputseedswork/context.mdwith the## Open Questions
table; the product→architect handoff carries the IDs into
reports/00_requirements/open-questions.md. - Wired through
CLAUDE.md,AGENTS.mdandOMNIGENT.md: Codex and the omnigent loader have
no harness-appended "Other", so they print an explicit "or type your own answer" line under
the numbered choices and record a non-matching reply as a free-text answer. - The token-cost dashboard now prices its component columns. In the live
/architect:report-token-costdashboard the per-modelin/out/cache-read/
cache-writecolumns hold money ($) instead of token counts — the dashboard is read to
answer "what did this cost", and the token total is already its own column.bswitches
them back to counts and the bottom bar names the current unit, so the toggle is
discoverable rather than documented-only. The static and--mdreport keeps token counts
(there the columns break down the token total the same table carries);--breakdown=still
overrides either default. - A nonzero cost never renders as
$0.0000. Amounts below a hundredth of a cent now show
as<$0.0001(<¥1under--currency=jpy), instead of rounding a real charge down to
something that reads as free — visible now that the dashboard prices per-component columns,
where cheap models land in that range.