@ctxo/cli 0.13.0 → 0.14.0
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 -4
- package/dist/chunk-2Q76LH2H.js +186 -0
- package/dist/chunk-2Q76LH2H.js.map +1 -0
- package/dist/chunk-72CEQEQD.js +53 -0
- package/dist/chunk-72CEQEQD.js.map +1 -0
- package/dist/chunk-7HSA5JVP.js +1049 -0
- package/dist/chunk-7HSA5JVP.js.map +1 -0
- package/dist/chunk-G4BMKCKG.js +76 -0
- package/dist/chunk-G4BMKCKG.js.map +1 -0
- package/dist/chunk-L2CAUER6.js +508 -0
- package/dist/chunk-L2CAUER6.js.map +1 -0
- package/dist/chunk-LAX6VVBU.js +98 -0
- package/dist/chunk-LAX6VVBU.js.map +1 -0
- package/dist/chunk-SRQZDGFZ.js +128 -0
- package/dist/chunk-SRQZDGFZ.js.map +1 -0
- package/dist/{chunk-DT3ZH4AU.js → chunk-X4TVFJ4C.js} +6 -1
- package/dist/chunk-X4TVFJ4C.js.map +1 -0
- package/dist/{chunk-BYX3UFBJ.js → chunk-ZL36YS2V.js} +785 -1624
- package/dist/chunk-ZL36YS2V.js.map +1 -0
- package/dist/{cli-router-RR4CM62C.js → cli-router-AZCDNX5R.js} +982 -506
- package/dist/cli-router-AZCDNX5R.js.map +1 -0
- package/dist/index.js +13 -2874
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +747 -0
- package/dist/internal.js +13 -0
- package/dist/{json-index-reader-OXTRTGE6.js → json-index-reader-DBW32TCN.js} +6 -4
- package/dist/json-index-reader-DBW32TCN.js.map +1 -0
- package/dist/mcp-server-6YG2QE6E.js +2604 -0
- package/dist/mcp-server-6YG2QE6E.js.map +1 -0
- package/dist/module-host-KNMKS62R.js +13 -0
- package/dist/module-host-KNMKS62R.js.map +1 -0
- package/dist/module-loader-B3LYF3W7.js +14 -0
- package/dist/module-loader-B3LYF3W7.js.map +1 -0
- package/dist/plugin-discovery-XRXI3IJF.js +13 -0
- package/dist/plugin-discovery-XRXI3IJF.js.map +1 -0
- package/llms-full.txt +105 -1
- package/llms.txt +21 -1
- package/package.json +15 -4
- package/dist/chunk-BYX3UFBJ.js.map +0 -1
- package/dist/chunk-DT3ZH4AU.js.map +0 -1
- package/dist/cli-router-RR4CM62C.js.map +0 -1
- /package/dist/{json-index-reader-OXTRTGE6.js.map → internal.js.map} +0 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SymbolGraph
|
|
4
|
+
} from "./chunk-7HSA5JVP.js";
|
|
5
|
+
import {
|
|
6
|
+
DetailLevelSchema,
|
|
7
|
+
JsonIndexReader
|
|
8
|
+
} from "./chunk-X4TVFJ4C.js";
|
|
9
|
+
import {
|
|
10
|
+
createLogger
|
|
11
|
+
} from "./chunk-5DSKZJNN.js";
|
|
12
|
+
|
|
13
|
+
// src/core/response-envelope.ts
|
|
14
|
+
var DEFAULT_THRESHOLD = 8192;
|
|
15
|
+
var workspaceMeta;
|
|
16
|
+
function setWorkspaceMeta(workspace) {
|
|
17
|
+
workspaceMeta = workspace;
|
|
18
|
+
}
|
|
19
|
+
var TRUNCATABLE_FIELDS = {
|
|
20
|
+
impactedSymbols: "Use search_symbols to find specific impacted symbols, or get_blast_radius with a confidence filter.",
|
|
21
|
+
importers: "Use find_importers with edgeKinds filter or maxDepth to narrow results.",
|
|
22
|
+
deadSymbols: "Use search_symbols with kind filter to find specific dead symbols.",
|
|
23
|
+
unusedExports: "Use find_importers to check specific symbols.",
|
|
24
|
+
results: "Use search_symbols or get_ranked_context with a narrower query.",
|
|
25
|
+
rankings: "Use get_symbol_importance with kind or filePattern filter to narrow results.",
|
|
26
|
+
context: "Use get_context_for_task with a smaller tokenBudget.",
|
|
27
|
+
hierarchies: "Use get_class_hierarchy with a specific symbolId.",
|
|
28
|
+
scaffolding: "Review the scaffolding markers directly in the listed files.",
|
|
29
|
+
deadFiles: "Use find_dead_code with includeTests to adjust scope.",
|
|
30
|
+
files: "Use get_changed_symbols with a narrower since ref or smaller maxFiles.",
|
|
31
|
+
clusters: "Use get_architectural_overlay with mode=communities and layer filter for a focused view.",
|
|
32
|
+
candidates: "Use get_architectural_overlay with mode=contexts and candidate: <id> for one candidate in full, or ctxo contexts --json for the full contexts view.",
|
|
33
|
+
clustersAffected: "Use get_blast_radius on individual changed symbols for per-cluster impact."
|
|
34
|
+
};
|
|
35
|
+
function getThreshold() {
|
|
36
|
+
const env = process.env["CTXO_RESPONSE_LIMIT"];
|
|
37
|
+
if (env) {
|
|
38
|
+
const n = parseInt(env, 10);
|
|
39
|
+
if (!isNaN(n) && n > 0) return n;
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_THRESHOLD;
|
|
42
|
+
}
|
|
43
|
+
function findTruncatableArray(data) {
|
|
44
|
+
let best = null;
|
|
45
|
+
for (const [key, value] of Object.entries(data)) {
|
|
46
|
+
if (Array.isArray(value) && key in TRUNCATABLE_FIELDS) {
|
|
47
|
+
if (!best || value.length > best.arr.length) {
|
|
48
|
+
best = { key, arr: value };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return best;
|
|
53
|
+
}
|
|
54
|
+
function buildMeta(base, extras) {
|
|
55
|
+
const meta = workspaceMeta ? { ...base, workspace: workspaceMeta } : base;
|
|
56
|
+
if (extras?.snapshotStaleness) {
|
|
57
|
+
return { ...meta, snapshotStaleness: extras.snapshotStaleness };
|
|
58
|
+
}
|
|
59
|
+
return meta;
|
|
60
|
+
}
|
|
61
|
+
function wrapResponse(data, extras) {
|
|
62
|
+
const threshold = getThreshold();
|
|
63
|
+
const fullJson = JSON.stringify(data);
|
|
64
|
+
const totalBytes = Buffer.byteLength(fullJson, "utf-8");
|
|
65
|
+
const truncatable = findTruncatableArray(data);
|
|
66
|
+
const totalItems = truncatable ? truncatable.arr.length : 0;
|
|
67
|
+
if (totalBytes <= threshold) {
|
|
68
|
+
return {
|
|
69
|
+
...data,
|
|
70
|
+
_meta: buildMeta(
|
|
71
|
+
{
|
|
72
|
+
totalItems,
|
|
73
|
+
returnedItems: totalItems,
|
|
74
|
+
truncated: false,
|
|
75
|
+
totalBytes
|
|
76
|
+
},
|
|
77
|
+
extras
|
|
78
|
+
)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!truncatable || truncatable.arr.length <= 1) {
|
|
82
|
+
return {
|
|
83
|
+
...data,
|
|
84
|
+
_meta: buildMeta(
|
|
85
|
+
{
|
|
86
|
+
totalItems,
|
|
87
|
+
returnedItems: totalItems,
|
|
88
|
+
truncated: false,
|
|
89
|
+
totalBytes
|
|
90
|
+
},
|
|
91
|
+
extras
|
|
92
|
+
)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
let lo = 1;
|
|
96
|
+
let hi = truncatable.arr.length;
|
|
97
|
+
while (lo < hi) {
|
|
98
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
99
|
+
const trial = {
|
|
100
|
+
...data,
|
|
101
|
+
[truncatable.key]: truncatable.arr.slice(0, mid),
|
|
102
|
+
_meta: buildMeta(
|
|
103
|
+
{
|
|
104
|
+
totalItems,
|
|
105
|
+
returnedItems: mid,
|
|
106
|
+
truncated: true,
|
|
107
|
+
totalBytes,
|
|
108
|
+
hint: TRUNCATABLE_FIELDS[truncatable.key]
|
|
109
|
+
},
|
|
110
|
+
extras
|
|
111
|
+
)
|
|
112
|
+
};
|
|
113
|
+
const size = Buffer.byteLength(JSON.stringify(trial), "utf-8");
|
|
114
|
+
if (size <= threshold) {
|
|
115
|
+
lo = mid;
|
|
116
|
+
} else {
|
|
117
|
+
hi = mid - 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const hint = TRUNCATABLE_FIELDS[truncatable.key];
|
|
121
|
+
return {
|
|
122
|
+
...data,
|
|
123
|
+
[truncatable.key]: truncatable.arr.slice(0, lo),
|
|
124
|
+
_meta: buildMeta(
|
|
125
|
+
{
|
|
126
|
+
totalItems,
|
|
127
|
+
returnedItems: lo,
|
|
128
|
+
truncated: true,
|
|
129
|
+
totalBytes,
|
|
130
|
+
hint
|
|
131
|
+
},
|
|
132
|
+
extras
|
|
133
|
+
)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/describe-error.ts
|
|
138
|
+
function describeError(err) {
|
|
139
|
+
try {
|
|
140
|
+
if (err instanceof Error) {
|
|
141
|
+
const message = err.message;
|
|
142
|
+
if (typeof message === "string") return message;
|
|
143
|
+
}
|
|
144
|
+
if (typeof err === "string") return err;
|
|
145
|
+
return String(err);
|
|
146
|
+
} catch {
|
|
147
|
+
try {
|
|
148
|
+
return Object.prototype.toString.call(err);
|
|
149
|
+
} catch {
|
|
150
|
+
return "[unprintable error]";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/adapters/mcp/enrich-payload.ts
|
|
156
|
+
var log = createLogger("ctxo:modules");
|
|
157
|
+
function applyEnricher(enrich, payload, args, tool) {
|
|
158
|
+
if (enrich === void 0) return payload;
|
|
159
|
+
try {
|
|
160
|
+
const enriched = enrich(structuredClone(payload), args);
|
|
161
|
+
if (!isPlainObject(enriched) || !isPlainData(enriched, /* @__PURE__ */ new Set())) {
|
|
162
|
+
log.warn(
|
|
163
|
+
`The ${tool} enricher returned something other than a plain JSON payload object; its fields are left out of this response`
|
|
164
|
+
);
|
|
165
|
+
return payload;
|
|
166
|
+
}
|
|
167
|
+
JSON.stringify(enriched);
|
|
168
|
+
return enriched;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
log.warn(`The ${tool} enricher failed: ${describeError(err)}; its fields are left out of this response`);
|
|
171
|
+
return payload;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isPlainObject(value) {
|
|
175
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
176
|
+
const proto = Object.getPrototypeOf(value);
|
|
177
|
+
return proto === Object.prototype || proto === null;
|
|
178
|
+
}
|
|
179
|
+
function isPlainData(value, path) {
|
|
180
|
+
if (value === null) return true;
|
|
181
|
+
const type = typeof value;
|
|
182
|
+
if (type === "string" || type === "number" || type === "boolean") return true;
|
|
183
|
+
if (type !== "object") return false;
|
|
184
|
+
const node = value;
|
|
185
|
+
if (path.has(node)) return false;
|
|
186
|
+
if (!Array.isArray(node) && !isPlainObject(node)) return false;
|
|
187
|
+
path.add(node);
|
|
188
|
+
try {
|
|
189
|
+
for (const child of Array.isArray(node) ? node : Object.values(node)) {
|
|
190
|
+
if (child !== void 0 && !isPlainData(child, path)) return false;
|
|
191
|
+
}
|
|
192
|
+
} finally {
|
|
193
|
+
path.delete(node);
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function collectEnrichers(modules) {
|
|
198
|
+
const chains = /* @__PURE__ */ new Map();
|
|
199
|
+
for (const module of modules) {
|
|
200
|
+
let entries;
|
|
201
|
+
try {
|
|
202
|
+
entries = Object.entries(module.registration.enrich ?? {});
|
|
203
|
+
} catch (err) {
|
|
204
|
+
log.warn(`${module.name} enrichers could not be read: ${describeError(err)}`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
for (const [tool, enrich] of entries) {
|
|
208
|
+
if (typeof enrich !== "function") continue;
|
|
209
|
+
const chain = chains.get(tool) ?? [];
|
|
210
|
+
chain.push(enrich);
|
|
211
|
+
chains.set(tool, chain);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const enrichers = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const [tool, chain] of chains) {
|
|
216
|
+
enrichers.set(
|
|
217
|
+
tool,
|
|
218
|
+
chain.length === 1 ? chain[0] : (payload, args) => chain.reduce((current, enrich) => applyEnricher(enrich, current, args, tool), payload)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return enrichers;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/adapters/mcp/get-logic-slice.ts
|
|
225
|
+
import { z } from "zod";
|
|
226
|
+
|
|
227
|
+
// src/core/logic-slice/logic-slice-query.ts
|
|
228
|
+
var LogicSliceQuery = class {
|
|
229
|
+
getLogicSlice(graph, symbolId, maxDepth = Infinity) {
|
|
230
|
+
const root = graph.getNode(symbolId);
|
|
231
|
+
if (!root) return void 0;
|
|
232
|
+
const visited = /* @__PURE__ */ new Set([root.symbolId]);
|
|
233
|
+
const dependencies = [];
|
|
234
|
+
const collectedEdges = [];
|
|
235
|
+
const queue = [{ id: root.symbolId, depth: 0 }];
|
|
236
|
+
while (queue.length > 0) {
|
|
237
|
+
const current = queue.shift();
|
|
238
|
+
if (current.depth >= maxDepth) continue;
|
|
239
|
+
const edges = graph.getForwardEdges(current.id);
|
|
240
|
+
for (const edge of edges) {
|
|
241
|
+
const depNode = graph.getNode(edge.to);
|
|
242
|
+
if (!depNode) continue;
|
|
243
|
+
collectedEdges.push(edge);
|
|
244
|
+
if (visited.has(edge.to)) continue;
|
|
245
|
+
visited.add(edge.to);
|
|
246
|
+
dependencies.push(depNode);
|
|
247
|
+
queue.push({ id: edge.to, depth: current.depth + 1 });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { root, dependencies, edges: collectedEdges };
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/core/detail-levels/detail-formatter.ts
|
|
255
|
+
var L1_MAX_LINES = 150;
|
|
256
|
+
var L4_TOKEN_BUDGET = 8e3;
|
|
257
|
+
function withoutAnnotations(node) {
|
|
258
|
+
if (node.annotations === void 0) return node;
|
|
259
|
+
const { annotations: _annotations, ...rest } = node;
|
|
260
|
+
return rest;
|
|
261
|
+
}
|
|
262
|
+
var DetailFormatter = class {
|
|
263
|
+
format(slice, level) {
|
|
264
|
+
switch (level) {
|
|
265
|
+
case 1:
|
|
266
|
+
return this.formatL1(slice);
|
|
267
|
+
case 2:
|
|
268
|
+
return this.formatL2(slice);
|
|
269
|
+
case 3:
|
|
270
|
+
return this.formatL3(slice);
|
|
271
|
+
case 4:
|
|
272
|
+
return this.formatL4(slice);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
formatL1(slice) {
|
|
276
|
+
const root = slice.root;
|
|
277
|
+
const lineCount = root.endLine - root.startLine + 1;
|
|
278
|
+
const clampedRoot = lineCount > L1_MAX_LINES ? { ...root, endLine: root.startLine + L1_MAX_LINES - 1 } : root;
|
|
279
|
+
return {
|
|
280
|
+
root: withoutAnnotations(clampedRoot),
|
|
281
|
+
dependencies: [],
|
|
282
|
+
edges: [],
|
|
283
|
+
level: 1,
|
|
284
|
+
levelDescription: "L1: Root symbol signature only (no dependencies)",
|
|
285
|
+
...lineCount > L1_MAX_LINES ? { truncation: { truncated: true, reason: "token_budget_exceeded" } } : {}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
formatL2(slice) {
|
|
289
|
+
const directEdges = slice.edges.filter((e) => e.from === slice.root.symbolId);
|
|
290
|
+
const directDepIds = new Set(directEdges.map((e) => e.to));
|
|
291
|
+
const directDeps = slice.dependencies.filter((d) => directDepIds.has(d.symbolId));
|
|
292
|
+
return {
|
|
293
|
+
root: withoutAnnotations(slice.root),
|
|
294
|
+
dependencies: directDeps.map(withoutAnnotations),
|
|
295
|
+
edges: directEdges,
|
|
296
|
+
level: 2,
|
|
297
|
+
levelDescription: "L2: Root + direct dependencies (depth 1)"
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
formatL3(slice) {
|
|
301
|
+
return {
|
|
302
|
+
root: withoutAnnotations(slice.root),
|
|
303
|
+
dependencies: slice.dependencies.map(withoutAnnotations),
|
|
304
|
+
edges: slice.edges,
|
|
305
|
+
level: 3,
|
|
306
|
+
levelDescription: "L3: Full transitive dependency closure"
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
formatL4(slice) {
|
|
310
|
+
const estimatedTokens = this.estimateTokens(slice);
|
|
311
|
+
if (estimatedTokens > L4_TOKEN_BUDGET) {
|
|
312
|
+
const truncated = this.truncateToTokenBudget(slice, L4_TOKEN_BUDGET);
|
|
313
|
+
return {
|
|
314
|
+
...truncated,
|
|
315
|
+
level: 4,
|
|
316
|
+
levelDescription: "L4: Full closure with 8K token budget (truncated)",
|
|
317
|
+
truncation: {
|
|
318
|
+
truncated: true,
|
|
319
|
+
reason: "token_budget_exceeded"
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
root: slice.root,
|
|
325
|
+
dependencies: slice.dependencies,
|
|
326
|
+
edges: slice.edges,
|
|
327
|
+
level: 4,
|
|
328
|
+
levelDescription: "L4: Full closure with 8K token budget"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
estimateTokens(slice) {
|
|
332
|
+
let chars = this.symbolCharEstimate(slice.root);
|
|
333
|
+
for (const dep of slice.dependencies) {
|
|
334
|
+
chars += this.symbolCharEstimate(dep);
|
|
335
|
+
}
|
|
336
|
+
for (const edge of slice.edges) {
|
|
337
|
+
chars += edge.from.length + edge.to.length + edge.kind.length + 10;
|
|
338
|
+
}
|
|
339
|
+
return Math.ceil(chars / 4);
|
|
340
|
+
}
|
|
341
|
+
symbolCharEstimate(node) {
|
|
342
|
+
if (node.startOffset !== void 0 && node.endOffset !== void 0) {
|
|
343
|
+
return node.endOffset - node.startOffset;
|
|
344
|
+
}
|
|
345
|
+
return (node.endLine - node.startLine + 1) * 40;
|
|
346
|
+
}
|
|
347
|
+
truncateToTokenBudget(slice, budget) {
|
|
348
|
+
let currentTokens = this.symbolCharEstimate(slice.root) / 4;
|
|
349
|
+
const deps = [];
|
|
350
|
+
const includedIds = /* @__PURE__ */ new Set([slice.root.symbolId]);
|
|
351
|
+
for (const dep of slice.dependencies) {
|
|
352
|
+
const depTokens = this.symbolCharEstimate(dep) / 4;
|
|
353
|
+
if (currentTokens + depTokens > budget) break;
|
|
354
|
+
currentTokens += depTokens;
|
|
355
|
+
deps.push(dep);
|
|
356
|
+
includedIds.add(dep.symbolId);
|
|
357
|
+
}
|
|
358
|
+
const edges = slice.edges.filter(
|
|
359
|
+
(e) => includedIds.has(e.from) && includedIds.has(e.to)
|
|
360
|
+
);
|
|
361
|
+
return { root: slice.root, dependencies: deps, edges };
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/core/intent-filter.ts
|
|
366
|
+
function extractKeywords(intent) {
|
|
367
|
+
return intent.toLowerCase().split(/\s+/).filter((w) => w.length >= 2);
|
|
368
|
+
}
|
|
369
|
+
function matchesAny(keywords, values) {
|
|
370
|
+
const lower = values.map((v) => v.toLowerCase());
|
|
371
|
+
return keywords.some((kw) => lower.some((v) => v.includes(kw)));
|
|
372
|
+
}
|
|
373
|
+
function collectMatchableStrings(entry) {
|
|
374
|
+
const strings = [];
|
|
375
|
+
for (const key of ["symbolId", "file", "name", "kind", "edgeKind", "reason", "confidence"]) {
|
|
376
|
+
const v = entry[key];
|
|
377
|
+
if (typeof v === "string") strings.push(v);
|
|
378
|
+
}
|
|
379
|
+
const edgeKinds = entry["edgeKinds"];
|
|
380
|
+
if (Array.isArray(edgeKinds)) {
|
|
381
|
+
for (const ek of edgeKinds) {
|
|
382
|
+
if (typeof ek === "string") strings.push(ek);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return strings;
|
|
386
|
+
}
|
|
387
|
+
function filterByIntent(items, intent) {
|
|
388
|
+
if (!intent || intent.trim().length === 0) return items;
|
|
389
|
+
const keywords = extractKeywords(intent);
|
|
390
|
+
if (keywords.length === 0) return items;
|
|
391
|
+
return items.filter((item) => {
|
|
392
|
+
const strings = collectMatchableStrings(item);
|
|
393
|
+
return matchesAny(keywords, strings);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/adapters/mcp/get-logic-slice.ts
|
|
398
|
+
var InputSchema = z.object({
|
|
399
|
+
symbolId: z.string().min(1).optional(),
|
|
400
|
+
symbolIds: z.array(z.string().min(1)).optional(),
|
|
401
|
+
level: DetailLevelSchema.optional().default(3),
|
|
402
|
+
intent: z.string().optional().describe('Filter dependencies by intent keywords (e.g., "core", "adapter", "storage")')
|
|
403
|
+
}).refine(
|
|
404
|
+
(data) => data.symbolId || data.symbolIds && data.symbolIds.length > 0,
|
|
405
|
+
{ message: "Either symbolId or symbolIds must be provided" }
|
|
406
|
+
);
|
|
407
|
+
function buildGraphFromStorage(storage) {
|
|
408
|
+
const graph = new SymbolGraph();
|
|
409
|
+
for (const sym of storage.getAllSymbols()) {
|
|
410
|
+
graph.addNode(sym);
|
|
411
|
+
}
|
|
412
|
+
for (const edge of storage.getAllEdges()) {
|
|
413
|
+
graph.addEdge(edge);
|
|
414
|
+
}
|
|
415
|
+
return graph;
|
|
416
|
+
}
|
|
417
|
+
function buildGraphAndFilesFromJsonIndex(ctxoRoot) {
|
|
418
|
+
const reader = new JsonIndexReader(ctxoRoot);
|
|
419
|
+
const indices = reader.readAll();
|
|
420
|
+
return { graph: buildGraphFromIndices(indices), indexedFiles: indices.map((fileIndex) => fileIndex.file) };
|
|
421
|
+
}
|
|
422
|
+
function buildGraphFromIndices(indices) {
|
|
423
|
+
const graph = new SymbolGraph();
|
|
424
|
+
for (const fileIndex of indices) {
|
|
425
|
+
for (const sym of fileIndex.symbols) {
|
|
426
|
+
graph.addNode(sym);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const fileIndex of indices) {
|
|
430
|
+
for (const edge of fileIndex.edges) {
|
|
431
|
+
graph.addEdge(edge);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return graph;
|
|
435
|
+
}
|
|
436
|
+
function getGraphAndFiles(ctxoRoot, storage) {
|
|
437
|
+
const json = buildGraphAndFilesFromJsonIndex(ctxoRoot);
|
|
438
|
+
if (json.graph.nodeCount > 0) return json;
|
|
439
|
+
return { graph: buildGraphFromStorage(storage), indexedFiles: storage.listIndexedFiles() };
|
|
440
|
+
}
|
|
441
|
+
function handleGetLogicSlice(storage, masking, staleness, ctxoRoot = ".ctxo", enrich) {
|
|
442
|
+
const query = new LogicSliceQuery();
|
|
443
|
+
const formatter = new DetailFormatter();
|
|
444
|
+
const getGraph = () => getGraphAndFiles(ctxoRoot, storage);
|
|
445
|
+
const handler = (args) => {
|
|
446
|
+
try {
|
|
447
|
+
const parsed = InputSchema.safeParse(args);
|
|
448
|
+
if (!parsed.success) {
|
|
449
|
+
return {
|
|
450
|
+
content: [{ type: "text", text: JSON.stringify({ error: true, message: parsed.error.message }) }]
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const { symbolId, symbolIds, level, intent } = parsed.data;
|
|
454
|
+
const ids = symbolIds ?? (symbolId ? [symbolId] : []);
|
|
455
|
+
const { graph, indexedFiles } = getGraph();
|
|
456
|
+
const results = [];
|
|
457
|
+
for (const id of ids) {
|
|
458
|
+
const slice = query.getLogicSlice(graph, id);
|
|
459
|
+
if (slice) {
|
|
460
|
+
const formatted = formatter.format(slice, level);
|
|
461
|
+
if (intent && Array.isArray(formatted["dependencies"])) {
|
|
462
|
+
formatted["dependencies"] = filterByIntent(
|
|
463
|
+
formatted["dependencies"],
|
|
464
|
+
intent
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
results.push(formatted);
|
|
468
|
+
} else {
|
|
469
|
+
results.push({ found: false, symbolId: id, hint: 'Symbol not found. Run "ctxo index".' });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const responseData = ids.length === 1 ? results[0] : { batch: true, results };
|
|
473
|
+
const body = applyEnricher(enrich, responseData, parsed.data, "get_logic_slice");
|
|
474
|
+
const payload = masking.mask(JSON.stringify(wrapResponse(body)));
|
|
475
|
+
const content = [];
|
|
476
|
+
if (staleness) {
|
|
477
|
+
const warning = staleness.check(indexedFiles);
|
|
478
|
+
if (warning) {
|
|
479
|
+
content.push({ type: "text", text: `\u26A0\uFE0F ${warning.message}` });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
content.push({ type: "text", text: payload });
|
|
483
|
+
return { content };
|
|
484
|
+
} catch (err) {
|
|
485
|
+
return {
|
|
486
|
+
content: [{ type: "text", text: JSON.stringify({ error: true, message: err.message }) }]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
return handler;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export {
|
|
494
|
+
LogicSliceQuery,
|
|
495
|
+
withoutAnnotations,
|
|
496
|
+
setWorkspaceMeta,
|
|
497
|
+
getThreshold,
|
|
498
|
+
wrapResponse,
|
|
499
|
+
filterByIntent,
|
|
500
|
+
describeError,
|
|
501
|
+
applyEnricher,
|
|
502
|
+
collectEnrichers,
|
|
503
|
+
buildGraphFromStorage,
|
|
504
|
+
buildGraphFromIndices,
|
|
505
|
+
getGraphAndFiles,
|
|
506
|
+
handleGetLogicSlice
|
|
507
|
+
};
|
|
508
|
+
//# sourceMappingURL=chunk-L2CAUER6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/response-envelope.ts","../src/core/describe-error.ts","../src/adapters/mcp/enrich-payload.ts","../src/adapters/mcp/get-logic-slice.ts","../src/core/logic-slice/logic-slice-query.ts","../src/core/detail-levels/detail-formatter.ts","../src/core/intent-filter.ts"],"sourcesContent":["/**\n * Response envelope — adds _meta field and truncates large payloads.\n *\n * Applied between payload construction and masking in every MCP handler.\n * Threshold default: 8192 bytes (~8KB). Configurable via CTXO_RESPONSE_LIMIT env.\n */\n\nconst DEFAULT_THRESHOLD = 8192;\n\nexport interface ResponseMeta {\n totalItems: number;\n returnedItems: number;\n truncated: boolean;\n totalBytes: number;\n hint?: string;\n /**\n * Workspace descriptor (forward-compat for Monorepo Workspace PRD).\n * v0.7: `root` is set at startup; `package` is always undefined.\n */\n workspace?: {\n root: string;\n package?: string;\n };\n /**\n * How far the community snapshot has drifted from current git HEAD.\n * Only populated on cluster-aware MCP tools (overlay, blast-radius,\n * why-context, pr-impact). Tells AI agents whether the cluster / drift /\n * boundary data they are about to act on is fresh or N commits stale.\n */\n snapshotStaleness?: {\n snapshotCommit: string;\n currentHeadCommit?: string;\n commitsBehind?: number;\n hint?: string;\n };\n}\n\nlet workspaceMeta: ResponseMeta['workspace'] | undefined;\n\n/**\n * Set the workspace descriptor attached to every subsequent `_meta` envelope.\n * Called once at composition root after `detectWorkspace()` resolves.\n */\nexport function setWorkspaceMeta(workspace: ResponseMeta['workspace']): void {\n workspaceMeta = workspace;\n}\n\nexport function getWorkspaceMeta(): ResponseMeta['workspace'] | undefined {\n return workspaceMeta;\n}\n\n/**\n * Known array field names in tool responses that can be truncated.\n * Maps field name → drill-in hint for the LLM.\n */\nconst TRUNCATABLE_FIELDS: Record<string, string> = {\n impactedSymbols: 'Use search_symbols to find specific impacted symbols, or get_blast_radius with a confidence filter.',\n importers: 'Use find_importers with edgeKinds filter or maxDepth to narrow results.',\n deadSymbols: 'Use search_symbols with kind filter to find specific dead symbols.',\n unusedExports: 'Use find_importers to check specific symbols.',\n results: 'Use search_symbols or get_ranked_context with a narrower query.',\n rankings: 'Use get_symbol_importance with kind or filePattern filter to narrow results.',\n context: 'Use get_context_for_task with a smaller tokenBudget.',\n hierarchies: 'Use get_class_hierarchy with a specific symbolId.',\n scaffolding: 'Review the scaffolding markers directly in the listed files.',\n deadFiles: 'Use find_dead_code with includeTests to adjust scope.',\n files: 'Use get_changed_symbols with a narrower since ref or smaller maxFiles.',\n clusters: 'Use get_architectural_overlay with mode=communities and layer filter for a focused view.',\n candidates:\n 'Use get_architectural_overlay with mode=contexts and candidate: <id> for one candidate in full, or ctxo contexts --json for the full contexts view.',\n clustersAffected: 'Use get_blast_radius on individual changed symbols for per-cluster impact.',\n};\n\n/** The response limit in bytes: `CTXO_RESPONSE_LIMIT` when it is a positive integer, else 8192. */\nexport function getThreshold(): number {\n const env = process.env['CTXO_RESPONSE_LIMIT'];\n if (env) {\n const n = parseInt(env, 10);\n if (!isNaN(n) && n > 0) return n;\n }\n return DEFAULT_THRESHOLD;\n}\n\n/**\n * Find the largest array field in a payload object that is in our truncatable list.\n */\nfunction findTruncatableArray(data: Record<string, unknown>): { key: string; arr: unknown[] } | null {\n let best: { key: string; arr: unknown[] } | null = null;\n for (const [key, value] of Object.entries(data)) {\n if (Array.isArray(value) && key in TRUNCATABLE_FIELDS) {\n if (!best || value.length > best.arr.length) {\n best = { key, arr: value };\n }\n }\n }\n return best;\n}\n\n/**\n * Wrap a payload object with _meta and truncate if needed.\n *\n * @param data - The raw response object (before JSON.stringify)\n * @returns The data object with _meta added (and arrays truncated if over threshold)\n */\nexport interface WrapResponseExtras {\n snapshotStaleness?: ResponseMeta['snapshotStaleness'];\n}\n\nfunction buildMeta(\n base: Omit<ResponseMeta, 'workspace' | 'snapshotStaleness'>,\n extras?: WrapResponseExtras,\n): ResponseMeta {\n const meta: ResponseMeta = workspaceMeta ? { ...base, workspace: workspaceMeta } : base;\n if (extras?.snapshotStaleness) {\n return { ...meta, snapshotStaleness: extras.snapshotStaleness };\n }\n return meta;\n}\n\nexport function wrapResponse(\n data: Record<string, unknown>,\n extras?: WrapResponseExtras,\n): Record<string, unknown> {\n const threshold = getThreshold();\n const fullJson = JSON.stringify(data);\n const totalBytes = Buffer.byteLength(fullJson, 'utf-8');\n\n const truncatable = findTruncatableArray(data);\n const totalItems = truncatable ? truncatable.arr.length : 0;\n\n // Under threshold — return with _meta, no truncation\n if (totalBytes <= threshold) {\n return {\n ...data,\n _meta: buildMeta(\n {\n totalItems,\n returnedItems: totalItems,\n truncated: false,\n totalBytes,\n },\n extras,\n ),\n };\n }\n\n // Over threshold — truncate the largest array\n if (!truncatable || truncatable.arr.length <= 1) {\n return {\n ...data,\n _meta: buildMeta(\n {\n totalItems,\n returnedItems: totalItems,\n truncated: false,\n totalBytes,\n },\n extras,\n ),\n };\n }\n\n // Binary search for the number of items that fit within threshold\n let lo = 1;\n let hi = truncatable.arr.length;\n\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n const trial = {\n ...data,\n [truncatable.key]: truncatable.arr.slice(0, mid),\n _meta: buildMeta(\n {\n totalItems,\n returnedItems: mid,\n truncated: true,\n totalBytes,\n hint: TRUNCATABLE_FIELDS[truncatable.key],\n },\n extras,\n ),\n };\n const size = Buffer.byteLength(JSON.stringify(trial), 'utf-8');\n if (size <= threshold) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n\n const hint = TRUNCATABLE_FIELDS[truncatable.key];\n\n return {\n ...data,\n [truncatable.key]: truncatable.arr.slice(0, lo),\n _meta: buildMeta(\n {\n totalItems,\n returnedItems: lo,\n truncated: true,\n totalBytes,\n hint,\n },\n extras,\n ),\n };\n}\n","/**\n * Text for a thrown value, safe for a log line or an error payload. It never throws: a non-Error throw\n * (a string, a null-prototype object, a value with a hostile `message` or `toString`) still gives a string.\n */\nexport function describeError(err: unknown): string {\n try {\n if (err instanceof Error) {\n const message = err.message;\n if (typeof message === 'string') return message;\n }\n if (typeof err === 'string') return err;\n return String(err);\n } catch {\n try {\n return Object.prototype.toString.call(err);\n } catch {\n return '[unprintable error]';\n }\n }\n}\n","import type { CoreToolName, LoadedModule, ResponseEnricher } from '../../ports/i-ctxo-module.js';\nimport { createLogger } from '../../core/logger.js';\nimport { describeError } from '../../core/describe-error.js';\n\nconst log = createLogger('ctxo:modules');\n\n/**\n * The response enricher seam (ADR-018): a module adds fields to a core tool's payload before the\n * `_meta` envelope, truncation and masking see it, so all three treat the added fields like the core's.\n */\n\n/**\n * `payload` with the fields `enrich` adds. The enricher works on a copy, so a throwing enricher, or one\n * that returns something other than an object, leaves the response exactly as the core built it; either\n * case logs one warning for the call. Without an enricher the payload itself is returned.\n *\n * @param args - the tool's validated arguments\n */\nexport function applyEnricher(\n enrich: ResponseEnricher | undefined,\n payload: Record<string, unknown>,\n args: Readonly<Record<string, unknown>>,\n tool: CoreToolName,\n): Record<string, unknown> {\n if (enrich === undefined) return payload;\n try {\n const enriched: unknown = enrich(structuredClone(payload), args);\n if (!isPlainObject(enriched) || !isPlainData(enriched, new Set())) {\n log.warn(\n `The ${tool} enricher returned something other than a plain JSON payload object; its fields are left out of this response`,\n );\n return payload;\n }\n // The response is serialised with JSON; a value JSON cannot hold (a BigInt) is caught here, not later.\n JSON.stringify(enriched);\n return enriched;\n } catch (err) {\n log.warn(`The ${tool} enricher failed: ${describeError(err)}; its fields are left out of this response`);\n return payload;\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Whether `value` holds only JSON data: strings, numbers, booleans, null, arrays and plain objects (an\n * `Object.prototype` or null prototype), nested to any depth and without a cycle. A Promise, Map, Set,\n * Date, class instance or function fails it.\n */\nfunction isPlainData(value: unknown, path: Set<object>): boolean {\n if (value === null) return true;\n const type = typeof value;\n if (type === 'string' || type === 'number' || type === 'boolean') return true;\n if (type !== 'object') return false;\n const node = value as object;\n if (path.has(node)) return false;\n if (!Array.isArray(node) && !isPlainObject(node)) return false;\n path.add(node);\n try {\n for (const child of Array.isArray(node) ? node : Object.values(node)) {\n // JSON drops an undefined property; an undefined array slot becomes null. Both are harmless.\n if (child !== undefined && !isPlainData(child, path)) return false;\n }\n } finally {\n path.delete(node);\n }\n return true;\n}\n\n/**\n * The enricher for each core tool that a loaded module enriches. Several modules enriching one tool run in\n * load order, each on the previous one's output, and each is guarded on its own: a later enricher that\n * throws drops only its own fields. A module whose `enrich` cannot be read contributes nothing.\n */\nexport function collectEnrichers(modules: readonly LoadedModule[]): Map<CoreToolName, ResponseEnricher> {\n const chains = new Map<CoreToolName, ResponseEnricher[]>();\n for (const module of modules) {\n let entries: Array<[string, unknown]>;\n try {\n entries = Object.entries(module.registration.enrich ?? {});\n } catch (err) {\n log.warn(`${module.name} enrichers could not be read: ${describeError(err)}`);\n continue;\n }\n for (const [tool, enrich] of entries) {\n if (typeof enrich !== 'function') continue;\n const chain = chains.get(tool as CoreToolName) ?? [];\n chain.push(enrich as ResponseEnricher);\n chains.set(tool as CoreToolName, chain);\n }\n }\n const enrichers = new Map<CoreToolName, ResponseEnricher>();\n for (const [tool, chain] of chains) {\n enrichers.set(\n tool,\n chain.length === 1\n ? chain[0]!\n : (payload, args) => chain.reduce((current, enrich) => applyEnricher(enrich, current, args, tool), payload),\n );\n }\n return enrichers;\n}\n","import { z } from 'zod';\nimport type { IStoragePort } from '../../ports/i-storage-port.js';\nimport type { IMaskingPort } from '../../ports/i-masking-port.js';\nimport { SymbolGraph } from '../../core/graph/symbol-graph.js';\nimport { JsonIndexReader } from '../storage/json-index-reader.js';\nimport { LogicSliceQuery } from '../../core/logic-slice/logic-slice-query.js';\nimport { DetailFormatter } from '../../core/detail-levels/detail-formatter.js';\nimport { DetailLevelSchema, type FileIndex } from '../../core/types.js';\nimport { wrapResponse } from '../../core/response-envelope.js';\nimport { filterByIntent } from '../../core/intent-filter.js';\nimport type { ResponseEnricher } from '../../ports/i-ctxo-module.js';\nimport { applyEnricher } from './enrich-payload.js';\n\nexport const InputSchema = z.object({\n symbolId: z.string().min(1).optional(),\n symbolIds: z.array(z.string().min(1)).optional(),\n level: DetailLevelSchema.optional().default(3),\n intent: z.string().optional().describe('Filter dependencies by intent keywords (e.g., \"core\", \"adapter\", \"storage\")'),\n}).refine(\n (data) => data.symbolId || (data.symbolIds && data.symbolIds.length > 0),\n { message: 'Either symbolId or symbolIds must be provided' },\n);\n\nexport type GetLogicSliceInput = z.infer<typeof InputSchema>;\n\nexport function getLogicSliceInputSchema() {\n return {\n type: 'object' as const,\n properties: {\n symbolId: { type: 'string' as const, description: 'The symbol ID to retrieve (format: file::name::kind)' },\n level: { type: 'number' as const, enum: [1, 2, 3, 4], description: 'Detail level (1=minimal, 4=full)', default: 3 },\n },\n required: ['symbolId'] as const,\n };\n}\n\nexport function buildGraphFromStorage(storage: IStoragePort): SymbolGraph {\n const graph = new SymbolGraph();\n for (const sym of storage.getAllSymbols()) {\n graph.addNode(sym);\n }\n for (const edge of storage.getAllEdges()) {\n graph.addEdge(edge);\n }\n return graph;\n}\n\n/**\n * Build graph + return file list from a single JSON index read.\n * Returning both from one source prevents staleness warnings from diverging\n * from the graph data (see issue #40).\n */\nexport function buildGraphAndFilesFromJsonIndex(\n ctxoRoot: string,\n): { graph: SymbolGraph; indexedFiles: string[] } {\n const reader = new JsonIndexReader(ctxoRoot);\n const indices = reader.readAll();\n return { graph: buildGraphFromIndices(indices), indexedFiles: indices.map((fileIndex) => fileIndex.file) };\n}\n\n/** Build a graph from file indices already read: every node first, then every edge. */\nexport function buildGraphFromIndices(indices: readonly FileIndex[]): SymbolGraph {\n const graph = new SymbolGraph();\n for (const fileIndex of indices) {\n for (const sym of fileIndex.symbols) {\n graph.addNode(sym);\n }\n }\n for (const fileIndex of indices) {\n for (const edge of fileIndex.edges) {\n graph.addEdge(edge);\n }\n }\n return graph;\n}\n\n/**\n * Build graph directly from JSON index files (bypasses SQLite cache).\n * Always reads fresh data from disk — works even if ctxo index ran in another process.\n */\nexport function buildGraphFromJsonIndex(ctxoRoot: string): SymbolGraph {\n return buildGraphAndFilesFromJsonIndex(ctxoRoot).graph;\n}\n\n/**\n * Returns graph + indexedFiles from JSON index, falling back to SQLite storage\n * when JSON is empty. Keeps graph data and staleness check file list aligned.\n */\nexport function getGraphAndFiles(\n ctxoRoot: string,\n storage: IStoragePort,\n): { graph: SymbolGraph; indexedFiles: string[] } {\n const json = buildGraphAndFilesFromJsonIndex(ctxoRoot);\n if (json.graph.nodeCount > 0) return json;\n return { graph: buildGraphFromStorage(storage), indexedFiles: storage.listIndexedFiles() };\n}\n\nexport interface StalenessCheck {\n check(indexedFiles: readonly string[]): { message: string } | undefined;\n}\n\nexport function handleGetLogicSlice(\n storage: IStoragePort,\n masking: IMaskingPort,\n staleness?: StalenessCheck,\n ctxoRoot = '.ctxo',\n enrich?: ResponseEnricher,\n) {\n const query = new LogicSliceQuery();\n const formatter = new DetailFormatter();\n\n // Build graph fresh on each call — try JSON index first (always up to date),\n // fall back to SQLite storage (for tests and when JSON not available).\n const getGraph = () => getGraphAndFiles(ctxoRoot, storage);\n\n const handler = (args: Record<string, unknown>) => {\n try {\n const parsed = InputSchema.safeParse(args);\n if (!parsed.success) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: true, message: parsed.error.message }) }],\n };\n }\n\n const { symbolId, symbolIds, level, intent } = parsed.data;\n const ids = symbolIds ?? (symbolId ? [symbolId] : []);\n\n const { graph, indexedFiles } = getGraph();\n\n // Batch support: query multiple symbols\n const results = [];\n for (const id of ids) {\n const slice = query.getLogicSlice(graph, id);\n if (slice) {\n const formatted = formatter.format(slice, level) as unknown as Record<string, unknown>;\n // Apply intent filter to dependencies if present\n if (intent && Array.isArray(formatted['dependencies'])) {\n formatted['dependencies'] = filterByIntent(\n formatted['dependencies'] as Record<string, unknown>[],\n intent,\n );\n }\n results.push(formatted);\n } else {\n results.push({ found: false, symbolId: id, hint: 'Symbol not found. Run \"ctxo index\".' });\n }\n }\n\n // Single symbol: return directly. Batch: return array.\n const responseData = ids.length === 1 ? results[0] : { batch: true, results };\n\n // A module's enricher sees the single slice or the { batch, results } object; then envelope and masking.\n const body = applyEnricher(enrich, responseData as Record<string, unknown>, parsed.data, 'get_logic_slice');\n const payload = masking.mask(JSON.stringify(wrapResponse(body)));\n\n // Check staleness\n const content: Array<{ type: 'text'; text: string }> = [];\n if (staleness) {\n const warning = staleness.check(indexedFiles);\n if (warning) {\n content.push({ type: 'text', text: `⚠️ ${warning.message}` });\n }\n }\n content.push({ type: 'text', text: payload });\n\n return { content };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: true, message: (err as Error).message }) }],\n };\n }\n };\n\n return handler;\n}\n","import type { SymbolNode, GraphEdge, LogicSliceResult } from '../types.js';\nimport type { SymbolGraph } from '../graph/symbol-graph.js';\n\nexport class LogicSliceQuery {\n getLogicSlice(\n graph: SymbolGraph,\n symbolId: string,\n maxDepth: number = Infinity,\n ): LogicSliceResult | undefined {\n // Resolve the id once and walk from the node it names: a 2-part data id walks its winning definition, and\n // a full id of another copy walks that copy's own edges.\n const root = graph.getNode(symbolId);\n if (!root) return undefined;\n\n const visited = new Set<string>([root.symbolId]);\n const dependencies: SymbolNode[] = [];\n const collectedEdges: GraphEdge[] = [];\n\n // BFS transitive closure\n const queue: Array<{ id: string; depth: number }> = [{ id: root.symbolId, depth: 0 }];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n if (current.depth >= maxDepth) continue;\n\n const edges = graph.getForwardEdges(current.id);\n for (const edge of edges) {\n const depNode = graph.getNode(edge.to);\n // Only include edges whose target node exists in the graph\n if (!depNode) continue;\n\n collectedEdges.push(edge);\n\n if (visited.has(edge.to)) continue;\n visited.add(edge.to);\n\n dependencies.push(depNode);\n queue.push({ id: edge.to, depth: current.depth + 1 });\n }\n }\n\n return { root, dependencies, edges: collectedEdges };\n }\n}\n","import type {\n LogicSliceResult,\n FormattedSlice,\n DetailLevel,\n SymbolNode,\n GraphEdge,\n} from '../types.js';\n\nconst L1_MAX_LINES = 150;\nconst L4_TOKEN_BUDGET = 8000;\n\n/**\n * `node` without its `annotations`. Node output leaves them out everywhere except logic slice L4, which\n * carries them unchanged: up to 20 attributes per C# symbol plus whole column lists would shrink every\n * other slice before its dependencies.\n *\n * Only a graph built from the JSON index has annotations. The SQLite fallback stores none, so even L4\n * carries none when the graph comes from there.\n */\nexport function withoutAnnotations(node: SymbolNode): SymbolNode {\n if (node.annotations === undefined) return node;\n const { annotations: _annotations, ...rest } = node;\n return rest;\n}\n\nexport class DetailFormatter {\n format(slice: LogicSliceResult, level: DetailLevel): FormattedSlice {\n switch (level) {\n case 1:\n return this.formatL1(slice);\n case 2:\n return this.formatL2(slice);\n case 3:\n return this.formatL3(slice);\n case 4:\n return this.formatL4(slice);\n }\n }\n\n private formatL1(slice: LogicSliceResult): FormattedSlice {\n const root = slice.root;\n const lineCount = root.endLine - root.startLine + 1;\n\n const clampedRoot: SymbolNode = lineCount > L1_MAX_LINES\n ? { ...root, endLine: root.startLine + L1_MAX_LINES - 1 }\n : root;\n\n return {\n root: withoutAnnotations(clampedRoot),\n dependencies: [],\n edges: [],\n level: 1,\n levelDescription: 'L1: Root symbol signature only (no dependencies)',\n ...(lineCount > L1_MAX_LINES ? { truncation: { truncated: true as const, reason: 'token_budget_exceeded' as const } } : {}),\n };\n }\n\n private formatL2(slice: LogicSliceResult): FormattedSlice {\n // L2: root + depth-1 direct dependencies\n const directEdges = slice.edges.filter((e) => e.from === slice.root.symbolId);\n const directDepIds = new Set(directEdges.map((e) => e.to));\n const directDeps = slice.dependencies.filter((d) => directDepIds.has(d.symbolId));\n\n return {\n root: withoutAnnotations(slice.root),\n dependencies: directDeps.map(withoutAnnotations),\n edges: directEdges,\n level: 2,\n levelDescription: 'L2: Root + direct dependencies (depth 1)',\n };\n }\n\n private formatL3(slice: LogicSliceResult): FormattedSlice {\n return {\n root: withoutAnnotations(slice.root),\n dependencies: slice.dependencies.map(withoutAnnotations),\n edges: slice.edges,\n level: 3,\n levelDescription: 'L3: Full transitive dependency closure',\n };\n }\n\n private formatL4(slice: LogicSliceResult): FormattedSlice {\n // L4: full closure with token budget enforcement. Nodes keep their annotations (see withoutAnnotations;\n // a graph from the SQLite fallback has none to keep).\n const estimatedTokens = this.estimateTokens(slice);\n\n if (estimatedTokens > L4_TOKEN_BUDGET) {\n // Truncate dependencies to fit budget\n const truncated = this.truncateToTokenBudget(slice, L4_TOKEN_BUDGET);\n return {\n ...truncated,\n level: 4,\n levelDescription: 'L4: Full closure with 8K token budget (truncated)',\n truncation: {\n truncated: true,\n reason: 'token_budget_exceeded',\n },\n };\n }\n\n return {\n root: slice.root,\n dependencies: slice.dependencies,\n edges: slice.edges,\n level: 4,\n levelDescription: 'L4: Full closure with 8K token budget',\n };\n }\n\n private estimateTokens(slice: LogicSliceResult): number {\n // Rough estimate: ~4 chars per token\n let chars = this.symbolCharEstimate(slice.root);\n for (const dep of slice.dependencies) {\n chars += this.symbolCharEstimate(dep);\n }\n for (const edge of slice.edges) {\n chars += (edge.from.length + edge.to.length + edge.kind.length + 10);\n }\n return Math.ceil(chars / 4);\n }\n\n private symbolCharEstimate(node: SymbolNode): number {\n // Use byte offsets for exact size when available, otherwise estimate from line count\n if (node.startOffset !== undefined && node.endOffset !== undefined) {\n return node.endOffset - node.startOffset;\n }\n return (node.endLine - node.startLine + 1) * 40;\n }\n\n private truncateToTokenBudget(\n slice: LogicSliceResult,\n budget: number,\n ): { root: SymbolNode; dependencies: SymbolNode[]; edges: GraphEdge[] } {\n let currentTokens = this.symbolCharEstimate(slice.root) / 4;\n const deps: SymbolNode[] = [];\n const includedIds = new Set<string>([slice.root.symbolId]);\n\n for (const dep of slice.dependencies) {\n const depTokens = this.symbolCharEstimate(dep) / 4;\n if (currentTokens + depTokens > budget) break;\n currentTokens += depTokens;\n deps.push(dep);\n includedIds.add(dep.symbolId);\n }\n\n const edges = slice.edges.filter(\n (e) => includedIds.has(e.from) && includedIds.has(e.to),\n );\n\n return { root: slice.root, dependencies: deps, edges };\n }\n}\n","/**\n * Intent-based result filtering for MCP tool responses.\n *\n * When an `intent` string is provided, filters array results to only include\n * entries whose text fields (symbolId, file, name, kind, edgeKind, reason)\n * match at least one keyword from the intent.\n *\n * Keywords are extracted by splitting the intent on whitespace and lowercasing.\n * A match is found when any keyword appears as a substring of any text field.\n */\n\n/**\n * Extract lowercase keywords from an intent string.\n * Filters out very short words (< 2 chars) to avoid noise.\n */\nfunction extractKeywords(intent: string): string[] {\n return intent\n .toLowerCase()\n .split(/\\s+/)\n .filter(w => w.length >= 2);\n}\n\n/**\n * Check if any keyword matches any of the given text values (case-insensitive substring).\n */\nfunction matchesAny(keywords: string[], values: string[]): boolean {\n const lower = values.map(v => v.toLowerCase());\n return keywords.some(kw => lower.some(v => v.includes(kw)));\n}\n\n/**\n * Collect all string values from a record for matching.\n * Extracts from known fields: symbolId, file, name, kind, edgeKind, edgeKinds, reason, confidence.\n */\nfunction collectMatchableStrings(entry: Record<string, unknown>): string[] {\n const strings: string[] = [];\n for (const key of ['symbolId', 'file', 'name', 'kind', 'edgeKind', 'reason', 'confidence']) {\n const v = entry[key];\n if (typeof v === 'string') strings.push(v);\n }\n const edgeKinds = entry['edgeKinds'];\n if (Array.isArray(edgeKinds)) {\n for (const ek of edgeKinds) {\n if (typeof ek === 'string') strings.push(ek);\n }\n }\n return strings;\n}\n\n/**\n * Filter an array of result entries by intent keywords.\n * Returns only entries where at least one keyword matches a text field.\n *\n * @param items - Array of result objects\n * @param intent - User intent string (e.g., \"test coverage\", \"adapter\", \"security\")\n * @returns Filtered array (empty if no matches)\n */\nexport function filterByIntent<T extends Record<string, unknown>>(\n items: T[],\n intent: string | undefined,\n): T[] {\n if (!intent || intent.trim().length === 0) return items;\n\n const keywords = extractKeywords(intent);\n if (keywords.length === 0) return items;\n\n return items.filter(item => {\n const strings = collectMatchableStrings(item);\n return matchesAny(keywords, strings);\n });\n}\n"],"mappings":"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","names":[]}
|