arcgraph 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.
- arcgraph/__init__.py +41 -0
- arcgraph/_build_provenance.json +10 -0
- arcgraph/adapters/__init__.py +6 -0
- arcgraph/adapters/arq_adapter.py +369 -0
- arcgraph/adapters/celery_adapter.py +440 -0
- arcgraph/adapters/common.py +436 -0
- arcgraph/adapters/django_adapter.py +765 -0
- arcgraph/adapters/fastapi_adapter.py +415 -0
- arcgraph/adapters/logging_adapter.py +335 -0
- arcgraph/adapters/mcp_adapter.py +100 -0
- arcgraph/adapters/pydantic_adapter.py +290 -0
- arcgraph/adapters/pytest_adapter.py +221 -0
- arcgraph/adapters/pytest_collection.py +96 -0
- arcgraph/adapters/pytest_fixtures.py +371 -0
- arcgraph/adapters/pytest_types.py +251 -0
- arcgraph/adapters/registry.py +34 -0
- arcgraph/adapters/service_container_adapter.py +548 -0
- arcgraph/adapters/sqlalchemy_adapter.py +446 -0
- arcgraph/adapters/typer_click_adapter.py +292 -0
- arcgraph/analyzers/__init__.py +1 -0
- arcgraph/analyzers/bindings.py +782 -0
- arcgraph/analyzers/calls/__init__.py +6 -0
- arcgraph/analyzers/calls/analyzer.py +2921 -0
- arcgraph/analyzers/calls/constants.py +702 -0
- arcgraph/analyzers/calls/context.py +298 -0
- arcgraph/analyzers/calls/lexical.py +237 -0
- arcgraph/analyzers/coverage.py +826 -0
- arcgraph/analyzers/exports.py +46 -0
- arcgraph/analyzers/har_trace.py +323 -0
- arcgraph/analyzers/imports.py +190 -0
- arcgraph/analyzers/otel_trace.py +352 -0
- arcgraph/analyzers/precise_references.py +1199 -0
- arcgraph/analyzers/python_scopes.py +56 -0
- arcgraph/analyzers/runtime_trace.py +516 -0
- arcgraph/analyzers/similarity.py +430 -0
- arcgraph/analyzers/symbols.py +363 -0
- arcgraph/analyzers/type_unions.py +89 -0
- arcgraph/analyzers/types.py +2293 -0
- arcgraph/assets/__init__.py +1 -0
- arcgraph/assets/workbench/__init__.py +1 -0
- arcgraph/assets/workbench/index.html +2398 -0
- arcgraph/assets/workbench/vendor/LICENSE.d3.txt +15 -0
- arcgraph/assets/workbench/vendor/d3.v7.min.js +2 -0
- arcgraph/change/__init__.py +10 -0
- arcgraph/change/baseline.py +145 -0
- arcgraph/change/changed_regions.py +728 -0
- arcgraph/change/contracts.py +927 -0
- arcgraph/change/errors.py +88 -0
- arcgraph/change/evidence.py +289 -0
- arcgraph/change/graph_delta.py +1422 -0
- arcgraph/change/identities.py +1277 -0
- arcgraph/change/paths.py +234 -0
- arcgraph/change/pins.py +426 -0
- arcgraph/change/planner.py +504 -0
- arcgraph/change/protected_surfaces.py +328 -0
- arcgraph/change/provider.py +46 -0
- arcgraph/change/service.py +1030 -0
- arcgraph/change/source_provider.py +311 -0
- arcgraph/change/store.py +1138 -0
- arcgraph/change/trusted_runners.py +154 -0
- arcgraph/change/verdicts.py +141 -0
- arcgraph/change/verification_plan.py +113 -0
- arcgraph/change/verifier.py +579 -0
- arcgraph/core/__init__.py +1 -0
- arcgraph/core/assurance.py +343 -0
- arcgraph/core/binding_metrics.py +75 -0
- arcgraph/core/cleanup.py +701 -0
- arcgraph/core/confidence_profiles.py +108 -0
- arcgraph/core/evidence_manifest.py +862 -0
- arcgraph/core/expression_kinds.py +11 -0
- arcgraph/core/force_graph_export.py +1125 -0
- arcgraph/core/freshness.py +94 -0
- arcgraph/core/graph_store.py +1099 -0
- arcgraph/core/ids.py +206 -0
- arcgraph/core/language_tiers.py +150 -0
- arcgraph/core/merge.py +649 -0
- arcgraph/core/metadata.py +30 -0
- arcgraph/core/operation_lock.py +218 -0
- arcgraph/core/passes.py +142 -0
- arcgraph/core/payload_policy.py +708 -0
- arcgraph/core/precise_references.py +124 -0
- arcgraph/core/query_engine.py +3078 -0
- arcgraph/core/recovery.py +18 -0
- arcgraph/core/scanner.py +1454 -0
- arcgraph/core/schemas.py +700 -0
- arcgraph/core/semantic.py +263 -0
- arcgraph/core/semantic_metrics.py +619 -0
- arcgraph/core/sharing_retry.py +84 -0
- arcgraph/core/structural.py +260 -0
- arcgraph/core/target_resolver.py +486 -0
- arcgraph/core/type_metrics.py +67 -0
- arcgraph/core/unresolved_classification.py +471 -0
- arcgraph/core/utils.py +100 -0
- arcgraph/core/visual_contract.py +240 -0
- arcgraph/core/visual_slice_engine.py +933 -0
- arcgraph/interfaces/__init__.py +1 -0
- arcgraph/interfaces/agent_capabilities.py +493 -0
- arcgraph/interfaces/agent_help.py +313 -0
- arcgraph/interfaces/benchmark.py +548 -0
- arcgraph/interfaces/ci.py +1992 -0
- arcgraph/interfaces/cli.py +2799 -0
- arcgraph/interfaces/cli_change.py +690 -0
- arcgraph/interfaces/cli_feedback.py +176 -0
- arcgraph/interfaces/cli_ops.py +69 -0
- arcgraph/interfaces/cli_support.py +113 -0
- arcgraph/interfaces/cli_trial.py +42 -0
- arcgraph/interfaces/cli_visual.py +601 -0
- arcgraph/interfaces/cli_workspace.py +300 -0
- arcgraph/interfaces/client_setup.py +481 -0
- arcgraph/interfaces/docs.py +1255 -0
- arcgraph/interfaces/human_output.py +409 -0
- arcgraph/interfaces/local_state.py +577 -0
- arcgraph/interfaces/mcp_server.py +457 -0
- arcgraph/interfaces/mcp_tools.py +1209 -0
- arcgraph/interfaces/metrics.py +571 -0
- arcgraph/interfaces/ops.py +330 -0
- arcgraph/interfaces/precision.py +1670 -0
- arcgraph/interfaces/project_probe.py +75 -0
- arcgraph/interfaces/reports.py +1121 -0
- arcgraph/interfaces/stdio_encoding.py +29 -0
- arcgraph/interfaces/trace.py +592 -0
- arcgraph/interfaces/trial_feedback.py +519 -0
- arcgraph/interfaces/trial_setup.py +535 -0
- arcgraph/interfaces/visual_server.py +354 -0
- arcgraph/interfaces/visual_slice.py +168 -0
- arcgraph/interfaces/visual_smoke.py +666 -0
- arcgraph/interfaces/workbench.py +576 -0
- arcgraph/interfaces/workspace.py +1513 -0
- arcgraph/pipeline/__init__.py +1 -0
- arcgraph/pipeline/contracts.py +122 -0
- arcgraph/pipeline/external_frontend.py +451 -0
- arcgraph/pipeline/external_l3_frontends.py +368 -0
- arcgraph/pipeline/external_protocol.py +358 -0
- arcgraph/pipeline/frontends.py +437 -0
- arcgraph/pipeline/indexer.py +661 -0
- arcgraph/pipeline/openapi_frontend.py +708 -0
- arcgraph/pipeline/python_frontend.py +298 -0
- arcgraph/pipeline/reindexer.py +1333 -0
- arcgraph/pipeline/scip_frontend.py +726 -0
- arcgraph/pipeline/typescript_extractor/declarations.mjs +183 -0
- arcgraph/pipeline/typescript_extractor/edges.mjs +2794 -0
- arcgraph/pipeline/typescript_extractor/graph_primitives.mjs +189 -0
- arcgraph/pipeline/typescript_extractor/module_resolution.mjs +877 -0
- arcgraph/pipeline/typescript_extractor/runtime.mjs +56 -0
- arcgraph/pipeline/typescript_extractor/similarity.mjs +619 -0
- arcgraph/pipeline/typescript_extractor/similarity_runner.mjs +19 -0
- arcgraph/pipeline/typescript_extractor/sources.mjs +23 -0
- arcgraph/pipeline/typescript_extractor.mjs +177 -0
- arcgraph/pipeline/typescript_frameworks.py +947 -0
- arcgraph/pipeline/typescript_frontend.py +656 -0
- arcgraph/pipeline/typescript_references.mjs +167 -0
- arcgraph/providers/__init__.py +27 -0
- arcgraph/providers/context_provider.py +1258 -0
- arcgraph/providers/flow_payload.py +117 -0
- arcgraph/providers/memory_connector.py +216 -0
- arcgraph/providers/orchestrator.py +475 -0
- arcgraph/providers/relationship_payload.py +102 -0
- arcgraph/providers/risk_provider.py +843 -0
- arcgraph/providers/why_provider.py +349 -0
- arcgraph/version_info.py +246 -0
- arcgraph-0.1.0.dist-info/METADATA +676 -0
- arcgraph-0.1.0.dist-info/RECORD +165 -0
- arcgraph-0.1.0.dist-info/WHEEL +4 -0
- arcgraph-0.1.0.dist-info/entry_points.txt +2 -0
- arcgraph-0.1.0.dist-info/licenses/LICENSE +203 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""Language frontend contracts and the current Python compatibility frontend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from arcgraph.core.ids import diagnostic_id
|
|
9
|
+
from arcgraph.core.language_tiers import language_tier_capabilities
|
|
10
|
+
from arcgraph.core.passes import (
|
|
11
|
+
AnalysisPassContext,
|
|
12
|
+
AnalysisPassResult,
|
|
13
|
+
PassDispatcher,
|
|
14
|
+
)
|
|
15
|
+
from arcgraph.pipeline.contracts import FrontendGraphFragment
|
|
16
|
+
from arcgraph.pipeline.python_frontend import PythonGraphAnalyzer
|
|
17
|
+
from arcgraph.core.schemas import (
|
|
18
|
+
BuildWarning,
|
|
19
|
+
Edge,
|
|
20
|
+
FileRecord,
|
|
21
|
+
FrontendAnalyzeRequest,
|
|
22
|
+
FrontendAnalyzeResult,
|
|
23
|
+
FrontendCapabilities,
|
|
24
|
+
IndexMetadata,
|
|
25
|
+
Node,
|
|
26
|
+
SemanticDiagnostic,
|
|
27
|
+
)
|
|
28
|
+
from arcgraph.core.semantic import (
|
|
29
|
+
COMPAT_FRONTEND_NAME,
|
|
30
|
+
COMPAT_FRONTEND_VERSION,
|
|
31
|
+
diagnostics_from_warnings,
|
|
32
|
+
semantic_facts_from_graph,
|
|
33
|
+
)
|
|
34
|
+
from arcgraph.pipeline.typescript_frontend import (
|
|
35
|
+
TYPESCRIPT_FILE_EXTENSIONS,
|
|
36
|
+
TYPESCRIPT_FRONTEND_NAME,
|
|
37
|
+
TYPESCRIPT_FRONTEND_VERSION,
|
|
38
|
+
TypeScriptGraphAnalyzer,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LanguageFrontend(ABC):
|
|
43
|
+
name: str
|
|
44
|
+
language_ids: tuple[str, ...]
|
|
45
|
+
version: str
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def file_extensions(self) -> tuple[str, ...]:
|
|
49
|
+
"""File extensions this frontend can analyze (e.g. ('.py',)).
|
|
50
|
+
|
|
51
|
+
Used by FileScanner to dynamically determine which files to scan.
|
|
52
|
+
Default returns all extensions based on language_ids.
|
|
53
|
+
"""
|
|
54
|
+
_LANG_EXTENSIONS: dict[str, tuple[str, ...]] = {
|
|
55
|
+
"python": (".py",),
|
|
56
|
+
"typescript": (".ts", ".tsx"),
|
|
57
|
+
"javascript": (".js", ".jsx"),
|
|
58
|
+
"java": (".java",),
|
|
59
|
+
"go": (".go",),
|
|
60
|
+
}
|
|
61
|
+
extensions: list[str] = []
|
|
62
|
+
for lang in self.language_ids:
|
|
63
|
+
extensions.extend(_LANG_EXTENSIONS.get(lang, ()))
|
|
64
|
+
return tuple(extensions) or (".py",) # Default to Python
|
|
65
|
+
|
|
66
|
+
def accepts(self, file: FileRecord) -> bool:
|
|
67
|
+
"""Return whether this frontend should analyze an indexed file."""
|
|
68
|
+
return Path(file.path).suffix in self.file_extensions
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def capabilities(self) -> FrontendCapabilities:
|
|
72
|
+
"""Return the frontend's declared capabilities."""
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def detect(self, request: FrontendAnalyzeRequest) -> bool:
|
|
76
|
+
"""Return whether this frontend should analyze the request."""
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def analyze(self, request: FrontendAnalyzeRequest) -> FrontendAnalyzeResult:
|
|
80
|
+
"""Analyze the request and return semantic facts."""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def analyze_to_graph(
|
|
84
|
+
self,
|
|
85
|
+
files: list[FileRecord],
|
|
86
|
+
warnings: list[BuildWarning] | None = None,
|
|
87
|
+
module_names: set[str] | None = None,
|
|
88
|
+
call_context_nodes: list[Node] | None = None,
|
|
89
|
+
incremental_similarity_buckets: set[str] | None = None,
|
|
90
|
+
) -> FrontendGraphFragment:
|
|
91
|
+
"""Analyze files and return a raw graph fragment."""
|
|
92
|
+
|
|
93
|
+
def analyze_incremental(
|
|
94
|
+
self, request: FrontendAnalyzeRequest
|
|
95
|
+
) -> FrontendAnalyzeResult:
|
|
96
|
+
return self.analyze(request)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class PythonSemanticFrontend(LanguageFrontend):
|
|
100
|
+
"""Compatibility wrapper for the existing Python V1 graph analyzers."""
|
|
101
|
+
|
|
102
|
+
name = COMPAT_FRONTEND_NAME
|
|
103
|
+
language_ids = ("python",)
|
|
104
|
+
version = COMPAT_FRONTEND_VERSION
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
enable_v2_call_resolution: bool = True,
|
|
110
|
+
adapter_registry: object | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
self._enable_v2 = enable_v2_call_resolution
|
|
113
|
+
self._adapter_registry = adapter_registry
|
|
114
|
+
|
|
115
|
+
def capabilities(self) -> FrontendCapabilities:
|
|
116
|
+
return FrontendCapabilities(
|
|
117
|
+
name=self.name,
|
|
118
|
+
version=self.version,
|
|
119
|
+
language="python",
|
|
120
|
+
capabilities={
|
|
121
|
+
"mode": "v1-compat",
|
|
122
|
+
"fact_kinds": "node,edge,diagnostic",
|
|
123
|
+
"confidence": "canonical",
|
|
124
|
+
"incremental": "file",
|
|
125
|
+
**language_tier_capabilities(
|
|
126
|
+
tier="L3",
|
|
127
|
+
source="native-python",
|
|
128
|
+
scope=(
|
|
129
|
+
"Python semantic graph facts with receiver-aware static "
|
|
130
|
+
"analysis; runtime evidence remains separate."
|
|
131
|
+
),
|
|
132
|
+
languages=("python",),
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
file_extensions=list(self.file_extensions),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def detect(self, request: FrontendAnalyzeRequest) -> bool:
|
|
139
|
+
repo_root = Path(request.repo_root)
|
|
140
|
+
candidate_paths = request.changed_files or request.source_roots or ["."]
|
|
141
|
+
for raw_path in candidate_paths:
|
|
142
|
+
path = repo_root / raw_path
|
|
143
|
+
if path.is_file() and path.suffix == ".py":
|
|
144
|
+
return True
|
|
145
|
+
if path.is_dir():
|
|
146
|
+
try:
|
|
147
|
+
if next(path.rglob("*.py"), None) is not None:
|
|
148
|
+
return True
|
|
149
|
+
except OSError:
|
|
150
|
+
continue
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
def analyze(self, request: FrontendAnalyzeRequest) -> FrontendAnalyzeResult:
|
|
154
|
+
return FrontendAnalyzeResult(
|
|
155
|
+
frontend=self.capabilities(),
|
|
156
|
+
diagnostics=[
|
|
157
|
+
SemanticDiagnostic(
|
|
158
|
+
diagnostic_id=diagnostic_id(
|
|
159
|
+
request.repo_id,
|
|
160
|
+
"frontend_graph_context_note",
|
|
161
|
+
self.name,
|
|
162
|
+
request.index_version or "working-tree",
|
|
163
|
+
),
|
|
164
|
+
repo_id=request.repo_id,
|
|
165
|
+
index_version=request.index_version or "working-tree",
|
|
166
|
+
diagnostic_kind="frontend_graph_context_note",
|
|
167
|
+
message=(
|
|
168
|
+
"PythonSemanticFrontend.analyze() generates semantic "
|
|
169
|
+
"facts via analyze_graph(); use analyze_to_graph() for "
|
|
170
|
+
"raw graph fragments."
|
|
171
|
+
),
|
|
172
|
+
severity="info",
|
|
173
|
+
frontend_name=self.name,
|
|
174
|
+
)
|
|
175
|
+
],
|
|
176
|
+
status="available",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def analyze_to_graph(
|
|
180
|
+
self,
|
|
181
|
+
files: list[FileRecord],
|
|
182
|
+
warnings: list[BuildWarning] | None = None,
|
|
183
|
+
module_names: set[str] | None = None,
|
|
184
|
+
call_context_nodes: list[Node] | None = None,
|
|
185
|
+
incremental_similarity_buckets: set[str] | None = None,
|
|
186
|
+
) -> FrontendGraphFragment:
|
|
187
|
+
"""Run the Python graph analysis pipeline and return a raw fragment.
|
|
188
|
+
|
|
189
|
+
This is the primary entry point for the indexer to get
|
|
190
|
+
Python-specific graph data without going through the semantic
|
|
191
|
+
fact / merge pipeline.
|
|
192
|
+
"""
|
|
193
|
+
analyzer = PythonGraphAnalyzer(
|
|
194
|
+
enable_v2_call_resolution=self._enable_v2,
|
|
195
|
+
adapter_registry=self._adapter_registry,
|
|
196
|
+
)
|
|
197
|
+
return analyzer.analyze(
|
|
198
|
+
files,
|
|
199
|
+
warnings=warnings,
|
|
200
|
+
module_names=module_names,
|
|
201
|
+
call_context_nodes=call_context_nodes,
|
|
202
|
+
incremental_similarity_buckets=incremental_similarity_buckets,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def analyze_graph(
|
|
206
|
+
self,
|
|
207
|
+
metadata: IndexMetadata,
|
|
208
|
+
nodes: list[Node],
|
|
209
|
+
edges: list[Edge],
|
|
210
|
+
warnings: list[BuildWarning],
|
|
211
|
+
*,
|
|
212
|
+
repo_id: str = "default",
|
|
213
|
+
) -> FrontendAnalyzeResult:
|
|
214
|
+
facts = semantic_facts_from_graph(metadata, nodes, edges, repo_id=repo_id)
|
|
215
|
+
diagnostics = diagnostics_from_warnings(metadata, warnings, repo_id=repo_id)
|
|
216
|
+
return FrontendAnalyzeResult(
|
|
217
|
+
frontend=self.capabilities(),
|
|
218
|
+
facts=facts,
|
|
219
|
+
diagnostics=diagnostics,
|
|
220
|
+
metrics={
|
|
221
|
+
"node_facts": sum(1 for fact in facts if fact.fact_kind == "node"),
|
|
222
|
+
"edge_facts": sum(1 for fact in facts if fact.fact_kind == "edge"),
|
|
223
|
+
"diagnostics": len(diagnostics),
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TypeScriptSemanticFrontend(LanguageFrontend):
|
|
229
|
+
"""Static TypeScript/React frontend using the TypeScript compiler API."""
|
|
230
|
+
|
|
231
|
+
name = TYPESCRIPT_FRONTEND_NAME
|
|
232
|
+
language_ids = ("typescript", "javascript")
|
|
233
|
+
version = TYPESCRIPT_FRONTEND_VERSION
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
repo_root: Path | None = None,
|
|
238
|
+
*,
|
|
239
|
+
timeout_seconds: float = 60,
|
|
240
|
+
) -> None:
|
|
241
|
+
self._repo_root = repo_root.resolve() if repo_root is not None else None
|
|
242
|
+
self._timeout_seconds = timeout_seconds
|
|
243
|
+
|
|
244
|
+
def capabilities(self) -> FrontendCapabilities:
|
|
245
|
+
return FrontendCapabilities(
|
|
246
|
+
name=self.name,
|
|
247
|
+
version=self.version,
|
|
248
|
+
language="typescript",
|
|
249
|
+
capabilities={
|
|
250
|
+
"mode": "typescript-compiler-ast",
|
|
251
|
+
"fact_kinds": "node,edge,diagnostic",
|
|
252
|
+
"confidence": "confirmed-inferred-and-heuristic",
|
|
253
|
+
"incremental": "file",
|
|
254
|
+
**language_tier_capabilities(
|
|
255
|
+
tier="L3",
|
|
256
|
+
source="typescript-compiler-api",
|
|
257
|
+
scope=(
|
|
258
|
+
"TypeScript and JavaScript semantic static facts from "
|
|
259
|
+
"the compiler API; typechecker call targets are inferred."
|
|
260
|
+
),
|
|
261
|
+
languages=("typescript", "javascript"),
|
|
262
|
+
),
|
|
263
|
+
},
|
|
264
|
+
file_extensions=list(self.file_extensions),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def file_extensions(self) -> tuple[str, ...]:
|
|
269
|
+
return TYPESCRIPT_FILE_EXTENSIONS
|
|
270
|
+
|
|
271
|
+
def detect(self, request: FrontendAnalyzeRequest) -> bool:
|
|
272
|
+
repo_root = Path(request.repo_root)
|
|
273
|
+
candidate_paths = request.changed_files or request.source_roots or ["."]
|
|
274
|
+
for raw_path in candidate_paths:
|
|
275
|
+
path = repo_root / raw_path
|
|
276
|
+
if path.is_file() and path.suffix in self.file_extensions:
|
|
277
|
+
return True
|
|
278
|
+
if path.is_dir():
|
|
279
|
+
try:
|
|
280
|
+
if any(
|
|
281
|
+
next(path.rglob(f"*{extension}"), None) is not None
|
|
282
|
+
for extension in self.file_extensions
|
|
283
|
+
):
|
|
284
|
+
return True
|
|
285
|
+
except OSError:
|
|
286
|
+
continue
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
def analyze(self, request: FrontendAnalyzeRequest) -> FrontendAnalyzeResult:
|
|
290
|
+
return FrontendAnalyzeResult(
|
|
291
|
+
frontend=self.capabilities(),
|
|
292
|
+
diagnostics=[
|
|
293
|
+
SemanticDiagnostic(
|
|
294
|
+
diagnostic_id=diagnostic_id(
|
|
295
|
+
request.repo_id,
|
|
296
|
+
"frontend_graph_context_note",
|
|
297
|
+
self.name,
|
|
298
|
+
request.index_version or "working-tree",
|
|
299
|
+
),
|
|
300
|
+
repo_id=request.repo_id,
|
|
301
|
+
index_version=request.index_version or "working-tree",
|
|
302
|
+
diagnostic_kind="frontend_graph_context_note",
|
|
303
|
+
message=(
|
|
304
|
+
"TypeScriptSemanticFrontend.analyze() generates graph "
|
|
305
|
+
"fragments via analyze_to_graph()."
|
|
306
|
+
),
|
|
307
|
+
severity="info",
|
|
308
|
+
frontend_name=self.name,
|
|
309
|
+
)
|
|
310
|
+
],
|
|
311
|
+
status="available",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def analyze_to_graph(
|
|
315
|
+
self,
|
|
316
|
+
files: list[FileRecord],
|
|
317
|
+
warnings: list[BuildWarning] | None = None,
|
|
318
|
+
module_names: set[str] | None = None,
|
|
319
|
+
call_context_nodes: list[Node] | None = None,
|
|
320
|
+
incremental_similarity_buckets: set[str] | None = None,
|
|
321
|
+
) -> FrontendGraphFragment:
|
|
322
|
+
repo_root = self._repo_root or self._infer_repo_root(files)
|
|
323
|
+
analyzer = TypeScriptGraphAnalyzer(
|
|
324
|
+
repo_root,
|
|
325
|
+
timeout_seconds=self._timeout_seconds,
|
|
326
|
+
)
|
|
327
|
+
return analyzer.analyze(
|
|
328
|
+
files,
|
|
329
|
+
warnings=warnings,
|
|
330
|
+
module_names=module_names,
|
|
331
|
+
call_context_nodes=call_context_nodes,
|
|
332
|
+
incremental_similarity_buckets=incremental_similarity_buckets,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _infer_repo_root(files: list[FileRecord]) -> Path:
|
|
337
|
+
if not files:
|
|
338
|
+
return Path(".").resolve()
|
|
339
|
+
first_abs = Path(files[0].abs_path).resolve()
|
|
340
|
+
for parent in [first_abs.parent, *first_abs.parents]:
|
|
341
|
+
if (parent / ".git").exists() or (
|
|
342
|
+
parent / "frontend" / "package.json"
|
|
343
|
+
).exists():
|
|
344
|
+
return parent
|
|
345
|
+
return first_abs.parent
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class PythonSemanticGraphPass:
|
|
349
|
+
name = "p1-python-v1-compat"
|
|
350
|
+
|
|
351
|
+
def __init__(self, frontend: PythonSemanticFrontend | None = None) -> None:
|
|
352
|
+
self.frontend = frontend or PythonSemanticFrontend()
|
|
353
|
+
|
|
354
|
+
def run(self, context: AnalysisPassContext) -> AnalysisPassResult:
|
|
355
|
+
return AnalysisPassResult(
|
|
356
|
+
name=self.name,
|
|
357
|
+
result=self.frontend.analyze_graph(
|
|
358
|
+
context.metadata,
|
|
359
|
+
context.nodes,
|
|
360
|
+
context.edges,
|
|
361
|
+
context.warnings,
|
|
362
|
+
repo_id=context.repo_id,
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def python_compat_pass_dispatcher() -> PassDispatcher:
|
|
368
|
+
return PassDispatcher([PythonSemanticGraphPass()])
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class FrontendRegistry:
|
|
372
|
+
"""Minimal registry for language frontend discovery and dispatch."""
|
|
373
|
+
|
|
374
|
+
def __init__(self, frontends: list[LanguageFrontend] | None = None) -> None:
|
|
375
|
+
self._frontends: list[LanguageFrontend] = []
|
|
376
|
+
for frontend in frontends or []:
|
|
377
|
+
self.register(frontend)
|
|
378
|
+
|
|
379
|
+
def register(self, frontend: LanguageFrontend) -> None:
|
|
380
|
+
if any(existing.name == frontend.name for existing in self._frontends):
|
|
381
|
+
raise ValueError(f"Frontend already registered: {frontend.name}")
|
|
382
|
+
self._frontends.append(frontend)
|
|
383
|
+
|
|
384
|
+
def detect_frontends(
|
|
385
|
+
self, request: FrontendAnalyzeRequest
|
|
386
|
+
) -> list[LanguageFrontend]:
|
|
387
|
+
return [frontend for frontend in self._frontends if frontend.detect(request)]
|
|
388
|
+
|
|
389
|
+
def build_plan(self, request: FrontendAnalyzeRequest) -> list[LanguageFrontend]:
|
|
390
|
+
"""Return detected frontends in registration order for build context flow."""
|
|
391
|
+
return self.detect_frontends(request)
|
|
392
|
+
|
|
393
|
+
def analyze(self, request: FrontendAnalyzeRequest) -> list[FrontendAnalyzeResult]:
|
|
394
|
+
return [
|
|
395
|
+
frontend.analyze(request) for frontend in self.detect_frontends(request)
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def frontends(self) -> tuple[LanguageFrontend, ...]:
|
|
400
|
+
return tuple(self._frontends)
|
|
401
|
+
|
|
402
|
+
def inventory(self) -> tuple[dict[str, object], ...]:
|
|
403
|
+
"""Return a stable, JSON-serializable inventory of registered frontends."""
|
|
404
|
+
entries: list[dict[str, object]] = []
|
|
405
|
+
for frontend in self._frontends:
|
|
406
|
+
capabilities = frontend.capabilities()
|
|
407
|
+
entries.append(
|
|
408
|
+
{
|
|
409
|
+
"name": frontend.name,
|
|
410
|
+
"version": frontend.version,
|
|
411
|
+
"language_ids": list(frontend.language_ids),
|
|
412
|
+
"file_extensions": list(frontend.file_extensions),
|
|
413
|
+
"capabilities": capabilities.model_dump(mode="json"),
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
return tuple(entries)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def default_frontend_registry(
|
|
420
|
+
*,
|
|
421
|
+
repo_root: Path | None = None,
|
|
422
|
+
typescript_timeout_seconds: float = 60,
|
|
423
|
+
enable_v2_call_resolution: bool = True,
|
|
424
|
+
adapter_registry: object | None = None,
|
|
425
|
+
) -> FrontendRegistry:
|
|
426
|
+
return FrontendRegistry(
|
|
427
|
+
[
|
|
428
|
+
PythonSemanticFrontend(
|
|
429
|
+
enable_v2_call_resolution=enable_v2_call_resolution,
|
|
430
|
+
adapter_registry=adapter_registry,
|
|
431
|
+
),
|
|
432
|
+
TypeScriptSemanticFrontend(
|
|
433
|
+
repo_root=repo_root,
|
|
434
|
+
timeout_seconds=typescript_timeout_seconds,
|
|
435
|
+
),
|
|
436
|
+
]
|
|
437
|
+
)
|