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,264 @@
1
+ """Source adapter protocol and regex base class.
2
+
3
+ Adapters extract language-specific code structure and emit IR signals.
4
+ Builders consume IR signals, decoupling language parsing from map semantics.
5
+
6
+ L1: protocol defined; PythonAdapter implements it via AST.
7
+ L2+: TypeScript/JavaScript adapters added as RegexAdapterBase subclasses.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from pathlib import Path
13
+ from re import Pattern
14
+ from typing import Protocol, runtime_checkable
15
+
16
+ from ._ir import (
17
+ AuthorityWriteCandidate,
18
+ ContractCandidate,
19
+ ImportEdge,
20
+ RuntimeSignal,
21
+ SymbolDef,
22
+ )
23
+
24
+ __all__ = ["SourceAdapter", "RegexAdapterBase"]
25
+
26
+ _log = logging.getLogger(__name__)
27
+
28
+
29
+ @runtime_checkable
30
+ class SourceAdapter(Protocol):
31
+ """Language-specific source extractor. Declares capabilities per map type.
32
+
33
+ Attributes:
34
+ language: Canonical language name, e.g. ``"python"``, ``"typescript"``.
35
+ file_extensions: Tuple of lowercase extensions this adapter handles,
36
+ e.g. ``(".py",)`` or ``(".ts", ".tsx")``.
37
+ supports_structural: True when extract_imports + extract_symbols are
38
+ implemented (not just the empty-list fallback).
39
+ supports_contracts: True when extract_contracts is implemented.
40
+ supports_runtime_signals: True when extract_runtime is implemented.
41
+ supports_authority_writes: True when extract_writer_calls is implemented.
42
+ """
43
+
44
+ language: str
45
+ file_extensions: tuple[str, ...]
46
+ supports_structural: bool
47
+ supports_contracts: bool
48
+ supports_runtime_signals: bool
49
+ supports_authority_writes: bool
50
+
51
+ def extract_imports(self, content: str, path: Path) -> list[ImportEdge]:
52
+ """Return all import relationships found in *content*."""
53
+ ...
54
+
55
+ def extract_symbols(self, content: str, path: Path) -> list[SymbolDef]:
56
+ """Return all top-level class/function definitions found in *content*."""
57
+ ...
58
+
59
+ def extract_contracts(self, content: str, path: Path) -> list[ContractCandidate]:
60
+ """Return data-contract-style type definitions found in *content*."""
61
+ ...
62
+
63
+ def extract_runtime(self, content: str, path: Path) -> list[RuntimeSignal]:
64
+ """Return import-time side effects / dynamic patterns found in *content*."""
65
+ ...
66
+
67
+ def extract_writer_calls(
68
+ self, content: str, path: Path
69
+ ) -> list[AuthorityWriteCandidate]:
70
+ """Return write/save operations that may indicate data-authority ownership."""
71
+ ...
72
+
73
+
74
+ class RegexAdapterBase:
75
+ """Shared infrastructure for regex-based adapters.
76
+
77
+ L2+ languages (TypeScript, JavaScript, Go, Java) inherit from this class
78
+ and override the capability methods they support together with the
79
+ ``supports_*`` flags.
80
+
81
+ Subclasses should also override ``_preprocess`` to strip comments and
82
+ string literals before regex matching (use ``_lexer`` helpers).
83
+ """
84
+
85
+ language: str = "unknown"
86
+ file_extensions: tuple[str, ...] = ()
87
+ supports_structural: bool = False
88
+ supports_contracts: bool = False
89
+ supports_runtime_signals: bool = False
90
+ supports_authority_writes: bool = False
91
+
92
+ # ------------------------------------------------------------------
93
+ # Pre-processing hook — subclasses override for comment/string removal
94
+ # ------------------------------------------------------------------
95
+
96
+ def _preprocess(self, content: str) -> str:
97
+ """Strip comments and strings, normalize multiline constructs.
98
+
99
+ Default: no-op. Subclasses import helpers from ``_lexer`` and override.
100
+ Python adapter does NOT override -- it uses ``ast.parse`` directly.
101
+ """
102
+ return content
103
+
104
+ # ------------------------------------------------------------------
105
+ # Default unsupported implementations — return empty lists
106
+ # Subclasses override per capability and set the matching supports_* flag.
107
+ # ------------------------------------------------------------------
108
+
109
+ def extract_imports(self, content: str, path: Path) -> list[ImportEdge]:
110
+ """Default: unsupported. Returns []."""
111
+ return []
112
+
113
+ def extract_symbols(self, content: str, path: Path) -> list[SymbolDef]:
114
+ """Default: unsupported. Returns []."""
115
+ return []
116
+
117
+ def extract_contracts(self, content: str, path: Path) -> list[ContractCandidate]:
118
+ """Default: unsupported. Returns []."""
119
+ return []
120
+
121
+ def extract_runtime(self, content: str, path: Path) -> list[RuntimeSignal]:
122
+ """Default: unsupported. Returns []."""
123
+ _log.debug("extract_runtime: %s -- not supported by %s", path, self.__class__.__name__)
124
+ return []
125
+
126
+ def extract_writer_calls(
127
+ self, content: str, path: Path
128
+ ) -> list[AuthorityWriteCandidate]:
129
+ """Default: unsupported. Returns []."""
130
+ return []
131
+
132
+ # ------------------------------------------------------------------
133
+ # Shared ES-module / CommonJS import extraction algorithm
134
+ # ------------------------------------------------------------------
135
+
136
+ @staticmethod
137
+ def _line_of(match_start: int, content: str) -> int:
138
+ """Return the 1-based line number of *match_start* within *content*."""
139
+ return content.count("\n", 0, match_start) + 1
140
+
141
+ def _symbols_from_ordered_patterns(
142
+ self,
143
+ ordered: tuple[tuple[str, "Pattern[str]"], ...],
144
+ cleaned: str,
145
+ ) -> "list[SymbolDef]":
146
+ """Collect SymbolDefs by iterating ordered (kind, regex) pairs over *cleaned*.
147
+
148
+ Deduplicates by match start position so a declaration matched by multiple
149
+ patterns is emitted only once (first-match-wins via ordering).
150
+ """
151
+ syms: list[SymbolDef] = []
152
+ seen_positions: set[int] = set()
153
+ for kind, pat in ordered:
154
+ for m in pat.finditer(cleaned):
155
+ pos = m.start()
156
+ if pos in seen_positions:
157
+ continue
158
+ seen_positions.add(pos)
159
+ name = m.group("name")
160
+ visibility = "public" if m.group("export") else "module"
161
+ syms.append(SymbolDef(
162
+ name=name,
163
+ kind=kind,
164
+ line=self._line_of(pos, cleaned),
165
+ visibility=visibility,
166
+ confidence=0.9 if visibility == "public" else 0.8,
167
+ ))
168
+ syms.sort(key=lambda s: (s.line, s.name))
169
+ return syms
170
+
171
+ def _extract_imports_from_patterns(
172
+ self,
173
+ from_path_posix: str,
174
+ multi_collapsed: str,
175
+ was_multiline_in_source: bool,
176
+ specific_patterns: tuple[Pattern[str], ...],
177
+ side_effect_patterns: tuple[Pattern[str], ...],
178
+ dynamic_pattern: Pattern[str],
179
+ classify_fn: object,
180
+ ) -> list[ImportEdge]:
181
+ """Core ES-module / CJS import extraction loop shared by JS and TS adapters.
182
+
183
+ Parameters:
184
+ from_path_posix: Posix path of the source file (used in ImportEdge).
185
+ multi_collapsed: Source text after comment-stripping and multiline
186
+ import collapsing.
187
+ was_multiline_in_source: True when the collapsing step changed the text;
188
+ used to downgrade confidence to 0.7.
189
+ specific_patterns: Ordered tuple of compiled regexes for named/
190
+ namespace/default/type/re-export forms. All must
191
+ expose a ``module`` named group.
192
+ side_effect_patterns: Regexes for side-effect and bare require imports.
193
+ Applied after *specific_patterns*; same group
194
+ requirement.
195
+ dynamic_pattern: Regex for dynamic ``import('...')`` -- always
196
+ emitted at confidence 0.7.
197
+ classify_fn: Callable(module: str) -> str -- returns
198
+ ``"absolute"`` or ``"relative"``.
199
+
200
+ Returns a list of ImportEdge sorted by (line, to_module, kind).
201
+ """
202
+ from typing import Callable
203
+ _classify: Callable[[str], str] = classify_fn # type: ignore[assignment]
204
+
205
+ edges: list[ImportEdge] = []
206
+ seen: set[tuple[int, str, str]] = set()
207
+
208
+ def _emit(module: str, line: int, kind: str, base_confidence: float) -> None:
209
+ if not module:
210
+ return
211
+ key = (line, module, kind)
212
+ if key in seen:
213
+ return
214
+ seen.add(key)
215
+ confidence = 0.7 if was_multiline_in_source else base_confidence
216
+ edges.append(
217
+ ImportEdge(
218
+ from_file=from_path_posix,
219
+ to_module=module,
220
+ kind=kind,
221
+ line=line,
222
+ confidence=confidence,
223
+ )
224
+ )
225
+
226
+ # Specific forms first (named, namespace, default, type-only, re-export).
227
+ for pat in specific_patterns:
228
+ for m in pat.finditer(multi_collapsed):
229
+ module = m.group("module")
230
+ kind = _classify(module)
231
+ base = 0.9 if kind == "absolute" else 0.8
232
+ _emit(module, self._line_of(m.start(), multi_collapsed), kind, base)
233
+
234
+ # Side-effect and bare require forms — run after specific patterns.
235
+ for pat in side_effect_patterns:
236
+ for m in pat.finditer(multi_collapsed):
237
+ module = m.group("module")
238
+ kind = _classify(module)
239
+ base = 0.9 if kind == "absolute" else 0.8
240
+ _emit(module, self._line_of(m.start(), multi_collapsed), kind, base)
241
+
242
+ # Dynamic imports — always confidence 0.7 regardless of multiline.
243
+ for m in dynamic_pattern.finditer(multi_collapsed):
244
+ module = m.group("module")
245
+ if not module:
246
+ continue
247
+ kind = _classify(module)
248
+ line = self._line_of(m.start(), multi_collapsed)
249
+ key = (line, module, kind)
250
+ if key in seen:
251
+ continue
252
+ seen.add(key)
253
+ edges.append(
254
+ ImportEdge(
255
+ from_file=from_path_posix,
256
+ to_module=module,
257
+ kind=kind,
258
+ line=line,
259
+ confidence=0.7,
260
+ )
261
+ )
262
+
263
+ edges.sort(key=lambda e: (e.line, e.to_module, e.kind))
264
+ return edges
@@ -0,0 +1,156 @@
1
+ """IR (Intermediate Representation) signal dataclasses for source adapters.
2
+
3
+ Each dataclass represents one extracted piece of information from a source file.
4
+ Builders consume these signals in L2+ to decouple language parsing from map semantics.
5
+
6
+ L1: defined and populated by PythonAdapter; builders still use internal logic directly.
7
+ L2+: builders switch to consuming IR signals via adapter dispatch.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ import logging
13
+ _log = logging.getLogger(__name__)
14
+
15
+ __all__ = [
16
+ "ImportEdge",
17
+ "SymbolDef",
18
+ "ContractCandidate",
19
+ "RuntimeSignal",
20
+ "TSRuntimeSignal",
21
+ "AuthorityWriteCandidate",
22
+ ]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class ImportEdge:
27
+ """A single import relationship extracted from a source file.
28
+
29
+ Attributes:
30
+ from_file: Posix path of the file containing the import statement.
31
+ to_module: Dotted module name being imported (may include leading dots
32
+ for relative imports, e.g. ``".sibling"``).
33
+ kind: ``"absolute"`` or ``"relative"``.
34
+ line: 1-based line number of the import statement in the source file.
35
+ confidence: Extraction confidence in [0.0, 1.0].
36
+ AST-based extractions emit 1.0; regex-based may be lower.
37
+ """
38
+
39
+ from_file: str
40
+ to_module: str
41
+ kind: str # "absolute" | "relative"
42
+ line: int
43
+ confidence: float
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class SymbolDef:
48
+ """A top-level symbol (class or function) defined in a source file.
49
+
50
+ Attributes:
51
+ name: Symbol identifier (e.g. ``"MyClass"`` or ``"my_function"``).
52
+ kind: ``"class"`` or ``"function"``.
53
+ line: 1-based line number of the definition.
54
+ visibility: ``"public"`` (no leading underscore) or ``"private"``.
55
+ confidence: Extraction confidence in [0.0, 1.0].
56
+ """
57
+
58
+ name: str
59
+ kind: str # "class" | "function"
60
+ line: int
61
+ visibility: str # "public" | "private"
62
+ confidence: float
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class ContractCandidate:
67
+ """A data-contract-style type definition detected in a source file.
68
+
69
+ Covers: ``@dataclass``, ``NamedTuple``, ``TypedDict``, ``pydantic.BaseModel``.
70
+
71
+ Attributes:
72
+ name: Class name.
73
+ contract_kind: Detection pattern, e.g. ``"dataclass"``, ``"TypedDict"``,
74
+ ``"NamedTuple"``, ``"pydantic_model"``.
75
+ line: 1-based line number.
76
+ confidence: Extraction confidence in [0.0, 1.0].
77
+ """
78
+
79
+ name: str
80
+ contract_kind: str
81
+ line: int
82
+ confidence: float
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class RuntimeSignal:
87
+ """An import-time side effect or dynamic registration pattern.
88
+
89
+ Covers: import-time side effects, ``@decorator`` registries, background
90
+ task spawns, ``os.environ`` / ``os.getenv`` reads.
91
+
92
+ Attributes:
93
+ signal_kind: Category tag, e.g. ``"import_time_side_effects"``,
94
+ ``"decorator_registry"``, ``"background_task"``,
95
+ ``"env_var_read"``.
96
+ detail: Human-readable description of the detected pattern.
97
+ line: 1-based line number (0 if unavailable).
98
+ confidence: Extraction confidence in [0.0, 1.0].
99
+ """
100
+
101
+ signal_kind: str
102
+ detail: str
103
+ line: int
104
+ confidence: float
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class TSRuntimeSignal:
109
+ """A runtime signal extracted from a TypeScript/TSX source file.
110
+
111
+ Used by TypescriptAdapter.extract_runtime() to represent framework routes,
112
+ middleware, server bootstrap, background jobs, and environment variable
113
+ reads detected via regex.
114
+
115
+ Attributes:
116
+ kind: Signal category -- ``"framework_route"``, ``"middleware"``,
117
+ ``"module_init"``, ``"background_job"``, or ``"env_access"``.
118
+ file: Posix path of the source file (relative to project root when
119
+ available, absolute otherwise).
120
+ line: 1-based line number of the detected pattern.
121
+ confidence: Extraction confidence in [0.0, 1.0].
122
+ payload: Kind-specific detail dict. Keys vary per kind:
123
+ ``framework_route``: ``route_path``, ``http_methods``, ``framework``
124
+ ``middleware``: ``framework``
125
+ ``module_init``: ``call``
126
+ ``background_job``: ``call``
127
+ ``env_access``: ``env_var``
128
+ """
129
+
130
+ kind: str # "framework_route" | "middleware" | "module_init" | "background_job" | "env_access"
131
+ file: str # posix path
132
+ line: int
133
+ confidence: float
134
+ payload: dict # kind-specific detail
135
+
136
+
137
+ @dataclass(frozen=True)
138
+ class AuthorityWriteCandidate:
139
+ """A write/save operation that may indicate data-authority ownership.
140
+
141
+ Covers: ``.write_text()``, ``.write_bytes()``, ``.save()``, ``json.dump()``,
142
+ ``open(..., "w")``, and similar patterns.
143
+
144
+ Attributes:
145
+ write_kind: Pattern category, e.g. ``"write_text"``, ``"json_dump"``,
146
+ ``"open_write"``.
147
+ target_hint: Best-effort string identifying the write target (variable
148
+ name or path fragment) -- empty string if unknown.
149
+ line: 1-based line number.
150
+ confidence: Extraction confidence in [0.0, 1.0].
151
+ """
152
+
153
+ write_kind: str
154
+ target_hint: str
155
+ line: int
156
+ confidence: float