Skip to content

Design Decisions

Gregor Biswanger edited this page Aug 3, 2026 · 2 revisions

Design Decisions

Why FeatherSpec is shaped the way it is. Every rule here has a cost attached to breaking it.

One rule, one place

No rule and no workflow is written down twice — apart from three reinforcements listed below, which are deliberate and documented.

  • AGENTS.md is the only place mutable state livesDocLanguage, the architecture: snapshot, and Style & Output Preferences. Every command that changes those writes to AGENTS.md. CLAUDE.md is one line and holds no copy.
  • Rules and workflow bodies are single files both tools read. No .github/instructions/, no .github/copilot-instructions.md — they would only duplicate what .claude/rules/ and .claude/commands/ already provide. The one Copilot-side folder that does exist, .github/prompts/, holds loaders: a link and a sentence, never a rule.
  • AGENTS.md states policy; rule files state craft. AGENTS.md defines the spec and plan lifecycle and the Memory Bank file set. .claude/rules/specs.md, plans.md and memory-bank.md cover only how to write those files, and link to AGENTS.md for the policy. .specs/README.md is orientation and links rather than a restatement.

This is not tidiness for its own sake. Two copies of a rule drift, and when they drift the model follows whichever it read last. One copy cannot drift.

The documented exceptions

They all exist for the same reason: a path-scoped rule loads only when a matching file is read, which is too late for a workflow that is creating or moving that very file.

Reinforcement Where Why
Spec essentials (DocLanguage, the **Status:** line, the document structure) commands/sdd-specify.md a brand-new spec has not been read, so .claude/rules/specs.md may not have loaded yet
Plan essentials (DocLanguage, naming, the **Status:** line, the file structure) commands/sdd-plan.md same reason — a brand-new plan file has not been read, so .claude/rules/plans.md may not have loaded yet
Lifecycle safety rules (one folder at a time, delete the original, duplicate check, plan travels with spec) commands/sdd-lifecycle.md this workflow performs the moves and deletions; the safety rules must be in front of the model as it acts

AGENTS.md remains authoritative in every case: if a copy ever diverges, fix the copy. Do not "clean up" these by deleting them — each one is load-order insurance, and removing it reintroduces a real failure mode. The regression would be silent, too: nothing fails, the new plan file just comes out in the wrong language with no status line.

Each exception is annotated from both ends. The command says it is restating something and names AGENTS.md as authoritative; the corresponding rule file carries a reverse note saying why the command repeats it. specs.md, plans.md and memory-bank.md all carry that note. A deliberate duplication that is not labelled as one is indistinguishable from an accident, and the next contributor deletes it.

The rule that follows from this: a command may restate a rule when it must be in front of the model at the moment it acts. Anything else is a copy, and copies drift. The Copilot persona file used to hold five of them — an operating protocol with a fourth step the constitution does not have, a memory-sync rule missing its "same change set" clause, a preference rule missing its rewrite clause. All five had drifted from the original before anyone noticed. It is a pointer now.

One action, one autonomy level

A gate that exists in one file and not in another is not a gate — the effective behaviour is whatever happened to be in context. The architecture snapshot used to be written immediately for "obvious" drift by the constitution and only after confirmation by /sdd-architecture-update, with new top-level folder listed on both sides. There is now exactly one gate for that write, and the drift rule proposes rather than writes.

The same principle sets the shape of the non-negotiables: always, ask first, never — with ask first naming the operations whose blast radius reaches your codebase rather than a Markdown file (dependencies, migrations, deleting or moving files, git writes, network calls). The earlier version had no ask first tier at all, and every gate in the repository protected a document while nothing protected the code.

No skills on purpose

A skill advertises itself: both tools read every skill's name and description up front so the model can decide to load it. Claude Code drops that advertising when a skill sets disable-model-invocation: true, but VS Code does not document the same guarantee — so nine workflow descriptions could sit in the system prompt of every single request.

Workflows here are commands, which no model is offered:

  • .claude/commands/<name>.md holds the body and Claude Code runs it as /<name>
  • .github/prompts/<name>.prompt.md is a one-line loader that points Copilot at that same body

Reach for a skill only when a workflow genuinely needs the model to trigger it on its own. These do not: they create, move and delete files, and they should run when you say so.

Deliberate constraints in the shared bodies

Because one body file is executed by both tools, it is written to the lowest common denominator. Each constraint has a reason:

  • No !`shell` injection. Copilot does not support it; it would land in the prompt as literal text. Bodies instruct the model to run a command and use the result instead.
  • No $ARGUMENTS / $0 / $1. Same reason — Copilot's documented mechanism is ${input:…}. Bodies say "the user may name a spec path after the command; if none is given, list the candidates and ask." Both tools append the user's trailing text to the invocation, so the model still sees it.
  • No tool names in the bodies. The tool that fetches a web page is WebFetch in one and #fetch in the other, so bodies name the capability ("your web search or fetch capability") and state what to do when it is missing. Same for terminal work: bodies say which command to run and use the result, rather than assuming a tool name.
  • The file name is the command name in both tools, so .claude/commands/sdd-plan.md and .github/prompts/sdd-plan.prompt.md must share the stem.

A comment at the top of each body restates this, so nobody reintroduces the syntax later.

Why planning writes a file

A plan that lives only in the chat is gone when the session ends. Making the plan a file changes three things:

  1. Resumability — a new session reads Current step and Session handoff and continues, instead of asking you to re-explain.
  2. Reviewability — the plan shows up in a pull request next to the code it produced.
  3. Impact analysis — the traceability table can be read in reverse, so a changed requirement yields the list of steps and code paths it reaches before anything is edited.

The rule that makes all three work: the plan is updated in the same change set as the code. The assistant's own todo list is scratch state that dies with the session; the plan file is the durable one, and when the two differ, the file wins and gets corrected.

Why the data folders are top-level

.specs/ and .memory-bank/ are your project's content, not tool configuration. The predecessor template kept them under .github/, which made them look like GitHub plumbing and tied them to one vendor's folder. At the top level they are visibly data, they are tool-neutral, and they survive any decision you later make about which assistant to keep.

Why sdd- prefixes everything

Two reasons: the commands do not shadow Claude Code's built-in commands, and typing /sdd shows you the whole workflow set in one autocomplete group.

Clone this wiki locally