ai-coding-assistant 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
- ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
- ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
- ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
- ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
- coding_assistant/__init__.py +3 -0
- coding_assistant/__main__.py +19 -0
- coding_assistant/cli/__init__.py +1 -0
- coding_assistant/cli/app.py +158 -0
- coding_assistant/cli/commands/__init__.py +19 -0
- coding_assistant/cli/commands/ask.py +178 -0
- coding_assistant/cli/commands/config.py +438 -0
- coding_assistant/cli/commands/diagram.py +267 -0
- coding_assistant/cli/commands/document.py +410 -0
- coding_assistant/cli/commands/explain.py +192 -0
- coding_assistant/cli/commands/fix.py +249 -0
- coding_assistant/cli/commands/index.py +162 -0
- coding_assistant/cli/commands/refactor.py +245 -0
- coding_assistant/cli/commands/search.py +182 -0
- coding_assistant/cli/commands/serve_docs.py +128 -0
- coding_assistant/cli/repl.py +381 -0
- coding_assistant/cli/theme.py +90 -0
- coding_assistant/codebase/__init__.py +1 -0
- coding_assistant/codebase/crawler.py +93 -0
- coding_assistant/codebase/parser.py +266 -0
- coding_assistant/config/__init__.py +25 -0
- coding_assistant/config/config_manager.py +615 -0
- coding_assistant/config/settings.py +82 -0
- coding_assistant/context/__init__.py +19 -0
- coding_assistant/context/chunker.py +443 -0
- coding_assistant/context/enhanced_retriever.py +322 -0
- coding_assistant/context/hybrid_search.py +311 -0
- coding_assistant/context/ranker.py +355 -0
- coding_assistant/context/retriever.py +119 -0
- coding_assistant/context/window.py +362 -0
- coding_assistant/documentation/__init__.py +23 -0
- coding_assistant/documentation/agents/__init__.py +27 -0
- coding_assistant/documentation/agents/coordinator.py +510 -0
- coding_assistant/documentation/agents/module_documenter.py +111 -0
- coding_assistant/documentation/agents/synthesizer.py +139 -0
- coding_assistant/documentation/agents/task_delegator.py +100 -0
- coding_assistant/documentation/decomposition/__init__.py +21 -0
- coding_assistant/documentation/decomposition/context_preserver.py +477 -0
- coding_assistant/documentation/decomposition/module_detector.py +302 -0
- coding_assistant/documentation/decomposition/partitioner.py +621 -0
- coding_assistant/documentation/generators/__init__.py +14 -0
- coding_assistant/documentation/generators/dataflow_generator.py +440 -0
- coding_assistant/documentation/generators/diagram_generator.py +511 -0
- coding_assistant/documentation/graph/__init__.py +13 -0
- coding_assistant/documentation/graph/dependency_builder.py +468 -0
- coding_assistant/documentation/graph/module_analyzer.py +475 -0
- coding_assistant/documentation/writers/__init__.py +11 -0
- coding_assistant/documentation/writers/markdown_writer.py +322 -0
- coding_assistant/embeddings/__init__.py +0 -0
- coding_assistant/embeddings/generator.py +89 -0
- coding_assistant/embeddings/store.py +187 -0
- coding_assistant/exceptions/__init__.py +50 -0
- coding_assistant/exceptions/base.py +110 -0
- coding_assistant/exceptions/llm.py +249 -0
- coding_assistant/exceptions/recovery.py +263 -0
- coding_assistant/exceptions/storage.py +213 -0
- coding_assistant/exceptions/validation.py +230 -0
- coding_assistant/llm/__init__.py +1 -0
- coding_assistant/llm/client.py +277 -0
- coding_assistant/llm/gemini_client.py +181 -0
- coding_assistant/llm/groq_client.py +160 -0
- coding_assistant/llm/prompts.py +98 -0
- coding_assistant/llm/together_client.py +160 -0
- coding_assistant/operations/__init__.py +13 -0
- coding_assistant/operations/differ.py +369 -0
- coding_assistant/operations/generator.py +347 -0
- coding_assistant/operations/linter.py +430 -0
- coding_assistant/operations/validator.py +406 -0
- coding_assistant/storage/__init__.py +9 -0
- coding_assistant/storage/database.py +363 -0
- coding_assistant/storage/session.py +231 -0
- coding_assistant/utils/__init__.py +31 -0
- coding_assistant/utils/cache.py +477 -0
- coding_assistant/utils/hardware.py +132 -0
- coding_assistant/utils/keystore.py +206 -0
- coding_assistant/utils/logger.py +32 -0
- coding_assistant/utils/progress.py +311 -0
- coding_assistant/validation/__init__.py +13 -0
- coding_assistant/validation/files.py +305 -0
- coding_assistant/validation/inputs.py +335 -0
- coding_assistant/validation/params.py +280 -0
- coding_assistant/validation/sanitizers.py +243 -0
- coding_assistant/vcs/__init__.py +5 -0
- coding_assistant/vcs/git.py +269 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Database layer for session persistence."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Dict, Optional, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Database:
|
|
12
|
+
"""SQLite database for storing sessions, messages, and code changes."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, db_path: str):
|
|
15
|
+
"""Initialize database.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_path: Path to SQLite database file
|
|
19
|
+
"""
|
|
20
|
+
self.db_path = Path(db_path)
|
|
21
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
self._init_schema()
|
|
23
|
+
|
|
24
|
+
@contextmanager
|
|
25
|
+
def get_connection(self):
|
|
26
|
+
"""Get database connection context manager."""
|
|
27
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
28
|
+
conn.row_factory = sqlite3.Row # Return rows as dicts
|
|
29
|
+
try:
|
|
30
|
+
yield conn
|
|
31
|
+
conn.commit()
|
|
32
|
+
except Exception:
|
|
33
|
+
conn.rollback()
|
|
34
|
+
raise
|
|
35
|
+
finally:
|
|
36
|
+
conn.close()
|
|
37
|
+
|
|
38
|
+
def _init_schema(self):
|
|
39
|
+
"""Initialize database schema."""
|
|
40
|
+
with self.get_connection() as conn:
|
|
41
|
+
cursor = conn.cursor()
|
|
42
|
+
|
|
43
|
+
# Sessions table
|
|
44
|
+
cursor.execute("""
|
|
45
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
project_path TEXT NOT NULL,
|
|
48
|
+
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
49
|
+
ended_at TIMESTAMP,
|
|
50
|
+
title TEXT,
|
|
51
|
+
metadata TEXT
|
|
52
|
+
)
|
|
53
|
+
""")
|
|
54
|
+
|
|
55
|
+
# Messages table
|
|
56
|
+
cursor.execute("""
|
|
57
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
58
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
session_id INTEGER NOT NULL,
|
|
60
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
|
|
61
|
+
content TEXT NOT NULL,
|
|
62
|
+
tokens INTEGER,
|
|
63
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
+
metadata TEXT,
|
|
65
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
66
|
+
)
|
|
67
|
+
""")
|
|
68
|
+
|
|
69
|
+
# Code changes table
|
|
70
|
+
cursor.execute("""
|
|
71
|
+
CREATE TABLE IF NOT EXISTS code_changes (
|
|
72
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
73
|
+
session_id INTEGER NOT NULL,
|
|
74
|
+
message_id INTEGER,
|
|
75
|
+
file_path TEXT NOT NULL,
|
|
76
|
+
change_type TEXT CHECK(change_type IN ('create', 'modify', 'delete')),
|
|
77
|
+
original_content TEXT,
|
|
78
|
+
new_content TEXT,
|
|
79
|
+
diff TEXT,
|
|
80
|
+
applied BOOLEAN DEFAULT 0,
|
|
81
|
+
applied_at TIMESTAMP,
|
|
82
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
84
|
+
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE SET NULL
|
|
85
|
+
)
|
|
86
|
+
""")
|
|
87
|
+
|
|
88
|
+
# Context snapshots table
|
|
89
|
+
cursor.execute("""
|
|
90
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
91
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
|
+
session_id INTEGER NOT NULL,
|
|
93
|
+
message_id INTEGER NOT NULL,
|
|
94
|
+
retrieved_chunks TEXT,
|
|
95
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
96
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
97
|
+
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
|
|
98
|
+
)
|
|
99
|
+
""")
|
|
100
|
+
|
|
101
|
+
# Create indexes
|
|
102
|
+
cursor.execute("""
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
104
|
+
ON messages(session_id)
|
|
105
|
+
""")
|
|
106
|
+
|
|
107
|
+
cursor.execute("""
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_code_changes_session
|
|
109
|
+
ON code_changes(session_id)
|
|
110
|
+
""")
|
|
111
|
+
|
|
112
|
+
cursor.execute("""
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
114
|
+
ON sessions(project_path)
|
|
115
|
+
""")
|
|
116
|
+
|
|
117
|
+
# Session operations
|
|
118
|
+
|
|
119
|
+
def insert_session(self, project_path: str, title: Optional[str] = None,
|
|
120
|
+
metadata: Optional[Dict] = None) -> int:
|
|
121
|
+
"""Create a new session.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
project_path: Path to project
|
|
125
|
+
title: Optional session title
|
|
126
|
+
metadata: Optional metadata dict
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Session ID
|
|
130
|
+
"""
|
|
131
|
+
with self.get_connection() as conn:
|
|
132
|
+
cursor = conn.cursor()
|
|
133
|
+
cursor.execute("""
|
|
134
|
+
INSERT INTO sessions (project_path, title, metadata)
|
|
135
|
+
VALUES (?, ?, ?)
|
|
136
|
+
""", (project_path, title, json.dumps(metadata) if metadata else None))
|
|
137
|
+
return cursor.lastrowid
|
|
138
|
+
|
|
139
|
+
def update_session(self, session_id: int, ended_at: Optional[datetime] = None,
|
|
140
|
+
title: Optional[str] = None, metadata: Optional[Dict] = None):
|
|
141
|
+
"""Update session.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
session_id: Session ID
|
|
145
|
+
ended_at: End timestamp
|
|
146
|
+
title: New title
|
|
147
|
+
metadata: New metadata
|
|
148
|
+
"""
|
|
149
|
+
updates = []
|
|
150
|
+
params = []
|
|
151
|
+
|
|
152
|
+
if ended_at:
|
|
153
|
+
updates.append("ended_at = ?")
|
|
154
|
+
params.append(ended_at.isoformat())
|
|
155
|
+
|
|
156
|
+
if title is not None:
|
|
157
|
+
updates.append("title = ?")
|
|
158
|
+
params.append(title)
|
|
159
|
+
|
|
160
|
+
if metadata is not None:
|
|
161
|
+
updates.append("metadata = ?")
|
|
162
|
+
params.append(json.dumps(metadata))
|
|
163
|
+
|
|
164
|
+
if not updates:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
params.append(session_id)
|
|
168
|
+
query = f"UPDATE sessions SET {', '.join(updates)} WHERE id = ?"
|
|
169
|
+
|
|
170
|
+
with self.get_connection() as conn:
|
|
171
|
+
conn.execute(query, params)
|
|
172
|
+
|
|
173
|
+
def get_session(self, session_id: int) -> Optional[Dict]:
|
|
174
|
+
"""Get session by ID.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
session_id: Session ID
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Session dict or None
|
|
181
|
+
"""
|
|
182
|
+
with self.get_connection() as conn:
|
|
183
|
+
cursor = conn.cursor()
|
|
184
|
+
cursor.execute("SELECT * FROM sessions WHERE id = ?", (session_id,))
|
|
185
|
+
row = cursor.fetchone()
|
|
186
|
+
if row:
|
|
187
|
+
return dict(row)
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def get_recent_sessions(self, limit: int = 10, project_path: Optional[str] = None) -> List[Dict]:
|
|
191
|
+
"""Get recent sessions.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
limit: Maximum number of sessions
|
|
195
|
+
project_path: Filter by project path
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of session dicts
|
|
199
|
+
"""
|
|
200
|
+
with self.get_connection() as conn:
|
|
201
|
+
cursor = conn.cursor()
|
|
202
|
+
|
|
203
|
+
if project_path:
|
|
204
|
+
cursor.execute("""
|
|
205
|
+
SELECT * FROM sessions
|
|
206
|
+
WHERE project_path = ?
|
|
207
|
+
ORDER BY started_at DESC
|
|
208
|
+
LIMIT ?
|
|
209
|
+
""", (project_path, limit))
|
|
210
|
+
else:
|
|
211
|
+
cursor.execute("""
|
|
212
|
+
SELECT * FROM sessions
|
|
213
|
+
ORDER BY started_at DESC
|
|
214
|
+
LIMIT ?
|
|
215
|
+
""", (limit,))
|
|
216
|
+
|
|
217
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
218
|
+
|
|
219
|
+
# Message operations
|
|
220
|
+
|
|
221
|
+
def insert_message(self, session_id: int, role: str, content: str,
|
|
222
|
+
tokens: Optional[int] = None, metadata: Optional[Dict] = None) -> int:
|
|
223
|
+
"""Add message to session.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
session_id: Session ID
|
|
227
|
+
role: Message role (user/assistant/system)
|
|
228
|
+
content: Message content
|
|
229
|
+
tokens: Token count
|
|
230
|
+
metadata: Optional metadata
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Message ID
|
|
234
|
+
"""
|
|
235
|
+
with self.get_connection() as conn:
|
|
236
|
+
cursor = conn.cursor()
|
|
237
|
+
cursor.execute("""
|
|
238
|
+
INSERT INTO messages (session_id, role, content, tokens, metadata)
|
|
239
|
+
VALUES (?, ?, ?, ?, ?)
|
|
240
|
+
""", (session_id, role, content, tokens, json.dumps(metadata) if metadata else None))
|
|
241
|
+
return cursor.lastrowid
|
|
242
|
+
|
|
243
|
+
def get_messages_by_session(self, session_id: int) -> List[Dict]:
|
|
244
|
+
"""Get all messages in a session.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
session_id: Session ID
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of message dicts
|
|
251
|
+
"""
|
|
252
|
+
with self.get_connection() as conn:
|
|
253
|
+
cursor = conn.cursor()
|
|
254
|
+
cursor.execute("""
|
|
255
|
+
SELECT * FROM messages
|
|
256
|
+
WHERE session_id = ?
|
|
257
|
+
ORDER BY created_at ASC
|
|
258
|
+
""", (session_id,))
|
|
259
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
260
|
+
|
|
261
|
+
# Code change operations
|
|
262
|
+
|
|
263
|
+
def insert_code_change(self, session_id: int, file_path: str,
|
|
264
|
+
change_type: str, original_content: Optional[str] = None,
|
|
265
|
+
new_content: Optional[str] = None, diff: Optional[str] = None,
|
|
266
|
+
message_id: Optional[int] = None) -> int:
|
|
267
|
+
"""Record a code change.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
session_id: Session ID
|
|
271
|
+
file_path: Path to changed file
|
|
272
|
+
change_type: Type of change (create/modify/delete)
|
|
273
|
+
original_content: Original file content
|
|
274
|
+
new_content: New file content
|
|
275
|
+
diff: Diff text
|
|
276
|
+
message_id: Associated message ID
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Code change ID
|
|
280
|
+
"""
|
|
281
|
+
with self.get_connection() as conn:
|
|
282
|
+
cursor = conn.cursor()
|
|
283
|
+
cursor.execute("""
|
|
284
|
+
INSERT INTO code_changes
|
|
285
|
+
(session_id, message_id, file_path, change_type,
|
|
286
|
+
original_content, new_content, diff)
|
|
287
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
288
|
+
""", (session_id, message_id, file_path, change_type,
|
|
289
|
+
original_content, new_content, diff))
|
|
290
|
+
return cursor.lastrowid
|
|
291
|
+
|
|
292
|
+
def mark_change_applied(self, change_id: int):
|
|
293
|
+
"""Mark a code change as applied.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
change_id: Code change ID
|
|
297
|
+
"""
|
|
298
|
+
with self.get_connection() as conn:
|
|
299
|
+
conn.execute("""
|
|
300
|
+
UPDATE code_changes
|
|
301
|
+
SET applied = 1, applied_at = CURRENT_TIMESTAMP
|
|
302
|
+
WHERE id = ?
|
|
303
|
+
""", (change_id,))
|
|
304
|
+
|
|
305
|
+
def get_code_changes(self, session_id: int) -> List[Dict]:
|
|
306
|
+
"""Get all code changes for a session.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
session_id: Session ID
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of code change dicts
|
|
313
|
+
"""
|
|
314
|
+
with self.get_connection() as conn:
|
|
315
|
+
cursor = conn.cursor()
|
|
316
|
+
cursor.execute("""
|
|
317
|
+
SELECT * FROM code_changes
|
|
318
|
+
WHERE session_id = ?
|
|
319
|
+
ORDER BY created_at ASC
|
|
320
|
+
""", (session_id,))
|
|
321
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
322
|
+
|
|
323
|
+
# Context snapshot operations
|
|
324
|
+
|
|
325
|
+
def insert_context_snapshot(self, session_id: int, message_id: int,
|
|
326
|
+
retrieved_chunks: List[Dict]) -> int:
|
|
327
|
+
"""Save a context snapshot.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
session_id: Session ID
|
|
331
|
+
message_id: Message ID
|
|
332
|
+
retrieved_chunks: List of retrieved chunks
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Snapshot ID
|
|
336
|
+
"""
|
|
337
|
+
with self.get_connection() as conn:
|
|
338
|
+
cursor = conn.cursor()
|
|
339
|
+
cursor.execute("""
|
|
340
|
+
INSERT INTO context_snapshots (session_id, message_id, retrieved_chunks)
|
|
341
|
+
VALUES (?, ?, ?)
|
|
342
|
+
""", (session_id, message_id, json.dumps(retrieved_chunks)))
|
|
343
|
+
return cursor.lastrowid
|
|
344
|
+
|
|
345
|
+
def get_context_snapshot(self, message_id: int) -> Optional[List[Dict]]:
|
|
346
|
+
"""Get context snapshot for a message.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
message_id: Message ID
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
List of chunks or None
|
|
353
|
+
"""
|
|
354
|
+
with self.get_connection() as conn:
|
|
355
|
+
cursor = conn.cursor()
|
|
356
|
+
cursor.execute("""
|
|
357
|
+
SELECT retrieved_chunks FROM context_snapshots
|
|
358
|
+
WHERE message_id = ?
|
|
359
|
+
""", (message_id,))
|
|
360
|
+
row = cursor.fetchone()
|
|
361
|
+
if row:
|
|
362
|
+
return json.loads(row['retrieved_chunks'])
|
|
363
|
+
return None
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Session management for persistent conversations."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from coding_assistant.storage.database import Database
|
|
8
|
+
from coding_assistant.operations.differ import DiffGenerator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionManager:
|
|
12
|
+
"""Manages conversation sessions with persistence."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, db_path: str):
|
|
15
|
+
"""Initialize session manager.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_path: Path to SQLite database
|
|
19
|
+
"""
|
|
20
|
+
self.db = Database(db_path)
|
|
21
|
+
self.diff_generator = DiffGenerator()
|
|
22
|
+
|
|
23
|
+
def create_session(self, project_path: str, title: Optional[str] = None) -> int:
|
|
24
|
+
"""Create a new session.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
project_path: Path to project
|
|
28
|
+
title: Optional session title
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Session ID
|
|
32
|
+
"""
|
|
33
|
+
return self.db.insert_session(project_path, title)
|
|
34
|
+
|
|
35
|
+
def end_session(self, session_id: int):
|
|
36
|
+
"""Mark session as ended.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
session_id: Session ID
|
|
40
|
+
"""
|
|
41
|
+
self.db.update_session(session_id, ended_at=datetime.now())
|
|
42
|
+
|
|
43
|
+
def update_session_title(self, session_id: int, title: str):
|
|
44
|
+
"""Update session title.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
session_id: Session ID
|
|
48
|
+
title: New title
|
|
49
|
+
"""
|
|
50
|
+
self.db.update_session(session_id, title=title)
|
|
51
|
+
|
|
52
|
+
def add_message(self, session_id: int, role: str, content: str,
|
|
53
|
+
tokens: Optional[int] = None) -> int:
|
|
54
|
+
"""Add message to session.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
session_id: Session ID
|
|
58
|
+
role: Message role (user/assistant/system)
|
|
59
|
+
content: Message content
|
|
60
|
+
tokens: Optional token count
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Message ID
|
|
64
|
+
"""
|
|
65
|
+
return self.db.insert_message(session_id, role, content, tokens)
|
|
66
|
+
|
|
67
|
+
def get_session_history(self, session_id: int) -> List[Dict]:
|
|
68
|
+
"""Get all messages in a session.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
session_id: Session ID
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of messages
|
|
75
|
+
"""
|
|
76
|
+
messages = self.db.get_messages_by_session(session_id)
|
|
77
|
+
# Convert to format expected by LLM
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
'role': msg['role'],
|
|
81
|
+
'content': msg['content']
|
|
82
|
+
}
|
|
83
|
+
for msg in messages
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
def record_code_change(self, session_id: int, file_path: str,
|
|
87
|
+
original_content: str, new_content: str,
|
|
88
|
+
message_id: Optional[int] = None) -> int:
|
|
89
|
+
"""Record a code change.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
session_id: Session ID
|
|
93
|
+
file_path: Path to file
|
|
94
|
+
original_content: Original content
|
|
95
|
+
new_content: New content
|
|
96
|
+
message_id: Associated message ID
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Code change ID
|
|
100
|
+
"""
|
|
101
|
+
# Generate diff
|
|
102
|
+
diff = self.diff_generator.generate_diff(
|
|
103
|
+
original_content,
|
|
104
|
+
new_content,
|
|
105
|
+
file_path
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Determine change type
|
|
109
|
+
if not original_content:
|
|
110
|
+
change_type = 'create'
|
|
111
|
+
elif not new_content:
|
|
112
|
+
change_type = 'delete'
|
|
113
|
+
else:
|
|
114
|
+
change_type = 'modify'
|
|
115
|
+
|
|
116
|
+
return self.db.insert_code_change(
|
|
117
|
+
session_id=session_id,
|
|
118
|
+
file_path=file_path,
|
|
119
|
+
change_type=change_type,
|
|
120
|
+
original_content=original_content,
|
|
121
|
+
new_content=new_content,
|
|
122
|
+
diff=diff,
|
|
123
|
+
message_id=message_id
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def mark_change_applied(self, change_id: int):
|
|
127
|
+
"""Mark a code change as applied.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
change_id: Code change ID
|
|
131
|
+
"""
|
|
132
|
+
self.db.mark_change_applied(change_id)
|
|
133
|
+
|
|
134
|
+
def get_code_changes(self, session_id: int) -> List[Dict]:
|
|
135
|
+
"""Get all code changes for a session.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
session_id: Session ID
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of code changes
|
|
142
|
+
"""
|
|
143
|
+
return self.db.get_code_changes(session_id)
|
|
144
|
+
|
|
145
|
+
def save_context_snapshot(self, session_id: int, message_id: int,
|
|
146
|
+
retrieved_chunks: List[Dict]) -> int:
|
|
147
|
+
"""Save context snapshot for a message.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
session_id: Session ID
|
|
151
|
+
message_id: Message ID
|
|
152
|
+
retrieved_chunks: Retrieved context chunks
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Snapshot ID
|
|
156
|
+
"""
|
|
157
|
+
return self.db.insert_context_snapshot(
|
|
158
|
+
session_id,
|
|
159
|
+
message_id,
|
|
160
|
+
retrieved_chunks
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def get_context_snapshot(self, message_id: int) -> Optional[List[Dict]]:
|
|
164
|
+
"""Get context snapshot for a message.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
message_id: Message ID
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of chunks or None
|
|
171
|
+
"""
|
|
172
|
+
return self.db.get_context_snapshot(message_id)
|
|
173
|
+
|
|
174
|
+
def list_sessions(self, limit: int = 10, project_path: Optional[str] = None) -> List[Dict]:
|
|
175
|
+
"""List recent sessions.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
limit: Maximum number of sessions
|
|
179
|
+
project_path: Filter by project path
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List of sessions
|
|
183
|
+
"""
|
|
184
|
+
return self.db.get_recent_sessions(limit, project_path)
|
|
185
|
+
|
|
186
|
+
def get_session(self, session_id: int) -> Optional[Dict]:
|
|
187
|
+
"""Get session info.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
session_id: Session ID
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Session dict or None
|
|
194
|
+
"""
|
|
195
|
+
return self.db.get_session(session_id)
|
|
196
|
+
|
|
197
|
+
def get_session_summary(self, session_id: int) -> Dict:
|
|
198
|
+
"""Get summary of a session.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
session_id: Session ID
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Summary dict with message count, code changes, etc.
|
|
205
|
+
"""
|
|
206
|
+
session = self.get_session(session_id)
|
|
207
|
+
if not session:
|
|
208
|
+
return {}
|
|
209
|
+
|
|
210
|
+
messages = self.db.get_messages_by_session(session_id)
|
|
211
|
+
code_changes = self.get_code_changes(session_id)
|
|
212
|
+
|
|
213
|
+
# Count by role
|
|
214
|
+
user_messages = sum(1 for m in messages if m['role'] == 'user')
|
|
215
|
+
assistant_messages = sum(1 for m in messages if m['role'] == 'assistant')
|
|
216
|
+
|
|
217
|
+
# Count applied changes
|
|
218
|
+
applied_changes = sum(1 for c in code_changes if c['applied'])
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
'session_id': session_id,
|
|
222
|
+
'project_path': session['project_path'],
|
|
223
|
+
'started_at': session['started_at'],
|
|
224
|
+
'ended_at': session['ended_at'],
|
|
225
|
+
'title': session['title'],
|
|
226
|
+
'total_messages': len(messages),
|
|
227
|
+
'user_messages': user_messages,
|
|
228
|
+
'assistant_messages': assistant_messages,
|
|
229
|
+
'code_changes': len(code_changes),
|
|
230
|
+
'applied_changes': applied_changes,
|
|
231
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Utility functions and helpers."""
|
|
2
|
+
|
|
3
|
+
from coding_assistant.utils.cache import (
|
|
4
|
+
Cache,
|
|
5
|
+
EmbeddingCache,
|
|
6
|
+
ResponseCache,
|
|
7
|
+
CacheEntry,
|
|
8
|
+
compute_context_hash
|
|
9
|
+
)
|
|
10
|
+
from coding_assistant.utils.progress import (
|
|
11
|
+
ProgressManager,
|
|
12
|
+
ProgressCallback,
|
|
13
|
+
status,
|
|
14
|
+
progress_bar,
|
|
15
|
+
show_operation,
|
|
16
|
+
get_progress_manager
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'Cache',
|
|
21
|
+
'EmbeddingCache',
|
|
22
|
+
'ResponseCache',
|
|
23
|
+
'CacheEntry',
|
|
24
|
+
'compute_context_hash',
|
|
25
|
+
'ProgressManager',
|
|
26
|
+
'ProgressCallback',
|
|
27
|
+
'status',
|
|
28
|
+
'progress_bar',
|
|
29
|
+
'show_operation',
|
|
30
|
+
'get_progress_manager',
|
|
31
|
+
]
|