[loop-cycle-65] fix: validate file paths before filing thinking-engine issues (#327) (#329)
All checks were successful
Tests / lint (push) Successful in 3s
Tests / test (push) Successful in 1m9s

This commit was merged in pull request #329.
This commit is contained in:
2026-03-18 20:07:19 -04:00
parent 8ef0ad1778
commit 844923b16b
2 changed files with 93 additions and 1 deletions

View File

@@ -628,6 +628,35 @@ class ThinkingEngine:
except Exception as exc:
logger.warning("Memory status check failed: %s", exc)
@staticmethod
def _references_real_files(text: str) -> bool:
"""Check that all source-file paths mentioned in *text* actually exist.
Extracts paths that look like Python/config source references
(e.g. ``src/timmy/session.py``, ``config/foo.yaml``) and verifies
each one on disk relative to the project root. Returns ``True``
only when **every** referenced path resolves to a real file — or
when no paths are referenced at all (pure prose is fine).
"""
# Match paths like src/thing.py swarm/init.py config/x.yaml
# Requires at least one slash and a file extension.
path_pattern = re.compile(
r"(?<![/\w])" # not preceded by path chars (avoid partial matches)
r"((?:src|tests|config|scripts|data|swarm|timmy)"
r"(?:/[\w./-]+\.(?:py|yaml|yml|json|toml|md|txt|cfg|ini)))"
)
paths = path_pattern.findall(text)
if not paths:
return True # No file refs → nothing to validate
# Project root: two levels up from this file (src/timmy/thinking.py)
project_root = Path(__file__).resolve().parent.parent.parent
for p in paths:
if not (project_root / p).is_file():
logger.info("Phantom file reference blocked: %s (not in %s)", p, project_root)
return False
return True
async def _maybe_file_issues(self) -> None:
"""Every N thoughts, classify recent thoughts and file Gitea issues.
@@ -639,6 +668,9 @@ class ThinkingEngine:
- Gitea is enabled and configured
- Thought count is divisible by thinking_issue_every
- LLM extracts at least one actionable item
Safety: every generated issue is validated to ensure referenced
file paths actually exist on disk, preventing phantom-bug reports.
"""
try:
interval = settings.thinking_issue_every
@@ -666,7 +698,10 @@ class ThinkingEngine:
"Rules:\n"
"- Only include things that could become a real code fix or feature\n"
"- Skip vague reflections, philosophical musings, or repeated themes\n"
"- Category must be one of: bug, feature, suggestion, maintenance\n\n"
"- Category must be one of: bug, feature, suggestion, maintenance\n"
"- ONLY reference files that you are CERTAIN exist in the project\n"
"- Do NOT invent or guess file paths — if unsure, describe the "
"area of concern without naming specific files\n\n"
"For each item, write an ENGINEER-QUALITY issue:\n"
'- "title": A clear, specific title (e.g. "[Memory] MEMORY.md timestamp not updating")\n'
'- "body": A detailed body with these sections:\n'
@@ -707,6 +742,15 @@ class ThinkingEngine:
if not title or len(title) < 10:
continue
# Validate all referenced file paths exist on disk
combined = f"{title}\n{body}"
if not self._references_real_files(combined):
logger.info(
"Skipped phantom issue: %s (references non-existent files)",
title[:60],
)
continue
label = category if category in ("bug", "feature") else ""
result = await create_gitea_issue_via_mcp(title=title, body=body, labels=label)
logger.info("Thought→Issue: %s%s", title[:60], result[:80])

View File

@@ -1140,3 +1140,51 @@ def test_maybe_check_memory_graceful_on_error(tmp_path):
mock_settings.thinking_memory_check_every = 50
# Should not raise
engine._maybe_check_memory()
# ---------------------------------------------------------------------------
# Phantom file validation (_references_real_files)
# ---------------------------------------------------------------------------
def test_references_real_files_passes_existing_file(tmp_path):
"""Existing source files should pass validation."""
from timmy.thinking import ThinkingEngine
# src/timmy/thinking.py definitely exists in the project
text = "The bug is in src/timmy/thinking.py where the loop crashes."
assert ThinkingEngine._references_real_files(text) is True
def test_references_real_files_blocks_phantom_file(tmp_path):
"""Non-existent files should be blocked."""
from timmy.thinking import ThinkingEngine
# A completely fabricated module path
text = "The bug is in src/timmy/quantum_brain.py where sessions aren't tracked."
assert ThinkingEngine._references_real_files(text) is False
def test_references_real_files_blocks_phantom_swarm(tmp_path):
"""Non-existent swarm files should be blocked."""
from timmy.thinking import ThinkingEngine
text = "swarm/initialization.py needs to be fixed for proper startup."
assert ThinkingEngine._references_real_files(text) is False
def test_references_real_files_allows_no_paths(tmp_path):
"""Text with no file references should pass (pure prose is fine)."""
from timmy.thinking import ThinkingEngine
text = "The memory system should persist across restarts."
assert ThinkingEngine._references_real_files(text) is True
def test_references_real_files_blocks_mixed(tmp_path):
"""If any referenced file is phantom, the whole text fails."""
from timmy.thinking import ThinkingEngine
# Mix of real and fake files — should fail because of the fake one
text = "Fix src/timmy/thinking.py and also src/timmy/nonexistent_module.py for the memory leak."
assert ThinkingEngine._references_real_files(text) is False