Skip to content

Commit cfbb830

Browse files
authored
Merge pull request #20 from sam43/feature-code-summarizer-impl
Feature code summarizer impl
2 parents 4b6ffca + 1da6e4f commit cfbb830

File tree

7 files changed

+851
-4
lines changed

7 files changed

+851
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ __pycache__/
66
.pytest_cache/
77
.zencoder/
88
core/__pycache__/
9+
reports/
910
node_modules/
1011
codechat/tree-sitter-c
1112
codechat/tree-sitter-swift

core/mode_manager.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""
2+
Mode management and permission system for automatic code editing (build mode).
3+
Follows clean architecture and SOLID principles.
4+
"""
5+
from enum import Enum, auto
6+
from typing import Dict, List, Optional, Tuple
7+
from pathlib import Path
8+
import difflib
9+
import tempfile
10+
import shutil
11+
12+
class OperationMode(Enum):
13+
ASK = "ask"
14+
BUILD = "build"
15+
16+
class PermissionLevel(Enum):
17+
NONE = auto()
18+
ONCE = auto()
19+
ALL = auto()
20+
GLOBAL = auto()
21+
22+
class FileChange:
23+
def __init__(self, file_path: str, original_content: str, new_content: str, operation: str):
24+
self.file_path = file_path
25+
self.original_content = original_content
26+
self.new_content = new_content
27+
self.operation = operation # 'edit', 'create', 'delete'
28+
self.applied = False
29+
self.backup_path: Optional[str] = None
30+
31+
def get_diff(self) -> str:
32+
"""Return unified diff preview."""
33+
orig = self.original_content.splitlines(keepends=True)
34+
new = self.new_content.splitlines(keepends=True)
35+
diff = difflib.unified_diff(
36+
orig, new,
37+
fromfile=f"a/{self.file_path}",
38+
tofile=f"b/{self.file_path}",
39+
lineterm=""
40+
)
41+
return ''.join(diff)
42+
43+
class ModeManager:
44+
def __init__(self):
45+
self.current_mode = OperationMode.ASK
46+
self.permissions: Dict[str, PermissionLevel] = {}
47+
self.pending_changes: List[FileChange] = []
48+
self.global_permission: Optional[PermissionLevel] = None
49+
self.temp_dir: Optional[str] = None
50+
51+
def set_mode(self, mode: str) -> str:
52+
try:
53+
new_mode = OperationMode(mode.lower())
54+
except ValueError:
55+
return "❌ Invalid mode. Use: /mode ask or /mode build"
56+
if new_mode == self.current_mode:
57+
return f"Already in {new_mode.value.upper()} mode."
58+
self.current_mode = new_mode
59+
if new_mode == OperationMode.BUILD:
60+
self._init_build_mode()
61+
return "✅ Switched to BUILD mode. I can now edit files automatically with your permission."
62+
else:
63+
self._cleanup_build_mode()
64+
return "✅ Switched to ASK mode. I'll only suggest code, not edit files."
65+
66+
def _init_build_mode(self):
67+
self.temp_dir = tempfile.mkdtemp(prefix="codez_build_")
68+
self.pending_changes.clear()
69+
self.permissions.clear()
70+
self.global_permission = None
71+
72+
def _cleanup_build_mode(self):
73+
if self.temp_dir and Path(self.temp_dir).exists():
74+
shutil.rmtree(self.temp_dir)
75+
self.pending_changes.clear()
76+
self.permissions.clear()
77+
self.global_permission = None
78+
79+
def is_build_mode(self) -> bool:
80+
return self.current_mode == OperationMode.BUILD
81+
82+
def can_edit_file(self, file_path: str) -> Tuple[bool, str]:
83+
if not self.is_build_mode():
84+
return False, "Not in build mode."
85+
if self.global_permission == PermissionLevel.GLOBAL:
86+
return True, "Global permission granted."
87+
perm = self.permissions.get(file_path, PermissionLevel.NONE)
88+
if perm in (PermissionLevel.ALL, PermissionLevel.ONCE):
89+
return True, f"File permission: {perm.name}"
90+
return False, "Permission required."
91+
92+
def add_pending_change(self, change: FileChange):
93+
self.pending_changes.append(change)
94+
95+
def request_permission_message(self, file_path: str, operation: str, diff_preview: str) -> str:
96+
preview = diff_preview[:500] + ('...' if len(diff_preview) > 500 else '')
97+
return (
98+
f"\n🔧 **BUILD MODE: Permission Required**\n\n"
99+
f"**File**: `{file_path}`\n"
100+
f"**Operation**: {operation}\n\n"
101+
f"**Preview of changes**:\n"
102+
f"```diff\n{preview}\n```\n"
103+
"Choose your response:\n\n"
104+
"accept once - Apply this change only\n"
105+
"accept all - Apply this and all future changes to this file\n"
106+
"accept global - Apply all changes to all files (until mode switch)\n"
107+
"reject - Skip this change\n"
108+
"show full - Show complete diff\n"
109+
)
110+
111+
def handle_permission_response(self, response: str, file_path: str) -> Tuple[bool, str]:
112+
resp = response.strip().lower()
113+
if resp == "accept once":
114+
self.permissions[file_path] = PermissionLevel.ONCE
115+
return True, "Accepted once."
116+
elif resp == "accept all":
117+
self.permissions[file_path] = PermissionLevel.ALL
118+
return True, "Accepted all for this file."
119+
elif resp == "accept global":
120+
self.global_permission = PermissionLevel.GLOBAL
121+
return True, "Accepted all for all files."
122+
elif resp == "reject":
123+
self.permissions[file_path] = PermissionLevel.NONE
124+
return False, "Change rejected."
125+
elif resp == "show full":
126+
return False, "Show full diff."
127+
else:
128+
return False, "Unrecognized response."
129+
130+
def apply_change(self, change: FileChange) -> bool:
131+
"""Apply the file change if permitted."""
132+
if not self.can_edit_file(change.file_path)[0]:
133+
return False
134+
# Backup original file
135+
file_path = Path(change.file_path)
136+
if file_path.exists():
137+
backup = Path(self.temp_dir) / (file_path.name + ".bak")
138+
shutil.copy2(file_path, backup)
139+
change.backup_path = str(backup)
140+
# Write new content
141+
file_path.write_text(change.new_content, encoding="utf-8")
142+
change.applied = True
143+
return True
144+
145+
def revert_change(self, change: FileChange) -> bool:
146+
"""Revert a previously applied change."""
147+
if not change.applied or not change.backup_path:
148+
return False
149+
file_path = Path(change.file_path)
150+
backup = Path(change.backup_path)
151+
if backup.exists():
152+
shutil.copy2(backup, file_path)
153+
change.applied = False
154+
return True
155+
return False
156+
157+
def clear_permissions(self):
158+
self.permissions.clear()
159+
self.global_permission = None
160+
161+
def list_pending_changes(self) -> List[FileChange]:
162+
return self.pending_changes
163+
164+
def cleanup(self):
165+
self._cleanup_build_mode()

0 commit comments

Comments
 (0)