@nithin554/code-crawler 0.1.55 → 0.1.56
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.
- package/README.md +20 -1
- package/dist/graph/budget.d.ts +46 -0
- package/dist/graph/budget.js +72 -0
- package/dist/graph/budget.js.map +1 -0
- package/dist/graph/insights.d.ts +217 -0
- package/dist/graph/insights.js +612 -0
- package/dist/graph/insights.js.map +1 -0
- package/dist/graph/queries.d.ts +15 -1
- package/dist/graph/queries.js +5 -5
- package/dist/graph/queries.js.map +1 -1
- package/dist/server.js +462 -163
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Requires Node.js ≥ 20.
|
|
|
107
107
|
```bash
|
|
108
108
|
npm install
|
|
109
109
|
npm run build # tsc → dist/
|
|
110
|
-
npm test # vitest (
|
|
110
|
+
npm test # vitest (215 tests; run with CODECRAWLER_BENCH=1 for the scale test)
|
|
111
111
|
npm run bench # crawl/query benchmark on a generated ~137k-line project
|
|
112
112
|
```
|
|
113
113
|
|
|
@@ -321,6 +321,11 @@ See [`USAGE.md`](USAGE.md) for the full task-oriented walkthrough.
|
|
|
321
321
|
| `find_injections` | DI-supplied dependencies (`@Autowired`, NestJS, FastAPI `Depends`) |
|
|
322
322
|
| `find_reflections` | Run-time lookups: `Class.forName`, `getattr`, `importlib`, dynamic `import()` |
|
|
323
323
|
| `find_external_dependencies` | Third-party packages with reference and file counts |
|
|
324
|
+
| `find_unresolved` | Call sites the resolver could not bind, grouped by failure reason |
|
|
325
|
+
| `impact_analysis` | Callers + entry points + routes + overrides + risk band in one call |
|
|
326
|
+
| `explain_symbol` | One-shot digest: members, decorators, callers, callees, wiring |
|
|
327
|
+
| `find_cycles` | Call cycles (SCCs) with a sample closed path, plus direct recursion |
|
|
328
|
+
| `export_graph` | Neighborhood rendered as Mermaid or Graphviz DOT |
|
|
324
329
|
| `get_subgraph` | Neighborhood extraction for visualization |
|
|
325
330
|
| `get_schema` | Describe the graph model |
|
|
326
331
|
| `clear_index` | Reset the database |
|
|
@@ -328,6 +333,20 @@ See [`USAGE.md`](USAGE.md) for the full task-oriented walkthrough.
|
|
|
328
333
|
**Resources**: `codecrawler://schema`, `codecrawler://status`
|
|
329
334
|
**Prompt**: `trace-call-path(from, to)`
|
|
330
335
|
|
|
336
|
+
**Token budgeting.** The 18 list-returning query tools — every tool whose payload is a
|
|
337
|
+
single result list — accept `max_tokens` (100–200000) and `cursor`. Passing either adds a
|
|
338
|
+
`page` block with `totalCount`, `returnedCount`, `truncated`, `estimatedTokens`, and
|
|
339
|
+
`nextCursor`; omit both and the response shape is unchanged, so existing clients keep
|
|
340
|
+
working. Paging never returns an empty page while items remain, so a continuation loop
|
|
341
|
+
always terminates. Token counts are estimates from JSON length (~4 characters per token) —
|
|
342
|
+
no tokenizer dependency, deterministic across models.
|
|
343
|
+
|
|
344
|
+
`get_subgraph` is the one exception: its `nodes` and `edges` are two views of the same slice,
|
|
345
|
+
so cutting one list would leave edges pointing at omitted nodes. It is bounded by `radius` and
|
|
346
|
+
`limit` instead. Aggregate tools (`impact_analysis`, `explain_symbol`, `export_graph`) and
|
|
347
|
+
single-object tools (`get_node`, `index_status`, `get_schema`) return one object and take no
|
|
348
|
+
budget.
|
|
349
|
+
|
|
331
350
|
## Graph model
|
|
332
351
|
|
|
333
352
|
**Nodes** — `kind` is one of `file | package | module | class | interface | enum | method |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token budgeting and cursor pagination for tool responses.
|
|
3
|
+
*
|
|
4
|
+
* Token counts are *estimates*: JSON payloads average roughly four characters
|
|
5
|
+
* per token for source-code-adjacent text. Estimating avoids a tokenizer
|
|
6
|
+
* dependency and keeps responses deterministic across models. The budget only
|
|
7
|
+
* decides where to cut a list; it is never a hard failure condition.
|
|
8
|
+
*/
|
|
9
|
+
export interface BudgetOptions {
|
|
10
|
+
/** Soft cap on the estimated tokens the returned items may cost. */
|
|
11
|
+
maxTokens?: number;
|
|
12
|
+
/** Zero-based offset into the item list. */
|
|
13
|
+
cursor?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface BudgetPage<T> {
|
|
16
|
+
items: T[];
|
|
17
|
+
/** Number of items available in the window handed to applyBudget. */
|
|
18
|
+
totalCount: number;
|
|
19
|
+
returnedCount: number;
|
|
20
|
+
cursor: number;
|
|
21
|
+
/** Offset to pass as `cursor` to fetch the next page. */
|
|
22
|
+
nextCursor?: number;
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
estimatedTokens: number;
|
|
25
|
+
}
|
|
26
|
+
/** Metadata describing a page, without the items themselves. */
|
|
27
|
+
export interface PageMeta {
|
|
28
|
+
totalCount: number;
|
|
29
|
+
returnedCount: number;
|
|
30
|
+
cursor: number;
|
|
31
|
+
nextCursor?: number;
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
estimatedTokens: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function estimateTokens(value: unknown): number;
|
|
36
|
+
/**
|
|
37
|
+
* Slice a list to a token budget, starting at `cursor`.
|
|
38
|
+
*
|
|
39
|
+
* A single item is always returned even when it exceeds the budget, so a page
|
|
40
|
+
* can never come back empty while items remain.
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyBudget<T>(items: readonly T[], opts?: BudgetOptions): BudgetPage<T>;
|
|
43
|
+
/** Strip the items from a page so it can be attached as response metadata. */
|
|
44
|
+
export declare function pageMeta<T>(page: BudgetPage<T>): PageMeta;
|
|
45
|
+
/** True when the caller opted into budgeted output in any way. */
|
|
46
|
+
export declare function budgetRequested(opts: BudgetOptions): boolean;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token budgeting and cursor pagination for tool responses.
|
|
3
|
+
*
|
|
4
|
+
* Token counts are *estimates*: JSON payloads average roughly four characters
|
|
5
|
+
* per token for source-code-adjacent text. Estimating avoids a tokenizer
|
|
6
|
+
* dependency and keeps responses deterministic across models. The budget only
|
|
7
|
+
* decides where to cut a list; it is never a hard failure condition.
|
|
8
|
+
*/
|
|
9
|
+
export function estimateTokens(value) {
|
|
10
|
+
if (value === undefined)
|
|
11
|
+
return 0;
|
|
12
|
+
const text = JSON.stringify(value) ?? String(value);
|
|
13
|
+
return Math.ceil(text.length / 4);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Slice a list to a token budget, starting at `cursor`.
|
|
17
|
+
*
|
|
18
|
+
* A single item is always returned even when it exceeds the budget, so a page
|
|
19
|
+
* can never come back empty while items remain.
|
|
20
|
+
*/
|
|
21
|
+
export function applyBudget(items, opts = {}) {
|
|
22
|
+
const totalCount = items.length;
|
|
23
|
+
const cursor = Math.max(0, Math.floor(opts.cursor ?? 0));
|
|
24
|
+
const available = cursor >= totalCount ? [] : items.slice(cursor);
|
|
25
|
+
const maxTokens = opts.maxTokens;
|
|
26
|
+
if (maxTokens === undefined || maxTokens <= 0) {
|
|
27
|
+
return {
|
|
28
|
+
items: available,
|
|
29
|
+
totalCount,
|
|
30
|
+
returnedCount: available.length,
|
|
31
|
+
cursor,
|
|
32
|
+
truncated: false,
|
|
33
|
+
estimatedTokens: estimateTokens(available),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const page = [];
|
|
37
|
+
let used = 0;
|
|
38
|
+
for (const item of available) {
|
|
39
|
+
const cost = estimateTokens(item);
|
|
40
|
+
if (page.length > 0 && used + cost > maxTokens)
|
|
41
|
+
break;
|
|
42
|
+
page.push(item);
|
|
43
|
+
used += cost;
|
|
44
|
+
}
|
|
45
|
+
const nextCursor = cursor + page.length;
|
|
46
|
+
const truncated = nextCursor < totalCount;
|
|
47
|
+
return {
|
|
48
|
+
items: page,
|
|
49
|
+
totalCount,
|
|
50
|
+
returnedCount: page.length,
|
|
51
|
+
cursor,
|
|
52
|
+
...(truncated ? { nextCursor } : {}),
|
|
53
|
+
truncated,
|
|
54
|
+
estimatedTokens: used,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Strip the items from a page so it can be attached as response metadata. */
|
|
58
|
+
export function pageMeta(page) {
|
|
59
|
+
return {
|
|
60
|
+
totalCount: page.totalCount,
|
|
61
|
+
returnedCount: page.returnedCount,
|
|
62
|
+
cursor: page.cursor,
|
|
63
|
+
...(page.nextCursor !== undefined ? { nextCursor: page.nextCursor } : {}),
|
|
64
|
+
truncated: page.truncated,
|
|
65
|
+
estimatedTokens: page.estimatedTokens,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** True when the caller opted into budgeted output in any way. */
|
|
69
|
+
export function budgetRequested(opts) {
|
|
70
|
+
return opts.maxTokens !== undefined || opts.cursor !== undefined;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/graph/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA+BH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAI,KAAmB,EAAE,IAAI,GAAkB,EAAE;IAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAEjC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAC9C,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,UAAU;YACV,aAAa,EAAE,SAAS,CAAC,MAAM;YAC/B,MAAM;YACN,SAAS,EAAE,KAAK;YAChB,eAAe,EAAE,cAAc,CAAC,SAAS,CAAC;SAC3C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,SAAS;YAAE,MAAM;QACtD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,IAAI,IAAI,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,MAAM,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IAC1C,OAAO;QACL,KAAK,EAAE,IAAI;QACX,UAAU;QACV,aAAa,EAAE,IAAI,CAAC,MAAM;QAC1B,MAAM;QACN,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,SAAS;QACT,eAAe,EAAE,IAAI;KACtB,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,QAAQ,CAAI,IAAmB;IAC7C,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Higher-level analyses built on top of the primitive queries.
|
|
3
|
+
*
|
|
4
|
+
* These power the "agent-facing" tools: what the index could not resolve, what
|
|
5
|
+
* breaks if a symbol changes, a one-shot explanation of a symbol, call cycles,
|
|
6
|
+
* and graph export in diagram formats.
|
|
7
|
+
*/
|
|
8
|
+
import type { GraphStore } from "./store.js";
|
|
9
|
+
import type { EdgeKind, NodeKind } from "./schema.js";
|
|
10
|
+
import { type DecoratorInfo, type SymbolRef, type WiringEdge } from "./queries.js";
|
|
11
|
+
export interface UnresolvedCall {
|
|
12
|
+
caller: SymbolRef;
|
|
13
|
+
/** Placeholder node id the call edge points at (`__unknown__:<label>`). */
|
|
14
|
+
targetId: string;
|
|
15
|
+
/** Callee as written in source, e.g. `System.out.println`. */
|
|
16
|
+
targetLabel: string;
|
|
17
|
+
/** Why the resolver gave up. */
|
|
18
|
+
reason: string;
|
|
19
|
+
line?: number;
|
|
20
|
+
edgeKind: EdgeKind;
|
|
21
|
+
}
|
|
22
|
+
export interface ReasonCount {
|
|
23
|
+
reason: string;
|
|
24
|
+
count: number;
|
|
25
|
+
}
|
|
26
|
+
export interface UnresolvedReport {
|
|
27
|
+
/** Total unresolved call edges matching the filters, ignoring `limit`. */
|
|
28
|
+
total: number;
|
|
29
|
+
/** Unresolved edges grouped by resolver reason, most frequent first. */
|
|
30
|
+
byReason: ReasonCount[];
|
|
31
|
+
calls: UnresolvedCall[];
|
|
32
|
+
/** True when `calls` hit the scan limit before `total` did. */
|
|
33
|
+
scanTruncated: boolean;
|
|
34
|
+
/** `kind` filter that was applied to the caller node kind. */
|
|
35
|
+
callerKind?: NodeKind;
|
|
36
|
+
/** `reason` filter that was applied (substring, case-insensitive). */
|
|
37
|
+
reasonFilter?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface UnresolvedQueryOptions {
|
|
40
|
+
/** Substring match against the resolver reason, case-insensitive. */
|
|
41
|
+
reason?: string;
|
|
42
|
+
/** Restrict to unresolved calls made by callers of this node kind. */
|
|
43
|
+
kind?: NodeKind;
|
|
44
|
+
/** Cap on rows scanned (default 2000). */
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Every call the resolver could not bind to a node, with the reason it failed.
|
|
49
|
+
*
|
|
50
|
+
* Unresolved calls are the honest boundary of a static index: they show where
|
|
51
|
+
* analysis stops (dynamic dispatch, untyped receivers, missing dependencies)
|
|
52
|
+
* and they are the highest-signal input for improving the parsers.
|
|
53
|
+
*/
|
|
54
|
+
export declare function findUnresolved(store: GraphStore, opts?: UnresolvedQueryOptions): UnresolvedReport;
|
|
55
|
+
export interface NodeUnresolvedInfo {
|
|
56
|
+
/** Calls this node makes that could not be resolved. */
|
|
57
|
+
outgoing: UnresolvedCall[];
|
|
58
|
+
/** Unresolved calls elsewhere in the graph that point at this node. */
|
|
59
|
+
incoming: {
|
|
60
|
+
caller: SymbolRef;
|
|
61
|
+
line?: number;
|
|
62
|
+
}[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Unresolved call detail for a single node, used to annotate `get_node`.
|
|
66
|
+
* Returns `undefined` when the node has no unresolved edges in either
|
|
67
|
+
* direction so callers can omit the block entirely.
|
|
68
|
+
*/
|
|
69
|
+
export declare function unresolvedForNode(store: GraphStore, nodeId: string): NodeUnresolvedInfo | undefined;
|
|
70
|
+
export interface ImpactCaller {
|
|
71
|
+
caller: SymbolRef;
|
|
72
|
+
depth: number;
|
|
73
|
+
callSiteLine?: number;
|
|
74
|
+
edgeKind: EdgeKind;
|
|
75
|
+
}
|
|
76
|
+
export interface ImpactImplementation {
|
|
77
|
+
type: SymbolRef;
|
|
78
|
+
/** Override of the analyzed method, when the symbol is a member. */
|
|
79
|
+
method?: SymbolRef;
|
|
80
|
+
depth: number;
|
|
81
|
+
via: "EXTENDS" | "IMPLEMENTS";
|
|
82
|
+
}
|
|
83
|
+
export interface ImpactSummary {
|
|
84
|
+
totalCallers: number;
|
|
85
|
+
directCallers: number;
|
|
86
|
+
entryPoints: number;
|
|
87
|
+
routes: number;
|
|
88
|
+
implementations: number;
|
|
89
|
+
affectedFiles: number;
|
|
90
|
+
/**
|
|
91
|
+
* Heuristic blast radius: `high` when a framework entry point reaches the
|
|
92
|
+
* symbol, `medium` when at least one caller exists, else `low`.
|
|
93
|
+
*/
|
|
94
|
+
risk: "low" | "medium" | "high";
|
|
95
|
+
}
|
|
96
|
+
export interface ImpactAnalysis {
|
|
97
|
+
symbol: SymbolRef;
|
|
98
|
+
summary: ImpactSummary;
|
|
99
|
+
callers: ImpactCaller[];
|
|
100
|
+
entryPoints: {
|
|
101
|
+
symbol: SymbolRef;
|
|
102
|
+
entryPointKind: string;
|
|
103
|
+
detail?: string;
|
|
104
|
+
viaDepth?: number;
|
|
105
|
+
}[];
|
|
106
|
+
routes: WiringEdge[];
|
|
107
|
+
implementations: ImpactImplementation[];
|
|
108
|
+
affectedFiles: string[];
|
|
109
|
+
}
|
|
110
|
+
export interface ImpactOptions {
|
|
111
|
+
maxDepth?: number;
|
|
112
|
+
includeDynamic?: boolean;
|
|
113
|
+
limit?: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* "What breaks if I change this?" — reverse callers, framework entry points,
|
|
117
|
+
* routes and implementations in a single response.
|
|
118
|
+
*
|
|
119
|
+
* A type aggregates the callers of its contained methods, since call edges bind
|
|
120
|
+
* to methods and a class is usually changed through its members.
|
|
121
|
+
*/
|
|
122
|
+
export declare function impactAnalysis(store: GraphStore, symbol: string, opts?: ImpactOptions): ImpactAnalysis | null;
|
|
123
|
+
export interface ExplainedMember {
|
|
124
|
+
symbol: SymbolRef;
|
|
125
|
+
line?: number;
|
|
126
|
+
decorators: DecoratorInfo[];
|
|
127
|
+
}
|
|
128
|
+
export interface SymbolExplanation {
|
|
129
|
+
symbol: SymbolRef;
|
|
130
|
+
line?: number;
|
|
131
|
+
isEntryPoint?: boolean;
|
|
132
|
+
decorators: DecoratorInfo[];
|
|
133
|
+
members: ExplainedMember[];
|
|
134
|
+
callers: ImpactCaller[];
|
|
135
|
+
callees: ImpactCaller[];
|
|
136
|
+
implementations: ImpactImplementation[];
|
|
137
|
+
routes: WiringEdge[];
|
|
138
|
+
injections: WiringEdge[];
|
|
139
|
+
reflections: WiringEdge[];
|
|
140
|
+
summary: {
|
|
141
|
+
members: number;
|
|
142
|
+
callers: number;
|
|
143
|
+
callees: number;
|
|
144
|
+
implementations: number;
|
|
145
|
+
wiringEdges: number;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export interface ExplainOptions {
|
|
149
|
+
maxDepth?: number;
|
|
150
|
+
includeDynamic?: boolean;
|
|
151
|
+
limit?: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One-shot digest of a symbol: what it is, what it is annotated with, what it
|
|
155
|
+
* contains, who calls it, what it calls, and how frameworks reach it.
|
|
156
|
+
*
|
|
157
|
+
* Intended to replace a chain of five tool calls with one round trip.
|
|
158
|
+
*/
|
|
159
|
+
export declare function explainSymbol(store: GraphStore, symbol: string, opts?: ExplainOptions): SymbolExplanation | null;
|
|
160
|
+
export interface CallCycle {
|
|
161
|
+
size: number;
|
|
162
|
+
members: SymbolRef[];
|
|
163
|
+
/** Shortest closed walk through the cycle, starting at the first member. */
|
|
164
|
+
sample: string[];
|
|
165
|
+
}
|
|
166
|
+
export interface CycleReport {
|
|
167
|
+
/** Number of multi-node cycles reported (after `minSize`). */
|
|
168
|
+
cycleCount: number;
|
|
169
|
+
cycles: CallCycle[];
|
|
170
|
+
/** Symbols that call themselves directly (size-1 cycles). */
|
|
171
|
+
selfRecursive: SymbolRef[];
|
|
172
|
+
edgeKinds: EdgeKind[];
|
|
173
|
+
scannedNodes: number;
|
|
174
|
+
scannedEdges: number;
|
|
175
|
+
/** True when the edge scan hit `maxNodes` and results may be incomplete. */
|
|
176
|
+
scanTruncated: boolean;
|
|
177
|
+
}
|
|
178
|
+
export interface CycleOptions {
|
|
179
|
+
edgeKinds?: EdgeKind[];
|
|
180
|
+
includeDynamic?: boolean;
|
|
181
|
+
/** Minimum cycle size to report. Default 2 (direct recursion is separate). */
|
|
182
|
+
minSize?: number;
|
|
183
|
+
/** Cap on edges scanned, guarding against very large graphs. */
|
|
184
|
+
maxEdges?: number;
|
|
185
|
+
limit?: number;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Find strongly connected components in the call graph with an iterative
|
|
189
|
+
* Tarjan. A component larger than one node means the members are mutually
|
|
190
|
+
* reachable, i.e. a call cycle; a self-edge is direct recursion.
|
|
191
|
+
*
|
|
192
|
+
* Iterative rather than recursive so deep call chains cannot blow the stack.
|
|
193
|
+
*/
|
|
194
|
+
export declare function findCycles(store: GraphStore, opts?: CycleOptions): CycleReport;
|
|
195
|
+
export type ExportFormat = "mermaid" | "dot";
|
|
196
|
+
export interface GraphExport {
|
|
197
|
+
format: ExportFormat;
|
|
198
|
+
diagram: string;
|
|
199
|
+
nodeCount: number;
|
|
200
|
+
edgeCount: number;
|
|
201
|
+
truncated: boolean;
|
|
202
|
+
/** Symbol the export was centered on, when one was given. */
|
|
203
|
+
center?: SymbolRef;
|
|
204
|
+
}
|
|
205
|
+
export interface ExportOptions {
|
|
206
|
+
symbol?: string;
|
|
207
|
+
radius?: number;
|
|
208
|
+
edgeKinds?: EdgeKind[];
|
|
209
|
+
format?: ExportFormat;
|
|
210
|
+
direction?: "LR" | "TB" | "RL" | "BT";
|
|
211
|
+
limit?: number;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Render a neighborhood (or the whole graph) as Mermaid or Graphviz DOT so an
|
|
215
|
+
* agent can show the user a diagram instead of a list of edges.
|
|
216
|
+
*/
|
|
217
|
+
export declare function exportGraph(store: GraphStore, opts?: ExportOptions): GraphExport | null;
|