aline-ai 0.1.10__py3-none-any.whl → 0.2.1__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.10.dist-info → aline_ai-0.2.1.dist-info}/METADATA +1 -1
- aline_ai-0.2.1.dist-info/RECORD +25 -0
- realign/__init__.py +1 -1
- realign/commands/auto_commit.py +1 -1
- realign/commands/commit.py +100 -0
- realign/commands/config.py +13 -12
- realign/commands/init.py +48 -16
- realign/commands/search.py +57 -31
- realign/commands/session_utils.py +28 -0
- realign/commands/show.py +25 -38
- realign/file_lock.py +120 -0
- realign/hooks.py +362 -49
- realign/mcp_server.py +4 -54
- realign/mcp_watcher.py +356 -253
- aline_ai-0.1.10.dist-info/RECORD +0 -23
- {aline_ai-0.1.10.dist-info → aline_ai-0.2.1.dist-info}/WHEEL +0 -0
- {aline_ai-0.1.10.dist-info → aline_ai-0.2.1.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.1.10.dist-info → aline_ai-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.1.10.dist-info → aline_ai-0.2.1.dist-info}/top_level.txt +0 -0
realign/file_lock.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""File-based locking mechanism for cross-process synchronization."""
|
|
2
|
+
|
|
3
|
+
import fcntl
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileLock:
|
|
12
|
+
"""Simple file-based lock using fcntl (Unix/macOS only)."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, lock_file: Path, timeout: float = 10.0):
|
|
15
|
+
"""
|
|
16
|
+
Initialize a file lock.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
lock_file: Path to the lock file
|
|
20
|
+
timeout: Maximum time to wait for lock acquisition (seconds)
|
|
21
|
+
"""
|
|
22
|
+
self.lock_file = lock_file
|
|
23
|
+
self.timeout = timeout
|
|
24
|
+
self.fd: Optional[int] = None
|
|
25
|
+
|
|
26
|
+
def acquire(self, blocking: bool = True) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Acquire the lock.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
blocking: If True, wait for lock; if False, return immediately
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if lock was acquired, False otherwise
|
|
35
|
+
"""
|
|
36
|
+
# Create lock file directory if needed
|
|
37
|
+
self.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
# Open lock file
|
|
40
|
+
self.fd = os.open(str(self.lock_file), os.O_CREAT | os.O_RDWR)
|
|
41
|
+
|
|
42
|
+
if blocking:
|
|
43
|
+
# Try to acquire with timeout
|
|
44
|
+
start_time = time.time()
|
|
45
|
+
while True:
|
|
46
|
+
try:
|
|
47
|
+
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
48
|
+
return True
|
|
49
|
+
except BlockingIOError:
|
|
50
|
+
if time.time() - start_time > self.timeout:
|
|
51
|
+
os.close(self.fd)
|
|
52
|
+
self.fd = None
|
|
53
|
+
return False
|
|
54
|
+
time.sleep(0.1)
|
|
55
|
+
else:
|
|
56
|
+
# Non-blocking attempt
|
|
57
|
+
try:
|
|
58
|
+
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
59
|
+
return True
|
|
60
|
+
except BlockingIOError:
|
|
61
|
+
os.close(self.fd)
|
|
62
|
+
self.fd = None
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def release(self):
|
|
66
|
+
"""Release the lock."""
|
|
67
|
+
if self.fd is not None:
|
|
68
|
+
try:
|
|
69
|
+
fcntl.flock(self.fd, fcntl.LOCK_UN)
|
|
70
|
+
os.close(self.fd)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
finally:
|
|
74
|
+
self.fd = None
|
|
75
|
+
|
|
76
|
+
def __enter__(self):
|
|
77
|
+
"""Context manager entry."""
|
|
78
|
+
if not self.acquire():
|
|
79
|
+
raise TimeoutError(f"Could not acquire lock on {self.lock_file} within {self.timeout}s")
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
83
|
+
"""Context manager exit."""
|
|
84
|
+
self.release()
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def __del__(self):
|
|
88
|
+
"""Cleanup on deletion."""
|
|
89
|
+
self.release()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@contextmanager
|
|
93
|
+
def commit_lock(repo_path: Path, timeout: float = 10.0):
|
|
94
|
+
"""
|
|
95
|
+
Context manager for acquiring a commit lock.
|
|
96
|
+
|
|
97
|
+
Prevents multiple watchers from committing simultaneously.
|
|
98
|
+
|
|
99
|
+
Usage:
|
|
100
|
+
with commit_lock(repo_path):
|
|
101
|
+
# Perform git commit
|
|
102
|
+
subprocess.run(["git", "commit", ...])
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
repo_path: Path to the repository
|
|
106
|
+
timeout: Maximum time to wait for lock (seconds)
|
|
107
|
+
|
|
108
|
+
Yields:
|
|
109
|
+
True if lock was acquired
|
|
110
|
+
"""
|
|
111
|
+
lock_file = repo_path / ".realign" / ".commit.lock"
|
|
112
|
+
lock = FileLock(lock_file, timeout=timeout)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
if lock.acquire():
|
|
116
|
+
yield True
|
|
117
|
+
else:
|
|
118
|
+
yield False
|
|
119
|
+
finally:
|
|
120
|
+
lock.release()
|