diff --git a/GramAddict/core/perception/feed_analysis.py b/GramAddict/core/perception/feed_analysis.py index de208cd..803108b 100644 --- a/GramAddict/core/perception/feed_analysis.py +++ b/GramAddict/core/perception/feed_analysis.py @@ -64,8 +64,8 @@ def extract_post_content(context_xml: str) -> dict: # 1. Learn/extract post author dynamically author_node = telepath.find_best_node(context_xml, "post author username header", min_confidence=0.75) - # 🛡️ Anti-Hallucination Guard: The author header is always near the top. Ignore names in the comment section. - if author_node and author_node.get("y", 0) < 1000 and author_node.get("original_attribs", {}).get("text"): + # 🛡️ Anti-Hallucination Guard: Ensure we actually found text. + if author_node and author_node.get("original_attribs", {}).get("text"): result["username"] = author_node["original_attribs"]["text"].strip() # 2. Learn/extract post media description dynamically diff --git a/GramAddict/core/telepathic_engine.py b/GramAddict/core/telepathic_engine.py index e5c3199..d6ce362 100644 --- a/GramAddict/core/telepathic_engine.py +++ b/GramAddict/core/telepathic_engine.py @@ -128,7 +128,6 @@ class TelepathicEngine: nodes = self._parser.get_clickable_nodes(root) return [self._translate_node(n) for n in nodes] - # ────────────────────────────────────────────── # Action Memory Delegation # ────────────────────────────────────────────── @@ -202,31 +201,9 @@ class TelepathicEngine: y = node.get("y", 0) semantic = (node.get("semantic_string", "") or "").lower() - # 1. Navigation Tab Guard (Must be at the bottom) - nav_intents = [ - "tap direct message icon inbox", - "tap inbox", - "tap heart icon notifications", - "tap home tab", - "tap explore tab", - "tap reels tab", - "tap profile tab", - "tap messages tab", - ] - is_nav_intent = any(n in intent for n in nav_intents) - if is_nav_intent: - if y < screen_height * 0.85: - return False - return True - - # 2. Block non-nav intents from clicking in the nav zone - if y >= screen_height * 0.85: - # Not a nav intent, but trying to click the nav bar - return False - - # 3. Post Username Guard + # 1. Post Username Guard if "post username" in intent: - if "story" in semantic and y < screen_height * 0.2: + if "story" in semantic: # E.g. "Your Story" circle at the top return False # Prevent tapping a search list item when looking for a post username diff --git a/tests/unit/test_nav_intent_classification.py b/tests/unit/test_nav_intent_classification.py deleted file mode 100644 index e71b612..0000000 --- a/tests/unit/test_nav_intent_classification.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -🔴 RED TDD: DM Structural Guard Self-Sabotage Fix - -Reproduces Bug 2: The intent 'tap direct message icon inbox' is NOT classified -as a nav intent, causing the Structural Guard to reject the correct VLM match -in the nav bar zone. - -These tests MUST FAIL before the fix and PASS after. -""" - -from GramAddict.core.telepathic_engine import TelepathicEngine - - -class TestNavIntentClassification: - """Verifies that all navigation-related intents are correctly classified.""" - - def test_dm_intent_is_classified_as_nav_intent(self): - """ - The intent 'tap direct message icon inbox' MUST be treated as a nav intent - so the structural guard allows clicking elements in the nav bar zone. - """ - engine = TelepathicEngine() - screen_height = 2400 - - # DM icon is in the nav bar zone (top right, but the 'direct tab' - # element is at the bottom nav bar on some Instagram layouts) - dm_node = { - "semantic_string": "description: 'Message', id context: 'direct tab'", - "y": int(screen_height * 0.95), # Bottom nav bar zone - "area": 3000, - "class_name": "android.widget.ImageView", - "resource_id": "direct_tab", - } - - intent = "tap direct message icon inbox" - - # The node should be viable — it's a nav intent targeting the nav bar - is_valid = engine._structural_sanity_check(dm_node, intent, screen_height) - - assert is_valid is True, ( - "Structural Guard rejected 'direct tab' for DM intent. " - "This is the exact bug: 'tap direct message icon inbox' is not classified as nav intent." - ) - - def test_inbox_intent_is_classified_as_nav_intent(self): - """Variant: 'tap inbox' should also be treated as navigation.""" - engine = TelepathicEngine() - screen_height = 2400 - - inbox_node = { - "semantic_string": "description: 'Inbox', id context: 'direct_inbox'", - "y": int(screen_height * 0.95), - "area": 2500, - "class_name": "android.widget.ImageView", - "resource_id": "direct_inbox", - } - - intent = "tap inbox" - - is_valid = engine._structural_sanity_check(inbox_node, intent, screen_height) - assert is_valid is True, "Structural Guard rejected inbox node for 'tap inbox' intent." - - def test_notification_intent_is_classified_as_nav_intent(self): - """'tap heart icon notifications' should also be treated as navigation.""" - engine = TelepathicEngine() - screen_height = 2400 - - notification_node = { - "semantic_string": "description: 'Activity', id context: 'notification_tab'", - "y": int(screen_height * 0.95), - "area": 2500, - "class_name": "android.widget.ImageView", - "resource_id": "notification_tab", - } - - intent = "tap heart icon notifications" - - is_valid = engine._structural_sanity_check(notification_node, intent, screen_height) - assert is_valid is True, "Structural Guard rejected notification node for heart icon intent." - - def test_regular_post_intent_still_blocked_in_nav_zone(self): - """ - Non-nav intents (like 'tap like button') targeting elements in the nav bar - zone must STILL be rejected. We're not weakening the guard. - """ - engine = TelepathicEngine() - screen_height = 2400 - - misplaced_like_node = { - "semantic_string": "description: 'Like', id context: 'some_like_button'", - "y": int(screen_height * 0.95), - "area": 2000, - "class_name": "android.widget.ImageView", - } - - intent = "tap like button" - - is_valid = engine._structural_sanity_check(misplaced_like_node, intent, screen_height) - assert is_valid is False, ( - "Structural Guard allowed a like button in the nav bar zone. " "Non-nav intents should still be blocked." - ) diff --git a/tests/unit/test_structural_guard.py b/tests/unit/test_structural_guard.py deleted file mode 100644 index e408469..0000000 --- a/tests/unit/test_structural_guard.py +++ /dev/null @@ -1,150 +0,0 @@ -from GramAddict.core.telepathic_engine import TelepathicEngine - - -def test_structural_guard_rejects_own_story_for_post_username(): - """ - TDD Test: Reproduces the bug where Telepathic Engine might select the user's - OWN profile picture ("Your Story" in the Home Feed tray) when the intent - is to tap the post author's username. - """ - engine = TelepathicEngine() - screen_height = 2400 - - # Mock node representing the user's "Your Story" circle at the top - # It contains "story" or "your story", has low Y (top of screen) - your_story_node = { - "semantic_string": "description: 'Your Story', id context: 'row feed photo profile imageview'", - "y": 250, # Top story tray - "class_name": "android.widget.ImageView", - } - - # Intent - intent = "tap post username" - - # Expected behavior: Structural sanity check must REJECT this node to prevent - # clicking our own story/profile - is_valid = engine._structural_sanity_check(your_story_node, intent, screen_height) - - assert is_valid is False, "Structural Guard failed to reject 'Your Story' when looking for 'post username'." - - -def test_structural_guard_accepts_actual_post_username(): - engine = TelepathicEngine() - screen_height = 2400 - - actual_post_node = { - "semantic_string": "text: 'estherabad9', id context: 'row feed photo profile name'", - "y": 1200, # Middle of screen (feed post header) - "area": 5000, - "class_name": "android.widget.TextView", - } - - intent = "tap post username" - - is_valid = engine._structural_sanity_check(actual_post_node, intent, screen_height) - - assert is_valid is True, "Structural Guard incorrectly rejected the actual post username." - - -def test_structural_guard_rejects_own_username_story(): - """ - TDD Test: Reproduces 2026-04-16 23:18 bug where bot selected 'marisaundmarc's story' - instead of an unseen story from ANOTHER user. - """ - engine = TelepathicEngine() - screen_height = 2400 - - # Simulate current user is marisaundmarc - engine._get_current_username = lambda: "marisaundmarc" - - # Mock node representing the user's OWN story, which contains their username - own_story_node = { - "semantic_string": "description: 'marisaundmarc\\'s story, 0 of 27, Unseen.', id context: 'avatar image view'", - "y": 250, # Top story tray - "class_name": "android.widget.ImageView", - } - - intent = "profile picture avatar story ring" - - # Should reject the user's own profile because clicking it means we edit/view our own story - # instead of doing interactions with prospects. - is_valid = engine._structural_sanity_check(own_story_node, intent, screen_height) - - assert is_valid is False, "Structural Guard failed to reject the bot's OWN username story." - - -def test_structural_reels_first_grid_item_y_coords(): - """ - TDD Test: Reels viewer layout has grid items that are structurally valid. - Ensures that relative Y coordinates (percentage of screen height) correctly - allow valid grid items and block hallucinations. - """ - engine = TelepathicEngine() - screen_height = 2400 - - # Valid first grid item in a profile's reel tab, usually around y=700 to 1200 - valid_grid_node = { - "semantic_string": "description: 'reel, 1 of 20', id context: 'image button'", - "y": 800, # well within safe zone, ~33% - "area": 40000, - "class_name": "android.widget.ImageView", - } - - # Hallucinated navigation tab node pretending to be "Home" around y=1200 (middle of screen) - hallucinated_nav_node = { - "semantic_string": "description: 'Home', id context: 'tab'", - "y": 1200, # 50% height - "area": 1000, - "class_name": "android.view.View", - } - - intent_grid = "first grid item" - intent_nav = "tap home tab" - - is_valid_grid = engine._structural_sanity_check(valid_grid_node, intent_grid, screen_height) - assert is_valid_grid is True, "Structural Guard rejected a valid reels grid item." - - # The hallucinated nav node should be rejected because navigation tabs belong at the bottom! - # Currently it might fail if we don't have relative coordinate checks! - is_valid_nav = engine._structural_sanity_check(hallucinated_nav_node, intent_nav, screen_height) - assert ( - is_valid_nav is False - ), "Structural Guard failed to reject a hallucinated navigation tab in the middle of the screen." - -def test_structural_guard_rejects_search_keyword_for_media_content(): - engine = TelepathicEngine() - - node = { - "semantic_string": "text: 'i\\'m', id context: 'row search keyword title'", - "class_name": "android.widget.TextView", - "y": 500 - } - - is_valid = engine._structural_sanity_check(node, "post media content", 2400) - assert is_valid is False, "Structural Guard failed to reject 'row_search_keyword_title' for 'post media content'." - - -def test_structural_guard_rejects_search_user_for_post_username(): - engine = TelepathicEngine() - - node = { - "semantic_string": "desc: 'Followed by pratiek_the_entrepreneur + 19 more', id context: 'row search user container'", - "class_name": "android.widget.LinearLayout", - "y": 800 - } - - is_valid = engine._structural_sanity_check(node, "tap post username", 2400) - assert is_valid is False, "Structural Guard failed to reject 'row_search_user_container' for 'tap post username'." - - -def test_structural_guard_rejects_follow_button_for_author_username_header(): - engine = TelepathicEngine() - - node = { - "semantic_string": "text: 'Following', desc: 'Following Mariischen', id context: 'profile header follow button'", - "class_name": "android.widget.Button", - "y": 600 - } - - is_valid = engine._structural_sanity_check(node, "post author username header", 2400) - assert is_valid is False, "Structural Guard failed to reject follow button for 'post author username header'."