Skip to content

fix(presets): seed constitution from preset constitution-template (#3272)#3276

Draft
BenBtg wants to merge 1 commit into
mainfrom
benbtg-fix-3272-preset-constitution-template-se
Draft

fix(presets): seed constitution from preset constitution-template (#3272)#3276
BenBtg wants to merge 1 commit into
mainfrom
benbtg-fix-3272-preset-constitution-template-se

Conversation

@BenBtg

@BenBtg BenBtg commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #3272. A preset that ships a type: template constitution-template entry (e.g. strategy: replace with a ratified constitution) was never applied to .specify/memory/constitution.md. The constitution is the only template materialized to a live file rather than resolved on demand, yet ensure_constitution_from_template hardcoded a copy from the core template and bypassed PresetResolver. Combined with init seeding the constitution before preset installation, a preset's constitution could never go live.

This is a provenance fix (resolve through the same priority stack every other template uses), not a copy-into-core fix.

Changes

  • commands/init.pyensure_constitution_from_template now resolves constitution-template through PresetResolver (project overrides → installed presets → extensions → core). Core remains the fallback when nothing overrides it. Skip-if-exists / not-found behavior preserved.
  • commands/init.py — reordered so the constitution is seeded after preset installation, so specify init --preset seeds from the resolved stack. No-op for non-preset init.
  • presets/__init__.pyinstall_from_directory re-seeds .specify/memory/constitution.md from the resolved preset template, only when the memory file is missing or still contains generic placeholder tokens ([PROJECT_NAME] / [PRINCIPLE_1_NAME]). Authored constitutions are never overwritten. Covers specify preset add and install_from_zip (which delegates here).
  • Tests — preset seeds memory; placeholder memory re-seeded; authored constitution preserved; CORE_TEMPLATE_NAMES override resolves constitution-template to the preset file; resolver-aware init seeding (core fallback, preset override, existing-file preservation).

Key guard

Placeholder-token detection (_constitution_is_placeholder) prevents clobbering legitimately authored constitutions during preset add on an existing project.

Testing

  • pytest tests/test_presets.py — 333 passed (7 new)
  • Full suite — 3853 passed, 4 skipped

This PR was authored autonomously by GitHub Copilot (model: Claude Opus 4.8). Each commit carries an Assisted-by: trailer.

)

The constitution is the only template materialized to a live file
(.specify/memory/constitution.md) rather than resolved on demand, yet
ensure_constitution_from_template hardcoded a copy from the core template
and ignored PresetResolver. Combined with init seeding the constitution
before preset installation, a preset's constitution-template (e.g.
strategy: replace with a ratified constitution) could never go live.

Changes:
- ensure_constitution_from_template now resolves constitution-template
  through PresetResolver, so a preset/override/extension wins and core is
  the fallback.
- init seeds the constitution after preset installation so init --preset
  uses the resolved stack.
- install_from_directory re-seeds memory/constitution.md from the resolved
  preset template, guarded to only act when the memory file is missing or
  still contains generic placeholder tokens — authored constitutions are
  never overwritten. Covers preset add and install_from_zip.
- Tests for preset seeding, placeholder re-seed, authored-constitution
  preservation, override resolution, and resolver-aware init seeding.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 18:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@gglachant

Copy link
Copy Markdown

+1 — we hit this independently. We maintain a spec-kit harness for a team with a fixed, ratified constitution authored upstream in a canonical repo and installed verbatim downstream. /speckit.constitution treating .specify/memory/constitution.md as a template to (re-)author from placeholders is exactly the hazard #3272 names: for us an AI-authored variant is a governance violation, not a convenience.

We read #3276's diff against our use case, and it handles our edge cases correctly:

  • Deferred-value markers are preserved. Our authored constitution carries a few intentional spec-kit-style TODO(FIELD) deferred values (e.g. TODO(RATIFICATION_DATE)) that resolve at a later milestone. Because _constitution_is_placeholder keys on the stock template's specific sentinels ([PROJECT_NAME], [PRINCIPLE_1_NAME]) rather than any generic TODO( / [...], such a constitution is correctly classified authored and preserved. 👍 (That was our one worry from the summary; the diff resolves it.)
  • Byte-faithful install. shutil.copy2(resolved, memory_constitution) materializes the preset's replace template unchanged — exactly what a ratified constitution needs (no reformatting, token-fill, or version recompute).

One small maintainability suggestion: _CONSTITUTION_PLACEHOLDER_TOKENS is a fixed pair — worth a test pinning it to the actual [ALL_CAPS] tokens in the stock constitution-template.md, so the guard can't silently drift out of sync if that template's placeholders ever change.

Our current stopgap is cp canonical → diff (confirm byte-identical) → run the command report-only plus disabling the auto-authoring path; #3276's preset-aware replace path would let us ship the constitution as a preset template and retire that entirely. Thanks for picking this up.

(Disclosure, per CONTRIBUTING: this comment was drafted with the assistance of Claude Code (an AI coding agent) and reviewed by a human before posting.)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +47 to 49
template_constitution = PresetResolver(project_path).resolve(
"constitution-template", "template"
)
Comment on lines +1667 to +1675
resolved = PresetResolver(self.project_root).resolve(
"constitution-template", "template"
)
if resolved is None or not resolved.exists():
return

try:
memory_constitution.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(resolved, memory_constitution)
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.

Preset constitution-template (strategy: replace) is not installed, and speckit.constitution is not preset-aware

4 participants