diff --git a/CHANGELOG.md b/CHANGELOG.md index 938aa6ce..37caca8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,11 @@ and README to the house pattern: 17/17 branches at 100%. - **Fleet-100 sweep**: 15 branches gained Quick Start READMEs and/or Usage/Examples help sections — each owner fixed their own front against the new gate. +- **Debug_Print detector hardened**: the regex-based checker matched `print(` + inside string literals (flagging cli_ux_check's own error messages — the + auditor was the last branch under 100%). String content is now stripped + before matching, with a regression test; plus a depth-5 nesting refactor in + the same file. **v2.7.2** — everything merged since v2.7.1, headlined by the compass decision engine v2: curation with supersedes links + write-time conflict advisories diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_ux_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_ux_check.py index 4a7f443d..7d33fc9b 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_ux_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_ux_check.py @@ -214,20 +214,10 @@ def _find_function(tree: ast.Module, name: str) -> Optional[ast.FunctionDef]: def _collect_string_constants(func_node: ast.FunctionDef) -> List[str]: - """ - Extract all string constants from a function body, including strings - inside f-strings (ast.JoinedStr -> ast.Constant). - """ - strings: List[str] = [] - for node in ast.walk(func_node): - if isinstance(node, ast.Constant) and isinstance(node.value, str): - strings.append(node.value) - elif isinstance(node, ast.JoinedStr): - # f-string: extract string parts - for part in node.values: - if isinstance(part, ast.Constant) and isinstance(part.value, str): - strings.append(part.value) - return strings + """Extract all string constants from a function body, including f-string parts.""" + return [ + node.value for node in ast.walk(func_node) if isinstance(node, ast.Constant) and isinstance(node.value, str) + ] # --------------------------------------------------------------------------- diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py index 0d260f1e..059e890b 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py @@ -34,6 +34,9 @@ _DOCTEST_RE = re.compile(r"^\s*(\.\.\.|>>>)\s") # Test file name patterns _TEST_FILE_RE = re.compile(r"^(test_.+|.+_test|conftest)\.py$") +# String literals — strip content before regex to avoid false positives on print( inside strings +_STRING_LITERAL_RE = re.compile(r'""".*?"""|\'\'\'.*?\'\'\'|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\'') + def _is_in_main_block(lines: list[str], lineno: int) -> bool: """ @@ -108,8 +111,9 @@ def _scan_file(file_path: Path) -> tuple[list[int], str | None]: if _DOCTEST_RE.match(line): continue - # Strip inline comments before checking for print( + # Strip inline comments and string literal contents before checking code_part = line.split("#")[0] + code_part = _STRING_LITERAL_RE.sub('""', code_part) if not _PRINT_RE.search(code_part): continue diff --git a/src/aipass/seedgo/tests/test_checkers_batch1.py b/src/aipass/seedgo/tests/test_checkers_batch1.py index 84dfbff5..67ab1ad9 100644 --- a/src/aipass/seedgo/tests/test_checkers_batch1.py +++ b/src/aipass/seedgo/tests/test_checkers_batch1.py @@ -329,6 +329,22 @@ def test_debug_print_check_bypass_respected(tmp_path): assert result["score"] == 100, f"Bypass should yield 100: {result}" +def test_debug_print_string_literal_not_flagged(tmp_path): + """print() mentioned inside string literals must not trigger false positives.""" + f = tmp_path / "messages.py" + f.write_text( + '"""Module with print() in docstring."""\n\n' + "def check():\n" + ' msg = "use console.print() not bare print()"\n' + " return msg\n", + encoding="utf-8", + ) + from aipass.seedgo.apps.handlers.aipass_standards.debug_print_check import check_module + + result = check_module(str(f)) + assert result["score"] == 100, f"print() inside strings should not flag: {result}" + + # =========================================================================== # 6. deep_nesting_check # ===========================================================================