vigil-codeintel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vigil_codeintel-0.1.0.dist-info/METADATA +780 -0
- vigil_codeintel-0.1.0.dist-info/RECORD +131 -0
- vigil_codeintel-0.1.0.dist-info/WHEEL +5 -0
- vigil_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
- vigil_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
- vigil_codeintel-0.1.0.dist-info/top_level.txt +3 -0
- vigil_forensic/__init__.py +224 -0
- vigil_forensic/_git_utils.py +178 -0
- vigil_forensic/_shared.py +510 -0
- vigil_forensic/_stubs.py +156 -0
- vigil_forensic/gate_checks/__init__.py +1 -0
- vigil_forensic/gate_checks/_ast_helpers.py +629 -0
- vigil_forensic/gate_checks/_deployment_detector.py +573 -0
- vigil_forensic/gate_checks/atomic_write_checks.py +1143 -0
- vigil_forensic/gate_checks/authority_checks.py +95 -0
- vigil_forensic/gate_checks/boundary_breach_checks.py +202 -0
- vigil_forensic/gate_checks/broad_except_checks.py +301 -0
- vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py +365 -0
- vigil_forensic/gate_checks/common.py +253 -0
- vigil_forensic/gate_checks/config_safety_checks.py +704 -0
- vigil_forensic/gate_checks/config_ssot_checks.py +78 -0
- vigil_forensic/gate_checks/conflict_checks.py +193 -0
- vigil_forensic/gate_checks/context_fallback_checks.py +697 -0
- vigil_forensic/gate_checks/context_health_checks.py +289 -0
- vigil_forensic/gate_checks/contract_shape_drift_checks.py +459 -0
- vigil_forensic/gate_checks/dirty_baseline_check.py +274 -0
- vigil_forensic/gate_checks/duplication_checks.py +387 -0
- vigil_forensic/gate_checks/embedded_string_checks.py +123 -0
- vigil_forensic/gate_checks/empty_output_checks.py +87 -0
- vigil_forensic/gate_checks/encoding_checks.py +847 -0
- vigil_forensic/gate_checks/export_completeness_checks.py +156 -0
- vigil_forensic/gate_checks/fallback_checks.py +41 -0
- vigil_forensic/gate_checks/file_proliferation_checks.py +171 -0
- vigil_forensic/gate_checks/fix_without_test_checks.py +69 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py +9 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py +71 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py +322 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/core.py +273 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py +203 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py +666 -0
- vigil_forensic/gate_checks/forensic_clusters/__init__.py +193 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist.py +426 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py +302 -0
- vigil_forensic/gate_checks/forensic_clusters/api_protocol.py +231 -0
- vigil_forensic/gate_checks/forensic_clusters/async_quality.py +1156 -0
- vigil_forensic/gate_checks/forensic_clusters/code_style.py +808 -0
- vigil_forensic/gate_checks/forensic_clusters/core.py +319 -0
- vigil_forensic/gate_checks/forensic_clusters/data_quality.py +763 -0
- vigil_forensic/gate_checks/forensic_clusters/dead_code.py +480 -0
- vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py +842 -0
- vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py +240 -0
- vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py +556 -0
- vigil_forensic/gate_checks/forensic_clusters/static_analysis.py +834 -0
- vigil_forensic/gate_checks/forensic_clusters/structural_quality.py +298 -0
- vigil_forensic/gate_checks/god_object_zones_checks.py +173 -0
- vigil_forensic/gate_checks/hallucination_checks.py +566 -0
- vigil_forensic/gate_checks/hunter_artifact_completeness_check.py +139 -0
- vigil_forensic/gate_checks/implementation_overfit_checks.py +380 -0
- vigil_forensic/gate_checks/import_integrity_checks.py +233 -0
- vigil_forensic/gate_checks/imports_in_function_checks.py +283 -0
- vigil_forensic/gate_checks/ml_checks.py +318 -0
- vigil_forensic/gate_checks/performance_checks.py +106 -0
- vigil_forensic/gate_checks/project_specific_runner.py +691 -0
- vigil_forensic/gate_checks/provider_capability_checks.py +73 -0
- vigil_forensic/gate_checks/refactor_completeness_checks.py +274 -0
- vigil_forensic/gate_checks/reliability_checks.py +389 -0
- vigil_forensic/gate_checks/reporting_checks.py +55 -0
- vigil_forensic/gate_checks/runtime_behavior_checks.py +220 -0
- vigil_forensic/gate_checks/security_injection_checks.py +332 -0
- vigil_forensic/gate_checks/semantic_intent_checks.py +139 -0
- vigil_forensic/gate_checks/size_complexity_checks.py +336 -0
- vigil_forensic/gate_checks/stuck_feature_flag_checks.py +354 -0
- vigil_forensic/gate_checks/syntax_validity_checks.py +217 -0
- vigil_forensic/gate_checks/temporal_freshness_checks.py +79 -0
- vigil_forensic/gate_checks/test_quality_checks.py +946 -0
- vigil_forensic/gate_checks/testing_checks.py +149 -0
- vigil_forensic/gate_checks/toctou_checks.py +367 -0
- vigil_forensic/gate_checks/type_checking_checks.py +316 -0
- vigil_forensic/gate_models.py +392 -0
- vigil_forensic/gate_packs/__init__.py +1 -0
- vigil_forensic/gate_packs/universal.py +179 -0
- vigil_forensic/gate_profile.json +31 -0
- vigil_forensic/gate_registry.py +21 -0
- vigil_forensic/language_profiles.py +219 -0
- vigil_forensic/meta_findings.py +207 -0
- vigil_forensic/self_audit.py +725 -0
- vigil_forensic/source_analysis.py +175 -0
- vigil_mapper/__init__.py +103 -0
- vigil_mapper/_ast_helpers_minimal.py +229 -0
- vigil_mapper/_extract_imports_impl.py +123 -0
- vigil_mapper/_file_count_guard.py +129 -0
- vigil_mapper/_git_utils.py +178 -0
- vigil_mapper/_runtime_ast.py +438 -0
- vigil_mapper/_runtime_dispatch.py +137 -0
- vigil_mapper/_seed_helpers.py +82 -0
- vigil_mapper/authority_builder.py +1102 -0
- vigil_mapper/cli_entry.py +731 -0
- vigil_mapper/conflict_builder.py +818 -0
- vigil_mapper/data_contract_builder.py +446 -0
- vigil_mapper/findings_builder.py +716 -0
- vigil_mapper/fingerprint.py +53 -0
- vigil_mapper/hotspot_builder.py +539 -0
- vigil_mapper/map_common.py +449 -0
- vigil_mapper/map_errors.py +55 -0
- vigil_mapper/map_models.py +431 -0
- vigil_mapper/map_models_ext.py +206 -0
- vigil_mapper/map_models_findings.py +130 -0
- vigil_mapper/map_storage.py +455 -0
- vigil_mapper/parse_cache.py +795 -0
- vigil_mapper/refactor_boundary_builder.py +266 -0
- vigil_mapper/runtime_builder.py +527 -0
- vigil_mapper/runtime_tracer.py +243 -0
- vigil_mapper/runtime_tracer_entry.py +199 -0
- vigil_mapper/semantic_diff.py +71 -0
- vigil_mapper/source_adapters/__init__.py +109 -0
- vigil_mapper/source_adapters/_base.py +264 -0
- vigil_mapper/source_adapters/_ir.py +156 -0
- vigil_mapper/source_adapters/_lexer.py +309 -0
- vigil_mapper/source_adapters/_patterns.py +212 -0
- vigil_mapper/source_adapters/_treesitter.py +182 -0
- vigil_mapper/source_adapters/go.py +553 -0
- vigil_mapper/source_adapters/java.py +541 -0
- vigil_mapper/source_adapters/javascript.py +626 -0
- vigil_mapper/source_adapters/python.py +325 -0
- vigil_mapper/source_adapters/typescript.py +749 -0
- vigil_mapper/structural_builder.py +586 -0
- vigil_mcp/__init__.py +1 -0
- vigil_mcp/_jobs.py +587 -0
- vigil_mcp/_paths.py +93 -0
- vigil_mcp/forensic_server.py +419 -0
- vigil_mcp/map_server.py +452 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Provider capability gate check.
|
|
2
|
+
|
|
3
|
+
Detects executor provider capability mismatches before gate-monitored runs.
|
|
4
|
+
Registered in gate_registry.py before tool_hook_coverage_checks.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from vigil_forensic._shared import GateCategory, GateCheckResult, GateImpact, GateSeverity
|
|
9
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
10
|
+
from .common import build_check_result, build_finding
|
|
11
|
+
import logging
|
|
12
|
+
_log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
__all__ = ["check_provider_capabilities"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_provider_capabilities(ctx: PostExecGateContext) -> GateCheckResult:
|
|
18
|
+
"""Check executor provider capabilities against gate requirements.
|
|
19
|
+
|
|
20
|
+
- PC01: Gemini executor uses trace-file hooks (not stream) -- WARN so that
|
|
21
|
+
tool_hook_coverage gate does not BLOCK on zero hook-event count.
|
|
22
|
+
- PC02: executor_provider missing from metadata -- soft WARN.
|
|
23
|
+
"""
|
|
24
|
+
findings = []
|
|
25
|
+
|
|
26
|
+
executor_provider = str(
|
|
27
|
+
ctx.control_task_metadata.get("executor_provider") or ""
|
|
28
|
+
).strip()
|
|
29
|
+
|
|
30
|
+
if executor_provider == "gemini":
|
|
31
|
+
findings.append(
|
|
32
|
+
build_finding(
|
|
33
|
+
check_id="PC01_gemini_hook_events_in_trace",
|
|
34
|
+
category=GateCategory.CONTRACT,
|
|
35
|
+
title="Gemini executor: hook events go to trace file, not JSON stream",
|
|
36
|
+
severity=GateSeverity.LOW,
|
|
37
|
+
impact=GateImpact.REVISE,
|
|
38
|
+
summary=(
|
|
39
|
+
"Gemini executor writes hook events to the trace file, not the "
|
|
40
|
+
"JSON stream. tool_hook_coverage gate counts will be 0 -- expected."
|
|
41
|
+
),
|
|
42
|
+
recommendation="No action needed. tool_hook_coverage gate should not BLOCK on Gemini runs.",
|
|
43
|
+
|
|
44
|
+
repair_kind='refactor',
|
|
45
|
+
executor_action='Address finding details',
|
|
46
|
+
proof_required='Issue fixed',
|
|
47
|
+
allowlist_allowed=False,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not executor_provider:
|
|
52
|
+
findings.append(
|
|
53
|
+
build_finding(
|
|
54
|
+
check_id="PC02_executor_provider_unknown",
|
|
55
|
+
category=GateCategory.CONTRACT,
|
|
56
|
+
title="executor_provider metadata missing -- cannot validate capabilities",
|
|
57
|
+
severity=GateSeverity.LOW,
|
|
58
|
+
impact=GateImpact.REVISE,
|
|
59
|
+
summary="executor_provider key absent from control task metadata.",
|
|
60
|
+
recommendation="Ensure pocketcoder_executor.py writes executor_provider to control task metadata.",
|
|
61
|
+
|
|
62
|
+
repair_kind='refactor',
|
|
63
|
+
executor_action='Address finding details',
|
|
64
|
+
proof_required='Issue fixed',
|
|
65
|
+
allowlist_allowed=False,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return build_check_result(
|
|
70
|
+
check_id="provider_capability",
|
|
71
|
+
category=GateCategory.CONTRACT,
|
|
72
|
+
findings=findings,
|
|
73
|
+
)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Refactor completeness forensic gate.
|
|
2
|
+
|
|
3
|
+
refactor.partial_rename: detect AI artifact where a symbol was renamed in some
|
|
4
|
+
files but the old name persists in others — i.e. an incomplete rename.
|
|
5
|
+
|
|
6
|
+
Detection approach (v1 — static, no git history required):
|
|
7
|
+
1. Walk all touched .py files, extract public function/class names via AST.
|
|
8
|
+
2. For every pair of names across different files, compute Levenshtein distance.
|
|
9
|
+
3. If distance <= 2 and the names are not identical, emit a
|
|
10
|
+
refactor.partial_rename_candidate finding (MEDIUM / WARN).
|
|
11
|
+
|
|
12
|
+
Fails open: AST/IO errors -> skip file, never crash.
|
|
13
|
+
Allowlist supported: similar names can be legitimate (overloads, aliases).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from vigil_forensic._shared import (
|
|
21
|
+
EvidenceReference,
|
|
22
|
+
GateCategory,
|
|
23
|
+
GateImpact,
|
|
24
|
+
GateSeverity,
|
|
25
|
+
RepairKind,
|
|
26
|
+
)
|
|
27
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
28
|
+
from ..source_analysis import is_source_file
|
|
29
|
+
from .common import build_check_result, build_finding, normalize_path
|
|
30
|
+
from ._ast_helpers import parse_python_source_or_emit_finding
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
|
|
34
|
+
_log = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# FP-mitigation constants
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
# Names shorter than this are too generic to flag as partial renames.
|
|
41
|
+
_MIN_NAME_LENGTH = 6
|
|
42
|
+
|
|
43
|
+
# Levenshtein ratio floor: if edit_distance / max(len_a, len_b) > this, names
|
|
44
|
+
# are not similar enough to warrant a finding.
|
|
45
|
+
_MAX_SIMILARITY_RATIO = 0.3
|
|
46
|
+
|
|
47
|
+
# O(n²) guard: if total public names across all touched files exceeds this,
|
|
48
|
+
# sample only the first N names and emit a note.
|
|
49
|
+
_MAX_TOTAL_NAMES = 200
|
|
50
|
+
|
|
51
|
+
# Hard cap on emitted findings per run.
|
|
52
|
+
_MAX_FINDINGS = 20
|
|
53
|
+
|
|
54
|
+
# Pattern matching intentional versioned names like MyClass_v1 / MyClass_v2.
|
|
55
|
+
_VERSION_SUFFIX_RE = re.compile(r"_v\d+$")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Levenshtein distance (stdlib-only, no external deps)
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def _levenshtein(a: str, b: str) -> int:
|
|
63
|
+
"""Return the Levenshtein edit distance between *a* and *b*."""
|
|
64
|
+
if a == b:
|
|
65
|
+
return 0
|
|
66
|
+
m, n = len(a), len(b)
|
|
67
|
+
if m < n:
|
|
68
|
+
a, b, m, n = b, a, n, m
|
|
69
|
+
# Single-row DP.
|
|
70
|
+
prev = list(range(n + 1))
|
|
71
|
+
for i in range(1, m + 1):
|
|
72
|
+
curr = [i] + [0] * n
|
|
73
|
+
for j in range(1, n + 1):
|
|
74
|
+
cost = 0 if a[i - 1] == b[j - 1] else 1
|
|
75
|
+
curr[j] = min(
|
|
76
|
+
prev[j] + 1, # deletion
|
|
77
|
+
curr[j - 1] + 1, # insertion
|
|
78
|
+
prev[j - 1] + cost, # substitution
|
|
79
|
+
)
|
|
80
|
+
prev = curr
|
|
81
|
+
return prev[n]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# AST helper: extract public top-level names
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def _extract_public_names(
|
|
89
|
+
content: str,
|
|
90
|
+
*,
|
|
91
|
+
rel_path: str = "",
|
|
92
|
+
emit_finding=None,
|
|
93
|
+
) -> list[str]:
|
|
94
|
+
"""Return public (non-dunder, non-private) function and class names at
|
|
95
|
+
module or class level from *content*.
|
|
96
|
+
|
|
97
|
+
B4 (2026-04-23): on SyntaxError, emit ``meta.syntax_parse_error`` via the
|
|
98
|
+
shared helper (if ``emit_finding`` supplied) instead of silently returning
|
|
99
|
+
``[]``.
|
|
100
|
+
"""
|
|
101
|
+
tree = parse_python_source_or_emit_finding(
|
|
102
|
+
content,
|
|
103
|
+
rel_path=rel_path,
|
|
104
|
+
emit_finding=emit_finding,
|
|
105
|
+
emitting_gate="refactor_completeness",
|
|
106
|
+
)
|
|
107
|
+
if tree is None:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
names: list[str] = []
|
|
111
|
+
for node in ast.walk(tree):
|
|
112
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
113
|
+
name: str = node.name
|
|
114
|
+
if not name.startswith("_"):
|
|
115
|
+
names.append(name)
|
|
116
|
+
return names
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Main gate runner
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def run_refactor_completeness_checks(ctx: PostExecGateContext):
|
|
124
|
+
"""Detect partial renames across touched .py files.
|
|
125
|
+
|
|
126
|
+
For each pair of public names (name_a from file_a, name_b from file_b):
|
|
127
|
+
- Skip identical names.
|
|
128
|
+
- If Levenshtein(name_a, name_b) <= 2: emit MEDIUM / WARN finding.
|
|
129
|
+
|
|
130
|
+
The check operates on ctx.touched_files (not ctx.changed_files_observed)
|
|
131
|
+
so it works even without git diff context.
|
|
132
|
+
"""
|
|
133
|
+
findings = []
|
|
134
|
+
|
|
135
|
+
# Build map: normalized_path -> list[public_name]
|
|
136
|
+
file_names: dict[str, list[str]] = {}
|
|
137
|
+
|
|
138
|
+
for raw_path in ctx.touched_files:
|
|
139
|
+
normalized = normalize_path(raw_path)
|
|
140
|
+
if not is_source_file(normalized):
|
|
141
|
+
continue
|
|
142
|
+
abs_path = ctx.project_dir / normalized
|
|
143
|
+
try:
|
|
144
|
+
content = abs_path.read_text(encoding="utf-8")
|
|
145
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
146
|
+
_log.debug("refactor_completeness: cannot read %s: %s", normalized, exc)
|
|
147
|
+
continue
|
|
148
|
+
public_names = _extract_public_names(
|
|
149
|
+
content,
|
|
150
|
+
rel_path=normalized,
|
|
151
|
+
emit_finding=findings.append,
|
|
152
|
+
)
|
|
153
|
+
if public_names:
|
|
154
|
+
file_names[normalized] = public_names
|
|
155
|
+
|
|
156
|
+
# O(n²) guard: cap total public names to avoid quadratic blowup.
|
|
157
|
+
total_names = sum(len(v) for v in file_names.values())
|
|
158
|
+
_partial_scan = total_names > _MAX_TOTAL_NAMES
|
|
159
|
+
if _partial_scan:
|
|
160
|
+
_log.debug(
|
|
161
|
+
"refactor_completeness: total public names %d > %d cap; "
|
|
162
|
+
"partial scan — tune threshold if needed",
|
|
163
|
+
total_names,
|
|
164
|
+
_MAX_TOTAL_NAMES,
|
|
165
|
+
)
|
|
166
|
+
# Truncate each file's name list proportionally by capping the flat list.
|
|
167
|
+
truncated: dict[str, list[str]] = {}
|
|
168
|
+
remaining = _MAX_TOTAL_NAMES
|
|
169
|
+
for p, names in file_names.items():
|
|
170
|
+
take = min(len(names), remaining)
|
|
171
|
+
if take <= 0:
|
|
172
|
+
break
|
|
173
|
+
truncated[p] = names[:take]
|
|
174
|
+
remaining -= take
|
|
175
|
+
file_names = truncated
|
|
176
|
+
|
|
177
|
+
# Deduplicate pairs: only emit once per (name_a, name_b) pair regardless
|
|
178
|
+
# of how many files contain either name.
|
|
179
|
+
seen_pairs: set[frozenset[str]] = set()
|
|
180
|
+
|
|
181
|
+
paths = list(file_names.keys())
|
|
182
|
+
for i, path_a in enumerate(paths):
|
|
183
|
+
for path_b in paths[i + 1:]:
|
|
184
|
+
for name_a in file_names[path_a]:
|
|
185
|
+
for name_b in file_names[path_b]:
|
|
186
|
+
if name_a == name_b:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
# FP filter 1: names too short to be meaningful.
|
|
190
|
+
if len(name_a) < _MIN_NAME_LENGTH or len(name_b) < _MIN_NAME_LENGTH:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
# FP filter 2: plural-singular pairs (name_b == name_a + "s" or vice versa).
|
|
194
|
+
if name_b == name_a + "s" or name_a == name_b + "s":
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# FP filter 3: intentional versioning suffix (X_v1 vs X_v2).
|
|
198
|
+
base_a = _VERSION_SUFFIX_RE.sub("", name_a)
|
|
199
|
+
base_b = _VERSION_SUFFIX_RE.sub("", name_b)
|
|
200
|
+
if base_a == base_b:
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
pair_key = frozenset({name_a, name_b})
|
|
204
|
+
if pair_key in seen_pairs:
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
dist = _levenshtein(name_a, name_b)
|
|
208
|
+
|
|
209
|
+
# FP filter 4: similarity ratio gate.
|
|
210
|
+
ratio = dist / max(len(name_a), len(name_b))
|
|
211
|
+
if ratio > _MAX_SIMILARITY_RATIO:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
if dist <= 2:
|
|
215
|
+
seen_pairs.add(pair_key)
|
|
216
|
+
# Hard cap on findings.
|
|
217
|
+
if len(findings) >= _MAX_FINDINGS:
|
|
218
|
+
_log.debug(
|
|
219
|
+
"refactor_completeness: findings capped at %d; "
|
|
220
|
+
"tune threshold to see more",
|
|
221
|
+
_MAX_FINDINGS,
|
|
222
|
+
)
|
|
223
|
+
break
|
|
224
|
+
findings.append(
|
|
225
|
+
build_finding(
|
|
226
|
+
check_id="refactor.partial_rename_candidate",
|
|
227
|
+
category=GateCategory.DRIFT,
|
|
228
|
+
title=(
|
|
229
|
+
f"Possible partial rename: '{name_a}' vs '{name_b}'"
|
|
230
|
+
),
|
|
231
|
+
severity=GateSeverity.MEDIUM,
|
|
232
|
+
impact=GateImpact.WARN,
|
|
233
|
+
summary=(
|
|
234
|
+
f"Name '{name_a}' in {path_a} and '{name_b}' in {path_b} "
|
|
235
|
+
f"differ by {dist} edit(s) — possible incomplete rename."
|
|
236
|
+
),
|
|
237
|
+
recommendation=(
|
|
238
|
+
f"Unify naming: '{name_a}' vs '{name_b}' in "
|
|
239
|
+
f"{path_a} vs {path_b}. "
|
|
240
|
+
f"Decide the canonical name and propagate it to all call sites. "
|
|
241
|
+
f"If the similarity is intentional (overload, alias), add both "
|
|
242
|
+
f"names to the allowlist."
|
|
243
|
+
),
|
|
244
|
+
evidence=[
|
|
245
|
+
EvidenceReference(
|
|
246
|
+
kind="file",
|
|
247
|
+
path=path_a,
|
|
248
|
+
detail=f"name '{name_a}'",
|
|
249
|
+
),
|
|
250
|
+
EvidenceReference(
|
|
251
|
+
kind="file",
|
|
252
|
+
path=path_b,
|
|
253
|
+
detail=f"name '{name_b}'",
|
|
254
|
+
),
|
|
255
|
+
],
|
|
256
|
+
repair_kind=RepairKind.NORMALIZE_SHAPE.value,
|
|
257
|
+
executor_action=(
|
|
258
|
+
f"Unify naming: '{name_a}' vs '{name_b}' in "
|
|
259
|
+
f"{path_a} vs {path_b}. "
|
|
260
|
+
f"Decide canonical name and propagate."
|
|
261
|
+
),
|
|
262
|
+
proof_required=(
|
|
263
|
+
"grep for old name returns 0 matches; "
|
|
264
|
+
"all callers use canonical name"
|
|
265
|
+
),
|
|
266
|
+
allowlist_allowed=True,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return build_check_result(
|
|
271
|
+
check_id="refactor_completeness",
|
|
272
|
+
category=GateCategory.DRIFT,
|
|
273
|
+
findings=findings,
|
|
274
|
+
)
|