feat: auto-resolve encounters on clean Bash runs

When an active encounter exists and the next Bash tool call produces
output with no matching error patterns, the monster is automatically
defeated and XP is awarded — no manual /buddymon fight needed.

/buddymon fight is kept as a manual fallback for fixes that happen
outside Bash (config edits, etc.).
This commit is contained in:
pyr0ball 2026-04-01 15:15:02 -07:00
parent f3d1f45253
commit 46734e79c8
2 changed files with 63 additions and 7 deletions

View file

@ -135,6 +135,50 @@ def match_bug_monster(output_text: str, catalog: dict) -> dict | None:
return None
def encounter_still_present(encounter: dict, output_text: str, catalog: dict) -> bool:
"""Return True if the active encounter's error patterns still appear in output."""
monster_id = encounter.get("id")
monster = catalog.get("bug_monsters", {}).get(monster_id)
if not monster or not output_text:
return False
sample = output_text[:4000]
return any(
re.search(pat, sample, re.IGNORECASE)
for pat in monster.get("error_patterns", [])
)
def auto_resolve_encounter(encounter: dict, buddy_id: str | None) -> tuple[int, str]:
"""Defeat encounter automatically, award XP, clear state. Returns (xp, message)."""
from datetime import datetime, timezone
enc_file = BUDDYMON_DIR / "encounters.json"
active_file = BUDDYMON_DIR / "active.json"
roster_file = BUDDYMON_DIR / "roster.json"
xp = encounter.get("xp_reward", 50)
active = load_json(active_file)
active["session_xp"] = active.get("session_xp", 0) + xp
save_json(active_file, active)
if buddy_id:
roster = load_json(roster_file)
if buddy_id in roster.get("owned", {}):
roster["owned"][buddy_id]["xp"] = roster["owned"][buddy_id].get("xp", 0) + xp
save_json(roster_file, roster)
data = load_json(enc_file)
resolved = dict(encounter)
resolved["outcome"] = "defeated"
resolved["timestamp"] = datetime.now(timezone.utc).isoformat()
data.setdefault("history", []).append(resolved)
data["active_encounter"] = None
save_json(enc_file, data)
return xp, resolved["display"]
def compute_strength(monster: dict, elapsed_minutes: float) -> int:
"""Scale monster strength based on how long the error has persisted."""
base = monster.get("base_strength", 50)
@ -175,7 +219,7 @@ def format_encounter_message(monster: dict, strength: int, buddy_display: str) -
" `[CATCH]` Weaken it first (write a test, isolate repro, add comment) → attempt catch",
" `[FLEE]` Ignore → monster grows stronger",
"",
" Use `/buddymon-fight` or `/buddymon-catch` to engage.",
" Use `/buddymon fight` or `/buddymon catch` to weaken + catch it.",
]
return "\n".join(lines)
@ -232,7 +276,7 @@ def main():
messages = []
# ── Bash tool: error detection + commit tracking ───────────────────────
# ── Bash tool: error detection + auto-resolution + commit tracking ───────
if tool_name == "Bash":
output = ""
if isinstance(tool_response, dict):
@ -240,13 +284,22 @@ def main():
elif isinstance(tool_response, str):
output = tool_response
# Don't spawn new encounter if one is already active
existing = get_active_encounter()
if not existing and output:
if existing:
# Auto-resolve if the monster's patterns no longer appear in output
if output and not encounter_still_present(existing, output, catalog):
xp, display = auto_resolve_encounter(existing, buddy_id)
messages.append(
f"\n⚔️ **{buddy_display} defeated {display}!** (auto-resolved)\n"
f" +{xp} XP\n"
)
# else: monster persists, no message — don't spam every tool call
elif output:
# No active encounter — check for a new one
monster = match_bug_monster(output, catalog)
if monster:
# 70% chance to trigger (avoid every minor warning spawning)
# 70% chance to trigger (avoids every minor warning spawning)
if random.random() < 0.70:
strength = compute_strength(monster, elapsed_minutes=0)
encounter = {

View file

@ -127,9 +127,12 @@ Show challenge proposal with Accept / Decline / Reroll.
## `fight` — Fight Encounter
Read `encounters.json``active_encounter`. If none: "No active encounter."
**Note:** Encounters auto-resolve when a clean Bash run (no matching error patterns) is detected.
Use `/buddymon fight` when the error was fixed outside Bash (e.g., in a config file) or to manually confirm a fix.
Show encounter state. Confirm the user has actually fixed the bug.
Read `encounters.json``active_encounter`. If none: "No active encounter — it may have already been auto-resolved."
Show encounter state. Confirm the user has fixed the bug.
On confirm:
```python