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,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
+ )
@@ -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