Skip to content

feat(fx-dev): teach spec-writer duvet mode and authority-not-procedure - #21

Merged
fx merged 4 commits into
mainfrom
feat/spec-writer-duvet-mode
Aug 10, 2026
Merged

feat(fx-dev): teach spec-writer duvet mode and authority-not-procedure#21
fx merged 4 commits into
mainfrom
feat/spec-writer-duvet-mode

Conversation

@fx

@fx fx commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Why

Two independent problems in how fx-dev:spec-writer produces 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 Title headings zero-padded and allocated globally across the whole docs/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.toml with format = "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 a duvet extract verification 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 is fx-dev:setup's job, deliberately not this skill's.

### REQ-NNN: Title over 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:upgrade adoption prompt, and teaching the implementing skills to establish traces. Both are listed under Follow-ups.

Versions

plugins/fx-dev 3.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 unchanged
  • The existing duvet= annotation block in spec-writer/SKILL.md is intact — 35 annotations parsed, 35 references matched
  • Both JSON manifests validate under jq empty
  • fx-dev synced to the plugin cache at v3.6.0
  • CI validate job passes
  • CI Requirements traceability job passes

Follow-ups

Not implemented here, tracked for subsequent PRs:

  • Adoption prompt: fx-dev:setup and fx-dev:upgrade should ask whether to adopt duvet, and scaffold .duvet/ when the user says yes.
  • Establishing traces: the implementing skills (coder, orchestrated by dev / team / fix) should establish duvet requirement traces. The annotation mechanics belong there — type=implication versus the vacuous type=test, the duvet= / 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, and wait-for-copilot-review.sh consequently 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.md self-contradicts on commit authority, and dev and team disagree on human review threads.

Review coverage

Local cr and codex remain 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.

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.
Copilot AI lite review requested due to automatic review settings August 10, 2026 01:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-dev plugin 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
@fx
fx requested a lite review from Copilot August 10, 2026 02:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`.
@fx
fx requested a lite review from Copilot August 10, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ## Notes block currently includes normative keywords (e.g., MUST/SHOULD/MAY), which duvet would extract as uncitable requirements and cause duvet query -c implementation failures. Consider rewriting ## Notes to 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-toplevel fails (e.g., invoked outside a git repo). Other fx-dev scripts use a safer git rev-parse ... 2>/dev/null || pwd pattern; 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"

Comment thread plugins/fx-dev/skills/spec-writer/references/spec-index-template.md
@fx

fx commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Copilot review triage — head 863a4c2

Review 4893297588, submitted 2026-08-10T02:22:57Z against 863a4c2. One visible comment, two suppressed comments. Suppressed comments create no thread, so their disposition is recorded here.

1. Visible — spec-index-template.md, "every other section is unchanged" vs. the Overview keyword guidance — VALID, fix pending

Disposition and rationale in the thread. Line 100 claims nothing outside ## Requirements changes under duvet mode, but the in-fence Overview guidance at line 12 instructs the author to use RFC 2119 keywords, which line 128 forbids. Thread stays open until the fix lands.

2. Suppressed — spec-index-template.md:135, ## Notes contains MUST/SHOULD/MAY — REJECTED, factually incorrect

## Notes sits outside the fenced ```markdown block; the fence closes at line 94. It is guidance addressed to the spec author, not content copied into docs/specs/<spec-name>/index.md. duvet extracts only from files registered as [[specification]] in .duvet/config.toml, and the template file itself — plugins/fx-dev/skills/spec-writer/references/spec-index-template.md — is not a spec and is never registered. No keyword in ## Notes can reach an extraction.

The valid form of this concern is finding 1, which targets line 12 — inside the fence, and therefore actually copied.

3. Suppressed — SKILL.md:254, harden git rev-parse --show-toplevel with 2>/dev/null || pwdREMEDY REJECTED; stderr half optional

The factual claim checks out: setup/references/instruction-files.md:126 and upgrade/SKILL.md:64 do use that pattern. But || pwd is wrong at this call site. It degrades the check to test -d "$(pwd)/.duvet" — cwd-relative — which is the exact failure mode the very next paragraph (line 257) exists to prevent: "A bare test -d .duvet is cwd-relative and reports 'off' for a duvet repo whenever the session's working directory is a subdirectory." Adopting the suggestion would reintroduce the silent skip.

The asymmetry is intentional. setup and upgrade bootstrap directories that may not be git repos yet, so falling back to pwd is correct there. spec-writer's duvet gate is defined at the repository root by construction; if there is no repository root there is no gate.

Current behavior outside a git repo is already correct — git writes to stderr, the substitution is empty, test -d "/.duvet" is false, output is "duvet mode: off". Only the stderr noise is real, and a bare 2>/dev/null would fix that without the || pwd. Cosmetic, non-blocking.

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.
@fx
fx requested a lite review from Copilot August 10, 2026 02:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@fx
fx merged commit 43d8be5 into main Aug 10, 2026
3 checks passed
@fx
fx deleted the feat/spec-writer-duvet-mode branch August 10, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants