Skip to content

[bug-fix] preset-constitution-not-installed: resolver-aware seeding + post-install reseed#3298

Draft
github-actions[bot] wants to merge 1 commit into
mainfrom
fix/3272-preset-constitution-not-installed-7cf1ee45d83b42b6
Draft

[bug-fix] preset-constitution-not-installed: resolver-aware seeding + post-install reseed#3298
github-actions[bot] wants to merge 1 commit into
mainfrom
fix/3272-preset-constitution-not-installed-7cf1ee45d83b42b6

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Bug fix — preset-constitution-not-installed

Proposed fix for issue #3272, applying the remediation from the bug assessment.

Verdict: valid · Severity: high

Summary

ensure_constitution_from_template previously hardcoded a copy from the core template, bypassing PresetResolver entirely and always seeding .specify/memory/constitution.md with the generic upstream placeholder. This change makes constitution seeding preset-aware in two places (init flow and preset add), so a preset that provides a ratified constitution-template automatically lands in the project's memory file.

Changes

File Change Notes
src/specify_cli/commands/init.py modified ensure_constitution_from_template now resolves via PresetResolver; call moved after preset installation block
src/specify_cli/presets/__init__.py modified Added _CONSTITUTION_PLACEHOLDER_TOKENS constant and _maybe_reseed_constitution method; called at end of install_from_directory
tests/test_presets.py added tests 7 regression tests in new TestConstitutionReseed class

Tests Added or Updated

  • TestConstitutionReseed::test_ensure_constitution_uses_resolver_when_preset_installed — verifies ensure_constitution_from_template picks up the preset's constitution-template via PresetResolver
  • TestConstitutionReseed::test_ensure_constitution_falls_back_to_core_without_preset — verifies fallback to core template when no preset overrides it
  • TestConstitutionReseed::test_ensure_constitution_skips_existing_file — verifies existing memory files are never overwritten
  • TestConstitutionReseed::test_install_from_directory_reseeds_generic_constitution — verifies install_from_directory re-seeds a generic (placeholder-containing) memory constitution from the preset
  • TestConstitutionReseed::test_install_from_directory_does_not_overwrite_authored_constitution — verifies authored constitutions (no placeholder tokens) are left untouched
  • TestConstitutionReseed::test_install_from_directory_skips_reseed_without_constitution_template — verifies presets that don't provide constitution-template don't touch the memory file
  • TestConstitutionReseed::test_self_test_preset_reseeds_generic_constitution_on_install — end-to-end test using the self-test preset

Local Verification

  • Syntax check: python3 -c "import ast; ast.parse(open(f).read())" on all three files → OK
  • No test runner available in the CI environment (no uv/venv installed at workflow time); verified by code inspection and AST parsing.

Deviations from Assessment

None. The implementation follows all three parts of the preferred remediation exactly:

  1. PresetResolver used in ensure_constitution_from_template
  2. Init flow reordered (call moved after preset block) ✅
  3. Guarded re-seed hook in install_from_directory via _maybe_reseed_constitution

The guard condition uses both [PROJECT_NAME] and [PRINCIPLE_1_NAME] as placeholder tokens, as recommended in the assessment's Risks & Considerations.

Risks & Review Notes

  • The re-seed guard (placeholder token check) may produce false negatives for authored constitutions that happen to retain [PROJECT_NAME] or [PRINCIPLE_1_NAME] as literal text. The conservative dual-token check (any(token in content for token in ...)) reduces but cannot eliminate this risk — a single stray [PROJECT_NAME] in an otherwise complete constitution would still trigger a re-seed.
  • Moving ensure_constitution_from_template after the preset block does not affect init runs without a --preset argument (no-op change for the common case).
  • PresetResolver is now imported lazily inside ensure_constitution_from_template with a try/except Exception guard, keeping the fallback to direct core-path lookup for environments where the preset system is unavailable.

Refs #3272 · cc @PechiSW

Generated by 🛠️ Fix Bug from Labeled Issue for issue #3272 · 3.2K AIC · ⌖ 29.5 AIC · ⊞ 35.6K ·

…d hook

Apply the remediation from the bug assessment on issue #3272.

Three-part fix:

1. Make ensure_constitution_from_template resolver-aware (init.py): replace
   the hardcoded shutil.copy2 from the core template path with a call to
   PresetResolver(project_path).resolve('constitution-template', 'template').
   The resolver walks the full preset priority stack so a preset-provided
   constitution-template is picked up automatically. Falls back to the core
   template when no preset overrides it, preserving existing behaviour for
   projects without presets.

2. Reorder the init flow (init.py): move the call to
   ensure_constitution_from_template from before the preset installation block
   to after it. For 'specify init --preset', the memory file is now seeded
   from the already-resolved template stack instead of from the generic core
   template that existed before the preset arrived.

3. Add guarded re-seed hook in install_from_directory (presets/__init__.py):
   _maybe_reseed_constitution is called at the end of install_from_directory
   and re-seeds .specify/memory/constitution.md from the preset's resolved
   constitution-template, but only when (a) the manifest provides a
   constitution-template entry AND (b) the current memory file still contains
   generic placeholder tokens ([PROJECT_NAME] or [PRINCIPLE_1_NAME]).
   Legitimately authored constitutions (no placeholder tokens) are never
   overwritten.

Also adds a _CONSTITUTION_PLACEHOLDER_TOKENS module-level constant and seven
regression tests covering:
- ensure_constitution_from_template picks up a preset override via resolver
- falls back to core template when no preset is installed
- skips existing memory files
- install_from_directory re-seeds a generic memory constitution
- install_from_directory does not overwrite an authored constitution
- install_from_directory skips re-seed for presets without constitution-template
- self-test preset re-seeds a generic memory constitution on install

Refs #3272

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

try:
shutil.copy2(resolved, memory_path)
except OSError:
resolved_template = PresetResolver(project_path).resolve(
"constitution-template", "template"
)
except Exception:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated bug-fix Trigger the bug-fix agentic workflow

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

1 participant