1
0

[claude] Refactor thinking.py::_maybe_file_issues() into focused helpers (#1170) (#1173)

Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
This commit is contained in:
2026-03-23 20:47:06 +00:00
committed by rockachopa
parent ff921da547
commit d796fe7c53

View File

@@ -692,25 +692,48 @@ class ThinkingEngine:
file paths actually exist on disk, preventing phantom-bug reports.
"""
try:
recent = self._get_recent_thoughts_for_issues()
if recent is None:
return
classify_prompt = self._build_issue_classify_prompt(recent)
raw = await self._call_agent(classify_prompt)
items = self._parse_issue_items(raw)
if items is None:
return
from timmy.mcp_tools import create_gitea_issue_via_mcp
for item in items[:2]: # Safety cap
await self._file_single_issue(item, create_gitea_issue_via_mcp)
except Exception as exc:
logger.debug("Thought issue filing skipped: %s", exc)
def _get_recent_thoughts_for_issues(self):
"""Return recent thoughts if conditions for filing issues are met, else None."""
interval = settings.thinking_issue_every
if interval <= 0:
return
return None
count = self.count_thoughts()
if count == 0 or count % interval != 0:
return
return None
# Check Gitea availability before spending LLM tokens
if not settings.gitea_enabled or not settings.gitea_token:
return
return None
recent = self.get_recent_thoughts(limit=interval)
if len(recent) < interval:
return
return None
return recent
@staticmethod
def _build_issue_classify_prompt(recent) -> str:
"""Build the LLM prompt that extracts actionable issues from recent thoughts."""
thought_text = "\n".join(f"- [{t.seed_type}] {t.content}" for t in reversed(recent))
classify_prompt = (
return (
"You are reviewing your own recent thoughts for actionable items.\n"
"Extract 0-2 items that are CONCRETE bugs, broken features, stale "
"state, or clear improvement opportunities in your own codebase.\n\n"
@@ -735,48 +758,46 @@ class ThinkingEngine:
f"Recent thoughts:\n{thought_text}\n\nJSON array:"
)
raw = await self._call_agent(classify_prompt)
if not raw or not raw.strip():
return
@staticmethod
def _parse_issue_items(raw: str):
"""Strip markdown fences and parse JSON issue list; return None on failure."""
import json
# Strip markdown code fences if present
if not raw or not raw.strip():
return None
cleaned = raw.strip()
if cleaned.startswith("```"):
cleaned = cleaned.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
items = json.loads(cleaned)
if not isinstance(items, list) or not items:
return
return None
from timmy.mcp_tools import create_gitea_issue_via_mcp
return items
for item in items[:2]: # Safety cap
async def _file_single_issue(self, item: dict, create_fn) -> None:
"""Validate one issue dict and create it via *create_fn* if it passes checks."""
if not isinstance(item, dict):
continue
return
title = item.get("title", "").strip()
body = item.get("body", "").strip()
category = item.get("category", "suggestion").strip()
if not title or len(title) < 10:
continue
return
# 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
return
label = category if category in ("bug", "feature") else ""
result = await create_gitea_issue_via_mcp(title=title, body=body, labels=label)
result = await create_fn(title=title, body=body, labels=label)
logger.info("Thought→Issue: %s%s", title[:60], result[:80])
except Exception as exc:
logger.debug("Thought issue filing skipped: %s", exc)
# ── System snapshot helpers ────────────────────────────────────────────
def _snap_thought_count(self, now: datetime) -> str | None: