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
|
@@ -0,0 +1,2444 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Descry Explorer</title>
|
|
7
|
+
<link rel="stylesheet" href="/static/style.css">
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
|
9
|
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
|
10
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/rust.min.js"></script>
|
|
12
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
|
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
15
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
17
|
+
<script>document.addEventListener('DOMContentLoaded', () => { mermaid.initialize({ startOnLoad: false, theme: 'dark', themeVariables: { primaryColor: '#3b82f6', primaryTextColor: '#e2e8f0', lineColor: '#64748b', secondaryColor: '#1e293b', tertiaryColor: '#0f172a' }}); });</script>
|
|
18
|
+
</head>
|
|
19
|
+
<body x-data="app()" x-init="init()">
|
|
20
|
+
|
|
21
|
+
<!-- Header -->
|
|
22
|
+
<div class="header">
|
|
23
|
+
<div class="header-logo" @click="goHome()" style="cursor:pointer">
|
|
24
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
25
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
26
|
+
</svg>
|
|
27
|
+
<span>Descry Explorer</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="search-bar">
|
|
31
|
+
<span class="search-icon">🔍</span>
|
|
32
|
+
<input type="text" placeholder="Search symbols... (Enter to search)"
|
|
33
|
+
x-model="globalSearch"
|
|
34
|
+
@keydown.enter="doGlobalSearch()">
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="header-status" x-show="health">
|
|
38
|
+
<span class="status-dot" :class="health?.status === 'ok' ? 'ok' : health?.status === 'stale' ? 'stale' : 'error'"></span>
|
|
39
|
+
<span x-text="health?.graph?.nodes?.toLocaleString() + 'n / ' + health?.graph?.edges?.toLocaleString() + 'e'"></span>
|
|
40
|
+
<span x-show="health?.graph?.age_str" x-text="' | ' + health?.graph?.age_str" style="color:var(--text-muted)"></span>
|
|
41
|
+
</div>
|
|
42
|
+
<button class="help-btn" @click="showHelp ? (showHelp = false) : openHelp()" title="Encyclopedia">?</button>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Layout -->
|
|
46
|
+
<div class="layout">
|
|
47
|
+
|
|
48
|
+
<!-- Sidebar -->
|
|
49
|
+
<div class="sidebar">
|
|
50
|
+
<div class="sidebar-group">
|
|
51
|
+
<div class="sidebar-group-title">Search</div>
|
|
52
|
+
<div class="sidebar-item" :class="activeTool === 'search' && 'active'" @click="setTool('search')">
|
|
53
|
+
<span class="icon">⌕</span> Search
|
|
54
|
+
</div>
|
|
55
|
+
<div class="sidebar-item" :class="activeTool === 'semantic' && 'active'" @click="setTool('semantic')">
|
|
56
|
+
<span class="icon">≈</span> Semantic
|
|
57
|
+
</div>
|
|
58
|
+
<div class="sidebar-item" :class="activeTool === 'quick' && 'active'" @click="setTool('quick')">
|
|
59
|
+
<span class="icon">↯</span> Quick Lookup
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="sidebar-group">
|
|
64
|
+
<div class="sidebar-group-title">Navigate</div>
|
|
65
|
+
<div class="sidebar-item" :class="activeTool === 'callers' && 'active'" @click="setTool('callers')">
|
|
66
|
+
<span class="icon">←</span> Callers
|
|
67
|
+
</div>
|
|
68
|
+
<div class="sidebar-item" :class="activeTool === 'callees' && 'active'" @click="setTool('callees')">
|
|
69
|
+
<span class="icon">→</span> Callees
|
|
70
|
+
</div>
|
|
71
|
+
<div class="sidebar-item" :class="activeTool === 'context' && 'active'" @click="setTool('context')">
|
|
72
|
+
<span class="icon">▣</span> Context
|
|
73
|
+
</div>
|
|
74
|
+
<div class="sidebar-item" :class="activeTool === 'structure' && 'active'" @click="setTool('structure')">
|
|
75
|
+
<span class="icon">☰</span> Structure
|
|
76
|
+
</div>
|
|
77
|
+
<div class="sidebar-item" :class="activeTool === 'flatten' && 'active'" @click="setTool('flatten')">
|
|
78
|
+
<span class="icon">▽</span> Flatten
|
|
79
|
+
</div>
|
|
80
|
+
<div class="sidebar-item" :class="activeTool === 'impls' && 'active'" @click="setTool('impls')">
|
|
81
|
+
<span class="icon">≡</span> Impls
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="sidebar-group">
|
|
86
|
+
<div class="sidebar-group-title">Flow</div>
|
|
87
|
+
<div class="sidebar-item" :class="activeTool === 'flow' && 'active'" @click="setTool('flow')">
|
|
88
|
+
<span class="icon">➡</span> Trace
|
|
89
|
+
</div>
|
|
90
|
+
<div class="sidebar-item" :class="activeTool === 'path' && 'active'" @click="setTool('path')">
|
|
91
|
+
<span class="icon">↔</span> Path
|
|
92
|
+
</div>
|
|
93
|
+
<div class="sidebar-item" :class="activeTool === 'cross-lang' && 'active'" @click="setTool('cross-lang')">
|
|
94
|
+
<span class="icon">⧉</span> Cross-Lang
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="sidebar-group">
|
|
99
|
+
<div class="sidebar-group-title">History</div>
|
|
100
|
+
<div class="sidebar-item" :class="activeTool === 'churn' && 'active'" @click="setTool('churn')">
|
|
101
|
+
<span class="icon">♦</span> Churn
|
|
102
|
+
</div>
|
|
103
|
+
<div class="sidebar-item" :class="activeTool === 'evolution' && 'active'" @click="setTool('evolution')">
|
|
104
|
+
<span class="icon">⧖</span> Evolution
|
|
105
|
+
</div>
|
|
106
|
+
<div class="sidebar-item" :class="activeTool === 'changes' && 'active'" @click="setTool('changes')">
|
|
107
|
+
<span class="icon">Δ</span> Changes
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="sidebar-group">
|
|
112
|
+
<div class="sidebar-group-title">Admin</div>
|
|
113
|
+
<div class="sidebar-item" :class="activeTool === 'health' && 'active'" @click="setTool('health')">
|
|
114
|
+
<span class="icon">❤</span> Health
|
|
115
|
+
</div>
|
|
116
|
+
<div class="sidebar-item" :class="activeTool === 'reindex' && 'active'" @click="setTool('reindex')">
|
|
117
|
+
<span class="icon">↻</span> Reindex
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="sidebar-group">
|
|
122
|
+
<div class="sidebar-group-title">Source</div>
|
|
123
|
+
<div class="sidebar-item" :class="activeTool === 'source' && 'active'" @click="setTool('source')">
|
|
124
|
+
<span class="icon">‹›</span> View File
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Main Content -->
|
|
130
|
+
<div class="main">
|
|
131
|
+
|
|
132
|
+
<!-- Breadcrumbs -->
|
|
133
|
+
<div class="breadcrumbs" x-show="breadcrumbs.length > 1">
|
|
134
|
+
<template x-for="(crumb, idx) in breadcrumbs" :key="idx">
|
|
135
|
+
<span>
|
|
136
|
+
<span x-show="idx > 0" class="sep"> / </span>
|
|
137
|
+
<span class="crumb" @click="navigateTo(idx)" x-text="crumb.label"></span>
|
|
138
|
+
</span>
|
|
139
|
+
</template>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Error Banner -->
|
|
143
|
+
<div class="error-banner" x-show="error" x-text="error"></div>
|
|
144
|
+
|
|
145
|
+
<!-- ========= SEARCH TOOL ========= -->
|
|
146
|
+
<template x-if="activeTool === 'search'">
|
|
147
|
+
<div>
|
|
148
|
+
<div class="tool-form">
|
|
149
|
+
<h3>Search Symbols</h3>
|
|
150
|
+
<p class="tool-desc">Find functions, classes, methods, and constants across the codebase using hybrid keyword + semantic matching. Filter by language, package, or symbol type to narrow results. Use this as a starting point to discover symbols, then click any result to view its full context. Useful for locating entry points before tracing call flows.</p>
|
|
151
|
+
<div class="form-row">
|
|
152
|
+
<div class="form-group flex-1">
|
|
153
|
+
<label>Search Terms</label>
|
|
154
|
+
<input type="text" x-model="forms.search.terms" placeholder="e.g. validate_token, authentication logic"
|
|
155
|
+
@keydown.enter="runSearch()">
|
|
156
|
+
<div class="input-hints" x-show="examples">
|
|
157
|
+
Try:
|
|
158
|
+
<template x-for="name in (examples?.search || [])" :key="name">
|
|
159
|
+
<a @click.prevent="forms.search.terms = name; runSearch()" x-text="name"></a>
|
|
160
|
+
</template>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="form-group">
|
|
164
|
+
<label>Limit</label>
|
|
165
|
+
<input type="number" x-model.number="forms.search.limit" min="1" max="50" style="width:70px">
|
|
166
|
+
</div>
|
|
167
|
+
<div class="form-group">
|
|
168
|
+
<label>Language</label>
|
|
169
|
+
<select x-model="forms.search.lang">
|
|
170
|
+
<option value="">All</option>
|
|
171
|
+
<option value="rust">Rust</option>
|
|
172
|
+
<option value="typescript">TypeScript</option>
|
|
173
|
+
<option value="python">Python</option>
|
|
174
|
+
<option value="svelte">Svelte</option>
|
|
175
|
+
</select>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="form-group">
|
|
178
|
+
<label>Package</label>
|
|
179
|
+
<input type="text" x-model="forms.search.crate" placeholder="e.g. backend" style="width:120px">
|
|
180
|
+
</div>
|
|
181
|
+
<div class="form-group">
|
|
182
|
+
<label>Type</label>
|
|
183
|
+
<select x-model="forms.search.type">
|
|
184
|
+
<option value="">All</option>
|
|
185
|
+
<option value="function">Function</option>
|
|
186
|
+
<option value="class">Class</option>
|
|
187
|
+
<option value="method">Method</option>
|
|
188
|
+
<option value="constant">Constant</option>
|
|
189
|
+
</select>
|
|
190
|
+
</div>
|
|
191
|
+
<button class="btn btn-primary" @click="runSearch()" :disabled="loading">Go</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="results" x-show="results.search">
|
|
195
|
+
<div class="results-header">
|
|
196
|
+
<span x-text="results.search?.total + ' result(s)' + (results.search?.method ? ' [' + results.search.method + ']' : '')"></span>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="results-body">
|
|
199
|
+
<template x-if="results.search?.results?.length === 0">
|
|
200
|
+
<div class="empty-state"><div class="es-title">No results found</div></div>
|
|
201
|
+
</template>
|
|
202
|
+
<template x-for="(r, i) in results.search?.results || []" :key="r.id">
|
|
203
|
+
<div class="result-card" @click="viewContext(r.id)">
|
|
204
|
+
<div class="rc-header">
|
|
205
|
+
<span class="rc-type" :class="typeClass(r.type)" x-text="typeLabel(r.type)" :title="r.type"></span>
|
|
206
|
+
<span class="rc-name" x-text="r.parent_name ? r.parent_name + '::' + r.name : r.name"></span>
|
|
207
|
+
<span class="rc-meta" x-text="r.token_count + 't, ' + r.in_degree + 'c'"></span>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="rc-location" x-text="r.location"></div>
|
|
210
|
+
<div class="rc-signature" x-show="r.signature" x-text="r.signature"></div>
|
|
211
|
+
<div class="rc-docstring" x-show="r.docstring" x-text="r.docstring?.split('\\n')[0]?.substring(0, 120)"></div>
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</template>
|
|
218
|
+
|
|
219
|
+
<!-- ========= SEMANTIC TOOL ========= -->
|
|
220
|
+
<template x-if="activeTool === 'semantic'">
|
|
221
|
+
<div>
|
|
222
|
+
<div class="tool-form">
|
|
223
|
+
<h3>Semantic Search</h3>
|
|
224
|
+
<p class="tool-desc">Search by meaning using code embeddings rather than exact names. Describe what you're looking for in natural language (e.g. "rate limiting middleware" or "database connection pooling") and get results ranked by conceptual similarity. Best when you know what a function does but not what it's called. Especially useful for exploratory queries where keyword search would miss relevant results.</p>
|
|
225
|
+
<div class="form-row">
|
|
226
|
+
<div class="form-group flex-1">
|
|
227
|
+
<label>Natural Language Query</label>
|
|
228
|
+
<input type="text" x-model="forms.semantic.query" placeholder="e.g. authentication and token validation"
|
|
229
|
+
@keydown.enter="runSemantic()">
|
|
230
|
+
<div class="input-hints">
|
|
231
|
+
Try:
|
|
232
|
+
<a @click.prevent="forms.semantic.query = 'rate limiting and request throttling'; runSemantic()">rate limiting</a>
|
|
233
|
+
<a @click.prevent="forms.semantic.query = 'database connection pooling'; runSemantic()">connection pooling</a>
|
|
234
|
+
<a @click.prevent="forms.semantic.query = 'TLS certificate verification'; runSemantic()">TLS verification</a>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="form-group">
|
|
238
|
+
<label>Limit</label>
|
|
239
|
+
<input type="number" x-model.number="forms.semantic.limit" min="1" max="50" style="width:70px">
|
|
240
|
+
</div>
|
|
241
|
+
<button class="btn btn-primary" @click="runSemantic()" :disabled="loading">Go</button>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="results" x-show="results.semantic">
|
|
245
|
+
<div class="results-header">
|
|
246
|
+
<span x-text="results.semantic?.total + ' result(s)'"></span>
|
|
247
|
+
</div>
|
|
248
|
+
<div class="results-body">
|
|
249
|
+
<template x-for="r in results.semantic?.results || []" :key="r.id">
|
|
250
|
+
<div class="result-card" @click="viewContext(r.id)">
|
|
251
|
+
<div class="rc-header">
|
|
252
|
+
<span class="rc-type" :class="typeClass(r.type)" x-text="typeLabel(r.type)" :title="r.type"></span>
|
|
253
|
+
<span class="rc-name" x-text="r.name"></span>
|
|
254
|
+
<span class="rc-meta" x-text="'score: ' + r.score"></span>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="rc-location" x-text="r.location"></div>
|
|
257
|
+
<div class="rc-signature" x-show="r.signature" x-text="r.signature"></div>
|
|
258
|
+
</div>
|
|
259
|
+
</template>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</template>
|
|
264
|
+
|
|
265
|
+
<!-- ========= QUICK LOOKUP ========= -->
|
|
266
|
+
<template x-if="activeTool === 'quick'">
|
|
267
|
+
<div>
|
|
268
|
+
<div class="tool-form">
|
|
269
|
+
<h3>Quick Lookup</h3>
|
|
270
|
+
<p class="tool-desc">The fastest way to get full details on a symbol. Combines search and context in one step: finds the best match and immediately returns its source code, callers, callees, and related tests. Use when you already know a symbol name and want everything about it at once. Ideal when you need a complete picture in a single call instead of search-then-context round trips.</p>
|
|
271
|
+
<div class="form-row">
|
|
272
|
+
<div class="form-group flex-1">
|
|
273
|
+
<label>Symbol Name</label>
|
|
274
|
+
<input type="text" x-model="forms.quick.name" placeholder="e.g. validate_token"
|
|
275
|
+
@keydown.enter="runQuick()">
|
|
276
|
+
<div class="input-hints" x-show="examples">
|
|
277
|
+
Try:
|
|
278
|
+
<template x-for="name in (examples?.quick || [])" :key="name">
|
|
279
|
+
<a @click.prevent="forms.quick.name = name; runQuick()" x-text="name"></a>
|
|
280
|
+
</template>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="checkbox-row">
|
|
284
|
+
<input type="checkbox" id="quick-full" x-model="forms.quick.full">
|
|
285
|
+
<label for="quick-full">Full source</label>
|
|
286
|
+
</div>
|
|
287
|
+
<button class="btn btn-primary" @click="runQuick()" :disabled="loading">Go</button>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="results" x-show="results.quick">
|
|
291
|
+
<div class="results-header">
|
|
292
|
+
<span x-text="results.quick?.symbol?.name + (results.quick?.other_matches > 0 ? ' (+' + results.quick.other_matches + ' others)' : '')"></span>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="results-body markdown-body" x-html="safeHtml(results.quick?.markdown)" x-effect="if(results.quick?.markdown) $nextTick(() => renderMermaid($el))"></div>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</template>
|
|
298
|
+
|
|
299
|
+
<!-- ========= CALLERS ========= -->
|
|
300
|
+
<template x-if="activeTool === 'callers'">
|
|
301
|
+
<div>
|
|
302
|
+
<div class="tool-form">
|
|
303
|
+
<h3>Find Callers</h3>
|
|
304
|
+
<p class="tool-desc">Lists every function or method that calls the given symbol. More reliable than grep because it uses the call graph to distinguish actual call sites from definitions, comments, and string matches. Essential for impact analysis before refactoring: understand who depends on a function before changing its signature or behavior.</p>
|
|
305
|
+
<div class="form-row">
|
|
306
|
+
<div class="form-group flex-1">
|
|
307
|
+
<label>Symbol Name</label>
|
|
308
|
+
<input type="text" x-model="forms.callers.name" placeholder="e.g. validate_token"
|
|
309
|
+
@keydown.enter="runCallers()">
|
|
310
|
+
<div class="input-hints" x-show="examples">
|
|
311
|
+
Try:
|
|
312
|
+
<template x-for="name in (examples?.callers || [])" :key="name">
|
|
313
|
+
<a @click.prevent="forms.callers.name = name; runCallers()" x-text="name"></a>
|
|
314
|
+
</template>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="form-group">
|
|
318
|
+
<label>Limit</label>
|
|
319
|
+
<input type="number" x-model.number="forms.callers.limit" min="1" max="100" style="width:70px">
|
|
320
|
+
</div>
|
|
321
|
+
<button class="btn btn-primary" @click="runCallers()" :disabled="loading">Go</button>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="results" x-show="results.callers">
|
|
325
|
+
<div class="results-header">
|
|
326
|
+
<span x-text="results.callers?.total + ' caller(s) of ' + results.callers?.symbol + (results.callers?.fuzzy ? ' (fuzzy)' : '')"></span>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="results-body">
|
|
329
|
+
<template x-for="c in results.callers?.callers || []" :key="c.id">
|
|
330
|
+
<div class="symbol-list-item" @click="viewContext(c.id)">
|
|
331
|
+
<span x-text="c.name"></span>
|
|
332
|
+
<span class="sl-location" x-text="c.location"></span>
|
|
333
|
+
</div>
|
|
334
|
+
</template>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</template>
|
|
339
|
+
|
|
340
|
+
<!-- ========= CALLEES ========= -->
|
|
341
|
+
<template x-if="activeTool === 'callees'">
|
|
342
|
+
<div>
|
|
343
|
+
<div class="tool-form">
|
|
344
|
+
<h3>Find Callees</h3>
|
|
345
|
+
<p class="tool-desc">Lists every function or method that the given symbol calls. Use this to understand a function's dependencies and downstream effects before modifying it. Helps answer "what will break if this dependency changes?" and "what side effects does this function trigger?" Useful for dependency analysis when planning changes.</p>
|
|
346
|
+
<div class="form-row">
|
|
347
|
+
<div class="form-group flex-1">
|
|
348
|
+
<label>Symbol Name</label>
|
|
349
|
+
<input type="text" x-model="forms.callees.name" placeholder="e.g. validate_token"
|
|
350
|
+
@keydown.enter="runCallees()">
|
|
351
|
+
<div class="input-hints" x-show="examples">
|
|
352
|
+
Try:
|
|
353
|
+
<template x-for="name in (examples?.callees || [])" :key="name">
|
|
354
|
+
<a @click.prevent="forms.callees.name = name; runCallees()" x-text="name"></a>
|
|
355
|
+
</template>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="form-group">
|
|
359
|
+
<label>Limit</label>
|
|
360
|
+
<input type="number" x-model.number="forms.callees.limit" min="1" max="100" style="width:70px">
|
|
361
|
+
</div>
|
|
362
|
+
<button class="btn btn-primary" @click="runCallees()" :disabled="loading">Go</button>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="results" x-show="results.callees">
|
|
366
|
+
<div class="results-header">
|
|
367
|
+
<span x-text="results.callees?.total + ' callee(s) of ' + results.callees?.symbol + (results.callees?.fuzzy ? ' (fuzzy)' : '')"></span>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="results-body">
|
|
370
|
+
<template x-for="c in results.callees?.callees || []" :key="c.id">
|
|
371
|
+
<div class="symbol-list-item" @click="viewContext(c.id)">
|
|
372
|
+
<span x-text="c.name"></span>
|
|
373
|
+
<span class="sl-location" x-text="c.location"></span>
|
|
374
|
+
</div>
|
|
375
|
+
</template>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</template>
|
|
380
|
+
|
|
381
|
+
<!-- ========= CONTEXT ========= -->
|
|
382
|
+
<template x-if="activeTool === 'context'">
|
|
383
|
+
<div>
|
|
384
|
+
<div class="tool-form">
|
|
385
|
+
<h3>Symbol Context</h3>
|
|
386
|
+
<p class="tool-desc">The complete dossier for a symbol: source code, list of callers, list of callees, and related tests. Usually reached by clicking a search result. Toggle "Full" to see the complete source without truncation, or "Expand callees" to inline the source of small dependencies directly in the output. Use this as the primary way to gather all information needed to understand or modify a symbol.</p>
|
|
387
|
+
<div class="form-row">
|
|
388
|
+
<div class="form-group flex-1">
|
|
389
|
+
<label>Node ID</label>
|
|
390
|
+
<input type="text" x-model="forms.context.node_id" placeholder="e.g. FILE:src/app.py::MyClass"
|
|
391
|
+
@keydown.enter="runContext()">
|
|
392
|
+
</div>
|
|
393
|
+
<div class="checkbox-row">
|
|
394
|
+
<input type="checkbox" id="ctx-full" x-model="forms.context.full">
|
|
395
|
+
<label for="ctx-full">Full</label>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="checkbox-row">
|
|
398
|
+
<input type="checkbox" id="ctx-expand" x-model="forms.context.expand_callees">
|
|
399
|
+
<label for="ctx-expand">Expand callees</label>
|
|
400
|
+
</div>
|
|
401
|
+
<button class="btn btn-primary" @click="runContext()" :disabled="loading">Go</button>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="results" x-show="results.context">
|
|
405
|
+
<div class="results-header">
|
|
406
|
+
<span x-text="results.context?.node_id"></span>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="results-body markdown-body" x-html="safeHtml(results.context?.markdown)" x-effect="if(results.context?.markdown) $nextTick(() => renderMermaid($el))"></div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
</template>
|
|
412
|
+
|
|
413
|
+
<!-- ========= STRUCTURE ========= -->
|
|
414
|
+
<template x-if="activeTool === 'structure'">
|
|
415
|
+
<div>
|
|
416
|
+
<div class="tool-form">
|
|
417
|
+
<h3>File Structure</h3>
|
|
418
|
+
<p class="tool-desc">Shows the skeleton of a file: imports, constants, classes, and functions with line numbers, without reading the full source. Much faster than opening a large file when you just need to know what's defined in it. Click any symbol to jump to its full context. Useful for orientation when entering an unfamiliar file.</p>
|
|
419
|
+
<div class="form-row">
|
|
420
|
+
<div class="form-group flex-1">
|
|
421
|
+
<label>Filename</label>
|
|
422
|
+
<input type="text" x-model="forms.structure.filename" placeholder="e.g. handlers.py, query.py"
|
|
423
|
+
@keydown.enter="runStructure()">
|
|
424
|
+
<div class="input-hints" x-show="examples">
|
|
425
|
+
Try:
|
|
426
|
+
<template x-for="name in (examples?.structure || [])" :key="name">
|
|
427
|
+
<a @click.prevent="forms.structure.filename = name; runStructure()" x-text="name"></a>
|
|
428
|
+
</template>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
<button class="btn btn-primary" @click="runStructure()" :disabled="loading">Go</button>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="results" x-show="results.structure">
|
|
435
|
+
<div class="results-header">
|
|
436
|
+
<span x-text="results.structure?.file"></span>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="results-body">
|
|
439
|
+
<template x-if="results.structure?.imports?.length">
|
|
440
|
+
<div style="margin-bottom:12px">
|
|
441
|
+
<div style="font-weight:600;margin-bottom:4px;color:var(--text-secondary);font-size:12px">IMPORTS</div>
|
|
442
|
+
<div style="font-family:var(--font-mono);font-size:12px;color:var(--text-muted)" x-text="results.structure.imports.join(', ')"></div>
|
|
443
|
+
</div>
|
|
444
|
+
</template>
|
|
445
|
+
<template x-for="(items, kind) in results.structure?.definitions || {}" :key="kind">
|
|
446
|
+
<div style="margin-bottom:12px">
|
|
447
|
+
<div style="font-weight:600;margin-bottom:4px;color:var(--text-secondary);font-size:12px;text-transform:uppercase" x-text="kind"></div>
|
|
448
|
+
<template x-for="item in items" :key="item.id || item.name">
|
|
449
|
+
<div class="symbol-list-item" @click="item.id && viewContext(item.id)">
|
|
450
|
+
<span x-text="item.name"></span>
|
|
451
|
+
<span class="sl-location" x-show="item.lineno" x-text="'L' + item.lineno"></span>
|
|
452
|
+
</div>
|
|
453
|
+
</template>
|
|
454
|
+
</div>
|
|
455
|
+
</template>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</template>
|
|
460
|
+
|
|
461
|
+
<!-- ========= FLATTEN ========= -->
|
|
462
|
+
<template x-if="activeTool === 'flatten'">
|
|
463
|
+
<div>
|
|
464
|
+
<div class="tool-form">
|
|
465
|
+
<h3>Flatten Class API</h3>
|
|
466
|
+
<p class="tool-desc">Shows the effective public API of a class including methods inherited from traits, interfaces, and parent types. Requires the full node ID (e.g. FILE:src/app.py::MyClass). Use this to understand the complete interface of a type without manually tracing implementations. Useful for enumerating all available methods when working with a class or struct.</p>
|
|
467
|
+
<div class="form-row">
|
|
468
|
+
<div class="form-group flex-1">
|
|
469
|
+
<label>Class Node ID</label>
|
|
470
|
+
<input type="text" x-model="forms.flatten.class_node_id" placeholder="e.g. FILE:src/app.py::MyClass"
|
|
471
|
+
@keydown.enter="runFlatten()">
|
|
472
|
+
<div class="input-hints" x-show="examples?.flatten">
|
|
473
|
+
Try:
|
|
474
|
+
<a @click.prevent="forms.flatten.class_node_id = examples.flatten; runFlatten()" x-text="examples.flatten.split('::').pop()"></a>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
<button class="btn btn-primary" @click="runFlatten()" :disabled="loading">Go</button>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="results" x-show="results.flatten">
|
|
481
|
+
<div class="results-body markdown-body" x-html="safeHtml(results.flatten?.markdown)" x-effect="if(results.flatten?.markdown) $nextTick(() => renderMermaid($el))"></div>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</template>
|
|
485
|
+
|
|
486
|
+
<!-- ========= IMPLS ========= -->
|
|
487
|
+
<template x-if="activeTool === 'impls'">
|
|
488
|
+
<div>
|
|
489
|
+
<div class="tool-form">
|
|
490
|
+
<h3>Trait Implementations</h3>
|
|
491
|
+
<p class="tool-desc">Find every type (struct, class) that implements a given trait or interface method across the codebase. Essential for polymorphic code where grep matches dozens of false positives. For example, searching a method name shows all types implementing it. Optionally filter by trait name. Use this to find all concrete implementations of an abstract method.</p>
|
|
492
|
+
<div class="form-row">
|
|
493
|
+
<div class="form-group flex-1">
|
|
494
|
+
<label>Method Name</label>
|
|
495
|
+
<input type="text" x-model="forms.impls.method" placeholder="e.g. parse, validate"
|
|
496
|
+
@keydown.enter="runImpls()">
|
|
497
|
+
<div class="input-hints" x-show="examples?.impls?.length">
|
|
498
|
+
Try:
|
|
499
|
+
<template x-for="name in (examples?.impls || [])" :key="name">
|
|
500
|
+
<a @click.prevent="forms.impls.method = name; runImpls()" x-text="name"></a>
|
|
501
|
+
</template>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="form-group">
|
|
505
|
+
<label>Trait Filter</label>
|
|
506
|
+
<input type="text" x-model="forms.impls.trait_name" placeholder="(optional)" style="width:150px">
|
|
507
|
+
</div>
|
|
508
|
+
<button class="btn btn-primary" @click="runImpls()" :disabled="loading">Go</button>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="results" x-show="results.impls">
|
|
512
|
+
<div class="results-header">
|
|
513
|
+
<span x-text="results.impls?.total + ' implementation(s) of ' + results.impls?.method"></span>
|
|
514
|
+
</div>
|
|
515
|
+
<div class="results-body">
|
|
516
|
+
<template x-for="impl in results.impls?.implementations || []" :key="impl.id">
|
|
517
|
+
<div class="result-card" @click="viewContext(impl.id)">
|
|
518
|
+
<div class="rc-header">
|
|
519
|
+
<span class="rc-type met">impl</span>
|
|
520
|
+
<span class="rc-name" x-text="impl.struct_name + '::' + results.impls?.method"></span>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="rc-location" x-text="impl.file_path + (impl.lineno ? ':' + impl.lineno : '')"></div>
|
|
523
|
+
<div class="rc-signature" x-show="impl.signature" x-text="impl.signature"></div>
|
|
524
|
+
<div class="rc-meta" x-text="'trait: ' + impl.trait_name"></div>
|
|
525
|
+
</div>
|
|
526
|
+
</template>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
</template>
|
|
531
|
+
|
|
532
|
+
<!-- ========= FLOW ========= -->
|
|
533
|
+
<template x-if="activeTool === 'flow'">
|
|
534
|
+
<div>
|
|
535
|
+
<div class="tool-form">
|
|
536
|
+
<h3>Trace Call Flow</h3>
|
|
537
|
+
<p class="tool-desc">Visualize the call tree starting from any symbol. "Forward" traces what a function calls (and what those call, recursively). "Backward" traces what calls the function. Set depth to control how many levels deep to explore. Optionally set a target to stop when a specific symbol is reached. Small functions are inlined so you can read the logic without leaving the page.</p>
|
|
538
|
+
<div class="form-row">
|
|
539
|
+
<div class="form-group flex-1">
|
|
540
|
+
<label>Start Symbol</label>
|
|
541
|
+
<input type="text" x-model="forms.flow.start" placeholder="e.g. validate_token"
|
|
542
|
+
@keydown.enter="runFlow()">
|
|
543
|
+
<div class="input-hints" x-show="examples">
|
|
544
|
+
Try:
|
|
545
|
+
<a x-show="examples?.flow_forward" @click.prevent="forms.flow.start = examples.flow_forward; forms.flow.direction = 'forward'; forms.flow.depth = 3; runFlow()" x-text="examples?.flow_forward + ' (fwd)'"></a>
|
|
546
|
+
<a x-show="examples?.flow_backward" @click.prevent="forms.flow.start = examples.flow_backward; forms.flow.direction = 'backward'; forms.flow.depth = 2; runFlow()" x-text="examples?.flow_backward + ' (bwd)'"></a>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="form-group">
|
|
550
|
+
<label>Direction</label>
|
|
551
|
+
<select x-model="forms.flow.direction">
|
|
552
|
+
<option value="forward">Forward (callees)</option>
|
|
553
|
+
<option value="backward">Backward (callers)</option>
|
|
554
|
+
</select>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="form-group">
|
|
557
|
+
<label>Depth</label>
|
|
558
|
+
<input type="number" x-model.number="forms.flow.depth" min="1" max="10" style="width:70px">
|
|
559
|
+
</div>
|
|
560
|
+
<div class="form-group">
|
|
561
|
+
<label>Target (optional)</label>
|
|
562
|
+
<input type="text" x-model="forms.flow.target" placeholder="stop at..." style="width:150px">
|
|
563
|
+
</div>
|
|
564
|
+
<button class="btn btn-primary" @click="runFlow()" :disabled="loading">Go</button>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
<div class="results" x-show="results.flow?.tree">
|
|
568
|
+
<div class="results-header">
|
|
569
|
+
<span x-text="'Flow from ' + results.flow?.start + ' (' + results.flow?.direction + ', depth ' + results.flow?.depth + ')'"></span>
|
|
570
|
+
<button class="btn btn-sm" @click="flowToggleAll()" x-text="flowExpandAll ? 'Collapse all' : 'Expand all'" style="font-size:11px; padding: 2px 8px;"></button>
|
|
571
|
+
</div>
|
|
572
|
+
<!-- Disambiguation note -->
|
|
573
|
+
<div class="flow-note" x-show="results.flow?.tree?.note" x-text="results.flow?.tree?.note"></div>
|
|
574
|
+
<!-- Alternatives -->
|
|
575
|
+
<div class="flow-alternatives" x-show="results.flow?.tree?.alternatives?.length">
|
|
576
|
+
<span class="flow-alt-label">Alternatives:</span>
|
|
577
|
+
<template x-for="alt in (results.flow?.tree?.alternatives || [])" :key="alt.id">
|
|
578
|
+
<a class="flow-alt-link" @click.prevent="forms.flow.start = alt.id; runFlow()" x-text="alt.name + ' (' + alt.file + ')'"></a>
|
|
579
|
+
</template>
|
|
580
|
+
</div>
|
|
581
|
+
<!-- Error -->
|
|
582
|
+
<div class="flow-error" x-show="results.flow?.tree?.error" x-text="results.flow?.tree?.error"></div>
|
|
583
|
+
<!-- Tree -->
|
|
584
|
+
<div class="flow-tree" x-show="results.flow?.tree?.root"
|
|
585
|
+
x-html="renderFlowTree(results.flow?.tree?.root, 0)"
|
|
586
|
+
@click="handleFlowClick($event)"
|
|
587
|
+
x-effect="if(results.flow?.tree?.root) $nextTick(() => highlightFlowCode($el))"></div>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
</template>
|
|
591
|
+
|
|
592
|
+
<!-- ========= PATH ========= -->
|
|
593
|
+
<template x-if="activeTool === 'path'">
|
|
594
|
+
<div>
|
|
595
|
+
<div class="tool-form">
|
|
596
|
+
<h3>Find Call Path</h3>
|
|
597
|
+
<p class="tool-desc">Find the shortest call chain between two symbols. Answers "how does function A eventually call function B?" by showing each hop with the call site code snippet. More focused than Trace, which shows the entire call tree. Use this when you know both endpoints and need to understand the connection between them. Useful for debugging or verifying that a specific code path exists.</p>
|
|
598
|
+
<div class="form-row">
|
|
599
|
+
<div class="form-group flex-1">
|
|
600
|
+
<label>Start</label>
|
|
601
|
+
<input type="text" x-model="forms.path.start" placeholder="e.g. main"
|
|
602
|
+
@keydown.enter="runPath()">
|
|
603
|
+
</div>
|
|
604
|
+
<div class="form-group flex-1">
|
|
605
|
+
<label>End</label>
|
|
606
|
+
<input type="text" x-model="forms.path.end" placeholder="e.g. validate"
|
|
607
|
+
@keydown.enter="runPath()">
|
|
608
|
+
</div>
|
|
609
|
+
<div class="form-group">
|
|
610
|
+
<label>Max Depth</label>
|
|
611
|
+
<input type="number" x-model.number="forms.path.max_depth" min="1" max="20" style="width:70px">
|
|
612
|
+
</div>
|
|
613
|
+
<button class="btn btn-primary" @click="runPath()" :disabled="loading">Go</button>
|
|
614
|
+
</div>
|
|
615
|
+
<div class="input-hints" x-show="examples?.path_start && examples?.path_end">
|
|
616
|
+
Try:
|
|
617
|
+
<a @click.prevent="forms.path.start = examples.path_start; forms.path.end = examples.path_end; runPath()" x-text="examples.path_start + ' \u2192 ' + examples.path_end"></a>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="results" x-show="results.path">
|
|
621
|
+
<div class="results-header">
|
|
622
|
+
<span x-text="results.path?.hops + ' hop(s) from ' + results.path?.start + ' to ' + results.path?.end"></span>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="results-body markdown-body" x-html="safeHtml(results.path?.markdown)" x-effect="if(results.path?.markdown) $nextTick(() => renderMermaid($el))"></div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</template>
|
|
628
|
+
|
|
629
|
+
<!-- ========= CROSS-LANG ========= -->
|
|
630
|
+
<template x-if="activeTool === 'cross-lang'">
|
|
631
|
+
<div>
|
|
632
|
+
<!-- Not configured notice (shown immediately, no query needed) -->
|
|
633
|
+
<div class="tool-form" x-show="health && !health.features?.cross_lang">
|
|
634
|
+
<h3>Cross-Language Trace</h3>
|
|
635
|
+
<p class="tool-desc">Bridge the frontend-backend gap by mapping frontend API calls to their backend handler functions via the OpenAPI spec.</p>
|
|
636
|
+
<div class="tool-notice">
|
|
637
|
+
<strong>Cross-language tracing is not configured.</strong>
|
|
638
|
+
<p>This tool requires an OpenAPI spec to map frontend API calls to backend handlers. Place your spec at <code>public/api/openapi.json</code> relative to the project root, then reindex.</p>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
<!-- Normal form (shown when cross_lang is available) -->
|
|
642
|
+
<div class="tool-form" x-show="!health || health.features?.cross_lang">
|
|
643
|
+
<h3>Cross-Language Trace</h3>
|
|
644
|
+
<p class="tool-desc">Bridge the frontend-backend gap by mapping frontend API calls to their backend handler functions via the OpenAPI spec. Use "Endpoint" mode to find which handler serves a specific route. Use "List" mode to see all endpoints for a resource. Use "Stats" mode for an overview of API coverage. Useful for tracing user actions from the UI all the way to the backend layer.</p>
|
|
645
|
+
<div class="form-row">
|
|
646
|
+
<div class="form-group">
|
|
647
|
+
<label>Mode</label>
|
|
648
|
+
<select x-model="forms.crossLang.mode">
|
|
649
|
+
<option value="endpoint">Endpoint</option>
|
|
650
|
+
<option value="list">List</option>
|
|
651
|
+
<option value="stats">Stats</option>
|
|
652
|
+
</select>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="form-group" x-show="forms.crossLang.mode === 'endpoint'">
|
|
655
|
+
<label>Method</label>
|
|
656
|
+
<select x-model="forms.crossLang.method">
|
|
657
|
+
<option value="GET">GET</option>
|
|
658
|
+
<option value="POST">POST</option>
|
|
659
|
+
<option value="PUT">PUT</option>
|
|
660
|
+
<option value="DELETE">DELETE</option>
|
|
661
|
+
<option value="PATCH">PATCH</option>
|
|
662
|
+
</select>
|
|
663
|
+
</div>
|
|
664
|
+
<div class="form-group flex-1" x-show="forms.crossLang.mode === 'endpoint'">
|
|
665
|
+
<label>Path</label>
|
|
666
|
+
<input type="text" x-model="forms.crossLang.path" placeholder="/api/v1/users"
|
|
667
|
+
@keydown.enter="runCrossLang()">
|
|
668
|
+
</div>
|
|
669
|
+
<div class="form-group" x-show="forms.crossLang.mode === 'list'">
|
|
670
|
+
<label>Tag (optional)</label>
|
|
671
|
+
<input type="text" x-model="forms.crossLang.tag" placeholder="e.g. users" style="width:150px">
|
|
672
|
+
</div>
|
|
673
|
+
<button class="btn btn-primary" @click="runCrossLang()" :disabled="loading">Go</button>
|
|
674
|
+
</div>
|
|
675
|
+
<div class="input-hints">
|
|
676
|
+
Try:
|
|
677
|
+
<a @click.prevent="forms.crossLang.mode = 'stats'; runCrossLang()">stats</a>
|
|
678
|
+
<a @click.prevent="forms.crossLang.mode = 'list'; forms.crossLang.tag = ''; runCrossLang()">list all endpoints</a>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
<!-- Error -->
|
|
682
|
+
<div class="results" x-show="results.crossLang?.error">
|
|
683
|
+
<div class="flow-error" x-text="results.crossLang?.error"></div>
|
|
684
|
+
</div>
|
|
685
|
+
<!-- Endpoint mode result -->
|
|
686
|
+
<div class="results" x-show="results.crossLang?.operationId">
|
|
687
|
+
<div class="results-header">
|
|
688
|
+
<span x-text="results.crossLang?.method + ' ' + results.crossLang?.path"></span>
|
|
689
|
+
</div>
|
|
690
|
+
<div class="cl-endpoint-detail">
|
|
691
|
+
<div class="cl-row">
|
|
692
|
+
<span class="cl-label">Summary</span>
|
|
693
|
+
<span class="cl-value" x-text="results.crossLang?.summary || 'No summary'"></span>
|
|
694
|
+
</div>
|
|
695
|
+
<div class="cl-row">
|
|
696
|
+
<span class="cl-label">Operation</span>
|
|
697
|
+
<span class="cl-value cl-mono" x-text="results.crossLang?.operationId"></span>
|
|
698
|
+
</div>
|
|
699
|
+
<div class="cl-row" x-show="results.crossLang?.tags?.length">
|
|
700
|
+
<span class="cl-label">Tags</span>
|
|
701
|
+
<span class="cl-value">
|
|
702
|
+
<template x-for="t in (results.crossLang?.tags || [])" :key="t">
|
|
703
|
+
<span class="cl-tag" @click="forms.crossLang.mode = 'list'; forms.crossLang.tag = t; runCrossLang()" x-text="t"></span>
|
|
704
|
+
</template>
|
|
705
|
+
</span>
|
|
706
|
+
</div>
|
|
707
|
+
<div class="cl-row">
|
|
708
|
+
<span class="cl-label">Handler</span>
|
|
709
|
+
<span class="cl-value" x-show="results.crossLang?.node_id">
|
|
710
|
+
<span class="cl-handler-name" @click="forms.context.node_id = results.crossLang.node_id; setTool('context'); runContext()" x-text="results.crossLang?.node_id?.split('::').pop()"></span>
|
|
711
|
+
<span class="cl-handler-file" @click="
|
|
712
|
+
const nid = results.crossLang.node_id;
|
|
713
|
+
const file = nid.split('::')[0].replace('FILE:', '');
|
|
714
|
+
forms.source.file = file; forms.source.line = null; setTool('source'); runSource();
|
|
715
|
+
" x-text="results.crossLang?.node_id?.split('::')[0].replace('FILE:', '').replace(/^FILE:/, '')"></span>
|
|
716
|
+
</span>
|
|
717
|
+
<span class="cl-value cl-no-handler" x-show="!results.crossLang?.node_id">Not linked to graph</span>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="cl-actions" x-show="results.crossLang?.node_id">
|
|
720
|
+
<button class="btn btn-sm" @click="forms.context.node_id = results.crossLang.node_id; setTool('context'); runContext()">View Context</button>
|
|
721
|
+
<button class="btn btn-sm" @click="
|
|
722
|
+
const name = results.crossLang.node_id.split('::').pop();
|
|
723
|
+
forms.flow.start = name; forms.flow.direction = 'forward'; setTool('flow'); runFlow();
|
|
724
|
+
">Trace Forward</button>
|
|
725
|
+
<button class="btn btn-sm" @click="
|
|
726
|
+
const nid = results.crossLang.node_id;
|
|
727
|
+
const file = nid.split('::')[0].replace('FILE:', '');
|
|
728
|
+
forms.source.file = file; forms.source.line = null; setTool('source'); runSource();
|
|
729
|
+
">View Source</button>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
<!-- List mode result -->
|
|
734
|
+
<div class="results" x-show="results.crossLang?.endpoints">
|
|
735
|
+
<div class="results-header">
|
|
736
|
+
<span x-text="(results.crossLang?.tag ? 'Tag: ' + results.crossLang.tag + ' — ' : '') + results.crossLang?.total + ' endpoints'"></span>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="cl-endpoint-list">
|
|
739
|
+
<template x-for="ep in (results.crossLang?.endpoints || [])" :key="ep.operationId">
|
|
740
|
+
<div class="cl-endpoint-item" @click="forms.crossLang.mode = 'endpoint'; forms.crossLang.method = ep.method; forms.crossLang.path = ep.path; runCrossLang()">
|
|
741
|
+
<span class="cl-method-badge" :class="'method-' + ep.method.toLowerCase()" x-text="ep.method"></span>
|
|
742
|
+
<span class="cl-ep-path" x-text="ep.path"></span>
|
|
743
|
+
<span class="cl-ep-summary" x-text="ep.summary || ''"></span>
|
|
744
|
+
<span class="cl-ep-handler" x-show="ep.node_id" x-text="ep.node_id?.split('::').pop()"></span>
|
|
745
|
+
</div>
|
|
746
|
+
</template>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
<!-- Stats mode result -->
|
|
750
|
+
<div class="results" x-show="results.crossLang?.total_endpoints !== undefined">
|
|
751
|
+
<div class="results-header">
|
|
752
|
+
<span>API Coverage Stats</span>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="cl-stats">
|
|
755
|
+
<div class="cl-stat-card">
|
|
756
|
+
<div class="cl-stat-value" x-text="results.crossLang?.total_endpoints"></div>
|
|
757
|
+
<div class="cl-stat-label">Total Endpoints</div>
|
|
758
|
+
</div>
|
|
759
|
+
<div class="cl-stat-card">
|
|
760
|
+
<div class="cl-stat-value" x-text="results.crossLang?.linked_to_graph"></div>
|
|
761
|
+
<div class="cl-stat-label">Linked to Graph</div>
|
|
762
|
+
</div>
|
|
763
|
+
<div class="cl-stat-card">
|
|
764
|
+
<div class="cl-stat-value" x-text="results.crossLang?.total_endpoints ? Math.round(results.crossLang.linked_to_graph / results.crossLang.total_endpoints * 100) + '%' : '—'"></div>
|
|
765
|
+
<div class="cl-stat-label">Coverage</div>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
</template>
|
|
771
|
+
|
|
772
|
+
<!-- ========= CHURN ========= -->
|
|
773
|
+
<template x-if="activeTool === 'churn'">
|
|
774
|
+
<div>
|
|
775
|
+
<div class="tool-form">
|
|
776
|
+
<h3>Code Churn Hotspots</h3>
|
|
777
|
+
<p class="tool-desc">Find the most frequently changed code using git history analysis. "Symbols" mode maps changes to specific functions and methods. "Files" mode shows file-level change frequency. "Co-change" mode reveals symbol pairs that always change together, indicating hidden coupling. Filter by time range and path. Use this to prioritize refactoring, identify unstable code, or find areas needing better test coverage.</p>
|
|
778
|
+
<div class="form-row">
|
|
779
|
+
<div class="form-group">
|
|
780
|
+
<label>Mode</label>
|
|
781
|
+
<select x-model="forms.churn.mode">
|
|
782
|
+
<option value="symbols">Symbols</option>
|
|
783
|
+
<option value="files">Files</option>
|
|
784
|
+
<option value="co-change">Co-Change</option>
|
|
785
|
+
</select>
|
|
786
|
+
</div>
|
|
787
|
+
<div class="form-group">
|
|
788
|
+
<label>Time Range</label>
|
|
789
|
+
<select x-model="forms.churn.time_range">
|
|
790
|
+
<option value="">All time</option>
|
|
791
|
+
<option value="last 7 days">Last 7 days</option>
|
|
792
|
+
<option value="last 14 days">Last 14 days</option>
|
|
793
|
+
<option value="last 30 days">Last 30 days</option>
|
|
794
|
+
<option value="last 3 months">Last 3 months</option>
|
|
795
|
+
<option value="last 6 months">Last 6 months</option>
|
|
796
|
+
</select>
|
|
797
|
+
<div class="input-hints">
|
|
798
|
+
Try:
|
|
799
|
+
<a @click.prevent="forms.churn.mode = 'symbols'; forms.churn.time_range = 'last 30 days'; forms.churn.path_filter = ''; runChurn()">top symbols (30d)</a>
|
|
800
|
+
<a @click.prevent="forms.churn.mode = 'files'; forms.churn.time_range = 'last 30 days'; forms.churn.path_filter = ''; runChurn()">files (30 days)</a>
|
|
801
|
+
<a @click.prevent="forms.churn.mode = 'co-change'; forms.churn.time_range = ''; forms.churn.path_filter = ''; runChurn()">co-change pairs</a>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
<div class="form-group">
|
|
805
|
+
<label>Path Filter</label>
|
|
806
|
+
<input type="text" x-model="forms.churn.path_filter" placeholder="e.g. backend/" style="width:150px">
|
|
807
|
+
</div>
|
|
808
|
+
<div class="form-group">
|
|
809
|
+
<label>Limit</label>
|
|
810
|
+
<input type="number" x-model.number="forms.churn.limit" min="1" max="100" style="width:70px">
|
|
811
|
+
</div>
|
|
812
|
+
<button class="btn btn-primary" @click="runChurn()" :disabled="loading">Go</button>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
<div class="results" x-show="results.churn?.data">
|
|
816
|
+
<!-- Error -->
|
|
817
|
+
<div class="flow-error" x-show="results.churn?.data?.error" x-text="results.churn?.data?.error"></div>
|
|
818
|
+
<!-- Symbols mode -->
|
|
819
|
+
<template x-if="results.churn?.data?.symbols">
|
|
820
|
+
<div>
|
|
821
|
+
<div class="changes-header">
|
|
822
|
+
<span x-text="'Top ' + results.churn.data.symbols.length + ' Most Changed Symbols'"></span>
|
|
823
|
+
<span class="changes-meta">
|
|
824
|
+
<span x-show="results.churn.data.time_range" x-text="results.churn.data.time_range"></span>
|
|
825
|
+
<template x-if="results.churn.data.path_filter">
|
|
826
|
+
<span><span class="changes-sep">·</span><span x-text="results.churn.data.path_filter"></span></span>
|
|
827
|
+
</template>
|
|
828
|
+
</span>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="churn-list">
|
|
831
|
+
<template x-for="(sym, idx) in results.churn.data.symbols" :key="sym.id">
|
|
832
|
+
<div class="churn-item">
|
|
833
|
+
<span class="churn-rank" x-text="idx + 1"></span>
|
|
834
|
+
<span class="rc-type" :class="sym.type.toLowerCase()" x-text="sym.type"></span>
|
|
835
|
+
<span class="changes-sym-name" @click="forms.context.node_id = sym.id; setTool('context'); runContext()" x-text="sym.name"></span>
|
|
836
|
+
<span class="churn-commits" x-text="sym.commits + ' commits'"></span>
|
|
837
|
+
<span class="changes-diff-stat" x-show="sym.added || sym.removed">
|
|
838
|
+
<span class="changes-added" x-text="'+' + sym.added"></span>
|
|
839
|
+
<span class="changes-removed" x-text="'-' + sym.removed"></span>
|
|
840
|
+
</span>
|
|
841
|
+
<span class="changes-sym-file" x-show="sym.file" @click="forms.source.file = sym.file; forms.source.line = sym.line; setTool('source'); runSource()" x-text="sym.file.replace(/^FILE:/, '') + (sym.line ? ':' + sym.line : '')"></span>
|
|
842
|
+
</div>
|
|
843
|
+
</template>
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
846
|
+
</template>
|
|
847
|
+
<!-- Files mode -->
|
|
848
|
+
<template x-if="results.churn?.data?.files">
|
|
849
|
+
<div>
|
|
850
|
+
<div class="changes-header">
|
|
851
|
+
<span x-text="'Top ' + results.churn.data.files.length + ' Most Changed Files'"></span>
|
|
852
|
+
<span class="changes-meta">
|
|
853
|
+
<span x-show="results.churn.data.time_range" x-text="results.churn.data.time_range"></span>
|
|
854
|
+
</span>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="churn-list">
|
|
857
|
+
<template x-for="(f, idx) in results.churn.data.files" :key="f.file">
|
|
858
|
+
<div class="churn-item">
|
|
859
|
+
<span class="churn-rank" x-text="idx + 1"></span>
|
|
860
|
+
<span class="churn-file-name" @click="forms.source.file = f.file; forms.source.line = null; setTool('source'); runSource()" x-text="f.file"></span>
|
|
861
|
+
<span class="churn-commits" x-text="f.commits + ' commits'"></span>
|
|
862
|
+
</div>
|
|
863
|
+
</template>
|
|
864
|
+
</div>
|
|
865
|
+
</div>
|
|
866
|
+
</template>
|
|
867
|
+
<!-- Co-change mode -->
|
|
868
|
+
<template x-if="results.churn?.data?.pairs">
|
|
869
|
+
<div>
|
|
870
|
+
<div class="changes-header">
|
|
871
|
+
<span x-text="'Top ' + results.churn.data.pairs.length + ' Co-Change Pairs'"></span>
|
|
872
|
+
<span class="changes-meta">
|
|
873
|
+
<span x-show="results.churn.data.file_level" style="font-style:italic">file-level (symbol data sparse)</span>
|
|
874
|
+
</span>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="churn-list">
|
|
877
|
+
<template x-for="(pair, idx) in results.churn.data.pairs" :key="idx">
|
|
878
|
+
<div class="churn-pair">
|
|
879
|
+
<span class="churn-rank" x-text="idx + 1"></span>
|
|
880
|
+
<span class="churn-pair-sym" @click="if(pair.a_id) { forms.context.node_id = pair.a_id; setTool('context'); runContext(); }" :class="pair.a_id ? 'clickable' : ''" x-text="pair.a_name"></span>
|
|
881
|
+
<span class="churn-pair-arrow">↔</span>
|
|
882
|
+
<span class="churn-pair-sym" @click="if(pair.b_id) { forms.context.node_id = pair.b_id; setTool('context'); runContext(); }" :class="pair.b_id ? 'clickable' : ''" x-text="pair.b_name"></span>
|
|
883
|
+
<span class="churn-commits" x-text="pair.shared_commits + ' shared'"></span>
|
|
884
|
+
</div>
|
|
885
|
+
</template>
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
</template>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
</template>
|
|
892
|
+
|
|
893
|
+
<!-- ========= EVOLUTION ========= -->
|
|
894
|
+
<template x-if="activeTool === 'evolution'">
|
|
895
|
+
<div>
|
|
896
|
+
<div class="tool-form">
|
|
897
|
+
<h3>Symbol Evolution</h3>
|
|
898
|
+
<p class="tool-desc">Track the commit history of a specific function or method over time. Shows a timeline of commits that touched the symbol, including authors, dates, and change sizes. Enable "Show diffs" to see the actual code changes at each point. Uses git's native function tracking to follow the symbol across renames and line drift. Use this to understand why a function looks the way it does, or to find when a bug was introduced.</p>
|
|
899
|
+
<div class="form-row">
|
|
900
|
+
<div class="form-group flex-1">
|
|
901
|
+
<label>Symbol Name</label>
|
|
902
|
+
<input type="text" x-model="forms.evolution.name" placeholder="e.g. validate_token"
|
|
903
|
+
@keydown.enter="runEvolution()">
|
|
904
|
+
<div class="input-hints" x-show="examples?.evolution?.length">
|
|
905
|
+
Try:
|
|
906
|
+
<template x-for="(name, idx) in (examples?.evolution || [])" :key="name">
|
|
907
|
+
<a @click.prevent="forms.evolution.name = name; forms.evolution.show_diff = idx > 0; runEvolution()" x-text="name + (idx > 0 ? ' (+diffs)' : '')"></a>
|
|
908
|
+
</template>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
<div class="form-group">
|
|
912
|
+
<label>Time Range</label>
|
|
913
|
+
<select x-model="forms.evolution.time_range">
|
|
914
|
+
<option value="">All time</option>
|
|
915
|
+
<option value="last 7 days">Last 7 days</option>
|
|
916
|
+
<option value="last 14 days">Last 14 days</option>
|
|
917
|
+
<option value="last 30 days">Last 30 days</option>
|
|
918
|
+
<option value="last 3 months">Last 3 months</option>
|
|
919
|
+
<option value="last 6 months">Last 6 months</option>
|
|
920
|
+
</select>
|
|
921
|
+
</div>
|
|
922
|
+
<div class="form-group">
|
|
923
|
+
<label>Limit</label>
|
|
924
|
+
<input type="number" x-model.number="forms.evolution.limit" min="1" max="50" style="width:70px">
|
|
925
|
+
</div>
|
|
926
|
+
<div class="checkbox-row">
|
|
927
|
+
<input type="checkbox" id="evo-diff" x-model="forms.evolution.show_diff">
|
|
928
|
+
<label for="evo-diff">Show diffs</label>
|
|
929
|
+
</div>
|
|
930
|
+
<button class="btn btn-primary" @click="runEvolution()" :disabled="loading">Go</button>
|
|
931
|
+
</div>
|
|
932
|
+
</div>
|
|
933
|
+
<div class="results" x-show="results.evolution">
|
|
934
|
+
<div class="results-body markdown-body" x-html="safeHtml(results.evolution?.markdown)" x-effect="if(results.evolution?.markdown) $nextTick(() => renderMermaid($el))"></div>
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
937
|
+
</template>
|
|
938
|
+
|
|
939
|
+
<!-- ========= CHANGES ========= -->
|
|
940
|
+
<template x-if="activeTool === 'changes'">
|
|
941
|
+
<div>
|
|
942
|
+
<div class="tool-form">
|
|
943
|
+
<h3>Change Impact Analysis</h3>
|
|
944
|
+
<p class="tool-desc">Analyze a commit range to see which symbols were modified and who calls them, revealing ripple risk. Maps changed lines to their enclosing functions, then shows callers of each changed function so you can assess the blast radius. Use for code review, pre-merge safety checks, or to generate a list of things to test after a set of commits. Defaults to the last commit if no range is specified.</p>
|
|
945
|
+
<div class="form-row">
|
|
946
|
+
<div class="form-group flex-1">
|
|
947
|
+
<label>Commit Range</label>
|
|
948
|
+
<input type="text" x-model="forms.changes.commit_range" placeholder="e.g. HEAD~5..HEAD"
|
|
949
|
+
@keydown.enter="runChanges()">
|
|
950
|
+
<div class="input-hints">
|
|
951
|
+
Try:
|
|
952
|
+
<a @click.prevent="forms.changes.commit_range = 'HEAD~1..HEAD'; forms.changes.show_callers = true; runChanges()">last commit</a>
|
|
953
|
+
<a @click.prevent="forms.changes.commit_range = 'HEAD~5..HEAD'; forms.changes.show_callers = true; runChanges()">last 5</a>
|
|
954
|
+
<a @click.prevent="forms.changes.commit_range = 'HEAD~10..HEAD'; forms.changes.path_filter = ''; forms.changes.show_callers = true; runChanges()">last 10 commits</a>
|
|
955
|
+
</div>
|
|
956
|
+
</div>
|
|
957
|
+
<div class="form-group">
|
|
958
|
+
<label>Time Range</label>
|
|
959
|
+
<select x-model="forms.changes.time_range">
|
|
960
|
+
<option value="">All time</option>
|
|
961
|
+
<option value="last 7 days">Last 7 days</option>
|
|
962
|
+
<option value="last 14 days">Last 14 days</option>
|
|
963
|
+
<option value="last 30 days">Last 30 days</option>
|
|
964
|
+
<option value="last 3 months">Last 3 months</option>
|
|
965
|
+
<option value="last 6 months">Last 6 months</option>
|
|
966
|
+
</select>
|
|
967
|
+
</div>
|
|
968
|
+
<div class="form-group">
|
|
969
|
+
<label>Path Filter</label>
|
|
970
|
+
<input type="text" x-model="forms.changes.path_filter" placeholder="(optional)" style="width:150px">
|
|
971
|
+
</div>
|
|
972
|
+
<div class="checkbox-row">
|
|
973
|
+
<input type="checkbox" id="chg-callers" x-model="forms.changes.show_callers">
|
|
974
|
+
<label for="chg-callers">Show callers</label>
|
|
975
|
+
</div>
|
|
976
|
+
<button class="btn btn-primary" @click="runChanges()" :disabled="loading">Go</button>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="results" x-show="results.changes?.data">
|
|
980
|
+
<!-- Error -->
|
|
981
|
+
<div class="flow-error" x-show="results.changes?.data?.error" x-text="results.changes?.data?.error"></div>
|
|
982
|
+
<!-- Summary header -->
|
|
983
|
+
<template x-if="results.changes?.data && !results.changes?.data?.error">
|
|
984
|
+
<div>
|
|
985
|
+
<div class="changes-header">
|
|
986
|
+
<span class="changes-range" x-text="results.changes.data.range"></span>
|
|
987
|
+
<span class="changes-meta">
|
|
988
|
+
<span x-text="results.changes.data.commits + ' commit' + (results.changes.data.commits !== 1 ? 's' : '')"></span>
|
|
989
|
+
<span class="changes-sep">·</span>
|
|
990
|
+
<span x-text="results.changes.data.files_count + ' files'"></span>
|
|
991
|
+
<span class="changes-sep">·</span>
|
|
992
|
+
<span x-text="results.changes.data.symbols_count + ' symbols'"></span>
|
|
993
|
+
</span>
|
|
994
|
+
</div>
|
|
995
|
+
<!-- File-only fallback (no symbol attribution) -->
|
|
996
|
+
<template x-if="results.changes.data.changed_files && !results.changes.data.symbols">
|
|
997
|
+
<div class="changes-files">
|
|
998
|
+
<div class="changes-section-title">Changed Files</div>
|
|
999
|
+
<template x-for="f in results.changes.data.changed_files" :key="f.file">
|
|
1000
|
+
<div class="changes-file-row">
|
|
1001
|
+
<span class="changes-file-path" @click="forms.source.file = f.file; forms.source.line = null; setTool('source'); runSource()" x-text="f.file"></span>
|
|
1002
|
+
<span class="changes-diff-stat">
|
|
1003
|
+
<span class="changes-added" x-text="'+' + f.added"></span>
|
|
1004
|
+
<span class="changes-removed" x-text="'-' + f.removed"></span>
|
|
1005
|
+
</span>
|
|
1006
|
+
</div>
|
|
1007
|
+
</template>
|
|
1008
|
+
</div>
|
|
1009
|
+
</template>
|
|
1010
|
+
<!-- Symbol list -->
|
|
1011
|
+
<template x-if="results.changes.data.symbols">
|
|
1012
|
+
<div class="changes-symbols">
|
|
1013
|
+
<div class="changes-section-title" x-text="'Modified (' + results.changes.data.symbols.length + ')'"></div>
|
|
1014
|
+
<template x-for="(sym, idx) in results.changes.data.symbols" :key="sym.id">
|
|
1015
|
+
<div class="changes-symbol">
|
|
1016
|
+
<div class="changes-symbol-row">
|
|
1017
|
+
<span class="rc-type" :class="sym.type.toLowerCase()" x-text="sym.type"></span>
|
|
1018
|
+
<span class="changes-sym-name" @click="forms.context.node_id = sym.id; setTool('context'); runContext()" x-text="sym.name"></span>
|
|
1019
|
+
<span class="changes-diff-stat">
|
|
1020
|
+
<span class="changes-added" x-text="'+' + sym.added"></span>
|
|
1021
|
+
<span class="changes-removed" x-text="'-' + sym.removed"></span>
|
|
1022
|
+
</span>
|
|
1023
|
+
<span class="changes-sym-file" x-show="sym.file" @click="forms.source.file = sym.file; forms.source.line = sym.line; setTool('source'); runSource()" x-text="sym.file.replace(/^FILE:/, '') + (sym.line ? ':' + sym.line : '')"></span>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div class="changes-callers" x-show="sym.callers?.length">
|
|
1026
|
+
<span class="changes-callers-label">Callers:</span>
|
|
1027
|
+
<template x-for="c in (sym.callers || [])" :key="c.id">
|
|
1028
|
+
<span class="changes-caller" @click="forms.context.node_id = c.id; setTool('context'); runContext()" x-text="c.name"></span>
|
|
1029
|
+
</template>
|
|
1030
|
+
<span class="changes-callers-more" x-show="sym.callers_total > 5" x-text="'+' + (sym.callers_total - 5) + ' more'"></span>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
</template>
|
|
1034
|
+
</div>
|
|
1035
|
+
</template>
|
|
1036
|
+
</div>
|
|
1037
|
+
</template>
|
|
1038
|
+
</div>
|
|
1039
|
+
</div>
|
|
1040
|
+
</template>
|
|
1041
|
+
|
|
1042
|
+
<!-- ========= HEALTH ========= -->
|
|
1043
|
+
<template x-if="activeTool === 'health'">
|
|
1044
|
+
<div>
|
|
1045
|
+
<div class="tool-form">
|
|
1046
|
+
<h3>System Health</h3>
|
|
1047
|
+
<p class="tool-desc">Check the status of the descry server, including graph freshness, node/edge counts, and feature availability (SCIP type-aware resolution, semantic embeddings, git history, cross-language tracing). Use this to verify the server is running and diagnose issues. The graph age indicates when the index was last rebuilt.</p>
|
|
1048
|
+
<button class="btn btn-primary" @click="fetchHealth()" :disabled="loading">Refresh</button>
|
|
1049
|
+
</div>
|
|
1050
|
+
<div class="results" x-show="health">
|
|
1051
|
+
<div class="results-body">
|
|
1052
|
+
<pre style="white-space:pre-wrap;font-family:var(--font-mono);font-size:13px" x-text="JSON.stringify(health, null, 2)"></pre>
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
</template>
|
|
1057
|
+
|
|
1058
|
+
<!-- ========= REINDEX ========= -->
|
|
1059
|
+
<template x-if="activeTool === 'reindex'">
|
|
1060
|
+
<div>
|
|
1061
|
+
<div class="tool-form">
|
|
1062
|
+
<h3>Reindex Codebase</h3>
|
|
1063
|
+
<p class="tool-desc">Regenerate the full codebase graph, SCIP type-aware indices, and semantic search embeddings from scratch. Run this after significant code changes such as new files, refactoring, or renamed symbols to keep the graph accurate. Progress streams live to the output log below. This may take several minutes for large codebases, especially when SCIP compilation is enabled.</p>
|
|
1064
|
+
<div class="form-row">
|
|
1065
|
+
<button class="btn btn-primary" @click="runReindex()" :disabled="reindexing">
|
|
1066
|
+
<span x-show="!reindexing">Go</span>
|
|
1067
|
+
<span x-show="reindexing">Running...</span>
|
|
1068
|
+
</button>
|
|
1069
|
+
<span x-show="reindexing" style="color:var(--text-muted);font-size:12px;display:flex;align-items:center;gap:6px">
|
|
1070
|
+
<span class="spinner"></span>
|
|
1071
|
+
<span x-text="reindexElapsed"></span>
|
|
1072
|
+
</span>
|
|
1073
|
+
</div>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div class="reindex-output" x-show="reindexLines.length > 0">
|
|
1076
|
+
<div class="reindex-header">
|
|
1077
|
+
<span>Output</span>
|
|
1078
|
+
<span x-show="reindexStatus === 'success'" style="color:var(--green)">Completed</span>
|
|
1079
|
+
<span x-show="reindexStatus === 'error'" style="color:var(--red)">Failed</span>
|
|
1080
|
+
</div>
|
|
1081
|
+
<div class="reindex-log" x-ref="reindexLog">
|
|
1082
|
+
<template x-for="(line, i) in reindexLines" :key="i">
|
|
1083
|
+
<div class="reindex-line" :class="line.type" x-text="line.text"></div>
|
|
1084
|
+
</template>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
<div class="results" x-show="reindexGraph">
|
|
1088
|
+
<div class="results-header">Updated Graph Status</div>
|
|
1089
|
+
<div class="results-body">
|
|
1090
|
+
<pre style="white-space:pre-wrap;font-family:var(--font-mono);font-size:13px" x-text="JSON.stringify(reindexGraph, null, 2)"></pre>
|
|
1091
|
+
</div>
|
|
1092
|
+
</div>
|
|
1093
|
+
</div>
|
|
1094
|
+
</template>
|
|
1095
|
+
|
|
1096
|
+
<!-- ========= SOURCE VIEWER ========= -->
|
|
1097
|
+
<template x-if="activeTool === 'source'">
|
|
1098
|
+
<div>
|
|
1099
|
+
<div class="tool-form">
|
|
1100
|
+
<h3>Source Viewer</h3>
|
|
1101
|
+
<p class="tool-desc">Read source files directly in the browser with line numbers. Optionally jump to a specific line which will be highlighted. Reached automatically when clicking file locations in other tools, or use directly to browse any project file. Paths are relative to the project root.</p>
|
|
1102
|
+
<div class="form-row">
|
|
1103
|
+
<div class="form-group flex-1">
|
|
1104
|
+
<label>File Path</label>
|
|
1105
|
+
<input type="text" x-model="forms.source.file" placeholder="e.g. src/handlers.py"
|
|
1106
|
+
@keydown.enter="runSource()">
|
|
1107
|
+
<div class="input-hints" x-show="examples?.structure?.length">
|
|
1108
|
+
Try:
|
|
1109
|
+
<template x-for="name in (examples?.structure || []).slice(0, 2)" :key="name">
|
|
1110
|
+
<a @click.prevent="forms.source.file = name; forms.source.line = 1; runSource()" x-text="name.split('/').pop() + ':1'"></a>
|
|
1111
|
+
</template>
|
|
1112
|
+
</div>
|
|
1113
|
+
</div>
|
|
1114
|
+
<div class="form-group">
|
|
1115
|
+
<label>Line</label>
|
|
1116
|
+
<input type="number" x-model.number="forms.source.line" placeholder="(optional)" style="width:80px">
|
|
1117
|
+
</div>
|
|
1118
|
+
<button class="btn btn-primary" @click="runSource()" :disabled="loading">Go</button>
|
|
1119
|
+
</div>
|
|
1120
|
+
</div>
|
|
1121
|
+
<div class="source-viewer" x-show="results.source">
|
|
1122
|
+
<div class="source-header">
|
|
1123
|
+
<span x-text="results.source?.file + ' (' + results.source?.total_lines + ' lines)'"></span>
|
|
1124
|
+
<span x-text="results.source?.language"></span>
|
|
1125
|
+
<template x-if="results.source?.language === 'markdown'">
|
|
1126
|
+
<button class="btn btn-sm" style="margin-left:auto;font-size:12px;padding:2px 8px"
|
|
1127
|
+
@click="sourceRenderMd = !sourceRenderMd"
|
|
1128
|
+
x-text="sourceRenderMd ? 'View Raw' : 'Render Markdown'"></button>
|
|
1129
|
+
</template>
|
|
1130
|
+
</div>
|
|
1131
|
+
<!-- Rendered markdown view for .md files -->
|
|
1132
|
+
<div class="source-markdown markdown-body" x-show="results.source?.language === 'markdown' && sourceRenderMd"
|
|
1133
|
+
x-html="safeHtml(results.source?.content || '')"
|
|
1134
|
+
x-effect="if(results.source?.language === 'markdown' && sourceRenderMd) $nextTick(() => renderMermaid($el))">
|
|
1135
|
+
</div>
|
|
1136
|
+
<!-- Raw source view (default for code, toggle for markdown) -->
|
|
1137
|
+
<div class="source-content" x-show="results.source?.language !== 'markdown' || !sourceRenderMd">
|
|
1138
|
+
<table>
|
|
1139
|
+
<template x-for="(line, idx) in (results.source?.content || '').split('\\n')" :key="idx">
|
|
1140
|
+
<tr :class="(results.source?.start_line + idx) === results.source?.target_line && 'highlight'">
|
|
1141
|
+
<td class="line-number" x-text="results.source?.start_line + idx"></td>
|
|
1142
|
+
<td class="line-code" x-text="line"></td>
|
|
1143
|
+
</tr>
|
|
1144
|
+
</template>
|
|
1145
|
+
</table>
|
|
1146
|
+
</div>
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
</template>
|
|
1150
|
+
|
|
1151
|
+
<!-- Loading Overlay -->
|
|
1152
|
+
<div class="loading" x-show="loading">
|
|
1153
|
+
<div class="spinner"></div>
|
|
1154
|
+
<span>Loading...</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
|
|
1157
|
+
<!-- Welcome Screen (Home) -->
|
|
1158
|
+
<div class="welcome-screen" x-show="activeTool === 'home'">
|
|
1159
|
+
<div class="es-icon">🔍</div>
|
|
1160
|
+
<div class="es-title">Descry Explorer</div>
|
|
1161
|
+
<div class="es-subtitle">Search symbols, trace call flows, and navigate your codebase</div>
|
|
1162
|
+
<div class="welcome-scenarios">
|
|
1163
|
+
<div class="scenario-card" @click="forms.search.terms = ex('search', 0, 'main'); setTool('search'); runSearch();">
|
|
1164
|
+
<div class="sc-title">Find a function</div>
|
|
1165
|
+
<div class="sc-desc">Search for <code x-text="ex('search', 0, 'main')"></code> across the codebase</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
<div class="scenario-card" @click="forms.quick.name = ex('quick', 0, 'init'); setTool('quick'); runQuick();">
|
|
1168
|
+
<div class="sc-title">Quick lookup</div>
|
|
1169
|
+
<div class="sc-desc">Get full details on <code x-text="ex('quick', 0, 'init')"></code> in a single step</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
<div class="scenario-card" @click="forms.flow.start = ex('flow_forward', null, 'main'); forms.flow.direction = 'forward'; forms.flow.depth = 3; setTool('flow'); runFlow();">
|
|
1172
|
+
<div class="sc-title">Trace a call flow</div>
|
|
1173
|
+
<div class="sc-desc">Follow <code x-text="ex('flow_forward', null, 'main')"></code> forward through the call graph</div>
|
|
1174
|
+
</div>
|
|
1175
|
+
<div class="scenario-card" @click="forms.churn.mode = 'symbols'; forms.churn.time_range = 'last 30 days'; forms.churn.path_filter = ''; setTool('churn'); runChurn();">
|
|
1176
|
+
<div class="sc-title">Find churn hotspots</div>
|
|
1177
|
+
<div class="sc-desc">Most-changed symbols over last 30 days</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
<div class="scenario-card" @click="forms.changes.commit_range = 'HEAD~5..HEAD'; forms.changes.show_callers = true; setTool('changes'); runChanges();">
|
|
1180
|
+
<div class="sc-title">Review recent changes</div>
|
|
1181
|
+
<div class="sc-desc">Impact analysis of the last 5 commits</div>
|
|
1182
|
+
</div>
|
|
1183
|
+
<div class="scenario-card" @click="forms.structure.filename = ex('structure', 0, 'handlers.py'); setTool('structure'); runStructure();">
|
|
1184
|
+
<div class="sc-title">Explore a file</div>
|
|
1185
|
+
<div class="sc-desc">See the structure of <code x-text="ex('structure', 0, 'handlers.py').split('/').pop()"></code></div>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
|
|
1190
|
+
</div>
|
|
1191
|
+
</div>
|
|
1192
|
+
|
|
1193
|
+
<script>
|
|
1194
|
+
function app() {
|
|
1195
|
+
return {
|
|
1196
|
+
// State
|
|
1197
|
+
activeTool: 'home',
|
|
1198
|
+
showHelp: false,
|
|
1199
|
+
globalSearch: '',
|
|
1200
|
+
loading: false,
|
|
1201
|
+
error: null,
|
|
1202
|
+
health: null,
|
|
1203
|
+
reindexing: false,
|
|
1204
|
+
reindexLines: [],
|
|
1205
|
+
reindexStatus: null,
|
|
1206
|
+
reindexGraph: null,
|
|
1207
|
+
sourceRenderMd: true, // Default to rendered view for markdown files
|
|
1208
|
+
reindexElapsed: '',
|
|
1209
|
+
reindexTimer: null,
|
|
1210
|
+
breadcrumbs: [{label: 'Home', tool: 'home'}],
|
|
1211
|
+
results: {},
|
|
1212
|
+
|
|
1213
|
+
// Dynamic examples from graph (populated on init)
|
|
1214
|
+
examples: null,
|
|
1215
|
+
|
|
1216
|
+
// Encyclopedia state
|
|
1217
|
+
helpSearch: '',
|
|
1218
|
+
helpArticle: null,
|
|
1219
|
+
helpArticles: [
|
|
1220
|
+
// Tools
|
|
1221
|
+
{ slug: 'search', title: 'Search', category: 'tools', desc: 'Hybrid keyword + semantic symbol search', keywords: ['keyword', 'filter', 'regex', 'find', 'symbol'] },
|
|
1222
|
+
{ slug: 'semantic', title: 'Semantic Search', category: 'tools', desc: 'Vector similarity search by meaning', keywords: ['embedding', 'vector', 'natural language', 'meaning', 'conceptual'] },
|
|
1223
|
+
{ slug: 'quick', title: 'Quick Lookup', category: 'tools', desc: 'Full details on a symbol in one step', keywords: ['symbol', 'lookup', 'details', 'fast'] },
|
|
1224
|
+
{ slug: 'callers', title: 'Callers', category: 'tools', desc: 'Who calls this function', keywords: ['call', 'reference', 'impact', 'upstream', 'who calls'] },
|
|
1225
|
+
{ slug: 'callees', title: 'Callees', category: 'tools', desc: 'What this function calls', keywords: ['dependency', 'downstream', 'calls', 'what calls'] },
|
|
1226
|
+
{ slug: 'context', title: 'Context', category: 'tools', desc: 'Full node dossier: source, callers, callees', keywords: ['details', 'source', 'node', 'dossier'] },
|
|
1227
|
+
{ slug: 'structure', title: 'Structure', category: 'tools', desc: 'File skeleton with all symbols', keywords: ['file', 'skeleton', 'outline', 'symbols', 'imports'] },
|
|
1228
|
+
{ slug: 'flatten', title: 'Flatten', category: 'tools', desc: 'Inheritance and trait hierarchy', keywords: ['inheritance', 'trait', 'interface', 'hierarchy', 'class'] },
|
|
1229
|
+
{ slug: 'impls', title: 'Implementations', category: 'tools', desc: 'Find all implementations of a method', keywords: ['trait', 'interface', 'implement', 'polymorphic', 'concrete'] },
|
|
1230
|
+
{ slug: 'flow', title: 'Flow Trace', category: 'tools', desc: 'Forward/backward call tree visualization', keywords: ['trace', 'call tree', 'dataflow', 'forward', 'backward'] },
|
|
1231
|
+
{ slug: 'path', title: 'Call Path', category: 'tools', desc: 'Shortest call chain between two symbols', keywords: ['path', 'route', 'chain', 'between', 'connection'] },
|
|
1232
|
+
{ slug: 'cross-lang', title: 'Cross-Language', category: 'tools', desc: 'Frontend-to-backend API call tracing', keywords: ['api', 'frontend', 'backend', 'openapi', 'endpoint', 'cross'] },
|
|
1233
|
+
{ slug: 'churn', title: 'Churn', category: 'tools', desc: 'Most-changed code from git history', keywords: ['hotspot', 'frequency', 'change', 'git', 'coupling'] },
|
|
1234
|
+
{ slug: 'evolution', title: 'Evolution', category: 'tools', desc: 'Commit timeline for a symbol or file', keywords: ['timeline', 'history', 'commits', 'diff', 'author'] },
|
|
1235
|
+
{ slug: 'changes', title: 'Changes', category: 'tools', desc: 'Impact analysis of recent commits', keywords: ['impact', 'commit', 'blast radius', 'review', 'callers'] },
|
|
1236
|
+
{ slug: 'health', title: 'Health', category: 'tools', desc: 'Graph status and feature availability', keywords: ['status', 'graph', 'nodes', 'edges', 'stale'] },
|
|
1237
|
+
{ slug: 'reindex', title: 'Reindex', category: 'tools', desc: 'Rebuild the knowledge graph', keywords: ['rebuild', 'regenerate', 'refresh', 'index'] },
|
|
1238
|
+
{ slug: 'source', title: 'Source', category: 'tools', desc: 'View source files with line numbers', keywords: ['file', 'code', 'read', 'view', 'line'] },
|
|
1239
|
+
// Concepts
|
|
1240
|
+
{ slug: 'concept-graph', title: 'The Knowledge Graph', category: 'concepts', desc: 'How nodes and edges model your code', keywords: ['graph', 'node', 'edge', 'model', 'architecture'] },
|
|
1241
|
+
{ slug: 'concept-symbols', title: 'Symbol Types', category: 'concepts', desc: 'FUN, MET, CLA, CON, FIL, CFG explained', keywords: ['type', 'badge', 'legend', 'function', 'method', 'class'] },
|
|
1242
|
+
{ slug: 'concept-scip', title: 'SCIP Indexing', category: 'concepts', desc: 'Precise cross-references via compiler data', keywords: ['scip', 'compiler', 'type-aware', 'precise', 'resolution'] },
|
|
1243
|
+
{ slug: 'concept-embeddings', title: 'Semantic Embeddings', category: 'concepts', desc: 'How vector search finds similar code', keywords: ['vector', 'embedding', 'similarity', 'model', 'cosine'] },
|
|
1244
|
+
{ slug: 'concept-git', title: 'Git History Analysis', category: 'concepts', desc: 'How descry uses git log for insights', keywords: ['git', 'log', 'blame', 'churn', 'history'] },
|
|
1245
|
+
// Workflows
|
|
1246
|
+
{ slug: 'workflow-investigate', title: 'Investigating a Function', category: 'workflows', desc: 'Start with a name, expand outward', keywords: ['investigate', 'function', 'deep dive', 'understand'] },
|
|
1247
|
+
{ slug: 'workflow-explore', title: 'Understanding a Codebase', category: 'workflows', desc: 'Get the lay of the land', keywords: ['explore', 'codebase', 'orientation', 'new project'] },
|
|
1248
|
+
{ slug: 'workflow-changes', title: 'Tracking Recent Changes', category: 'workflows', desc: 'See what has been happening', keywords: ['recent', 'changes', 'review', 'commits', 'impact'] },
|
|
1249
|
+
],
|
|
1250
|
+
|
|
1251
|
+
// Form state per tool
|
|
1252
|
+
forms: {
|
|
1253
|
+
search: { terms: '', limit: 10, lang: '', crate: '', type: '' },
|
|
1254
|
+
semantic: { query: '', limit: 10 },
|
|
1255
|
+
quick: { name: '', full: false },
|
|
1256
|
+
callers: { name: '', limit: 20 },
|
|
1257
|
+
callees: { name: '', limit: 20 },
|
|
1258
|
+
context: { node_id: '', full: false, expand_callees: false },
|
|
1259
|
+
structure: { filename: '' },
|
|
1260
|
+
flatten: { class_node_id: '' },
|
|
1261
|
+
impls: { method: '', trait_name: '' },
|
|
1262
|
+
flow: { start: '', direction: 'forward', depth: 3, target: '' },
|
|
1263
|
+
path: { start: '', end: '', max_depth: 10 },
|
|
1264
|
+
crossLang: { mode: 'endpoint', method: 'GET', path: '', tag: '' },
|
|
1265
|
+
churn: { mode: 'symbols', time_range: '', path_filter: '', limit: 20 },
|
|
1266
|
+
evolution: { name: '', time_range: '', limit: 10, show_diff: false },
|
|
1267
|
+
changes: { commit_range: 'HEAD~5..HEAD', time_range: '', path_filter: '', show_callers: true },
|
|
1268
|
+
source: { file: '', line: null },
|
|
1269
|
+
},
|
|
1270
|
+
|
|
1271
|
+
async init() {
|
|
1272
|
+
await this.fetchHealth();
|
|
1273
|
+
await this.fetchExamples();
|
|
1274
|
+
},
|
|
1275
|
+
|
|
1276
|
+
async fetchExamples() {
|
|
1277
|
+
try {
|
|
1278
|
+
const r = await fetch('/api/examples');
|
|
1279
|
+
const data = await r.json();
|
|
1280
|
+
if (data.available) this.examples = data;
|
|
1281
|
+
} catch(e) { /* silent — examples are optional */ }
|
|
1282
|
+
},
|
|
1283
|
+
|
|
1284
|
+
// Helper: get example value with fallback.
|
|
1285
|
+
// For arrays: ex('search', 0, 'main') returns examples.search[0]
|
|
1286
|
+
// For scalars: ex('flow_forward', null, 'main') returns examples.flow_forward
|
|
1287
|
+
ex(category, index, fallback) {
|
|
1288
|
+
if (!this.examples || this.examples[category] === undefined) return fallback || '';
|
|
1289
|
+
const val = this.examples[category];
|
|
1290
|
+
if (index === null || index === undefined) return val || fallback || '';
|
|
1291
|
+
if (Array.isArray(val) && val[index] !== undefined) return val[index];
|
|
1292
|
+
return fallback || '';
|
|
1293
|
+
},
|
|
1294
|
+
|
|
1295
|
+
openHelp() {
|
|
1296
|
+
this.helpSearch = '';
|
|
1297
|
+
const toolMatch = this.helpArticles.find(a => a.slug === this.activeTool);
|
|
1298
|
+
this.helpArticle = toolMatch ? this.activeTool : null;
|
|
1299
|
+
this.showHelp = true;
|
|
1300
|
+
},
|
|
1301
|
+
|
|
1302
|
+
openHelpTOC() {
|
|
1303
|
+
this.helpSearch = '';
|
|
1304
|
+
this.helpArticle = null;
|
|
1305
|
+
this.showHelp = true;
|
|
1306
|
+
},
|
|
1307
|
+
|
|
1308
|
+
filteredHelpArticles() {
|
|
1309
|
+
if (!this.helpSearch.trim()) return this.helpArticles;
|
|
1310
|
+
const q = this.helpSearch.toLowerCase();
|
|
1311
|
+
return this.helpArticles.filter(a =>
|
|
1312
|
+
a.title.toLowerCase().includes(q) ||
|
|
1313
|
+
a.desc.toLowerCase().includes(q) ||
|
|
1314
|
+
a.keywords.some(k => k.includes(q))
|
|
1315
|
+
);
|
|
1316
|
+
},
|
|
1317
|
+
|
|
1318
|
+
goHome() {
|
|
1319
|
+
this.activeTool = 'home';
|
|
1320
|
+
this.error = null;
|
|
1321
|
+
this.results = {};
|
|
1322
|
+
this.breadcrumbs = [{label: 'Home', tool: 'home'}];
|
|
1323
|
+
},
|
|
1324
|
+
|
|
1325
|
+
setTool(tool) {
|
|
1326
|
+
this.activeTool = tool;
|
|
1327
|
+
this.error = null;
|
|
1328
|
+
this.breadcrumbs = [{label: this.toolLabel(tool), tool}];
|
|
1329
|
+
},
|
|
1330
|
+
|
|
1331
|
+
toolLabel(tool) {
|
|
1332
|
+
const labels = {
|
|
1333
|
+
home: 'Home',
|
|
1334
|
+
search: 'Search', semantic: 'Semantic', quick: 'Quick Lookup',
|
|
1335
|
+
callers: 'Callers', callees: 'Callees', context: 'Context',
|
|
1336
|
+
structure: 'Structure', flatten: 'Flatten', impls: 'Impls',
|
|
1337
|
+
flow: 'Flow Trace', path: 'Call Path', 'cross-lang': 'Cross-Lang',
|
|
1338
|
+
churn: 'Churn', evolution: 'Evolution', changes: 'Changes',
|
|
1339
|
+
health: 'Health', reindex: 'Reindex', source: 'Source',
|
|
1340
|
+
};
|
|
1341
|
+
return labels[tool] || tool;
|
|
1342
|
+
},
|
|
1343
|
+
|
|
1344
|
+
typeClass(type) {
|
|
1345
|
+
if (!type) return '';
|
|
1346
|
+
const map = {
|
|
1347
|
+
'function': 'fun', 'method': 'met', 'class': 'cla',
|
|
1348
|
+
'constant': 'con', 'file': 'fil', 'configuration': 'cfg'
|
|
1349
|
+
};
|
|
1350
|
+
return map[type.toLowerCase()] || '';
|
|
1351
|
+
},
|
|
1352
|
+
|
|
1353
|
+
typeLabel(type) {
|
|
1354
|
+
const labels = {
|
|
1355
|
+
'function': 'FUN', 'method': 'MET', 'class': 'CLA',
|
|
1356
|
+
'constant': 'CON', 'file': 'FIL', 'configuration': 'CFG'
|
|
1357
|
+
};
|
|
1358
|
+
return labels[type?.toLowerCase()] || type?.substring(0,3)?.toUpperCase() || '';
|
|
1359
|
+
},
|
|
1360
|
+
|
|
1361
|
+
hasResults() {
|
|
1362
|
+
return Object.keys(this.results).some(k => this.results[k]);
|
|
1363
|
+
},
|
|
1364
|
+
|
|
1365
|
+
pushBreadcrumb(label, tool, params) {
|
|
1366
|
+
this.breadcrumbs.push({label, tool, params});
|
|
1367
|
+
},
|
|
1368
|
+
|
|
1369
|
+
navigateTo(idx) {
|
|
1370
|
+
const crumb = this.breadcrumbs[idx];
|
|
1371
|
+
this.breadcrumbs = this.breadcrumbs.slice(0, idx + 1);
|
|
1372
|
+
this.activeTool = crumb.tool;
|
|
1373
|
+
if (crumb.params) {
|
|
1374
|
+
Object.assign(this.forms[crumb.tool] || {}, crumb.params);
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
|
|
1378
|
+
// Navigation helpers
|
|
1379
|
+
viewContext(nodeId) {
|
|
1380
|
+
this.forms.context.node_id = nodeId;
|
|
1381
|
+
this.activeTool = 'context';
|
|
1382
|
+
const shortName = nodeId.split('::').pop() || nodeId;
|
|
1383
|
+
this.pushBreadcrumb(shortName, 'context', {node_id: nodeId});
|
|
1384
|
+
this.runContext();
|
|
1385
|
+
},
|
|
1386
|
+
|
|
1387
|
+
viewSource(file, line) {
|
|
1388
|
+
this.forms.source.file = file;
|
|
1389
|
+
this.forms.source.line = line || null;
|
|
1390
|
+
this.activeTool = 'source';
|
|
1391
|
+
const shortFile = file.split('/').pop();
|
|
1392
|
+
this.pushBreadcrumb(shortFile + (line ? ':' + line : ''), 'source', {file, line});
|
|
1393
|
+
this.runSource();
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
viewCallers(name) {
|
|
1397
|
+
this.forms.callers.name = name;
|
|
1398
|
+
this.activeTool = 'callers';
|
|
1399
|
+
this.pushBreadcrumb('callers(' + name + ')', 'callers', {name});
|
|
1400
|
+
this.runCallers();
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
viewCallees(name) {
|
|
1404
|
+
this.forms.callees.name = name;
|
|
1405
|
+
this.activeTool = 'callees';
|
|
1406
|
+
this.pushBreadcrumb('callees(' + name + ')', 'callees', {name});
|
|
1407
|
+
this.runCallees();
|
|
1408
|
+
},
|
|
1409
|
+
|
|
1410
|
+
// DOMPurify config: tags and attributes allowed for rendered markdown + SVG (mermaid diagrams)
|
|
1411
|
+
_purifyConfig: {
|
|
1412
|
+
ADD_TAGS: ['code', 'pre', 'span', 'svg', 'g', 'path', 'rect', 'text', 'tspan',
|
|
1413
|
+
'line', 'circle', 'polygon', 'polyline', 'marker', 'defs', 'style',
|
|
1414
|
+
'foreignObject', 'div', 'br', 'p', 'ul', 'ol', 'li', 'table', 'tr',
|
|
1415
|
+
'td', 'th', 'thead', 'tbody', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
1416
|
+
'a', 'strong', 'em', 'blockquote', 'hr', 'img', 'dl', 'dt', 'dd'],
|
|
1417
|
+
ADD_ATTR: ['class', 'id', 'viewBox', 'xmlns', 'd', 'fill', 'stroke',
|
|
1418
|
+
'stroke-width', 'transform', 'x', 'y', 'width', 'height',
|
|
1419
|
+
'style', 'href', 'src', 'alt', 'text-anchor', 'dominant-baseline',
|
|
1420
|
+
'font-size', 'font-family', 'marker-end', 'points', 'rx', 'ry',
|
|
1421
|
+
'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2'],
|
|
1422
|
+
},
|
|
1423
|
+
|
|
1424
|
+
// Safe HTML rendering: markdown -> HTML with DOMPurify sanitization + syntax highlighting.
|
|
1425
|
+
// Mermaid code blocks are converted to <pre class="mermaid"> for post-render processing
|
|
1426
|
+
// via renderMermaid(). All HTML is sanitized through DOMPurify before insertion.
|
|
1427
|
+
safeHtml(md) {
|
|
1428
|
+
if (!md) return '';
|
|
1429
|
+
try {
|
|
1430
|
+
const rawHtml = marked.parse(md);
|
|
1431
|
+
// First sanitize pass: clean the raw markdown-generated HTML
|
|
1432
|
+
const div = document.createElement('div');
|
|
1433
|
+
div.textContent = '';
|
|
1434
|
+
div.appendChild(
|
|
1435
|
+
DOMPurify.sanitize(rawHtml, { ...this._purifyConfig, RETURN_DOM_FRAGMENT: true })
|
|
1436
|
+
);
|
|
1437
|
+
// Convert mermaid code blocks into <pre class="mermaid"> for Mermaid.js
|
|
1438
|
+
div.querySelectorAll('pre code.language-mermaid').forEach(block => {
|
|
1439
|
+
const pre = block.parentElement;
|
|
1440
|
+
const mermaidPre = document.createElement('pre');
|
|
1441
|
+
mermaidPre.className = 'mermaid';
|
|
1442
|
+
mermaidPre.textContent = block.textContent;
|
|
1443
|
+
pre.replaceWith(mermaidPre);
|
|
1444
|
+
});
|
|
1445
|
+
// Apply syntax highlighting to remaining (non-mermaid) code blocks
|
|
1446
|
+
div.querySelectorAll('pre code').forEach(block => {
|
|
1447
|
+
hljs.highlightElement(block);
|
|
1448
|
+
});
|
|
1449
|
+
// Final sanitize pass on the highlighted output
|
|
1450
|
+
return DOMPurify.sanitize(div.innerHTML, this._purifyConfig);
|
|
1451
|
+
} catch (e) {
|
|
1452
|
+
// Fallback: escape and show as preformatted text
|
|
1453
|
+
const escaped = md.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1454
|
+
return '<pre>' + escaped + '</pre>';
|
|
1455
|
+
}
|
|
1456
|
+
},
|
|
1457
|
+
|
|
1458
|
+
// Render mermaid diagrams within a container element (called via x-effect after DOM update).
|
|
1459
|
+
// Uses mermaid.render() which returns sanitized SVG - inserted via DOMPurify as a safety layer.
|
|
1460
|
+
async renderMermaid(el) {
|
|
1461
|
+
if (!el || typeof mermaid === 'undefined') return;
|
|
1462
|
+
const blocks = el.querySelectorAll('pre.mermaid:not([data-processed])');
|
|
1463
|
+
for (const block of blocks) {
|
|
1464
|
+
block.setAttribute('data-processed', 'true');
|
|
1465
|
+
try {
|
|
1466
|
+
const id = 'mermaid-' + Math.random().toString(36).substring(2, 9);
|
|
1467
|
+
const { svg } = await mermaid.render(id, block.textContent);
|
|
1468
|
+
// Mermaid.render returns SVG - sanitize before inserting
|
|
1469
|
+
const clean = DOMPurify.sanitize(svg, this._purifyConfig);
|
|
1470
|
+
block.textContent = '';
|
|
1471
|
+
block.appendChild(
|
|
1472
|
+
DOMPurify.sanitize(svg, { ...this._purifyConfig, RETURN_DOM_FRAGMENT: true })
|
|
1473
|
+
);
|
|
1474
|
+
block.classList.add('mermaid-rendered');
|
|
1475
|
+
} catch (e) {
|
|
1476
|
+
// If mermaid fails, keep the raw text visible as a code block
|
|
1477
|
+
block.classList.add('mermaid-error');
|
|
1478
|
+
console.warn('Mermaid render failed:', e.message);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
|
|
1483
|
+
// --- Flow Tree Rendering ---
|
|
1484
|
+
flowExpandAll: true,
|
|
1485
|
+
|
|
1486
|
+
renderFlowTree(node, depth) {
|
|
1487
|
+
if (!node) return '<div class="flow-empty">No results found.</div>';
|
|
1488
|
+
this.flowExpandAll = true;
|
|
1489
|
+
return this._renderNode(node, depth);
|
|
1490
|
+
},
|
|
1491
|
+
|
|
1492
|
+
_renderNode(node, depth) {
|
|
1493
|
+
const typeClass = node.type.toLowerCase();
|
|
1494
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
1495
|
+
const hasCode = !!node.code;
|
|
1496
|
+
const expandable = hasChildren || hasCode;
|
|
1497
|
+
|
|
1498
|
+
// Shorten file path for display
|
|
1499
|
+
const shortFile = node.file
|
|
1500
|
+
? node.file.replace(/^FILE:/, '')
|
|
1501
|
+
: '';
|
|
1502
|
+
const fileLoc = shortFile + (node.line ? ':' + node.line : '');
|
|
1503
|
+
|
|
1504
|
+
// Escape code for safe embedding
|
|
1505
|
+
const escapedCode = node.code
|
|
1506
|
+
? node.code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
1507
|
+
: '';
|
|
1508
|
+
|
|
1509
|
+
// Escape data attributes
|
|
1510
|
+
const safeId = node.id.replace(/"/g, '"');
|
|
1511
|
+
const safeFile = (node.file || '').replace(/"/g, '"');
|
|
1512
|
+
|
|
1513
|
+
let html = `<div class="flow-node ${depth === 0 ? 'flow-root' : ''}" data-depth="${depth}">`;
|
|
1514
|
+
|
|
1515
|
+
// Node row - clickable to toggle
|
|
1516
|
+
html += `<div class="flow-node-row">`;
|
|
1517
|
+
|
|
1518
|
+
// Expand/collapse toggle
|
|
1519
|
+
if (expandable) {
|
|
1520
|
+
html += `<span class="flow-toggle" data-action="toggle">▾</span>`;
|
|
1521
|
+
} else {
|
|
1522
|
+
html += `<span class="flow-toggle flow-toggle-leaf">•</span>`;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// Type badge
|
|
1526
|
+
html += `<span class="rc-type ${typeClass}">${node.type}</span>`;
|
|
1527
|
+
|
|
1528
|
+
// Function name - navigates to Context
|
|
1529
|
+
html += `<span class="flow-fn-name" data-action="context" data-node-id="${safeId}">${node.name}</span>`;
|
|
1530
|
+
|
|
1531
|
+
// Token count
|
|
1532
|
+
html += `<span class="flow-tokens">${node.tokens.toLocaleString()} toks</span>`;
|
|
1533
|
+
|
|
1534
|
+
// File location - navigates to Source
|
|
1535
|
+
if (fileLoc) {
|
|
1536
|
+
html += `<span class="flow-file" data-action="source" data-file="${safeFile}" data-line="${node.line || ''}">${fileLoc}</span>`;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
html += `</div>`;
|
|
1540
|
+
|
|
1541
|
+
// Inline code block (for small functions)
|
|
1542
|
+
if (hasCode) {
|
|
1543
|
+
html += `<div class="flow-code">`;
|
|
1544
|
+
html += `<pre><code class="language-${node.lang || 'rust'}">${escapedCode}</code></pre>`;
|
|
1545
|
+
html += `</div>`;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Children container
|
|
1549
|
+
if (hasChildren) {
|
|
1550
|
+
html += `<div class="flow-children">`;
|
|
1551
|
+
node.children.forEach((child) => {
|
|
1552
|
+
html += this._renderNode(child, depth + 1);
|
|
1553
|
+
});
|
|
1554
|
+
html += `</div>`;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
html += `</div>`;
|
|
1558
|
+
return html;
|
|
1559
|
+
},
|
|
1560
|
+
|
|
1561
|
+
// Event delegation handler for flow tree clicks
|
|
1562
|
+
handleFlowClick(e) {
|
|
1563
|
+
const target = e.target.closest('[data-action]');
|
|
1564
|
+
if (!target) {
|
|
1565
|
+
// Click on row itself toggles collapse
|
|
1566
|
+
const row = e.target.closest('.flow-node-row');
|
|
1567
|
+
if (row) {
|
|
1568
|
+
row.closest('.flow-node').classList.toggle('collapsed');
|
|
1569
|
+
}
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
const action = target.dataset.action;
|
|
1574
|
+
if (action === 'toggle') {
|
|
1575
|
+
target.closest('.flow-node').classList.toggle('collapsed');
|
|
1576
|
+
} else if (action === 'context') {
|
|
1577
|
+
e.stopPropagation();
|
|
1578
|
+
this.forms.context.node_id = target.dataset.nodeId;
|
|
1579
|
+
this.setTool('context');
|
|
1580
|
+
this.runContext();
|
|
1581
|
+
} else if (action === 'source') {
|
|
1582
|
+
e.stopPropagation();
|
|
1583
|
+
this.forms.source.file = target.dataset.file;
|
|
1584
|
+
this.forms.source.line = target.dataset.line ? parseInt(target.dataset.line) : null;
|
|
1585
|
+
this.setTool('source');
|
|
1586
|
+
this.runSource();
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
|
|
1590
|
+
highlightFlowCode(el) {
|
|
1591
|
+
if (!el) return;
|
|
1592
|
+
el.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
1593
|
+
hljs.highlightElement(block);
|
|
1594
|
+
block.setAttribute('data-highlighted', 'true');
|
|
1595
|
+
});
|
|
1596
|
+
},
|
|
1597
|
+
|
|
1598
|
+
flowToggleAll() {
|
|
1599
|
+
this.flowExpandAll = !this.flowExpandAll;
|
|
1600
|
+
const tree = document.querySelector('.flow-tree');
|
|
1601
|
+
if (!tree) return;
|
|
1602
|
+
tree.querySelectorAll('.flow-node').forEach(node => {
|
|
1603
|
+
if (this.flowExpandAll) {
|
|
1604
|
+
node.classList.remove('collapsed');
|
|
1605
|
+
} else {
|
|
1606
|
+
node.classList.add('collapsed');
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
},
|
|
1610
|
+
|
|
1611
|
+
// API calls
|
|
1612
|
+
async api(url, opts = {}) {
|
|
1613
|
+
this.loading = true;
|
|
1614
|
+
this.error = null;
|
|
1615
|
+
try {
|
|
1616
|
+
const res = await fetch(url, opts);
|
|
1617
|
+
const data = await res.json();
|
|
1618
|
+
if (!res.ok) {
|
|
1619
|
+
this.error = data.error || 'HTTP ' + res.status;
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
return data;
|
|
1623
|
+
} catch (e) {
|
|
1624
|
+
this.error = e.message;
|
|
1625
|
+
return null;
|
|
1626
|
+
} finally {
|
|
1627
|
+
this.loading = false;
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1631
|
+
async fetchHealth() {
|
|
1632
|
+
this.health = await this.api('/api/health');
|
|
1633
|
+
},
|
|
1634
|
+
|
|
1635
|
+
doGlobalSearch() {
|
|
1636
|
+
if (!this.globalSearch.trim()) return;
|
|
1637
|
+
this.forms.search.terms = this.globalSearch;
|
|
1638
|
+
this.setTool('search');
|
|
1639
|
+
this.runSearch();
|
|
1640
|
+
},
|
|
1641
|
+
|
|
1642
|
+
async runSearch() {
|
|
1643
|
+
const f = this.forms.search;
|
|
1644
|
+
if (!f.terms.trim()) return;
|
|
1645
|
+
const p = new URLSearchParams({terms: f.terms, limit: f.limit});
|
|
1646
|
+
if (f.lang) p.set('lang', f.lang);
|
|
1647
|
+
if (f.crate) p.set('crate', f.crate);
|
|
1648
|
+
if (f.type) p.set('type', f.type);
|
|
1649
|
+
this.results.search = await this.api('/api/search?' + p);
|
|
1650
|
+
},
|
|
1651
|
+
|
|
1652
|
+
async runSemantic() {
|
|
1653
|
+
const f = this.forms.semantic;
|
|
1654
|
+
if (!f.query.trim()) return;
|
|
1655
|
+
this.results.semantic = await this.api('/api/semantic?query=' + encodeURIComponent(f.query) + '&limit=' + f.limit);
|
|
1656
|
+
},
|
|
1657
|
+
|
|
1658
|
+
async runQuick() {
|
|
1659
|
+
const f = this.forms.quick;
|
|
1660
|
+
if (!f.name.trim()) return;
|
|
1661
|
+
this.results.quick = await this.api('/api/quick?name=' + encodeURIComponent(f.name) + '&full=' + f.full);
|
|
1662
|
+
},
|
|
1663
|
+
|
|
1664
|
+
async runCallers() {
|
|
1665
|
+
const f = this.forms.callers;
|
|
1666
|
+
if (!f.name.trim()) return;
|
|
1667
|
+
this.results.callers = await this.api('/api/callers?name=' + encodeURIComponent(f.name) + '&limit=' + f.limit);
|
|
1668
|
+
},
|
|
1669
|
+
|
|
1670
|
+
async runCallees() {
|
|
1671
|
+
const f = this.forms.callees;
|
|
1672
|
+
if (!f.name.trim()) return;
|
|
1673
|
+
this.results.callees = await this.api('/api/callees?name=' + encodeURIComponent(f.name) + '&limit=' + f.limit);
|
|
1674
|
+
},
|
|
1675
|
+
|
|
1676
|
+
async runContext() {
|
|
1677
|
+
const f = this.forms.context;
|
|
1678
|
+
if (!f.node_id.trim()) return;
|
|
1679
|
+
const p = new URLSearchParams({node_id: f.node_id});
|
|
1680
|
+
if (f.full) p.set('full', 'true');
|
|
1681
|
+
if (f.expand_callees) p.set('expand_callees', 'true');
|
|
1682
|
+
this.results.context = await this.api('/api/context?' + p);
|
|
1683
|
+
},
|
|
1684
|
+
|
|
1685
|
+
async runStructure() {
|
|
1686
|
+
const f = this.forms.structure;
|
|
1687
|
+
if (!f.filename.trim()) return;
|
|
1688
|
+
this.results.structure = await this.api('/api/structure?filename=' + encodeURIComponent(f.filename));
|
|
1689
|
+
},
|
|
1690
|
+
|
|
1691
|
+
async runFlatten() {
|
|
1692
|
+
const f = this.forms.flatten;
|
|
1693
|
+
if (!f.class_node_id.trim()) return;
|
|
1694
|
+
this.results.flatten = await this.api('/api/flatten?class_node_id=' + encodeURIComponent(f.class_node_id));
|
|
1695
|
+
},
|
|
1696
|
+
|
|
1697
|
+
async runImpls() {
|
|
1698
|
+
const f = this.forms.impls;
|
|
1699
|
+
if (!f.method.trim()) return;
|
|
1700
|
+
const p = new URLSearchParams({method: f.method});
|
|
1701
|
+
if (f.trait_name) p.set('trait_name', f.trait_name);
|
|
1702
|
+
this.results.impls = await this.api('/api/impls?' + p);
|
|
1703
|
+
},
|
|
1704
|
+
|
|
1705
|
+
async runFlow() {
|
|
1706
|
+
const f = this.forms.flow;
|
|
1707
|
+
if (!f.start.trim()) return;
|
|
1708
|
+
const p = new URLSearchParams({start: f.start, direction: f.direction, depth: f.depth, format: 'tree'});
|
|
1709
|
+
if (f.target) p.set('target', f.target);
|
|
1710
|
+
this.results.flow = await this.api('/api/flow?' + p);
|
|
1711
|
+
},
|
|
1712
|
+
|
|
1713
|
+
async runPath() {
|
|
1714
|
+
const f = this.forms.path;
|
|
1715
|
+
if (!f.start.trim() || !f.end.trim()) return;
|
|
1716
|
+
this.results.path = await this.api('/api/path?start=' + encodeURIComponent(f.start) + '&end=' + encodeURIComponent(f.end) + '&max_depth=' + f.max_depth);
|
|
1717
|
+
},
|
|
1718
|
+
|
|
1719
|
+
async runCrossLang() {
|
|
1720
|
+
const f = this.forms.crossLang;
|
|
1721
|
+
const p = new URLSearchParams({mode: f.mode});
|
|
1722
|
+
if (f.mode === 'endpoint') { p.set('method', f.method); p.set('path', f.path); }
|
|
1723
|
+
if (f.mode === 'list' && f.tag) p.set('tag', f.tag);
|
|
1724
|
+
this.results.crossLang = await this.api('/api/cross-lang?' + p);
|
|
1725
|
+
},
|
|
1726
|
+
|
|
1727
|
+
async runChurn() {
|
|
1728
|
+
const f = this.forms.churn;
|
|
1729
|
+
const p = new URLSearchParams({mode: f.mode, limit: f.limit, format: 'structured'});
|
|
1730
|
+
if (f.time_range) p.set('time_range', f.time_range);
|
|
1731
|
+
if (f.path_filter) p.set('path_filter', f.path_filter);
|
|
1732
|
+
this.results.churn = await this.api('/api/churn?' + p);
|
|
1733
|
+
},
|
|
1734
|
+
|
|
1735
|
+
async runEvolution() {
|
|
1736
|
+
const f = this.forms.evolution;
|
|
1737
|
+
if (!f.name.trim()) return;
|
|
1738
|
+
const p = new URLSearchParams({name: f.name, limit: f.limit});
|
|
1739
|
+
if (f.time_range) p.set('time_range', f.time_range);
|
|
1740
|
+
if (f.show_diff) p.set('show_diff', 'true');
|
|
1741
|
+
this.results.evolution = await this.api('/api/evolution?' + p);
|
|
1742
|
+
},
|
|
1743
|
+
|
|
1744
|
+
async runChanges() {
|
|
1745
|
+
const f = this.forms.changes;
|
|
1746
|
+
const p = new URLSearchParams({limit: 50, format: 'structured'});
|
|
1747
|
+
if (f.commit_range) p.set('commit_range', f.commit_range);
|
|
1748
|
+
if (f.time_range) p.set('time_range', f.time_range);
|
|
1749
|
+
if (f.path_filter) p.set('path_filter', f.path_filter);
|
|
1750
|
+
if (f.show_callers) p.set('show_callers', 'true');
|
|
1751
|
+
this.results.changes = await this.api('/api/changes?' + p);
|
|
1752
|
+
},
|
|
1753
|
+
|
|
1754
|
+
async runSource() {
|
|
1755
|
+
const f = this.forms.source;
|
|
1756
|
+
if (!f.file.trim()) return;
|
|
1757
|
+
const p = new URLSearchParams({file: f.file});
|
|
1758
|
+
if (f.line) p.set('line', f.line);
|
|
1759
|
+
this.results.source = await this.api('/api/source?' + p);
|
|
1760
|
+
},
|
|
1761
|
+
|
|
1762
|
+
async runReindex() {
|
|
1763
|
+
if (this.reindexing) return;
|
|
1764
|
+
this.reindexing = true;
|
|
1765
|
+
this.reindexLines = [];
|
|
1766
|
+
this.reindexStatus = null;
|
|
1767
|
+
this.reindexGraph = null;
|
|
1768
|
+
this.error = null;
|
|
1769
|
+
|
|
1770
|
+
// Start elapsed timer
|
|
1771
|
+
const startTime = Date.now();
|
|
1772
|
+
this.reindexElapsed = '0s';
|
|
1773
|
+
this.reindexTimer = setInterval(() => {
|
|
1774
|
+
const secs = Math.floor((Date.now() - startTime) / 1000);
|
|
1775
|
+
const mins = Math.floor(secs / 60);
|
|
1776
|
+
this.reindexElapsed = mins > 0 ? `${mins}m ${secs % 60}s` : `${secs}s`;
|
|
1777
|
+
}, 1000);
|
|
1778
|
+
|
|
1779
|
+
try {
|
|
1780
|
+
const res = await fetch('/api/index/stream', {method: 'POST'});
|
|
1781
|
+
const reader = res.body.getReader();
|
|
1782
|
+
const decoder = new TextDecoder();
|
|
1783
|
+
let buffer = '';
|
|
1784
|
+
|
|
1785
|
+
while (true) {
|
|
1786
|
+
const {done, value} = await reader.read();
|
|
1787
|
+
if (done) break;
|
|
1788
|
+
buffer += decoder.decode(value, {stream: true});
|
|
1789
|
+
|
|
1790
|
+
const lines = buffer.split('\n');
|
|
1791
|
+
buffer = lines.pop(); // keep incomplete line in buffer
|
|
1792
|
+
|
|
1793
|
+
for (const line of lines) {
|
|
1794
|
+
if (!line.startsWith('data: ')) continue;
|
|
1795
|
+
try {
|
|
1796
|
+
const evt = JSON.parse(line.slice(6));
|
|
1797
|
+
if (evt.type === 'output') {
|
|
1798
|
+
this.reindexLines.push({text: evt.line, type: 'output'});
|
|
1799
|
+
} else if (evt.type === 'start') {
|
|
1800
|
+
this.reindexLines.push({text: evt.message, type: 'info'});
|
|
1801
|
+
} else if (evt.type === 'error') {
|
|
1802
|
+
this.reindexLines.push({text: evt.message, type: 'error'});
|
|
1803
|
+
} else if (evt.type === 'done') {
|
|
1804
|
+
this.reindexStatus = evt.success ? 'success' : 'error';
|
|
1805
|
+
if (evt.graph) this.reindexGraph = evt.graph;
|
|
1806
|
+
if (evt.success) {
|
|
1807
|
+
this.reindexLines.push({text: 'Reindex completed successfully.', type: 'success'});
|
|
1808
|
+
await this.fetchHealth();
|
|
1809
|
+
} else {
|
|
1810
|
+
this.reindexLines.push({text: evt.message || 'Reindex failed.', type: 'error'});
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
// Auto-scroll log
|
|
1814
|
+
this.$nextTick(() => {
|
|
1815
|
+
const log = this.$refs.reindexLog;
|
|
1816
|
+
if (log) log.scrollTop = log.scrollHeight;
|
|
1817
|
+
});
|
|
1818
|
+
} catch (e) { /* skip malformed SSE */ }
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
} catch (e) {
|
|
1822
|
+
this.error = e.message;
|
|
1823
|
+
this.reindexStatus = 'error';
|
|
1824
|
+
} finally {
|
|
1825
|
+
this.reindexing = false;
|
|
1826
|
+
clearInterval(this.reindexTimer);
|
|
1827
|
+
this.reindexTimer = null;
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
</script>
|
|
1833
|
+
|
|
1834
|
+
<!-- Encyclopedia Panel -->
|
|
1835
|
+
<div class="ency-overlay"
|
|
1836
|
+
x-show="showHelp"
|
|
1837
|
+
x-transition.opacity
|
|
1838
|
+
@click.self="showHelp = false"
|
|
1839
|
+
@keydown.escape.window="showHelp = false"
|
|
1840
|
+
style="display:none">
|
|
1841
|
+
<div class="ency-panel" @click.stop>
|
|
1842
|
+
<!-- Header -->
|
|
1843
|
+
<div class="ency-header">
|
|
1844
|
+
<button class="ency-back" x-show="helpArticle" @click="helpArticle = null; helpSearch = ''">← TOC</button>
|
|
1845
|
+
<span class="ency-title" x-text="helpArticle ? (helpArticles.find(a => a.slug === helpArticle)?.title || 'Encyclopedia') : 'Descry Encyclopedia'"></span>
|
|
1846
|
+
<input class="ency-search" type="text" placeholder="Search articles..."
|
|
1847
|
+
x-model="helpSearch" x-show="!helpArticle"
|
|
1848
|
+
@keydown.escape="helpSearch = ''">
|
|
1849
|
+
<button class="ency-close" @click="showHelp = false">×</button>
|
|
1850
|
+
</div>
|
|
1851
|
+
|
|
1852
|
+
<!-- Body -->
|
|
1853
|
+
<div class="ency-body">
|
|
1854
|
+
|
|
1855
|
+
<!-- TOC View -->
|
|
1856
|
+
<div x-show="!helpArticle">
|
|
1857
|
+
<!-- Grouped by category when not searching -->
|
|
1858
|
+
<template x-if="!helpSearch.trim()">
|
|
1859
|
+
<div>
|
|
1860
|
+
<div class="ency-category">Tools</div>
|
|
1861
|
+
<template x-for="a in helpArticles.filter(a => a.category === 'tools')" :key="a.slug">
|
|
1862
|
+
<div class="ency-toc-item" @click="helpArticle = a.slug">
|
|
1863
|
+
<span class="ency-toc-title" x-text="a.title"></span>
|
|
1864
|
+
<span class="ency-toc-desc" x-text="a.desc"></span>
|
|
1865
|
+
</div>
|
|
1866
|
+
</template>
|
|
1867
|
+
<div class="ency-category">Concepts</div>
|
|
1868
|
+
<template x-for="a in helpArticles.filter(a => a.category === 'concepts')" :key="a.slug">
|
|
1869
|
+
<div class="ency-toc-item" @click="helpArticle = a.slug">
|
|
1870
|
+
<span class="ency-toc-title" x-text="a.title"></span>
|
|
1871
|
+
<span class="ency-toc-desc" x-text="a.desc"></span>
|
|
1872
|
+
</div>
|
|
1873
|
+
</template>
|
|
1874
|
+
<div class="ency-category">Workflows</div>
|
|
1875
|
+
<template x-for="a in helpArticles.filter(a => a.category === 'workflows')" :key="a.slug">
|
|
1876
|
+
<div class="ency-toc-item" @click="helpArticle = a.slug">
|
|
1877
|
+
<span class="ency-toc-title" x-text="a.title"></span>
|
|
1878
|
+
<span class="ency-toc-desc" x-text="a.desc"></span>
|
|
1879
|
+
</div>
|
|
1880
|
+
</template>
|
|
1881
|
+
</div>
|
|
1882
|
+
</template>
|
|
1883
|
+
|
|
1884
|
+
<!-- Flat filtered list when searching -->
|
|
1885
|
+
<template x-if="helpSearch.trim()">
|
|
1886
|
+
<div>
|
|
1887
|
+
<template x-for="a in filteredHelpArticles()" :key="a.slug">
|
|
1888
|
+
<div class="ency-toc-item" @click="helpArticle = a.slug">
|
|
1889
|
+
<span class="ency-toc-title" x-text="a.title"></span>
|
|
1890
|
+
<span class="ency-toc-desc" x-text="a.desc"></span>
|
|
1891
|
+
</div>
|
|
1892
|
+
</template>
|
|
1893
|
+
<div class="ency-no-results" x-show="filteredHelpArticles().length === 0">
|
|
1894
|
+
No articles match your search.
|
|
1895
|
+
</div>
|
|
1896
|
+
</div>
|
|
1897
|
+
</template>
|
|
1898
|
+
</div>
|
|
1899
|
+
|
|
1900
|
+
<!-- ===== TOOL ARTICLES ===== -->
|
|
1901
|
+
|
|
1902
|
+
<!-- Search -->
|
|
1903
|
+
<div class="ency-article" x-show="helpArticle === 'search'">
|
|
1904
|
+
<h2>Search</h2>
|
|
1905
|
+
<p class="ency-oneliner">Find symbols using hybrid keyword + semantic matching.</p>
|
|
1906
|
+
<h3>Parameters</h3>
|
|
1907
|
+
<table>
|
|
1908
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
1909
|
+
<tr><td>terms</td><td>Keywords to match against symbol names, docstrings, and code</td></tr>
|
|
1910
|
+
<tr><td>limit</td><td>Max results to return (default: 10)</td></tr>
|
|
1911
|
+
<tr><td>lang</td><td>Filter by language: python, rust, typescript, etc.</td></tr>
|
|
1912
|
+
<tr><td>package</td><td>Filter by top-level directory / package name</td></tr>
|
|
1913
|
+
<tr><td>type</td><td>Filter by symbol type: function, method, class, constant, file</td></tr>
|
|
1914
|
+
</table>
|
|
1915
|
+
<h3>Example</h3>
|
|
1916
|
+
<div class="ency-example">
|
|
1917
|
+
Search for <code>validate</code> → returns functions, methods, and classes whose name, docstring, or code contains "validate", ranked by relevance score (0–1).
|
|
1918
|
+
</div>
|
|
1919
|
+
<h3>Tips</h3>
|
|
1920
|
+
<ul class="ency-tips">
|
|
1921
|
+
<li>Combines keyword matching with semantic similarity when embeddings are available</li>
|
|
1922
|
+
<li>Click any result to jump to its full Context view</li>
|
|
1923
|
+
<li>For conceptual queries like "error handling middleware", try Semantic Search instead</li>
|
|
1924
|
+
</ul>
|
|
1925
|
+
</div>
|
|
1926
|
+
|
|
1927
|
+
<!-- Semantic Search -->
|
|
1928
|
+
<div class="ency-article" x-show="helpArticle === 'semantic'">
|
|
1929
|
+
<h2>Semantic Search</h2>
|
|
1930
|
+
<p class="ency-oneliner">Search by meaning using code embeddings rather than exact names.</p>
|
|
1931
|
+
<h3>Parameters</h3>
|
|
1932
|
+
<table>
|
|
1933
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
1934
|
+
<tr><td>query</td><td>Natural language description of what you're looking for</td></tr>
|
|
1935
|
+
<tr><td>limit</td><td>Max results to return (default: 10)</td></tr>
|
|
1936
|
+
</table>
|
|
1937
|
+
<h3>Example</h3>
|
|
1938
|
+
<div class="ency-example">
|
|
1939
|
+
Query <code>database connection pooling</code> → finds functions related to connection pools even if they're named <code>get_pool</code> or <code>acquire_conn</code>.
|
|
1940
|
+
</div>
|
|
1941
|
+
<h3>Tips</h3>
|
|
1942
|
+
<ul class="ency-tips">
|
|
1943
|
+
<li>Requires semantic embeddings to be built (check Health for availability)</li>
|
|
1944
|
+
<li>Best when you know what code does but not what it's called</li>
|
|
1945
|
+
<li>Use keyword Search when you know the exact symbol name</li>
|
|
1946
|
+
</ul>
|
|
1947
|
+
</div>
|
|
1948
|
+
|
|
1949
|
+
<!-- Quick Lookup -->
|
|
1950
|
+
<div class="ency-article" x-show="helpArticle === 'quick'">
|
|
1951
|
+
<h2>Quick Lookup</h2>
|
|
1952
|
+
<p class="ency-oneliner">Get full details on a symbol in a single step.</p>
|
|
1953
|
+
<h3>Parameters</h3>
|
|
1954
|
+
<table>
|
|
1955
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
1956
|
+
<tr><td>name</td><td>Symbol name to look up (e.g. function or class name)</td></tr>
|
|
1957
|
+
<tr><td>full</td><td>Show complete source without truncation</td></tr>
|
|
1958
|
+
</table>
|
|
1959
|
+
<h3>Example</h3>
|
|
1960
|
+
<div class="ency-example">
|
|
1961
|
+
Look up <code>parse_config</code> → returns source code, callers, callees, and related tests for the best-matching symbol, all in one response.
|
|
1962
|
+
</div>
|
|
1963
|
+
<h3>Tips</h3>
|
|
1964
|
+
<ul class="ency-tips">
|
|
1965
|
+
<li>Combines Search + Context into one step — fastest way to get a complete picture</li>
|
|
1966
|
+
<li>If the wrong match is found, use Search to pick the right one, then Context</li>
|
|
1967
|
+
</ul>
|
|
1968
|
+
</div>
|
|
1969
|
+
|
|
1970
|
+
<!-- Callers -->
|
|
1971
|
+
<div class="ency-article" x-show="helpArticle === 'callers'">
|
|
1972
|
+
<h2>Callers</h2>
|
|
1973
|
+
<p class="ency-oneliner">Find every function or method that calls the given symbol.</p>
|
|
1974
|
+
<h3>Parameters</h3>
|
|
1975
|
+
<table>
|
|
1976
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
1977
|
+
<tr><td>name</td><td>Symbol name to find callers of</td></tr>
|
|
1978
|
+
<tr><td>limit</td><td>Max callers to return (default: 20)</td></tr>
|
|
1979
|
+
</table>
|
|
1980
|
+
<h3>Example</h3>
|
|
1981
|
+
<div class="ency-example">
|
|
1982
|
+
Callers of <code>validate_token</code> → shows every function that invokes <code>validate_token</code>, with file locations and call count.
|
|
1983
|
+
</div>
|
|
1984
|
+
<h3>Tips</h3>
|
|
1985
|
+
<ul class="ency-tips">
|
|
1986
|
+
<li>More reliable than grep — uses the call graph to distinguish real calls from comments and strings</li>
|
|
1987
|
+
<li>Essential before refactoring: know who depends on a function before changing its signature</li>
|
|
1988
|
+
<li>Click any caller to view its full Context</li>
|
|
1989
|
+
</ul>
|
|
1990
|
+
</div>
|
|
1991
|
+
|
|
1992
|
+
<!-- Callees -->
|
|
1993
|
+
<div class="ency-article" x-show="helpArticle === 'callees'">
|
|
1994
|
+
<h2>Callees</h2>
|
|
1995
|
+
<p class="ency-oneliner">Find every function or method that the given symbol calls.</p>
|
|
1996
|
+
<h3>Parameters</h3>
|
|
1997
|
+
<table>
|
|
1998
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
1999
|
+
<tr><td>name</td><td>Symbol name to find callees of</td></tr>
|
|
2000
|
+
<tr><td>limit</td><td>Max callees to return (default: 20)</td></tr>
|
|
2001
|
+
</table>
|
|
2002
|
+
<h3>Example</h3>
|
|
2003
|
+
<div class="ency-example">
|
|
2004
|
+
Callees of <code>handle_request</code> → shows every function that <code>handle_request</code> invokes internally.
|
|
2005
|
+
</div>
|
|
2006
|
+
<h3>Tips</h3>
|
|
2007
|
+
<ul class="ency-tips">
|
|
2008
|
+
<li>Answers "what will break if this dependency changes?"</li>
|
|
2009
|
+
<li>Use Flow Trace for a recursive view (callees of callees of callees...)</li>
|
|
2010
|
+
</ul>
|
|
2011
|
+
</div>
|
|
2012
|
+
|
|
2013
|
+
<!-- Context -->
|
|
2014
|
+
<div class="ency-article" x-show="helpArticle === 'context'">
|
|
2015
|
+
<h2>Context</h2>
|
|
2016
|
+
<p class="ency-oneliner">Complete dossier for a symbol: source, callers, callees, tests.</p>
|
|
2017
|
+
<h3>Parameters</h3>
|
|
2018
|
+
<table>
|
|
2019
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2020
|
+
<tr><td>node_id</td><td>Full node ID (e.g. <code>FILE:src/app.py::MyClass::init</code>)</td></tr>
|
|
2021
|
+
<tr><td>full</td><td>Show complete source without truncation</td></tr>
|
|
2022
|
+
<tr><td>expand callees</td><td>Inline source of small callees directly in the output</td></tr>
|
|
2023
|
+
</table>
|
|
2024
|
+
<h3>Example</h3>
|
|
2025
|
+
<div class="ency-example">
|
|
2026
|
+
Context for a function → shows its signature, docstring, full source code, list of callers, list of callees, and any test functions that reference it.
|
|
2027
|
+
</div>
|
|
2028
|
+
<h3>Tips</h3>
|
|
2029
|
+
<ul class="ency-tips">
|
|
2030
|
+
<li>Usually reached by clicking a result in Search, Callers, or other tools</li>
|
|
2031
|
+
<li>The node_id is the full graph identifier — you rarely need to type it manually</li>
|
|
2032
|
+
<li>"Expand callees" inlines the source of small helper functions so you can read them in place</li>
|
|
2033
|
+
</ul>
|
|
2034
|
+
</div>
|
|
2035
|
+
|
|
2036
|
+
<!-- Structure -->
|
|
2037
|
+
<div class="ency-article" x-show="helpArticle === 'structure'">
|
|
2038
|
+
<h2>Structure</h2>
|
|
2039
|
+
<p class="ency-oneliner">Show the skeleton of a file: all symbols with line numbers.</p>
|
|
2040
|
+
<h3>Parameters</h3>
|
|
2041
|
+
<table>
|
|
2042
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2043
|
+
<tr><td>filename</td><td>File path or partial filename to match (e.g. <code>handlers.py</code>)</td></tr>
|
|
2044
|
+
</table>
|
|
2045
|
+
<h3>Example</h3>
|
|
2046
|
+
<div class="ency-example">
|
|
2047
|
+
Structure of <code>server.py</code> → lists imports, constants, classes, and functions defined in the file, with line numbers and types. Much faster than reading the full source.
|
|
2048
|
+
</div>
|
|
2049
|
+
<h3>Tips</h3>
|
|
2050
|
+
<ul class="ency-tips">
|
|
2051
|
+
<li>Great for orientation when entering an unfamiliar file</li>
|
|
2052
|
+
<li>Click any symbol to jump to its full Context</li>
|
|
2053
|
+
<li>Partial filenames work — <code>server</code> matches <code>web/server.py</code></li>
|
|
2054
|
+
</ul>
|
|
2055
|
+
</div>
|
|
2056
|
+
|
|
2057
|
+
<!-- Flatten -->
|
|
2058
|
+
<div class="ency-article" x-show="helpArticle === 'flatten'">
|
|
2059
|
+
<h2>Flatten</h2>
|
|
2060
|
+
<p class="ency-oneliner">Show the full public API of a type including inherited methods.</p>
|
|
2061
|
+
<h3>Parameters</h3>
|
|
2062
|
+
<table>
|
|
2063
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2064
|
+
<tr><td>class_node_id</td><td>Full node ID of the class/struct (e.g. <code>FILE:src/app.py::MyClass</code>)</td></tr>
|
|
2065
|
+
</table>
|
|
2066
|
+
<h3>Example</h3>
|
|
2067
|
+
<div class="ency-example">
|
|
2068
|
+
Flatten <code>HttpClient</code> → shows all methods including those inherited from traits, interfaces, or parent classes, giving you the complete available API.
|
|
2069
|
+
</div>
|
|
2070
|
+
<h3>Tips</h3>
|
|
2071
|
+
<ul class="ency-tips">
|
|
2072
|
+
<li>Requires the full node ID — use Search or Structure to find it first</li>
|
|
2073
|
+
<li>Shows methods from all levels of the inheritance hierarchy</li>
|
|
2074
|
+
</ul>
|
|
2075
|
+
</div>
|
|
2076
|
+
|
|
2077
|
+
<!-- Implementations -->
|
|
2078
|
+
<div class="ency-article" x-show="helpArticle === 'impls'">
|
|
2079
|
+
<h2>Implementations</h2>
|
|
2080
|
+
<p class="ency-oneliner">Find all types that implement a trait or interface method.</p>
|
|
2081
|
+
<h3>Parameters</h3>
|
|
2082
|
+
<table>
|
|
2083
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2084
|
+
<tr><td>method</td><td>Method name to find implementations of</td></tr>
|
|
2085
|
+
<tr><td>trait_name</td><td>Optional: filter to a specific trait or interface</td></tr>
|
|
2086
|
+
</table>
|
|
2087
|
+
<h3>Example</h3>
|
|
2088
|
+
<div class="ency-example">
|
|
2089
|
+
Impls of <code>parse</code> → shows every struct, class, or type that has a <code>parse</code> method, grouped by trait when applicable.
|
|
2090
|
+
</div>
|
|
2091
|
+
<h3>Tips</h3>
|
|
2092
|
+
<ul class="ency-tips">
|
|
2093
|
+
<li>Far more precise than grep for polymorphic code — filters out false positives</li>
|
|
2094
|
+
<li>Add a trait name to narrow results when a method name is common</li>
|
|
2095
|
+
</ul>
|
|
2096
|
+
</div>
|
|
2097
|
+
|
|
2098
|
+
<!-- Flow Trace -->
|
|
2099
|
+
<div class="ency-article" x-show="helpArticle === 'flow'">
|
|
2100
|
+
<h2>Flow Trace</h2>
|
|
2101
|
+
<p class="ency-oneliner">Visualize the recursive call tree from any symbol.</p>
|
|
2102
|
+
<h3>Parameters</h3>
|
|
2103
|
+
<table>
|
|
2104
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2105
|
+
<tr><td>start</td><td>Symbol name to start tracing from</td></tr>
|
|
2106
|
+
<tr><td>direction</td><td><code>forward</code> (what it calls) or <code>backward</code> (what calls it)</td></tr>
|
|
2107
|
+
<tr><td>depth</td><td>How many levels deep to trace (default: 3)</td></tr>
|
|
2108
|
+
<tr><td>target</td><td>Optional: stop tracing when this symbol is reached</td></tr>
|
|
2109
|
+
</table>
|
|
2110
|
+
<h3>Example</h3>
|
|
2111
|
+
<div class="ency-example">
|
|
2112
|
+
Flow forward from <code>main</code>, depth 3 → shows a tree of <code>main</code> → its callees → their callees → their callees. Small functions are inlined with source code.
|
|
2113
|
+
</div>
|
|
2114
|
+
<h3>Tips</h3>
|
|
2115
|
+
<ul class="ency-tips">
|
|
2116
|
+
<li>Forward = "what happens when this runs?" / Backward = "how do we get here?"</li>
|
|
2117
|
+
<li>Click any node in the tree to jump to its Context</li>
|
|
2118
|
+
<li>Set a target to focus the trace on a specific call path</li>
|
|
2119
|
+
<li>Use Call Path instead if you just need the shortest route between two symbols</li>
|
|
2120
|
+
</ul>
|
|
2121
|
+
</div>
|
|
2122
|
+
|
|
2123
|
+
<!-- Call Path -->
|
|
2124
|
+
<div class="ency-article" x-show="helpArticle === 'path'">
|
|
2125
|
+
<h2>Call Path</h2>
|
|
2126
|
+
<p class="ency-oneliner">Find the shortest call chain between two symbols.</p>
|
|
2127
|
+
<h3>Parameters</h3>
|
|
2128
|
+
<table>
|
|
2129
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2130
|
+
<tr><td>start</td><td>Source symbol name</td></tr>
|
|
2131
|
+
<tr><td>end</td><td>Target symbol name</td></tr>
|
|
2132
|
+
<tr><td>max_depth</td><td>Maximum hops to search (default: 10)</td></tr>
|
|
2133
|
+
</table>
|
|
2134
|
+
<h3>Example</h3>
|
|
2135
|
+
<div class="ency-example">
|
|
2136
|
+
Path from <code>handle_request</code> to <code>write_log</code> → shows each hop with the call site code snippet: <code>handle_request</code> → <code>process</code> → <code>write_log</code>.
|
|
2137
|
+
</div>
|
|
2138
|
+
<h3>Tips</h3>
|
|
2139
|
+
<ul class="ency-tips">
|
|
2140
|
+
<li>More focused than Flow Trace, which shows the entire tree</li>
|
|
2141
|
+
<li>Useful for verifying that a specific code path exists</li>
|
|
2142
|
+
<li>If no path is found, the symbols may be in disconnected parts of the graph</li>
|
|
2143
|
+
</ul>
|
|
2144
|
+
</div>
|
|
2145
|
+
|
|
2146
|
+
<!-- Cross-Language -->
|
|
2147
|
+
<div class="ency-article" x-show="helpArticle === 'cross-lang'">
|
|
2148
|
+
<h2>Cross-Language</h2>
|
|
2149
|
+
<p class="ency-oneliner">Trace API calls from frontend to backend via OpenAPI spec.</p>
|
|
2150
|
+
<h3>Parameters</h3>
|
|
2151
|
+
<table>
|
|
2152
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2153
|
+
<tr><td>mode</td><td><code>endpoint</code> (single route), <code>list</code> (all routes for a tag), or <code>stats</code> (coverage overview)</td></tr>
|
|
2154
|
+
<tr><td>method</td><td>HTTP method: GET, POST, PUT, DELETE</td></tr>
|
|
2155
|
+
<tr><td>path</td><td>API path (e.g. <code>/api/v1/users</code>)</td></tr>
|
|
2156
|
+
<tr><td>tag</td><td>OpenAPI tag to filter by (for list mode)</td></tr>
|
|
2157
|
+
</table>
|
|
2158
|
+
<h3>Example</h3>
|
|
2159
|
+
<div class="ency-example">
|
|
2160
|
+
Endpoint <code>GET /api/v1/users</code> → shows which backend handler function serves this route and which frontend functions call it.
|
|
2161
|
+
</div>
|
|
2162
|
+
<h3>Tips</h3>
|
|
2163
|
+
<ul class="ency-tips">
|
|
2164
|
+
<li>Requires an OpenAPI spec to be configured (see project config)</li>
|
|
2165
|
+
<li>Use "stats" mode first to see available endpoints and coverage</li>
|
|
2166
|
+
<li>Bridges the gap when tracing user actions from UI to backend</li>
|
|
2167
|
+
</ul>
|
|
2168
|
+
</div>
|
|
2169
|
+
|
|
2170
|
+
<!-- Churn -->
|
|
2171
|
+
<div class="ency-article" x-show="helpArticle === 'churn'">
|
|
2172
|
+
<h2>Churn</h2>
|
|
2173
|
+
<p class="ency-oneliner">Find the most frequently changed code using git history.</p>
|
|
2174
|
+
<h3>Parameters</h3>
|
|
2175
|
+
<table>
|
|
2176
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2177
|
+
<tr><td>mode</td><td><code>symbols</code> (function-level), <code>files</code> (file-level), or <code>co-change</code> (coupled pairs)</td></tr>
|
|
2178
|
+
<tr><td>time_range</td><td>Git time range, e.g. <code>last 30 days</code>, <code>since 2024-01-01</code></td></tr>
|
|
2179
|
+
<tr><td>path_filter</td><td>Optional: limit to files matching a path prefix or pattern</td></tr>
|
|
2180
|
+
<tr><td>limit</td><td>Max results (default: 20)</td></tr>
|
|
2181
|
+
</table>
|
|
2182
|
+
<h3>Example</h3>
|
|
2183
|
+
<div class="ency-example">
|
|
2184
|
+
Churn in <code>symbols</code> mode, last 30 days → ranks functions by how many commits touched them. High-churn symbols may need refactoring or better test coverage.
|
|
2185
|
+
</div>
|
|
2186
|
+
<h3>Tips</h3>
|
|
2187
|
+
<ul class="ency-tips">
|
|
2188
|
+
<li><code>co-change</code> mode reveals hidden coupling: pairs of symbols that always change together</li>
|
|
2189
|
+
<li>Filter by path to focus on a specific module or directory</li>
|
|
2190
|
+
<li>Use Evolution to drill into a specific high-churn symbol's history</li>
|
|
2191
|
+
</ul>
|
|
2192
|
+
</div>
|
|
2193
|
+
|
|
2194
|
+
<!-- Evolution -->
|
|
2195
|
+
<div class="ency-article" x-show="helpArticle === 'evolution'">
|
|
2196
|
+
<h2>Evolution</h2>
|
|
2197
|
+
<p class="ency-oneliner">Track the commit history of a specific function or file.</p>
|
|
2198
|
+
<h3>Parameters</h3>
|
|
2199
|
+
<table>
|
|
2200
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2201
|
+
<tr><td>name</td><td>Symbol or file name to track</td></tr>
|
|
2202
|
+
<tr><td>time_range</td><td>Optional: limit to a time window</td></tr>
|
|
2203
|
+
<tr><td>limit</td><td>Max commits to show (default: 10)</td></tr>
|
|
2204
|
+
<tr><td>show_diff</td><td>Include actual code diffs at each commit</td></tr>
|
|
2205
|
+
</table>
|
|
2206
|
+
<h3>Example</h3>
|
|
2207
|
+
<div class="ency-example">
|
|
2208
|
+
Evolution of <code>parse_config</code> with diffs → shows a timeline of commits, authors, dates, and the actual code changes at each point. Follows the function across renames.
|
|
2209
|
+
</div>
|
|
2210
|
+
<h3>Tips</h3>
|
|
2211
|
+
<ul class="ency-tips">
|
|
2212
|
+
<li>Uses git's native function tracking to follow symbols across line drift and renames</li>
|
|
2213
|
+
<li>Enable "Show diffs" to see what changed at each commit</li>
|
|
2214
|
+
<li>Great for understanding why a function looks the way it does</li>
|
|
2215
|
+
</ul>
|
|
2216
|
+
</div>
|
|
2217
|
+
|
|
2218
|
+
<!-- Changes -->
|
|
2219
|
+
<div class="ency-article" x-show="helpArticle === 'changes'">
|
|
2220
|
+
<h2>Changes</h2>
|
|
2221
|
+
<p class="ency-oneliner">Analyze recent commits to see modified symbols and their callers.</p>
|
|
2222
|
+
<h3>Parameters</h3>
|
|
2223
|
+
<table>
|
|
2224
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2225
|
+
<tr><td>commit_range</td><td>Git range, e.g. <code>HEAD~5..HEAD</code>, <code>main..feature</code></td></tr>
|
|
2226
|
+
<tr><td>time_range</td><td>Alternative: time-based range like <code>last 7 days</code></td></tr>
|
|
2227
|
+
<tr><td>path_filter</td><td>Optional: limit to files matching a pattern</td></tr>
|
|
2228
|
+
<tr><td>show_callers</td><td>Include callers of changed functions (blast radius)</td></tr>
|
|
2229
|
+
</table>
|
|
2230
|
+
<h3>Example</h3>
|
|
2231
|
+
<div class="ency-example">
|
|
2232
|
+
Changes in <code>HEAD~5..HEAD</code> with callers → shows which functions were modified in the last 5 commits, plus every function that calls them, revealing the full blast radius.
|
|
2233
|
+
</div>
|
|
2234
|
+
<h3>Tips</h3>
|
|
2235
|
+
<ul class="ency-tips">
|
|
2236
|
+
<li>The "show callers" option is what makes this powerful — it reveals ripple risk</li>
|
|
2237
|
+
<li>Use for code review: see at a glance what a PR touches and what depends on it</li>
|
|
2238
|
+
<li>Defaults to the last commit if no range is specified</li>
|
|
2239
|
+
</ul>
|
|
2240
|
+
</div>
|
|
2241
|
+
|
|
2242
|
+
<!-- Health -->
|
|
2243
|
+
<div class="ency-article" x-show="helpArticle === 'health'">
|
|
2244
|
+
<h2>Health</h2>
|
|
2245
|
+
<p class="ency-oneliner">Check graph status, feature availability, and freshness.</p>
|
|
2246
|
+
<h3>Parameters</h3>
|
|
2247
|
+
<table>
|
|
2248
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2249
|
+
<tr><td colspan="2"><em>No parameters — runs automatically</em></td></tr>
|
|
2250
|
+
</table>
|
|
2251
|
+
<h3>Example</h3>
|
|
2252
|
+
<div class="ency-example">
|
|
2253
|
+
Health → shows node count, edge count, graph age, and which features are available (SCIP indexing, semantic embeddings, git history, cross-language tracing).
|
|
2254
|
+
</div>
|
|
2255
|
+
<h3>Tips</h3>
|
|
2256
|
+
<ul class="ency-tips">
|
|
2257
|
+
<li>Check this first if tools return unexpected results — the graph may be stale</li>
|
|
2258
|
+
<li>The status dot in the header gives a quick summary (green = fresh, yellow = stale)</li>
|
|
2259
|
+
</ul>
|
|
2260
|
+
</div>
|
|
2261
|
+
|
|
2262
|
+
<!-- Reindex -->
|
|
2263
|
+
<div class="ency-article" x-show="helpArticle === 'reindex'">
|
|
2264
|
+
<h2>Reindex</h2>
|
|
2265
|
+
<p class="ency-oneliner">Rebuild the knowledge graph from scratch.</p>
|
|
2266
|
+
<h3>Parameters</h3>
|
|
2267
|
+
<table>
|
|
2268
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2269
|
+
<tr><td colspan="2"><em>No parameters — rebuilds everything</em></td></tr>
|
|
2270
|
+
</table>
|
|
2271
|
+
<h3>Example</h3>
|
|
2272
|
+
<div class="ency-example">
|
|
2273
|
+
Reindex → re-scans all source files, rebuilds the call graph, regenerates SCIP indices and semantic embeddings. Progress streams live to the output log.
|
|
2274
|
+
</div>
|
|
2275
|
+
<h3>Tips</h3>
|
|
2276
|
+
<ul class="ency-tips">
|
|
2277
|
+
<li>Run after significant code changes: new files, renames, refactoring</li>
|
|
2278
|
+
<li>May take several minutes for large codebases with SCIP enabled</li>
|
|
2279
|
+
<li>Progress is streamed live — watch the log for current step</li>
|
|
2280
|
+
</ul>
|
|
2281
|
+
</div>
|
|
2282
|
+
|
|
2283
|
+
<!-- Source -->
|
|
2284
|
+
<div class="ency-article" x-show="helpArticle === 'source'">
|
|
2285
|
+
<h2>Source</h2>
|
|
2286
|
+
<p class="ency-oneliner">View source files with line numbers and optional line highlighting.</p>
|
|
2287
|
+
<h3>Parameters</h3>
|
|
2288
|
+
<table>
|
|
2289
|
+
<tr><th>Field</th><th>Description</th></tr>
|
|
2290
|
+
<tr><td>file</td><td>File path relative to project root</td></tr>
|
|
2291
|
+
<tr><td>line</td><td>Optional: line number to highlight and scroll to</td></tr>
|
|
2292
|
+
</table>
|
|
2293
|
+
<h3>Example</h3>
|
|
2294
|
+
<div class="ency-example">
|
|
2295
|
+
Source <code>src/app.py</code> line 42 → opens the file with line 42 highlighted. Useful for examining the surrounding code of a specific call site.
|
|
2296
|
+
</div>
|
|
2297
|
+
<h3>Tips</h3>
|
|
2298
|
+
<ul class="ency-tips">
|
|
2299
|
+
<li>Usually reached by clicking file locations in other tools</li>
|
|
2300
|
+
<li>Markdown files can be toggled between raw and rendered view</li>
|
|
2301
|
+
</ul>
|
|
2302
|
+
</div>
|
|
2303
|
+
|
|
2304
|
+
<!-- ===== CONCEPT ARTICLES ===== -->
|
|
2305
|
+
|
|
2306
|
+
<!-- The Knowledge Graph -->
|
|
2307
|
+
<div class="ency-article" x-show="helpArticle === 'concept-graph'">
|
|
2308
|
+
<h2>The Knowledge Graph</h2>
|
|
2309
|
+
<p class="ency-oneliner">How descry models your codebase as nodes and edges.</p>
|
|
2310
|
+
<h3>Nodes</h3>
|
|
2311
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">Every symbol in your code becomes a node: functions, methods, classes, constants, files, and configuration items. Each node has a unique ID like <code>FILE:src/app.py::MyClass::init</code> encoding its location in the file hierarchy.</p>
|
|
2312
|
+
<h3>Edges</h3>
|
|
2313
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">Edges represent relationships: <strong>calls</strong> (function A calls function B), <strong>contains</strong> (file contains class), <strong>inherits</strong> (class extends parent), and <strong>imports</strong>. This structure enables tools like Callers, Callees, Flow Trace, and Call Path.</p>
|
|
2314
|
+
<h3>How It's Built</h3>
|
|
2315
|
+
<p style="font-size:13px;color:var(--text-secondary)">Descry parses source files using language-aware AST parsers for Python, Rust, TypeScript, Go, Svelte, and more. When SCIP indexing is available, cross-references are precise (compiler-level accuracy). The graph is cached as JSON and rebuilt on demand via Reindex.</p>
|
|
2316
|
+
</div>
|
|
2317
|
+
|
|
2318
|
+
<!-- Symbol Types -->
|
|
2319
|
+
<div class="ency-article" x-show="helpArticle === 'concept-symbols'">
|
|
2320
|
+
<h2>Symbol Types</h2>
|
|
2321
|
+
<p class="ency-oneliner">The badge system used throughout descry results.</p>
|
|
2322
|
+
<h3>Legend</h3>
|
|
2323
|
+
<div>
|
|
2324
|
+
<div class="ency-legend-row"><span class="rc-type fun">FUN</span><span class="ency-legend-name">Function</span><span class="ency-legend-desc">Standalone functions and arrow functions</span></div>
|
|
2325
|
+
<div class="ency-legend-row"><span class="rc-type met">MET</span><span class="ency-legend-name">Method</span><span class="ency-legend-desc">Methods on a class, struct, or impl block</span></div>
|
|
2326
|
+
<div class="ency-legend-row"><span class="rc-type cla">CLA</span><span class="ency-legend-name">Class</span><span class="ency-legend-desc">Classes, structs, enums, traits, interfaces, protobuf messages</span></div>
|
|
2327
|
+
<div class="ency-legend-row"><span class="rc-type con">CON</span><span class="ency-legend-name">Constant</span><span class="ency-legend-desc">Exported constants and static values</span></div>
|
|
2328
|
+
<div class="ency-legend-row"><span class="rc-type fil">FIL</span><span class="ency-legend-name">File</span><span class="ency-legend-desc">Source files (.py, .rs, .ts, .svelte, .go, .proto, etc.)</span></div>
|
|
2329
|
+
<div class="ency-legend-row"><span class="rc-type cfg">CFG</span><span class="ency-legend-name">Configuration</span><span class="ency-legend-desc">Interceptors, middleware, and event handlers</span></div>
|
|
2330
|
+
</div>
|
|
2331
|
+
<h3>Tips</h3>
|
|
2332
|
+
<ul class="ency-tips">
|
|
2333
|
+
<li>The <strong>Score</strong> (0–1) shown in search results combines keyword and semantic relevance</li>
|
|
2334
|
+
<li>Click any badge in results to view that symbol's full Context</li>
|
|
2335
|
+
<li>Use the type filter in Search to narrow results to a specific kind of symbol</li>
|
|
2336
|
+
</ul>
|
|
2337
|
+
</div>
|
|
2338
|
+
|
|
2339
|
+
<!-- SCIP Indexing -->
|
|
2340
|
+
<div class="ency-article" x-show="helpArticle === 'concept-scip'">
|
|
2341
|
+
<h2>SCIP Indexing</h2>
|
|
2342
|
+
<p class="ency-oneliner">Compiler-level precision for cross-references and type resolution.</p>
|
|
2343
|
+
<h3>What is SCIP?</h3>
|
|
2344
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">SCIP (Source Code Intelligence Protocol) is a standard for representing code intelligence data. Descry uses SCIP indices produced by language-specific indexers (rust-analyzer for Rust, scip-typescript for TypeScript) to resolve cross-references with compiler-level accuracy.</p>
|
|
2345
|
+
<h3>Why It Matters</h3>
|
|
2346
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">Without SCIP, descry relies on AST-based name matching which can produce false positives (e.g., two functions with the same name in different modules). With SCIP, every reference is resolved to its exact definition — no ambiguity.</p>
|
|
2347
|
+
<h3>Supported Languages</h3>
|
|
2348
|
+
<p style="font-size:13px;color:var(--text-secondary)">Rust (via rust-analyzer), TypeScript/JavaScript (via scip-typescript). Check Health to see if SCIP is available for your project. SCIP is optional — all tools work without it, just with less precision.</p>
|
|
2349
|
+
</div>
|
|
2350
|
+
|
|
2351
|
+
<!-- Semantic Embeddings -->
|
|
2352
|
+
<div class="ency-article" x-show="helpArticle === 'concept-embeddings'">
|
|
2353
|
+
<h2>Semantic Embeddings</h2>
|
|
2354
|
+
<p class="ency-oneliner">How vector search finds conceptually similar code.</p>
|
|
2355
|
+
<h3>How It Works</h3>
|
|
2356
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">Each symbol's name, docstring, and signature are converted into a high-dimensional vector using a code embedding model. When you search, your query is also embedded, and results are ranked by cosine similarity — how close two vectors point in the same direction.</p>
|
|
2357
|
+
<h3>When to Use</h3>
|
|
2358
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px"><strong>Semantic Search</strong>: when you know what code does but not its name. "rate limiting middleware" finds <code>throttle_requests</code>. <strong>Keyword Search</strong>: when you know the name. <code>throttle_requests</code> finds it directly.</p>
|
|
2359
|
+
<h3>Tips</h3>
|
|
2360
|
+
<ul class="ency-tips">
|
|
2361
|
+
<li>Embeddings are built during Reindex — check Health for availability</li>
|
|
2362
|
+
<li>The hybrid Search tool combines both approaches automatically</li>
|
|
2363
|
+
</ul>
|
|
2364
|
+
</div>
|
|
2365
|
+
|
|
2366
|
+
<!-- Git History Analysis -->
|
|
2367
|
+
<div class="ency-article" x-show="helpArticle === 'concept-git'">
|
|
2368
|
+
<h2>Git History Analysis</h2>
|
|
2369
|
+
<p class="ency-oneliner">How descry uses git log for churn, evolution, and change impact.</p>
|
|
2370
|
+
<h3>Three Perspectives</h3>
|
|
2371
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px"><strong>Churn</strong> answers "what changes most?" by counting commits per symbol or file. <strong>Evolution</strong> answers "how did this change over time?" with a commit timeline. <strong>Changes</strong> answers "what did these commits affect?" with impact analysis.</p>
|
|
2372
|
+
<h3>How It Works</h3>
|
|
2373
|
+
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:8px">Descry maps git diff output back to the knowledge graph. Changed line ranges are resolved to their enclosing functions, giving you symbol-level granularity instead of just file-level diffs.</p>
|
|
2374
|
+
<h3>Tips</h3>
|
|
2375
|
+
<ul class="ency-tips">
|
|
2376
|
+
<li>Requires a git repository — check Health for availability</li>
|
|
2377
|
+
<li>Time ranges use git's natural language: <code>last 30 days</code>, <code>since 2024-01-01</code></li>
|
|
2378
|
+
</ul>
|
|
2379
|
+
</div>
|
|
2380
|
+
|
|
2381
|
+
<!-- ===== WORKFLOW ARTICLES ===== -->
|
|
2382
|
+
|
|
2383
|
+
<!-- Investigating a Function -->
|
|
2384
|
+
<div class="ency-article" x-show="helpArticle === 'workflow-investigate'">
|
|
2385
|
+
<h2>Investigating a Function</h2>
|
|
2386
|
+
<p class="ency-oneliner">Start with a name, expand outward to build full understanding.</p>
|
|
2387
|
+
<h3>Steps</h3>
|
|
2388
|
+
<div class="ency-steps">
|
|
2389
|
+
<div class="ency-step"><span class="ency-step-num">1</span><span><strong>Quick Lookup</strong> the function name to get source, callers, callees, and tests in one shot.</span></div>
|
|
2390
|
+
<div class="ency-step"><span class="ency-step-num">2</span><span><strong>Context</strong> — click any related symbol to explore its details. Toggle "full" for complete source.</span></div>
|
|
2391
|
+
<div class="ency-step"><span class="ency-step-num">3</span><span><strong>Callers</strong> — understand who depends on this function and how it's used in practice.</span></div>
|
|
2392
|
+
<div class="ency-step"><span class="ency-step-num">4</span><span><strong>Flow Trace</strong> forward to see the full execution tree, or backward to see how control reaches this point.</span></div>
|
|
2393
|
+
</div>
|
|
2394
|
+
<h3>When to Use</h3>
|
|
2395
|
+
<ul class="ency-tips">
|
|
2396
|
+
<li>Before modifying a function — understand its callers and dependencies first</li>
|
|
2397
|
+
<li>When debugging — trace the flow to find where things go wrong</li>
|
|
2398
|
+
<li>During code review — verify that changes don't break callers</li>
|
|
2399
|
+
</ul>
|
|
2400
|
+
</div>
|
|
2401
|
+
|
|
2402
|
+
<!-- Understanding a Codebase -->
|
|
2403
|
+
<div class="ency-article" x-show="helpArticle === 'workflow-explore'">
|
|
2404
|
+
<h2>Understanding a Codebase</h2>
|
|
2405
|
+
<p class="ency-oneliner">Get the lay of the land when entering an unfamiliar project.</p>
|
|
2406
|
+
<h3>Steps</h3>
|
|
2407
|
+
<div class="ency-steps">
|
|
2408
|
+
<div class="ency-step"><span class="ency-step-num">1</span><span><strong>Health</strong> — see how large the codebase is (node/edge counts) and what features are available.</span></div>
|
|
2409
|
+
<div class="ency-step"><span class="ency-step-num">2</span><span><strong>Structure</strong> on key files (e.g. <code>main.py</code>, <code>app.rs</code>) to see what's defined where.</span></div>
|
|
2410
|
+
<div class="ency-step"><span class="ency-step-num">3</span><span><strong>Search</strong> for entry points like <code>main</code>, <code>handler</code>, <code>route</code> to find where execution starts.</span></div>
|
|
2411
|
+
<div class="ency-step"><span class="ency-step-num">4</span><span><strong>Flow Trace</strong> forward from entry points to understand the main execution paths.</span></div>
|
|
2412
|
+
</div>
|
|
2413
|
+
<h3>When to Use</h3>
|
|
2414
|
+
<ul class="ency-tips">
|
|
2415
|
+
<li>Onboarding onto a new project</li>
|
|
2416
|
+
<li>Returning to a codebase after a long break</li>
|
|
2417
|
+
<li>Evaluating an open-source library before adopting it</li>
|
|
2418
|
+
</ul>
|
|
2419
|
+
</div>
|
|
2420
|
+
|
|
2421
|
+
<!-- Tracking Recent Changes -->
|
|
2422
|
+
<div class="ency-article" x-show="helpArticle === 'workflow-changes'">
|
|
2423
|
+
<h2>Tracking Recent Changes</h2>
|
|
2424
|
+
<p class="ency-oneliner">See what's been happening in the codebase recently.</p>
|
|
2425
|
+
<h3>Steps</h3>
|
|
2426
|
+
<div class="ency-steps">
|
|
2427
|
+
<div class="ency-step"><span class="ency-step-num">1</span><span><strong>Changes</strong> with <code>HEAD~5..HEAD</code> and "show callers" to see recent modifications and their blast radius.</span></div>
|
|
2428
|
+
<div class="ency-step"><span class="ency-step-num">2</span><span><strong>Churn</strong> in symbols mode to identify which functions are changing most frequently.</span></div>
|
|
2429
|
+
<div class="ency-step"><span class="ency-step-num">3</span><span><strong>Evolution</strong> on high-churn symbols to see their full change timeline and understand patterns.</span></div>
|
|
2430
|
+
</div>
|
|
2431
|
+
<h3>When to Use</h3>
|
|
2432
|
+
<ul class="ency-tips">
|
|
2433
|
+
<li>Starting your workday — catch up on what teammates changed</li>
|
|
2434
|
+
<li>Before a release — assess the scope of recent changes</li>
|
|
2435
|
+
<li>Prioritizing refactoring — high-churn code benefits most from cleanup</li>
|
|
2436
|
+
</ul>
|
|
2437
|
+
</div>
|
|
2438
|
+
|
|
2439
|
+
</div><!-- ency-body -->
|
|
2440
|
+
</div>
|
|
2441
|
+
</div>
|
|
2442
|
+
|
|
2443
|
+
</body>
|
|
2444
|
+
</html>
|