fix(seedgo): template checker — stop false-flagging memory prose + README code
The advisory 'template' stale-checker matched marker strings anywhere in a file,
firing on documentation ABOUT templates rather than un-rendered stubs. Two root
causes fixed:
1. Scanned .trinity/*.json (all memory) — local.json/observations.json accumulate
marker mentions (seedgo's own note about the checker, prax's template_pusher
note). Now scans passport.json only, the sole spawn-templated trinity file.
2. Single-curly {…} regex ran on every .md, matching inline JSON/f-strings/code
paths in READMEs. Now single-curly detection runs on the branch prompt only
(README template has no single-curly placeholders) and strips fenced + inline
code first.
Definitive-marker detection unchanged — real stubs (cli/drone/prax prompts) still
flag. Verified live: seedgo 100%, drone/prax flag only the real prompt stub.
+4 tests (21/21). Dispatched to @seedgo (owner), verified independently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5b04c2125c
commit
26893fb66a
@@ -27,6 +27,23 @@ Post-2.6.1 cycle — **unreleased** (held for a later merge).
|
||||
flipping `edit`→`send`; +2 regression tests in `TestAdvancePending` (114/114).
|
||||
(fixed by @hooks, `f42a98b`, PR #651 — not yet merged)
|
||||
|
||||
- **`template` audit checker no longer false-flags memory prose or README code.**
|
||||
The advisory stale-template checker (`seedgo/.../template_check.py`) matched its
|
||||
marker strings anywhere in a file, so it fired on documentation *about* templates
|
||||
rather than un-rendered stubs. Two root causes, both fixed: (1) it globbed
|
||||
**`.trinity/*.json`** — scanning live memory (`local.json`, `observations.json`)
|
||||
that naturally accumulates marker mentions (e.g. seedgo's own note "Detects
|
||||
NEEDS CONFIGURATION", prax's note about `template_pusher` restoring
|
||||
`{{BRANCHNAME}}`); now scans **`passport.json` only**, the sole spawn-templated
|
||||
trinity file. (2) the single-curly `{…}` regex ran on every `.md` and matched
|
||||
inline JSON / f-strings / code paths in READMEs (`{"new": 3}`, `{e}`,
|
||||
`apps/plugins/{name}/`); now single-curly detection runs on the branch **prompt
|
||||
only** (the spawn README template has no single-curly placeholders) and strips
|
||||
fenced + inline code first. Definitive-marker detection unchanged, so real
|
||||
unconfigured stubs (cli/drone/prax prompts) still flag correctly. Verified live:
|
||||
seedgo `100%` (was flagged), drone/prax flag only the real prompt stub; +4 tests
|
||||
(21/21). (fixed by @seedgo, dispatched by @devpulse)
|
||||
|
||||
## [2026-07-02]
|
||||
|
||||
Released as **2.6.1**. Rolls up the DPLAN-0226 / FPLAN-0289 / TDPLAN-0010 /
|
||||
|
||||
@@ -16,7 +16,7 @@ branches know they have unconfigured stub files.
|
||||
Checks:
|
||||
1. .aipass/aipass_local_prompt.md for template markers
|
||||
2. README.md for template markers
|
||||
3. .trinity/*.json for template markers (definitive only — no curly brace regex)
|
||||
3. .trinity/passport.json for template markers (the only spawn-templated artifact)
|
||||
|
||||
AUDIT_SCOPE: branch_level — runs once per branch via check_branch().
|
||||
ADVISORY: always passes so it never blocks commits or audits.
|
||||
@@ -43,9 +43,17 @@ _DEFINITIVE_MARKERS = [
|
||||
|
||||
_SINGLE_CURLY_RE = re.compile(r"\{[^{}\n]+\}")
|
||||
_DOUBLE_CURLY_RE = re.compile(r"\{\{[^}]*\}\}")
|
||||
_FENCED_BLOCK_RE = re.compile(r"(?:```|~~~).*?(?:```|~~~)", re.DOTALL)
|
||||
_INLINE_CODE_RE = re.compile(r"`[^`\n]+`")
|
||||
|
||||
|
||||
def _find_markers(content: str, is_markdown: bool) -> list[str]:
|
||||
def _strip_markdown_code(content: str) -> str:
|
||||
"""Remove fenced code blocks and inline code spans."""
|
||||
content = _FENCED_BLOCK_RE.sub("", content)
|
||||
return _INLINE_CODE_RE.sub("", content)
|
||||
|
||||
|
||||
def _find_markers(content: str, check_single_curly: bool) -> list[str]:
|
||||
"""Return list of matched template marker descriptions."""
|
||||
found: list[str] = []
|
||||
content_upper = content.upper()
|
||||
@@ -54,8 +62,9 @@ def _find_markers(content: str, is_markdown: bool) -> list[str]:
|
||||
if marker.upper() in content_upper:
|
||||
found.append(marker)
|
||||
|
||||
if is_markdown:
|
||||
stripped = _DOUBLE_CURLY_RE.sub("", content)
|
||||
if check_single_curly:
|
||||
stripped = _strip_markdown_code(content)
|
||||
stripped = _DOUBLE_CURLY_RE.sub("", stripped)
|
||||
curly_matches = _SINGLE_CURLY_RE.findall(stripped)
|
||||
if curly_matches:
|
||||
examples = curly_matches[:3]
|
||||
@@ -80,8 +89,8 @@ def _check_file(file_path: Path, bypass_rules: list | None) -> Dict:
|
||||
logger.info("Cannot read %s for template check", file_path)
|
||||
return {"name": rel, "passed": True, "message": f"{rel} unreadable (skipped)"}
|
||||
|
||||
is_md = file_path.suffix == ".md"
|
||||
markers = _find_markers(content, is_md)
|
||||
is_prompt = file_path.name == "aipass_local_prompt.md"
|
||||
markers = _find_markers(content, check_single_curly=is_prompt)
|
||||
|
||||
if not markers:
|
||||
return {"name": rel, "passed": True, "message": "no template markers"}
|
||||
@@ -137,8 +146,9 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
bp / ".aipass" / "aipass_local_prompt.md",
|
||||
bp / "README.md",
|
||||
]
|
||||
for trinity_file in sorted((bp / ".trinity").glob("*.json")) if (bp / ".trinity").is_dir() else []:
|
||||
targets.append(trinity_file)
|
||||
passport = bp / ".trinity" / "passport.json"
|
||||
if passport.exists():
|
||||
targets.append(passport)
|
||||
|
||||
for target in targets:
|
||||
checks.append(_check_file(target, bypass_rules))
|
||||
|
||||
@@ -272,6 +272,47 @@ class TestCheckBranch:
|
||||
trinity_checks = [c for c in result["checks"] if c["name"].endswith(".json")]
|
||||
assert all(c["passed"] for c in trinity_checks)
|
||||
|
||||
def test_trinity_memory_prose_not_flagged(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
)
|
||||
|
||||
aipass_dir = tmp_path / ".aipass"
|
||||
aipass_dir.mkdir()
|
||||
(aipass_dir / "aipass_local_prompt.md").write_text("Real prompt.")
|
||||
(tmp_path / "README.md").write_text("# Branch\nReal docs.")
|
||||
trinity = tmp_path / ".trinity"
|
||||
trinity.mkdir()
|
||||
(trinity / "passport.json").write_text(json.dumps({"branch": "test"}))
|
||||
(trinity / "local.json").write_text(
|
||||
json.dumps({"key_learnings": [{"value": "Detects NEEDS CONFIGURATION + mustache + curly placeholders"}]})
|
||||
)
|
||||
(trinity / "observations.json").write_text(
|
||||
json.dumps({"observations": [{"note": "template_pusher restoring {{BRANCHNAME}}"}]})
|
||||
)
|
||||
|
||||
result = check_branch(str(tmp_path))
|
||||
assert result["score"] == 100
|
||||
|
||||
def test_trinity_passport_with_markers_flagged(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
)
|
||||
|
||||
aipass_dir = tmp_path / ".aipass"
|
||||
aipass_dir.mkdir()
|
||||
(aipass_dir / "aipass_local_prompt.md").write_text("Real prompt.")
|
||||
(tmp_path / "README.md").write_text("# Branch\nReal docs.")
|
||||
trinity = tmp_path / ".trinity"
|
||||
trinity.mkdir()
|
||||
(trinity / "passport.json").write_text(json.dumps({"branch": "{{BRANCHNAME}}", "role": "NEEDS CONFIGURATION"}))
|
||||
|
||||
result = check_branch(str(tmp_path))
|
||||
assert result["score"] < 100
|
||||
passport_check = next(c for c in result["checks"] if "passport" in c["name"])
|
||||
assert not passport_check["passed"]
|
||||
assert "template markers" in passport_check["message"]
|
||||
|
||||
def test_standard_level_bypass(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
@@ -291,6 +332,40 @@ class TestCheckBranch:
|
||||
assert result["passed"] is True
|
||||
assert result["advisory"] is True
|
||||
|
||||
def test_readme_code_braces_not_flagged(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
)
|
||||
|
||||
aipass_dir = tmp_path / ".aipass"
|
||||
aipass_dir.mkdir()
|
||||
(aipass_dir / "aipass_local_prompt.md").write_text("Real prompt.")
|
||||
(tmp_path / "README.md").write_text(
|
||||
"# Branch\n\nExample:\n```python\nwrite_section(data, {'new': 3, 'total': 5})\n```\n"
|
||||
"Use `apps/plugins/{name}/` for plugins.\n"
|
||||
)
|
||||
|
||||
result = check_branch(str(tmp_path))
|
||||
readme_check = next(c for c in result["checks"] if c["name"] == "README.md")
|
||||
assert readme_check["passed"]
|
||||
|
||||
def test_prompt_code_fence_braces_not_flagged(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
)
|
||||
|
||||
aipass_dir = tmp_path / ".aipass"
|
||||
aipass_dir.mkdir()
|
||||
(aipass_dir / "aipass_local_prompt.md").write_text(
|
||||
"Real configured prompt.\n\n```python\nprint(f'Error: {e}')\nresult = {k: v}\n```\n"
|
||||
"Also `{inline_code}` is fine.\n"
|
||||
)
|
||||
(tmp_path / "README.md").write_text("# Branch\nConfigured.")
|
||||
|
||||
result = check_branch(str(tmp_path))
|
||||
prompt_check = next(c for c in result["checks"] if "aipass_local_prompt" in c["name"])
|
||||
assert prompt_check["passed"]
|
||||
|
||||
def test_curly_placeholders_in_prompt(self, tmp_path):
|
||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||
check_branch,
|
||||
|
||||
Reference in New Issue
Block a user