* feat(presets): resolve constitutions at command time Gate install-time constitution materialization behind the constitution-sync preset while preserving one-time init seeding and authored-file safeguards. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): emit composed template content Add a machine-readable preset resolve mode backed by PresetResolver.resolve_content and require the constitution command to consume it. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): unify runtime template composition Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 * fix(presets): secure runtime template resolution Align runtime resolution across script variants, validate registry path components, and honor canonical extension ordering and convention paths. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): align runtime priority semantics Normalize and tie-break preset priorities consistently across script variants, and preserve template bytes when Python materializes generated files. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): stop at effective template base Avoid parsing irrelevant lower layers once resolution reaches a replace base, and decode raw bytes so Python preserves source line endings. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): align extension template resolution Support root-level extension templates across runtime resolvers, fail safely when Bash cannot parse an extension registry, and validate requested templates in every prerequisite output mode. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(presets): resolve dotted command identifiers Route safe dotted names through command resolution, correct traversal coverage, and make Windows CI text decoding explicit. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Avoid orphan feature directories on template errors Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Align malformed preset manifest handling Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Complete runtime resolver parity Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Fail closed on resolver input errors Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Force UTF-8 and full manifest validation Force UTF-8 decoding for registry and manifest reads in the Bash and PowerShell embedded-Python parsers so resolution no longer depends on the process locale, and validate every manifest template entry's required fields, type, and strategy consistent with the canonical PresetManifest. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * Fail closed on empty manifests and corrupt registries Reject manifests missing the provides/templates sections or declaring an empty template list in all three runtime resolvers, matching the canonical PresetManifest which treats those as invalid instead of silently degrading a composing layer to a convention `replace` lookup. Make a corrupt or unreadable extension registry fail closed in Bash, PowerShell, and Python instead of swallowing the error and treating every on-disk extension directory as unregistered-and-enabled, which could activate a disabled extension. Read the preset and extension registries as explicit UTF-8 in the PowerShell resolver so priority/enabled-state decoding no longer depends on the process code page under Windows PowerShell 5.1. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): fail closed when extension registry is not a regular file The Bash and Python resolvers used is_file()/`-f` to gate reading the extension `.registry`, which returns false for a directory or a broken symlink at that path. In those cases the resolvers treated the registry as absent and scanned every on-disk extension directory as unregistered and enabled — a fail-open path. Detect any filesystem entry at the registry path (including broken symlinks) and reject unless it is a readable regular file. PowerShell now rejects a non-leaf entry explicitly for parity. Adds directory- and broken-symlink parity regressions. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): fail closed on corrupt registry in canonical resolver and PowerShell Two remaining fail-open paths for an invalid extension registry: - The canonical PresetResolver enumerated extensions through ExtensionRegistry, whose _load() normalizes a corrupt or unreadable registry to an empty mapping. The directory scan then admitted every on-disk extension directory as unregistered-and-enabled, so a corrupt registry could still supply constitution content at init and through constitution-sync materialization. Add a non-invasive is_corrupt() probe (recovery behavior for install/enable/disable is unchanged) and raise from _get_all_extensions_by_priority() when the registry exists but is invalid. _load() now also recovers from OSError/UnicodeDecodeError so a directory or unreadable registry no longer crashes construction. - The PowerShell resolver gated the registry read with Test-Path, which returns false for a dangling symlink on Windows, letting a broken .registry symlink bypass the guard and enable every on-disk extension. Detect the entry via directory enumeration (which observes a broken symlink) and reject it unless it is a readable regular file. Adds canonical corrupt/directory-registry regressions and extends the broken-symlink parity test to PowerShell. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c * fix(presets): detect dangling registry symlink in ExtensionRegistry.is_corrupt is_corrupt() gated on Path.exists(), which follows symlinks and returns False for a dangling .registry symlink — so the canonical PresetResolver treated it as an absent registry and fell back to scanning every on-disk extension directory as unregistered-and-enabled, reopening the fail-open path this guard closes. Detect lexical existence with os.path.lexists and require a regular file before parsing, so a broken symlink (or directory) is reported corrupt and resolution fails closed. Adds a canonical broken-symlink regression alongside the directory case. Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7dbce70f-80c6-4e14-a30d-78cb358bcb84 Copilot-Session: 3158e06f-95df-4e3a-843f-f159a35aa30c
Constitution Template Sync
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 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 clobbered on the next recompose). This preset knowingly reintroduces that behavior, and those tradeoffs, for teams that want it. Read the caveats before installing.
For most projects the default composable stack is the recommended approach, and at organization scale it is usually the stronger governance model. Runtime resolution keeps the live constitution as the single source of truth (nothing to re-sync, so nothing drifts), and the stack composes the entire Spec Kit ecosystem — not just the SDD commands, but every command, template, script and extension — with explicit priority levels, strategies, and independent versioning. It is a capability, not automatic governance: a core team authors its own organizational presets and extensions, then owns, versions, and audits that policy in one place and rolls it across many repositories, instead of scattering frozen, per-repo copies no central team can see. This preset is a supported escape hatch for teams whose workflow depends on reviewing materialized artifacts directly — useful as a bridge, though for org-wide policy the better long-term path is usually a versioned preset a core team maintains.
What it does
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:
- Aligns
plan/spec/tasks-template.mdin.specify/templates/with the updated principles. - Updates project-local command files and guidance docs to correct stale references.
- Extends the Sync Impact Report in
.specify/memory/constitution.mdwith the files it touched.
What it does not do
- It does not change behavior for anyone who does not install it — the default runtime resolution model is untouched.
- It does not disable runtime resolution.
plan,tasks, andanalyzestill 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 are not managed by a preset/extension.
When to use it
Install it only if your team treats the materialized templates and commands 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 track the constitution.
If you rely on the default runtime-resolution model, you do not need this preset: the live constitution is already the single source of truth and there is nothing to sync.
Caveats you take on
The preset resolution stack is how Spec Kit composes templates and commands going forward: they are layered, package-owned artifacts recomposed on demand, not frozen files you edit in place. Propagation is the opposite idea — it materializes guidance into files and freezes it. That tension is the main thing to understand before installing:
-
Materialized copies can drift. Anything propagated is a snapshot; if you amend the constitution and do not re-run
/constitution, the copies fall out of sync. The default runtime model has no drift because it reads the live constitution every run. -
Edits to composed files do not survive reconciliation. If the rest of your SDD flow is preset/extension-managed, the commands it materializes (
speckit.plan,speckit.specify,speckit.tasks,speckit.analyze,speckit.implement, …) are recomputed from the stack. Any guidance propagated into them is clobbered the next time the stack reconciles — onspecify integration use <key>/switch,specify integration upgrade, or any preset/extension install or remove. The same applies to templates owned by another preset/extension. This is why the preset restricts itself to project-local files; propagation is reliable only for artifacts you own outright. -
A pre-filled Constitution Check can bias
/plan. Materializing concrete gates intoplan-template.mdreplaces the runtime pointer, so the first/planpass may anchor on the frozen text. Keep the pointer unless you specifically want committed gates.
Bottom line: this preset fits projects whose governed templates and commands are project-local
artifacts they review, with the rest of the SDD flow on the plain bundled core. If your
plan/specify/tasks/analyze commands or templates come from other presets or extensions,
prefer the default runtime-resolution model.
Installation
# constitution-sync is a bundled preset — no download needed
specify preset add constitution-sync
Development
# 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
To move back to runtime resolution, reset each materialized ## Constitution Check section in
.specify/templates/plan-template.md to the pointer:
## 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