Skip to content

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

Open
BenBtg wants to merge 5 commits into
mainfrom
benbtg-fix-3272-preset-constitution-template-se
Open

fix(presets): seed constitution from preset constitution-template (#3272)#3276
BenBtg wants to merge 5 commits 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.

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 thread src/specify_cli/commands/init.py Outdated
Comment thread src/specify_cli/presets/__init__.py Outdated

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: 0 new
  • Review effort level: Low

@BenBtg

BenBtg commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review feedback in f954e30.\n\n- Fixed constitution seeding to handle composing strategies (//) in both paths ( and ): we now materialize composed content via whenever the winning layer is non-.\n- Added regression tests that verify wrap composition is materialized (no unresolved ) in both seeding paths.\n- Added a drift-guard test that pins to placeholders present in the core , per gglachant’s maintainability suggestion.\n\nPosted on behalf of @BenBtg by GitHub Copilot (model: gpt-5.3-codex).

@BenBtg

BenBtg commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Correction to my previous comment (shell quoting ate formatting). Addressed review feedback in f954e30.

  • Fixed constitution seeding to handle composing strategies (prepend/append/wrap) in both paths: ensure_constitution_from_template and PresetManager._seed_constitution_from_preset now materialize composed content via PresetResolver.resolve_content() whenever the winning layer is non-replace.
  • Added regression tests that verify wrap composition is materialized with no unresolved {CORE_TEMPLATE} in both seeding paths.
  • Added a drift-guard test that pins _CONSTITUTION_PLACEHOLDER_TOKENS to placeholders present in the core templates/constitution-template.md, per gglachant’s maintainability suggestion.

Posted on behalf of @BenBtg by GitHub Copilot (model: gpt-5.3-codex).

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: 3
  • Review effort level: Low

Comment thread src/specify_cli/commands/init.py
Comment thread src/specify_cli/commands/init.py Outdated
Comment thread src/specify_cli/presets/__init__.py Outdated
@BenBtg

BenBtg commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest Copilot feedback in aa09272.

  • Moved resolver/layer collection behind the existing-memory fast path in ensure_constitution_from_template, so we avoid unnecessary I/O when memory already exists.
  • Updated tracker output to distinguish copied vs composed constitution materialization.
  • Deduplicated constitution materialization logic used by both init seeding and preset install re-seeding into a shared helper (_materialize_constitution_template) to keep behavior in sync.

Posted on behalf of @BenBtg by GitHub Copilot (model: gpt-5.3-codex).

BenBtg and others added 4 commits July 13, 2026 16:41
)

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>
Take on review feedback from Copilot and gglachant:
- constitution seeding previously copied the top layer file path verbatim
  even when the winning layer used a composing strategy
  (prepend/append/wrap), which could leave {CORE_TEMPLATE} unresolved.
- both seeding paths now inspect resolver layers and only copy verbatim for
  replace; non-replace strategies materialize composed content via
  PresetResolver.resolve_content().
- add regression tests for wrap strategy composition in both
  PresetManager seeding and ensure_constitution_from_template.
- add a drift-guard test pinning _CONSTITUTION_PLACEHOLDER_TOKENS to the
  placeholders in templates/constitution-template.md.

Assisted-by: GitHub Copilot (model: GPT-5.3-Codex, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address latest Copilot feedback on the constitution seeding path:
- moved resolver/layer I/O behind the existing-memory fast path in init
- corrected tracker output for composed materialization
- deduplicated materialization logic shared by init and preset install seeding
  into presets._materialize_constitution_template()

Behavior is unchanged for replace strategies (copy verbatim) and remains
composed for prepend/append/wrap via resolve_content().

Assisted-by: GitHub Copilot (model: GPT-5.3-Codex, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The constitution materialization refactor removed the module import, but init
still uses shutil.rmtree when cleaning up a failed new-project initialization.
Restore the import so the required ruff check passes.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 13, 2026 15:46
@BenBtg BenBtg force-pushed the benbtg-fix-3272-preset-constitution-template-se branch from aa09272 to e03fb69 Compare July 13, 2026 15:46
@BenBtg BenBtg marked this pull request as ready for review July 13, 2026 15:51
@BenBtg BenBtg requested a review from mnriem as a code owner July 13, 2026 15:51
@BenBtg

BenBtg commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Resynced this PR with current main and marked it ready for review.

  • Rebased cleanly with no conflicts; branch is now 0 commits behind main.
  • Restored the required shutil import in init.py (commit e03fb69), fixing the prior ruff failure.
  • Local validation: ruff passed; 4,040 tests passed and 4 skipped.
  • All GitHub checks are now passing across lint, CodeQL, and the Python 3.13/3.14 OS matrix.

Posted on behalf of @BenBtg by GitHub Copilot (model: gpt-5.6-sol).

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: 4
  • Review effort level: Medium

Comment thread src/specify_cli/presets/__init__.py Outdated
Comment thread src/specify_cli/presets/__init__.py Outdated
Comment thread src/specify_cli/presets/__init__.py Outdated
Comment thread src/specify_cli/presets/__init__.py Outdated
Address the outstanding review batch for preset constitution seeding:
- use checked atomic writes and reject symlinked memory paths
- replace placeholder heuristics with hash/source provenance
- rematerialize unchanged generated constitutions by resolver priority
- preserve authored or edited constitutions, including placeholder mentions
- warn non-fatally when post-install materialization cannot complete
- retain exact core-template comparison for legacy projects without provenance

Add focused provenance, priority, symlink, and failure-path coverage, and
update integration inventories for the generated provenance sidecar.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b2c095d-b45c-4d52-8d56-bd6121d96ab6
Copilot AI review requested due to automatic review settings July 13, 2026 16:56
@BenBtg

BenBtg commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the outstanding review batch in commit 116ab2c.

  • Routed constitution and provenance writes through checked atomic helpers and rejected symlinked parents and destinations.
  • Replaced placeholder substring heuristics with hash/source provenance, retaining exact core-template comparison for legacy projects.
  • Made unchanged generated constitutions follow resolver priority while preserving edited or authored files.
  • Made expected post-install materialization failures non-fatal warnings.
  • Confirmed the earlier composition, fast-path, tracker, and shared-helper feedback remains addressed.

Validation: ruff passed; 343 focused preset tests passed; full suite passed with 4,045 tests and 4 skips.

Posted on behalf of @BenBtg by GitHub Copilot (model: gpt-5.6-sol).

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: 10/10 changed files
  • Comments generated: 2
  • Review effort level: Medium

# 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.
self._seed_constitution_from_preset(manifest)
Comment on lines +1711 to +1712
# already-authored constitution by only replacing a file whose recorded
# hash (or exact legacy core-template content) proves it was generated.
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