aline-ai 0.6.2__py3-none-any.whl → 0.6.3__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.
@@ -8,7 +8,6 @@ from .base import TurnTrigger, TurnInfo
8
8
  from .claude_trigger import ClaudeTrigger
9
9
  from .codex_trigger import CodexTrigger
10
10
  from .gemini_trigger import GeminiTrigger
11
- from .antigravity_trigger import AntigravityTrigger
12
11
  from .next_turn_trigger import NextTurnTrigger
13
12
  from .registry import TriggerRegistry, get_global_registry
14
13
 
@@ -19,7 +18,6 @@ __all__ = [
19
18
  "ClaudeTrigger",
20
19
  "CodexTrigger",
21
20
  "GeminiTrigger",
22
- "AntigravityTrigger",
23
21
  "TriggerRegistry",
24
22
  "get_global_registry",
25
23
  ]
@@ -2,7 +2,7 @@
2
2
  NextTurnTrigger
3
3
 
4
4
  A generic delegating trigger that auto-detects the session format and dispatches
5
- to the appropriate concrete trigger (Claude/Codex/Gemini/Antigravity).
5
+ to the appropriate concrete trigger (Claude/Codex/Gemini).
6
6
 
7
7
  This is used as a stable default trigger name ("next_turn") across the codebase.
8
8
  """
@@ -17,16 +17,15 @@ from .base import TurnInfo, TurnTrigger
17
17
  from .claude_trigger import ClaudeTrigger
18
18
  from .codex_trigger import CodexTrigger
19
19
  from .gemini_trigger import GeminiTrigger
20
- from .antigravity_trigger import AntigravityTrigger
21
20
 
22
21
 
23
22
  class NextTurnTrigger(TurnTrigger):
24
23
  """Auto-detect trigger for any supported session type."""
25
24
 
26
25
  def select_trigger(self, session_file: Path) -> Optional[TurnTrigger]:
27
- # Antigravity sessions are directories under ~/.gemini/antigravity/brain/<uuid>
26
+ # Skip directories (not a supported session format)
28
27
  if session_file.is_dir():
29
- return AntigravityTrigger(self.config)
28
+ return None
30
29
 
31
30
  # Heuristics based on path
32
31
  path_str = str(session_file).lower()
@@ -87,7 +86,7 @@ class NextTurnTrigger(TurnTrigger):
87
86
  def get_supported_formats(self) -> List[str]:
88
87
  # Union of underlying triggers.
89
88
  formats: List[str] = []
90
- for cls in (ClaudeTrigger, CodexTrigger, GeminiTrigger, AntigravityTrigger):
89
+ for cls in (ClaudeTrigger, CodexTrigger, GeminiTrigger):
91
90
  try:
92
91
  formats.extend(cls(self.config).get_supported_formats())
93
92
  except Exception:
@@ -11,7 +11,6 @@ from .base import TurnTrigger
11
11
  from .claude_trigger import ClaudeTrigger
12
12
  from .codex_trigger import CodexTrigger
13
13
  from .gemini_trigger import GeminiTrigger
14
- from .antigravity_trigger import AntigravityTrigger
15
14
  from .next_turn_trigger import NextTurnTrigger
16
15
 
17
16
  logger = logging.getLogger(__name__)
@@ -28,7 +27,6 @@ class TriggerRegistry:
28
27
  "claude": ClaudeTrigger,
29
28
  "codex": CodexTrigger,
30
29
  "gemini": GeminiTrigger,
31
- "antigravity": AntigravityTrigger,
32
30
  }
33
31
 
34
32
  # 注册所有triggers (用于list_triggers和get_trigger)
@@ -36,7 +34,6 @@ class TriggerRegistry:
36
34
  self.register("claude", ClaudeTrigger)
37
35
  self.register("codex", CodexTrigger)
38
36
  self.register("gemini", GeminiTrigger)
39
- self.register("antigravity", AntigravityTrigger)
40
37
 
41
38
  def register(self, name: str, trigger_class: Type[TurnTrigger]):
42
39
  """
@@ -71,7 +68,7 @@ class TriggerRegistry:
71
68
  根据session类型获取对应的trigger
72
69
 
73
70
  Args:
74
- session_type: session类型 ("claude", "codex", "gemini", "antigravity")
71
+ session_type: session类型 ("claude", "codex", "gemini")
75
72
  config: 可选的配置字典
76
73
 
77
74
  Returns:
realign/watcher_core.py CHANGED
@@ -23,7 +23,7 @@ logger = setup_logger("realign.watcher_core", "watcher_core.log")
23
23
 
24
24
 
25
25
  # Session type detection
26
- SessionType = Literal["claude", "codex", "gemini", "antigravity", "unknown"]
26
+ SessionType = Literal["claude", "codex", "gemini", "unknown"]
27
27
 
28
28
 
29
29
  def is_path_blacklisted(project_path: Path) -> bool:
@@ -540,9 +540,6 @@ class DialogueWatcher:
540
540
  current_count = self._count_complete_turns(session_file)
541
541
  last_count = self.last_stop_reason_counts.get(session_path, 0)
542
542
 
543
- if session_type == "antigravity":
544
- return [1] if current_count >= 1 else []
545
-
546
543
  if current_count <= last_count:
547
544
  return []
548
545
 
@@ -974,9 +971,9 @@ class DialogueWatcher:
974
971
  adapter = registry.auto_detect_adapter(session_file)
975
972
  if adapter:
976
973
  # Map adapter name to SessionType
977
- # Adapter names: "claude", "codex", "gemini", "antigravity"
974
+ # Adapter names: "claude", "codex", "gemini"
978
975
  name = adapter.name
979
- if name in ["claude", "codex", "gemini", "antigravity"]:
976
+ if name in ["claude", "codex", "gemini"]:
980
977
  return name
981
978
 
982
979
  return "unknown"
@@ -1956,35 +1953,6 @@ class DialogueWatcher:
1956
1953
  indent=2,
1957
1954
  )
1958
1955
 
1959
- if session_format == "antigravity_markdown":
1960
- print(
1961
- f"[Debug] Extracting Antigravity content for turn {turn_number}",
1962
- file=sys.stderr,
1963
- )
1964
- # Directly read artifact files from directory (don't rely on trigger.user_message which is empty)
1965
- session_dir = session_file if session_file.is_dir() else session_file.parent
1966
- artifacts = ["task.md", "walkthrough.md", "implementation_plan.md"]
1967
- content_parts = []
1968
- for filename in artifacts:
1969
- path = session_dir / filename
1970
- if path.exists():
1971
- try:
1972
- text = path.read_text(encoding="utf-8")
1973
- content_parts.append(f"--- {filename} ---\n{text}")
1974
- except Exception as e:
1975
- print(f"[Debug] Failed to read {filename}: {e}", file=sys.stderr)
1976
- full_content = "\n\n".join(content_parts)
1977
- if full_content:
1978
- print(
1979
- f"[Debug] Antigravity content extracted: {len(full_content)} chars",
1980
- file=sys.stderr,
1981
- )
1982
- return full_content
1983
- print(
1984
- f"[Debug] Antigravity content empty - no artifact files found", file=sys.stderr
1985
- )
1986
- return ""
1987
-
1988
1956
  # For JSONL formats, extract by line numbers
1989
1957
  start_line = group.get("start_line") or (group.get("lines") or [None])[0]
1990
1958
  end_line = group.get("end_line") or (group.get("lines") or [None])[-1]
