aline-ai 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,11 +1,11 @@
1
- aline_ai-0.2.0.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=BQbPK4WDn_XTLzhYq5k0CThranUR0W-UN21_yU9w-D4,68
1
+ aline_ai-0.2.1.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
+ realign/__init__.py,sha256=sNvIfQwzqITXLdabCJUP5PXKHVVX4aRdXM2HnStJpuY,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/file_lock.py,sha256=-9c3tMdMj_ZxmasK5y6hV9Gfo6KDsSO3Q7PXiTBhsu4,3369
8
- realign/hooks.py,sha256=l4RclCoUaj4w84r3kLSPTh3c9IVqYE5_pv_JKXMp1f8,45076
8
+ realign/hooks.py,sha256=kJXtmATg627dR_5ytER-lyGJwWCgfoANUjIg7gNgAJE,48724
9
9
  realign/logging_config.py,sha256=KvkKktF-bkUu031y9vgUoHpsbnOw7ud25jhpzliNZwA,4929
10
10
  realign/mcp_server.py,sha256=dntFatMpozI80K5hHrIiQ9sviC6ARKTP89goULhi1T4,16477
11
11
  realign/mcp_watcher.py,sha256=D6qVM0yD2hQPxrD_HnHkDob78_dqK96klHIogh48cw4,23971
@@ -14,12 +14,12 @@ realign/commands/__init__.py,sha256=GG6IMw6fUBQAXGJDFJvOOQgv6pkiRSfMh8z3AYXTyRM,
14
14
  realign/commands/auto_commit.py,sha256=jgjAYZHqN34NmQkncZg3Vtwsl3MyAlsvucxEBwUj7ko,7450
15
15
  realign/commands/commit.py,sha256=mlwrv5nfTRY17WlcAdiJKKGh5uM7dGvT7sMxhdbsfkw,12605
16
16
  realign/commands/config.py,sha256=iiu7usqw00djKZja5bx0iDH8DB0vU2maUPMkXLdgXwI,6609
17
- realign/commands/init.py,sha256=_w5_ZzNpdiHO5EJ6KHKwJf3_ne54EXhwuq4sT-XSZ0U,13246
17
+ realign/commands/init.py,sha256=52WkcgdTdsvYXYOuTQ0zR1QF6QOWsjWOuu0f8vLhbL4,13452
18
18
  realign/commands/search.py,sha256=xTWuX0lpjQPX8cen0ewl-BNF0FeWgjMwN06bdeesED8,18770
19
19
  realign/commands/session_utils.py,sha256=L1DwZIGCOBirp6tkAswACJEeDa6i9aAAfsialAs4rRY,864
20
20
  realign/commands/show.py,sha256=A9LvhOBcY6_HoI76irPB2rBOSgdftBuX2uZiO8IwNoU,16338
21
- aline_ai-0.2.0.dist-info/METADATA,sha256=vBklfx2KSYMZbpmnjY9h1A3AhwlCQDdHMr4xEVX2iuM,1398
22
- aline_ai-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- aline_ai-0.2.0.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
24
- aline_ai-0.2.0.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
25
- aline_ai-0.2.0.dist-info/RECORD,,
21
+ aline_ai-0.2.1.dist-info/METADATA,sha256=oG0lXeu_ljDF-lXinBIrfA_Hhk-fbxNG9KqKnw7V8vw,1398
22
+ aline_ai-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ aline_ai-0.2.1.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
24
+ aline_ai-0.2.1.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
25
+ aline_ai-0.2.1.dist-info/RECORD,,
realign/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Aline - AI Agent Chat Session Tracker."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
realign/commands/init.py CHANGED
@@ -119,10 +119,16 @@ def init_repository(
119
119
  hook_path.chmod(0o755)
120
120
  result["hooks_created"].append(f"prepare-commit-msg ({action})")
121
121
 
122
- # Create .gitignore for sessions (optional - commented out for MVP)
122
+ # Create .gitignore for sessions and metadata
123
123
  gitignore_path = realign_dir / ".gitignore"
124
124
  if not gitignore_path.exists():
125
- gitignore_path.write_text("# Uncomment to ignore session files\n# sessions/\n", encoding="utf-8")
125
+ gitignore_content = (
126
+ "# Uncomment to ignore session files\n"
127
+ "# sessions/\n\n"
128
+ "# Ignore metadata files (used internally to prevent duplicate processing)\n"
129
+ ".metadata/\n"
130
+ )
131
+ gitignore_path.write_text(gitignore_content, encoding="utf-8")
126
132
 
127
133
  # Backup and set core.hooksPath
128
134
  backup_file = realign_dir / "backup_hook_config.yaml"
realign/hooks.py CHANGED
@@ -689,12 +689,12 @@ def copy_session_to_repo(
689
689
  repo_root: Path,
690
690
  user: str,
691
691
  config: Optional[ReAlignConfig] = None
692
- ) -> Tuple[Path, str, bool]:
692
+ ) -> Tuple[Path, str, bool, int]:
693
693
  """
694
694
  Copy session file to repository .realign/sessions/ directory.
695
695
  Optionally redacts sensitive information if configured.
696
696
  If the source filename is in UUID format, renames it to include username for better identification.
697
- Returns (absolute_path, relative_path, was_redacted).
697
+ Returns (absolute_path, relative_path, was_redacted, content_size).
698
698
  """
