descry-codegraph 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.
- descry/__init__.py +4 -0
- descry/_env.py +30 -0
- descry/_graph.py +36 -0
- descry/ast_grep.py +382 -0
- descry/cli.py +457 -0
- descry/cross_lang.py +404 -0
- descry/embeddings.py +593 -0
- descry/generate.py +3579 -0
- descry/git_history.py +1631 -0
- descry/handlers.py +1907 -0
- descry/mcp_server.py +355 -0
- descry/query.py +2345 -0
- descry/scip/__init__.py +0 -0
- descry/scip/cache.py +1047 -0
- descry/scip/parser.py +531 -0
- descry/scip/pb2.py +92 -0
- descry/scip/support.py +174 -0
- descry/tree_sitter_parser.py +356 -0
- descry/web/__init__.py +0 -0
- descry/web/server.py +1457 -0
- descry/web/web/index.html +2444 -0
- descry/web/web/style.css +1791 -0
- descry_codegraph-0.1.0.dist-info/METADATA +345 -0
- descry_codegraph-0.1.0.dist-info/RECORD +27 -0
- descry_codegraph-0.1.0.dist-info/WHEEL +4 -0
- descry_codegraph-0.1.0.dist-info/entry_points.txt +5 -0
- descry_codegraph-0.1.0.dist-info/licenses/LICENSE +21 -0
descry/cli.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""Descry CLI — standalone command-line interface for codebase knowledge graph tools.
|
|
2
|
+
|
|
3
|
+
Delegates all business logic to DescryService.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
descry health
|
|
7
|
+
descry search foo bar --limit 5
|
|
8
|
+
descry context FILE:src/lib.rs::main --full
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import asyncio
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from descry.handlers import DescryService, DescryConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _make_service() -> DescryService:
|
|
19
|
+
config = DescryConfig.from_env()
|
|
20
|
+
return DescryService(config)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _run(coro) -> str:
|
|
24
|
+
return asyncio.run(coro)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _print_result(coro):
|
|
28
|
+
"""Run a service coroutine, print the result, and translate service-side
|
|
29
|
+
errors to a nonzero exit code so shell chains like `descry callers X &&
|
|
30
|
+
next_step` don't silently advance on "ERROR: Graph not found".
|
|
31
|
+
"""
|
|
32
|
+
result = _run(coro)
|
|
33
|
+
print(result)
|
|
34
|
+
if isinstance(result, str) and result.lstrip().upper().startswith("ERROR:"):
|
|
35
|
+
sys.exit(2)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_health(args):
|
|
39
|
+
svc = _make_service()
|
|
40
|
+
_print_result(svc.health())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cmd_status(args):
|
|
44
|
+
svc = _make_service()
|
|
45
|
+
_print_result(svc.status())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cmd_ensure(args):
|
|
49
|
+
svc = _make_service()
|
|
50
|
+
_print_result(svc.ensure(max_age_hours=args.max_age_hours))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cmd_index(args):
|
|
54
|
+
svc = _make_service()
|
|
55
|
+
path = args.path if args.path else "."
|
|
56
|
+
_print_result(svc.index(path))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cmd_callers(args):
|
|
60
|
+
svc = _make_service()
|
|
61
|
+
_print_result(svc.callers(name=args.name, limit=args.limit))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_callees(args):
|
|
65
|
+
svc = _make_service()
|
|
66
|
+
_print_result(svc.callees(name=args.name, limit=args.limit))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cmd_context(args):
|
|
70
|
+
svc = _make_service()
|
|
71
|
+
_print_result(
|
|
72
|
+
svc.context(
|
|
73
|
+
node_id=args.node_id,
|
|
74
|
+
brief=args.brief,
|
|
75
|
+
full=args.full,
|
|
76
|
+
expand_callees=args.expand_callees,
|
|
77
|
+
deduplicate=args.deduplicate,
|
|
78
|
+
depth=args.depth,
|
|
79
|
+
max_tokens=args.max_tokens,
|
|
80
|
+
callee_budget=args.callee_budget,
|
|
81
|
+
head_lines=args.head_lines,
|
|
82
|
+
max_output_tokens=args.max_output_tokens,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def cmd_flow(args):
|
|
88
|
+
svc = _make_service()
|
|
89
|
+
_print_result(
|
|
90
|
+
svc.flow(
|
|
91
|
+
start=args.start,
|
|
92
|
+
direction=args.direction,
|
|
93
|
+
depth=args.depth,
|
|
94
|
+
target=args.target,
|
|
95
|
+
inline_threshold=args.inline_threshold,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_search(args):
|
|
101
|
+
svc = _make_service()
|
|
102
|
+
_print_result(
|
|
103
|
+
svc.search(
|
|
104
|
+
terms=args.terms,
|
|
105
|
+
compact=args.compact,
|
|
106
|
+
limit=args.limit,
|
|
107
|
+
lang=args.lang,
|
|
108
|
+
crate=args.crate,
|
|
109
|
+
symbol_type=args.type,
|
|
110
|
+
exclude_tests=args.exclude_tests,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_structure(args):
|
|
116
|
+
svc = _make_service()
|
|
117
|
+
_print_result(svc.structure(filename=args.filename))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cmd_flatten(args):
|
|
121
|
+
svc = _make_service()
|
|
122
|
+
_print_result(svc.flatten(class_node_id=args.class_node_id))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def cmd_semantic(args):
|
|
126
|
+
svc = _make_service()
|
|
127
|
+
_print_result(svc.semantic(query=args.query, limit=args.limit))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cmd_quick(args):
|
|
131
|
+
svc = _make_service()
|
|
132
|
+
_print_result(svc.quick(name=args.name, full=args.full, brief=args.brief))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def cmd_impls(args):
|
|
136
|
+
svc = _make_service()
|
|
137
|
+
_print_result(svc.impls(method=args.method, trait_name=args.trait_name))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def cmd_path(args):
|
|
141
|
+
svc = _make_service()
|
|
142
|
+
_print_result(
|
|
143
|
+
svc.path(
|
|
144
|
+
start=args.start,
|
|
145
|
+
end=args.end,
|
|
146
|
+
max_depth=args.max_depth,
|
|
147
|
+
direction=args.direction,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def cmd_cross_lang(args):
|
|
153
|
+
svc = _make_service()
|
|
154
|
+
_print_result(
|
|
155
|
+
svc.cross_lang(
|
|
156
|
+
mode=args.mode,
|
|
157
|
+
method=args.method,
|
|
158
|
+
path=args.path,
|
|
159
|
+
tag=args.tag,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cmd_churn(args):
|
|
165
|
+
svc = _make_service()
|
|
166
|
+
_print_result(
|
|
167
|
+
svc.churn(
|
|
168
|
+
time_range=args.time_range,
|
|
169
|
+
path_filter=args.path_filter,
|
|
170
|
+
limit=args.limit,
|
|
171
|
+
mode=args.mode,
|
|
172
|
+
exclude_generated=args.exclude_generated,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def cmd_evolution(args):
|
|
178
|
+
svc = _make_service()
|
|
179
|
+
_print_result(
|
|
180
|
+
svc.evolution(
|
|
181
|
+
name=args.name,
|
|
182
|
+
time_range=args.time_range,
|
|
183
|
+
limit=args.limit,
|
|
184
|
+
show_diff=args.show_diff,
|
|
185
|
+
crate=args.crate,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def cmd_changes(args):
|
|
191
|
+
svc = _make_service()
|
|
192
|
+
_print_result(
|
|
193
|
+
svc.changes(
|
|
194
|
+
commit_range=args.commit_range,
|
|
195
|
+
time_range=args.time_range,
|
|
196
|
+
path_filter=args.path_filter,
|
|
197
|
+
show_callers=args.show_callers,
|
|
198
|
+
limit=args.limit,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main():
|
|
204
|
+
parser = argparse.ArgumentParser(
|
|
205
|
+
prog="descry",
|
|
206
|
+
description="Codebase knowledge graph tools",
|
|
207
|
+
)
|
|
208
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
209
|
+
|
|
210
|
+
# health
|
|
211
|
+
sub = subparsers.add_parser("health", help="Quick diagnostic check")
|
|
212
|
+
sub.set_defaults(func=cmd_health)
|
|
213
|
+
|
|
214
|
+
# status
|
|
215
|
+
sub = subparsers.add_parser("status", help="Check graph existence and freshness")
|
|
216
|
+
sub.set_defaults(func=cmd_status)
|
|
217
|
+
|
|
218
|
+
# ensure
|
|
219
|
+
sub = subparsers.add_parser("ensure", help="Ensure graph exists and is fresh")
|
|
220
|
+
sub.add_argument(
|
|
221
|
+
"--max-age-hours",
|
|
222
|
+
type=float,
|
|
223
|
+
default=24,
|
|
224
|
+
help="Max graph age in hours (default: 24)",
|
|
225
|
+
)
|
|
226
|
+
sub.set_defaults(func=cmd_ensure)
|
|
227
|
+
|
|
228
|
+
# index
|
|
229
|
+
sub = subparsers.add_parser("index", help="Regenerate the codebase graph")
|
|
230
|
+
sub.add_argument(
|
|
231
|
+
"path", nargs="?", default=None, help="Path to index (default: project root)"
|
|
232
|
+
)
|
|
233
|
+
sub.set_defaults(func=cmd_index)
|
|
234
|
+
|
|
235
|
+
# callers
|
|
236
|
+
sub = subparsers.add_parser("callers", help="Find all callers of a symbol")
|
|
237
|
+
sub.add_argument("name", help="Symbol name to look up")
|
|
238
|
+
sub.add_argument("--limit", type=int, default=20, help="Max results (default: 20)")
|
|
239
|
+
sub.set_defaults(func=cmd_callers)
|
|
240
|
+
|
|
241
|
+
# callees
|
|
242
|
+
sub = subparsers.add_parser("callees", help="Find what a symbol calls")
|
|
243
|
+
sub.add_argument("name", help="Symbol name to look up")
|
|
244
|
+
sub.add_argument("--limit", type=int, default=20, help="Max results (default: 20)")
|
|
245
|
+
sub.set_defaults(func=cmd_callees)
|
|
246
|
+
|
|
247
|
+
# context
|
|
248
|
+
sub = subparsers.add_parser(
|
|
249
|
+
"context", help="Get full context for a symbol by node ID"
|
|
250
|
+
)
|
|
251
|
+
sub.add_argument("node_id", help="Graph node ID (e.g. FILE:src/lib.rs::main)")
|
|
252
|
+
sub.add_argument("--brief", action="store_true", help="Brief output")
|
|
253
|
+
sub.add_argument("--full", action="store_true", help="Full source output")
|
|
254
|
+
sub.add_argument(
|
|
255
|
+
"--expand-callees", action="store_true", help="Include callee source"
|
|
256
|
+
)
|
|
257
|
+
sub.add_argument(
|
|
258
|
+
"--deduplicate", action="store_true", help="Deduplicate repeated lookups"
|
|
259
|
+
)
|
|
260
|
+
sub.add_argument(
|
|
261
|
+
"--depth", type=int, default=1, help="Traversal depth (default: 1)"
|
|
262
|
+
)
|
|
263
|
+
sub.add_argument(
|
|
264
|
+
"--max-tokens", type=int, default=2000, help="Max token budget (default: 2000)"
|
|
265
|
+
)
|
|
266
|
+
sub.add_argument(
|
|
267
|
+
"--callee-budget",
|
|
268
|
+
type=int,
|
|
269
|
+
default=2000,
|
|
270
|
+
help="Token budget for expanded callees (default: 2000)",
|
|
271
|
+
)
|
|
272
|
+
sub.add_argument(
|
|
273
|
+
"--head-lines",
|
|
274
|
+
type=int,
|
|
275
|
+
default=None,
|
|
276
|
+
help="Truncate source head to N lines (default: full)",
|
|
277
|
+
)
|
|
278
|
+
sub.add_argument(
|
|
279
|
+
"--max-output-tokens",
|
|
280
|
+
type=int,
|
|
281
|
+
default=None,
|
|
282
|
+
help="Overall output token cap (default: unlimited)",
|
|
283
|
+
)
|
|
284
|
+
sub.set_defaults(func=cmd_context)
|
|
285
|
+
|
|
286
|
+
# flow
|
|
287
|
+
sub = subparsers.add_parser("flow", help="Trace call flow from a starting symbol")
|
|
288
|
+
sub.add_argument("start", help="Starting symbol name")
|
|
289
|
+
sub.add_argument(
|
|
290
|
+
"--direction",
|
|
291
|
+
default="forward",
|
|
292
|
+
choices=["forward", "backward"],
|
|
293
|
+
help="Trace direction (default: forward)",
|
|
294
|
+
)
|
|
295
|
+
sub.add_argument("--depth", type=int, default=3, help="Trace depth (default: 3)")
|
|
296
|
+
sub.add_argument("--target", default=None, help="Target symbol to reach")
|
|
297
|
+
sub.add_argument(
|
|
298
|
+
"--inline-threshold",
|
|
299
|
+
type=int,
|
|
300
|
+
default=100,
|
|
301
|
+
help="Inline callee source below this token count (default: 100)",
|
|
302
|
+
)
|
|
303
|
+
sub.set_defaults(func=cmd_flow)
|
|
304
|
+
|
|
305
|
+
# search
|
|
306
|
+
sub = subparsers.add_parser("search", help="Search symbol names and docstrings")
|
|
307
|
+
sub.add_argument("terms", nargs="+", help="Search terms")
|
|
308
|
+
sub.add_argument(
|
|
309
|
+
"--compact", action="store_true", default=True, help="Compact output (default)"
|
|
310
|
+
)
|
|
311
|
+
sub.add_argument(
|
|
312
|
+
"--no-compact", dest="compact", action="store_false", help="Detailed output"
|
|
313
|
+
)
|
|
314
|
+
sub.add_argument("--limit", type=int, default=10, help="Max results (default: 10)")
|
|
315
|
+
sub.add_argument("--lang", default=None, help="Filter by language")
|
|
316
|
+
sub.add_argument("--crate", default=None, help="Filter by crate")
|
|
317
|
+
sub.add_argument("--type", default=None, help="Filter by symbol type")
|
|
318
|
+
sub.add_argument(
|
|
319
|
+
"--exclude-tests", action="store_true", help="Exclude test symbols"
|
|
320
|
+
)
|
|
321
|
+
sub.set_defaults(func=cmd_search)
|
|
322
|
+
|
|
323
|
+
# structure
|
|
324
|
+
sub = subparsers.add_parser("structure", help="Show file structure")
|
|
325
|
+
sub.add_argument("filename", help="Filename to inspect")
|
|
326
|
+
sub.set_defaults(func=cmd_structure)
|
|
327
|
+
|
|
328
|
+
# flatten
|
|
329
|
+
sub = subparsers.add_parser(
|
|
330
|
+
"flatten", help="Show effective API of a class including inherited methods"
|
|
331
|
+
)
|
|
332
|
+
sub.add_argument("class_node_id", help="Class node ID")
|
|
333
|
+
sub.set_defaults(func=cmd_flatten)
|
|
334
|
+
|
|
335
|
+
# semantic
|
|
336
|
+
sub = subparsers.add_parser(
|
|
337
|
+
"semantic", help="Pure semantic search using embeddings"
|
|
338
|
+
)
|
|
339
|
+
sub.add_argument("query", help="Natural language query")
|
|
340
|
+
sub.add_argument("--limit", type=int, default=10, help="Max results (default: 10)")
|
|
341
|
+
sub.set_defaults(func=cmd_semantic)
|
|
342
|
+
|
|
343
|
+
# quick
|
|
344
|
+
sub = subparsers.add_parser(
|
|
345
|
+
"quick", help="Find symbol and show full context in one step"
|
|
346
|
+
)
|
|
347
|
+
sub.add_argument("name", help="Symbol name")
|
|
348
|
+
sub.add_argument("--full", action="store_true", help="Full source output")
|
|
349
|
+
sub.add_argument("--brief", action="store_true", help="Brief output")
|
|
350
|
+
sub.set_defaults(func=cmd_quick)
|
|
351
|
+
|
|
352
|
+
# impls
|
|
353
|
+
sub = subparsers.add_parser(
|
|
354
|
+
"impls", help="Find all implementations of a trait method"
|
|
355
|
+
)
|
|
356
|
+
sub.add_argument("method", help="Method name")
|
|
357
|
+
sub.add_argument("--trait-name", default=None, help="Filter by trait name")
|
|
358
|
+
sub.set_defaults(func=cmd_impls)
|
|
359
|
+
|
|
360
|
+
# path
|
|
361
|
+
sub = subparsers.add_parser(
|
|
362
|
+
"path", help="Find shortest call path between two symbols"
|
|
363
|
+
)
|
|
364
|
+
sub.add_argument("start", help="Starting symbol")
|
|
365
|
+
sub.add_argument("end", help="Ending symbol")
|
|
366
|
+
sub.add_argument(
|
|
367
|
+
"--max-depth", type=int, default=10, help="Max search depth (default: 10)"
|
|
368
|
+
)
|
|
369
|
+
sub.add_argument(
|
|
370
|
+
"--direction",
|
|
371
|
+
default="forward",
|
|
372
|
+
choices=["forward", "backward"],
|
|
373
|
+
help="Search direction (default: forward)",
|
|
374
|
+
)
|
|
375
|
+
sub.set_defaults(func=cmd_path)
|
|
376
|
+
|
|
377
|
+
# cross-lang
|
|
378
|
+
sub = subparsers.add_parser(
|
|
379
|
+
"cross-lang", help="Trace API calls from frontend to backend via OpenAPI"
|
|
380
|
+
)
|
|
381
|
+
sub.add_argument(
|
|
382
|
+
"--mode",
|
|
383
|
+
default="endpoint",
|
|
384
|
+
choices=["endpoint", "list", "stats"],
|
|
385
|
+
help="Trace mode (default: endpoint)",
|
|
386
|
+
)
|
|
387
|
+
sub.add_argument("--method", default=None, help="HTTP method (e.g. GET, POST)")
|
|
388
|
+
sub.add_argument("--path", default=None, help="API path (e.g. /api/v1/deployments)")
|
|
389
|
+
sub.add_argument("--tag", default=None, help="Filter by tag")
|
|
390
|
+
sub.set_defaults(func=cmd_cross_lang)
|
|
391
|
+
|
|
392
|
+
# churn
|
|
393
|
+
sub = subparsers.add_parser("churn", help="Find code churn hotspots")
|
|
394
|
+
sub.add_argument("--time-range", default=None, help='Time range (e.g. "30 days")')
|
|
395
|
+
sub.add_argument("--path-filter", default=None, help='Path filter (e.g. "src/")')
|
|
396
|
+
sub.add_argument("--limit", type=int, default=20, help="Max results (default: 20)")
|
|
397
|
+
sub.add_argument(
|
|
398
|
+
"--mode",
|
|
399
|
+
default="symbols",
|
|
400
|
+
choices=["symbols", "files", "co-change"],
|
|
401
|
+
help="Churn mode (default: symbols)",
|
|
402
|
+
)
|
|
403
|
+
sub.add_argument(
|
|
404
|
+
"--include-generated",
|
|
405
|
+
dest="exclude_generated",
|
|
406
|
+
action="store_false",
|
|
407
|
+
default=True,
|
|
408
|
+
help="Include generated/lockfiles (default: excluded)",
|
|
409
|
+
)
|
|
410
|
+
sub.set_defaults(func=cmd_churn)
|
|
411
|
+
|
|
412
|
+
# evolution
|
|
413
|
+
sub = subparsers.add_parser(
|
|
414
|
+
"evolution", help="Track how a symbol has changed over time"
|
|
415
|
+
)
|
|
416
|
+
sub.add_argument("name", help="Symbol name")
|
|
417
|
+
sub.add_argument("--time-range", default=None, help='Time range (e.g. "90 days")')
|
|
418
|
+
sub.add_argument("--limit", type=int, default=10, help="Max results (default: 10)")
|
|
419
|
+
sub.add_argument("--show-diff", action="store_true", help="Show diffs")
|
|
420
|
+
sub.add_argument("--crate", default=None, help="Filter by crate")
|
|
421
|
+
sub.set_defaults(func=cmd_evolution)
|
|
422
|
+
|
|
423
|
+
# changes
|
|
424
|
+
sub = subparsers.add_parser(
|
|
425
|
+
"changes", help="Analyze change impact for a commit range"
|
|
426
|
+
)
|
|
427
|
+
sub.add_argument(
|
|
428
|
+
"--commit-range", default=None, help="Commit range (e.g. HEAD~3..HEAD)"
|
|
429
|
+
)
|
|
430
|
+
sub.add_argument("--time-range", default=None, help='Time range (e.g. "7 days")')
|
|
431
|
+
sub.add_argument("--path-filter", default=None, help='Path filter (e.g. "src/")')
|
|
432
|
+
sub.add_argument(
|
|
433
|
+
"--show-callers",
|
|
434
|
+
action="store_true",
|
|
435
|
+
default=True,
|
|
436
|
+
help="Show callers of changed symbols (default)",
|
|
437
|
+
)
|
|
438
|
+
sub.add_argument(
|
|
439
|
+
"--no-show-callers",
|
|
440
|
+
dest="show_callers",
|
|
441
|
+
action="store_false",
|
|
442
|
+
help="Hide callers",
|
|
443
|
+
)
|
|
444
|
+
sub.add_argument("--limit", type=int, default=50, help="Max results (default: 50)")
|
|
445
|
+
sub.set_defaults(func=cmd_changes)
|
|
446
|
+
|
|
447
|
+
args = parser.parse_args()
|
|
448
|
+
|
|
449
|
+
if not args.command:
|
|
450
|
+
parser.print_help()
|
|
451
|
+
sys.exit(1)
|
|
452
|
+
|
|
453
|
+
args.func(args)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
if __name__ == "__main__":
|
|
457
|
+
main()
|