compass-code 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.
- compass_cli/__init__.py +0 -0
- compass_cli/commands/__init__.py +0 -0
- compass_cli/commands/ingest.py +310 -0
- compass_cli/commands/mcp.py +25 -0
- compass_cli/commands/query.py +181 -0
- compass_cli/commands/serve.py +123 -0
- compass_cli/commands/setup.py +171 -0
- compass_cli/display.py +231 -0
- compass_cli/errors.py +30 -0
- compass_cli/main.py +54 -0
- compass_cli/progress.py +191 -0
- compass_cli/result_views.py +389 -0
- compass_cli/types.py +41 -0
- compass_code-0.1.0.dist-info/METADATA +72 -0
- compass_code-0.1.0.dist-info/RECORD +137 -0
- compass_code-0.1.0.dist-info/WHEEL +4 -0
- compass_code-0.1.0.dist-info/entry_points.txt +2 -0
- compass_core/__init__.py +1 -0
- compass_core/contract/__init__.py +6 -0
- compass_core/contract/errors.py +116 -0
- compass_core/contract/models.py +204 -0
- compass_core/contract/params.py +99 -0
- compass_core/contract/results.py +370 -0
- compass_core/contract/serialize.py +59 -0
- compass_core/contract/types.py +76 -0
- compass_core/errors.py +148 -0
- compass_core/graph/__init__.py +0 -0
- compass_core/graph/db.py +205 -0
- compass_core/graph/schema.py +165 -0
- compass_core/graph/stored_ids.py +133 -0
- compass_core/graph/writer.py +482 -0
- compass_core/index/__init__.py +1 -0
- compass_core/index/freshness.py +55 -0
- compass_core/index/locate.py +155 -0
- compass_core/index/metadata.py +107 -0
- compass_core/ir/__init__.py +0 -0
- compass_core/ir/graph.py +65 -0
- compass_core/ir/models.py +75 -0
- compass_core/languages/__init__.py +1 -0
- compass_core/languages/base.py +26 -0
- compass_core/languages/python/__init__.py +6 -0
- compass_core/languages/python/extractor.py +670 -0
- compass_core/languages/python/import_resolver.py +107 -0
- compass_core/languages/registry.py +89 -0
- compass_core/logger.py +42 -0
- compass_core/phases/__init__.py +0 -0
- compass_core/phases/base.py +58 -0
- compass_core/phases/discovery.py +167 -0
- compass_core/phases/parse.py +80 -0
- compass_core/phases/persistence.py +33 -0
- compass_core/phases/pipeline.py +152 -0
- compass_core/phases/resolution.py +572 -0
- compass_core/phases/symbol_table.py +36 -0
- compass_core/phases/validation.py +76 -0
- compass_core/progress.py +45 -0
- compass_core/py.typed +0 -0
- compass_core/query/__init__.py +1 -0
- compass_core/query/cypher.py +92 -0
- compass_core/query/detail.py +241 -0
- compass_core/query/graph_map.py +127 -0
- compass_core/query/impact.py +273 -0
- compass_core/query/impact_walk.py +352 -0
- compass_core/query/overview.py +221 -0
- compass_core/query/references.py +235 -0
- compass_core/query/rows.py +378 -0
- compass_core/query/search.py +183 -0
- compass_core/query/status.py +49 -0
- compass_core/query/walk_queries.py +202 -0
- compass_core/render.py +408 -0
- compass_core/settings.py +61 -0
- compass_core/shared_types.py +78 -0
- compass_mcp/__init__.py +0 -0
- compass_mcp/guide.py +45 -0
- compass_mcp/py.typed +0 -0
- compass_mcp/server.py +261 -0
- compass_server/__init__.py +5 -0
- compass_server/errors.py +84 -0
- compass_server/main.py +42 -0
- compass_server/py.typed +0 -0
- compass_server/routers/__init__.py +0 -0
- compass_server/routers/query.py +182 -0
- compass_server/settings.py +37 -0
- compass_server/static/404.html +1 -0
- compass_server/static/_next/static/chunks/1629039a.eed2a06658ec4f9c.js +1 -0
- compass_server/static/_next/static/chunks/1ff282bf-bb7c483245264fc9.js +1 -0
- compass_server/static/_next/static/chunks/223.f881a22f446259bf.js +508 -0
- compass_server/static/_next/static/chunks/51fa5c19.6396973222c9d6fd.js +485 -0
- compass_server/static/_next/static/chunks/556.443c1682b6b20128.js +1 -0
- compass_server/static/_next/static/chunks/684-1aa0170f59baf77e.js +1 -0
- compass_server/static/_next/static/chunks/92e71660.3a42d4f70d017951.js +342 -0
- compass_server/static/_next/static/chunks/937-8692224bb6d1b605.js +1 -0
- compass_server/static/_next/static/chunks/app/_not-found/page-2c96ddf295eb6a86.js +1 -0
- compass_server/static/_next/static/chunks/app/layout-4bd0ba4a7d18aff9.js +1 -0
- compass_server/static/_next/static/chunks/app/page-5376a0a98bbd1faa.js +1 -0
- compass_server/static/_next/static/chunks/framework-96dcd958cfe08bd4.js +1 -0
- compass_server/static/_next/static/chunks/main-6dee8e626b16731a.js +1 -0
- compass_server/static/_next/static/chunks/main-app-460e8bc670818718.js +1 -0
- compass_server/static/_next/static/chunks/pages/_app-638a3e7363f92b80.js +1 -0
- compass_server/static/_next/static/chunks/pages/_error-6a72dbbb45cae743.js +1 -0
- compass_server/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- compass_server/static/_next/static/chunks/webpack-597057bf48ce4111.js +1 -0
- compass_server/static/_next/static/css/1b0fbfd8dd653f37.css +3 -0
- compass_server/static/_next/static/ctUZeD9K7jQUgLqmBas2L/_buildManifest.js +1 -0
- compass_server/static/_next/static/ctUZeD9K7jQUgLqmBas2L/_ssgManifest.js +1 -0
- compass_server/static/_next/static/media/0086f8992871c45b-s.woff2 +0 -0
- compass_server/static/_next/static/media/034d78ad42e9620c-s.woff2 +0 -0
- compass_server/static/_next/static/media/03b436aa846269de-s.woff2 +0 -0
- compass_server/static/_next/static/media/0484562807a97172-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/08aedeceaf1dcd57-s.woff2 +0 -0
- compass_server/static/_next/static/media/0aa834ed78bf6d07-s.woff2 +0 -0
- compass_server/static/_next/static/media/29a4aea02fdee119-s.woff2 +0 -0
- compass_server/static/_next/static/media/29e7bbdce9332268-s.woff2 +0 -0
- compass_server/static/_next/static/media/36008747766f78c6-s.woff2 +0 -0
- compass_server/static/_next/static/media/39969fcf98a3026e-s.woff2 +0 -0
- compass_server/static/_next/static/media/4de1fea1a954a5b6-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/5c0c2bcbaa4149ca-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/67957d42bae0796d-s.woff2 +0 -0
- compass_server/static/_next/static/media/6d38f9d4e0f4772b-s.woff2 +0 -0
- compass_server/static/_next/static/media/6d664cce900333ee-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/886030b0b59bc5a7-s.woff2 +0 -0
- compass_server/static/_next/static/media/8888a3826f4a3af4-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/939c4f875ee75fbb-s.woff2 +0 -0
- compass_server/static/_next/static/media/a1386beebedccca4-s.woff2 +0 -0
- compass_server/static/_next/static/media/ab00a911ac2adf48-s.woff2 +0 -0
- compass_server/static/_next/static/media/b957ea75a84b6ea7-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/bb3ef058b751a6ad-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/c3bc380753a8436c-s.woff2 +0 -0
- compass_server/static/_next/static/media/cce080f35d014443-s.woff2 +0 -0
- compass_server/static/_next/static/media/db911767852bc875-s.woff2 +0 -0
- compass_server/static/_next/static/media/eafabf029ad39a43-s.p.woff2 +0 -0
- compass_server/static/_next/static/media/f10b8e9d91f3edcb-s.woff2 +0 -0
- compass_server/static/_next/static/media/f911b923c6adde36-s.woff2 +0 -0
- compass_server/static/_next/static/media/fe0777f1195381cb-s.woff2 +0 -0
- compass_server/static/index.html +1 -0
- compass_server/static/index.txt +25 -0
- compass_server/web_bundle.py +27 -0
- compass_server/web_limits.py +48 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Result types returned by query operations.
|
|
2
|
+
|
|
3
|
+
Every successful result inherits ``ResultHeader`` so adapters read ``index`` and
|
|
4
|
+
``caveats`` flat. ``operation`` plus ``outcome`` form a discriminated union.
|
|
5
|
+
Reference outcomes (ambiguous / not found) are values, not exceptions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Literal, TypeAlias, TypeVar
|
|
13
|
+
|
|
14
|
+
from compass_core.contract.models import Caveat, EdgeRef, IndexStatus, ResultItem, SymbolRef
|
|
15
|
+
from compass_core.contract.types import (
|
|
16
|
+
MatchRule,
|
|
17
|
+
Operation,
|
|
18
|
+
Outcome,
|
|
19
|
+
RepoPath,
|
|
20
|
+
SearchMatch,
|
|
21
|
+
SymbolId,
|
|
22
|
+
)
|
|
23
|
+
from compass_core.shared_types import EdgeType, SymbolKind
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
27
|
+
class ResultHeader:
|
|
28
|
+
"""Fields common to every successful result.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
outcome: Always ``Outcome.OK`` for a successful result.
|
|
32
|
+
index: Status of the index that produced the answer.
|
|
33
|
+
caveats: Reasons the answer may be incomplete.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
Subclasses are built as ``SearchResult(index=status, caveats=(), ...)``.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
outcome: Literal[Outcome.OK] = Outcome.OK
|
|
40
|
+
index: IndexStatus
|
|
41
|
+
caveats: tuple[Caveat, ...]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
45
|
+
class SearchHit:
|
|
46
|
+
"""One search match.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
symbol: The matching symbol.
|
|
50
|
+
match: How it matched the query.
|
|
51
|
+
fan_in: Number of incoming edges, used for ranking.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
``SearchHit(symbol=<f>, match=SearchMatch.EXACT, fan_in=2)``
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
symbol: SymbolRef
|
|
58
|
+
match: SearchMatch
|
|
59
|
+
fan_in: int
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
63
|
+
class SearchResult(ResultHeader):
|
|
64
|
+
"""Result of a symbol search.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
operation: Always ``Operation.FIND``.
|
|
68
|
+
query: The search text.
|
|
69
|
+
kind: Kind filter that was applied, if any.
|
|
70
|
+
hits: Matching symbols, best first.
|
|
71
|
+
total: Number of matches before truncation.
|
|
72
|
+
truncated: Whether ``hits`` was cut at the limit.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
``find "write"`` yields ``SearchResult(query="write", hits=(...), total=3,
|
|
76
|
+
truncated=False, ...)``.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
operation: Literal[Operation.FIND] = Operation.FIND
|
|
80
|
+
query: str
|
|
81
|
+
kind: SymbolKind | None
|
|
82
|
+
hits: tuple[SearchHit, ...]
|
|
83
|
+
total: int
|
|
84
|
+
truncated: bool
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
88
|
+
class ImpactGroup:
|
|
89
|
+
"""Affected symbols at one distance within one file.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
distance: Hops from the target.
|
|
93
|
+
file_path: Repo-relative path of the file holding the items.
|
|
94
|
+
items: Affected symbols in that file at that distance.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
``ImpactGroup(distance=1, file_path=RepoPath("a.py"), items=(<item>,))``
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
distance: int
|
|
101
|
+
file_path: RepoPath
|
|
102
|
+
items: tuple[ResultItem, ...]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
106
|
+
class ImpactResult(ResultHeader):
|
|
107
|
+
"""Result of an impact analysis.
|
|
108
|
+
|
|
109
|
+
Attributes:
|
|
110
|
+
operation: Always ``Operation.IMPACT``.
|
|
111
|
+
target: The analysed file or symbol.
|
|
112
|
+
matched_by: Resolution rule that matched the reference.
|
|
113
|
+
depth: Depth that was traversed.
|
|
114
|
+
groups: Affected symbols grouped by distance and file.
|
|
115
|
+
affected_file_count: Number of distinct affected files.
|
|
116
|
+
edges: Edges connecting the affected symbols.
|
|
117
|
+
total: Number of affected symbols before truncation.
|
|
118
|
+
truncated: Whether the output was cut at the limit.
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
``impact write`` yields ``ImpactResult(target=<write>, depth=3,
|
|
122
|
+
groups=(ImpactGroup(...),), affected_file_count=6, total=14, ...)``.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
operation: Literal[Operation.IMPACT] = Operation.IMPACT
|
|
126
|
+
target: SymbolRef
|
|
127
|
+
matched_by: MatchRule
|
|
128
|
+
depth: int
|
|
129
|
+
groups: tuple[ImpactGroup, ...]
|
|
130
|
+
affected_file_count: int
|
|
131
|
+
edges: tuple[EdgeRef, ...]
|
|
132
|
+
total: int
|
|
133
|
+
truncated: bool
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
137
|
+
class RankedSymbol:
|
|
138
|
+
"""A symbol with a count used for ranking.
|
|
139
|
+
|
|
140
|
+
Attributes:
|
|
141
|
+
symbol: The symbol.
|
|
142
|
+
count: Ranking count (callers, importers, or affected symbols).
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
``RankedSymbol(symbol=<write>, count=14)``
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
symbol: SymbolRef
|
|
149
|
+
count: int
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
153
|
+
class LibraryUsage:
|
|
154
|
+
"""How heavily an external library is called.
|
|
155
|
+
|
|
156
|
+
Attributes:
|
|
157
|
+
library: Library name, e.g. ``"pydantic"``.
|
|
158
|
+
call_count: Number of call sites into the library.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
``LibraryUsage(library="pydantic", call_count=12)``
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
library: str
|
|
165
|
+
call_count: int
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
169
|
+
class IndexCounts:
|
|
170
|
+
"""Node and edge totals of the index.
|
|
171
|
+
|
|
172
|
+
Attributes:
|
|
173
|
+
files: Number of file nodes.
|
|
174
|
+
classes: Number of class nodes.
|
|
175
|
+
functions: Number of function and method nodes.
|
|
176
|
+
edges_by_type: Edge count per edge type.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
``IndexCounts(files=10, classes=2, functions=40,
|
|
180
|
+
edges_by_type={EdgeType.CALLS: 55})``
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
files: int
|
|
184
|
+
classes: int
|
|
185
|
+
functions: int
|
|
186
|
+
edges_by_type: Mapping[EdgeType, int]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
190
|
+
class CallResolution:
|
|
191
|
+
"""How many call sites in the index were tied to a definition.
|
|
192
|
+
|
|
193
|
+
Attributes:
|
|
194
|
+
total: Call sites recorded in function and method bodies.
|
|
195
|
+
resolved: Call sites whose called name matches a function the caller has a
|
|
196
|
+
``CALLS`` or ``CALLS_EXTERN`` edge to.
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
``CallResolution(total=193, resolved=171)`` means 22 calls are unresolved.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
total: int
|
|
203
|
+
resolved: int
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
207
|
+
class OverviewResult(ResultHeader):
|
|
208
|
+
"""Repository overview.
|
|
209
|
+
|
|
210
|
+
Attributes:
|
|
211
|
+
operation: Always ``Operation.OVERVIEW``.
|
|
212
|
+
counts: Node and edge totals.
|
|
213
|
+
call_resolution: Share of call sites the indexer resolved.
|
|
214
|
+
entry_points: Symbols that are entry points.
|
|
215
|
+
most_imported_files: Files ranked by number of importers.
|
|
216
|
+
most_called_functions: Functions ranked by number of callers.
|
|
217
|
+
riskiest_to_change: Symbols ranked by number of affected symbols.
|
|
218
|
+
top_external_libraries: External libraries ranked by call count.
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
``OverviewResult(counts=IndexCounts(...), entry_points=(), ...,
|
|
222
|
+
riskiest_to_change=(RankedSymbol(symbol=<write>, count=14),), ...)``
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
operation: Literal[Operation.OVERVIEW] = Operation.OVERVIEW
|
|
226
|
+
counts: IndexCounts
|
|
227
|
+
call_resolution: CallResolution
|
|
228
|
+
entry_points: tuple[SymbolRef, ...]
|
|
229
|
+
most_imported_files: tuple[RankedSymbol, ...]
|
|
230
|
+
most_called_functions: tuple[RankedSymbol, ...]
|
|
231
|
+
riskiest_to_change: tuple[RankedSymbol, ...]
|
|
232
|
+
top_external_libraries: tuple[LibraryUsage, ...]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
236
|
+
class NodeDetail(ResultHeader):
|
|
237
|
+
"""Everything known about one node, for a details panel.
|
|
238
|
+
|
|
239
|
+
Only the relationships that apply to the node's kind are filled; the rest are empty.
|
|
240
|
+
|
|
241
|
+
Attributes:
|
|
242
|
+
operation: Always ``Operation.DESCRIBE``.
|
|
243
|
+
symbol: The node; ``symbol.source`` holds its source text for classes and functions.
|
|
244
|
+
docstring: Extracted docstring, or ``None`` when there is none.
|
|
245
|
+
bases: Classes this class inherits from, within the repository.
|
|
246
|
+
subclasses: Classes that inherit from this class.
|
|
247
|
+
members: Methods of a class, or the classes and functions a file defines.
|
|
248
|
+
callees: Functions this function calls.
|
|
249
|
+
callers: Functions that call this function.
|
|
250
|
+
imports: Files this file imports.
|
|
251
|
+
imported_by: Files that import this file.
|
|
252
|
+
truncated: Whether any relationship was cut at the per-relationship limit.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
``describe(db, loc, "app/auth.py::Policy")`` yields ``NodeDetail(symbol=<Policy>,
|
|
256
|
+
docstring="Declarative mapping...", bases=(), subclasses=(), members=(<Policy.allows>,),
|
|
257
|
+
...)``.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
operation: Literal[Operation.DESCRIBE] = Operation.DESCRIBE
|
|
261
|
+
symbol: SymbolRef
|
|
262
|
+
docstring: str | None
|
|
263
|
+
bases: tuple[SymbolRef, ...]
|
|
264
|
+
subclasses: tuple[SymbolRef, ...]
|
|
265
|
+
members: tuple[SymbolRef, ...]
|
|
266
|
+
callees: tuple[SymbolRef, ...]
|
|
267
|
+
callers: tuple[SymbolRef, ...]
|
|
268
|
+
imports: tuple[SymbolRef, ...]
|
|
269
|
+
imported_by: tuple[SymbolRef, ...]
|
|
270
|
+
truncated: bool
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
274
|
+
class AmbiguousReference:
|
|
275
|
+
"""A reference that matched several symbols.
|
|
276
|
+
|
|
277
|
+
Attributes:
|
|
278
|
+
outcome: Always ``Outcome.AMBIGUOUS``.
|
|
279
|
+
reference: What the user typed.
|
|
280
|
+
candidates: At least two matches, ordered by rule then fan-in.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
``AmbiguousReference(reference="run", candidates=(<a.run>, <b.run>))``
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
outcome: Literal[Outcome.AMBIGUOUS] = Outcome.AMBIGUOUS
|
|
287
|
+
reference: str
|
|
288
|
+
candidates: tuple[SymbolRef, ...]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
292
|
+
class SymbolNotFound:
|
|
293
|
+
"""A reference that matched nothing.
|
|
294
|
+
|
|
295
|
+
Attributes:
|
|
296
|
+
outcome: Always ``Outcome.NOT_FOUND``.
|
|
297
|
+
reference: What the user typed.
|
|
298
|
+
suggestions: Up to five case-insensitive substring matches.
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
``SymbolNotFound(reference="wrte", suggestions=(<write>,))``
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
outcome: Literal[Outcome.NOT_FOUND] = Outcome.NOT_FOUND
|
|
305
|
+
reference: str
|
|
306
|
+
suggestions: tuple[SymbolRef, ...]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
310
|
+
class MapNode:
|
|
311
|
+
"""One file, class, function or method in the map.
|
|
312
|
+
|
|
313
|
+
Attributes:
|
|
314
|
+
id: What ``MapEdge.source`` and ``MapEdge.target`` refer to: the public id of a
|
|
315
|
+
class or function, the path of a file.
|
|
316
|
+
label: Short display name.
|
|
317
|
+
kind: Kind of node; never ``EXTERNAL``.
|
|
318
|
+
file_path: Repo-relative path.
|
|
319
|
+
public_id: Public id, accepted wherever a query takes a reference.
|
|
320
|
+
|
|
321
|
+
Example:
|
|
322
|
+
``def f(): ...`` in ``a.py`` becomes ``MapNode(id="a.py::f", label="f",
|
|
323
|
+
kind=SymbolKind.FUNCTION, file_path="a.py", public_id=SymbolId("a.py::f"))``.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
id: str
|
|
327
|
+
label: str
|
|
328
|
+
kind: SymbolKind
|
|
329
|
+
file_path: str
|
|
330
|
+
public_id: SymbolId
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
334
|
+
class MapEdge:
|
|
335
|
+
"""One relationship between two ``MapNode`` values.
|
|
336
|
+
|
|
337
|
+
Attributes:
|
|
338
|
+
source: ``MapNode.id`` the edge leaves.
|
|
339
|
+
target: ``MapNode.id`` the edge enters.
|
|
340
|
+
type: Kind of edge; never ``CALLS_EXTERN``, whose targets are not in the map.
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
``a.py`` importing ``b.py`` becomes ``MapEdge(source="a.py", target="b.py",
|
|
344
|
+
type=EdgeType.IMPORTS)``.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
source: str
|
|
348
|
+
target: str
|
|
349
|
+
type: EdgeType
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
353
|
+
class GraphMap:
|
|
354
|
+
"""Every indexed node and the deduplicated edges between them.
|
|
355
|
+
|
|
356
|
+
Attributes:
|
|
357
|
+
nodes: Files, then classes, then functions and methods.
|
|
358
|
+
edges: Distinct ``(source, target, type)`` edges, grouped by type.
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
A repo with one file holding one function has two nodes and one ``CONTAINS``
|
|
362
|
+
edge.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
nodes: tuple[MapNode, ...]
|
|
366
|
+
edges: tuple[MapEdge, ...]
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
R = TypeVar("R")
|
|
370
|
+
Lookup: TypeAlias = R | AmbiguousReference | SymbolNotFound
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""JSON serialization of query results via cached Pydantic ``TypeAdapter``s.
|
|
2
|
+
|
|
3
|
+
Models stay plain dataclasses; Pydantic is used only here, at the adapter edge.
|
|
4
|
+
Enums serialize as their value, ``datetime`` as ISO-8601, ``Path`` as a string and
|
|
5
|
+
tuples / frozensets as arrays.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pydantic import TypeAdapter
|
|
11
|
+
|
|
12
|
+
from compass_core.contract.errors import ErrorResponse
|
|
13
|
+
from compass_core.contract.models import IndexStatus
|
|
14
|
+
from compass_core.contract.results import (
|
|
15
|
+
AmbiguousReference,
|
|
16
|
+
ImpactResult,
|
|
17
|
+
NodeDetail,
|
|
18
|
+
OverviewResult,
|
|
19
|
+
SearchResult,
|
|
20
|
+
SymbolNotFound,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
Serializable = (
|
|
24
|
+
SearchResult
|
|
25
|
+
| ImpactResult
|
|
26
|
+
| OverviewResult
|
|
27
|
+
| NodeDetail
|
|
28
|
+
| AmbiguousReference
|
|
29
|
+
| SymbolNotFound
|
|
30
|
+
| IndexStatus
|
|
31
|
+
| ErrorResponse
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_ADAPTERS: dict[type[object], TypeAdapter[object]] = {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _adapter_for(obj: Serializable) -> TypeAdapter[object]:
|
|
39
|
+
tp: type[object] = type(obj)
|
|
40
|
+
adapter = _ADAPTERS.get(tp)
|
|
41
|
+
if adapter is None:
|
|
42
|
+
adapter = _ADAPTERS[tp] = TypeAdapter(tp)
|
|
43
|
+
return adapter
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def to_json(obj: Serializable) -> str:
|
|
47
|
+
"""Serialize a query result, reference outcome or error envelope to JSON.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
obj: The value to serialize.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A compact JSON string.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
``to_json(SymbolNotFound(reference="x", suggestions=()))`` returns
|
|
57
|
+
``'{"outcome":"not_found","reference":"x","suggestions":[]}'``.
|
|
58
|
+
"""
|
|
59
|
+
return _adapter_for(obj).dump_json(obj).decode()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Closed vocabularies and identifier types of the query contract.
|
|
2
|
+
|
|
3
|
+
Enums are ``StrEnum`` so they serialize as their string value. Identifiers are
|
|
4
|
+
``NewType`` over ``str`` so a file path cannot be passed where a symbol id is expected.
|
|
5
|
+
Vocabulary shared with parsing and persistence lives in ``compass_core/shared_types.py``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import NewType
|
|
12
|
+
|
|
13
|
+
SymbolId = NewType("SymbolId", str)
|
|
14
|
+
"""Public node id, e.g. ``"packages/core/src/compass_core/graph/writer.py::write"``."""
|
|
15
|
+
|
|
16
|
+
RepoPath = NewType("RepoPath", str)
|
|
17
|
+
"""Repo-relative POSIX path, e.g. ``"packages/core/src/compass_core/graph/writer.py"``."""
|
|
18
|
+
|
|
19
|
+
QualifiedName = NewType("QualifiedName", str)
|
|
20
|
+
"""Dotted qualified name, e.g. ``"compass_core.graph.writer.write"``."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ViaKind(StrEnum):
|
|
24
|
+
"""Relationship through which an impact item was reached from the item before it.
|
|
25
|
+
|
|
26
|
+
Only values the query layer produces are listed; add one with the operation that
|
|
27
|
+
produces it.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
CALLS = "CALLS"
|
|
31
|
+
IMPORTS = "IMPORTS"
|
|
32
|
+
INHERITS = "INHERITS"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MatchRule(StrEnum):
|
|
36
|
+
"""Symbol resolution rules, in the order they are tried."""
|
|
37
|
+
|
|
38
|
+
EXACT_ID = "EXACT_ID"
|
|
39
|
+
FILE_PATH = "FILE_PATH"
|
|
40
|
+
QUALIFIED_NAME = "QUALIFIED_NAME"
|
|
41
|
+
PATH_AND_NAME = "PATH_AND_NAME"
|
|
42
|
+
CLASS_MEMBER = "CLASS_MEMBER"
|
|
43
|
+
BARE_NAME = "BARE_NAME"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SearchMatch(StrEnum):
|
|
47
|
+
"""How a search hit matched the query."""
|
|
48
|
+
|
|
49
|
+
EXACT = "EXACT"
|
|
50
|
+
QUALIFIED_SUFFIX = "QUALIFIED_SUFFIX"
|
|
51
|
+
PREFIX = "PREFIX"
|
|
52
|
+
SUBSTRING = "SUBSTRING"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Operation(StrEnum):
|
|
56
|
+
"""Query operations that return a ``ResultHeader`` result (the JSON discriminator)."""
|
|
57
|
+
|
|
58
|
+
FIND = "FIND"
|
|
59
|
+
DESCRIBE = "DESCRIBE"
|
|
60
|
+
IMPACT = "IMPACT"
|
|
61
|
+
OVERVIEW = "OVERVIEW"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Outcome(StrEnum):
|
|
65
|
+
"""JSON discriminator for a query result."""
|
|
66
|
+
|
|
67
|
+
OK = "ok"
|
|
68
|
+
AMBIGUOUS = "ambiguous"
|
|
69
|
+
NOT_FOUND = "not_found"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CaveatCode(StrEnum):
|
|
73
|
+
"""Reasons an answer may be incomplete; only codes the query layer emits."""
|
|
74
|
+
|
|
75
|
+
STALE_INDEX = "STALE_INDEX"
|
|
76
|
+
UNRESOLVED_CALLS = "UNRESOLVED_CALLS"
|
compass_core/errors.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Exception hierarchy for compass_core.
|
|
2
|
+
|
|
3
|
+
Reserved for genuinely unexpected/unrecoverable failures (DB connection
|
|
4
|
+
lost, malformed grammar binary, config missing) that propagate up to
|
|
5
|
+
phases/pipeline.py and are caught once at the top level. Expected failures
|
|
6
|
+
(an unresolved call site, a degraded parse) are modeled as typed result
|
|
7
|
+
objects elsewhere, never raised as one of these.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import ClassVar
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CompassError(Exception):
|
|
18
|
+
"""Base exception for compass_core. Carries enough context to debug without re-running."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
message: str,
|
|
23
|
+
*,
|
|
24
|
+
file_path: str | None = None,
|
|
25
|
+
language: str | None = None,
|
|
26
|
+
phase_name: str | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.file_path = file_path
|
|
30
|
+
self.language = language
|
|
31
|
+
self.phase_name = phase_name
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DiscoveryError(CompassError):
|
|
35
|
+
"""Phase 0 failure — repo walk or manifest construction failed unrecoverably."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ParseError(CompassError):
|
|
39
|
+
"""Phase 1 failure. Only raised for unrecoverable extractor setup failures
|
|
40
|
+
(e.g. grammar binary missing) — per-file parse failures are captured as a
|
|
41
|
+
`degraded` flag on ParsedFile, not raised."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UnsupportedLanguageError(ParseError):
|
|
45
|
+
"""Raised by languages/registry.py when no extractor is registered for a file's language."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ResolutionError(CompassError):
|
|
49
|
+
"""Phase 3 failure — unrecoverable failure building the symbol table or resolving edges."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ValidationError(CompassError):
|
|
53
|
+
"""Phase 4 failure — the ingest run failed a regression gate check."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PersistenceError(CompassError):
|
|
57
|
+
"""Phase 5 failure — DB connection lost or write failed unrecoverably mid-batch."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ErrorCode(StrEnum):
|
|
61
|
+
"""Machine-readable failure codes shared by the API, CLI and MCP adapters."""
|
|
62
|
+
|
|
63
|
+
INVALID_ARGUMENT = "INVALID_ARGUMENT"
|
|
64
|
+
NOT_FOUND = "NOT_FOUND"
|
|
65
|
+
AMBIGUOUS = "AMBIGUOUS"
|
|
66
|
+
INDEX_NOT_FOUND = "INDEX_NOT_FOUND"
|
|
67
|
+
INDEX_BUSY = "INDEX_BUSY"
|
|
68
|
+
INDEX_INCOMPATIBLE = "INDEX_INCOMPATIBLE"
|
|
69
|
+
QUERY_TIMEOUT = "QUERY_TIMEOUT"
|
|
70
|
+
INTERNAL = "INTERNAL"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class QueryError(CompassError):
|
|
74
|
+
"""Base class for failures raised by the query layer.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
code: Machine-readable failure code; every direct subclass declares a
|
|
78
|
+
distinct value.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
code: ClassVar[ErrorCode] = ErrorCode.INTERNAL
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class InvalidArgumentError(QueryError):
|
|
85
|
+
"""A caller-supplied argument was missing, malformed or out of range.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
argument: Name of the offending argument.
|
|
89
|
+
reason: Human-readable explanation of why it was rejected.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> InvalidArgumentError(argument="depth", reason="must be between 1 and 10")
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
code: ClassVar[ErrorCode] = ErrorCode.INVALID_ARGUMENT
|
|
96
|
+
|
|
97
|
+
def __init__(self, *, argument: str, reason: str) -> None:
|
|
98
|
+
super().__init__(f"Invalid {argument}: {reason}")
|
|
99
|
+
self.argument = argument
|
|
100
|
+
self.reason = reason
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class IndexNotFoundError(QueryError):
|
|
104
|
+
"""No ``.compass/graph.ladybug`` was found walking up from ``searched_from``.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
searched_from: Directory the upward search started from.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
code: ClassVar[ErrorCode] = ErrorCode.INDEX_NOT_FOUND
|
|
111
|
+
|
|
112
|
+
def __init__(self, *, searched_from: Path) -> None:
|
|
113
|
+
super().__init__(f"No Compass index found from {searched_from}. Run `compass ingest`.")
|
|
114
|
+
self.searched_from = searched_from
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class IndexBusyError(QueryError):
|
|
118
|
+
"""The index is locked by another process (typically an ingest rebuild).
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
retry_after_s: Suggested number of seconds to wait before retrying.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
code: ClassVar[ErrorCode] = ErrorCode.INDEX_BUSY
|
|
125
|
+
|
|
126
|
+
def __init__(self, *, retry_after_s: int) -> None:
|
|
127
|
+
super().__init__(f"The index is being rebuilt; retry in {retry_after_s}s.")
|
|
128
|
+
self.retry_after_s = retry_after_s
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class IndexIncompatibleError(QueryError):
|
|
132
|
+
"""The index or its metadata is unreadable or from an unsupported version."""
|
|
133
|
+
|
|
134
|
+
code: ClassVar[ErrorCode] = ErrorCode.INDEX_INCOMPATIBLE
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class QueryTimeoutError(QueryError):
|
|
138
|
+
"""A query ran longer than the configured timeout and was interrupted.
|
|
139
|
+
|
|
140
|
+
Attributes:
|
|
141
|
+
timeout_ms: The timeout, in milliseconds, that was exceeded.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
code: ClassVar[ErrorCode] = ErrorCode.QUERY_TIMEOUT
|
|
145
|
+
|
|
146
|
+
def __init__(self, *, timeout_ms: int) -> None:
|
|
147
|
+
super().__init__(f"Query exceeded {timeout_ms} ms.")
|
|
148
|
+
self.timeout_ms = timeout_ms
|
|
File without changes
|