@papyruslabsai/seshat-mcp 0.1.0 → 0.3.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.
@@ -0,0 +1,554 @@
1
+ /**
2
+ * Interpretation Functor Tools
3
+ *
4
+ * Each functor is I: J -> D — projecting the 9D JSTF-T coordinate space
5
+ * onto a domain-specific judgment. These are composite analyses built
6
+ * from the primitive dimensions (sigma, epsilon, delta, kappa, chi, tau, rho).
7
+ */
8
+ import { computeBlastRadius } from '../graph.js';
9
+ import { getLoader, getGraph, validateProject, entityLayer, entitySummary, } from './index.js';
10
+ // ─── Layer ordering for violation detection ──────────────────────
11
+ const LAYER_ORDER = {
12
+ route: 0,
13
+ controller: 1,
14
+ middleware: 2,
15
+ service: 3,
16
+ hook: 4,
17
+ repository: 5,
18
+ model: 6,
19
+ schema: 7,
20
+ utility: 8,
21
+ component: 1, // UI components are peers to controllers
22
+ };
23
+ // ─── Functor 1: find_dead_code ───────────────────────────────────
24
+ export function findDeadCode(args) {
25
+ const projErr = validateProject(args.project);
26
+ if (projErr)
27
+ return { error: projErr };
28
+ const { include_tests = false } = args;
29
+ const loader = getLoader();
30
+ const g = getGraph(args.project);
31
+ const entities = loader.getEntities(args.project);
32
+ // Entry points: routes, exported functions, test files, plugin registrations
33
+ const entryPointIds = new Set();
34
+ for (const e of entities) {
35
+ if (!e.id)
36
+ continue;
37
+ const layer = entityLayer(e);
38
+ // Routes and controllers are entry points
39
+ if (layer === 'route' || layer === 'controller') {
40
+ entryPointIds.add(e.id);
41
+ continue;
42
+ }
43
+ // Test entities are entry points
44
+ if (layer === 'test') {
45
+ entryPointIds.add(e.id);
46
+ continue;
47
+ }
48
+ // Plugin registrations
49
+ if (e.context?.exposure === 'framework') {
50
+ entryPointIds.add(e.id);
51
+ continue;
52
+ }
53
+ // Exported functions at the top level are entry points
54
+ if (typeof e.struct !== 'string' && e.struct?.exported) {
55
+ entryPointIds.add(e.id);
56
+ }
57
+ }
58
+ // BFS from all entry points through callees
59
+ const reachable = new Set(entryPointIds);
60
+ const queue = [...entryPointIds];
61
+ while (queue.length > 0) {
62
+ const current = queue.shift();
63
+ const calleeSet = g.callees.get(current);
64
+ if (!calleeSet)
65
+ continue;
66
+ for (const calleeId of calleeSet) {
67
+ if (!reachable.has(calleeId)) {
68
+ reachable.add(calleeId);
69
+ queue.push(calleeId);
70
+ }
71
+ }
72
+ }
73
+ // Unreachable = dead code candidates
74
+ let deadEntities = entities.filter(e => e.id && !reachable.has(e.id));
75
+ if (!include_tests) {
76
+ deadEntities = deadEntities.filter(e => entityLayer(e) !== 'test');
77
+ }
78
+ // Group by layer for overview
79
+ const byLayer = new Map();
80
+ for (const e of deadEntities) {
81
+ const l = entityLayer(e);
82
+ byLayer.set(l, (byLayer.get(l) || 0) + 1);
83
+ }
84
+ return {
85
+ totalEntities: entities.length,
86
+ entryPoints: entryPointIds.size,
87
+ reachable: reachable.size,
88
+ deadCount: deadEntities.length,
89
+ deadByLayer: Object.fromEntries([...byLayer.entries()].sort((a, b) => b[1] - a[1])),
90
+ dead: deadEntities.slice(0, 100).map(entitySummary),
91
+ };
92
+ }
93
+ // ─── Functor 2: find_layer_violations ────────────────────────────
94
+ export function findLayerViolations(args) {
95
+ const projErr = validateProject(args?.project);
96
+ if (projErr)
97
+ return { error: projErr };
98
+ const g = getGraph(args?.project);
99
+ const violations = [];
100
+ for (const [callerId, calleeIds] of g.callees) {
101
+ const callerEntity = g.entityById.get(callerId);
102
+ if (!callerEntity)
103
+ continue;
104
+ const callerLayer = entityLayer(callerEntity);
105
+ const callerOrder = LAYER_ORDER[callerLayer];
106
+ if (callerOrder === undefined)
107
+ continue;
108
+ for (const calleeId of calleeIds) {
109
+ const calleeEntity = g.entityById.get(calleeId);
110
+ if (!calleeEntity)
111
+ continue;
112
+ const calleeLayer = entityLayer(calleeEntity);
113
+ const calleeOrder = LAYER_ORDER[calleeLayer];
114
+ if (calleeOrder === undefined)
115
+ continue;
116
+ // Skip same-layer calls
117
+ if (callerLayer === calleeLayer)
118
+ continue;
119
+ // Backward call: lower layer calling higher layer
120
+ if (callerOrder > calleeOrder) {
121
+ violations.push({
122
+ from: entitySummary(callerEntity),
123
+ to: entitySummary(calleeEntity),
124
+ type: `backward: ${callerLayer}(${callerOrder}) -> ${calleeLayer}(${calleeOrder})`,
125
+ });
126
+ }
127
+ // Skip-layer: jumping over more than 1 layer
128
+ else if (calleeOrder - callerOrder > 2) {
129
+ violations.push({
130
+ from: entitySummary(callerEntity),
131
+ to: entitySummary(calleeEntity),
132
+ type: `skip-layer: ${callerLayer}(${callerOrder}) -> ${calleeLayer}(${calleeOrder})`,
133
+ });
134
+ }
135
+ }
136
+ }
137
+ // Group violations by type
138
+ const byType = new Map();
139
+ for (const v of violations) {
140
+ const typePrefix = v.type.split(':')[0];
141
+ byType.set(typePrefix, (byType.get(typePrefix) || 0) + 1);
142
+ }
143
+ return {
144
+ totalViolations: violations.length,
145
+ byType: Object.fromEntries(byType),
146
+ violations: violations.slice(0, 100),
147
+ };
148
+ }
149
+ // ─── Functor 3: get_coupling_metrics ─────────────────────────────
150
+ export function getCouplingMetrics(args) {
151
+ const projErr = validateProject(args.project);
152
+ if (projErr)
153
+ return { error: projErr };
154
+ const { group_by = 'module' } = args;
155
+ const loader = getLoader();
156
+ const g = getGraph(args.project);
157
+ const entities = loader.getEntities(args.project);
158
+ // Group entities
159
+ const groups = new Map();
160
+ for (const e of entities) {
161
+ if (!e.id)
162
+ continue;
163
+ const key = group_by === 'module'
164
+ ? (e.context?.module || 'unknown')
165
+ : entityLayer(e);
166
+ if (!groups.has(key))
167
+ groups.set(key, new Set());
168
+ groups.get(key).add(e.id);
169
+ }
170
+ const metrics = [];
171
+ for (const [groupName, memberIds] of groups) {
172
+ let internalEdges = 0;
173
+ let outgoingEdges = 0;
174
+ let incomingEdges = 0;
175
+ for (const memberId of memberIds) {
176
+ const calleeSet = g.callees.get(memberId);
177
+ if (calleeSet) {
178
+ for (const calleeId of calleeSet) {
179
+ if (memberIds.has(calleeId)) {
180
+ internalEdges++;
181
+ }
182
+ else {
183
+ outgoingEdges++;
184
+ }
185
+ }
186
+ }
187
+ const callerSet = g.callers.get(memberId);
188
+ if (callerSet) {
189
+ for (const callerId of callerSet) {
190
+ if (!memberIds.has(callerId)) {
191
+ incomingEdges++;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ const size = memberIds.size;
197
+ const maxInternalEdges = size * (size - 1); // directed
198
+ const cohesion = maxInternalEdges > 0 ? internalEdges / maxInternalEdges : 0;
199
+ const totalExternal = outgoingEdges + incomingEdges;
200
+ const coupling = totalExternal;
201
+ const instability = totalExternal > 0 ? outgoingEdges / totalExternal : 0;
202
+ metrics.push({
203
+ group: groupName,
204
+ size,
205
+ internalEdges,
206
+ externalEdges: totalExternal,
207
+ cohesion: Math.round(cohesion * 1000) / 1000,
208
+ coupling,
209
+ instability: Math.round(instability * 1000) / 1000,
210
+ });
211
+ }
212
+ // Sort by coupling (most coupled first)
213
+ metrics.sort((a, b) => b.coupling - a.coupling);
214
+ return {
215
+ groupBy: group_by,
216
+ groupCount: metrics.length,
217
+ metrics: metrics.slice(0, 50),
218
+ };
219
+ }
220
+ // ─── Functor 4: get_auth_matrix ──────────────────────────────────
221
+ export function getAuthMatrix(args) {
222
+ const projErr = validateProject(args?.project);
223
+ if (projErr)
224
+ return { error: projErr };
225
+ const loader = getLoader();
226
+ const entities = loader.getEntities(args?.project);
227
+ const apiEntities = entities.filter(e => {
228
+ const layer = entityLayer(e);
229
+ return layer === 'route' || layer === 'controller' ||
230
+ e.context?.exposure === 'api';
231
+ });
232
+ const withAuth = [];
233
+ const withoutAuth = [];
234
+ const inconsistencies = [];
235
+ for (const e of apiEntities) {
236
+ const constraints = e.constraints;
237
+ const hasAuth = constraints && !Array.isArray(constraints) &&
238
+ constraints.auth && constraints.auth !== 'none';
239
+ const summary = entitySummary(e);
240
+ if (hasAuth) {
241
+ withAuth.push({
242
+ ...summary,
243
+ auth: constraints.auth,
244
+ });
245
+ }
246
+ else {
247
+ withoutAuth.push(summary);
248
+ }
249
+ // Check for inconsistencies: has auth decorator but marked as public
250
+ if (hasAuth && e.context?.visibility === 'public') {
251
+ inconsistencies.push({
252
+ entity: summary,
253
+ issue: 'Has auth requirements but visibility is public',
254
+ });
255
+ }
256
+ // Has DB access but no auth
257
+ if (!hasAuth) {
258
+ const sideEffects = constraints && !Array.isArray(constraints)
259
+ ? constraints.sideEffects : undefined;
260
+ if (Array.isArray(sideEffects) && sideEffects.some((s) => s === 'db' || s === 'database')) {
261
+ inconsistencies.push({
262
+ entity: summary,
263
+ issue: 'DB access without auth — potential security gap',
264
+ });
265
+ }
266
+ }
267
+ }
268
+ return {
269
+ totalApiEntities: apiEntities.length,
270
+ withAuth: withAuth.length,
271
+ withoutAuth: withoutAuth.length,
272
+ inconsistencies: inconsistencies.length,
273
+ authenticated: withAuth.slice(0, 50),
274
+ unauthenticated: withoutAuth.slice(0, 50),
275
+ issues: inconsistencies.slice(0, 50),
276
+ };
277
+ }
278
+ // ─── Functor 5: find_error_gaps ──────────────────────────────────
279
+ export function findErrorGaps(args) {
280
+ const projErr = validateProject(args?.project);
281
+ if (projErr)
282
+ return { error: projErr };
283
+ const loader = getLoader();
284
+ const g = getGraph(args?.project);
285
+ const entities = loader.getEntities(args?.project);
286
+ // Find all fallible entities (throws === true or has THROWS tag)
287
+ const fallibleIds = new Set();
288
+ for (const e of entities) {
289
+ if (!e.id)
290
+ continue;
291
+ // Check kappa.throws
292
+ const constraints = e.constraints;
293
+ if (constraints && !Array.isArray(constraints) && constraints.throws) {
294
+ fallibleIds.add(e.id);
295
+ continue;
296
+ }
297
+ // Check tau.self.fallible
298
+ const traits = e.traits;
299
+ if (traits && !Array.isArray(traits) && traits.self?.fallible) {
300
+ fallibleIds.add(e.id);
301
+ continue;
302
+ }
303
+ // Check for network/db side effects (implicitly fallible)
304
+ if (constraints && !Array.isArray(constraints) && Array.isArray(constraints.sideEffects)) {
305
+ const effects = constraints.sideEffects;
306
+ if (effects.some((s) => s === 'network' || s === 'db' || s === 'database' || s === 'filesystem' || s === 'fs')) {
307
+ fallibleIds.add(e.id);
308
+ }
309
+ }
310
+ }
311
+ // Find callers of fallible entities that lack error handling
312
+ const gaps = [];
313
+ for (const fallibleId of fallibleIds) {
314
+ const callerSet = g.callers.get(fallibleId);
315
+ if (!callerSet)
316
+ continue;
317
+ const fallibleEntity = g.entityById.get(fallibleId);
318
+ if (!fallibleEntity)
319
+ continue;
320
+ for (const callerId of callerSet) {
321
+ const callerEntity = g.entityById.get(callerId);
322
+ if (!callerEntity)
323
+ continue;
324
+ // Check if caller has error handling
325
+ const callerConstraints = callerEntity.constraints;
326
+ const hasErrorHandling = callerConstraints && !Array.isArray(callerConstraints) &&
327
+ callerConstraints.errorHandling &&
328
+ (callerConstraints.errorHandling.tryCatch || callerConstraints.errorHandling.catchClause);
329
+ if (!hasErrorHandling) {
330
+ gaps.push({
331
+ caller: entitySummary(callerEntity),
332
+ fallibleCallee: entitySummary(fallibleEntity),
333
+ issue: 'Calls fallible function without try/catch',
334
+ });
335
+ }
336
+ }
337
+ }
338
+ return {
339
+ totalFallible: fallibleIds.size,
340
+ errorGaps: gaps.length,
341
+ gaps: gaps.slice(0, 100),
342
+ };
343
+ }
344
+ // ─── Functor 6: get_test_coverage ────────────────────────────────
345
+ export function getTestCoverage(args) {
346
+ const projErr = validateProject(args.project);
347
+ if (projErr)
348
+ return { error: projErr };
349
+ const { weight_by_blast_radius = false } = args;
350
+ const loader = getLoader();
351
+ const g = getGraph(args.project);
352
+ const entities = loader.getEntities(args.project);
353
+ // Partition into test and non-test entities
354
+ const testIds = new Set();
355
+ const productionEntities = [];
356
+ for (const e of entities) {
357
+ if (!e.id)
358
+ continue;
359
+ if (entityLayer(e) === 'test') {
360
+ testIds.add(e.id);
361
+ }
362
+ else {
363
+ productionEntities.push(e);
364
+ }
365
+ }
366
+ // BFS from test entities through callees to find what they exercise
367
+ const exercised = new Set();
368
+ const queue = [...testIds];
369
+ const visited = new Set(testIds);
370
+ while (queue.length > 0) {
371
+ const current = queue.shift();
372
+ const calleeSet = g.callees.get(current);
373
+ if (!calleeSet)
374
+ continue;
375
+ for (const calleeId of calleeSet) {
376
+ if (!visited.has(calleeId)) {
377
+ visited.add(calleeId);
378
+ if (!testIds.has(calleeId)) {
379
+ exercised.add(calleeId);
380
+ }
381
+ queue.push(calleeId);
382
+ }
383
+ }
384
+ }
385
+ const covered = productionEntities.filter(e => exercised.has(e.id));
386
+ const uncovered = productionEntities.filter(e => !exercised.has(e.id));
387
+ const result = {
388
+ totalProduction: productionEntities.length,
389
+ totalTests: testIds.size,
390
+ coveredCount: covered.length,
391
+ uncoveredCount: uncovered.length,
392
+ coveragePercent: productionEntities.length > 0
393
+ ? Math.round((covered.length / productionEntities.length) * 1000) / 10
394
+ : 0,
395
+ };
396
+ if (weight_by_blast_radius && uncovered.length > 0) {
397
+ // Compute blast radius for each uncovered entity to prioritize what to test
398
+ const prioritized = uncovered
399
+ .map(e => {
400
+ const br = computeBlastRadius(g, new Set([e.id]));
401
+ return {
402
+ ...entitySummary(e),
403
+ blastRadius: br.affected.length,
404
+ };
405
+ })
406
+ .sort((a, b) => b.blastRadius - a.blastRadius);
407
+ result.uncoveredByPriority = prioritized.slice(0, 50);
408
+ }
409
+ else {
410
+ // Group uncovered by layer
411
+ const byLayer = new Map();
412
+ for (const e of uncovered) {
413
+ const l = entityLayer(e);
414
+ byLayer.set(l, (byLayer.get(l) || 0) + 1);
415
+ }
416
+ result.uncoveredByLayer = Object.fromEntries([...byLayer.entries()].sort((a, b) => b[1] - a[1]));
417
+ result.uncovered = uncovered.slice(0, 50).map(entitySummary);
418
+ }
419
+ return result;
420
+ }
421
+ // ─── Functor 7: get_optimal_context ──────────────────────────────
422
+ export function getOptimalContext(args) {
423
+ const projErr = validateProject(args.project);
424
+ if (projErr)
425
+ return { error: projErr };
426
+ const { target_entity, max_tokens = 8000, strategy = 'bfs' } = args;
427
+ const loader = getLoader();
428
+ const g = getGraph(args.project);
429
+ const entity = loader.getEntityById(target_entity, args.project) || loader.getEntityByName(target_entity, args.project);
430
+ if (!entity) {
431
+ return { error: `Entity not found: ${target_entity}` };
432
+ }
433
+ const targetId = entity.id;
434
+ // Estimate tokens for an entity based on its dimensions
435
+ function estimateTokens(e) {
436
+ let tokens = 50; // Base: name, id, layer
437
+ if (e.struct && typeof e.struct !== 'string') {
438
+ tokens += 20; // signature
439
+ tokens += (e.struct.params?.length || 0) * 10;
440
+ }
441
+ if (e.edges?.calls)
442
+ tokens += e.edges.calls.length * 8;
443
+ if (e.edges?.imports)
444
+ tokens += e.edges.imports.length * 6;
445
+ if (e.data?.inputs)
446
+ tokens += e.data.inputs.length * 10;
447
+ if (e.constraints && typeof e.constraints === 'object' && !Array.isArray(e.constraints)) {
448
+ tokens += 30;
449
+ }
450
+ return tokens;
451
+ }
452
+ const candidates = [];
453
+ if (strategy === 'blast_radius') {
454
+ // Use blast radius to get all related entities with depth
455
+ const br = computeBlastRadius(g, new Set([targetId]));
456
+ for (const id of br.affected) {
457
+ if (id === targetId)
458
+ continue;
459
+ const e = g.entityById.get(id);
460
+ if (!e)
461
+ continue;
462
+ const depth = Math.abs(br.depthMap[id] || 99);
463
+ const tokens = estimateTokens(e);
464
+ // Relevance decays with distance; direct callers/callees are most valuable
465
+ const relevance = 1 / (1 + depth);
466
+ candidates.push({
467
+ entity: entitySummary(e),
468
+ relevance: Math.round(relevance * 1000) / 1000,
469
+ distance: depth,
470
+ direction: (br.depthMap[id] || 0) < 0 ? 'upstream' : 'downstream',
471
+ tokens,
472
+ });
473
+ }
474
+ }
475
+ else {
476
+ // BFS from target in both directions with distance tracking
477
+ const distances = new Map();
478
+ // BFS callees (downstream)
479
+ const downQueue = [[targetId, 0]];
480
+ const downVisited = new Set([targetId]);
481
+ while (downQueue.length > 0) {
482
+ const [current, d] = downQueue.shift();
483
+ if (d > 5)
484
+ continue;
485
+ const calleeSet = g.callees.get(current);
486
+ if (!calleeSet)
487
+ continue;
488
+ for (const id of calleeSet) {
489
+ if (!downVisited.has(id)) {
490
+ downVisited.add(id);
491
+ distances.set(id, { dist: d + 1, dir: 'downstream' });
492
+ downQueue.push([id, d + 1]);
493
+ }
494
+ }
495
+ }
496
+ // BFS callers (upstream)
497
+ const upQueue = [[targetId, 0]];
498
+ const upVisited = new Set([targetId]);
499
+ while (upQueue.length > 0) {
500
+ const [current, d] = upQueue.shift();
501
+ if (d > 5)
502
+ continue;
503
+ const callerSet = g.callers.get(current);
504
+ if (!callerSet)
505
+ continue;
506
+ for (const id of callerSet) {
507
+ if (!upVisited.has(id)) {
508
+ upVisited.add(id);
509
+ // Only override if not already found with shorter distance
510
+ if (!distances.has(id) || distances.get(id).dist > d + 1) {
511
+ distances.set(id, { dist: d + 1, dir: 'upstream' });
512
+ }
513
+ upQueue.push([id, d + 1]);
514
+ }
515
+ }
516
+ }
517
+ for (const [id, { dist, dir }] of distances) {
518
+ const e = g.entityById.get(id);
519
+ if (!e)
520
+ continue;
521
+ const tokens = estimateTokens(e);
522
+ const relevance = 1 / (1 + dist);
523
+ candidates.push({
524
+ entity: entitySummary(e),
525
+ relevance: Math.round(relevance * 1000) / 1000,
526
+ distance: dist,
527
+ direction: dir,
528
+ tokens,
529
+ });
530
+ }
531
+ }
532
+ // Greedy knapsack: sort by relevance/token ratio, fill until budget
533
+ candidates.sort((a, b) => (b.relevance / b.tokens) - (a.relevance / a.tokens));
534
+ const selected = [];
535
+ const targetTokens = estimateTokens(entity);
536
+ let usedTokens = targetTokens; // Reserve space for the target itself
537
+ for (const candidate of candidates) {
538
+ if (usedTokens + candidate.tokens > max_tokens)
539
+ continue;
540
+ selected.push(candidate);
541
+ usedTokens += candidate.tokens;
542
+ }
543
+ return {
544
+ target: {
545
+ ...entitySummary(entity),
546
+ tokens: targetTokens,
547
+ },
548
+ maxTokens: max_tokens,
549
+ usedTokens,
550
+ contextEntities: selected.length,
551
+ totalCandidates: candidates.length,
552
+ context: selected,
553
+ };
554
+ }
@@ -3,10 +3,33 @@
3
3
  *
4
4
  * Each tool exposes a dimension or computation over the 9D JSTF-T coordinate space.
5
5
  * Tools operate on the in-memory entity bundle loaded from .seshat/_bundle.json.
6
+ *
7
+ * Multi-project: when multiple projects are loaded, each tool accepts an optional
8
+ * `project` parameter. In multi-project mode, `project` is required.
9
+ */
10
+ import type { JstfEntity } from '../types.js';
11
+ import { MultiLoader } from '../loader.js';
12
+ import { type CallGraph } from '../graph.js';
13
+ export declare function initTools(multiLoader: MultiLoader): void;
14
+ export declare function getLoader(): MultiLoader;
15
+ export declare function getGraph(project?: string): CallGraph;
16
+ /**
17
+ * Validate project param. Returns error string if invalid, null if OK.
18
+ * In single-project mode, project is optional (defaults to the only project).
19
+ * In multi-project mode, project is required.
6
20
  */
7
- import { BundleLoader } from '../loader.js';
8
- export declare function initTools(bundleLoader: BundleLoader): void;
21
+ export declare function validateProject(project?: string): string | null;
22
+ export declare function entityName(e: JstfEntity): string;
23
+ export declare function entityLayer(e: JstfEntity): string;
24
+ export declare function entitySummary(e: JstfEntity): Record<string, unknown>;
25
+ export declare function normalizeConstraints(constraints: JstfEntity['constraints']): string[];
26
+ /**
27
+ * Deep search constraints — matches against raw constraint object fields too,
28
+ * not just normalized tags.
29
+ */
30
+ export declare function constraintMatches(constraints: JstfEntity['constraints'], target: string): boolean;
9
31
  export declare function queryEntities(args: {
32
+ project?: string;
10
33
  query?: string;
11
34
  layer?: string;
12
35
  module?: string;
@@ -15,22 +38,31 @@ export declare function queryEntities(args: {
15
38
  }): unknown;
16
39
  export declare function getEntity(args: {
17
40
  id: string;
41
+ project?: string;
18
42
  }): unknown;
19
43
  export declare function getDependencies(args: {
20
44
  entity_id: string;
21
45
  direction?: 'callers' | 'callees' | 'both';
22
46
  depth?: number;
47
+ project?: string;
23
48
  }): unknown;
49
+ export declare function collectTransitive(adjacency: Map<string, Set<string>>, startId: string, maxDepth: number): string[];
24
50
  export declare function getDataFlow(args: {
25
51
  entity_id: string;
52
+ project?: string;
26
53
  }): unknown;
27
54
  export declare function findByConstraint(args: {
28
55
  constraint: string;
56
+ project?: string;
29
57
  }): unknown;
30
58
  export declare function getBlastRadius(args: {
31
59
  entity_ids: string[];
60
+ project?: string;
32
61
  }): unknown;
33
62
  export declare function listModules(args: {
34
63
  group_by?: 'layer' | 'module' | 'file' | 'language';
64
+ project?: string;
65
+ }): unknown;
66
+ export declare function getTopology(args?: {
67
+ project?: string;
35
68
  }): unknown;
36
- export declare function getTopology(): unknown;