Add gitignore-claude template and wire into registration flows
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Introduce scripts/project_rules/gitignore-claude.template with the fleet
pattern that tracks .claude/rules/ while ignoring machine-local Claude state.
ensure_gitignore_claude_rules.py applies it during statehub register,
register_project.sh, and update_agent_instruction_files fleet regen.
This commit is contained in:
tegwick 2026-07-08 15:06:18 +02:00
parent 0d2fca5711
commit 638cdc4ee2
7 changed files with 128 additions and 0 deletions

View file

@ -0,0 +1,63 @@
"""Ensure repos track .claude/rules/ while ignoring other Claude Code local state."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SNIPPET_PATH = ROOT / "scripts" / "project_rules" / "gitignore-claude.template"
MARKER = "# state-hub: track .claude/rules"
TRACK_RULE = "!.claude/rules/"
BLANKET_IGNORE_RE = re.compile(r"^\.claude/?\s*$", re.MULTILINE)
def load_snippet() -> str:
text = SNIPPET_PATH.read_text(encoding="utf-8").strip()
return f"{MARKER}\n{text}\n"
def snippet_present(text: str) -> bool:
return TRACK_RULE in text
def ensure_claude_gitignore(gitignore_path: Path) -> bool:
"""Return True when the file was created or modified."""
snippet = load_snippet()
if gitignore_path.exists():
text = gitignore_path.read_text(encoding="utf-8")
if snippet_present(text):
return False
if BLANKET_IGNORE_RE.search(text):
updated = BLANKET_IGNORE_RE.sub(snippet.rstrip(), text, count=1)
if updated != text:
gitignore_path.write_text(_ensure_trailing_newline(updated), encoding="utf-8")
return True
separator = "" if text.endswith("\n") or not text else "\n"
gitignore_path.write_text(f"{text}{separator}\n{snippet}", encoding="utf-8")
return True
gitignore_path.write_text(snippet, encoding="utf-8")
return True
def _ensure_trailing_newline(text: str) -> str:
return text if text.endswith("\n") else f"{text}\n"
def main() -> int:
import argparse
parser = argparse.ArgumentParser(description="Ensure .gitignore tracks .claude/rules/.")
parser.add_argument("path", type=Path, help="Repo root or .gitignore file path")
args = parser.parse_args()
target = args.path
gitignore = target if target.name == ".gitignore" else target / ".gitignore"
changed = ensure_claude_gitignore(gitignore)
print(f"{'updated' if changed else 'ok'}: {gitignore}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,4 @@
# Claude Code local state (track shared rules; ignore machine-specific files)
.claude/*
!.claude/rules/
!.claude/rules/*.md

View file

@ -147,6 +147,9 @@ if [[ "$ADDITIONAL" != "true" ]]; then
echo "==> SCOPE.md already exists — skipping."
fi
echo "==> Ensuring .gitignore tracks .claude/rules/ ..."
python3 "$SCRIPT_DIR/ensure_gitignore_claude_rules.py" "$PROJECT_PATH"
if [[ "$CODEX_MODE" == "true" ]]; then
# ── 5b: Codex — write AGENTS.md from HTTP-API template ────────────────
AGENTS_MD="$PROJECT_PATH/AGENTS.md"

View file

@ -12,6 +12,13 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TEMPLATE_DIR = ROOT / "scripts" / "project_rules"
import sys
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.ensure_gitignore_claude_rules import ensure_claude_gitignore # noqa: E402
API_BASE = "http://127.0.0.1:8000"
HOME_ROOT = Path("/home/worsch")
WP_FILE_RE = re.compile(r"^([A-Za-z][A-Za-z0-9-]*-WP)-\d+", re.IGNORECASE)
@ -189,6 +196,8 @@ def update_repo(
if not scope_path.exists():
scope_path.write_text(render(scope_template, values), encoding="utf-8")
ensure_claude_gitignore(path / ".gitignore")
rules_dir = path / ".claude" / "rules"
rules_dir.mkdir(parents=True, exist_ok=True)
for name, template in rule_templates.items():