Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/reference/presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ specify preset resolve <name>

Shows which file will be used for a given name by tracing the full resolution stack. Useful for debugging when multiple presets provide the same file.

To emit the effective template content instead of human-readable diagnostics, use:

```bash
specify preset resolve <name> --content
```

This writes only the fully composed content to stdout, including `prepend`, `append`, and `wrap`
layers. It exits non-zero when the template cannot be found or composed, so commands and scripts can
consume it without accidentally treating diagnostic output as a template.

## Enable / Disable a Preset

```bash
Expand Down
13 changes: 13 additions & 0 deletions presets/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ Content resolution functions for composition:
- **Bash**: `resolve_template_content()` in `scripts/bash/common.sh` (templates only; command/script composition is handled by the Python resolver)
- **PowerShell**: `Resolve-TemplateContent` in `scripts/powershell/common.ps1` (templates only; command/script composition is handled by the Python resolver)

### Constitution lifecycle

Initialization resolves `constitution-template` through the full stack and seeds
`.specify/memory/constitution.md` once. Existing files are preserved byte-for-byte. On subsequent
`/constitution` runs, the command resolves the current composed template at runtime and uses the live
constitution as the source of project-specific values and amendments.

Preset installation, removal, enablement, disablement, and priority changes do not materialize
`constitution-template` by default. When the enabled preset registry contains `constitution-sync`,
those operations may reconcile the live file, but only if its provenance hash proves it is still
generated content. Missing files may be seeded when the preset is installed; authored or edited
constitutions are never overwritten.

## Command Registration

When a preset is installed with `type: "command"` entries, the `PresetManager` registers them into all detected agent directories using the shared `CommandRegistrar` from `src/specify_cli/agents.py`.
Expand Down
10 changes: 10 additions & 0 deletions presets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ If no preset is installed, core templates are used — exactly the same behavior

Template resolution happens **at runtime** — although preset files are copied into `.specify/presets/<id>/` during installation, Spec Kit walks the resolution stack on every template lookup rather than merging templates into a single location.

`constitution-template` follows the same runtime model. Project initialization seeds
`.specify/memory/constitution.md` once so downstream commands always have a constitution to read.
After that, installing, removing, enabling, disabling, or reprioritizing presets does not rewrite the
live constitution. Each `/constitution` run resolves the current composed `constitution-template`,
then applies existing project values and amendments to that scaffold.

Teams that intentionally want preset stack changes to refresh an unchanged generated constitution can
install the bundled `constitution-sync` preset. It restores guarded install-time materialization in
addition to its command-time propagation behavior; authored constitutions remain protected.

For detailed resolution and command registration flows, see [ARCHITECTURE.md](ARCHITECTURE.md).

## Command Overrides
Expand Down
4 changes: 2 additions & 2 deletions presets/catalog.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema_version": "1.0",
"updated_at": "2026-04-24T00:00:00Z",
"updated_at": "2026-08-04T00:00:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.json",
"presets": {
"lean": {
Expand Down Expand Up @@ -30,7 +30,7 @@
"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.",
"description": "Opt-in: restores guarded install-time constitution seeding and /constitution propagation for teams that treat materialized templates as reviewed artifacts.",
"author": "github",
"repository": "https://github.com/github/spec-kit",
"license": "MIT",
Expand Down
21 changes: 15 additions & 6 deletions presets/constitution-sync/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Constitution Template Sync

An **opt-in** preset that restores `/constitution`'s ability to propagate amended guidance into your
project's own templates and command files. After you update the constitution, it aligns
`plan-template.md`, `spec-template.md`, `tasks-template.md`, project-local command files, and
An **opt-in** preset that restores materialized constitution workflows. It refreshes an unchanged
generated `.specify/memory/constitution.md` when constitution-providing presets are installed,
removed, enabled, disabled, or reprioritized. After `/constitution` updates the live file, it also
aligns `plan-template.md`, `spec-template.md`, `tasks-template.md`, project-local command files, and
guidance docs so they reflect the current principles.

This propagation used to be built into `/constitution`; it was dropped when the command moved to the
preset model. Installing this preset opts you back into it: you get the guidance materialized into
reviewed, committed artifacts instead of relying on runtime resolution alone.
preset model. Installing this preset opts you back into materialization: preset stack changes refresh
the generated constitution, and `/constitution` propagates its guidance into reviewed, committed
artifacts instead of relying on runtime resolution alone.

> **What you're opting into.** Propagation was removed deliberately — it duplicates the constitution
> as the source of truth and can fight the composition stack (materialized edits get shadowed or
Expand All @@ -28,7 +30,12 @@ versioned preset a core team maintains.

## What it does

Ships a single `wrap`-strategy override of `speckit.constitution`. It composes on top of the
Its presence enables core's guarded install-time constitution reconciliation. Installing the preset
materializes the currently resolved `constitution-template`; later stack changes re-materialize it
only while the live file still matches its recorded generated-content hash. Human edits disable
automatic replacement.

It also 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, after the constitution is written:

Expand All @@ -43,6 +50,8 @@ appends a propagation pass that, after the constitution is written:
- It does **not** disable runtime resolution. `plan`, `tasks`, and `analyze` still read the live
constitution every run; this preset adds materialized copies on top — it does not replace the
source of truth.
- It does **not** overwrite an authored or edited constitution. Install-time reconciliation only
replaces content whose provenance proves it is an unchanged generated file.
- It does **not** edit versioned, package-owned files — templates or command files provided or
wrapped by another preset or extension. Those are recomposed from the resolution stack, so it
only ever writes into your project's own `.specify/templates/` scaffolds and command files that
Expand Down
2 changes: 1 addition & 1 deletion presets/constitution-sync/preset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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."
description: "Opt-in: restores guarded install-time constitution seeding and /constitution propagation for teams that treat materialized templates as reviewed artifacts."
author: "github"
repository: "https://github.com/github/spec-kit"
license: "MIT"
Expand Down
31 changes: 16 additions & 15 deletions src/specify_cli/presets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@


_CONSTITUTION_PROVENANCE_FILE = ".constitution-template.json"
_CONSTITUTION_SYNC_PRESET_ID = "constitution-sync"


def _content_sha256(content: bytes) -> str:
Expand Down Expand Up @@ -3550,28 +3551,24 @@ def install_from_directory(
stacklevel=2,
)

# Seed/re-seed memory/constitution.md from a preset-provided
# constitution-template. The constitution is the only template that is
# materialized to a live file rather than resolved on demand, so a
# preset that ships one (e.g. strategy: replace with a ratified
# constitution) must be propagated here. Guard against clobbering an
# already-authored constitution by only replacing a file whose recorded
# hash (or exact legacy core-template content) proves it was generated.
# Materialize constitution-template changes only for projects that opt
# into the constitution-sync preset. The core /constitution command
# resolves this template on demand; constitution-sync preserves the
# previous install-time behavior for teams that want reviewed snapshots.
self._seed_constitution_from_preset(manifest, dest_dir)

return manifest

def _seed_constitution_from_preset(
self, manifest: PresetManifest, preset_dir: Path
) -> None:
"""Seed memory/constitution.md from a preset constitution-template.
"""Seed memory/constitution.md when constitution-sync opts into snapshots.

Only runs when the preset declares a ``type: template`` entry named
``constitution-template`` or provides one at a convention path, and the
live memory file is either missing or is an unchanged generated file.
Authored constitutions are never overwritten.
Installing constitution-sync itself materializes the currently resolved
stack. Later preset installs only reconcile when they provide a
``constitution-template``. Authored constitutions are never overwritten.
"""
provides_constitution = any(
provides_constitution = manifest.id == _CONSTITUTION_SYNC_PRESET_ID or any(
t.get("type") == "template" and t.get("name") == "constitution-template"
for t in manifest.templates
) or any(
Expand All @@ -3592,7 +3589,7 @@ def _seed_constitution_from_preset(
def reconcile_constitution(
self, failure_context: str, *, create_if_missing: bool = False
) -> None:
"""Reconcile generated constitution content without failing a persisted change."""
"""Reconcile an opted-in generated constitution without failing a change."""
try:
self._reconcile_constitution(create_if_missing=create_if_missing)
except (OSError, UnicodeDecodeError, PresetValidationError, ValueError) as exc:
Expand All @@ -3604,7 +3601,11 @@ def reconcile_constitution(
)

def _reconcile_constitution(self, *, create_if_missing: bool = False) -> None:
"""Materialize the winning constitution layer when the live file is generated."""
"""Materialize the winning layer when constitution-sync is enabled."""
sync_metadata = self.registry.get(_CONSTITUTION_SYNC_PRESET_ID)
if sync_metadata is None or not sync_metadata.get("enabled", True):
return

memory_constitution = (
self.project_root / ".specify" / "memory" / "constitution.md"
)
Expand Down
25 changes: 23 additions & 2 deletions src/specify_cli/presets/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,13 +347,34 @@ def preset_search(
@preset_app.command("resolve")
def preset_resolve(
template_name: str = typer.Argument(..., help="Template name to resolve (e.g., spec-template)"),
content: bool = typer.Option(
False,
"--content",
help="Write the fully composed template content to stdout",
),
):
"""Show which template will be resolved for a given name."""
"""Show template resolution details or emit its composed content."""
from .. import _require_specify_project
from . import PresetResolver
from . import PresetResolver, PresetValidationError

project_root = _require_specify_project()
resolver = PresetResolver(project_root)

if content:
try:
resolved_content = resolver.resolve_content(template_name)
Comment thread
mnriem marked this conversation as resolved.
Outdated
except (OSError, UnicodeError, PresetValidationError, ValueError) as exc:
typer.echo(f"Error: failed to resolve {template_name}: {exc}", err=True)
raise typer.Exit(1)
if resolved_content is None:
typer.echo(
f"Error: template '{template_name}' was not found or could not be composed",
err=True,
)
raise typer.Exit(1)
typer.echo(resolved_content, nl=False)
return

layers = resolver.collect_all_layers(template_name)
safe_template_name = _escape_markup(str(template_name))

Expand Down
22 changes: 16 additions & 6 deletions templates/commands/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,23 @@ and commands read the constitution at runtime and are not modified here.

## Outline

You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values and (b) fill the template precisely.

**Note**: If `.specify/memory/constitution.md` does not exist yet, it should have been initialized from `.specify/templates/constitution-template.md` during project setup. If it's missing, copy the template first.
You are updating the project constitution at `.specify/memory/constitution.md`. The active
constitution scaffold is resolved at command time from `constitution-template` through the Spec Kit
preset/template resolution stack.

Follow this execution flow:

1. Load the existing constitution at `.specify/memory/constitution.md`.
1. Run `specify preset resolve constitution-template --content` from the repository root and use its
stdout as the active template.
- This command applies project overrides, composing preset layers, and extension layers before the
core template fallback. It MUST succeed before continuing.
- If it fails, stop and report the resolution error; do not continue with only one contributing
template layer.
- If `.specify/memory/constitution.md` exists, load it as the source of current project-specific
values and amendments. Preserve information that is still applicable when applying the newly
resolved scaffold.
- If it does not exist, use the resolved template as the initial document.
- Do not write back to any versioned template layer.
- Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
**IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.

Expand All @@ -90,7 +100,7 @@ Follow this execution flow:
- PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
- If version bump type ambiguous, propose reasoning before finalizing.

3. Draft the updated constitution content:
3. Draft the updated constitution content using the resolved template as the required structure:
- Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
- Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
- Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
Expand Down Expand Up @@ -128,7 +138,7 @@ If the user supplies partial updates (e.g., only one principle revision), still

If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.

Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
Write only `.specify/memory/constitution.md`; do not create or modify template source files.

## Post-Execution Checks

Expand Down
Loading
Loading