fix: 채널 중복 완전 근절 - reconnect 시 이름 dedup + 자동 삭제 + project name 최소 5자

This commit is contained in:
2026-03-07 12:55:41 +09:00
parent aa9be854b4
commit 8b3d723650

69
bot.py
View File

@@ -81,12 +81,14 @@ class ApprovalView(discord.ui.View):
def detect_project_name(conv_dir: Path) -> str:
"""Extract project name from conversation artifacts.
Output: lowercase_with_underscores (e.g. 'gravity_control')
Returns: lowercase_with_underscores (e.g. 'gravity_control')
Uses FIRST successful extraction and caches it.
"""
short_id = conv_dir.name[:8]
def _sanitize(raw: str) -> str:
for suffix in ["Task Tracker", "— Task Tracker", "태스크", "구현 계획"]:
for suffix in ["Task Tracker", "— Task Tracker", "태스크", "구현 계획",
"Implementation Plan", "Walkthrough"]:
raw = raw.replace(suffix, "")
raw = raw.strip(" —-")
raw = re.sub(r'[^a-zA-Z0-9가-힣\s\-]', '', raw)
@@ -102,7 +104,8 @@ def detect_project_name(conv_dir: Path) -> str:
match = re.match(r'^#\s+(.+)', line)
if match:
name = _sanitize(match.group(1))
if name and name != "task":
# Require at least 5 chars to avoid short generic names
if name and name != "task" and len(name) >= 5:
return name
except (OSError, UnicodeDecodeError):
pass
@@ -161,20 +164,40 @@ class GravityBot(commands.Bot):
await self._reconnect_existing_channels()
async def _reconnect_existing_channels(self):
"""Scan existing Discord channels and map them to conversation IDs."""
"""Scan existing Discord channels and map them — MERGE same-name channels."""
if not self.session_category:
return
count = 0
# Group channels by normalized name
name_to_channel: dict[str, discord.TextChannel] = {}
duplicates: list[discord.TextChannel] = []
for ch in self.session_category.text_channels:
if ch.topic and "Antigravity Session:" in ch.topic:
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
if conv_id:
self.session_channels[conv_id] = ch
await self._recover_task_message(ch, conv_id)
count += 1
if ch.name in name_to_channel:
# DUPLICATE — mark for cleanup
duplicates.append(ch)
else:
name_to_channel[ch.name] = ch
logger.info(f"Reconnected to {count} existing channels")
# Map the primary channel for each name
count = 0
for ch in name_to_channel.values():
conv_id = ch.topic.replace("Antigravity Session:", "").strip()
if conv_id:
self.session_channels[conv_id] = ch
await self._recover_task_message(ch, conv_id)
count += 1
# Delete duplicate channels
for ch in duplicates:
try:
await ch.delete(reason="Duplicate channel cleanup")
logger.info(f"Deleted duplicate channel: #{ch.name}")
except (discord.Forbidden, discord.HTTPException) as e:
logger.warning(f"Failed to delete duplicate #{ch.name}: {e}")
logger.info(f"Reconnected to {count} channels, cleaned {len(duplicates)} duplicates")
async def _recover_task_message(
self, channel: discord.TextChannel, conversation_id: str
@@ -196,21 +219,25 @@ class GravityBot(commands.Bot):
async def _ensure_channel(
self, conversation_id: str, project_name: str
) -> discord.TextChannel:
"""Get or create a channel. Uses GLOBAL lock + OWN dict lookup (no cache issues)."""
# Fast path: already mapped
"""Get or create a channel. SINGLE channel per project name, guaranteed."""
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
target_name = channel_name.lower().replace(" ", "-")
# Fast path: this conv_id already mapped
if conversation_id in self.session_channels:
return self.session_channels[conversation_id]
ch = self.session_channels[conversation_id]
# Verify the channel name matches (project name might have changed)
if ch.name == target_name:
return ch
async with self._channel_create_lock:
# Double-check after lock
if conversation_id in self.session_channels:
return self.session_channels[conversation_id]
ch = self.session_channels[conversation_id]
if ch.name == target_name:
return ch
channel_name = f"{Config.CHANNEL_PREFIX}-{project_name}"
target_name = channel_name.lower().replace(" ", "-")
# Check OWN dict for a channel with the same NAME
# (different conv IDs can share one channel)
# Check ALL mapped channels for same name
for cid, ch in self.session_channels.items():
if ch.name == target_name:
self.session_channels[conversation_id] = ch
@@ -218,7 +245,7 @@ class GravityBot(commands.Bot):
logger.info(f"Reusing channel #{ch.name} for {conversation_id[:8]}")
return ch
# Create new channel (truly first time)
# Create new channel (truly no match anywhere)
try:
channel = await self.guild.create_text_channel(
name=channel_name,