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.
Files changed (131) hide show
  1. vigil_codeintel-0.1.0.dist-info/METADATA +780 -0
  2. vigil_codeintel-0.1.0.dist-info/RECORD +131 -0
  3. vigil_codeintel-0.1.0.dist-info/WHEEL +5 -0
  4. vigil_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
  5. vigil_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. vigil_codeintel-0.1.0.dist-info/top_level.txt +3 -0
  7. vigil_forensic/__init__.py +224 -0
  8. vigil_forensic/_git_utils.py +178 -0
  9. vigil_forensic/_shared.py +510 -0
  10. vigil_forensic/_stubs.py +156 -0
  11. vigil_forensic/gate_checks/__init__.py +1 -0
  12. vigil_forensic/gate_checks/_ast_helpers.py +629 -0
  13. vigil_forensic/gate_checks/_deployment_detector.py +573 -0
  14. vigil_forensic/gate_checks/atomic_write_checks.py +1143 -0
  15. vigil_forensic/gate_checks/authority_checks.py +95 -0
  16. vigil_forensic/gate_checks/boundary_breach_checks.py +202 -0
  17. vigil_forensic/gate_checks/broad_except_checks.py +301 -0
  18. vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py +365 -0
  19. vigil_forensic/gate_checks/common.py +253 -0
  20. vigil_forensic/gate_checks/config_safety_checks.py +704 -0
  21. vigil_forensic/gate_checks/config_ssot_checks.py +78 -0
  22. vigil_forensic/gate_checks/conflict_checks.py +193 -0
  23. vigil_forensic/gate_checks/context_fallback_checks.py +697 -0
  24. vigil_forensic/gate_checks/context_health_checks.py +289 -0
  25. vigil_forensic/gate_checks/contract_shape_drift_checks.py +459 -0
  26. vigil_forensic/gate_checks/dirty_baseline_check.py +274 -0
  27. vigil_forensic/gate_checks/duplication_checks.py +387 -0
  28. vigil_forensic/gate_checks/embedded_string_checks.py +123 -0
  29. vigil_forensic/gate_checks/empty_output_checks.py +87 -0
  30. vigil_forensic/gate_checks/encoding_checks.py +847 -0
  31. vigil_forensic/gate_checks/export_completeness_checks.py +156 -0
  32. vigil_forensic/gate_checks/fallback_checks.py +41 -0
  33. vigil_forensic/gate_checks/file_proliferation_checks.py +171 -0
  34. vigil_forensic/gate_checks/fix_without_test_checks.py +69 -0
  35. vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py +9 -0
  36. vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py +71 -0
  37. vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py +322 -0
  38. vigil_forensic/gate_checks/forensic_cluster_runners/core.py +273 -0
  39. vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py +203 -0
  40. vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py +666 -0
  41. vigil_forensic/gate_checks/forensic_clusters/__init__.py +193 -0
  42. vigil_forensic/gate_checks/forensic_clusters/allowlist.py +426 -0
  43. vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py +302 -0
  44. vigil_forensic/gate_checks/forensic_clusters/api_protocol.py +231 -0
  45. vigil_forensic/gate_checks/forensic_clusters/async_quality.py +1156 -0
  46. vigil_forensic/gate_checks/forensic_clusters/code_style.py +808 -0
  47. vigil_forensic/gate_checks/forensic_clusters/core.py +319 -0
  48. vigil_forensic/gate_checks/forensic_clusters/data_quality.py +763 -0
  49. vigil_forensic/gate_checks/forensic_clusters/dead_code.py +480 -0
  50. vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py +842 -0
  51. vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py +240 -0
  52. vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py +556 -0
  53. vigil_forensic/gate_checks/forensic_clusters/static_analysis.py +834 -0
  54. vigil_forensic/gate_checks/forensic_clusters/structural_quality.py +298 -0
  55. vigil_forensic/gate_checks/god_object_zones_checks.py +173 -0
  56. vigil_forensic/gate_checks/hallucination_checks.py +566 -0
  57. vigil_forensic/gate_checks/hunter_artifact_completeness_check.py +139 -0
  58. vigil_forensic/gate_checks/implementation_overfit_checks.py +380 -0
  59. vigil_forensic/gate_checks/import_integrity_checks.py +233 -0
  60. vigil_forensic/gate_checks/imports_in_function_checks.py +283 -0
  61. vigil_forensic/gate_checks/ml_checks.py +318 -0
  62. vigil_forensic/gate_checks/performance_checks.py +106 -0
  63. vigil_forensic/gate_checks/project_specific_runner.py +691 -0
  64. vigil_forensic/gate_checks/provider_capability_checks.py +73 -0
  65. vigil_forensic/gate_checks/refactor_completeness_checks.py +274 -0
  66. vigil_forensic/gate_checks/reliability_checks.py +389 -0
  67. vigil_forensic/gate_checks/reporting_checks.py +55 -0
  68. vigil_forensic/gate_checks/runtime_behavior_checks.py +220 -0
  69. vigil_forensic/gate_checks/security_injection_checks.py +332 -0
  70. vigil_forensic/gate_checks/semantic_intent_checks.py +139 -0
  71. vigil_forensic/gate_checks/size_complexity_checks.py +336 -0
  72. vigil_forensic/gate_checks/stuck_feature_flag_checks.py +354 -0
  73. vigil_forensic/gate_checks/syntax_validity_checks.py +217 -0
  74. vigil_forensic/gate_checks/temporal_freshness_checks.py +79 -0
  75. vigil_forensic/gate_checks/test_quality_checks.py +946 -0
  76. vigil_forensic/gate_checks/testing_checks.py +149 -0
  77. vigil_forensic/gate_checks/toctou_checks.py +367 -0
  78. vigil_forensic/gate_checks/type_checking_checks.py +316 -0
  79. vigil_forensic/gate_models.py +392 -0
  80. vigil_forensic/gate_packs/__init__.py +1 -0
  81. vigil_forensic/gate_packs/universal.py +179 -0
  82. vigil_forensic/gate_profile.json +31 -0
  83. vigil_forensic/gate_registry.py +21 -0
  84. vigil_forensic/language_profiles.py +219 -0
  85. vigil_forensic/meta_findings.py +207 -0
  86. vigil_forensic/self_audit.py +725 -0
  87. vigil_forensic/source_analysis.py +175 -0
  88. vigil_mapper/__init__.py +103 -0
  89. vigil_mapper/_ast_helpers_minimal.py +229 -0
  90. vigil_mapper/_extract_imports_impl.py +123 -0
  91. vigil_mapper/_file_count_guard.py +129 -0
  92. vigil_mapper/_git_utils.py +178 -0
  93. vigil_mapper/_runtime_ast.py +438 -0
  94. vigil_mapper/_runtime_dispatch.py +137 -0
  95. vigil_mapper/_seed_helpers.py +82 -0
  96. vigil_mapper/authority_builder.py +1102 -0
  97. vigil_mapper/cli_entry.py +731 -0
  98. vigil_mapper/conflict_builder.py +818 -0
  99. vigil_mapper/data_contract_builder.py +446 -0
  100. vigil_mapper/findings_builder.py +716 -0
  101. vigil_mapper/fingerprint.py +53 -0
  102. vigil_mapper/hotspot_builder.py +539 -0
  103. vigil_mapper/map_common.py +449 -0
  104. vigil_mapper/map_errors.py +55 -0
  105. vigil_mapper/map_models.py +431 -0
  106. vigil_mapper/map_models_ext.py +206 -0
  107. vigil_mapper/map_models_findings.py +130 -0
  108. vigil_mapper/map_storage.py +455 -0
  109. vigil_mapper/parse_cache.py +795 -0
  110. vigil_mapper/refactor_boundary_builder.py +266 -0
  111. vigil_mapper/runtime_builder.py +527 -0
  112. vigil_mapper/runtime_tracer.py +243 -0
  113. vigil_mapper/runtime_tracer_entry.py +199 -0
  114. vigil_mapper/semantic_diff.py +71 -0
  115. vigil_mapper/source_adapters/__init__.py +109 -0
  116. vigil_mapper/source_adapters/_base.py +264 -0
  117. vigil_mapper/source_adapters/_ir.py +156 -0
  118. vigil_mapper/source_adapters/_lexer.py +309 -0
  119. vigil_mapper/source_adapters/_patterns.py +212 -0
  120. vigil_mapper/source_adapters/_treesitter.py +182 -0
  121. vigil_mapper/source_adapters/go.py +553 -0
  122. vigil_mapper/source_adapters/java.py +541 -0
  123. vigil_mapper/source_adapters/javascript.py +626 -0
  124. vigil_mapper/source_adapters/python.py +325 -0
  125. vigil_mapper/source_adapters/typescript.py +749 -0
  126. vigil_mapper/structural_builder.py +586 -0
  127. vigil_mcp/__init__.py +1 -0
  128. vigil_mcp/_jobs.py +587 -0
  129. vigil_mcp/_paths.py +93 -0
  130. vigil_mcp/forensic_server.py +419 -0
  131. 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
+ )