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.
Files changed (45) hide show
  1. {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/METADATA +3 -1
  2. aline_ai-0.3.0.dist-info/RECORD +41 -0
  3. aline_ai-0.3.0.dist-info/entry_points.txt +3 -0
  4. realign/__init__.py +32 -1
  5. realign/cli.py +203 -19
  6. realign/commands/__init__.py +2 -2
  7. realign/commands/clean.py +149 -0
  8. realign/commands/config.py +1 -1
  9. realign/commands/export_shares.py +1785 -0
  10. realign/commands/hide.py +112 -24
  11. realign/commands/import_history.py +873 -0
  12. realign/commands/init.py +104 -217
  13. realign/commands/mirror.py +131 -0
  14. realign/commands/pull.py +101 -0
  15. realign/commands/push.py +155 -245
  16. realign/commands/review.py +216 -54
  17. realign/commands/session_utils.py +139 -4
  18. realign/commands/share.py +965 -0
  19. realign/commands/status.py +559 -0
  20. realign/commands/sync.py +91 -0
  21. realign/commands/undo.py +423 -0
  22. realign/commands/watcher.py +805 -0
  23. realign/config.py +21 -10
  24. realign/file_lock.py +3 -1
  25. realign/hash_registry.py +310 -0
  26. realign/hooks.py +115 -411
  27. realign/logging_config.py +2 -2
  28. realign/mcp_server.py +263 -549
  29. realign/mcp_watcher.py +997 -139
  30. realign/mirror_utils.py +322 -0
  31. realign/prompts/__init__.py +21 -0
  32. realign/prompts/presets.py +238 -0
  33. realign/redactor.py +168 -16
  34. realign/tracker/__init__.py +9 -0
  35. realign/tracker/git_tracker.py +1123 -0
  36. realign/watcher_daemon.py +115 -0
  37. aline_ai-0.2.6.dist-info/RECORD +0 -28
  38. aline_ai-0.2.6.dist-info/entry_points.txt +0 -5
  39. realign/commands/auto_commit.py +0 -242
  40. realign/commands/commit.py +0 -379
  41. realign/commands/search.py +0 -449
  42. realign/commands/show.py +0 -416
  43. {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
  44. {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
  45. {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/top_level.txt +0 -0
@@ -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)}"