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).
|
flipping `edit`→`send`; +2 regression tests in `TestAdvancePending` (114/114).
|
||||||
(fixed by @hooks, `f42a98b`, PR #651 — not yet merged)
|
(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]
|
## [2026-07-02]
|
||||||
|
|
||||||
Released as **2.6.1**. Rolls up the DPLAN-0226 / FPLAN-0289 / TDPLAN-0010 /
|
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:
|
Checks:
|
||||||
1. .aipass/aipass_local_prompt.md for template markers
|
1. .aipass/aipass_local_prompt.md for template markers
|
||||||
2. README.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().
|
AUDIT_SCOPE: branch_level — runs once per branch via check_branch().
|
||||||
ADVISORY: always passes so it never blocks commits or audits.
|
ADVISORY: always passes so it never blocks commits or audits.
|
||||||
@@ -43,9 +43,17 @@ _DEFINITIVE_MARKERS = [
|
|||||||
|
|
||||||
_SINGLE_CURLY_RE = re.compile(r"\{[^{}\n]+\}")
|
_SINGLE_CURLY_RE = re.compile(r"\{[^{}\n]+\}")
|
||||||
_DOUBLE_CURLY_RE = re.compile(r"\{\{[^}]*\}\}")
|
_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."""
|
"""Return list of matched template marker descriptions."""
|
||||||
found: list[str] = []
|
found: list[str] = []
|
||||||
content_upper = content.upper()
|
content_upper = content.upper()
|
||||||
@@ -54,8 +62,9 @@ def _find_markers(content: str, is_markdown: bool) -> list[str]:
|
|||||||
if marker.upper() in content_upper:
|
if marker.upper() in content_upper:
|
||||||
found.append(marker)
|
found.append(marker)
|
||||||
|
|
||||||
if is_markdown:
|
if check_single_curly:
|
||||||
stripped = _DOUBLE_CURLY_RE.sub("", content)
|
stripped = _strip_markdown_code(content)
|
||||||
|
stripped = _DOUBLE_CURLY_RE.sub("", stripped)
|
||||||
curly_matches = _SINGLE_CURLY_RE.findall(stripped)
|
curly_matches = _SINGLE_CURLY_RE.findall(stripped)
|
||||||
if curly_matches:
|
if curly_matches:
|
||||||
examples = curly_matches[:3]
|
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)
|
logger.info("Cannot read %s for template check", file_path)
|
||||||
return {"name": rel, "passed": True, "message": f"{rel} unreadable (skipped)"}
|
return {"name": rel, "passed": True, "message": f"{rel} unreadable (skipped)"}
|
||||||
|
|
||||||
is_md = file_path.suffix == ".md"
|
is_prompt = file_path.name == "aipass_local_prompt.md"
|
||||||
markers = _find_markers(content, is_md)
|
markers = _find_markers(content, check_single_curly=is_prompt)
|
||||||
|
|
||||||
if not markers:
|
if not markers:
|
||||||
return {"name": rel, "passed": True, "message": "no template 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 / ".aipass" / "aipass_local_prompt.md",
|
||||||
bp / "README.md",
|
bp / "README.md",
|
||||||
]
|
]
|
||||||
for trinity_file in sorted((bp / ".trinity").glob("*.json")) if (bp / ".trinity").is_dir() else []:
|
passport = bp / ".trinity" / "passport.json"
|
||||||
targets.append(trinity_file)
|
if passport.exists():
|
||||||
|
targets.append(passport)
|
||||||
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
checks.append(_check_file(target, bypass_rules))
|
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")]
|
trinity_checks = [c for c in result["checks"] if c["name"].endswith(".json")]
|
||||||
assert all(c["passed"] for c in trinity_checks)
|
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):
|
def test_standard_level_bypass(self, tmp_path):
|
||||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||||
check_branch,
|
check_branch,
|
||||||
@@ -291,6 +332,40 @@ class TestCheckBranch:
|
|||||||
assert result["passed"] is True
|
assert result["passed"] is True
|
||||||
assert result["advisory"] 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):
|
def test_curly_placeholders_in_prompt(self, tmp_path):
|
||||||
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
from aipass.seedgo.apps.handlers.aipass_standards.template_check import (
|
||||||
check_branch,
|
check_branch,
|
||||||
|
|||||||
Reference in New Issue
Block a user