From feecd263eb4543aac69ade5a4042d0b191a31e22 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Wed, 24 Jun 2026 05:51:54 -0700 Subject: [PATCH] fix(seedgo): name-scope unused_function bypasses (kill silent line-drift) unused_function bypasses matched by file+line; the line was the function's def line, so any code shift above it staled the bypass and silently re-flagged the exempted function, dropping the branch below 100% (S216/S217). Mechanism: is_bypassed() gains a 'functions' field + name param; name-scoped match takes precedence, 'lines' kept for back-compat (no other standard changes). unused_function_check passes the function name. +7 tests (1093), seedgo self-audit 100%, bypass schema documented. Migration: converted 10 line-scoped entries to functions: across drone/memory/skills; removed 3 dead memory/vector_search entries already pointing past EOF (file is 152 lines). drone/memory/skills re-audit: Unused_Function 100% (names verified live). Closes td-009. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QEQZXCtgnF3NQtcttTErpq --- CHANGELOG.md | 6 + src/aipass/drone/.seedgo/bypass.json | 8 +- src/aipass/memory/.seedgo/bypass.json | 22 +- src/aipass/seedgo/.seedgo/README.md | 23 ++ .../aipass_standards/unused_function_check.py | 3 +- .../apps/handlers/bypass/bypass_handler.py | 3 +- .../seedgo/apps/handlers/bypass/utils.py | 13 +- src/aipass/seedgo/tests/test_bypass.py | 212 ++++++++++++++++++ src/aipass/skills/.seedgo/bypass.json | 3 +- 9 files changed, 260 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b27994b..f29023ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ PyPI version — not the changelog header. central audit collector. An in-place disabled module no longer trips a false "missing module" violation; disabled test files no longer inflate README test counts (td-103). +- **seedgo `unused_function` bypasses are now name-scoped** — bypasses match by + function name (`functions: [...]`) instead of line number (`lines: [...]`), + which drifted silently when code shifted and re-flagged exempted functions + (bit us S216/S217). `lines` stays supported for other standards. Migrated the + 10 existing line-scoped entries across drone/memory/skills and dropped 3 dead + entries already pointing past EOF (td-009). ## [2026-06-23] diff --git a/src/aipass/drone/.seedgo/bypass.json b/src/aipass/drone/.seedgo/bypass.json index 45467787..7f4124ef 100644 --- a/src/aipass/drone/.seedgo/bypass.json +++ b/src/aipass/drone/.seedgo/bypass.json @@ -252,25 +252,25 @@ { "file": "apps/handlers/command_registry/ops.py", "standard": "unused_function", - "lines": [232, 263], + "functions": ["update_command", "command_exists"], "reason": "Public CRUD API surface (update_command, command_exists) — tested, available for programmatic use. No CLI subcommand wired yet." }, { "file": "apps/handlers/git/pr_handler.py", "standard": "unused_function", - "lines": [108], + "functions": ["create_pr"], "reason": "create_pr() is deprecated per FPLAN-0210 — pr command blocked at auth tier. Handler kept for backwards compatibility; still tested in test_git_module.py." }, { "file": "apps/modules/git_module.py", "standard": "unused_function", - "lines": [665], + "functions": ["get_introspective"], "reason": "get_introspective() called dynamically via getattr() by module_registry_handler.py:219 for internal module introspection. Also tested in test_git_module, test_system_pr, test_devpulse_plugins, test_git_access." }, { "file": "apps/handlers/broker/daemon.py", "standard": "unused_function", - "lines": [422], + "functions": ["start_background"], "reason": "Threaded broker entrypoint, exercised by tests/test_broker.py; production uses blocking start(); intentionally not called in shipped non-test code." } ], diff --git a/src/aipass/memory/.seedgo/bypass.json b/src/aipass/memory/.seedgo/bypass.json index bb398233..16bd2a2f 100644 --- a/src/aipass/memory/.seedgo/bypass.json +++ b/src/aipass/memory/.seedgo/bypass.json @@ -559,31 +559,13 @@ { "file": "apps/handlers/learnings/manager.py", "standard": "unused_function", - "lines": [759], + "functions": ["update_status_counts"], "reason": "Public API for learning extraction pipeline — called dynamically by symbolic extraction." }, - { - "file": "apps/handlers/search/vector_search.py", - "standard": "unused_function", - "lines": [247], - "reason": "Public API surface for vector search — legacy handler retained for direct-import consumers." - }, - { - "file": "apps/handlers/search/vector_search.py", - "standard": "unused_function", - "lines": [316], - "reason": "Public API surface for vector search — legacy handler retained for direct-import consumers." - }, - { - "file": "apps/handlers/search/vector_search.py", - "standard": "unused_function", - "lines": [380], - "reason": "Public API surface for vector search — legacy handler retained for direct-import consumers." - }, { "file": "apps/handlers/symbolic/extractor.py", "standard": "unused_function", - "lines": [455], + "functions": ["analyze_conversation_llm"], "reason": "LLM-based extraction function — called conditionally when API key is available." }, { diff --git a/src/aipass/seedgo/.seedgo/README.md b/src/aipass/seedgo/.seedgo/README.md index dd56a69b..db291616 100644 --- a/src/aipass/seedgo/.seedgo/README.md +++ b/src/aipass/seedgo/.seedgo/README.md @@ -3,3 +3,26 @@ Seedgo audit bypass config for `SEEDGO`. When an audit flags a false positive that doesn't apply to your architecture, add a bypass entry in `bypass.json` with a reason explaining why it's justified. + +## Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `file` | yes | Relative path from branch root | +| `standard` | yes | Standard name (cli, imports, naming, etc.) | +| `lines` | no | Specific line numbers to bypass | +| `functions` | no | Function names for name-scoped bypass (required for `unused_function`) | +| `reason` | yes | Why this bypass exists | + +## Name-scoped bypass (unused_function) + +Use `functions` instead of `lines` for `unused_function` bypasses — function names are stable across edits, line numbers drift silently: + +```json +{ + "file": "apps/handlers/registry.py", + "standard": "unused_function", + "functions": ["get_skill", "get_skill_names"], + "reason": "Public API surface — called by external consumers" +} +``` diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py index 1f12df36..b866c6e6 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py @@ -277,8 +277,7 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict: unused_functions: list[dict] = [] for func_name, lineno, py_file in all_functions: - # Check bypass at file+line level - if is_bypassed(str(py_file), "unused_function", lineno, bypass_rules): + if is_bypassed(str(py_file), "unused_function", lineno, bypass_rules, name=func_name): continue total_refs = _count_references_in_corpus(func_name, corpus) diff --git a/src/aipass/seedgo/apps/handlers/bypass/bypass_handler.py b/src/aipass/seedgo/apps/handlers/bypass/bypass_handler.py index 98d7bc82..4c62c7ea 100644 --- a/src/aipass/seedgo/apps/handlers/bypass/bypass_handler.py +++ b/src/aipass/seedgo/apps/handlers/bypass/bypass_handler.py @@ -37,14 +37,13 @@ BYPASS_TEMPLATE = { "file": "apps/modules/logger.py", "standard": "cli", "lines": [146, 177], - "pattern": "if __name__ == '__main__'", "reason": "Circular dependency - logger cannot import CLI", }, "fields": { "file": "Relative path from branch root (required)", "standard": "Standard name: cli, imports, naming, etc. (required)", "lines": "Optional - specific line numbers to bypass", - "pattern": "Optional - pattern to match (e.g. 'if __name__')", + "functions": "Optional - list of function names for name-scoped bypass (required for unused_function)", "reason": "Required - why this bypass exists", }, }, diff --git a/src/aipass/seedgo/apps/handlers/bypass/utils.py b/src/aipass/seedgo/apps/handlers/bypass/utils.py index 8fe216ca..a1c7633f 100644 --- a/src/aipass/seedgo/apps/handlers/bypass/utils.py +++ b/src/aipass/seedgo/apps/handlers/bypass/utils.py @@ -18,6 +18,7 @@ def is_bypassed( standard: str, line: int | None = None, bypass_rules: list | None = None, + name: str | None = None, ) -> bool: """Check if a violation should be bypassed. @@ -26,6 +27,7 @@ def is_bypassed( standard: Standard name (e.g., 'cli', 'imports') line: Optional specific line number of the violation bypass_rules: List of bypass rules from .seedgo/bypass.json + name: Optional function/symbol name for name-scoped bypasses Returns: True if this violation should be bypassed @@ -40,15 +42,20 @@ def is_bypassed( rule_file = rule.get("file", "") if rule_file and rule_file not in file_path_posix: continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue + functions = rule.get("functions") + if functions and name is not None: + if name not in functions: + continue + elif rule.get("lines") and line is not None: + if line not in rule["lines"]: + continue json_handler.log_operation( "bypass_matched", { "file": file_path, "standard": standard, "line": line, + "name": name, "rule_file": rule_file, }, ) diff --git a/src/aipass/seedgo/tests/test_bypass.py b/src/aipass/seedgo/tests/test_bypass.py index 1f39c7fa..464104fc 100644 --- a/src/aipass/seedgo/tests/test_bypass.py +++ b/src/aipass/seedgo/tests/test_bypass.py @@ -44,6 +44,7 @@ def _mock_infrastructure(monkeypatch): for mod_name in [ "aipass.seedgo.apps.handlers.bypass.bypass_handler", "aipass.seedgo.apps.handlers.bypass.ignore_handler", + "aipass.seedgo.apps.handlers.bypass.utils", ]: monkeypatch.delitem(sys.modules, mod_name, raising=False) @@ -210,3 +211,214 @@ def test_get_deprecated_patterns_returns_dict(): for key, value in patterns.items(): assert isinstance(key, str) assert isinstance(value, str) + + +# --------------------------------------------------------------------------- +# Tests -- utils.is_bypassed name-scoped bypass +# --------------------------------------------------------------------------- + + +def test_utils_name_match_suppresses_regardless_of_line(): + """Name-scoped bypass matches by function name, ignoring line number.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/ops.py", + "standard": "unused_function", + "functions": ["update_command"], + "reason": "public API", + } + ] + assert ( + is_bypassed( + "/branch/apps/ops.py", + "unused_function", + line=999, + bypass_rules=rules, + name="update_command", + ) + is True + ) + + +def test_utils_name_not_in_functions_list(): + """Name-scoped bypass rejects function not in the functions list.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/ops.py", + "standard": "unused_function", + "functions": ["update_command"], + "reason": "public API", + } + ] + assert ( + is_bypassed( + "/branch/apps/ops.py", + "unused_function", + line=232, + bypass_rules=rules, + name="delete_command", + ) + is False + ) + + +def test_utils_line_drift_no_longer_breaks_name_scoped(): + """Line drift doesn't affect name-scoped bypass — name is stable.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/ops.py", + "standard": "unused_function", + "functions": ["get_skill", "get_skill_names"], + "reason": "public API", + } + ] + assert ( + is_bypassed( + "/branch/apps/ops.py", + "unused_function", + line=52, + bypass_rules=rules, + name="get_skill", + ) + is True + ) + assert ( + is_bypassed( + "/branch/apps/ops.py", + "unused_function", + line=9999, + bypass_rules=rules, + name="get_skill", + ) + is True + ) + + +def test_utils_functions_present_name_none_falls_back_to_lines(): + """When functions is set but name=None (other checker), fall back to line matching.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/ops.py", + "standard": "unused_function", + "functions": ["update_command"], + "lines": [10], + "reason": "test", + } + ] + assert ( + is_bypassed( + "/branch/apps/ops.py", + "unused_function", + line=10, + bypass_rules=rules, + name=None, + ) + is True + ) + + +def test_utils_existing_lines_only_rules_still_work(): + """Existing line-only rules (no functions field) still match by line.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/foo.py", + "standard": "cli", + "lines": [10, 20], + "reason": "circular", + } + ] + assert ( + is_bypassed( + "/branch/apps/foo.py", + "cli", + line=10, + bypass_rules=rules, + ) + is True + ) + assert ( + is_bypassed( + "/branch/apps/foo.py", + "cli", + line=99, + bypass_rules=rules, + ) + is False + ) + + +def test_utils_file_only_bypass_still_matches(): + """File-level bypass (no lines, no functions) still works.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/foo.py", + "standard": "unused_function", + "reason": "whole file bypassed", + } + ] + assert ( + is_bypassed( + "/branch/apps/foo.py", + "unused_function", + line=50, + bypass_rules=rules, + name="anything", + ) + is True + ) + + +def test_utils_multiple_functions_in_one_rule(): + """A single rule can list multiple function names.""" + from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed + + rules = [ + { + "file": "apps/registry.py", + "standard": "unused_function", + "functions": ["get_skill", "get_skill_names"], + "reason": "public API", + } + ] + assert ( + is_bypassed( + "/branch/apps/registry.py", + "unused_function", + line=1, + bypass_rules=rules, + name="get_skill", + ) + is True + ) + assert ( + is_bypassed( + "/branch/apps/registry.py", + "unused_function", + line=1, + bypass_rules=rules, + name="get_skill_names", + ) + is True + ) + assert ( + is_bypassed( + "/branch/apps/registry.py", + "unused_function", + line=1, + bypass_rules=rules, + name="other_func", + ) + is False + ) diff --git a/src/aipass/skills/.seedgo/bypass.json b/src/aipass/skills/.seedgo/bypass.json index cbbfffc4..184e9e23 100644 --- a/src/aipass/skills/.seedgo/bypass.json +++ b/src/aipass/skills/.seedgo/bypass.json @@ -39,8 +39,7 @@ { "file": "apps/handlers/registry.py", "standard": "unused_function", - "lines": [52, 68], - "pattern": "def get_skill|def get_skill_names", + "functions": ["get_skill", "get_skill_names"], "reason": "Public API functions — used by test_registry.py and available for external callers; part of the registry module's contract" } ],