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,431 @@
|
|
|
1
|
+
"""Data models for the map builder subsystem -- Maps 1-4 + envelope + container.
|
|
2
|
+
|
|
3
|
+
Seven frozen dataclasses (one per map) + MapMetadata envelope + RepoMaps container.
|
|
4
|
+
Each dataclass implements to_dict() / from_dict() for JSON round-trip.
|
|
5
|
+
|
|
6
|
+
Maps 5-7 (ConflictEntry, HotspotEntry, RefactorBoundary) are in map_models_ext.py.
|
|
7
|
+
|
|
8
|
+
Python 3.11 -> slots=True, kw_only=True supported.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any, Literal
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BuildMeta",
|
|
19
|
+
"MapMetadata",
|
|
20
|
+
"StructuralEntry",
|
|
21
|
+
"RuntimeNode",
|
|
22
|
+
"DataContractEntry",
|
|
23
|
+
"AuthorityDomain",
|
|
24
|
+
"ConflictEntry",
|
|
25
|
+
"HotspotEntry",
|
|
26
|
+
"RefactorBoundary",
|
|
27
|
+
"Finding",
|
|
28
|
+
"RepoMaps",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
_log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# BuildMeta — per-map build provenance (schema v2.0.0)
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class BuildMeta:
|
|
40
|
+
"""Build provenance metadata for one map payload (schema v2.0.0).
|
|
41
|
+
|
|
42
|
+
NOT frozen -- constructed incrementally in cli_entry._build_build_meta().
|
|
43
|
+
"""
|
|
44
|
+
analysis_mode: str # "python_ast" | "regex_signals" | "seed_only" | "derived" | "unsupported"
|
|
45
|
+
status: str # "ok" | "partial" | "unsupported"
|
|
46
|
+
reason: str # human-readable; empty string if status="ok"
|
|
47
|
+
confidence_avg: float
|
|
48
|
+
coverage: dict # {"files_scanned_by_lang": {...}, "files_supported_by_lang": {...}, "coverage_ratio": float}
|
|
49
|
+
producer: str # "vigil_mapper.<module>"
|
|
50
|
+
built_at: str # ISO8601 UTC Z-suffix
|
|
51
|
+
duration_s: float
|
|
52
|
+
# Phase 5.2: representative sample of files with no coverage for this map.
|
|
53
|
+
# 5-10 entries; empty list when all scanned files are supported (coverage_ratio=1.0)
|
|
54
|
+
# or when the caller does not populate the field (backward compat).
|
|
55
|
+
unsupported_files_sample: list = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict:
|
|
58
|
+
# NOTE: "built_at" and "build_duration_s" are in semantic_diff._IGNORED_FIELDS
|
|
59
|
+
# and will be stripped during I2 semantic comparisons, keeping builds deterministic.
|
|
60
|
+
# The key is "build_duration_s" (not "duration_s") so it matches _IGNORED_FIELDS.
|
|
61
|
+
return {
|
|
62
|
+
"analysis_mode": self.analysis_mode,
|
|
63
|
+
"build_duration_s": self.duration_s,
|
|
64
|
+
"built_at": self.built_at,
|
|
65
|
+
"confidence_avg": self.confidence_avg,
|
|
66
|
+
"coverage": self.coverage,
|
|
67
|
+
"producer": self.producer,
|
|
68
|
+
"reason": self.reason,
|
|
69
|
+
"status": self.status,
|
|
70
|
+
"unsupported_files_sample": list(self.unsupported_files_sample),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, d: dict) -> "BuildMeta":
|
|
75
|
+
"""Deserialise from a payload dict.
|
|
76
|
+
|
|
77
|
+
Backward-compat: if ``d`` is missing any required field (old v1.0.0
|
|
78
|
+
payload without build_meta), returns a sentinel with sensible defaults.
|
|
79
|
+
"build_duration_s" is the canonical key in the payload; "duration_s"
|
|
80
|
+
is accepted as a fallback for any pre-canonical payloads.
|
|
81
|
+
"unsupported_files_sample" defaults to [] for pre-Phase-5 payloads.
|
|
82
|
+
"""
|
|
83
|
+
return cls(
|
|
84
|
+
analysis_mode=str(d.get("analysis_mode", "python_ast")),
|
|
85
|
+
status=str(d.get("status", "ok")),
|
|
86
|
+
reason=str(d.get("reason", "")),
|
|
87
|
+
confidence_avg=float(d.get("confidence_avg", 1.0)),
|
|
88
|
+
coverage=dict(d.get("coverage", {
|
|
89
|
+
"files_scanned_by_lang": {},
|
|
90
|
+
"files_supported_by_lang": {},
|
|
91
|
+
"coverage_ratio": 0.0,
|
|
92
|
+
})),
|
|
93
|
+
producer=str(d.get("producer", "vigil_mapper.unknown")),
|
|
94
|
+
built_at=str(d.get("built_at", "")),
|
|
95
|
+
# Accept both "build_duration_s" (canonical) and legacy "duration_s".
|
|
96
|
+
duration_s=float(
|
|
97
|
+
d.get("build_duration_s", d.get("duration_s", 0.0))
|
|
98
|
+
),
|
|
99
|
+
unsupported_files_sample=list(d.get("unsupported_files_sample", [])),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Re-export Maps 5-7 from the extension module so callers can import all
|
|
104
|
+
# models from a single location: `from .map_models import ConflictEntry`.
|
|
105
|
+
from .map_models_ext import ConflictEntry, HotspotEntry, RefactorBoundary # noqa: E402
|
|
106
|
+
|
|
107
|
+
# Re-export Map 8 (Finding) from findings module
|
|
108
|
+
from .map_models_findings import Finding # noqa: E402
|
|
109
|
+
|
|
110
|
+
# Python 3.10+ supports slots + kw_only in @dataclass
|
|
111
|
+
_DATACLASS_KWARGS: dict[str, Any] = {"frozen": True}
|
|
112
|
+
if sys.version_info >= (3, 10):
|
|
113
|
+
_DATACLASS_KWARGS["slots"] = True
|
|
114
|
+
_DATACLASS_KWARGS["kw_only"] = True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# MapMetadata envelope (embedded in every entry)
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
122
|
+
class MapMetadata:
|
|
123
|
+
"""Common metadata envelope for all map entries."""
|
|
124
|
+
source: str
|
|
125
|
+
evidence: tuple[str, ...]
|
|
126
|
+
confidence: float
|
|
127
|
+
freshness: str # ISO8601 UTC Z-suffix
|
|
128
|
+
status: Literal["observed", "inferred", "validated", "canonical", "deprecated"]
|
|
129
|
+
|
|
130
|
+
def to_dict(self) -> dict:
|
|
131
|
+
return {
|
|
132
|
+
"source": self.source,
|
|
133
|
+
"evidence": list(self.evidence),
|
|
134
|
+
"confidence": self.confidence,
|
|
135
|
+
"freshness": self.freshness,
|
|
136
|
+
"status": self.status,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_dict(cls, d: dict) -> "MapMetadata":
|
|
141
|
+
return cls(
|
|
142
|
+
source=str(d["source"]),
|
|
143
|
+
evidence=tuple(d.get("evidence", [])),
|
|
144
|
+
confidence=float(d["confidence"]),
|
|
145
|
+
freshness=str(d["freshness"]),
|
|
146
|
+
status=d["status"], # type: ignore[arg-type]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Map 1: Structural Entry
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
155
|
+
class StructuralEntry:
|
|
156
|
+
"""One entry in the structural map -- represents a single Python file."""
|
|
157
|
+
file: str
|
|
158
|
+
language: str
|
|
159
|
+
size_lines: int
|
|
160
|
+
imports_out: tuple[str, ...]
|
|
161
|
+
imports_in: tuple[str, ...]
|
|
162
|
+
symbols_defined: tuple[str, ...]
|
|
163
|
+
symbols_used_external: tuple[str, ...]
|
|
164
|
+
cycles: tuple[str, ...]
|
|
165
|
+
tags: tuple[str, ...]
|
|
166
|
+
# Metadata fields (flattened for storage)
|
|
167
|
+
source: str
|
|
168
|
+
evidence: tuple[str, ...]
|
|
169
|
+
confidence: float
|
|
170
|
+
freshness: str
|
|
171
|
+
status: str
|
|
172
|
+
|
|
173
|
+
def to_dict(self) -> dict:
|
|
174
|
+
return {
|
|
175
|
+
"file": self.file,
|
|
176
|
+
"language": self.language,
|
|
177
|
+
"size_lines": self.size_lines,
|
|
178
|
+
"imports_out": list(self.imports_out),
|
|
179
|
+
"imports_in": list(self.imports_in),
|
|
180
|
+
"symbols_defined": list(self.symbols_defined),
|
|
181
|
+
"symbols_used_external": list(self.symbols_used_external),
|
|
182
|
+
"cycles": list(self.cycles),
|
|
183
|
+
"tags": list(self.tags),
|
|
184
|
+
"source": self.source,
|
|
185
|
+
"evidence": list(self.evidence),
|
|
186
|
+
"confidence": self.confidence,
|
|
187
|
+
"freshness": self.freshness,
|
|
188
|
+
"status": self.status,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_dict(cls, d: dict) -> "StructuralEntry":
|
|
193
|
+
return cls(
|
|
194
|
+
file=str(d["file"]),
|
|
195
|
+
language=str(d.get("language", "python")),
|
|
196
|
+
size_lines=int(d["size_lines"]),
|
|
197
|
+
imports_out=tuple(d.get("imports_out", [])),
|
|
198
|
+
imports_in=tuple(d.get("imports_in", [])),
|
|
199
|
+
symbols_defined=tuple(d.get("symbols_defined", [])),
|
|
200
|
+
symbols_used_external=tuple(d.get("symbols_used_external", [])),
|
|
201
|
+
cycles=tuple(d.get("cycles", [])),
|
|
202
|
+
tags=tuple(d.get("tags", [])),
|
|
203
|
+
source=str(d.get("source", "static_scan")),
|
|
204
|
+
evidence=tuple(d.get("evidence", [])),
|
|
205
|
+
confidence=float(d.get("confidence", 0.9)),
|
|
206
|
+
freshness=str(d.get("freshness", "")),
|
|
207
|
+
status=str(d.get("status", "observed")),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Map 2: Runtime Node
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
216
|
+
class RuntimeNode:
|
|
217
|
+
"""One node in the runtime map -- represents a runtime entry point or service."""
|
|
218
|
+
node: str
|
|
219
|
+
defined_in: str
|
|
220
|
+
kind: str
|
|
221
|
+
calls: tuple[str, ...]
|
|
222
|
+
side_effects: tuple[str, ...]
|
|
223
|
+
depends_on_env: tuple[str, ...]
|
|
224
|
+
order_constraints: tuple[str, ...]
|
|
225
|
+
hidden_runtime_dependencies: tuple[str, ...]
|
|
226
|
+
tags: tuple[str, ...]
|
|
227
|
+
# Metadata
|
|
228
|
+
source: str
|
|
229
|
+
evidence: tuple[str, ...]
|
|
230
|
+
confidence: float
|
|
231
|
+
freshness: str
|
|
232
|
+
status: str
|
|
233
|
+
|
|
234
|
+
def to_dict(self) -> dict:
|
|
235
|
+
return {
|
|
236
|
+
"node": self.node,
|
|
237
|
+
"defined_in": self.defined_in,
|
|
238
|
+
"kind": self.kind,
|
|
239
|
+
"calls": list(self.calls),
|
|
240
|
+
"side_effects": list(self.side_effects),
|
|
241
|
+
"depends_on_env": list(self.depends_on_env),
|
|
242
|
+
"order_constraints": list(self.order_constraints),
|
|
243
|
+
"hidden_runtime_dependencies": list(self.hidden_runtime_dependencies),
|
|
244
|
+
"tags": list(self.tags),
|
|
245
|
+
"source": self.source,
|
|
246
|
+
"evidence": list(self.evidence),
|
|
247
|
+
"confidence": self.confidence,
|
|
248
|
+
"freshness": self.freshness,
|
|
249
|
+
"status": self.status,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def from_dict(cls, d: dict) -> "RuntimeNode":
|
|
254
|
+
return cls(
|
|
255
|
+
node=str(d["node"]),
|
|
256
|
+
defined_in=str(d["defined_in"]),
|
|
257
|
+
kind=str(d.get("kind", "unknown")),
|
|
258
|
+
calls=tuple(d.get("calls", [])),
|
|
259
|
+
side_effects=tuple(d.get("side_effects", [])),
|
|
260
|
+
depends_on_env=tuple(d.get("depends_on_env", [])),
|
|
261
|
+
order_constraints=tuple(d.get("order_constraints", [])),
|
|
262
|
+
hidden_runtime_dependencies=tuple(d.get("hidden_runtime_dependencies", [])),
|
|
263
|
+
tags=tuple(d.get("tags", [])),
|
|
264
|
+
source=str(d.get("source", "static_scan")),
|
|
265
|
+
evidence=tuple(d.get("evidence", [])),
|
|
266
|
+
confidence=float(d.get("confidence", 0.8)),
|
|
267
|
+
freshness=str(d.get("freshness", "")),
|
|
268
|
+
status=str(d.get("status", "inferred")),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
# Map 3: Data Contract Entry
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
277
|
+
class DataContractEntry:
|
|
278
|
+
"""One entity in the data contract map."""
|
|
279
|
+
entity: str
|
|
280
|
+
canonical_schema: str
|
|
281
|
+
variants: tuple[str, ...] # JSON-serialised variant dicts as strings
|
|
282
|
+
transformations: tuple[str, ...] # JSON-serialised transformation dicts as strings
|
|
283
|
+
writers: tuple[str, ...]
|
|
284
|
+
readers: tuple[str, ...]
|
|
285
|
+
drift_flags: tuple[str, ...]
|
|
286
|
+
# Metadata
|
|
287
|
+
source: str
|
|
288
|
+
evidence: tuple[str, ...]
|
|
289
|
+
confidence: float
|
|
290
|
+
freshness: str
|
|
291
|
+
status: str
|
|
292
|
+
|
|
293
|
+
def to_dict(self) -> dict:
|
|
294
|
+
import json as _json
|
|
295
|
+
return {
|
|
296
|
+
"entity": self.entity,
|
|
297
|
+
"canonical_schema": self.canonical_schema,
|
|
298
|
+
"variants": [_json.loads(v) if isinstance(v, str) else v for v in self.variants],
|
|
299
|
+
"transformations": [_json.loads(t) if isinstance(t, str) else t for t in self.transformations],
|
|
300
|
+
"writers": list(self.writers),
|
|
301
|
+
"readers": list(self.readers),
|
|
302
|
+
"drift_flags": list(self.drift_flags),
|
|
303
|
+
"source": self.source,
|
|
304
|
+
"evidence": list(self.evidence),
|
|
305
|
+
"confidence": self.confidence,
|
|
306
|
+
"freshness": self.freshness,
|
|
307
|
+
"status": self.status,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def from_dict(cls, d: dict) -> "DataContractEntry":
|
|
312
|
+
import json as _json
|
|
313
|
+
def _serialize(items: list) -> tuple[str, ...]:
|
|
314
|
+
return tuple(
|
|
315
|
+
_json.dumps(item, sort_keys=True) if isinstance(item, dict) else str(item)
|
|
316
|
+
for item in items
|
|
317
|
+
)
|
|
318
|
+
return cls(
|
|
319
|
+
entity=str(d["entity"]),
|
|
320
|
+
canonical_schema=str(d.get("canonical_schema", "")),
|
|
321
|
+
variants=_serialize(d.get("variants", [])),
|
|
322
|
+
transformations=_serialize(d.get("transformations", [])),
|
|
323
|
+
writers=tuple(d.get("writers", [])),
|
|
324
|
+
readers=tuple(d.get("readers", [])),
|
|
325
|
+
drift_flags=tuple(d.get("drift_flags", [])),
|
|
326
|
+
source=str(d.get("source", "static_scan")),
|
|
327
|
+
evidence=tuple(d.get("evidence", [])),
|
|
328
|
+
confidence=float(d.get("confidence", 0.9)),
|
|
329
|
+
freshness=str(d.get("freshness", "")),
|
|
330
|
+
status=str(d.get("status", "observed")),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
# Map 4: Authority Domain
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
339
|
+
class AuthorityDomain:
|
|
340
|
+
"""One domain in the authority map."""
|
|
341
|
+
authority_domain: str
|
|
342
|
+
canonical_owner: str
|
|
343
|
+
allowed_writers: tuple[str, ...]
|
|
344
|
+
derived_readers: tuple[str, ...]
|
|
345
|
+
cache_layers: tuple[str, ...]
|
|
346
|
+
freshness_sla: str
|
|
347
|
+
invalidation_rule: str
|
|
348
|
+
drift_policy: str
|
|
349
|
+
writers_detected: tuple[str, ...] # JSON-serialised dicts as strings
|
|
350
|
+
last_drift_events: tuple[str, ...]
|
|
351
|
+
# Metadata
|
|
352
|
+
source: str
|
|
353
|
+
evidence: tuple[str, ...]
|
|
354
|
+
confidence: float
|
|
355
|
+
freshness: str
|
|
356
|
+
status: str
|
|
357
|
+
# Per-domain target file patterns for auto-discovery filtering.
|
|
358
|
+
# Glob patterns (fnmatch style) that a write call's resolved target must
|
|
359
|
+
# match for the writer to be attributed to this domain.
|
|
360
|
+
# Empty tuple → no auto-discovery (only seed-based allowed_writers remain).
|
|
361
|
+
target_file_patterns: tuple[str, ...] = ()
|
|
362
|
+
|
|
363
|
+
def to_dict(self) -> dict:
|
|
364
|
+
import json as _json
|
|
365
|
+
return {
|
|
366
|
+
"authority_domain": self.authority_domain,
|
|
367
|
+
"canonical_owner": self.canonical_owner,
|
|
368
|
+
"allowed_writers": list(self.allowed_writers),
|
|
369
|
+
"derived_readers": list(self.derived_readers),
|
|
370
|
+
"cache_layers": list(self.cache_layers),
|
|
371
|
+
"freshness_sla": self.freshness_sla,
|
|
372
|
+
"invalidation_rule": self.invalidation_rule,
|
|
373
|
+
"drift_policy": self.drift_policy,
|
|
374
|
+
"writers_detected": [
|
|
375
|
+
_json.loads(w) if isinstance(w, str) else w for w in self.writers_detected
|
|
376
|
+
],
|
|
377
|
+
"last_drift_events": list(self.last_drift_events),
|
|
378
|
+
"target_file_patterns": list(self.target_file_patterns),
|
|
379
|
+
"source": self.source,
|
|
380
|
+
"evidence": list(self.evidence),
|
|
381
|
+
"confidence": self.confidence,
|
|
382
|
+
"freshness": self.freshness,
|
|
383
|
+
"status": self.status,
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@classmethod
|
|
387
|
+
def from_dict(cls, d: dict) -> "AuthorityDomain":
|
|
388
|
+
import json as _json
|
|
389
|
+
def _serialize(items: list) -> tuple[str, ...]:
|
|
390
|
+
return tuple(
|
|
391
|
+
_json.dumps(item, sort_keys=True) if isinstance(item, dict) else str(item)
|
|
392
|
+
for item in items
|
|
393
|
+
)
|
|
394
|
+
return cls(
|
|
395
|
+
authority_domain=str(d["authority_domain"]),
|
|
396
|
+
canonical_owner=str(d.get("canonical_owner", "")),
|
|
397
|
+
allowed_writers=tuple(d.get("allowed_writers", [])),
|
|
398
|
+
derived_readers=tuple(d.get("derived_readers", [])),
|
|
399
|
+
cache_layers=tuple(d.get("cache_layers", [])),
|
|
400
|
+
freshness_sla=str(d.get("freshness_sla", "immediate")),
|
|
401
|
+
invalidation_rule=str(d.get("invalidation_rule", "")),
|
|
402
|
+
drift_policy=str(d.get("drift_policy", "fail_close")),
|
|
403
|
+
writers_detected=_serialize(d.get("writers_detected", [])),
|
|
404
|
+
last_drift_events=tuple(d.get("last_drift_events", [])),
|
|
405
|
+
# Backward-compat: missing field → empty tuple (no auto-discovery)
|
|
406
|
+
target_file_patterns=tuple(d.get("target_file_patterns", [])),
|
|
407
|
+
source=str(d.get("source", "static_scan")),
|
|
408
|
+
evidence=tuple(d.get("evidence", [])),
|
|
409
|
+
confidence=float(d.get("confidence", 0.85)),
|
|
410
|
+
freshness=str(d.get("freshness", "")),
|
|
411
|
+
status=str(d.get("status", "observed")),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
# RepoMaps container (imports ext models lazily to avoid circular imports)
|
|
417
|
+
# ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
420
|
+
class RepoMaps:
|
|
421
|
+
"""Container for all 8 maps loaded from disk."""
|
|
422
|
+
structural: tuple = field(default_factory=tuple) # tuple[StructuralEntry, ...]
|
|
423
|
+
runtime: tuple = field(default_factory=tuple) # tuple[RuntimeNode, ...]
|
|
424
|
+
data_contract: tuple = field(default_factory=tuple) # tuple[DataContractEntry, ...]
|
|
425
|
+
authority: tuple = field(default_factory=tuple) # tuple[AuthorityDomain, ...]
|
|
426
|
+
conflict: tuple = field(default_factory=tuple) # tuple[ConflictEntry, ...]
|
|
427
|
+
hotspot: tuple = field(default_factory=tuple) # tuple[HotspotEntry, ...]
|
|
428
|
+
refactor_boundary: tuple = field(default_factory=tuple) # tuple[RefactorBoundary, ...]
|
|
429
|
+
findings: tuple = field(default_factory=tuple) # tuple[Finding, ...] — Map 8
|
|
430
|
+
missing: bool = False
|
|
431
|
+
schema_version: str = "2.0.0"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Data models for the map builder subsystem -- Maps 5-7.
|
|
2
|
+
|
|
3
|
+
Continuation of map_models.py:
|
|
4
|
+
ConflictEntry -- Map 5: Conflict Map
|
|
5
|
+
HotspotEntry -- Map 6: Hotspot Map
|
|
6
|
+
RefactorBoundary -- Map 7: Refactor Boundary Map
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ConflictEntry",
|
|
17
|
+
"HotspotEntry",
|
|
18
|
+
"RefactorBoundary",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
_log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_DATACLASS_KWARGS: dict[str, Any] = {"frozen": True}
|
|
24
|
+
if sys.version_info >= (3, 10):
|
|
25
|
+
_DATACLASS_KWARGS["slots"] = True
|
|
26
|
+
_DATACLASS_KWARGS["kw_only"] = True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Map 5: Conflict Entry
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
34
|
+
class ConflictEntry:
|
|
35
|
+
"""One conflict entry in the conflict map."""
|
|
36
|
+
conflict_id: str
|
|
37
|
+
domain: str
|
|
38
|
+
subject: str
|
|
39
|
+
sources: tuple[str, ...] # JSON-serialised source dicts as strings
|
|
40
|
+
severity: str # low / medium / high / critical
|
|
41
|
+
conflict_status: str # open / in_progress / resolved
|
|
42
|
+
action: str
|
|
43
|
+
# Metadata
|
|
44
|
+
source: str
|
|
45
|
+
evidence: tuple[str, ...]
|
|
46
|
+
confidence: float
|
|
47
|
+
freshness: str
|
|
48
|
+
status: str
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict:
|
|
51
|
+
import json as _json
|
|
52
|
+
return {
|
|
53
|
+
"conflict_id": self.conflict_id,
|
|
54
|
+
"domain": self.domain,
|
|
55
|
+
"subject": self.subject,
|
|
56
|
+
"sources": [
|
|
57
|
+
_json.loads(s) if isinstance(s, str) else s for s in self.sources
|
|
58
|
+
],
|
|
59
|
+
"severity": self.severity,
|
|
60
|
+
"conflict_status": self.conflict_status,
|
|
61
|
+
"action": self.action,
|
|
62
|
+
"source": self.source,
|
|
63
|
+
"evidence": list(self.evidence),
|
|
64
|
+
"confidence": self.confidence,
|
|
65
|
+
"freshness": self.freshness,
|
|
66
|
+
"status": self.status,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_dict(cls, d: dict) -> "ConflictEntry":
|
|
71
|
+
import json as _json
|
|
72
|
+
def _serialize(items: list) -> tuple[str, ...]:
|
|
73
|
+
return tuple(
|
|
74
|
+
_json.dumps(item, sort_keys=True) if isinstance(item, dict) else str(item)
|
|
75
|
+
for item in items
|
|
76
|
+
)
|
|
77
|
+
return cls(
|
|
78
|
+
conflict_id=str(d["conflict_id"]),
|
|
79
|
+
domain=str(d.get("domain", "")),
|
|
80
|
+
subject=str(d.get("subject", "")),
|
|
81
|
+
sources=_serialize(d.get("sources", [])),
|
|
82
|
+
severity=str(d.get("severity", "medium")),
|
|
83
|
+
conflict_status=str(d.get("conflict_status", "open")),
|
|
84
|
+
action=str(d.get("action", "investigate")),
|
|
85
|
+
source=str(d.get("source", "inter_map_comparison")),
|
|
86
|
+
evidence=tuple(d.get("evidence", [])),
|
|
87
|
+
confidence=float(d.get("confidence", 0.9)),
|
|
88
|
+
freshness=str(d.get("freshness", "")),
|
|
89
|
+
status=str(d.get("status", "validated")),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Map 6: Hotspot Entry
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
98
|
+
class HotspotEntry:
|
|
99
|
+
"""One entry in the hotspot map."""
|
|
100
|
+
target: str
|
|
101
|
+
hotspot_score: int
|
|
102
|
+
reasons: tuple[str, ...]
|
|
103
|
+
recommended_mode: str
|
|
104
|
+
# Metadata
|
|
105
|
+
source: str
|
|
106
|
+
evidence: tuple[str, ...]
|
|
107
|
+
confidence: float
|
|
108
|
+
freshness: str
|
|
109
|
+
status: str
|
|
110
|
+
|
|
111
|
+
def to_dict(self) -> dict:
|
|
112
|
+
return {
|
|
113
|
+
"target": self.target,
|
|
114
|
+
"hotspot_score": self.hotspot_score,
|
|
115
|
+
"reasons": list(self.reasons),
|
|
116
|
+
"recommended_mode": self.recommended_mode,
|
|
117
|
+
"source": self.source,
|
|
118
|
+
"evidence": list(self.evidence),
|
|
119
|
+
"confidence": self.confidence,
|
|
120
|
+
"freshness": self.freshness,
|
|
121
|
+
"status": self.status,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_dict(cls, d: dict) -> "HotspotEntry":
|
|
126
|
+
return cls(
|
|
127
|
+
target=str(d["target"]),
|
|
128
|
+
hotspot_score=int(d.get("hotspot_score", 0)),
|
|
129
|
+
reasons=tuple(d.get("reasons", [])),
|
|
130
|
+
recommended_mode=str(d.get("recommended_mode", "safe_refactor")),
|
|
131
|
+
source=str(d.get("source", "automated_scoring")),
|
|
132
|
+
evidence=tuple(d.get("evidence", [])),
|
|
133
|
+
confidence=float(d.get("confidence", 0.88)),
|
|
134
|
+
freshness=str(d.get("freshness", "")),
|
|
135
|
+
status=str(d.get("status", "observed")),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Map 7: Refactor Boundary
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
@dataclass(**_DATACLASS_KWARGS)
|
|
144
|
+
class RefactorBoundary:
|
|
145
|
+
"""One refactor boundary entry."""
|
|
146
|
+
boundary_id: str
|
|
147
|
+
goal: str
|
|
148
|
+
phase: str
|
|
149
|
+
allowed_files: tuple[str, ...]
|
|
150
|
+
watch_files: tuple[str, ...]
|
|
151
|
+
forbidden_files: tuple[str, ...]
|
|
152
|
+
entrypoints: tuple[str, ...]
|
|
153
|
+
must_hold_invariants: tuple[str, ...]
|
|
154
|
+
# Metadata
|
|
155
|
+
source: str
|
|
156
|
+
evidence: tuple[str, ...]
|
|
157
|
+
confidence: float
|
|
158
|
+
freshness: str
|
|
159
|
+
status: str
|
|
160
|
+
# FOC extension (Wave 1.6, schema 1.1 — additive, backwards compat preserved).
|
|
161
|
+
# Defaults match pre-extension behaviour: any boundary loaded from an old
|
|
162
|
+
# payload (no FOC keys) is treated as freely patchable by FOC.
|
|
163
|
+
safe_auto_patch: bool = True
|
|
164
|
+
suggest_only: bool = False
|
|
165
|
+
forbidden_for_foc: bool = False
|
|
166
|
+
|
|
167
|
+
def to_dict(self) -> dict:
|
|
168
|
+
return {
|
|
169
|
+
"boundary_id": self.boundary_id,
|
|
170
|
+
"goal": self.goal,
|
|
171
|
+
"phase": self.phase,
|
|
172
|
+
"allowed_files": list(self.allowed_files),
|
|
173
|
+
"watch_files": list(self.watch_files),
|
|
174
|
+
"forbidden_files": list(self.forbidden_files),
|
|
175
|
+
"entrypoints": list(self.entrypoints),
|
|
176
|
+
"must_hold_invariants": list(self.must_hold_invariants),
|
|
177
|
+
"source": self.source,
|
|
178
|
+
"evidence": list(self.evidence),
|
|
179
|
+
"confidence": self.confidence,
|
|
180
|
+
"freshness": self.freshness,
|
|
181
|
+
"status": self.status,
|
|
182
|
+
"safe_auto_patch": self.safe_auto_patch,
|
|
183
|
+
"suggest_only": self.suggest_only,
|
|
184
|
+
"forbidden_for_foc": self.forbidden_for_foc,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def from_dict(cls, d: dict) -> "RefactorBoundary":
|
|
189
|
+
return cls(
|
|
190
|
+
boundary_id=str(d["boundary_id"]),
|
|
191
|
+
goal=str(d.get("goal", "")),
|
|
192
|
+
phase=str(d.get("phase", "")),
|
|
193
|
+
allowed_files=tuple(d.get("allowed_files", [])),
|
|
194
|
+
watch_files=tuple(d.get("watch_files", [])),
|
|
195
|
+
forbidden_files=tuple(d.get("forbidden_files", [])),
|
|
196
|
+
entrypoints=tuple(d.get("entrypoints", [])),
|
|
197
|
+
must_hold_invariants=tuple(d.get("must_hold_invariants", [])),
|
|
198
|
+
source=str(d.get("source", "manual_planning")),
|
|
199
|
+
evidence=tuple(d.get("evidence", [])),
|
|
200
|
+
confidence=float(d.get("confidence", 1.0)),
|
|
201
|
+
freshness=str(d.get("freshness", "")),
|
|
202
|
+
status=str(d.get("status", "canonical")),
|
|
203
|
+
safe_auto_patch=bool(d.get("safe_auto_patch", True)),
|
|
204
|
+
suggest_only=bool(d.get("suggest_only", False)),
|
|
205
|
+
forbidden_for_foc=bool(d.get("forbidden_for_foc", False)),
|
|
206
|
+
)
|