699
699
  logger.info(f"Copying session to repo: {session_file.name}")
700
700
  logger.debug(f"Source: {session_file}, Repo root: {repo_root}, User: {user}")
@@ -746,7 +746,12 @@ def copy_session_to_repo(
746
746
  temp_path.rename(dest_path)
747
747
  rel_path = dest_path.relative_to(repo_root)
748
748
  logger.warning(f"Copied session with fallback (no agent detection): {rel_path}")
749
- return dest_path, str(rel_path), False
749
+ # Get file size for the fallback case
750
+ try:
751
+ fallback_size = dest_path.stat().st_size
752
+ except Exception:
753
+ fallback_size = 0
754
+ return dest_path, str(rel_path), False, fallback_size
750
755
 
751
756
  # Detect agent type from session content
752
757
  agent_type = "unknown"
@@ -843,9 +848,68 @@ def copy_session_to_repo(
843
848
  shutil.copy2(session_file, dest_path)
844
849
  logger.warning("Fallback to simple copy")
845
850
 
846
- # Return both absolute and relative paths, plus redaction status
851
+ # Return both absolute and relative paths, plus redaction status and content size
847
852
  rel_path = dest_path.relative_to(repo_root)
848
- return dest_path, str(rel_path), was_redacted
853
+ content_size = len(content)
854
+ return dest_path, str(rel_path), was_redacted, content_size
855
+
856
+
857
+ def save_session_metadata(repo_root: Path, session_relpath: str, content_size: int):
858
+ """
859
+ Save metadata about a processed session to avoid reprocessing.
860
+
861
+ Args:
862
+ repo_root: Path to repository root
863
+ session_relpath: Relative path to session file
864
+ content_size: Size of session content when processed
865
+ """
866
+ metadata_dir = repo_root / ".realign" / ".metadata"
867
+ metadata_dir.mkdir(parents=True, exist_ok=True)
868
+
869
+ # Use session filename as metadata key
870
+ session_name = Path(session_relpath).name
871
+ metadata_file = metadata_dir / f"{session_name}.meta"
872
+
873
+ metadata = {
874
+ "processed_at": time.time(),
875
+ "content_size": content_size,
876
+ "session_relpath": session_relpath,
877
+ }
878
+
879
+ try:
880
+ with open(metadata_file, 'w', encoding='utf-8') as f:
881
+ json.dump(metadata, f)
882
+ logger.debug(f"Saved metadata for {session_relpath}: {content_size} bytes")
883
+ except Exception as e:
884
+ logger.warning(f"Failed to save metadata for {session_relpath}: {e}")
885
+
886
+
887
+ def get_session_metadata(repo_root: Path, session_relpath: str) -> Optional[Dict[str, Any]]:
888
+ """
889
+ Get metadata about a previously processed session.
890
+
891
+ Args:
892
+ repo_root: Path to repository root
893
+ session_relpath: Relative path to session file
894
+
895
+ Returns:
896
+ Metadata dictionary or None if not found
897
+ """
898
+ metadata_dir = repo_root / ".realign" / ".metadata"
899
+ session_name = Path(session_relpath).name
900
+ metadata_file = metadata_dir / f"{session_name}.meta"
901
+
902
+ if not metadata_file.exists():
903
+ return None
904
+
905
+ try:
906
+ with open(metadata_file, 'r', encoding='utf-8') as f:
907
+ metadata = json.load(f)
908
+ logger.debug(f"Loaded metadata for {session_relpath}: {metadata.get('content_size')} bytes")
909
+ return metadata
910
+ except Exception as e:
911
+ logger.warning(f"Failed to load metadata for {session_relpath}: {e}")
912
+ return None
849
913
 
850
914
 
851
915
  def process_sessions(
@@ -915,13 +979,15 @@ def process_sessions(
915
979
 
916
980
  # Copy all sessions to repo (with optional redaction)
917
981
  session_relpaths = []
982
+ session_metadata_map = {} # Map session_relpath -> content_size
918
983
  any_redacted = False
919
984
  for session_file in session_files:
920
985
  try:
921
- _, session_relpath, was_redacted = copy_session_to_repo(
986
+ _, session_relpath, was_redacted, content_size = copy_session_to_repo(
922
987
  session_file, repo_root, user, config
923
988
  )
924
989
  session_relpaths.append(session_relpath)
990
+ session_metadata_map[session_relpath] = content_size
925
991
  if was_redacted:
926
992
  any_redacted = True
927
993
  except Exception as e:
@@ -941,8 +1007,13 @@ def process_sessions(
941
1007
 
942
1008
  logger.info(f"Copied {len(session_relpaths)} session(s): {session_relpaths}")
943
1009
 
944
- # If pre-commit mode, just return session paths (summary will be generated later)
1010
+ # If pre-commit mode, save metadata and return session paths (summary will be generated later)
945
1011
  if pre_commit_mode:
1012
+ # Save metadata for each session to prevent reprocessing
1013
+ for session_relpath, content_size in session_metadata_map.items():
1014
+ save_session_metadata(repo_root, session_relpath, content_size)
1015
+ logger.debug(f"Saved metadata for {session_relpath} in pre-commit")
1016
+
946
1017
  elapsed = time.time() - start_time
947
1018
  logger.info(f"======== Hook completed: {hook_type} in {elapsed:.2f}s ========")
948
1019
  return {
@@ -974,6 +1045,19 @@ def process_sessions(
974
1045
  summary_model_label: Optional[str] = None
975
1046
 
976
1047
  for session_relpath in session_relpaths:
1048
+ # Check if this session was already processed in pre-commit hook
1049
+ previous_metadata = get_session_metadata(repo_root, session_relpath)
1050
+ current_size = session_metadata_map.get(session_relpath, 0)
1051
+
1052
+ if previous_metadata:
1053
+ previous_size = previous_metadata.get("content_size", 0)
1054
+ if previous_size == current_size:
1055
+ logger.info(f"Session {session_relpath} unchanged since pre-commit (size: {current_size}), skipping")
1056
+ print(f"⏭️ Skipping {Path(session_relpath).name} (no new content since pre-commit)", file=sys.stderr)
1057
+ continue
1058
+ else:
1059
+ logger.info(f"Session {session_relpath} size changed: {previous_size} -> {current_size}")
1060
+
977
1061
  # Extract NEW content using git diff
978
1062
  new_content = get_new_content_from_git_diff(repo_root, session_relpath)
979
1063
 
@@ -1020,6 +1104,10 @@ def process_sessions(
1020
1104
  })
1021
1105
  legacy_summary_chunks.append(f"[{agent_name}] {summary_text}")
1022
1106
 
1107
+ # Update metadata after successfully generating summary
1108
+ save_session_metadata(repo_root, session_relpath, current_size)
1109
+ logger.debug(f"Updated metadata for {session_relpath} in prepare-commit-msg")
1110
+
1023
1111
  # Combine all summaries
1024
1112
  if summary_entries:
1025
1113
  if summary_model_label is None: