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
realign/commands/push.py CHANGED
@@ -1,290 +1,200 @@
1
- #!/usr/bin/env python3
2
- """
3
- Push command - Interactive workflow for reviewing and pushing commits.
4
-
5
- This command provides an interactive workflow:
6
- 1. Run aline review to show unpushed commits
7
- 2. Ask user if they want to hide any commits
8
- 3. If yes, allow interactive hiding
9
- 4. Ask for final confirmation before pushing
10
- """
11
-
12
- import subprocess
13
- import sys
1
+ """Push command - Push session commits to remote repository."""
2
+
3
+ import os
14
4
  from pathlib import Path
15
5
  from typing import Optional
16
6
 
17
- from .review import review_command, get_unpushed_commits
18
- from .hide import hide_command, parse_commit_indices
7
+ from ..tracker.git_tracker import ReAlignGitTracker
19
8
  from ..logging_config import setup_logger
20
9
 
21
10
  logger = setup_logger('realign.commands.push', 'push.log')
22
11
 
23
12
 
24
- def prompt_yes_no(question: str, default: bool = False) -> bool:
13
+ def push_command(
14
+ force: bool = False,
15
+ repo_root: Optional[Path] = None
16
+ ) -> int:
25
17
  """
26
- Prompt user for yes/no confirmation.
18
+ Push session commits to remote repository.
19
+
20
+ This command pushes your local session commits to the configured
21
+ remote repository, making them available to your team.
27
22
 
28
23
  Args:
29
- question: Question to ask
30
- default: Default answer if user just presses Enter
24
+ force: If True, force push (use with caution)
25
+ repo_root: Path to repository root (uses cwd if not provided)
31
26
 
32
27
  Returns:
33
- True for yes, False for no
28
+ Exit code (0 for success, 1 for error)
34
29
  """
35
- default_str = "Y/n" if default else "y/N"
36
- prompt = f"{question} [{default_str}] "
30
+ # Get project root
31
+ if repo_root is None:
32
+ repo_root = Path(os.getcwd()).resolve()
33
+
34
+ # Initialize tracker
35
+ tracker = ReAlignGitTracker(repo_root)
37
36
 
38
- while True:
39
- response = input(prompt).strip().lower()
37
+ # Check if repository is initialized
38
+ if not tracker.is_initialized():
39
+ print("❌ Repository not initialized")
40
+ print("Run 'aline init' first")
41
+ return 1
42
+
43
+ # Check if remote is configured
44
+ if not tracker.has_remote():
45
+ print("❌ No remote configured")
46
+ print("\nTo set up sharing:")
47
+ print(" aline init --share (browser-based setup)")
48
+ print(" aline share configure (manual configuration)")
49
+ return 1
40
50
 
41
- if not response:
42
- return default
51
+ # Get unpushed commits
52
+ unpushed = tracker.get_unpushed_commits()
43
53
 
44
- if response in ('y', 'yes'):
45
- return True
46
- elif response in ('n', 'no'):
47
- return False
54
+ if not unpushed:
55
+ print("✓ All commits are already pushed")
56
+ print(f"\nRemote: {tracker.get_remote_url()}")
57
+ return 0
58
+
59
+ # Show review of unpushed commits
60
+ print(f"Found {len(unpushed)} unpushed commit(s)\n")
61
+
62
+ # Display commits summary
63
+ try:
64
+ display_commits_summary(tracker, unpushed)
65
+ except Exception as e:
66
+ logger.warning(f"Failed to display commit details: {e}")
67
+ # Just show hashes
68
+ for commit_hash in unpushed:
69
+ print(f" {commit_hash[:8]}")
70
+
71
+ print()
72
+
73
+ # For non-interactive mode, just push
74
+ if force:
75
+ print("Force pushing...")
76
+ success = tracker.safe_push(force=True)
77
+ if success:
78
+ print(f"✓ Successfully force-pushed {len(unpushed)} commit(s)")
79
+ print(f"\nRemote: {tracker.get_remote_url()}")
80
+ return 0
48
81
  else:
49
- print("Please answer 'y' or 'n'")
82
+ print(" Push failed")
83
+ return 1
50
84
 
85
+ # Interactive mode - prompt user
86
+ print("Push these commit(s)?")
87
+ print(" [Y] Push all")
88
+ print(" [n] Cancel")
89
+ print(" [h] Hide specific commits")
90
+ print()
51
91
 
52
- def push_command(
53
- repo_root: Optional[Path] = None,
54
- remote: str = "origin",
55
- branch: Optional[str] = None,
56
- force: bool = False,
57
- dry_run: bool = False
58
- ) -> int:
59
- """
60
- Interactive push workflow with review and hide options.
92
+ choice = input("Select [Y/n/h]: ").strip().lower()
61
93
 
62
- Workflow:
63
- 1. Review unpushed commits
64
- 2. Ask if user wants to hide any commits
65
- 3. Interactive hiding loop (if requested)
66
- 4. Final confirmation
67
- 5. Execute git push
94
+ if choice == 'n':
95
+ print("Cancelled")
96
+ return 0
68
97
 
69
- Args:
70
- repo_root: Path to repository root (auto-detected if None)
71
- remote: Git remote name (default: origin)
72
- branch: Branch to push (default: current branch)
73
- force: Skip all confirmation prompts
74
- dry_run: Don't actually push, just show what would happen
98
+ if choice == 'h':
99
+ # Interactive hide mode
100
+ print("\nEnter commit indices to hide (comma-separated, e.g., 1,3):")
101
+ print("Commits are numbered from the list above")
75
102
 
76
- Returns:
77
- 0 on success, 1 on error, 2 if cancelled by user
78
- """
79
- logger.info("======== Push command started ========")
103
+ indices_str = input("Indices: ").strip()
80
104
 
81
- # Auto-detect repo root if not provided
82
- if repo_root is None:
83
- try:
84
- result = subprocess.run(
85
- ["git", "rev-parse", "--show-toplevel"],
86
- capture_output=True,
87
- text=True,
88
- check=True
89
- )
90
- repo_root = Path(result.stdout.strip())
91
- logger.debug(f"Detected repo root: {repo_root}")
92
- except subprocess.CalledProcessError:
93
- print("Error: Not in a git repository", file=sys.stderr)
94
- logger.error("Not in a git repository")
95
- return 1
105
+ if indices_str:
106
+ try:
107
+ # Parse indices
108
+ indices = [int(i.strip()) for i in indices_str.split(',')]
96
109
 
97
- # Detect current branch if not provided
98
- if branch is None:
99
- try:
100
- result = subprocess.run(
101
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
102
- cwd=repo_root,
103
- capture_output=True,
104
- text=True,
105
- check=True
106
- )
107
- branch = result.stdout.strip()
108
- logger.debug(f"Detected current branch: {branch}")
109
- except subprocess.CalledProcessError:
110
- print("Error: Failed to detect current branch", file=sys.stderr)
111
- logger.error("Failed to detect current branch")
112
- return 1
110
+ # Validate indices
111
+ valid_indices = [i for i in indices if 1 <= i <= len(unpushed)]
113
112
 
114
- # Step 1: Review unpushed commits
115
- print("\n" + "=" * 60)
116
- print("STEP 1: Review unpushed commits")
117
- print("=" * 60 + "\n")
113
+ if not valid_indices:
114
+ print("No valid indices provided, cancelling")
115
+ return 0
118
116
 
119
- try:
120
- commits = get_unpushed_commits(repo_root)
121
- except Exception as e:
122
- print(f"Error: Failed to get unpushed commits: {e}", file=sys.stderr)
123
- logger.error(f"Failed to get unpushed commits: {e}", exc_info=True)
124
- return 1
117
+ # Hide commits using the hide command
118
+ from .hide import hide_command
125
119
 
126
- if not commits:
127
- print("✓ No unpushed commits found. Nothing to push.\n")
128
- logger.info("No unpushed commits found")
129
- return 0
120
+ # Convert indices back to string format for hide command
121
+ indices_for_hide = ','.join(map(str, valid_indices))
130
122
 
131
- # Run review command to display commits
132
- exit_code = review_command(repo_root=repo_root, verbose=False, detect_secrets=False)
133
- if exit_code != 0:
134
- logger.error("Review command failed")
135
- return 1
123
+ if hide_command(indices=indices_for_hide, repo_root=repo_root, force=True) == 0:
124
+ print(f"\n✓ Hidden {len(valid_indices)} commit(s)")
136
125
 
137
- # Step 2: Ask if user wants to hide any commits
138
- if not force:
139
- print("\n" + "=" * 60)
140
- print("STEP 2: Hide sensitive commits (optional)")
141
- print("=" * 60 + "\n")
126
+ # Re-check unpushed commits
127
+ unpushed = tracker.get_unpushed_commits()
142
128
 
143
- want_to_hide = prompt_yes_no(
144
- "Do you want to hide any commits before pushing?",
145
- default=False
146
- )
129
+ if not unpushed:
130
+ print("All commits have been hidden")
131
+ return 0
147
132
 
148
- if want_to_hide:
149
- # Interactive hiding loop
150
- while True:
151
- print("\nEnter commit indices to hide (e.g., '1', '1,3,5', '2-4', or 'done'):")
152
- print(" Type 'done' when finished hiding commits")
153
- print(" Type 'review' to see the current list of commits")
154
- print(" Type 'cancel' to abort the push operation")
155
-
156
- user_input = input("\n> ").strip()
157
-
158
- if not user_input:
159
- continue
160
-
161
- if user_input.lower() == 'done':
162
- break
163
- elif user_input.lower() == 'cancel':
164
- print("\n❌ Push cancelled by user.\n")
165
- logger.info("Push cancelled by user during hide phase")
166
- return 2
167
- elif user_input.lower() == 'review':
168
- print("\n" + "-" * 60)
169
- review_command(repo_root=repo_root, verbose=False, detect_secrets=False)
170
- print("-" * 60)
171
- continue
172
-
173
- # Try to parse and hide the commits
174
- try:
175
- # Validate indices first
176
- indices_list = parse_commit_indices(user_input)
177
-
178
- # Refresh commit list
179
- commits = get_unpushed_commits(repo_root)
180
- if not commits:
181
- print("\n✓ No more unpushed commits to hide.\n")
182
- break
183
-
184
- max_index = len(commits)
185
- invalid_indices = [i for i in indices_list if i < 1 or i > max_index]
186
- if invalid_indices:
187
- print(f"❌ Invalid indices (out of range 1-{max_index}): {invalid_indices}")
188
- continue
189
-
190
- # Execute hide command
191
- print()
192
- exit_code = hide_command(
193
- indices=user_input,
194
- repo_root=repo_root,
195
- force=False # Always ask for confirmation
196
- )
197
-
198
- if exit_code == 0:
199
- print("\n✅ Commits hidden successfully.\n")
200
- logger.info(f"Successfully hidden commits: {user_input}")
201
-
202
- # Refresh and show updated commit list
203
- commits = get_unpushed_commits(repo_root)
204
- if not commits:
205
- print("✓ No more unpushed commits.\n")
206
- logger.info("No commits left after hiding")
207
- return 0
208
- else:
209
- print("\n📋 Updated commit list:\n")
210
- review_command(repo_root=repo_root, verbose=False, detect_secrets=False)
211
- else:
212
- print(f"\n❌ Failed to hide commits.\n")
213
- logger.error(f"Hide command failed for indices: {user_input}")
214
-
215
- # Ask if user wants to continue
216
- if not prompt_yes_no("Continue with push workflow?", default=True):
217
- print("\n❌ Push cancelled.\n")
218
- return 2
219
-
220
- except ValueError as e:
221
- print(f"❌ Invalid input: {e}")
222
- print(" Examples: '1', '1,3,5', '2-4'")
223
- continue
224
- except Exception as e:
225
- print(f"❌ Error during hide: {e}", file=sys.stderr)
226
- logger.error(f"Error during hide: {e}", exc_info=True)
227
-
228
- # Ask if user wants to continue
229
- if not prompt_yes_no("Continue with push workflow?", default=True):
230
- print("\n❌ Push cancelled.\n")
231
- return 2
232
-
233
- # Step 3: Final confirmation
234
- if not force:
235
- print("\n" + "=" * 60)
236
- print("STEP 3: Final confirmation")
237
- print("=" * 60 + "\n")
238
-
239
- # Refresh commit list one more time
240
- try:
241
- commits = get_unpushed_commits(repo_root)
242
- except Exception as e:
243
- print(f"Error: Failed to refresh commits: {e}", file=sys.stderr)
244
- return 1
133
+ print(f"\nRemaining {len(unpushed)} commit(s) to push")
245
134
 
246
- if not commits:
247
- print("✓ No unpushed commits. Nothing to push.\n")
248
- return 0
135
+ confirm = input("Proceed with push? [Y/n]: ").strip().lower()
136
+ if confirm == 'n':
137
+ print("Cancelled")
138
+ return 0
139
+ else:
140
+ print("❌ Failed to hide commits")
141
+ return 1
249
142
 
250
- print(f"Ready to push {len(commits)} commit(s) to {remote}/{branch}\n")
143
+ except ValueError:
144
+ print("Invalid input format")
145
+ return 1
251
146
 
252
- if not prompt_yes_no("Proceed with git push?", default=True):
253
- print("\n❌ Push cancelled by user.\n")
254
- logger.info("Push cancelled by user at final confirmation")
255
- return 2
147
+ # Perform push
148
+ print("\nPushing to remote...")
256
149
 
257
- # Step 4: Execute git push
258
- print("\n" + "=" * 60)
259
- print("STEP 4: Pushing to remote")
260
- print("=" * 60 + "\n")
150
+ success = tracker.safe_push(force=force)
261
151
 
262
- if dry_run:
263
- print(f"🔍 DRY RUN: Would execute: git push {remote} {branch}\n")
264
- logger.info(f"Dry run: would push to {remote}/{branch}")
152
+ if success:
153
+ print(f" Successfully pushed {len(unpushed)} commit(s)")
154
+ print(f"\nRemote: {tracker.get_remote_url()}")
265
155
  return 0
156
+ else:
157
+ print("❌ Push failed")
158
+ print("\nTroubleshooting:")
159
+ print(" - Check network connection")
160
+ print(" - Verify remote repository access")
161
+ print(" - Check logs: .realign/logs/push.log")
162
+ return 1
266
163
 
267
- try:
268
- print(f"🚀 Pushing to {remote}/{branch}...")
269
164
 
165
+ def display_commits_summary(tracker: ReAlignGitTracker, commit_hashes: list):
166
+ """
167
+ Display a summary of commits to be pushed.
168
+
169
+ Args:
170
+ tracker: ReAlignGitTracker instance
171
+ commit_hashes: List of commit hashes
172
+ """
173
+ import subprocess
174
+
175
+ for i, commit_hash in enumerate(commit_hashes, 1):
176
+ # Get commit message
270
177
  result = subprocess.run(
271
- ["git", "push", remote, branch],
272
- cwd=repo_root,
178
+ ["git", "log", "-1", "--format=%s", commit_hash],
179
+ cwd=tracker.realign_dir,
273
180
  capture_output=True,
274
- text=True
181
+ text=True,
182
+ check=False
275
183
  )
276
184
 
277
- if result.returncode == 0:
278
- print("\n✅ Successfully pushed to remote!\n")
279
- print(result.stdout)
280
- logger.info(f"Successfully pushed to {remote}/{branch}")
281
- return 0
282
- else:
283
- print(f"\n❌ Push failed:\n{result.stderr}\n", file=sys.stderr)
284
- logger.error(f"Git push failed: {result.stderr}")
285
- return 1
185
+ message = result.stdout.strip() if result.returncode == 0 else "Unknown"
286
186
 
287
- except Exception as e:
288
- print(f"\n❌ Error during push: {e}\n", file=sys.stderr)
289
- logger.error(f"Error during push: {e}", exc_info=True)
290
- return 1
187
+ # Get timestamp
188
+ result = subprocess.run(
189
+ ["git", "log", "-1", "--format=%ar", commit_hash],
190
+ cwd=tracker.realign_dir,
191
+ capture_output=True,
192
+ text=True,
193
+ check=False
194
+ )
195
+
196
+ timestamp = result.stdout.strip() if result.returncode == 0 else ""
197
+
198
+ print(f"[{i}] {commit_hash[:8]} - {message}")
199
+ if timestamp:
200
+ print(f" {timestamp}")