aline-ai 0.1.6__py3-none-any.whl → 0.1.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,13 +1,13 @@
1
- aline_ai-0.1.6.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=7fKH0C4lNvrYFiQ00kUEAH-H3RUUMLXG-69ehNncimI,68
1
+ aline_ai-0.1.8.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
+ realign/__init__.py,sha256=SinCCjUdQJgIbiL3QFxgLcSeMMNgu-2OIZmOD-_yaHM,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=-LAJIsxN8U1VSzr-8TYhV9s2jC_t4_XdblnGAcbaKNk,17572
10
- realign/mcp_watcher.py,sha256=a_9R0r4DM8a9BiRIimJlY8_EhoHTQcTo55yCf4vVYOU,16163
9
+ realign/mcp_server.py,sha256=HGg0nW9_cJnW-22bSBwBONpid3Lq8UICyWyzOAny4OM,18963
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.6.dist-info/METADATA,sha256=GK1pSW1EFmnNV2ojpzEBhoBDlQrYKr9OAf4c_VELcww,1398
20
- aline_ai-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- aline_ai-0.1.6.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
22
- aline_ai-0.1.6.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
23
- aline_ai-0.1.6.dist-info/RECORD,,
19
+ aline_ai-0.1.8.dist-info/METADATA,sha256=_zzB8FOdgwI6cj1Ja6alPVifXEHVm4A8vN0G6P5_Joc,1398
20
+ aline_ai-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ aline_ai-0.1.8.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
22
+ aline_ai-0.1.8.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
23
+ aline_ai-0.1.8.dist-info/RECORD,,
realign/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Aline - AI Agent Chat Session Tracker."""
2
2
 
3
- __version__ = "0.1.6"
3
+ __version__ = "0.1.8"
realign/mcp_server.py CHANGED
@@ -395,10 +395,42 @@ 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
+
413
+ # Log immediately when module is imported
414
+ _early_log_path = Path.home() / ".aline_mcp_startup.log"
415
+ try:
416
+ with open(_early_log_path, "a") as f:
417
+ from datetime import datetime
418
+ f.write(f"\n{'='*60}\n")
419
+ f.write(f"[{datetime.now()}] MCP SERVER MODULE LOADED\n")
420
+ f.write(f"Python: {sys.executable}\n")
421
+ f.write(f"CWD: {Path.cwd()}\n")
422
+ f.write(f"HOME: {Path.home()}\n")
423
+ f.write(f"Version: {__version__}\n")
424
+ f.write(f"{'='*60}\n")
425
+ except Exception as e:
426
+ print(f"[MCP Server] Early log failed: {e}", file=sys.stderr)
427
+
428
+
398
429
  async def async_main():
399
430
  """Run the MCP server (async)."""
400
- print("[MCP Server] Starting Aline MCP server...", file=sys.stderr)
401
- print(f"[MCP Server] Current working directory: {Path.cwd()}", file=sys.stderr)
431
+ _server_log("Starting Aline MCP server...")
432
+ _server_log(f"Current working directory: {Path.cwd()}")
433
+ _server_log(f"Home directory: {Path.home()}")
402
434
 
403
435
  # Detect workspace path and start the watcher
404
436
  # Try multiple methods since MCP server may run in different context
@@ -414,52 +446,52 @@ async def async_main():
414
446
  )
415
447
  if result.returncode == 0:
416
448
  repo_path = Path(result.stdout.strip())
417
- print(f"[MCP Server] Detected workspace from git: {repo_path}", file=sys.stderr)
449
+ _server_log(f"Detected workspace from git: {repo_path}")
418
450
  else:
419
- print(f"[MCP Server] Not in git repo (cwd: {Path.cwd()})", file=sys.stderr)
451
+ _server_log(f"Not in git repo (cwd: {Path.cwd()})")
420
452
 
421
453
  # Method 2: If not in git repo, try to find from Claude session files
422
454
  if not repo_path:
423
455
  claude_projects = Path.home() / ".claude" / "projects"
424
- print(f"[MCP Server] Checking Claude projects at: {claude_projects}", file=sys.stderr)
456
+ _server_log(f"Checking Claude projects at: {claude_projects}")
425
457
  if claude_projects.exists():
426
458
  # Find most recently modified session file
427
459
  session_files = list(claude_projects.glob("*/*.jsonl"))
428
- print(f"[MCP Server] Found {len(session_files)} Claude session files", file=sys.stderr)
460
+ _server_log(f"Found {len(session_files)} Claude session files")
429
461
  if session_files:
430
462
  # Sort by modification time, newest first
431
463
  session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
432
- print(f"[MCP Server] Most recent session: {session_files[0]}", file=sys.stderr)
464
+ _server_log(f"Most recent session: {session_files[0]}")
433
465
  # Extract project path from directory name
434
466
  # Format: -Users-jundewu-Downloads-code-noclue -> /Users/jundewu/Downloads/code/noclue
435
467
  project_dir_name = session_files[0].parent.name
436
- print(f"[MCP Server] Project dir name: {project_dir_name}", file=sys.stderr)
468
+ _server_log(f"Project dir name: {project_dir_name}")
437
469
  if project_dir_name.startswith('-'):
438
470
  # Convert back to path: -Users-foo-bar -> /Users/foo/bar
439
471
  # Note: underscores were also replaced with dashes, but we can't distinguish
440
472
  # So we just replace dashes with slashes
441
473
  path_str = '/' + project_dir_name[1:].replace('-', '/')
442
474
  candidate_path = Path(path_str)
443
- print(f"[MCP Server] Candidate path: {candidate_path}, exists: {candidate_path.exists()}", file=sys.stderr)
475
+ _server_log(f"Candidate path: {candidate_path}, exists: {candidate_path.exists()}")
444
476
  if candidate_path.exists():
445
477
  repo_path = candidate_path
446
- print(f"[MCP Server] Detected workspace from Claude session: {repo_path}", file=sys.stderr)
478
+ _server_log(f"Detected workspace from Claude session: {repo_path}")
447
479
 
448
480
  # Method 3: Fallback to current directory
449
481
  if not repo_path:
450
482
  repo_path = Path.cwd()
451
- print(f"[MCP Server] Using current directory: {repo_path}", file=sys.stderr)
483
+ _server_log(f"Using current directory: {repo_path}")
452
484
 
453
485
  # Start the watcher
454
- print(f"[MCP Server] Starting watcher for: {repo_path}", file=sys.stderr)
486
+ _server_log(f"Starting watcher for: {repo_path}")
455
487
  await start_watcher(repo_path)
456
488
 
457
489
  except Exception as e:
458
- print(f"[MCP Server] Warning: Could not start watcher: {e}", file=sys.stderr)
490
+ _server_log(f"Warning: Could not start watcher: {e}")
459
491
  import traceback
460
492
  traceback.print_exc(file=sys.stderr)
461
493
 
462
- print("[MCP Server] MCP server ready", file=sys.stderr)
494
+ _server_log("MCP server ready")
463
495
 
464
496
  async with stdio_server() as (read_stream, write_stream):
465
497
  await app.run(
@@ -471,7 +503,27 @@ async def async_main():
471
503
 
472
504
  def main():
473
505
  """Entry point for the MCP server command."""
474
- asyncio.run(async_main())
506
+ # Log entry point
507
+ try:
508
+ with open(_early_log_path, "a") as f:
509
+ from datetime import datetime
510
+ f.write(f"[{datetime.now()}] main() called, starting asyncio.run\n")
511
+ except Exception:
512
+ pass
513
+
514
+ try:
515
+ asyncio.run(async_main())
516
+ except Exception as e:
517
+ # Log any exceptions
518
+ try:
519
+ with open(_early_log_path, "a") as f:
520
+ from datetime import datetime
521
+ import traceback
522
+ f.write(f"[{datetime.now()}] EXCEPTION in main: {e}\n")
523
+ f.write(traceback.format_exc())
524
+ except Exception:
525
+ pass
526
+ raise
475
527
 
476
528
 
477
529
  if __name__ == "__main__":
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
- print("[MCP Watcher] Auto-commit disabled in config", file=sys.stderr)
62
+ _log("Auto-commit disabled in config")
34
63
  return
35
64
 
36
65
  self.running = True
37
- print("[MCP Watcher] Started watching for dialogue completion", file=sys.stderr)
38
- print(f"[MCP Watcher] Mode: Per-request (triggers at end of each Claude response)", file=sys.stderr)
39
- print(f"[MCP Watcher] Debounce: {self.debounce_delay}s, Cooldown: {self.min_commit_interval}s", file=sys.stderr)
40
- print(f"[MCP Watcher] Home directory: {Path.home()}", file=sys.stderr)
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
- print(f"[MCP Watcher] Monitoring {len(self.last_session_sizes)} session file(s):", file=sys.stderr)
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
- print(f" - {session_path}", file=sys.stderr)
80
+ _log(f" - {session_path}")
51
81
  if len(self.last_session_sizes) > 5:
52
- print(f" ... and {len(self.last_session_sizes) - 5} more", file=sys.stderr)
82
+ _log(f" ... and {len(self.last_session_sizes) - 5} more")
53
83
  else:
54
84
  claude_projects = Path.home() / ".claude" / "projects"
55
- print(f"[MCP Watcher] WARNING: No session files found in {claude_projects}", file=sys.stderr)
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
- print(f"[MCP Watcher] Error: {e}", file=sys.stderr)
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
- print("[MCP Watcher] Stopped", file=sys.stderr)
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
- print(f"[MCP Watcher] Error getting session sizes: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Error getting stop_reason counts: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Error counting stop_reasons in {session_file}: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Session file changed: {Path(path).name} ({old_size} -> {size} bytes)", file=sys.stderr)
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
- print(f"[MCP Watcher] Error checking for changes: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Complete turn detected in {session_file.name}", file=sys.stderr)
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
- print(f"[MCP Watcher] Skipping commit for {project_path} (cooldown)", file=sys.stderr)
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
- print(f"[MCP Watcher] WARNING: Could not extract project path from {session_file}, skipping commit", file=sys.stderr)
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
- print(f"[MCP Watcher] Error in debounced commit: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Detected {new_stop_reasons} new end_turn entry(ies) in {session_file.name}", file=sys.stderr)
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
- print(f"[MCP Watcher] Error checking turn completion: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Extracting project path from: {project_dir_name}", file=sys.stderr)
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
- print(f"[MCP Watcher] Candidate project path: {candidate_path}", file=sys.stderr)
274
+ _log(f"Candidate project path: {candidate_path}")
245
275
 
246
276
  if candidate_path.exists():
247
- print(f"[MCP Watcher] Project path exists: {candidate_path}", file=sys.stderr)
277
+ _log(f"Project path exists: {candidate_path}")
248
278
  return candidate_path
249
279
  else:
250
- print(f"[MCP Watcher] WARNING: Project path does not exist: {candidate_path}", file=sys.stderr)
280
+ _log(f"WARNING: Project path does not exist: {candidate_path}")
251
281
  else:
252
- print(f"[MCP Watcher] WARNING: Directory name doesn't start with '-': {project_dir_name}", file=sys.stderr)
282
+ _log(f"WARNING: Directory name doesn't start with '-': {project_dir_name}")
253
283
  except Exception as e:
254
- print(f"[MCP Watcher] Error extracting project path: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Attempting commit in {project_path} with message: {message}", file=sys.stderr)
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
- print(f"[MCP Watcher] ✓ Committed in {project_path}: {message}", file=sys.stderr)
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
- print(f"[MCP Watcher] Error during commit: {e}", file=sys.stderr)
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
- print(f"[MCP Watcher] Aline not initialized in {project_path}, initializing...", file=sys.stderr)
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
- print(f"[MCP Watcher] Failed to initialize Aline: {init_result.get('message', 'Unknown error')}", file=sys.stderr)
340
+ _log(f"Failed to initialize Aline: {init_result.get('message', 'Unknown error')}")
311
341
  return False
312
342
 
313
- print("[MCP Watcher] ✓ Aline initialized successfully", file=sys.stderr)
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
- print(f"[MCP Watcher] Commit failed: {error_msg}", file=sys.stderr)
363
+ _log(f"Commit failed: {error_msg}")
334
364
  return False
335
365
 
336
366
  except Exception as e:
337
- print(f"[MCP Watcher] Commit error: {e}", file=sys.stderr)
367
+ _log(f"Commit error: {e}")
338
368
  return False
339
369
 
340
370