diff --git a/.clinerules b/.clinerules index da8e3fb..e798975 100644 --- a/.clinerules +++ b/.clinerules @@ -80,4 +80,10 @@ Bot: - Avoid long explanations unless asked # === GOAL === -Act like a mentor helping users complete an AOSSIE template repo step-by-step. \ No newline at end of file +Act like a mentor helping users complete an AOSSIE template repo step-by-step. + +# === CLARIFYING FLOW FOR OOD/UNSUPPORTED QUERIES === +If the user's question does not match setup, README, contribute, or error topics: +- Acknowledge that the query does not directly match the standard template tasks. +- Ask a friendly, concise clarifying question to guide them back to one of the supported tasks (setup, README, contributing, or error debugging). +- Under no circumstances should you generate answers outside these core categories. \ No newline at end of file diff --git a/bot.py b/bot.py index c258633..a0453b8 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,5 @@ import os +import re import json import logging import discord @@ -163,6 +164,27 @@ async def _get_or_create_thread(message: discord.Message, channel: discord.TextC return None +def is_query_covered(query: str) -> bool: + """Check if the query contains keywords covered in .clinerules using word boundaries.""" + q = query.lower() + + # Predefined keyword maps based on .clinerules + categories = { + "setup": ["setup", "install", "run", "build", "clone", "docker", "env", "start", "dev server", "npm run dev"], + "readme": ["readme", "read me", "documentation", "project name", "description", "user flow", "feature"], + "contribute": ["contribute", "contributor", "fork", "pr", "pull request", "issue", "branch", "git", "onboarding"], + "error": ["error", "exception", "bug", "fail", "crash", "issue", "logs", "broken", "debug", "not working"] + } + + for cat, keywords in categories.items(): + for kw in keywords: + # Use raw pattern and re.escape for safety, matching word boundaries for the keyword/phrase + pattern = r'\b' + re.escape(kw) + r'\b' + if re.search(pattern, q): + return True + return False + + async def process_message(message: discord.Message): """Process a single message: new messages in the main channel spawn a thread, messages in existing threads continue the conversation there.""" @@ -170,9 +192,12 @@ async def process_message(message: discord.Message): return is_in_thread = isinstance(message.channel, discord.Thread) - is_in_configured_channel = message.channel.id == DISCORD_CHANNEL_ID_INT + is_in_configured_channel = ( + (message.channel.parent_id if is_in_thread else message.channel.id) + == DISCORD_CHANNEL_ID_INT + ) - if not is_in_thread and not is_in_configured_channel: + if not is_in_configured_channel: return author = message.author @@ -212,6 +237,27 @@ async def process_message(message: discord.Message): else: full_prompt = message.content + # Check if the query has sufficient information/context based on .clinerules + if not is_query_covered(message.content): + # Pass conversation context explicitly to the LLM so it has thread history for the clarifying question + history_str = f"Previous conversation history:\n{conversation_context}\n\n" if conversation_context else "" + full_prompt = ( + f"{history_str}" + f"The user is asking: '{message.content}'. " + f"This query is not covered by the standard guidelines in .clinerules. " + f"Generate a polite response asking the user to clarify if they need help with: " + f"1. Setting up the project template\n" + f"2. Writing or updating the README\n" + f"3. Contributing to the repository\n" + f"4. Debugging an error\n" + f"Keep the response short, friendly, and under 5 lines." + ) + await _log_gap( + message.content, + "insufficient_info", + thread_id=thread.id, + ) + response_text, used_fallback = await generate_ollama_response(full_prompt, skill_context) if used_fallback or not skill_context: