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,510 @@
|
|
|
1
|
+
"""Inlined shared helpers for vigil_forensic.
|
|
2
|
+
|
|
3
|
+
Contains copies/extracts of:
|
|
4
|
+
- SYSTEM.shared_helpers.gate_models (enums + dataclasses)
|
|
5
|
+
- SYSTEM.shared_helpers.coerce_helpers (coerce_float etc.)
|
|
6
|
+
- SYSTEM.shared_helpers.file_lock (acquire_file_lock)
|
|
7
|
+
- SYSTEM.shared_helpers.redaction (FORBIDDEN_KEYS + helpers)
|
|
8
|
+
- SYSTEM.execution.executor_contract (is_executor_metadata_path)
|
|
9
|
+
- SYSTEM.shared_helpers.file_extensions (WINDOWS_CLI_RUNTIME_EXTENSIONS)
|
|
10
|
+
|
|
11
|
+
All stdlib only — no BRAIN/SYSTEM/INTERFACE imports.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# coerce_helpers
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
from collections.abc import Mapping as _Mapping
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
import contextlib
|
|
22
|
+
import hashlib
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from enum import Enum
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Generator
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def coerce_int(value: object, default: int = 0) -> int:
|
|
36
|
+
if isinstance(value, bool):
|
|
37
|
+
return int(value)
|
|
38
|
+
if isinstance(value, int):
|
|
39
|
+
return value
|
|
40
|
+
if isinstance(value, float):
|
|
41
|
+
return int(value)
|
|
42
|
+
if isinstance(value, str):
|
|
43
|
+
try:
|
|
44
|
+
return int(value)
|
|
45
|
+
except ValueError:
|
|
46
|
+
return default
|
|
47
|
+
return default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def coerce_float(value: object, default: float = 0.0) -> float:
|
|
51
|
+
if isinstance(value, bool):
|
|
52
|
+
return float(value)
|
|
53
|
+
if isinstance(value, (int, float)):
|
|
54
|
+
return float(value)
|
|
55
|
+
if isinstance(value, str):
|
|
56
|
+
try:
|
|
57
|
+
return float(value)
|
|
58
|
+
except ValueError:
|
|
59
|
+
return default
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def coerce_str(value: Any) -> str:
|
|
64
|
+
return str(value) if value is not None else ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def coerce_str_tuple(value: Any) -> tuple[str, ...]:
|
|
68
|
+
if value is None:
|
|
69
|
+
return ()
|
|
70
|
+
if isinstance(value, (list, tuple)):
|
|
71
|
+
return tuple(str(item) for item in value if item is not None)
|
|
72
|
+
return ()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def coerce_dict(value: Any) -> dict[str, object]:
|
|
76
|
+
if isinstance(value, dict):
|
|
77
|
+
return dict(value)
|
|
78
|
+
return {}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def collect_extras(
|
|
82
|
+
data: _Mapping[str, object],
|
|
83
|
+
known_keys: frozenset[str],
|
|
84
|
+
) -> tuple[tuple[str, object], ...]:
|
|
85
|
+
extras = [(key, value) for key, value in data.items() if key not in known_keys]
|
|
86
|
+
extras.sort(key=lambda item: item[0])
|
|
87
|
+
return tuple(extras)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# file_lock
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
_flock_log = logging.getLogger(__name__ + ".file_lock")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@contextlib.contextmanager
|
|
98
|
+
def acquire_file_lock(
|
|
99
|
+
lock_path: Path,
|
|
100
|
+
*,
|
|
101
|
+
timeout: float = 5.0,
|
|
102
|
+
retry_interval: float = 0.05,
|
|
103
|
+
) -> Generator[None, None, None]:
|
|
104
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
fd: int | None = None
|
|
106
|
+
deadline = time.monotonic() + timeout
|
|
107
|
+
try:
|
|
108
|
+
fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR)
|
|
109
|
+
if sys.platform == "win32":
|
|
110
|
+
import msvcrt
|
|
111
|
+
while True:
|
|
112
|
+
try:
|
|
113
|
+
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
|
114
|
+
break
|
|
115
|
+
except (OSError, IOError):
|
|
116
|
+
if time.monotonic() >= deadline:
|
|
117
|
+
raise TimeoutError(
|
|
118
|
+
f"Could not acquire file lock at {lock_path} "
|
|
119
|
+
f"within {timeout}s"
|
|
120
|
+
)
|
|
121
|
+
time.sleep(retry_interval)
|
|
122
|
+
else:
|
|
123
|
+
import fcntl
|
|
124
|
+
while True:
|
|
125
|
+
try:
|
|
126
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
127
|
+
break
|
|
128
|
+
except (OSError, IOError):
|
|
129
|
+
if time.monotonic() >= deadline:
|
|
130
|
+
raise TimeoutError(
|
|
131
|
+
f"Could not acquire file lock at {lock_path} "
|
|
132
|
+
f"within {timeout}s"
|
|
133
|
+
)
|
|
134
|
+
time.sleep(retry_interval)
|
|
135
|
+
yield
|
|
136
|
+
finally:
|
|
137
|
+
if fd is not None:
|
|
138
|
+
if sys.platform == "win32":
|
|
139
|
+
import msvcrt
|
|
140
|
+
try:
|
|
141
|
+
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
|
|
142
|
+
except (OSError, IOError):
|
|
143
|
+
_flock_log.warning("acquire_file_lock: failed to unlock fd", exc_info=True)
|
|
144
|
+
else:
|
|
145
|
+
import fcntl
|
|
146
|
+
try:
|
|
147
|
+
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
148
|
+
except (OSError, IOError):
|
|
149
|
+
_flock_log.warning("acquire_file_lock: failed to unlock fd", exc_info=True)
|
|
150
|
+
os.close(fd)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# redaction
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
FORBIDDEN_KEYS: frozenset[str] = frozenset({
|
|
158
|
+
"api_key", "apikey", "authorization", "token", "access_token",
|
|
159
|
+
"refresh_token", "password", "passwd", "secret", "private_key",
|
|
160
|
+
"cookie", "session", "set-cookie", "x-api-key", "bearer",
|
|
161
|
+
"prompt", "full_prompt", "raw_context", "private_context",
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
REDACTED_PLACEHOLDER = "[REDACTED]"
|
|
165
|
+
MAX_PREVIEW_CHARS = 200
|
|
166
|
+
|
|
167
|
+
_BEARER_RE = re.compile(r"\bBearer\s+\S+", re.IGNORECASE)
|
|
168
|
+
_AKID_RE = re.compile(r"\bAKIA[0-9A-Z]{16}\b")
|
|
169
|
+
_TOKEN_LIKE_RE = re.compile(r"\b(?:sk-|gh[pousr]_|xox[abps]-)\w{16,}\b")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def truncate_preview(text: object, *, max_chars: int = MAX_PREVIEW_CHARS) -> str:
|
|
173
|
+
s = str(text)
|
|
174
|
+
if len(s) <= max_chars:
|
|
175
|
+
return s
|
|
176
|
+
return s[: max_chars - 3] + "..."
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def hash_payload(payload: object) -> str:
|
|
180
|
+
try:
|
|
181
|
+
canonical = json.dumps(payload, sort_keys=True, default=str)
|
|
182
|
+
except (TypeError, ValueError):
|
|
183
|
+
canonical = repr(payload)
|
|
184
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def safe_command(cmd: object) -> str:
|
|
188
|
+
if isinstance(cmd, (list, tuple)) and cmd:
|
|
189
|
+
return str(cmd[0])
|
|
190
|
+
if isinstance(cmd, str):
|
|
191
|
+
parts = cmd.split()
|
|
192
|
+
return parts[0] if parts else REDACTED_PLACEHOLDER
|
|
193
|
+
return REDACTED_PLACEHOLDER
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def safe_path(path: object) -> str:
|
|
197
|
+
s = str(path).replace("\\", "/")
|
|
198
|
+
parts = [p for p in s.split("/") if p]
|
|
199
|
+
if len(parts) >= 2:
|
|
200
|
+
return ".../" + "/".join(parts[-2:])
|
|
201
|
+
return s or REDACTED_PLACEHOLDER
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def safe_url(url: object) -> str:
|
|
205
|
+
s = str(url)
|
|
206
|
+
if "://" not in s:
|
|
207
|
+
return REDACTED_PLACEHOLDER
|
|
208
|
+
scheme, rest = s.split("://", 1)
|
|
209
|
+
host = rest.split("/", 1)[0].split("?", 1)[0].split("#", 1)[0]
|
|
210
|
+
if "@" in host:
|
|
211
|
+
host = host.rsplit("@", 1)[1]
|
|
212
|
+
return f"{scheme}://{host}"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def safe_error(exc: BaseException) -> dict[str, str]:
|
|
216
|
+
return {"error_type": type(exc).__name__, "error_message": truncate_preview(str(exc))}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def redact_dict(payload: _Mapping[str, Any]) -> dict[str, Any]:
|
|
220
|
+
out: dict[str, Any] = {}
|
|
221
|
+
for k, v in payload.items():
|
|
222
|
+
if str(k).lower() in FORBIDDEN_KEYS:
|
|
223
|
+
out[k] = REDACTED_PLACEHOLDER
|
|
224
|
+
continue
|
|
225
|
+
if isinstance(v, _Mapping):
|
|
226
|
+
out[k] = redact_dict(v)
|
|
227
|
+
elif isinstance(v, (list, tuple)):
|
|
228
|
+
out[k] = [
|
|
229
|
+
redact_dict(item) if isinstance(item, _Mapping) else _scrub_value(item)
|
|
230
|
+
for item in v
|
|
231
|
+
]
|
|
232
|
+
else:
|
|
233
|
+
out[k] = _scrub_value(v)
|
|
234
|
+
return out
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _scrub_value(v: object) -> object:
|
|
238
|
+
if not isinstance(v, str):
|
|
239
|
+
return v
|
|
240
|
+
if _BEARER_RE.search(v):
|
|
241
|
+
return _BEARER_RE.sub(REDACTED_PLACEHOLDER, v)
|
|
242
|
+
if _AKID_RE.search(v):
|
|
243
|
+
return _AKID_RE.sub(REDACTED_PLACEHOLDER, v)
|
|
244
|
+
if _TOKEN_LIKE_RE.search(v):
|
|
245
|
+
return _TOKEN_LIKE_RE.sub(REDACTED_PLACEHOLDER, v)
|
|
246
|
+
return v
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ---------------------------------------------------------------------------
|
|
250
|
+
# executor_contract helpers
|
|
251
|
+
# ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
METADATA_PREFIXES = (".a1/", ".cortex/", ".claude/", ".prompt-engineer/")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def is_executor_metadata_path(path: str) -> bool:
|
|
257
|
+
normalized = str(path or "").strip().replace("\\", "/")
|
|
258
|
+
return any(normalized.startswith(prefix) for prefix in METADATA_PREFIXES)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
# file_extensions
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
WINDOWS_CLI_RUNTIME_EXTENSIONS: frozenset[str] = frozenset({
|
|
266
|
+
".bash", ".bat", ".cmd", ".cs", ".go", ".java",
|
|
267
|
+
".ps1", ".psm1", ".py", ".rs", ".sh",
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
BINARY_EXTENSIONS: frozenset[str] = frozenset({
|
|
271
|
+
".dll", ".eot", ".exe", ".gif", ".ico", ".jpeg", ".jpg",
|
|
272
|
+
".png", ".pyc", ".pyo", ".so", ".ttf", ".woff", ".woff2",
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
SOURCE_EXTENSIONS: frozenset[str] = frozenset({
|
|
276
|
+
".cs", ".go", ".java", ".js", ".jsx", ".py", ".rb", ".rs", ".ts", ".tsx",
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ---------------------------------------------------------------------------
|
|
281
|
+
# gate_models (vocabulary types) — copied from the Vigil shared_helpers.gate_models
|
|
282
|
+
# ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
class GateVerdict(str, Enum):
|
|
285
|
+
PASS = "pass"
|
|
286
|
+
REVISE = "revise"
|
|
287
|
+
BLOCK = "block"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class RepairKind(str, Enum):
|
|
291
|
+
REFACTOR = "refactor"
|
|
292
|
+
CONSOLIDATE = "consolidate"
|
|
293
|
+
ADD_PROOF = "add_proof"
|
|
294
|
+
ADD_TEST = "add_test"
|
|
295
|
+
FIX_CONTRACT = "fix_contract"
|
|
296
|
+
REMOVE_FALLBACK = "remove_fallback"
|
|
297
|
+
SPLIT_MODULE = "split_module"
|
|
298
|
+
EXTRACT_SHARED = "extract_shared"
|
|
299
|
+
VALIDATE_BOUNDARY = "validate_boundary"
|
|
300
|
+
REMOVE_DUPLICATE = "remove_duplicate"
|
|
301
|
+
EDIT_CANONICAL = "edit_canonical_module"
|
|
302
|
+
ADD_BOUNDARY_CHECK = "add_boundary_check"
|
|
303
|
+
REPLACE_WITH_FAIL_LOUD = "replace_fallback_with_fail_loud"
|
|
304
|
+
ADD_MISSING_PROOF = "add_missing_proof"
|
|
305
|
+
ADD_REGRESSION_TEST = "add_regression_test"
|
|
306
|
+
REMOVE_DEAD_SURFACE = "remove_dead_surface"
|
|
307
|
+
NORMALIZE_SHAPE = "normalize_shape"
|
|
308
|
+
FIX_ENCODING = "fix_encoding_safety"
|
|
309
|
+
INVESTIGATE_GATE_FAILURE = "investigate_gate_failure"
|
|
310
|
+
FIX_SYNTAX = "fix_syntax"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class GateSeverity(str, Enum):
|
|
314
|
+
INFO = "info"
|
|
315
|
+
LOW = "low"
|
|
316
|
+
MEDIUM = "medium"
|
|
317
|
+
HIGH = "high"
|
|
318
|
+
CRITICAL = "critical"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class GateImpact(str, Enum):
|
|
322
|
+
WARN = "warn"
|
|
323
|
+
REVISE = "revise"
|
|
324
|
+
BLOCK = "block"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class GateCategory(str, Enum):
|
|
328
|
+
CONTRACT = "contract_integrity"
|
|
329
|
+
TRUTH_BOUNDARY = "truth_boundary"
|
|
330
|
+
DRIFT = "drift"
|
|
331
|
+
FALLBACK = "fallback_hack_workaround"
|
|
332
|
+
DUPLICATION = "duplication_shadow_logic"
|
|
333
|
+
CONFIG_SSOT = "config_ssot"
|
|
334
|
+
RUNTIME_BEHAVIOR = "runtime_behavior"
|
|
335
|
+
PERFORMANCE = "performance"
|
|
336
|
+
SIZE_COMPLEXITY = "size_complexity"
|
|
337
|
+
TESTING = "testing_anti_patterns"
|
|
338
|
+
REPORTING = "reporting_artifact_integrity"
|
|
339
|
+
PIPELINE_CHAIN = "pipeline_chain_integrity"
|
|
340
|
+
SEMANTIC_INTENT = "semantic_intent"
|
|
341
|
+
TEMPORAL_FRESHNESS = "temporal_freshness"
|
|
342
|
+
TOOL_HOOK_COVERAGE = "tool_hook_coverage"
|
|
343
|
+
META = "meta_integrity"
|
|
344
|
+
OBSERVABILITY = "observability"
|
|
345
|
+
ML = "ml_correctness"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@dataclass(frozen=True)
|
|
349
|
+
class EvidenceReference:
|
|
350
|
+
kind: str
|
|
351
|
+
path: str = ""
|
|
352
|
+
detail: str = ""
|
|
353
|
+
ok: bool = True
|
|
354
|
+
|
|
355
|
+
def to_dict(self) -> dict[str, Any]:
|
|
356
|
+
return {"kind": self.kind, "path": self.path, "detail": self.detail, "ok": self.ok}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@dataclass(frozen=True)
|
|
360
|
+
class GateFinding:
|
|
361
|
+
check_id: str
|
|
362
|
+
category: GateCategory
|
|
363
|
+
title: str
|
|
364
|
+
severity: GateSeverity
|
|
365
|
+
impact: GateImpact
|
|
366
|
+
summary: str
|
|
367
|
+
recommendation: str
|
|
368
|
+
evidence: tuple[EvidenceReference, ...] = field(default_factory=tuple)
|
|
369
|
+
fingerprint: str = ""
|
|
370
|
+
repair_kind: str = ""
|
|
371
|
+
executor_action: str = ""
|
|
372
|
+
proof_required: str = ""
|
|
373
|
+
allowlist_allowed: bool = True
|
|
374
|
+
preferred_fix_shape: str = ""
|
|
375
|
+
confidence: float = 1.0
|
|
376
|
+
applicability: str = "applicable"
|
|
377
|
+
analysis_mode: str = "heuristic"
|
|
378
|
+
applicability_reason: str = ""
|
|
379
|
+
|
|
380
|
+
def __post_init__(self) -> None:
|
|
381
|
+
if not (0.0 <= float(self.confidence) <= 1.0):
|
|
382
|
+
raise ValueError(f"GateFinding.confidence must be in [0.0, 1.0], got {self.confidence!r}")
|
|
383
|
+
if self.applicability not in ("applicable", "not_applicable", "unknown"):
|
|
384
|
+
raise ValueError(
|
|
385
|
+
f"GateFinding.applicability must be one of "
|
|
386
|
+
f"{{applicable, not_applicable, unknown}}, got {self.applicability!r}"
|
|
387
|
+
)
|
|
388
|
+
if self.applicability != "applicable" and not (self.applicability_reason or "").strip():
|
|
389
|
+
raise ValueError(
|
|
390
|
+
f"GateFinding.applicability_reason is required when applicability != 'applicable' "
|
|
391
|
+
f"(applicability={self.applicability!r}, check_id={self.check_id!r})"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
def to_dict(self) -> dict[str, Any]:
|
|
395
|
+
return {
|
|
396
|
+
"check_id": self.check_id,
|
|
397
|
+
"category": self.category.value,
|
|
398
|
+
"title": self.title,
|
|
399
|
+
"severity": self.severity.value,
|
|
400
|
+
"impact": self.impact.value,
|
|
401
|
+
"summary": self.summary,
|
|
402
|
+
"recommendation": self.recommendation,
|
|
403
|
+
"evidence": [item.to_dict() for item in self.evidence],
|
|
404
|
+
"fingerprint": self.fingerprint,
|
|
405
|
+
"repair_kind": self.repair_kind,
|
|
406
|
+
"executor_action": self.executor_action,
|
|
407
|
+
"proof_required": self.proof_required,
|
|
408
|
+
"allowlist_allowed": self.allowlist_allowed,
|
|
409
|
+
"preferred_fix_shape": self.preferred_fix_shape,
|
|
410
|
+
"confidence": self.confidence,
|
|
411
|
+
"applicability": self.applicability,
|
|
412
|
+
"analysis_mode": self.analysis_mode,
|
|
413
|
+
"applicability_reason": self.applicability_reason,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@dataclass(frozen=True)
|
|
418
|
+
class GateCheckResult:
|
|
419
|
+
check_id: str
|
|
420
|
+
category: GateCategory
|
|
421
|
+
findings: tuple[GateFinding, ...] = field(default_factory=tuple)
|
|
422
|
+
notes: tuple[str, ...] = field(default_factory=tuple)
|
|
423
|
+
|
|
424
|
+
def to_dict(self) -> dict[str, Any]:
|
|
425
|
+
return {
|
|
426
|
+
"check_id": self.check_id,
|
|
427
|
+
"category": self.category.value,
|
|
428
|
+
"findings": [item.to_dict() for item in self.findings],
|
|
429
|
+
"notes": list(self.notes),
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@dataclass(frozen=True)
|
|
434
|
+
class GateFileSnapshot:
|
|
435
|
+
path: str
|
|
436
|
+
exists: bool
|
|
437
|
+
size: int
|
|
438
|
+
line_count: int
|
|
439
|
+
text: str = ""
|
|
440
|
+
|
|
441
|
+
def to_dict(self) -> dict[str, Any]:
|
|
442
|
+
return {
|
|
443
|
+
"path": self.path,
|
|
444
|
+
"exists": self.exists,
|
|
445
|
+
"size": self.size,
|
|
446
|
+
"line_count": self.line_count,
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@dataclass(frozen=True)
|
|
451
|
+
class RepoGateProfile:
|
|
452
|
+
profile_name: str
|
|
453
|
+
version: str
|
|
454
|
+
generated_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
455
|
+
vendored_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
456
|
+
forbidden_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
457
|
+
critical_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
458
|
+
allowlisted_large_files: tuple[str, ...] = field(default_factory=tuple)
|
|
459
|
+
performance_sensitive_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
460
|
+
required_test_roots: tuple[str, ...] = field(default_factory=tuple)
|
|
461
|
+
canonical_literal_owners: dict[str, tuple[str, ...]] = field(default_factory=dict)
|
|
462
|
+
forbidden_fallback_patterns: dict[str, GateImpact] = field(default_factory=dict)
|
|
463
|
+
size_thresholds: dict[str, int] = field(default_factory=dict)
|
|
464
|
+
severity_overrides: dict[str, GateImpact] = field(default_factory=dict)
|
|
465
|
+
required_proofs_overrides: dict[str, tuple[str, ...]] = field(default_factory=dict)
|
|
466
|
+
reporting_required_artifacts: tuple[str, ...] = field(default_factory=tuple)
|
|
467
|
+
enabled_categories: tuple[GateCategory, ...] = field(default_factory=lambda: tuple(GateCategory))
|
|
468
|
+
enabled_checks: tuple[str, ...] = field(default_factory=tuple)
|
|
469
|
+
disabled_checks: tuple[str, ...] = field(default_factory=tuple)
|
|
470
|
+
profile_path: str = ""
|
|
471
|
+
|
|
472
|
+
def is_generated_or_vendored(self, path: str) -> bool:
|
|
473
|
+
return _starts_with_any(path, self.generated_roots + self.vendored_roots)
|
|
474
|
+
|
|
475
|
+
def is_critical(self, path: str) -> bool:
|
|
476
|
+
return _starts_with_any(path, self.critical_roots)
|
|
477
|
+
|
|
478
|
+
def is_performance_sensitive(self, path: str) -> bool:
|
|
479
|
+
return _starts_with_any(path, self.performance_sensitive_roots)
|
|
480
|
+
|
|
481
|
+
def to_dict(self) -> dict[str, Any]:
|
|
482
|
+
return {
|
|
483
|
+
"profile_name": self.profile_name,
|
|
484
|
+
"version": self.version,
|
|
485
|
+
"generated_roots": list(self.generated_roots),
|
|
486
|
+
"vendored_roots": list(self.vendored_roots),
|
|
487
|
+
"forbidden_roots": list(self.forbidden_roots),
|
|
488
|
+
"critical_roots": list(self.critical_roots),
|
|
489
|
+
"allowlisted_large_files": list(self.allowlisted_large_files),
|
|
490
|
+
"performance_sensitive_roots": list(self.performance_sensitive_roots),
|
|
491
|
+
"required_test_roots": list(self.required_test_roots),
|
|
492
|
+
"canonical_literal_owners": {k: list(v) for k, v in self.canonical_literal_owners.items()},
|
|
493
|
+
"forbidden_fallback_patterns": {k: v.value for k, v in self.forbidden_fallback_patterns.items()},
|
|
494
|
+
"size_thresholds": self.size_thresholds,
|
|
495
|
+
"severity_overrides": {k: v.value for k, v in self.severity_overrides.items()},
|
|
496
|
+
"required_proofs_overrides": {k: list(v) for k, v in self.required_proofs_overrides.items()},
|
|
497
|
+
"reporting_required_artifacts": list(self.reporting_required_artifacts),
|
|
498
|
+
"enabled_categories": [item.value for item in self.enabled_categories],
|
|
499
|
+
"enabled_checks": list(self.enabled_checks),
|
|
500
|
+
"disabled_checks": list(self.disabled_checks),
|
|
501
|
+
"profile_path": self.profile_path,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _starts_with_any(path: str, prefixes: tuple[str, ...]) -> bool:
|
|
506
|
+
normalized = path.replace("\\", "/").lstrip("./")
|
|
507
|
+
return any(
|
|
508
|
+
normalized == prefix or normalized.startswith(prefix.rstrip("/") + "/")
|
|
509
|
+
for prefix in prefixes
|
|
510
|
+
)
|
vigil_forensic/_stubs.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Minimal stubs for control-plane types used by self_audit.py.
|
|
2
|
+
|
|
3
|
+
self_audit.py imports the control-plane ValidationContractProfile type and
|
|
4
|
+
the PocketCoderForensicReport type at module level. In the standalone package
|
|
5
|
+
those cluster modules do not exist, so this file provides drop-in stubs.
|
|
6
|
+
|
|
7
|
+
These are used ONLY to construct empty stub instances via .from_mapping({})
|
|
8
|
+
inside build_synthetic_context(). The full Vigil implementations have many
|
|
9
|
+
fields; the stubs here carry only what the gate logic actually reads in a
|
|
10
|
+
static (sessionless) context.
|
|
11
|
+
|
|
12
|
+
Verified minimal surface (from cli_forensic_audit._build_stub_* and
|
|
13
|
+
self_audit.build_synthetic_context):
|
|
14
|
+
- ValidationContractProfile: .from_mapping({}) -> instance; .to_dict() -> dict
|
|
15
|
+
- PocketCoderForensicReport: .from_mapping({}) -> instance; .to_dict() -> dict
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Any, Mapping
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ValidationContractProfile:
|
|
25
|
+
"""Minimal stub — enough for PostExecGateContext construction in static mode."""
|
|
26
|
+
contract_name: str = ""
|
|
27
|
+
task_classification: str = ""
|
|
28
|
+
required_proofs: tuple[str, ...] = field(default_factory=tuple)
|
|
29
|
+
optional_proofs: tuple[str, ...] = field(default_factory=tuple)
|
|
30
|
+
disqualifying_failures: tuple[str, ...] = field(default_factory=tuple)
|
|
31
|
+
requires_commit_proof: bool = False
|
|
32
|
+
requires_remote_truth: bool = False
|
|
33
|
+
requires_hook_policy_proof: bool = False
|
|
34
|
+
requires_codex_review: bool = False
|
|
35
|
+
requires_gate: bool = False
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_mapping(cls, data: Mapping[str, Any]) -> "ValidationContractProfile":
|
|
39
|
+
def _str(k: str) -> str:
|
|
40
|
+
return str(data.get(k, "") or "")
|
|
41
|
+
def _bool(k: str) -> bool:
|
|
42
|
+
return bool(data.get(k, False))
|
|
43
|
+
def _str_tuple(k: str) -> tuple[str, ...]:
|
|
44
|
+
v = data.get(k)
|
|
45
|
+
if isinstance(v, (list, tuple)):
|
|
46
|
+
return tuple(str(x) for x in v)
|
|
47
|
+
return ()
|
|
48
|
+
return cls(
|
|
49
|
+
contract_name=_str("contract_name"),
|
|
50
|
+
task_classification=_str("task_classification"),
|
|
51
|
+
required_proofs=_str_tuple("required_proofs"),
|
|
52
|
+
optional_proofs=_str_tuple("optional_proofs"),
|
|
53
|
+
disqualifying_failures=_str_tuple("disqualifying_failures"),
|
|
54
|
+
requires_commit_proof=_bool("requires_commit_proof"),
|
|
55
|
+
requires_remote_truth=_bool("requires_remote_truth"),
|
|
56
|
+
requires_hook_policy_proof=_bool("requires_hook_policy_proof"),
|
|
57
|
+
requires_codex_review=_bool("requires_codex_review"),
|
|
58
|
+
requires_gate=_bool("requires_gate"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict[str, Any]:
|
|
62
|
+
return {
|
|
63
|
+
"contract_name": self.contract_name,
|
|
64
|
+
"task_classification": self.task_classification,
|
|
65
|
+
"required_proofs": list(self.required_proofs),
|
|
66
|
+
"optional_proofs": list(self.optional_proofs),
|
|
67
|
+
"disqualifying_failures": list(self.disqualifying_failures),
|
|
68
|
+
"requires_commit_proof": self.requires_commit_proof,
|
|
69
|
+
"requires_remote_truth": self.requires_remote_truth,
|
|
70
|
+
"requires_hook_policy_proof": self.requires_hook_policy_proof,
|
|
71
|
+
"requires_codex_review": self.requires_codex_review,
|
|
72
|
+
"requires_gate": self.requires_gate,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class PocketCoderForensicReport:
|
|
78
|
+
"""Minimal stub — enough for PostExecGateContext construction in static mode.
|
|
79
|
+
|
|
80
|
+
The real class has ~30 fields. Static forensic audit reads none of them
|
|
81
|
+
substantively; gates that need forensic_report data are all in
|
|
82
|
+
skip_in_static. This stub holds the shape, all defaulting to empty/0.
|
|
83
|
+
"""
|
|
84
|
+
created_at: float = 0.0
|
|
85
|
+
session_number: int = 0
|
|
86
|
+
current_task_id: str = ""
|
|
87
|
+
blocking_issues: tuple[str, ...] = field(default_factory=tuple)
|
|
88
|
+
warnings: tuple[str, ...] = field(default_factory=tuple)
|
|
89
|
+
changed_files: tuple[str, ...] = field(default_factory=tuple)
|
|
90
|
+
task_relevant_changed_files: tuple[str, ...] = field(default_factory=tuple)
|
|
91
|
+
unexpected_changed_files: tuple[str, ...] = field(default_factory=tuple)
|
|
92
|
+
dirty_baseline_files: tuple[str, ...] = field(default_factory=tuple)
|
|
93
|
+
git_diff_stat: str = ""
|
|
94
|
+
summary: str = ""
|
|
95
|
+
project_id: str = ""
|
|
96
|
+
schema_version: str = "stub"
|
|
97
|
+
observability_coverage: float = 0.0
|
|
98
|
+
foc_findings_ids: tuple[str, ...] = field(default_factory=tuple)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_mapping(cls, data: Mapping[str, Any]) -> "PocketCoderForensicReport":
|
|
102
|
+
def _str(k: str) -> str:
|
|
103
|
+
return str(data.get(k, "") or "")
|
|
104
|
+
def _float(k: str) -> float:
|
|
105
|
+
v = data.get(k, 0.0)
|
|
106
|
+
try:
|
|
107
|
+
return float(v)
|
|
108
|
+
except (TypeError, ValueError):
|
|
109
|
+
return 0.0
|
|
110
|
+
def _int(k: str) -> int:
|
|
111
|
+
v = data.get(k, 0)
|
|
112
|
+
try:
|
|
113
|
+
return int(v)
|
|
114
|
+
except (TypeError, ValueError):
|
|
115
|
+
return 0
|
|
116
|
+
def _str_tuple(k: str) -> tuple[str, ...]:
|
|
117
|
+
v = data.get(k)
|
|
118
|
+
if isinstance(v, (list, tuple)):
|
|
119
|
+
return tuple(str(x) for x in v)
|
|
120
|
+
return ()
|
|
121
|
+
return cls(
|
|
122
|
+
created_at=_float("created_at"),
|
|
123
|
+
session_number=_int("session_number"),
|
|
124
|
+
current_task_id=_str("current_task_id"),
|
|
125
|
+
blocking_issues=_str_tuple("blocking_issues"),
|
|
126
|
+
warnings=_str_tuple("warnings"),
|
|
127
|
+
changed_files=_str_tuple("changed_files"),
|
|
128
|
+
task_relevant_changed_files=_str_tuple("task_relevant_changed_files"),
|
|
129
|
+
unexpected_changed_files=_str_tuple("unexpected_changed_files"),
|
|
130
|
+
dirty_baseline_files=_str_tuple("dirty_baseline_files"),
|
|
131
|
+
git_diff_stat=_str("git_diff_stat"),
|
|
132
|
+
summary=_str("summary"),
|
|
133
|
+
project_id=_str("project_id"),
|
|
134
|
+
schema_version=_str("schema_version") or "stub",
|
|
135
|
+
observability_coverage=_float("observability_coverage"),
|
|
136
|
+
foc_findings_ids=_str_tuple("foc_findings_ids"),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def to_dict(self) -> dict[str, Any]:
|
|
140
|
+
return {
|
|
141
|
+
"created_at": self.created_at,
|
|
142
|
+
"session_number": self.session_number,
|
|
143
|
+
"current_task_id": self.current_task_id,
|
|
144
|
+
"blocking_issues": list(self.blocking_issues),
|
|
145
|
+
"warnings": list(self.warnings),
|
|
146
|
+
"changed_files": list(self.changed_files),
|
|
147
|
+
"task_relevant_changed_files": list(self.task_relevant_changed_files),
|
|
148
|
+
"unexpected_changed_files": list(self.unexpected_changed_files),
|
|
149
|
+
"dirty_baseline_files": list(self.dirty_baseline_files),
|
|
150
|
+
"git_diff_stat": self.git_diff_stat,
|
|
151
|
+
"summary": self.summary,
|
|
152
|
+
"project_id": self.project_id,
|
|
153
|
+
"schema_version": self.schema_version,
|
|
154
|
+
"observability_coverage": self.observability_coverage,
|
|
155
|
+
"foc_findings_ids": list(self.foc_findings_ids),
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|