Просмотр исходного кода

fix(repo-doctor): harden large-file signals per adversarial review (3 findings)

GLM refuter (lane v4-refute-g4) refuted the first cut:
1. Bare 'one file'/'single file' guard signals matched innocent prose
   ('reads one file per call') and falsely blessed real monsters -
   signals now require intent wording (do not split / deliberately
   single-file / must not be split).
2. Section-marker regex demanded exactly '===' while the comments dim
   accepts [=-]{4,} runs - the two dims contradicted each other on the
   same body; now aligned ([=]{3,} and box chars).
3. Tests 11/13 were non-discriminating (old guard-alone logic passed
   them too); three regression fixtures added: guard-only -> WARN
   (pins the two-signal branch), prose-trap -> never INFO,
   wide-markers -> INFO (pattern parity).
Real-repo exemplars (summon.py, svg index.html) remain INFO; grade A.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0xDarkMatter 1 неделя назад
Родитель
Сommit
7c9451bda8
2 измененных файлов с 38 добавлено и 3 удалено
  1. 10 3
      skills/repo-doctor/scripts/repo-doctor.py
  2. 28 0
      skills/repo-doctor/tests/run.sh

+ 10 - 3
skills/repo-doctor/scripts/repo-doctor.py

@@ -62,14 +62,21 @@ SCRATCH_PAT = re.compile(r"^(tmp|temp|scratch|junk|old|copy|test)[-_]", re.I)
 GENERATED_PAT = re.compile(r"generated|do not edit|don'?t hand-edit|@generated", re.I)
 # Deliberateness signals for justified large files. Extend this list when the
 # doctrine adopts another unambiguous way to say that a file must stay whole.
+# Signals require INTENT wording — bare nouns like "one file"/"single file"
+# match innocent prose ("reads one file per call") and wrongly bless real
+# monsters (adversarial-review finding, 2026-07).
 LARGE_FILE_GUARD_SIGNALS = (
-    "do not split", "deliberately single-file", "deliberately single file",
-    "single-file", "single file", "one file",
+    "do not split", "don't split", "never split", "must not be split",
+    "deliberately single-file", "deliberately single file",
+    "deliberately one file",
 )
 LARGE_FILE_GUARD_PAT = re.compile(
     "|".join(re.escape(signal) for signal in LARGE_FILE_GUARD_SIGNALS), re.I)
+# Keep marker syntax in lockstep with the comments dim's SECTION_PAT or the
+# two dims contradict each other on the same body: accept any >=3-char run of
+# = or ═ around the name, not only the exact ===.
 LARGE_FILE_SECTION_PAT = re.compile(
-    r"^\s*(?:#|//|;|<!--)\s*===\s+\w[\w .-]*\s+===", re.M)
+    r"^\s*(?:#|//|;|<!--)\s*[=═]{3,}\s+\w[\w .-]*\s+[=═]{3,}", re.M)
 BOXED_SECTION_PAT = re.compile(
     r"(?m)^\s*#\s*=+\s*$\n^\s*#\s+\w[^\n]*$\n^\s*#\s*=+\s*$")
 LANDMINE_PAT = re.compile(r"landmine|gotcha|pitfall|footgun|hazard|trap|don'?t", re.I)

+ 28 - 0
skills/repo-doctor/tests/run.sh

@@ -141,6 +141,34 @@ OUT="$(run_json)"
 [ "$(get 'sum(1 for f in d["data"]["findings"] if "bare.py" in f["path"] and f["severity"]=="crit")')" = "1" ] \
     && ok "bare monster remains crit" || no "bare monster" "severity wrong"
 
+# 14-16. adversarial-review regressions (GLM refuter, 2026-07)
+# guard-only (no map) -> WARN: discriminates the two-signal branch from the
+# old guard-alone justification logic, which would have fully excused it.
+{ echo '"""Deliberately single-file; do not split this portable script."""'
+  "$PY" -c "print('\n'.join('g = %d' % i for i in range(1700)))"; } > src/guard-only.py
+# innocent prose + markers -> must stay CRIT (bare "one file" is not intent)
+{ echo '# reads one file per call and caches the result'
+  printf '# === INPUT ===\n# === PROCESSING ===\n# === OUTPUT ===\n'
+  "$PY" -c "print('\n'.join('p = %d' % i for i in range(1700)))"; } > src/prose-trap.py
+# 4-equals markers + guard -> INFO (marker-pattern parity with comments dim)
+{ echo '"""Deliberately single-file; do not split."""'
+  printf '# ==== INPUT ====\n# ==== PROCESSING ====\n# ==== OUTPUT ====\n'
+  "$PY" -c "print('\n'.join('w = %d' % i for i in range(1700)))"; } > src/wide-markers.py
+git add -A; git commit -qm "feat: refuter regression fixtures"
+OUT="$(run_json)"
+[ "$(get 'sum(1 for f in d["data"]["findings"] if "guard-only.py" in f["path"] and f["severity"]=="warn" and "map" in f["msg"].lower())')" = "1" ] \
+    && ok "guard-only monster warns for missing map (pins new branch)" \
+    || no "guard-only monster" "severity or message wrong"
+# prose is not a guard -> map-only WARN branch; the defended regression is
+# "never INFO" (no false blessing from bare 'one file' prose).
+[ "$(get 'sum(1 for f in d["data"]["findings"] if "prose-trap.py" in f["path"] and f["dim"]=="structure" and f["severity"]=="warn" and "guard" in f["msg"])')" = "1" ] \
+    && [ "$(get 'sum(1 for f in d["data"]["findings"] if "prose-trap.py" in f["path"] and f["dim"]=="structure" and f["severity"]=="info")')" = "0" ] \
+    && ok "innocent 'one file' prose does not excuse a monster" \
+    || no "prose-trap monster" "false downgrade"
+[ "$(get 'sum(1 for f in d["data"]["findings"] if "wide-markers.py" in f["path"] and f["severity"]=="info")')" = "1" ] \
+    && ok "4-equals markers count as a section map" \
+    || no "wide-markers monster" "marker pattern too narrow"
+
 echo
 echo "repo-doctor tests: $pass passed, $fail failed"
 [ "$fail" -eq 0 ] && exit 0 || exit 1