aline-ai 0.2.6__py3-none-any.whl → 0.3.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.
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/METADATA +3 -1
- aline_ai-0.3.0.dist-info/RECORD +41 -0
- aline_ai-0.3.0.dist-info/entry_points.txt +3 -0
- realign/__init__.py +32 -1
- realign/cli.py +203 -19
- realign/commands/__init__.py +2 -2
- realign/commands/clean.py +149 -0
- realign/commands/config.py +1 -1
- realign/commands/export_shares.py +1785 -0
- realign/commands/hide.py +112 -24
- realign/commands/import_history.py +873 -0
- realign/commands/init.py +104 -217
- realign/commands/mirror.py +131 -0
- realign/commands/pull.py +101 -0
- realign/commands/push.py +155 -245
- realign/commands/review.py +216 -54
- realign/commands/session_utils.py +139 -4
- realign/commands/share.py +965 -0
- realign/commands/status.py +559 -0
- realign/commands/sync.py +91 -0
- realign/commands/undo.py +423 -0
- realign/commands/watcher.py +805 -0
- realign/config.py +21 -10
- realign/file_lock.py +3 -1
- realign/hash_registry.py +310 -0
- realign/hooks.py +115 -411
- realign/logging_config.py +2 -2
- realign/mcp_server.py +263 -549
- realign/mcp_watcher.py +997 -139
- realign/mirror_utils.py +322 -0
- realign/prompts/__init__.py +21 -0
- realign/prompts/presets.py +238 -0
- realign/redactor.py +168 -16
- realign/tracker/__init__.py +9 -0
- realign/tracker/git_tracker.py +1123 -0
- realign/watcher_daemon.py +115 -0
- aline_ai-0.2.6.dist-info/RECORD +0 -28
- aline_ai-0.2.6.dist-info/entry_points.txt +0 -5
- realign/commands/auto_commit.py +0 -242
- realign/commands/commit.py +0 -379
- realign/commands/search.py +0 -449
- realign/commands/show.py +0 -416
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/top_level.txt +0 -0
realign/commands/commit.py
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
"""ReAlign commit command - Smart commit with session tracking."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import subprocess
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Optional, List, Tuple
|
|
7
|
-
import typer
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
|
|
10
|
-
from ..config import ReAlignConfig
|
|
11
|
-
from ..hooks import find_all_active_sessions
|
|
12
|
-
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def has_file_changes(cwd: Optional[str] = None) -> bool:
|
|
17
|
-
"""
|
|
18
|
-
Check if there are any staged or unstaged file changes (excluding .realign/sessions/).
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
cwd: Working directory for git commands (default: current directory)
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
True if there are file changes, False otherwise
|
|
25
|
-
"""
|
|
26
|
-
try:
|
|
27
|
-
# Check for unstaged changes (excluding .realign/sessions/)
|
|
28
|
-
result = subprocess.run(
|
|
29
|
-
["git", "diff", "--name-only"],
|
|
30
|
-
capture_output=True,
|
|
31
|
-
text=True,
|
|
32
|
-
check=True,
|
|
33
|
-
cwd=cwd,
|
|
34
|
-
)
|
|
35
|
-
unstaged = [
|
|
36
|
-
line.strip()
|
|
37
|
-
for line in result.stdout.strip().split('\n')
|
|
38
|
-
if line.strip() and not line.strip().startswith('.realign/sessions/')
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
# Check for staged changes (excluding .realign/sessions/)
|
|
42
|
-
result = subprocess.run(
|
|
43
|
-
["git", "diff", "--cached", "--name-only"],
|
|
44
|
-
capture_output=True,
|
|
45
|
-
text=True,
|
|
46
|
-
check=True,
|
|
47
|
-
cwd=cwd,
|
|
48
|
-
)
|
|
49
|
-
staged = [
|
|
50
|
-
line.strip()
|
|
51
|
-
for line in result.stdout.strip().split('\n')
|
|
52
|
-
if line.strip() and not line.strip().startswith('.realign/sessions/')
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
# Check for untracked files (excluding .realign/sessions/)
|
|
56
|
-
result = subprocess.run(
|
|
57
|
-
["git", "ls-files", "--others", "--exclude-standard"],
|
|
58
|
-
capture_output=True,
|
|
59
|
-
text=True,
|
|
60
|
-
check=True,
|
|
61
|
-
cwd=cwd,
|
|
62
|
-
)
|
|
63
|
-
untracked = [
|
|
64
|
-
line.strip()
|
|
65
|
-
for line in result.stdout.strip().split('\n')
|
|
66
|
-
if line.strip() and not line.strip().startswith('.realign/sessions/')
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
return bool(unstaged or staged or untracked)
|
|
70
|
-
|
|
71
|
-
except subprocess.CalledProcessError:
|
|
72
|
-
return False
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def has_session_changes(repo_root: Path) -> Tuple[bool, List[Path]]:
|
|
76
|
-
"""
|
|
77
|
-
Check if there are new AI session changes.
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
Tuple of (has_changes, session_files)
|
|
81
|
-
"""
|
|
82
|
-
config = ReAlignConfig.load()
|
|
83
|
-
|
|
84
|
-
# Find all active session files
|
|
85
|
-
session_files = find_all_active_sessions(config, repo_root)
|
|
86
|
-
|
|
87
|
-
if not session_files:
|
|
88
|
-
return False, []
|
|
89
|
-
|
|
90
|
-
# Check if any session file has been modified recently (within last 5 minutes)
|
|
91
|
-
import time
|
|
92
|
-
current_time = time.time()
|
|
93
|
-
recent_sessions = []
|
|
94
|
-
|
|
95
|
-
for session_file in session_files:
|
|
96
|
-
if session_file.exists():
|
|
97
|
-
mtime = session_file.stat().st_mtime
|
|
98
|
-
# Consider sessions modified in the last 5 minutes as "new"
|
|
99
|
-
if current_time - mtime < 300: # 5 minutes
|
|
100
|
-
recent_sessions.append(session_file)
|
|
101
|
-
|
|
102
|
-
return bool(recent_sessions), recent_sessions
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def stage_all_changes():
|
|
106
|
-
"""Stage all changes including untracked files."""
|
|
107
|
-
try:
|
|
108
|
-
subprocess.run(["git", "add", "-A"], check=True)
|
|
109
|
-
console.print("[green]✓[/green] Staged all changes")
|
|
110
|
-
except subprocess.CalledProcessError as e:
|
|
111
|
-
console.print(f"[red]Error staging changes:[/red] {e}")
|
|
112
|
-
raise typer.Exit(1)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def smart_commit(
|
|
116
|
-
message: str,
|
|
117
|
-
repo_path: str = ".",
|
|
118
|
-
stage_all: bool = False,
|
|
119
|
-
amend: bool = False,
|
|
120
|
-
no_edit: bool = False,
|
|
121
|
-
) -> dict:
|
|
122
|
-
"""
|
|
123
|
-
Smart commit function for programmatic use (returns dict instead of using Typer).
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
dict with keys: success, message, no_changes, commit_hash
|
|
127
|
-
"""
|
|
128
|
-
try:
|
|
129
|
-
# Check if we're in a git repository
|
|
130
|
-
result = subprocess.run(
|
|
131
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
132
|
-
cwd=repo_path,
|
|
133
|
-
capture_output=True,
|
|
134
|
-
text=True,
|
|
135
|
-
)
|
|
136
|
-
if result.returncode != 0:
|
|
137
|
-
return {"success": False, "message": "Not in a git repository", "no_changes": False}
|
|
138
|
-
|
|
139
|
-
repo_root = Path(result.stdout.strip())
|
|
140
|
-
|
|
141
|
-
# Stage all changes if requested
|
|
142
|
-
if stage_all:
|
|
143
|
-
subprocess.run(["git", "add", "-A"], cwd=repo_path, check=True)
|
|
144
|
-
|
|
145
|
-
# Check for file changes and session changes
|
|
146
|
-
has_files = has_file_changes(cwd=repo_path)
|
|
147
|
-
has_sessions, session_files = has_session_changes(repo_root)
|
|
148
|
-
|
|
149
|
-
# No changes detected
|
|
150
|
-
if not has_files and not has_sessions:
|
|
151
|
-
return {"success": False, "message": "No changes detected", "no_changes": True}
|
|
152
|
-
|
|
153
|
-
# Build git commit command
|
|
154
|
-
commit_cmd = ["git", "commit", "-m", message]
|
|
155
|
-
|
|
156
|
-
if amend:
|
|
157
|
-
commit_cmd.append("--amend")
|
|
158
|
-
if no_edit:
|
|
159
|
-
commit_cmd.append("--no-edit")
|
|
160
|
-
|
|
161
|
-
# If only session changes, use --allow-empty
|
|
162
|
-
if has_sessions and not has_files:
|
|
163
|
-
commit_cmd.append("--allow-empty")
|
|
164
|
-
|
|
165
|
-
# Execute git commit
|
|
166
|
-
result = subprocess.run(
|
|
167
|
-
commit_cmd,
|
|
168
|
-
cwd=repo_path,
|
|
169
|
-
capture_output=True,
|
|
170
|
-
text=True,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if result.returncode != 0:
|
|
174
|
-
return {"success": False, "message": result.stderr or result.stdout, "no_changes": False}
|
|
175
|
-
|
|
176
|
-
# Get commit hash
|
|
177
|
-
hash_result = subprocess.run(
|
|
178
|
-
["git", "rev-parse", "--short", "HEAD"],
|
|
179
|
-
cwd=repo_path,
|
|
180
|
-
capture_output=True,
|
|
181
|
-
text=True,
|
|
182
|
-
)
|
|
183
|
-
commit_hash = hash_result.stdout.strip() if hash_result.returncode == 0 else "unknown"
|
|
184
|
-
|
|
185
|
-
return {"success": True, "message": "Commit created", "commit_hash": commit_hash, "no_changes": False}
|
|
186
|
-
|
|
187
|
-
except Exception as e:
|
|
188
|
-
return {"success": False, "message": str(e), "no_changes": False}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def commit_command(
|
|
192
|
-
message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message"),
|
|
193
|
-
all_files: bool = typer.Option(False, "--all", "-a", help="Stage all changes before commit"),
|
|
194
|
-
amend: bool = typer.Option(False, "--amend", help="Amend the previous commit"),
|
|
195
|
-
no_edit: bool = typer.Option(False, "--no-edit", help="Use previous commit message (with --amend)"),
|
|
196
|
-
):
|
|
197
|
-
"""
|
|
198
|
-
Smart commit command with AI session tracking.
|
|
199
|
-
|
|
200
|
-
- If there are file changes: acts like `git commit`
|
|
201
|
-
- If there are only session changes: creates an empty commit with session tracking
|
|
202
|
-
- If there are no changes: shows an error
|
|
203
|
-
"""
|
|
204
|
-
# Check if we're in a git repository
|
|
205
|
-
try:
|
|
206
|
-
result = subprocess.run(
|
|
207
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
208
|
-
check=True,
|
|
209
|
-
capture_output=True,
|
|
210
|
-
text=True,
|
|
211
|
-
)
|
|
212
|
-
repo_root = Path(result.stdout.strip())
|
|
213
|
-
except subprocess.CalledProcessError:
|
|
214
|
-
console.print("[red]Error: Not in a git repository[/red]")
|
|
215
|
-
raise typer.Exit(1)
|
|
216
|
-
|
|
217
|
-
# Stage all changes if --all flag is set
|
|
218
|
-
if all_files:
|
|
219
|
-
stage_all_changes()
|
|
220
|
-
|
|
221
|
-
# Check for file changes and session changes
|
|
222
|
-
has_files = has_file_changes()
|
|
223
|
-
has_sessions, session_files = has_session_changes(repo_root)
|
|
224
|
-
|
|
225
|
-
# Determine commit type
|
|
226
|
-
if not has_files and not has_sessions:
|
|
227
|
-
console.print("[yellow]⚠ No changes detected[/yellow]")
|
|
228
|
-
console.print(" • No file changes")
|
|
229
|
-
console.print(" • No recent AI session updates")
|
|
230
|
-
console.print("\n[dim]Tip: Use `git status` to see your repository status[/dim]")
|
|
231
|
-
raise typer.Exit(1)
|
|
232
|
-
|
|
233
|
-
# Build git commit command
|
|
234
|
-
commit_cmd = ["git", "commit"]
|
|
235
|
-
|
|
236
|
-
# Add message if provided
|
|
237
|
-
if message:
|
|
238
|
-
commit_cmd.extend(["-m", message])
|
|
239
|
-
|
|
240
|
-
# Add amend flag if set
|
|
241
|
-
if amend:
|
|
242
|
-
commit_cmd.append("--amend")
|
|
243
|
-
if no_edit:
|
|
244
|
-
commit_cmd.append("--no-edit")
|
|
245
|
-
|
|
246
|
-
# If only session changes, use --allow-empty
|
|
247
|
-
if has_sessions and not has_files:
|
|
248
|
-
commit_cmd.append("--allow-empty")
|
|
249
|
-
console.print("[cyan]💬 Detected AI session changes without file changes[/cyan]")
|
|
250
|
-
console.print(f"[dim] Found {len(session_files)} recent session(s)[/dim]")
|
|
251
|
-
console.print("[cyan] Creating discussion commit...[/cyan]")
|
|
252
|
-
elif has_files and has_sessions:
|
|
253
|
-
console.print("[green]📝 Detected both file changes and AI session updates[/green]")
|
|
254
|
-
elif has_files:
|
|
255
|
-
console.print("[green]📝 Committing file changes[/green]")
|
|
256
|
-
|
|
257
|
-
# Execute git commit
|
|
258
|
-
try:
|
|
259
|
-
# If no message provided and not amending, git will open editor
|
|
260
|
-
result = subprocess.run(
|
|
261
|
-
commit_cmd,
|
|
262
|
-
check=True,
|
|
263
|
-
capture_output=False, # Let git output go to terminal
|
|
264
|
-
)
|
|
265
|
-
console.print("[green]✓[/green] Commit created successfully")
|
|
266
|
-
|
|
267
|
-
# Show commit hash
|
|
268
|
-
result = subprocess.run(
|
|
269
|
-
["git", "rev-parse", "--short", "HEAD"],
|
|
270
|
-
capture_output=True,
|
|
271
|
-
text=True,
|
|
272
|
-
check=True,
|
|
273
|
-
)
|
|
274
|
-
commit_hash = result.stdout.strip()
|
|
275
|
-
console.print(f"[dim] Commit: {commit_hash}[/dim]")
|
|
276
|
-
|
|
277
|
-
except subprocess.CalledProcessError as e:
|
|
278
|
-
console.print(f"[red]Error creating commit:[/red] {e}")
|
|
279
|
-
raise typer.Exit(1)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def commit_internal(
|
|
283
|
-
repo_root: Path,
|
|
284
|
-
message: str,
|
|
285
|
-
all_files: bool = True,
|
|
286
|
-
amend: bool = False,
|
|
287
|
-
no_edit: bool = False,
|
|
288
|
-
) -> Tuple[bool, Optional[str], str]:
|
|
289
|
-
"""
|
|
290
|
-
Internal commit API for programmatic use (e.g., by MCP watcher).
|
|
291
|
-
Does not use typer or rich console - pure Python function.
|
|
292
|
-
|
|
293
|
-
Args:
|
|
294
|
-
repo_root: Path to git repository root
|
|
295
|
-
message: Commit message
|
|
296
|
-
all_files: Stage all changes before commit (default: True)
|
|
297
|
-
amend: Amend the previous commit
|
|
298
|
-
no_edit: Use previous commit message (with --amend)
|
|
299
|
-
|
|
300
|
-
Returns:
|
|
301
|
-
Tuple of (success, commit_hash, message)
|
|
302
|
-
- success: True if commit was created, False otherwise
|
|
303
|
-
- commit_hash: Short commit hash if successful, None otherwise
|
|
304
|
-
- message: Status/error message
|
|
305
|
-
"""
|
|
306
|
-
try:
|
|
307
|
-
# Change to repo directory
|
|
308
|
-
original_cwd = Path.cwd()
|
|
309
|
-
os.chdir(repo_root)
|
|
310
|
-
|
|
311
|
-
try:
|
|
312
|
-
# Stage all changes if requested
|
|
313
|
-
if all_files:
|
|
314
|
-
try:
|
|
315
|
-
subprocess.run(["git", "add", "-A"], check=True, capture_output=True)
|
|
316
|
-
except subprocess.CalledProcessError as e:
|
|
317
|
-
return False, None, f"Failed to stage changes: {e}"
|
|
318
|
-
|
|
319
|
-
# Check for file changes and session changes
|
|
320
|
-
has_files = has_file_changes()
|
|
321
|
-
has_sessions, session_files = has_session_changes(repo_root)
|
|
322
|
-
|
|
323
|
-
# Determine commit type
|
|
324
|
-
if not has_files and not has_sessions:
|
|
325
|
-
return False, None, "No changes detected"
|
|
326
|
-
|
|
327
|
-
# Build git commit command
|
|
328
|
-
commit_cmd = ["git", "commit"]
|
|
329
|
-
|
|
330
|
-
# Add message
|
|
331
|
-
commit_cmd.extend(["-m", message])
|
|
332
|
-
|
|
333
|
-
# Add amend flag if set
|
|
334
|
-
if amend:
|
|
335
|
-
commit_cmd.append("--amend")
|
|
336
|
-
if no_edit:
|
|
337
|
-
commit_cmd.append("--no-edit")
|
|
338
|
-
|
|
339
|
-
# If only session changes, use --allow-empty
|
|
340
|
-
if has_sessions and not has_files:
|
|
341
|
-
commit_cmd.append("--allow-empty")
|
|
342
|
-
|
|
343
|
-
# Execute git commit
|
|
344
|
-
try:
|
|
345
|
-
subprocess.run(
|
|
346
|
-
commit_cmd,
|
|
347
|
-
check=True,
|
|
348
|
-
capture_output=True,
|
|
349
|
-
text=True,
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
# Get commit hash
|
|
353
|
-
result = subprocess.run(
|
|
354
|
-
["git", "rev-parse", "--short", "HEAD"],
|
|
355
|
-
capture_output=True,
|
|
356
|
-
text=True,
|
|
357
|
-
check=True,
|
|
358
|
-
)
|
|
359
|
-
commit_hash = result.stdout.strip()
|
|
360
|
-
|
|
361
|
-
# Build status message
|
|
362
|
-
if has_sessions and not has_files:
|
|
363
|
-
status_msg = f"Discussion commit created (sessions only): {commit_hash}"
|
|
364
|
-
elif has_files and has_sessions:
|
|
365
|
-
status_msg = f"Commit created (files + sessions): {commit_hash}"
|
|
366
|
-
else:
|
|
367
|
-
status_msg = f"Commit created: {commit_hash}"
|
|
368
|
-
|
|
369
|
-
return True, commit_hash, status_msg
|
|
370
|
-
|
|
371
|
-
except subprocess.CalledProcessError as e:
|
|
372
|
-
return False, None, f"Git commit failed: {e.stderr if e.stderr else str(e)}"
|
|
373
|
-
|
|
374
|
-
finally:
|
|
375
|
-
# Restore original directory
|
|
376
|
-
os.chdir(original_cwd)
|
|
377
|
-
|
|
378
|
-
except Exception as e:
|
|
379
|
-
return False, None, f"Unexpected error: {str(e)}"
|