fix(presets): seed constitution from preset constitution-template (#3272)#3276
fix(presets): seed constitution from preset constitution-template (#3272)#3276BenBtg wants to merge 1 commit into
Conversation
) 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>
|
+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. We read #3276's diff against our use case, and it handles our edge cases correctly:
One small maintainability suggestion: Our current stopgap is (Disclosure, per CONTRIBUTING: this comment was drafted with the assistance of Claude Code (an AI coding agent) and reviewed by a human before posting.) |
| template_constitution = PresetResolver(project_path).resolve( | ||
| "constitution-template", "template" | ||
| ) |
| 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) |
Summary
Fixes #3272. A preset that ships a
type: templateconstitution-templateentry (e.g.strategy: replacewith 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, yetensure_constitution_from_templatehardcoded a copy from the core template and bypassedPresetResolver. Combined withinitseeding 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.py—ensure_constitution_from_templatenow resolvesconstitution-templatethroughPresetResolver(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, sospecify init --presetseeds from the resolved stack. No-op for non-preset init.presets/__init__.py—install_from_directoryre-seeds.specify/memory/constitution.mdfrom 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. Coversspecify preset addandinstall_from_zip(which delegates here).CORE_TEMPLATE_NAMESoverride resolvesconstitution-templateto 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 duringpreset addon an existing project.Testing
pytest tests/test_presets.py— 333 passed (7 new)This PR was authored autonomously by GitHub Copilot (model: Claude Opus 4.8). Each commit carries an
Assisted-by:trailer.