@@ -1,159 +0,0 @@
1
- """
2
- Antigravity IDE Adapter
3
-
4
- Handles session discovery for Antigravity IDE (Gemini in IDE).
5
- Since .pb conversation files are encrypted, this adapter uses
6
- readable brain artifacts (walkthrough.md, task.md) as session indicators.
7
- """
8
-
9
- import re
10
- from pathlib import Path
11
- from typing import List, Optional, Dict, Any
12
-
13
- from .base import SessionAdapter
14
- from ..triggers.antigravity_trigger import AntigravityTrigger
15
-
16
-
17
- class AntigravityAdapter(SessionAdapter):
18
- """Adapter for Antigravity IDE sessions."""
19
-
20
- name = "antigravity"
21
- trigger_class = AntigravityTrigger
22
-
23
- def discover_sessions(self) -> List[Path]:
24
- """
25
- Find all active Antigravity IDE sessions.
26
- Returns the directory path for each conversation that contains relevant artifacts.
27
- """
28
- sessions = []
29
- gemini_brain = Path.home() / ".gemini" / "antigravity" / "brain"
30
-
31
- if not gemini_brain.exists():
32
- return sessions
33
-
34
- try:
35
- for conv_dir in gemini_brain.iterdir():
36
- if not conv_dir.is_dir():
37
- continue
38
-
39
- # Check for key artifacts
40
- has_artifacts = any(
41
- (conv_dir / filename).exists()
42
- for filename in ["task.md", "walkthrough.md", "implementation_plan.md"]
43
- )
44
-
45
- if has_artifacts:
46
- sessions.append(conv_dir)
47
- except Exception:
48
- pass
49
-
50
- return sessions
51
-
52
- def discover_sessions_for_project(self, project_path: Path) -> List[Path]:
53
- """
54
- Find sessions for a specific project.
55
- """
56
- all_sessions = self.discover_sessions()
57
- project_sessions = []
58
-
59
- for session in all_sessions:
60
- extracted_path = self.extract_project_path(session)
61
- if extracted_path and extracted_path == project_path:
62
- project_sessions.append(session)
63
-
64
- return project_sessions
65
-
66
- def extract_project_path(self, session_file: Path) -> Optional[Path]:
67
- """
68
- Infer project root from Antigravity brain artifacts.
69
-
70
- Antigravity stores readable artifacts (task.md / walkthrough.md / implementation_plan.md).
71
- We attempt to extract a `file://...` reference, then walk upward to find a `.git` root.
72
- """
73
- session_dir = session_file if session_file.is_dir() else session_file.parent
74
- candidates: List[Path] = []
75
-
76
- artifact_names = ["task.md", "walkthrough.md", "implementation_plan.md"]
77
- for name in artifact_names:
78
- p = session_dir / name
79
- if not p.exists():
80
- continue
81
- try:
82
- text = p.read_text(encoding="utf-8", errors="ignore")
83
- except Exception:
84
- continue
85
-
86
- # Capture file:// paths from Markdown links or plain text.
87
- # Example: file:///Users/me/Project/src/main.py or file://.../src/main.py
88
- for match in re.findall(r"file://([^)\s]+)", text):
89
- raw = match.strip()
90
- if raw.startswith("/"):
91
- candidates.append(Path(raw))
92
- else:
93
- # Some tools emit file://Users/... (missing leading slash)
94
- candidates.append(Path("/" + raw))
95
-
96
- for candidate in candidates:
97
- try:
98
- # Prefer the nearest VCS root
99
- path = candidate
100
- if path.is_file():
101
- path = path.parent
102
- for parent in [path] + list(path.parents):
103
- git_dir = parent / ".git"
104
- if git_dir.exists():
105
- return parent
106
- except Exception:
107
- continue
108
-
109
- # Fallback: if we found a candidate that exists, return its parent directory.
110
- for candidate in candidates:
111
- try:
112
- if candidate.exists():
113
- return candidate.parent if candidate.is_file() else candidate
114
- except Exception:
115
- continue
116
-
117
- return None
118
-
119
- def get_session_metadata(self, session_file: Path) -> Dict[str, Any]:
120
- """Extract rich metadata from Antigravity brain artifacts."""
121
- metadata = super().get_session_metadata(session_file)
122
-
123
- if not session_file.exists():
124
- return metadata
125
-
126
- session_dir = session_file if session_file.is_dir() else session_file.parent
127
-
128
- try:
129
- # We just return the turn count (always 1 if exists)
130
- # No task progress parsing required
131
- metadata["turn_count"] = self.trigger.count_complete_turns(session_file)
132
-
133
- except Exception:
134
- pass
135
-
136
- return metadata
137
-
138
- def is_session_valid(self, session_file: Path) -> bool:
139
- """Check if this is an Antigravity artifact directory."""
140
- if not session_file.is_dir():
141
- if (
142
- session_file.parent.name == "brain"
143
- and session_file.parent.parent.name == "antigravity"
144
- ):
145
- # It's a directory inside brain
146
- return True
147
- return False
148
-
149
- # Check parent hierarchy
150
- # .../.gemini/antigravity/brain/<uuid>
151
- try:
152
- if (
153
- session_file.parent.name == "brain"
154
- and session_file.parent.parent.name == "antigravity"
155
- ):
156
- return True
157
- except Exception:
158
- pass
159
- return False
@@ -1,140 +0,0 @@
1
- """
2
- AntigravityTrigger - Trigger for Antigravity IDE (Markdown artifacts)
3
-
4
- Since Antigravity produces walkthrough.md and task.md instead of turn-based JSONL,
5
- this trigger uses file content/structure as a signal for "turns".
6
- """
7
-
8
- from datetime import datetime
9
- from pathlib import Path
10
- from typing import List, Optional, Dict, Any
11
- import re
12
- from .base import TurnTrigger, TurnInfo
13
-
14
-
15
- class AntigravityTrigger(TurnTrigger):
16
- """
17
- Trigger for Antigravity IDE markdown artifacts.
18
- Each distinct Task in task.md or major change in walkthrough.md can be
19
- treated as a 'turn'.
20
- """
21
-
22
- def get_supported_formats(self) -> List[str]:
23
- return ["antigravity_markdown"]
24
-
25
- def detect_session_format(self, session_file: Path) -> Optional[str]:
26
- """Detect if this is an Antigravity brain artifact directory."""
27
- try:
28
- # Check if directory
29
- if session_file.is_dir():
30
- if session_file.parent.name == "brain" and "antigravity" in str(session_file):
31
- return "antigravity_markdown"
32
- return None
33
-
34
- # Legacy/Fallback: Check if file
35
- if session_file.suffix == ".md":
36
- path_str = str(session_file)
37
- if "gemini" in path_str and "brain" in path_str:
38
- return "antigravity_markdown"
39
- if ".antigravity" in path_str or "antigravity" in path_str.lower():
40
- return "antigravity_markdown"
41
- return None
42
- except Exception:
43
- return None
44
-
45
- def count_complete_turns(self, session_file: Path) -> int:
46
- """
47
- Antigravity sessions are effectively "single-turn" persistent states.
48
- We return 1 if the artifacts exist, 0 otherwise.
49
-
50
- The watcher will handle change detection via content hashing or mtime,
51
- even if this count stays at 1.
52
-
53
- Args:
54
- session_file: Path to brain directory (or file)
55
-
56
- Returns:
57
- int: 1 if artifacts exist, 0 otherwise.
58
- """
59
- if not session_file.exists():
60
- return 0
61
-
62
- session_dir = session_file if session_file.is_dir() else session_file.parent
63
- artifacts = ["task.md", "walkthrough.md", "implementation_plan.md"]
64
-
65
- has_artifacts = False
66
- for filename in artifacts:
67
- path = session_dir / filename
68
- if path.exists():
69
- has_artifacts = True
70
- break
71
-
72
- return 1 if has_artifacts else 0
73
-
74
- def extract_turn_info(self, session_file: Path, turn_number: int) -> Optional[TurnInfo]:
75
- """Extract information by aggregating artifacts."""
76
- if not session_file.exists():
77
- return None
78
-
79
- # Ensure we point to the directory
80
- session_dir = session_file if session_file.is_dir() else session_file.parent
81
-
82
- content_parts = []
83
- artifacts = ["task.md", "walkthrough.md", "implementation_plan.md"]
84
-
85
- # Aggregate content
86
- for filename in artifacts:
87
- path = session_dir / filename
88
- if path.exists():
89
- text = path.read_text(encoding="utf-8")
90
- content_parts.append(f"--- {filename} ---\n{text}")
91
-
92
- full_content = "\n\n".join(content_parts)
93
- if not full_content:
94
- return None
95
-
96
- # Always use current time for timestamp as this is an evolving state
97
- timestamp = datetime.now().isoformat()
98
-
99
- return TurnInfo(
100
- turn_number=1, # Always Turn 1
101
- user_message="", # Empty - full content used elsewhere for summary generation
102
- start_line=1,
103
- end_line=len(full_content.splitlines()) if full_content else 0,
104
- timestamp=timestamp,
105
- )
106
-
107
- def is_turn_complete(self, session_file: Path, turn_number: int) -> bool:
108
- # For Antigravity, if we have artifacts, it's "complete" in the sense that it exists.
109
- return self.count_complete_turns(session_file) >= 1
110
-
111
- def get_detailed_analysis(self, session_file: Path) -> Dict[str, Any]:
112
- """
113
- Get detailed analysis of the session.
114
- Since we treat the state as a single accumulated turn, we return one turn group.
115
- """
116
- current_turn = self.count_complete_turns(session_file)
117
-
118
- groups = []
119
- # Return a single entry representing the current state
120
- if current_turn > 0:
121
- info = self.extract_turn_info(session_file, 1)
122
- if info:
123
- groups.append(
124
- {
125
- "turn_number": 1,
126
- "user_message": info.user_message,
127
- "summary_message": "Antigravity Session State",
128
- "turn_status": "completed",
129
- "start_line": info.start_line,
130
- "end_line": info.end_line,
131
- "timestamp": info.timestamp,
132
- }
133
- )
134
-
135
- return {
136
- "groups": groups,
137
- "total_turns": 1 if current_turn > 0 else 0, # Conceptually one continuous session
138
- "latest_turn_id": 1 if current_turn > 0 else 0,
139
- "format": "antigravity_markdown",
140
- }