aline-ai 0.1.6__py3-none-any.whl → 0.1.7__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.
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/METADATA +1 -1
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/RECORD +9 -9
- realign/__init__.py +1 -1
- realign/mcp_server.py +30 -14
- realign/mcp_watcher.py +66 -36
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/WHEEL +0 -0
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.1.6.dist-info → aline_ai-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
aline_ai-0.1.
|
|
2
|
-
realign/__init__.py,sha256=
|
|
1
|
+
aline_ai-0.1.7.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=GAkpHLIQWfGF0lu7nkgDnXCA2GC4tJrr6dGTk8GMQB4,68
|
|
3
3
|
realign/claude_detector.py,sha256=NLxI0zJWcqNxNha9jAy9AslTMwHKakCc9yPGdkrbiFE,3028
|
|
4
4
|
realign/cli.py,sha256=bkwS329jMDEkrUEihXRN2DDyeTKE6HbAysoDxxskZ8g,941
|
|
5
5
|
realign/codex_detector.py,sha256=RI3JbZgebrhoqpRfTBMfclYCAISN7hZAHVW3bgftJpU,4428
|
|
6
6
|
realign/config.py,sha256=jarinbr0mA6e5DmgY19b_VpMnxk6SOYTwyvB9luq0ww,7207
|
|
7
7
|
realign/hooks.py,sha256=qhAeuln_62OgTq0vboZcUAuP2apOrNn58vSZqKwNmWQ,36456
|
|
8
8
|
realign/logging_config.py,sha256=KvkKktF-bkUu031y9vgUoHpsbnOw7ud25jhpzliNZwA,4929
|
|
9
|
-
realign/mcp_server.py,sha256
|
|
10
|
-
realign/mcp_watcher.py,sha256=
|
|
9
|
+
realign/mcp_server.py,sha256=srR1leOYbPyi6L0KALkOs94_djELi-SRLFQgrubQuhc,17760
|
|
10
|
+
realign/mcp_watcher.py,sha256=xL35Nz35YF6Mc9yu-nCbDzKzTFHQaYmtPNfyBzKKOhc,16038
|
|
11
11
|
realign/redactor.py,sha256=uZvLKKGrRGJm-qM8S4XJyJK6i0CSSby_wbKiay7VGJw,8148
|
|
12
12
|
realign/commands/__init__.py,sha256=GG6IMw6fUBQAXGJDFJvOOQgv6pkiRSfMh8z3AYXTyRM,31
|
|
13
13
|
realign/commands/auto_commit.py,sha256=_DOw7nt9q3tD_Y3qDL9IFKAUG1hM4qH_xZ-9nyBc2Bc,7451
|
|
@@ -16,8 +16,8 @@ realign/commands/config.py,sha256=oarvn6UuGT8svd2h5_8M_ueV5QWOCUOn8SYoa4XYjs8,65
|
|
|
16
16
|
realign/commands/init.py,sha256=EpSzh2Dd2EmEQ_wo3vAsg6Uq7_YOlQWIpzIkZa_2y0A,11863
|
|
17
17
|
realign/commands/search.py,sha256=0CZaXll99wtd01MRiZk5NAblxgogc4RUAzMyJunvckE,18044
|
|
18
18
|
realign/commands/show.py,sha256=P1waa94-AKJr9XjagkE40OHMXzE6IwC74DpeDKqwsqw,16693
|
|
19
|
-
aline_ai-0.1.
|
|
20
|
-
aline_ai-0.1.
|
|
21
|
-
aline_ai-0.1.
|
|
22
|
-
aline_ai-0.1.
|
|
23
|
-
aline_ai-0.1.
|
|
19
|
+
aline_ai-0.1.7.dist-info/METADATA,sha256=lgNJRQ4b1lDkDVvchqzD9VbvXB7bTkxgy9LgmmNaq34,1398
|
|
20
|
+
aline_ai-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
aline_ai-0.1.7.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
|
|
22
|
+
aline_ai-0.1.7.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
23
|
+
aline_ai-0.1.7.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
realign/mcp_server.py
CHANGED
|
@@ -395,10 +395,26 @@ async def handle_version(args: dict) -> list[TextContent]:
|
|
|
395
395
|
)]
|
|
396
396
|
|
|
397
397
|
|
|
398
|
+
def _server_log(msg: str):
|
|
399
|
+
"""Log MCP server messages to both stderr and file."""
|
|
400
|
+
from datetime import datetime
|
|
401
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
402
|
+
print(f"[MCP Server] {msg}", file=sys.stderr)
|
|
403
|
+
|
|
404
|
+
# Also write to the watcher log file for consistency
|
|
405
|
+
log_path = Path.home() / ".aline_watcher.log"
|
|
406
|
+
try:
|
|
407
|
+
with open(log_path, "a") as f:
|
|
408
|
+
f.write(f"[{timestamp}] [MCP Server] {msg}\n")
|
|
409
|
+
except Exception:
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
|
|
398
413
|
async def async_main():
|
|
399
414
|
"""Run the MCP server (async)."""
|
|
400
|
-
|
|
401
|
-
|
|
415
|
+
_server_log("Starting Aline MCP server...")
|
|
416
|
+
_server_log(f"Current working directory: {Path.cwd()}")
|
|
417
|
+
_server_log(f"Home directory: {Path.home()}")
|
|
402
418
|
|
|
403
419
|
# Detect workspace path and start the watcher
|
|
404
420
|
# Try multiple methods since MCP server may run in different context
|
|
@@ -414,52 +430,52 @@ async def async_main():
|
|
|
414
430
|
)
|
|
415
431
|
if result.returncode == 0:
|
|
416
432
|
repo_path = Path(result.stdout.strip())
|
|
417
|
-
|
|
433
|
+
_server_log(f"Detected workspace from git: {repo_path}")
|
|
418
434
|
else:
|
|
419
|
-
|
|
435
|
+
_server_log(f"Not in git repo (cwd: {Path.cwd()})")
|
|
420
436
|
|
|
421
437
|
# Method 2: If not in git repo, try to find from Claude session files
|
|
422
438
|
if not repo_path:
|
|
423
439
|
claude_projects = Path.home() / ".claude" / "projects"
|
|
424
|
-
|
|
440
|
+
_server_log(f"Checking Claude projects at: {claude_projects}")
|
|
425
441
|
if claude_projects.exists():
|
|
426
442
|
# Find most recently modified session file
|
|
427
443
|
session_files = list(claude_projects.glob("*/*.jsonl"))
|
|
428
|
-
|
|
444
|
+
_server_log(f"Found {len(session_files)} Claude session files")
|
|
429
445
|
if session_files:
|
|
430
446
|
# Sort by modification time, newest first
|
|
431
447
|
session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
432
|
-
|
|
448
|
+
_server_log(f"Most recent session: {session_files[0]}")
|
|
433
449
|
# Extract project path from directory name
|
|
434
450
|
# Format: -Users-jundewu-Downloads-code-noclue -> /Users/jundewu/Downloads/code/noclue
|
|
435
451
|
project_dir_name = session_files[0].parent.name
|
|
436
|
-
|
|
452
|
+
_server_log(f"Project dir name: {project_dir_name}")
|
|
437
453
|
if project_dir_name.startswith('-'):
|
|
438
454
|
# Convert back to path: -Users-foo-bar -> /Users/foo/bar
|
|
439
455
|
# Note: underscores were also replaced with dashes, but we can't distinguish
|
|
440
456
|
# So we just replace dashes with slashes
|
|
441
457
|
path_str = '/' + project_dir_name[1:].replace('-', '/')
|
|
442
458
|
candidate_path = Path(path_str)
|
|
443
|
-
|
|
459
|
+
_server_log(f"Candidate path: {candidate_path}, exists: {candidate_path.exists()}")
|
|
444
460
|
if candidate_path.exists():
|
|
445
461
|
repo_path = candidate_path
|
|
446
|
-
|
|
462
|
+
_server_log(f"Detected workspace from Claude session: {repo_path}")
|
|
447
463
|
|
|
448
464
|
# Method 3: Fallback to current directory
|
|
449
465
|
if not repo_path:
|
|
450
466
|
repo_path = Path.cwd()
|
|
451
|
-
|
|
467
|
+
_server_log(f"Using current directory: {repo_path}")
|
|
452
468
|
|
|
453
469
|
# Start the watcher
|
|
454
|
-
|
|
470
|
+
_server_log(f"Starting watcher for: {repo_path}")
|
|
455
471
|
await start_watcher(repo_path)
|
|
456
472
|
|
|
457
473
|
except Exception as e:
|
|
458
|
-
|
|
474
|
+
_server_log(f"Warning: Could not start watcher: {e}")
|
|
459
475
|
import traceback
|
|
460
476
|
traceback.print_exc(file=sys.stderr)
|
|
461
477
|
|
|
462
|
-
|
|
478
|
+
_server_log("MCP server ready")
|
|
463
479
|
|
|
464
480
|
async with stdio_server() as (read_stream, write_stream):
|
|
465
481
|
await app.run(
|
realign/mcp_watcher.py
CHANGED
|
@@ -13,6 +13,35 @@ from .config import ReAlignConfig
|
|
|
13
13
|
from .hooks import find_all_active_sessions
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
# File-based logger for debugging when stderr is not visible
|
|
17
|
+
_log_file = None
|
|
18
|
+
|
|
19
|
+
def _log(msg: str):
|
|
20
|
+
"""Log to both stderr and a debug file."""
|
|
21
|
+
global _log_file
|
|
22
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
23
|
+
full_msg = f"[{timestamp}] {msg}"
|
|
24
|
+
print(f"[MCP Watcher] {msg}", file=sys.stderr)
|
|
25
|
+
|
|
26
|
+
# Also write to file for debugging
|
|
27
|
+
if _log_file is None:
|
|
28
|
+
log_path = Path.home() / ".aline_watcher.log"
|
|
29
|
+
try:
|
|
30
|
+
_log_file = open(log_path, "a", buffering=1) # Line buffered
|
|
31
|
+
_log_file.write(f"\n{'='*60}\n")
|
|
32
|
+
_log_file.write(f"[{timestamp}] MCP Watcher Started\n")
|
|
33
|
+
_log_file.write(f"{'='*60}\n")
|
|
34
|
+
except Exception:
|
|
35
|
+
_log_file = False # Mark as failed so we don't retry
|
|
36
|
+
|
|
37
|
+
if _log_file and _log_file is not False:
|
|
38
|
+
try:
|
|
39
|
+
_log_file.write(full_msg + "\n")
|
|
40
|
+
_log_file.flush()
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
16
45
|
class DialogueWatcher:
|
|
17
46
|
"""Watch session files and auto-commit immediately after each user request completes."""
|
|
18
47
|
|
|
@@ -30,14 +59,15 @@ class DialogueWatcher:
|
|
|
30
59
|
async def start(self):
|
|
31
60
|
"""Start watching session files."""
|
|
32
61
|
if not self.config.mcp_auto_commit:
|
|
33
|
-
|
|
62
|
+
_log("Auto-commit disabled in config")
|
|
34
63
|
return
|
|
35
64
|
|
|
36
65
|
self.running = True
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
_log("Started watching for dialogue completion")
|
|
67
|
+
_log(f"Mode: Per-request (triggers at end of each Claude response)")
|
|
68
|
+
_log(f"Debounce: {self.debounce_delay}s, Cooldown: {self.min_commit_interval}s")
|
|
69
|
+
_log(f"Home directory: {Path.home()}")
|
|
70
|
+
_log(f"Current working directory: {Path.cwd()}")
|
|
41
71
|
|
|
42
72
|
# Initialize baseline sizes and stop_reason counts
|
|
43
73
|
self.last_session_sizes = self._get_session_sizes()
|
|
@@ -45,14 +75,14 @@ class DialogueWatcher:
|
|
|
45
75
|
|
|
46
76
|
# Log initial session files being monitored
|
|
47
77
|
if self.last_session_sizes:
|
|
48
|
-
|
|
78
|
+
_log(f"Monitoring {len(self.last_session_sizes)} session file(s):")
|
|
49
79
|
for session_path in list(self.last_session_sizes.keys())[:5]: # Show first 5
|
|
50
|
-
|
|
80
|
+
_log(f" - {session_path}")
|
|
51
81
|
if len(self.last_session_sizes) > 5:
|
|
52
|
-
|
|
82
|
+
_log(f" ... and {len(self.last_session_sizes) - 5} more")
|
|
53
83
|
else:
|
|
54
84
|
claude_projects = Path.home() / ".claude" / "projects"
|
|
55
|
-
|
|
85
|
+
_log(f"WARNING: No session files found in {claude_projects}")
|
|
56
86
|
|
|
57
87
|
# Poll for file changes more frequently
|
|
58
88
|
while self.running:
|
|
@@ -60,7 +90,7 @@ class DialogueWatcher:
|
|
|
60
90
|
await self.check_for_changes()
|
|
61
91
|
await asyncio.sleep(0.5) # Check every 0.5 seconds for responsiveness
|
|
62
92
|
except Exception as e:
|
|
63
|
-
|
|
93
|
+
_log(f"Error: {e}")
|
|
64
94
|
await asyncio.sleep(1.0)
|
|
65
95
|
|
|
66
96
|
async def stop(self):
|
|
@@ -68,7 +98,7 @@ class DialogueWatcher:
|
|
|
68
98
|
self.running = False
|
|
69
99
|
if self.pending_commit_task:
|
|
70
100
|
self.pending_commit_task.cancel()
|
|
71
|
-
|
|
101
|
+
_log("Stopped")
|
|
72
102
|
|
|
73
103
|
def _get_session_sizes(self) -> Dict[str, int]:
|
|
74
104
|
"""Get current sizes of all active session files (from all Claude projects)."""
|
|
@@ -81,7 +111,7 @@ class DialogueWatcher:
|
|
|
81
111
|
if session_file.exists():
|
|
82
112
|
sizes[str(session_file)] = session_file.stat().st_size
|
|
83
113
|
except Exception as e:
|
|
84
|
-
|
|
114
|
+
_log(f"Error getting session sizes: {e}")
|
|
85
115
|
return sizes
|
|
86
116
|
|
|
87
117
|
def _get_stop_reason_counts(self) -> Dict[str, int]:
|
|
@@ -95,7 +125,7 @@ class DialogueWatcher:
|
|
|
95
125
|
if session_file.exists():
|
|
96
126
|
counts[str(session_file)] = self._count_stop_reasons(session_file)
|
|
97
127
|
except Exception as e:
|
|
98
|
-
|
|
128
|
+
_log(f"Error getting stop_reason counts: {e}")
|
|
99
129
|
return counts
|
|
100
130
|
|
|
101
131
|
def _count_stop_reasons(self, session_file: Path) -> int:
|
|
@@ -127,7 +157,7 @@ class DialogueWatcher:
|
|
|
127
157
|
except json.JSONDecodeError:
|
|
128
158
|
continue
|
|
129
159
|
except Exception as e:
|
|
130
|
-
|
|
160
|
+
_log(f"Error counting stop_reasons in {session_file}: {e}")
|
|
131
161
|
return len(unique_message_ids)
|
|
132
162
|
|
|
133
163
|
async def check_for_changes(self):
|
|
@@ -146,7 +176,7 @@ class DialogueWatcher:
|
|
|
146
176
|
old_size = self.last_session_sizes.get(path, 0)
|
|
147
177
|
if size > old_size:
|
|
148
178
|
changed_files.append(Path(path))
|
|
149
|
-
|
|
179
|
+
_log(f"Session file changed: {Path(path).name} ({old_size} -> {size} bytes)")
|
|
150
180
|
|
|
151
181
|
if changed_files:
|
|
152
182
|
# File changed - cancel any pending commit and schedule a new one
|
|
@@ -162,7 +192,7 @@ class DialogueWatcher:
|
|
|
162
192
|
self.last_session_sizes = current_sizes
|
|
163
193
|
|
|
164
194
|
except Exception as e:
|
|
165
|
-
|
|
195
|
+
_log(f"Error checking for changes: {e}")
|
|
166
196
|
|
|
167
197
|
async def _debounced_commit(self, changed_files: list):
|
|
168
198
|
"""Wait for debounce period, then check if dialogue is complete and commit."""
|
|
@@ -173,7 +203,7 @@ class DialogueWatcher:
|
|
|
173
203
|
# Check if any of the changed files contains a complete dialogue turn
|
|
174
204
|
for session_file in changed_files:
|
|
175
205
|
if await self._check_if_turn_complete(session_file):
|
|
176
|
-
|
|
206
|
+
_log(f"Complete turn detected in {session_file.name}")
|
|
177
207
|
|
|
178
208
|
# Extract project path from session file's parent directory
|
|
179
209
|
project_path = self._get_project_path_from_session(session_file)
|
|
@@ -182,18 +212,18 @@ class DialogueWatcher:
|
|
|
182
212
|
current_time = time.time()
|
|
183
213
|
last_time = self.last_commit_time.get(str(project_path), 0)
|
|
184
214
|
if current_time - last_time < self.min_commit_interval:
|
|
185
|
-
|
|
215
|
+
_log(f"Skipping commit for {project_path} (cooldown)")
|
|
186
216
|
continue
|
|
187
217
|
|
|
188
218
|
await self._do_commit(project_path)
|
|
189
219
|
else:
|
|
190
|
-
|
|
220
|
+
_log(f"WARNING: Could not extract project path from {session_file}, skipping commit")
|
|
191
221
|
|
|
192
222
|
except asyncio.CancelledError:
|
|
193
223
|
# Task was cancelled because a newer change was detected
|
|
194
224
|
pass
|
|
195
225
|
except Exception as e:
|
|
196
|
-
|
|
226
|
+
_log(f"Error in debounced commit: {e}")
|
|
197
227
|
|
|
198
228
|
async def _check_if_turn_complete(self, session_file: Path) -> bool:
|
|
199
229
|
"""
|
|
@@ -215,7 +245,7 @@ class DialogueWatcher:
|
|
|
215
245
|
|
|
216
246
|
# Commit after each complete assistant response (1 new end_turn)
|
|
217
247
|
if new_stop_reasons >= 1:
|
|
218
|
-
|
|
248
|
+
_log(f"Detected {new_stop_reasons} new end_turn entry(ies) in {session_file.name}")
|
|
219
249
|
# Update baseline immediately to avoid double-counting
|
|
220
250
|
self.last_stop_reason_counts[session_path] = current_count
|
|
221
251
|
return True
|
|
@@ -223,7 +253,7 @@ class DialogueWatcher:
|
|
|
223
253
|
return False
|
|
224
254
|
|
|
225
255
|
except Exception as e:
|
|
226
|
-
|
|
256
|
+
_log(f"Error checking turn completion: {e}")
|
|
227
257
|
return False
|
|
228
258
|
|
|
229
259
|
def _get_project_path_from_session(self, session_file: Path) -> Optional[Path]:
|
|
@@ -235,23 +265,23 @@ class DialogueWatcher:
|
|
|
235
265
|
"""
|
|
236
266
|
try:
|
|
237
267
|
project_dir_name = session_file.parent.name
|
|
238
|
-
|
|
268
|
+
_log(f"Extracting project path from: {project_dir_name}")
|
|
239
269
|
|
|
240
270
|
if project_dir_name.startswith('-'):
|
|
241
271
|
# Convert back to path: -Users-foo-bar -> /Users/foo/bar
|
|
242
272
|
path_str = '/' + project_dir_name[1:].replace('-', '/')
|
|
243
273
|
candidate_path = Path(path_str)
|
|
244
|
-
|
|
274
|
+
_log(f"Candidate project path: {candidate_path}")
|
|
245
275
|
|
|
246
276
|
if candidate_path.exists():
|
|
247
|
-
|
|
277
|
+
_log(f"Project path exists: {candidate_path}")
|
|
248
278
|
return candidate_path
|
|
249
279
|
else:
|
|
250
|
-
|
|
280
|
+
_log(f"WARNING: Project path does not exist: {candidate_path}")
|
|
251
281
|
else:
|
|
252
|
-
|
|
282
|
+
_log(f"WARNING: Directory name doesn't start with '-': {project_dir_name}")
|
|
253
283
|
except Exception as e:
|
|
254
|
-
|
|
284
|
+
_log(f"Error extracting project path: {e}")
|
|
255
285
|
return None
|
|
256
286
|
|
|
257
287
|
async def _do_commit(self, project_path: Path):
|
|
@@ -261,7 +291,7 @@ class DialogueWatcher:
|
|
|
261
291
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
262
292
|
message = f"chore: Auto-commit MCP session ({timestamp})"
|
|
263
293
|
|
|
264
|
-
|
|
294
|
+
_log(f"Attempting commit in {project_path} with message: {message}")
|
|
265
295
|
|
|
266
296
|
# Use realign commit command with the specific project path
|
|
267
297
|
result = await asyncio.get_event_loop().run_in_executor(
|
|
@@ -272,12 +302,12 @@ class DialogueWatcher:
|
|
|
272
302
|
)
|
|
273
303
|
|
|
274
304
|
if result:
|
|
275
|
-
|
|
305
|
+
_log(f"✓ Committed in {project_path}: {message}")
|
|
276
306
|
self.last_commit_time[str(project_path)] = time.time()
|
|
277
307
|
# Baseline counts already updated in _check_if_turn_complete()
|
|
278
308
|
|
|
279
309
|
except Exception as e:
|
|
280
|
-
|
|
310
|
+
_log(f"Error during commit: {e}")
|
|
281
311
|
|
|
282
312
|
def _run_realign_commit(self, message: str, project_path: Path) -> bool:
|
|
283
313
|
"""
|
|
@@ -297,7 +327,7 @@ class DialogueWatcher:
|
|
|
297
327
|
realign_dir = project_path / ".realign"
|
|
298
328
|
|
|
299
329
|
if not realign_dir.exists():
|
|
300
|
-
|
|
330
|
+
_log(f"Aline not initialized in {project_path}, initializing...")
|
|
301
331
|
|
|
302
332
|
# Auto-initialize Aline (which also inits git if needed)
|
|
303
333
|
init_result = init_repository(
|
|
@@ -307,10 +337,10 @@ class DialogueWatcher:
|
|
|
307
337
|
)
|
|
308
338
|
|
|
309
339
|
if not init_result.get("success"):
|
|
310
|
-
|
|
340
|
+
_log(f"Failed to initialize Aline: {init_result.get('message', 'Unknown error')}")
|
|
311
341
|
return False
|
|
312
342
|
|
|
313
|
-
|
|
343
|
+
_log("✓ Aline initialized successfully")
|
|
314
344
|
|
|
315
345
|
# Now run the commit with stage_all=True
|
|
316
346
|
result = smart_commit(
|
|
@@ -330,11 +360,11 @@ class DialogueWatcher:
|
|
|
330
360
|
else:
|
|
331
361
|
# Log the error for debugging
|
|
332
362
|
error_msg = result.get("message", "Unknown error")
|
|
333
|
-
|
|
363
|
+
_log(f"Commit failed: {error_msg}")
|
|
334
364
|
return False
|
|
335
365
|
|
|
336
366
|
except Exception as e:
|
|
337
|
-
|
|
367
|
+
_log(f"Commit error: {e}")
|
|
338
368
|
return False
|
|
339
369
|
|
|
340
370
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|