Skip to content

Commit f065e27

Browse files
github-actions[bot]CopilotBenBtg
authored
test: cover preset constitution seeding through init CLI (#3297)
* Fix preset-constitution-not-installed: use PresetResolver in constitution setup Apply the remediation from the bug assessment on issue #3272. Changes: 1. Modify ensure_constitution_from_template (init.py) to resolve the constitution-template through the preset priority stack via PresetResolver, instead of hardcoding the core template path. This ensures a preset's replacement constitution-template is used when seeding .specify/memory/constitution.md. 2. Reorder init flow: move ensure_constitution_from_template to after the preset installation block so that 'specify init --preset' seeds the memory file from the already-resolved template stack, not from the generic template that existed before the preset arrived. 3. Add _maybe_reseed_constitution to PresetManager (presets/__init__.py): a post-install hook that re-seeds .specify/memory/constitution.md from the preset's constitution-template during 'specify preset add' on an existing project, but only when the memory file still contains generic placeholder tokens ([PROJECT_NAME] or [PRINCIPLE_1_NAME]). Legitimately authored constitutions (no placeholder tokens) are never overwritten. 4. Add regression tests covering both code paths (TestConstitutionReseedOnPresetInstall and TestEnsureConstitutionFromTemplate in tests/test_presets.py). Refs #3272 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden preset constitution resolution Use manifest-aware composed content, atomic safe writes, and conservative generic-template matching for constitution seeding and re-seeding. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49891a32-bec4-462c-a7f2-6d6ec4eefcdb * Limit preset CLI change to regression test Remove accidental whole-file Ruff formatting introduced during conflict resolution so the PR contains only the intended end-to-end test. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49891a32-bec4-462c-a7f2-6d6ec4eefcdb * Make preset init test depend on init ordering Disable preset-install lifecycle seeding in the regression test so it fails unless init materializes the constitution after registering the preset. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49891a32-bec4-462c-a7f2-6d6ec4eefcdb --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Buttigieg <70525+BenBtg@users.noreply.github.com>
1 parent 2fb18c7 commit f065e27

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

tests/integrations/test_cli.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,66 @@ def fail_install(self, path, version):
224224
assert "Continuing without the optional preset" in normalized
225225
assert "Project ready" in normalized
226226

227+
def test_init_with_local_preset_seeds_manifest_constitution(
228+
self, tmp_path, monkeypatch
229+
):
230+
from typer.testing import CliRunner
231+
from specify_cli import app
232+
from specify_cli.presets import PresetManager
233+
234+
monkeypatch.setattr(
235+
PresetManager,
236+
"_seed_constitution_from_preset",
237+
lambda *_args, **_kwargs: None,
238+
)
239+
240+
preset_dir = tmp_path / "constitution-preset"
241+
(preset_dir / "organization").mkdir(parents=True)
242+
preset_content = "# Ratified Organization Constitution\n"
243+
(preset_dir / "organization" / "ratified.md").write_text(preset_content)
244+
(preset_dir / "preset.yml").write_text(
245+
yaml.safe_dump({
246+
"schema_version": "1.0",
247+
"preset": {
248+
"id": "constitution-preset",
249+
"name": "Constitution Preset",
250+
"version": "1.0.0",
251+
"description": "Provides a ratified constitution",
252+
},
253+
"requires": {"speckit_version": ">=0.1.0"},
254+
"provides": {
255+
"templates": [{
256+
"type": "template",
257+
"name": "constitution-template",
258+
"file": "organization/ratified.md",
259+
"strategy": "replace",
260+
}]
261+
},
262+
})
263+
)
264+
project = tmp_path / "init-with-preset"
265+
266+
result = CliRunner().invoke(
267+
app,
268+
[
269+
"init",
270+
str(project),
271+
"--integration",
272+
"copilot",
273+
"--script",
274+
"sh",
275+
"--ignore-agent-tools",
276+
"--preset",
277+
str(preset_dir),
278+
],
279+
catch_exceptions=False,
280+
)
281+
282+
assert result.exit_code == 0, result.output
283+
assert (
284+
project / ".specify" / "memory" / "constitution.md"
285+
).read_text() == preset_content
286+
227287
def test_integration_claude_here_preserves_preexisting_commands(self, tmp_path):
228288
from typer.testing import CliRunner
229289
from specify_cli import app

0 commit comments

Comments
 (0)