Skip to content

Commit aa09272

Browse files
BenBtgCopilot
andcommitted
refactor(presets): unify constitution template materialization
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>
1 parent f954e30 commit aa09272

2 files changed

Lines changed: 47 additions & 38 deletions

File tree

src/specify_cli/commands/init.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import os
6-
import shutil
76
import sys
87
from pathlib import Path
98
from typing import Any
@@ -41,39 +40,31 @@ def ensure_constitution_from_template(
4140
constitution) can seed the memory file. When nothing overrides it, the
4241
resolver falls through to the core template.
4342
"""
44-
from ..presets import PresetResolver
43+
from ..presets import _materialize_constitution_template
4544

4645
memory_constitution = project_path / ".specify" / "memory" / "constitution.md"
47-
resolver = PresetResolver(project_path)
48-
layers = resolver.collect_all_layers("constitution-template", "template")
4946

5047
if memory_constitution.exists():
5148
if tracker:
5249
tracker.add("constitution", "Constitution setup")
5350
tracker.skip("constitution", "existing file preserved")
5451
return
5552

56-
if not layers:
57-
if tracker:
58-
tracker.add("constitution", "Constitution setup")
59-
tracker.error("constitution", "template not found")
60-
return
61-
6253
try:
63-
memory_constitution.parent.mkdir(parents=True, exist_ok=True)
64-
top_layer = layers[0]
65-
if top_layer["strategy"] == "replace":
66-
shutil.copy2(top_layer["path"], memory_constitution)
67-
else:
68-
composed_content = resolver.resolve_content(
69-
"constitution-template", "template"
70-
)
71-
if composed_content is None:
72-
raise FileNotFoundError("constitution template not found")
73-
memory_constitution.write_text(composed_content, encoding="utf-8")
54+
materialization = _materialize_constitution_template(
55+
project_path, memory_constitution
56+
)
57+
if materialization is None:
58+
if tracker:
59+
tracker.add("constitution", "Constitution setup")
60+
tracker.error("constitution", "template not found")
61+
return
7462
if tracker:
7563
tracker.add("constitution", "Constitution setup")
76-
tracker.complete("constitution", "copied from template")
64+
if materialization == "copied":
65+
tracker.complete("constitution", "copied from template")
66+
else:
67+
tracker.complete("constitution", "composed from template")
7768
else:
7869
console.print("[cyan]Initialized constitution from template[/cyan]")
7970
except Exception as e:

src/specify_cli/presets/__init__.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,35 @@ def _constitution_is_placeholder(content: str) -> bool:
4545
return any(token in content for token in _CONSTITUTION_PLACEHOLDER_TOKENS)
4646

4747

48+
def _materialize_constitution_template(
49+
project_root: Path,
50+
memory_constitution: Path,
51+
) -> str | None:
52+
"""Materialize constitution-template content into memory/constitution.md.
53+
54+
Returns:
55+
"copied" when the winning layer is ``replace`` and the source file is
56+
copied verbatim; "composed" when a composing strategy is materialized
57+
via ``resolve_content``; ``None`` when no constitution template resolves.
58+
"""
59+
resolver = PresetResolver(project_root)
60+
layers = resolver.collect_all_layers("constitution-template", "template")
61+
if not layers:
62+
return None
63+
64+
memory_constitution.parent.mkdir(parents=True, exist_ok=True)
65+
top_layer = layers[0]
66+
if top_layer["strategy"] == "replace":
67+
shutil.copy2(top_layer["path"], memory_constitution)
68+
return "copied"
69+
70+
composed_content = resolver.resolve_content("constitution-template", "template")
71+
if composed_content is None:
72+
return None
73+
memory_constitution.write_text(composed_content, encoding="utf-8")
74+
return "composed"
75+
76+
4877
def _substitute_core_template(
4978
body: str,
5079
cmd_name: str,
@@ -1664,23 +1693,12 @@ def _seed_constitution_from_preset(self, manifest: PresetManifest) -> None:
16641693
# Legitimately authored constitution; leave it untouched.
16651694
return
16661695

1667-
resolver = PresetResolver(self.project_root)
1668-
layers = resolver.collect_all_layers("constitution-template", "template")
1669-
if not layers:
1670-
return
1671-
16721696
try:
1673-
memory_constitution.parent.mkdir(parents=True, exist_ok=True)
1674-
top_layer = layers[0]
1675-
if top_layer["strategy"] == "replace":
1676-
shutil.copy2(top_layer["path"], memory_constitution)
1677-
else:
1678-
composed_content = resolver.resolve_content(
1679-
"constitution-template", "template"
1680-
)
1681-
if composed_content is None:
1682-
return
1683-
memory_constitution.write_text(composed_content, encoding="utf-8")
1697+
result = _materialize_constitution_template(
1698+
self.project_root, memory_constitution
1699+
)
1700+
if result is None:
1701+
return
16841702
except OSError as exc:
16851703
import warnings
16861704

0 commit comments

Comments
 (0)