feat(fx-dev): teach spec-writer duvet mode and authority-not-procedure - #21
Conversation
spec-writer now detects `.duvet/` at the repo root and, when present, switches into a mandatory requirements-traceability mode: - Requirement headings take the form `### REQ-NNN: Title`, with IDs allocated globally across `docs/specs/` so every duvet anchor carries a stable unique identifier. - Exactly one self-contained normative sentence per `###` section, so an annotation can quote it byte-for-byte. - New specs must be registered in `.duvet/config.toml` with `format = "markdown"` — its absence extracts zero requirements and exits 0, which is indistinguishable from success. - A `duvet extract` count check verifies the spec before the run ends. - Renaming a requirement heading is called out as a breaking change to every annotation citing it. The skill still does not annotate source — that is outside its documentation-only boundary. Instead it reports every requirement that needs a trace by REQ ID for an implementing skill to establish. Also adds "Specify Authority, Not Procedure": requirements should pin who may act and what must hold, never step order, tool names, or timeouts, which churn without changing any guarantee. When `.duvet/` is absent the skill behaves exactly as before and does not mention duvet at all.
There was a problem hiding this comment.
Pull request overview
Updates fx-dev:spec-writer guidance so newly written specs (a) avoid encoding workflow/procedure as normative requirements and (b) optionally follow this repo’s duvet-based requirements traceability conventions when duvet is adopted (signaled by .duvet/ existing).
Changes:
- Add “Specify Authority, Not Procedure” guidance to reduce spec churn from workflow/process tuning.
- Add “Duvet Mode — Requirements Traceability” rules and wire them into spec-writer phases (detect, authoring constraints, registration, and reporting requirements needing traces).
- Bump
fx-devplugin version and marketplace metadata version per versioning requirements.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
plugins/fx-dev/skills/spec-writer/SKILL.md |
Adds authority-not-procedure guidance and duvet-mode requirements/steps for traceable specs. |
plugins/fx-dev/.claude-plugin/plugin.json |
Bumps fx-dev plugin version to 3.6.0. |
.claude-plugin/marketplace.json |
Bumps marketplace metadata.version to 0.19.0. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Duvet mode told spec-writer to edit .duvet/config.toml, contradicting its own docs-only boundary and falsifying the requirement it is annotated as implementing. Registration, snapshot regeneration and [[source]] coverage are now reported, matching the stance already taken on annotations. Also: - REQ IDs are assigned to newly created requirements only; retrofitting an ID onto an existing heading breaks every annotation citing the old anchor and the remedy lies outside this skill's boundary - confine RFC 2119 keywords to REQ sections, since duvet extracts from every section and a keyword in Design or a scenario becomes an uncitable requirement that fails duvet query forever - report that the snapshot needs regenerating with rm -rf .duvet/requirements - fix the duvet-mode gate to resolve the repo root rather than cwd - invert the MUST-density wording, which read as an argument for more MUSTs - warn that a duvet-mode spec PR has a red check until traces land - add duvet guidance to Phase 4 and a duvet-mode shape to the spec template
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (1)
plugins/fx-dev/skills/spec-writer/SKILL.md:165
- The Duvet guidance says “Exactly one normative statement per
###section”, but the intent appears to be per requirement section. As written, it could be misread as applying to any other###headings (Design, Constraints, etc.), which would be unnecessarily restrictive and potentially confusing.
- **Exactly one normative statement per `###` section** — gives a 1:1 requirement→anchor mapping and makes each requirement individually citable.
…EQ IDs
The duvet-mode rules conflated "`### REQ-NNN:` section" with "requirement
section". These are not the same set. The retrofit ban landed in the same
branch guarantees that pre-existing requirement headings keep their plain
titles and carry no REQ ID, so a spec updated in duvet mode legitimately
contains both styles. Any rule phrased as "keywords only inside
`### REQ-NNN:` sections" therefore forbade RFC 2119 keywords in exactly the
requirement sections the retrofit ban deliberately preserves. In this repo's
own corpus, all 8 keyword-bearing headings in
docs/specs/fx-dev-authority/index.md are plain titles.
A second, local contradiction sat in the structural rules: "exactly one
normative statement per `###` section" could not hold alongside the rule six
lines later requiring Overview/Background/Design/Constraints to contain zero
normative statements.
Three one-line corrections, aligned to the wording already used correctly at
SKILL.md Phase 0 ("RFC 2119 keywords confined to requirement sections"):
- Structural rules: the one-statement rule now scopes to requirement
sections and states explicitly that it imposes no count on other `###`
sections, which must contain none.
- "Keywords belong ONLY in requirement sections": the rule now says
"requirement sections" and defines one as a `###` section whose sole
purpose is to state a single normative requirement, with or without a
REQ ID.
- spec-index-template.md: same correction to the same phrasing.
No structural change, no annotation change, no change to the retrofit ban or
the docs-only reporting stance.
Provenance: found via a suppressed GitHub Copilot review comment plus
follow-on analysis. A suppressed comment creates no review thread, so there
is nowhere on PR #21 for the disposition to live — hence this record.
Verification: `duvet query -c implementation` exits 0 at 16/16 and
`duvet report --ci` exits 0 after `rm -rf .duvet/requirements`.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
plugins/fx-dev/skills/spec-writer/references/spec-index-template.md:135
- In duvet-mode specs, RFC 2119 keywords must not appear outside
###requirement sections, but the template’s## Notesblock currently includes normative keywords (e.g., MUST/SHOULD/MAY), which duvet would extract as uncitable requirements and causeduvet query -c implementationfailures. Consider rewriting## Notesto be keyword-free guidance so the template is safe in duvet mode.
Full rules, including what the skill reports rather than applies: the "Duvet Mode — Requirements Traceability" section of `SKILL.md`.
---
## Notes
plugins/fx-dev/skills/spec-writer/SKILL.md:254
- The duvet-mode detection command can mis-detect and/or print git errors if
git rev-parse --show-toplevelfails (e.g., invoked outside a git repo). Other fx-dev scripts use a safergit rev-parse ... 2>/dev/null || pwdpattern; matching that here makes detection more robust and consistent.
test -d "$(git rev-parse --show-toplevel)/.duvet" && echo "duvet mode: ON" || echo "duvet mode: off"
Copilot review triage — head
|
Three consecutive review rounds each found another instance of the same defect class, because each fix corrected only the lines reported. This is a class sweep instead: grep both files for every restatement of the one-normative-statement rule and narrow all of them, rather than patching the reported lines and waiting for round four. Class A — "per section" not narrowed to requirement sections. The canonical rule at SKILL.md:165 was narrowed to "per requirement section", but its recaps were not. A rule that sets a count for non-requirement sections contradicts the zero-keywords-outside-requirement-sections rule, since those sections must contain zero normative statements. Corrected every occurrence found: - SKILL.md:259 (Phase 0.1 duvet-mode recap) — "one self-contained statement per section" -> "per requirement section" - SKILL.md:370 (Phase 3.2 critical rules) — "one self-contained normative sentence per section" -> "per requirement section" - spec-index-template.md:127 (duvet-mode rules list) — "Exactly one normative sentence per section" -> "per requirement section", plus an explicit note that it sets no count for any other section Verified as NOT instances of the class, deliberately left alone: SKILL.md:444 (already scoped to "requirements you ADD"), SKILL.md:165 (the canonical rule, already correct), and spec-index-template.md:102, which describes duvet's actual per-section extraction behaviour across all sections and would become false if narrowed. Class B — the template's duvet-mode preamble over-claimed. Line 100 said "Every other section of the template is unchanged", which is false for `## Overview`: the in-fence guidance at line 12 tells authors to use RFC 2119 keywords there, while the duvet-mode rules require Overview to be plain prose. An author following both writes a keyword that extracts as a requirement with no annotatable home and fails `duvet query -c implementation` permanently. Line 100 now names both overrides — the `## Requirements` shape and the `## Overview` guidance — and no longer claims the rest is untouched by implication. Audited the remaining in-fence guidance (fence spans lines 7-94) for the same shape of conflict. Only line 12 collided; the other RFC 2119 instruction, at line 25, sits inside the `## Requirements` block that duvet mode already replaces wholesale. Wording only. No rule was added, removed, or restructured; the retrofit ban, the docs-only reporting stance, the `git rev-parse` gate command and all three duvet annotations are unchanged. Verified: `duvet query -c implementation` exits 0 at 16/16, and `duvet report --ci` exits 0.
Why
Two independent problems in how
fx-dev:spec-writerproduces specs.Specs kept encoding procedure. Requirements outlive the workflow that satisfies them, but spec-writer had nothing telling it so — so specs pinned down step order, which tool runs when, timeouts and iteration caps. Every process improvement then broke a requirement and forced a spec edit that changed nothing about the actual contract. That churn is pure cost, and it trains everyone to read a failing requirement check as noise. The new "Specify Authority, Not Procedure" section is a general spec-authoring principle, not a duvet feature: a specify/never-specify table plus a test — if a requirement would become false because someone reordered or renamed a step without changing what the system guarantees, it is procedure, and it belongs in Design prose or nowhere.
This repo now traces requirements with duvet, and spec-writer did not know. New specs came out in a shape duvet cannot cite, and the failure mode is silent rather than loud. The new "Duvet Mode — Requirements Traceability" section teaches the shape:
### REQ-NNN: Descriptive Titleheadings zero-padded and allocated globally across the wholedocs/specs/tree, and exactly one self-contained normative sentence per section — annotations quote byte-for-byte, so a requirement spread across bullets, or one whose subject depends on the previous sentence, cannot be cited at all.The sharpest edge it documents: every spec must be registered in
.duvet/config.tomlwithformat = "markdown", and its absence is silent. duvet's default parser is IETF; pointed at a markdown spec it extracts zero requirements and exits 0, which is indistinguishable from success. That is why aduvet extractverification step is now mandatory — the requirement count is the only way to catch it.Also documented: renaming a requirement heading is a breaking change for its annotations, because the heading is the address. And spec-writer still does not annotate — establishing traces means editing source files, outside its documentation-only boundary. It reports which requirements need traces, by REQ ID, for an implementing skill to pick up.
Supporting wiring: Phase 0.1 duvet detection, a Phase 3.2 bullet, a Phase 6.3 registration step, and a new Output item.
Design decisions
.duvet/existence is the adoption flag. In a repo without it, spec-writer behaves exactly as before and does not mention duvet at all — no nagging, no unsolicited scaffolding. Offering adoption isfx-dev:setup's job, deliberately not this skill's.### REQ-NNN: Titleover an ID-only heading, by explicit user decision. It keeps the spec readable for humans while putting the ID in duvet's section anchor, so every annotation target carries the ID — which is what makes a later migration to a UID-based tool like StrictDoc a mechanical transform rather than a re-derivation. The trade-off is that retitling breaks annotations; that is accepted because it fails loudly via CI rather than drifting silently, and it is documented in the skill.Scope
This is the first of three related changes. Deliberately not in this PR: the
fx-dev:setup/fx-dev:upgradeadoption prompt, and teaching the implementing skills to establish traces. Both are listed under Follow-ups.Versions
plugins/fx-dev3.5.1 → 3.6.0, marketplace 0.18.0 → 0.19.0. Minor: adds capability, fully backward compatible, removes nothing.Test plan
duvet query -c implementation→ exit 0, 16/16 fully implemented (unchanged — this PR adds no requirements)duvet report --ci→ exit 0, snapshot unchangedduvet=annotation block inspec-writer/SKILL.mdis intact — 35 annotations parsed, 35 references matchedjq emptyvalidatejob passesRequirements traceabilityjob passesFollow-ups
Not implemented here, tracked for subsequent PRs:
fx-dev:setupandfx-dev:upgradeshould ask whether to adopt duvet, and scaffold.duvet/when the user says yes.coder, orchestrated bydev/team/fix) should establish duvet requirement traces. The annotation mechanics belong there —type=implicationversus the vacuoustype=test, theduvet=/duvet#block-comment form required for markdown, and the fact that duvet's single-line<!--= ... -->form is broken.fx-dev:copilot-review's review-request POST is inert, andwait-for-copilot-review.shconsequently returns a false "no review requested" — observed on PRs feat(ci): add duvet requirements traceability for marketplace structure #19 and feat(fx-dev): spec and trace agent authority boundaries #20.team/SKILL.mdself-contradicts on commit authority, anddevandteamdisagree on human review threads.Review coverage
Local
crandcodexremain unauthenticated on this machine, so two of the four pre-PR review passes did not run for this PR either. Copilot and CI are the reviewers that actually ran.