Skip to content

Commit 0d0e848

Browse files
arun-guptaclaude
andcommitted
fix(claude): address review feedback on CLAUDE.md generation
- Introduce CONSTITUTION_REL_PATH constant in specify_cli and reuse it in ensure_constitution_from_template and ClaudeIntegration.ensure_claude_md so the path cannot drift between init scaffolding and integration setup. - Make ensure_claude_md a classmethod that uses cls.context_file instead of hardcoding "CLAUDE.md". - Expand CLAUDE.md body to list all core speckit workflow commands (constitution, specify, clarify, plan, tasks, analyze, checklist, implement) so Claude Code sees the full workflow. - Strengthen tests to assert every section header and every command line via shared EXPECTED_CLAUDE_MD_SECTIONS / _COMMANDS constants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 43836ef commit 0d0e848

3 files changed

Lines changed: 52 additions & 12 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ def _build_ai_assistant_help() -> str:
9797
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
9898
CLAUDE_NPM_LOCAL_PATH = Path.home() / ".claude" / "local" / "node_modules" / ".bin" / "claude"
9999

100+
# Relative path (from project root) to the authoritative constitution file.
101+
# Shared by init-time scaffolding and integration-specific context-file
102+
# generation so the two cannot drift.
103+
CONSTITUTION_REL_PATH = Path(".specify") / "memory" / "constitution.md"
104+
100105
BANNER = """
101106
███████╗██████╗ ███████╗ ██████╗██╗███████╗██╗ ██╗
102107
██╔════╝██╔══██╗██╔════╝██╔════╝██║██╔════╝╚██╗ ██╔╝
@@ -753,7 +758,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
753758

754759
def ensure_constitution_from_template(project_path: Path, tracker: StepTracker | None = None) -> None:
755760
"""Copy constitution template to memory if it doesn't exist (preserves existing constitution on reinitialization)."""
756-
memory_constitution = project_path / ".specify" / "memory" / "constitution.md"
761+
memory_constitution = project_path / CONSTITUTION_REL_PATH
757762
template_constitution = project_path / ".specify" / "templates" / "constitution-template.md"
758763

759764
# If constitution already exists in memory, preserve it

src/specify_cli/integrations/claude/__init__.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,34 +148,44 @@ def _inject_frontmatter_flag(content: str, key: str, value: str = "true") -> str
148148
out.append(line)
149149
return "".join(out)
150150

151-
@staticmethod
152-
def ensure_claude_md(project_root: Path) -> Path | None:
153-
"""Create a minimal root ``CLAUDE.md`` if missing.
151+
@classmethod
152+
def ensure_claude_md(cls, project_root: Path) -> Path | None:
153+
"""Create a minimal root context file (``CLAUDE.md``) if missing.
154154
155-
Claude Code expects ``CLAUDE.md`` at the project root; this file
156-
acts as a bridge to ``.specify/memory/constitution.md``.
155+
Claude Code expects ``context_file`` at the project root; this file
156+
acts as a bridge to the constitution at ``CONSTITUTION_REL_PATH``.
157157
Returns the path if created, ``None`` otherwise.
158158
"""
159-
constitution = project_root / ".specify" / "memory" / "constitution.md"
160-
claude_file = project_root / "CLAUDE.md"
161-
if claude_file.exists() or not constitution.exists():
159+
from specify_cli import CONSTITUTION_REL_PATH
160+
161+
if cls.context_file is None:
162+
return None
163+
164+
constitution = project_root / CONSTITUTION_REL_PATH
165+
context_file = project_root / cls.context_file
166+
if context_file.exists() or not constitution.exists():
162167
return None
163168

169+
constitution_rel = CONSTITUTION_REL_PATH.as_posix()
164170
content = (
165171
"## Claude's Role\n"
166-
"Read `.specify/memory/constitution.md` first. It is the authoritative source of truth for this project. "
172+
f"Read `{constitution_rel}` first. It is the authoritative source of truth for this project. "
167173
"Everything in it is non-negotiable.\n\n"
168174
"## SpecKit Commands\n"
175+
"- `/speckit.constitution` — establish or amend project principles\n"
169176
"- `/speckit.specify` — generate spec\n"
177+
"- `/speckit.clarify` — ask structured de-risking questions (before `/speckit.plan`)\n"
170178
"- `/speckit.plan` — generate plan\n"
171179
"- `/speckit.tasks` — generate task list\n"
180+
"- `/speckit.analyze` — cross-artifact consistency report (after `/speckit.tasks`)\n"
181+
"- `/speckit.checklist` — generate quality checklists\n"
172182
"- `/speckit.implement` — execute plan\n\n"
173183
"## On Ambiguity\n"
174184
"If a spec is missing, incomplete, or conflicts with the constitution — stop and ask. "
175185
"Do not infer. Do not proceed.\n\n"
176186
)
177-
claude_file.write_text(content, encoding="utf-8")
178-
return claude_file
187+
context_file.write_text(content, encoding="utf-8")
188+
return context_file
179189

180190
def setup(
181191
self,

tests/integrations/test_integration_claude.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,23 @@ def test_claude_preset_creates_new_skill_without_commands_dir(self, tmp_path):
286286
assert "speckit-research" in metadata.get("registered_skills", [])
287287

288288

289+
EXPECTED_CLAUDE_MD_COMMANDS = (
290+
"/speckit.constitution",
291+
"/speckit.specify",
292+
"/speckit.clarify",
293+
"/speckit.plan",
294+
"/speckit.tasks",
295+
"/speckit.analyze",
296+
"/speckit.checklist",
297+
"/speckit.implement",
298+
)
299+
EXPECTED_CLAUDE_MD_SECTIONS = (
300+
"## Claude's Role",
301+
"## SpecKit Commands",
302+
"## On Ambiguity",
303+
)
304+
305+
289306
class TestClaudeMdCreation:
290307
"""Verify that CLAUDE.md is created during setup when constitution exists."""
291308

@@ -303,6 +320,10 @@ def test_setup_creates_claude_md_when_constitution_exists(self, tmp_path):
303320
content = claude_md.read_text(encoding="utf-8")
304321
assert ".specify/memory/constitution.md" in content
305322
assert claude_md in created
323+
for section in EXPECTED_CLAUDE_MD_SECTIONS:
324+
assert section in content, f"missing section header: {section}"
325+
for command in EXPECTED_CLAUDE_MD_COMMANDS:
326+
assert f"`{command}`" in content, f"missing command: {command}"
306327

307328
def test_setup_skips_claude_md_when_constitution_missing(self, tmp_path):
308329
integration = get_integration("claude")
@@ -354,6 +375,10 @@ def test_init_cli_creates_claude_md(self, tmp_path):
354375
assert claude_md.exists()
355376
content = claude_md.read_text(encoding="utf-8")
356377
assert ".specify/memory/constitution.md" in content
378+
for section in EXPECTED_CLAUDE_MD_SECTIONS:
379+
assert section in content, f"missing section header: {section}"
380+
for command in EXPECTED_CLAUDE_MD_COMMANDS:
381+
assert f"`{command}`" in content, f"missing command: {command}"
357382

358383

359384
class TestClaudeArgumentHints:

0 commit comments

Comments
 (0)