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,325 @@
1
+ """Python source adapter -- wraps stdlib ``ast`` for IR signal extraction.
2
+
3
+ Does NOT use regex. Does NOT depend on ``_lexer``. Uses ``ast.parse``
4
+ exclusively, inheriting empty-list fallbacks from ``RegexAdapterBase`` for
5
+ capability methods not yet wired into builders (L1 stubs).
6
+
7
+ Capabilities:
8
+ - extract_imports: working AST walker (``ast.Import`` / ``ast.ImportFrom``).
9
+ - extract_symbols: working AST walker (top-level class / function defs).
10
+ - extract_contracts: L1 stub -- returns []. L3+ wires data_contract_builder.
11
+ - extract_runtime: L1 stub -- returns []. L3+ wires runtime_builder.
12
+ - extract_writer_calls: L1 stub -- returns []. L3+ wires authority_builder.
13
+
14
+ Builders do NOT consume IR in L1 -- they continue calling their internal
15
+ helpers directly. PythonAdapter is ready for L2+ dispatch wiring.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import ast
20
+ import logging
21
+ from pathlib import Path
22
+
23
+ from ._base import RegexAdapterBase
24
+ from ._ir import (
25
+ AuthorityWriteCandidate,
26
+ ContractCandidate,
27
+ ImportEdge,
28
+ RuntimeSignal,
29
+ SymbolDef,
30
+ )
31
+
32
+ __all__ = ["PythonAdapter"]
33
+
34
+ _log = logging.getLogger(__name__)
35
+
36
+
37
+ class PythonAdapter(RegexAdapterBase):
38
+ """Python adapter using stdlib ``ast``. All four map capabilities declared.
39
+
40
+ extract_imports and extract_symbols are fully implemented via AST walking.
41
+ extract_contracts, extract_runtime, and extract_writer_calls are L1 stubs
42
+ that return empty lists -- their existing builder implementations remain
43
+ authoritative until L3+ dispatch wiring.
44
+ """
45
+
46
+ language = "python"
47
+ file_extensions = (".py",)
48
+ supports_structural = True
49
+ supports_contracts = True
50
+ supports_runtime_signals = True
51
+ supports_authority_writes = True
52
+
53
+ # ------------------------------------------------------------------
54
+ # Structural: imports + symbols
55
+ # ------------------------------------------------------------------
56
+
57
+ def extract_imports(self, content: str, path: Path) -> list[ImportEdge]:
58
+ """Parse *content* with ``ast`` and return one ImportEdge per import.
59
+
60
+ Handles:
61
+ ``import X`` -- kind="absolute"
62
+ ``import X as Y`` -- kind="absolute" (alias ignored; module name kept)
63
+ ``from X import Y`` -- kind="absolute"
64
+ ``from .X import Y`` -- kind="relative" (leading dots preserved)
65
+ ``from ..X import Y`` -- kind="relative"
66
+
67
+ Returns [] on ``SyntaxError`` without raising.
68
+ """
69
+ try:
70
+ tree = ast.parse(content)
71
+ except SyntaxError as exc:
72
+ _log.debug(
73
+ "extract_imports: SyntaxError in %s at line %s -- returning []",
74
+ path,
75
+ getattr(exc, "lineno", "?"),
76
+ )
77
+ return []
78
+
79
+ imports: list[ImportEdge] = []
80
+
81
+ for node in ast.walk(tree):
82
+ if isinstance(node, ast.Import):
83
+ for alias in node.names:
84
+ imports.append(
85
+ ImportEdge(
86
+ from_file=path.as_posix(),
87
+ to_module=alias.name,
88
+ kind="absolute",
89
+ line=node.lineno,
90
+ confidence=1.0,
91
+ )
92
+ )
93
+
94
+ elif isinstance(node, ast.ImportFrom):
95
+ if node.module is None:
96
+ # ``from . import X`` — no module name; emit one edge per name
97
+ dots = "." * (node.level or 0)
98
+ for alias in node.names:
99
+ imports.append(
100
+ ImportEdge(
101
+ from_file=path.as_posix(),
102
+ to_module=f"{dots}{alias.name}",
103
+ kind="relative",
104
+ line=node.lineno,
105
+ confidence=1.0,
106
+ )
107
+ )
108
+ else:
109
+ dots = "." * (node.level or 0)
110
+ kind = "relative" if node.level else "absolute"
111
+ imports.append(
112
+ ImportEdge(
113
+ from_file=path.as_posix(),
114
+ to_module=f"{dots}{node.module}",
115
+ kind=kind,
116
+ line=node.lineno,
117
+ confidence=1.0,
118
+ )
119
+ )
120
+
121
+ return imports
122
+
123
+ def extract_symbols(self, content: str, path: Path) -> list[SymbolDef]:
124
+ """Parse *content* with ``ast`` and return top-level class/function defs.
125
+
126
+ Only inspects ``tree.body`` (module-level statements) -- nested classes
127
+ and functions are not emitted in L1. Visibility follows Python convention:
128
+ names starting with ``_`` are ``"private"``, all others ``"public"``.
129
+
130
+ Returns [] on ``SyntaxError`` without raising.
131
+ """
132
+ try:
133
+ tree = ast.parse(content)
134
+ except SyntaxError as exc:
135
+ _log.debug(
136
+ "extract_symbols: SyntaxError in %s at line %s -- returning []",
137
+ path,
138
+ getattr(exc, "lineno", "?"),
139
+ )
140
+ return []
141
+
142
+ syms: list[SymbolDef] = []
143
+
144
+ for node in tree.body:
145
+ if isinstance(node, ast.ClassDef):
146
+ syms.append(
147
+ SymbolDef(
148
+ name=node.name,
149
+ kind="class",
150
+ line=node.lineno,
151
+ visibility=(
152
+ "private" if node.name.startswith("_") else "public"
153
+ ),
154
+ confidence=1.0,
155
+ )
156
+ )
157
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
158
+ syms.append(
159
+ SymbolDef(
160
+ name=node.name,
161
+ kind="function",
162
+ line=node.lineno,
163
+ visibility=(
164
+ "private" if node.name.startswith("_") else "public"
165
+ ),
166
+ confidence=1.0,
167
+ )
168
+ )
169
+
170
+ return syms
171
+
172
+ # ------------------------------------------------------------------
173
+ # AST helpers
174
+ # ------------------------------------------------------------------
175
+
176
+ @staticmethod
177
+ def _dotted(node: ast.AST) -> str:
178
+ """Return the dotted name of a Name/Attribute chain (best effort)."""
179
+ parts: list[str] = []
180
+ cur = node
181
+ while isinstance(cur, ast.Attribute):
182
+ parts.append(cur.attr)
183
+ cur = cur.value
184
+ if isinstance(cur, ast.Name):
185
+ parts.append(cur.id)
186
+ return ".".join(reversed(parts))
187
+
188
+ @staticmethod
189
+ def _receiver_hint(func_node: ast.AST) -> str:
190
+ """For an attribute call ``x.write_text(...)`` return the receiver name."""
191
+ if isinstance(func_node, ast.Attribute):
192
+ recv = func_node.value
193
+ if isinstance(recv, ast.Name):
194
+ return recv.id
195
+ if isinstance(recv, ast.Attribute):
196
+ return recv.attr
197
+ return ""
198
+
199
+ # ------------------------------------------------------------------
200
+ # Contracts: @dataclass / NamedTuple / TypedDict / pydantic.BaseModel
201
+ # ------------------------------------------------------------------
202
+
203
+ def extract_contracts(self, content: str, path: Path) -> list[ContractCandidate]:
204
+ """Detect data-contract classes via ``ast`` (parity with Go/Java/TS)."""
205
+ try:
206
+ tree = ast.parse(content)
207
+ except SyntaxError:
208
+ return []
209
+ out: list[ContractCandidate] = []
210
+ for node in ast.walk(tree):
211
+ if not isinstance(node, ast.ClassDef):
212
+ continue
213
+ kind: str | None = None
214
+ for dec in node.decorator_list:
215
+ target = dec.func if isinstance(dec, ast.Call) else dec
216
+ if self._dotted(target).split(".")[-1] == "dataclass":
217
+ kind = "dataclass"
218
+ break
219
+ if kind is None:
220
+ for base in node.bases:
221
+ leaf = self._dotted(base).split(".")[-1]
222
+ if leaf == "BaseModel":
223
+ kind = "pydantic_model"
224
+ break
225
+ if leaf == "TypedDict":
226
+ kind = "TypedDict"
227
+ break
228
+ if leaf == "NamedTuple":
229
+ kind = "NamedTuple"
230
+ break
231
+ if kind:
232
+ out.append(ContractCandidate(
233
+ name=node.name, contract_kind=kind, line=node.lineno, confidence=1.0,
234
+ ))
235
+ return out
236
+
237
+ # ------------------------------------------------------------------
238
+ # Runtime: import-time side effects, decorator registries, env reads
239
+ # ------------------------------------------------------------------
240
+
241
+ _REGISTRY_DECORATORS = frozenset({
242
+ "route", "register", "task", "command", "on_event",
243
+ "get", "post", "put", "delete", "fixture", "app",
244
+ })
245
+
246
+ def extract_runtime(self, content: str, path: Path) -> list[RuntimeSignal]:
247
+ """Detect import-time side effects / decorator registries / env reads via ``ast``."""
248
+ try:
249
+ tree = ast.parse(content)
250
+ except SyntaxError:
251
+ return []
252
+ out: list[RuntimeSignal] = []
253
+ # module-level bare calls = import-time side effects
254
+ for stmt in tree.body:
255
+ if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
256
+ fname = self._dotted(stmt.value.func)
257
+ out.append(RuntimeSignal(
258
+ signal_kind="import_time_side_effects",
259
+ detail=f"module-level call {fname}()",
260
+ line=stmt.lineno, confidence=0.8,
261
+ ))
262
+ for node in ast.walk(tree):
263
+ if isinstance(node, ast.Call):
264
+ fn = self._dotted(node.func)
265
+ if fn in ("os.getenv",) or fn.endswith("environ.get") or fn.endswith("os.environ"):
266
+ out.append(RuntimeSignal(
267
+ signal_kind="env_var_read", detail=fn,
268
+ line=int(getattr(node, "lineno", 0) or 0), confidence=0.9,
269
+ ))
270
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
271
+ for dec in node.decorator_list:
272
+ target = dec.func if isinstance(dec, ast.Call) else dec
273
+ dn = self._dotted(target)
274
+ if dn.split(".")[-1] in self._REGISTRY_DECORATORS:
275
+ out.append(RuntimeSignal(
276
+ signal_kind="decorator_registry",
277
+ detail=f"@{dn} on {node.name}",
278
+ line=node.lineno, confidence=0.7,
279
+ ))
280
+ return out
281
+
282
+ # ------------------------------------------------------------------
283
+ # Authority writes: .write_text/.write_bytes/.save/json.dump/open("w")
284
+ # ------------------------------------------------------------------
285
+
286
+ def extract_writer_calls(
287
+ self, content: str, path: Path
288
+ ) -> list[AuthorityWriteCandidate]:
289
+ """Detect write/save operations via ``ast`` (parity with other adapters)."""
290
+ try:
291
+ tree = ast.parse(content)
292
+ except SyntaxError:
293
+ return []
294
+ out: list[AuthorityWriteCandidate] = []
295
+ for node in ast.walk(tree):
296
+ if not isinstance(node, ast.Call):
297
+ continue
298
+ fn = self._dotted(node.func)
299
+ leaf = fn.split(".")[-1]
300
+ ln = int(getattr(node, "lineno", 0) or 0)
301
+ if leaf in ("write_text", "write_bytes"):
302
+ out.append(AuthorityWriteCandidate(
303
+ write_kind=leaf, target_hint=self._receiver_hint(node.func),
304
+ line=ln, confidence=0.9,
305
+ ))
306
+ elif leaf == "save":
307
+ out.append(AuthorityWriteCandidate(
308
+ write_kind="save", target_hint=self._receiver_hint(node.func),
309
+ line=ln, confidence=0.7,
310
+ ))
311
+ elif fn in ("json.dump",):
312
+ out.append(AuthorityWriteCandidate(
313
+ write_kind="json_dump", target_hint="", line=ln, confidence=0.9,
314
+ ))
315
+ elif leaf == "open" and len(node.args) >= 2:
316
+ mode = node.args[1]
317
+ if (
318
+ isinstance(mode, ast.Constant)
319
+ and isinstance(mode.value, str)
320
+ and any(c in mode.value for c in "wax+")
321
+ ):
322
+ out.append(AuthorityWriteCandidate(
323
+ write_kind="open_write", target_hint="", line=ln, confidence=0.8,
324
+ ))
325
+ return out