diff --git a/docs/upgrade.md b/docs/upgrade.md index f234be3352..500ffb1a6e 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -208,6 +208,66 @@ Restart your IDE to refresh the command list. --- +## Behavior change: `/constitution` no longer propagates into templates (0.14.4) + +Starting in **0.14.4** ([#3790](https://github.com/github/spec-kit/pull/3790)), the +`/constitution` command is scoped to its own artifact. It updates +`.specify/memory/constitution.md` and writes a Sync Impact Report, and **no longer edits** +`plan-template.md`, `spec-template.md`, `tasks-template.md`, installed command files, or +guidance docs. + +### Why + +Spec Kit uses **runtime resolution**: `plan`, `tasks`, and `analyze` read +`.specify/memory/constitution.md` live on every run, and `analyze` is the dedicated drift +checker. The governed templates carry a pointer, not a copy — `plan-template.md` ships +`[Gates determined based on constitution file]`, and `/plan` fills that section from the live +constitution each run. Propagation duplicated the single source of truth and fought the +preset/override composition system (a `replace` preset shadows an edited core template). + +### Is this a breaking change for existing projects? + +**No — your workflow keeps working.** The templates are scaffolds, not authorities. When you +run `/plan`, it copies the template into a per-feature `plan.md` and re-derives the Constitution +Check from the live constitution; `/analyze` validates against it. Even if a previous +`/constitution` run materialized concrete gate text into `.specify/templates/plan-template.md`, +the live constitution remains the source of truth at runtime. + +On a **non-forced upgrade**, a materialized template is *preserved* (its hash diverges from the +recorded managed copy, so the refresh treats it as a customization and does not overwrite it). +Nothing regresses. + +### Optional cleanup — return to the runtime pointer + +A frozen, pre-filled Constitution Check is a slightly misleading scaffold and can bias the first +`/plan` pass. To move fully back to runtime resolution, reset the section body in +`.specify/templates/plan-template.md` to the pointer: + +```text +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +[Gates determined based on constitution file] +``` + +Leave the rest of the file untouched. This is cleanup, not a required migration. + +### Keeping the old behavior (opt-in) + +If your team treats the materialized templates as **reviewed, committed artifacts** and wants +`/constitution` to keep propagating, install the bundled **`constitution-sync`** preset: + +```bash +specify preset add constitution-sync +``` + +It wraps the core `/constitution` command and re-adds the propagation pass. It does **not** edit +versioned preset- or extension-provided templates (those are owned by their packages). See +`presets/constitution-sync/README.md` for the tradeoffs. + +--- + ## Common Scenarios ### Scenario 1: "I just want new slash commands" diff --git a/presets/catalog.json b/presets/catalog.json index f272617926..196115ffb4 100644 --- a/presets/catalog.json +++ b/presets/catalog.json @@ -25,6 +25,29 @@ "workflow", "core" ] + }, + "constitution-sync": { + "name": "Constitution Template Sync", + "id": "constitution-sync", + "version": "1.0.0", + "description": "Opt-in: restores /constitution propagation of amended guidance into plan/spec/tasks templates and installed command files, for teams that treat materialized templates as reviewed artifacts.", + "author": "github", + "repository": "https://github.com/github/spec-kit", + "license": "MIT", + "bundled": true, + "requires": { + "speckit_version": ">=0.14.4" + }, + "provides": { + "commands": 1, + "templates": 0 + }, + "tags": [ + "constitution", + "governance", + "templates", + "compatibility" + ] } } } diff --git a/presets/constitution-sync/README.md b/presets/constitution-sync/README.md new file mode 100644 index 0000000000..2a8a39b6e2 --- /dev/null +++ b/presets/constitution-sync/README.md @@ -0,0 +1,113 @@ +# Constitution Template Sync + +An **opt-in** preset that restores the pre-0.14.4 `/constitution` behavior: after the +constitution is updated, it propagates the amended guidance into the project's dependent +templates and installed command files. + +## Background + +Through 0.14.3, `/constitution` performed a "consistency propagation checklist" — it read +`.specify/templates/plan-template.md`, `spec-template.md`, `tasks-template.md`, the installed +Spec Kit command files, and guidance docs, and updated them to match the amended principles. + +[#3790](https://github.com/github/spec-kit/pull/3790) (shipped in 0.14.4) **removed** that +propagation from the core command. The default model is now **runtime resolution**: `plan`, +`tasks`, and `analyze` read `.specify/memory/constitution.md` live on every run, so the +templates only need to carry a pointer (`plan-template.md` ships +`[Gates determined based on constitution file]`) rather than a materialized copy. This avoids +duplicating the source of truth and avoids fighting the preset/override composition system. + +## When to use this preset + +Install it **only** if your team treats the materialized templates as **reviewed, committed +artifacts** — for example, if `plan-template.md`'s Constitution Check is read in PRs as "here are +our current gates" and is expected to stay in sync with the constitution. + +If you rely on the default runtime-resolution model, you do **not** need this preset. Your +workflow is already correct without it: the live constitution is the single source of truth. + +## What it does + +This preset ships a single `wrap`-strategy override of `speckit.constitution`. It composes on top +of the current core command (via `{CORE_TEMPLATE}`), so it stays forward-compatible with core +changes, and appends a propagation pass that: + +- Aligns `plan/spec/tasks-template.md` with the updated principles. +- Scans installed command files for stale agent-specific references. +- Updates guidance-doc references to changed principles. +- Extends the Sync Impact Report with the templates it touched. + +It deliberately **does not** edit versioned preset- or extension-provided template files — those +are owned by their packages and recomposed on update. + +## Interaction with the resolution stack (important limitation) + +This preset and the preset resolution stack are built on **opposing philosophies**, and they can +bite each other: + +- The resolution stack (the default model since #3790) treats templates and commands as + **layered, package-owned artifacts that are recomposed on demand** — nothing is meant to be a + frozen copy. +- Auto-propagation does the **opposite**: it **materializes** constitutional guidance *into* + template files and freezes it there. + +So if a template you propagate into is actually **provided by another preset or extension in your +stack**, the two mechanisms fight: your propagated gate text is clobbered the next time that +package is reconciled/updated, and your hand edits are lost. For the same reason the wrapper only +ever writes into the project's **own** `.specify/templates/` scaffolds and installed command files +— never into stack-owned template layers. + +Practical guidance: + +- This preset is safe and useful when your governed templates are **project-local scaffolds** you + own and review. +- If your `plan/spec/tasks` templates come from **other presets or extensions**, propagation will + not stick — keep the default runtime-resolution model instead, where the live constitution is + read on every run and there is nothing to sync. + +## Tradeoffs + +- Re-introduces a materialized copy of constitutional guidance in the templates, which can drift + if `/constitution` is not re-run. The default runtime model does not have this problem. +- Materializing concrete gates into `plan-template.md` replaces the runtime pointer; a pre-filled + Constitution Check can bias the first `/plan` pass. Keep the pointer unless you specifically + want committed gates. + +## Installation + +```bash +# constitution-sync is a bundled preset — no download needed +specify preset add constitution-sync +``` + +## Development + +```bash +# Test from local directory +specify preset add --dev ./presets/constitution-sync + +# Verify the wrapped command resolves +specify preset resolve speckit.constitution + +# Remove when done +specify preset remove constitution-sync +``` + +## Migrating back to the default + +If you decide to move to runtime resolution, reset each materialized +`## Constitution Check` section in `.specify/templates/plan-template.md` back to the pointer: + +```text +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +[Gates determined based on constitution file] +``` + +Then remove this preset. See `docs/upgrade.md` for details. + +## License + +MIT diff --git a/presets/constitution-sync/commands/speckit.constitution.md b/presets/constitution-sync/commands/speckit.constitution.md new file mode 100644 index 0000000000..08ad159ce8 --- /dev/null +++ b/presets/constitution-sync/commands/speckit.constitution.md @@ -0,0 +1,42 @@ +--- +description: Create or update the project constitution, then propagate the amended guidance into dependent templates and installed command files (opt-in template sync). +strategy: wrap +handoffs: + - label: Build Specification + agent: speckit.specify + prompt: Implement the feature specification based on the updated constitution. I want to build... +--- + +{CORE_TEMPLATE} + +## Constitution Template Sync + +After you have written the updated constitution above, perform a consistency propagation pass +so the dependent artifacts reflect the amended principles: + +1. Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align + with the updated principles. Only materialize concrete gate text here if your team intends to + review it as committed content; otherwise leave the runtime pointer + `[Gates determined based on constitution file]` in place so `/plan` fills it from the live + constitution. +2. Read `.specify/templates/spec-template.md` for scope/requirements alignment — update if the + constitution adds/removes mandatory sections or constraints. +3. Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or + removed principle-driven task types (e.g., observability, versioning, testing discipline). +4. Read each installed Spec Kit command file for your agent (including this one) — named + `speckit.*` or `speckit-*` (dot or hyphen depending on the agent), or laid out as + `speckit-/SKILL.md` for skills-based integrations, e.g. in `.github/agents/`, + `.github/skills/`, `.claude/skills/`, or your agent's equivalent commands directory — to verify + no outdated references (CLAUDE-only or other agent-specific names) remain when generic guidance + is required. +5. Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific + guidance files if present) and update references to principles that changed. + +Then extend the Sync Impact Report at the top of `.specify/memory/constitution.md` with: + +- Templates requiring updates (✅ updated / ⚠ pending) with file paths. + +**Do not edit versioned preset- or extension-provided template files directly.** Those artifacts +are owned by their packages and are recomposed on the package's next update — hand edits are +clobbered. Limit propagation to the project's own `.specify/templates/` scaffolds and installed +command files. diff --git a/presets/constitution-sync/preset.yml b/presets/constitution-sync/preset.yml new file mode 100644 index 0000000000..574faa9698 --- /dev/null +++ b/presets/constitution-sync/preset.yml @@ -0,0 +1,30 @@ +schema_version: "1.0" + +preset: + id: "constitution-sync" + name: "Constitution Template Sync" + version: "1.0.0" + description: "Opt-in: restores /constitution propagation of amended guidance into plan/spec/tasks templates and installed command files, for teams that treat materialized templates as reviewed artifacts." + author: "github" + repository: "https://github.com/github/spec-kit" + license: "MIT" + +requires: + # Requires the runtime-resolution baseline (#3790, shipped in 0.14.4) where the + # core /constitution command no longer propagates. Installing this preset on an + # older core would double-apply propagation. + speckit_version: ">=0.14.4" + +provides: + templates: + - type: "command" + name: "speckit.constitution" + file: "commands/speckit.constitution.md" + description: "Wrap /constitution to also propagate guidance into dependent templates and command files" + strategy: "wrap" + +tags: + - "constitution" + - "governance" + - "templates" + - "compatibility" diff --git a/tests/test_presets.py b/tests/test_presets.py index ba2b98f8ed..70b9fb8ad0 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -12493,3 +12493,59 @@ def test_resolve_renders_composition_strategy_labels(self, temp_dir, project_dir assert "Composition chain" in output, output assert "[base]" in output, output assert "[append]" in output, output + + +class TestConstitutionSyncPreset: + """The bundled opt-in ``constitution-sync`` preset re-adds propagation. + + Follow-up to #3790: core ``/constitution`` no longer propagates guidance + into templates. This preset restores that behavior for teams that treat + materialized templates as reviewed artifacts, delivered as a ``wrap`` of + the core command so it stays forward-compatible with core changes. + """ + + PRESET_DIR = Path(__file__).parent.parent / "presets" / "constitution-sync" + + def test_manifest_provides_wrap_of_constitution(self): + manifest = yaml.safe_load((self.PRESET_DIR / "preset.yml").read_text()) + assert manifest["preset"]["id"] == "constitution-sync" + entries = manifest["provides"]["templates"] + assert len(entries) == 1 + entry = entries[0] + assert entry["type"] == "command" + assert entry["name"] == "speckit.constitution" + assert entry["strategy"] == "wrap" + # Must target the post-#3790 baseline so propagation is not double-applied. + assert manifest["requires"]["speckit_version"] == ">=0.14.4" + + def test_wrapper_uses_core_template_and_propagates(self): + text = (self.PRESET_DIR / "commands" / "speckit.constitution.md").read_text() + assert "strategy: wrap" in text + assert "{CORE_TEMPLATE}" in text + # The three governed scaffolds the old checklist propagated into. + assert "plan-template.md" in text + assert "spec-template.md" in text + assert "tasks-template.md" in text + # Must not mutate versioned preset/extension artifacts. + assert "Do not edit versioned preset- or extension-provided template files" in text + + def test_catalog_lists_bundled_preset(self): + manifest = yaml.safe_load((self.PRESET_DIR / "preset.yml").read_text()) + catalog = json.loads((self.PRESET_DIR.parent / "catalog.json").read_text()) + entry = catalog["presets"]["constitution-sync"] + assert entry["bundled"] is True + assert entry["version"] == manifest["preset"]["version"] + assert entry["provides"]["commands"] == 1 + assert entry["provides"]["templates"] == 0 + + def test_wrap_composes_over_core_constitution(self, project_dir): + """Installing the preset yields a wrap layer atop the bundled core.""" + manager = PresetManager(project_dir) + manager.install_from_directory(self.PRESET_DIR, "0.15.0") + + resolver = PresetResolver(project_dir) + layers = resolver.collect_all_layers("speckit.constitution", "command") + assert len(layers) >= 2, "expected preset wrap layer plus a core base" + assert layers[0]["strategy"] == "wrap" + assert any("constitution-sync" in str(layer["path"]) for layer in layers) + assert layers[-1]["source"] == "core (bundled)"