From c05f530de7129d0546d006afd2693e21b6dc9105 Mon Sep 17 00:00:00 2001 From: Mark2Mac Date: Fri, 31 Jul 2026 10:37:31 +0200 Subject: [PATCH] fix(mp2): aligned tables and ASCII boxes are layout, not context stuffing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MP2's repetition pattern `(.{2,20}?)\1{20,}` already has a guard for a single character repeated, but it exempts whitespace explicitly, so any run built from spaces still matches. That is most of what real projects contain: aligned markdown tables, ASCII boxes, horizontal rules, and UI code that pads columns. The guard is extended in the same spirit: a unit made only of whitespace and alignment characters (dashes, bars, colons, box drawing U+2500-U+257F) repeats because something is being *lined up*, not because context is being displaced. It is not a blanket exemption. Above 400 characters layout stops being a plausible explanation — nobody reads a table row or a box that wide — so a long run is still reported even when it is pure filler. Repetition of *content* is untouched. Measured on 25 real files drawn from a corpus of 65 skill/plugin units, all of which had MP2 findings: MP2 findings 95 -> 3 The three survivors come from MP2's *other* patterns (prose about exceeding the context window), not from the repetition pattern, and are outside this change. Tests: an aligned table, an ASCII box and a padded column produce nothing; a whitespace run wider than the layout limit, and repeated content, still do. Full suite: 1565 passed, 14 skipped, 6 xfailed. Signed-off-by: Mark2Mac --- .../static_patterns_memory_poisoning.py | 17 +++++++++++ tests/unit/test_patterns_new.py | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/skillspector/nodes/analyzers/static_patterns_memory_poisoning.py b/src/skillspector/nodes/analyzers/static_patterns_memory_poisoning.py index 1a0e3792..47afb2a4 100644 --- a/src/skillspector/nodes/analyzers/static_patterns_memory_poisoning.py +++ b/src/skillspector/nodes/analyzers/static_patterns_memory_poisoning.py @@ -79,6 +79,15 @@ ), ] +# Layout filler: characters that repeat because something is being *aligned*, not because +# context is being displaced — aligned table cells, ASCII boxes, horizontal rules, padded +# columns built by UI code. Box drawing is U+2500-U+257F. +_MP2_LAYOUT_FILLER = re.compile(r"^[\s\-=_*.|:+~\u2500-\u257F]+$") + +# Above this width the run stops being plausible formatting: nobody reads a table row or an +# ASCII box 400 characters wide, so a repetition that long is reported even if it is filler. +_MP2_LAYOUT_MAX_RUN = 400 + # MP2: Context Window Stuffing — filling context to displace content MP2_PATTERNS = [ (r"(.{2,20}?)\1{20,}", 0.8), @@ -186,6 +195,14 @@ def ctx(start: int) -> str: non_ws_chars = set(captured) - {" ", "\t", "\n", "\r"} if len(non_ws_chars) <= 1 and not any(c in captured for c in (" ", "\t")): continue + # Same reasoning as the guard above, extended to the case it misses: a unit made + # only of whitespace and alignment characters repeats because a table, a box or a + # column is being drawn. Runs wider than a readable line are still reported. + if ( + _MP2_LAYOUT_FILLER.match(captured) + and len(match.group(0)) < _MP2_LAYOUT_MAX_RUN + ): + continue line_num = get_line_number(content, match.start()) findings.append( AnalyzerFinding( diff --git a/tests/unit/test_patterns_new.py b/tests/unit/test_patterns_new.py index 9173e499..4f12fa27 100644 --- a/tests/unit/test_patterns_new.py +++ b/tests/unit/test_patterns_new.py @@ -551,6 +551,34 @@ def test_mp2_repeated_pattern(self) -> None: def test_mp2_separator_not_flagged(self) -> None: assert not any(f.rule_id == "MP2" for f in mp_mod.analyze("=" * 80, "test.md", "markdown")) + def test_mp2_aligned_markdown_table_is_layout(self) -> None: + # An aligned table repeats spaces and dashes because cells are being lined up, not + # because context is being displaced. + content = ( + "| campo | quando | significato |\n" + "| ---------- | ----------------- | ---------------------------------- |\n" + "| id | sempre | identificatore stabile del record |\n" + "| aggiornato | dopo ogni scrittura | timestamp dell'ultima modifica |\n" + ) + assert not any(f.rule_id == "MP2" for f in mp_mod.analyze(content, "docs/tabella.md", "markdown")) + + def test_mp2_ascii_box_is_layout(self) -> None: + content = "│ └───────────────────────────────────────────────────────┘ │\n" + assert not any(f.rule_id == "MP2" for f in mp_mod.analyze(content, "README.md", "markdown")) + + def test_mp2_padded_column_in_code_is_layout(self) -> None: + content = 'lines.append(f"{Colors.DIM}|{Colors.RESET} ")\n' + assert not any(f.rule_id == "MP2" for f in mp_mod.analyze(content, "src/ui.py", "python")) + + def test_mp2_whitespace_run_beyond_layout_width_is_still_stuffing(self) -> None: + # Layout stops being a plausible explanation well before this width. + content = "testo" + " " * 800 + "altro testo\n" + assert any(f.rule_id == "MP2" for f in mp_mod.analyze(content, "test.md", "markdown")) + + def test_mp2_repeated_content_is_still_stuffing(self) -> None: + # The unit repeated here is content, not alignment: unchanged behaviour. + assert any(f.rule_id == "MP2" for f in mp_mod.analyze("lorem " * 60, "test.md", "markdown")) + @pytest.mark.parametrize( "content", [