@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.
package/dist/index.d.ts CHANGED
@@ -3,22 +3,37 @@
3
3
  * @papyruslabs/seshat-mcp — Semantic MCP Server
4
4
  *
5
5
  * Exposes a codebase's 9D JSTF-T coordinate space as queryable MCP tools.
6
- * Reads .seshat/_bundle.json from the current working directory.
6
+ * Reads .seshat/_bundle.json from project directories.
7
7
  *
8
- * Tools:
9
- * query_entities — Search entities by name, layer, module, language
10
- * get_entity — Full 9D coordinate dump for one entity
11
- * get_dependencies — ε edge traversal (callers/callees with depth control)
12
- * get_data_flow — δ dimension: inputs, outputs, mutations
13
- * find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
14
- * get_blast_radius — Theorem 9.4: affected set for given entity IDs
15
- * list_modules — Group entities by layer, module, file, or language
16
- * get_topology — API topology (routes, plugins, auth, tables)
8
+ * Multi-project mode:
9
+ * Set SESHAT_PROJECTS env var to comma-separated paths or a glob pattern.
10
+ * In multi-project mode, all tools require a `project` parameter.
11
+ * Use `list_projects` to see available projects.
17
12
  *
18
- * Usage:
19
- * npx @papyruslabs/seshat-mcp
13
+ * Single-project mode (default):
14
+ * When SESHAT_PROJECTS is not set, loads from CWD. No `project` param needed.
15
+ *
16
+ * Tools (8 core + 7 interpretation functors + 1 meta):
17
+ * list_projects — Show loaded projects with entity counts
18
+ * query_entities — Search entities by name, layer, module, language
19
+ * get_entity — Full 9D coordinate dump for one entity
20
+ * get_dependencies — ε edge traversal (callers/callees with depth control)
21
+ * get_data_flow — δ dimension: inputs, outputs, mutations
22
+ * find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
23
+ * get_blast_radius — Theorem 9.4: affected set for given entity IDs
24
+ * list_modules — Group entities by layer, module, file, or language
25
+ * get_topology — API topology (routes, plugins, auth, tables)
26
+ * find_dead_code — Unreachable entities via ε-graph BFS
27
+ * find_layer_violations — ε edges violating architectural layer ordering
28
+ * get_coupling_metrics — Module coupling/cohesion/instability from ε-graph
29
+ * get_auth_matrix — Auth coverage across API-facing entities from κ
30
+ * find_error_gaps — Fallible callees whose callers lack try/catch
31
+ * get_test_coverage — Entities exercised by tests vs uncovered
32
+ * get_optimal_context — Greedy knapsack: max relevance per token for LLM context
20
33
  *
21
- * Configure in Claude Code (~/.claude/settings.json):
22
- * { "mcpServers": { "seshat": { "command": "npx", "args": ["@papyruslabs/seshat-mcp"] } } }
34
+ * Usage:
35
+ * npx @papyruslabs/seshat-mcp # single project (CWD)
36
+ * SESHAT_PROJECTS=/path/a,/path/b npx @papyruslabs/seshat-mcp # multi-project
37
+ * SESHAT_PROJECTS="/home/user/projects/*" npx @papyruslabs/seshat-mcp # glob
23
38
  */
24
39
  export {};
package/dist/index.js CHANGED
@@ -3,37 +3,109 @@
3
3
  * @papyruslabs/seshat-mcp — Semantic MCP Server
4
4
  *
5
5
  * Exposes a codebase's 9D JSTF-T coordinate space as queryable MCP tools.
6
- * Reads .seshat/_bundle.json from the current working directory.
6
+ * Reads .seshat/_bundle.json from project directories.
7
7
  *
8
- * Tools:
9
- * query_entities — Search entities by name, layer, module, language
10
- * get_entity — Full 9D coordinate dump for one entity
11
- * get_dependencies — ε edge traversal (callers/callees with depth control)
12
- * get_data_flow — δ dimension: inputs, outputs, mutations
13
- * find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
14
- * get_blast_radius — Theorem 9.4: affected set for given entity IDs
15
- * list_modules — Group entities by layer, module, file, or language
16
- * get_topology — API topology (routes, plugins, auth, tables)
8
+ * Multi-project mode:
9
+ * Set SESHAT_PROJECTS env var to comma-separated paths or a glob pattern.
10
+ * In multi-project mode, all tools require a `project` parameter.
11
+ * Use `list_projects` to see available projects.
17
12
  *
18
- * Usage:
19
- * npx @papyruslabs/seshat-mcp
13
+ * Single-project mode (default):
14
+ * When SESHAT_PROJECTS is not set, loads from CWD. No `project` param needed.
15
+ *
16
+ * Tools (8 core + 7 interpretation functors + 1 meta):
17
+ * list_projects — Show loaded projects with entity counts
18
+ * query_entities — Search entities by name, layer, module, language
19
+ * get_entity — Full 9D coordinate dump for one entity
20
+ * get_dependencies — ε edge traversal (callers/callees with depth control)
21
+ * get_data_flow — δ dimension: inputs, outputs, mutations
22
+ * find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
23
+ * get_blast_radius — Theorem 9.4: affected set for given entity IDs
24
+ * list_modules — Group entities by layer, module, file, or language
25
+ * get_topology — API topology (routes, plugins, auth, tables)
26
+ * find_dead_code — Unreachable entities via ε-graph BFS
27
+ * find_layer_violations — ε edges violating architectural layer ordering
28
+ * get_coupling_metrics — Module coupling/cohesion/instability from ε-graph
29
+ * get_auth_matrix — Auth coverage across API-facing entities from κ
30
+ * find_error_gaps — Fallible callees whose callers lack try/catch
31
+ * get_test_coverage — Entities exercised by tests vs uncovered
32
+ * get_optimal_context — Greedy knapsack: max relevance per token for LLM context
20
33
  *
21
- * Configure in Claude Code (~/.claude/settings.json):
22
- * { "mcpServers": { "seshat": { "command": "npx", "args": ["@papyruslabs/seshat-mcp"] } } }
34
+ * Usage:
35
+ * npx @papyruslabs/seshat-mcp # single project (CWD)
36
+ * SESHAT_PROJECTS=/path/a,/path/b npx @papyruslabs/seshat-mcp # multi-project
37
+ * SESHAT_PROJECTS="/home/user/projects/*" npx @papyruslabs/seshat-mcp # glob
23
38
  */
39
+ import fs from 'fs';
40
+ import path from 'path';
24
41
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
25
42
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
43
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
27
- import { BundleLoader } from './loader.js';
44
+ import { MultiLoader } from './loader.js';
28
45
  import { initTools, queryEntities, getEntity, getDependencies, getDataFlow, findByConstraint, getBlastRadius, listModules, getTopology, } from './tools/index.js';
46
+ import { findDeadCode, findLayerViolations, getCouplingMetrics, getAuthMatrix, findErrorGaps, getTestCoverage, getOptimalContext, } from './tools/functors.js';
47
+ // ─── Project Discovery ───────────────────────────────────────────
48
+ /**
49
+ * Discover project directories from SESHAT_PROJECTS env var.
50
+ *
51
+ * - Not set → [process.cwd()] (backward compat)
52
+ * - Comma-separated paths → resolve each
53
+ * - Glob pattern (contains *) → scan parent dir for subdirs with .seshat/_bundle.json
54
+ */
55
+ function discoverProjects() {
56
+ const env = process.env.SESHAT_PROJECTS;
57
+ if (!env)
58
+ return [process.cwd()];
59
+ const trimmed = env.trim();
60
+ // Glob pattern: /path/to/projects/*
61
+ if (trimmed.includes('*')) {
62
+ const globDir = trimmed.replace(/\/?\*$/, '');
63
+ const resolved = path.resolve(globDir);
64
+ if (!fs.existsSync(resolved)) {
65
+ process.stderr.write(`SESHAT_PROJECTS glob dir not found: ${resolved}\n`);
66
+ return [process.cwd()];
67
+ }
68
+ const dirs = [];
69
+ for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
70
+ if (!entry.isDirectory())
71
+ continue;
72
+ const candidate = path.join(resolved, entry.name);
73
+ const bundlePath = path.join(candidate, '.seshat', '_bundle.json');
74
+ if (fs.existsSync(bundlePath)) {
75
+ dirs.push(candidate);
76
+ }
77
+ }
78
+ if (dirs.length === 0) {
79
+ process.stderr.write(`SESHAT_PROJECTS: no dirs with .seshat/_bundle.json found in ${resolved}\n`);
80
+ return [process.cwd()];
81
+ }
82
+ return dirs;
83
+ }
84
+ // Comma-separated paths
85
+ return trimmed.split(',').map(p => path.resolve(p.trim())).filter(p => p.length > 0);
86
+ }
87
+ // ─── Project param definition (injected into all tool schemas) ───
88
+ const projectParam = {
89
+ type: 'string',
90
+ description: 'Project name (required in multi-project mode). Use list_projects to see available projects.',
91
+ };
29
92
  // ─── Tool Definitions ─────────────────────────────────────────────
30
93
  const TOOLS = [
94
+ {
95
+ name: 'list_projects',
96
+ description: 'List all loaded projects with entity counts, languages, and metadata. Call this first to see what projects are available, then pass the project name to other tools.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {},
100
+ },
101
+ },
31
102
  {
32
103
  name: 'query_entities',
33
104
  description: 'Search entities in the 9D semantic coordinate space. Filter by name, architectural layer (route/service/repository/component), module, or source language. Returns entity summaries with constraint tags.',
34
105
  inputSchema: {
35
106
  type: 'object',
36
107
  properties: {
108
+ project: projectParam,
37
109
  query: {
38
110
  type: 'string',
39
111
  description: 'Search term — matches against entity name, ID, source file, and module',
@@ -63,6 +135,7 @@ const TOOLS = [
63
135
  inputSchema: {
64
136
  type: 'object',
65
137
  properties: {
138
+ project: projectParam,
66
139
  id: {
67
140
  type: 'string',
68
141
  description: 'Entity ID or name',
@@ -77,6 +150,7 @@ const TOOLS = [
77
150
  inputSchema: {
78
151
  type: 'object',
79
152
  properties: {
153
+ project: projectParam,
80
154
  entity_id: {
81
155
  type: 'string',
82
156
  description: 'Entity ID or name',
@@ -100,6 +174,7 @@ const TOOLS = [
100
174
  inputSchema: {
101
175
  type: 'object',
102
176
  properties: {
177
+ project: projectParam,
103
178
  entity_id: {
104
179
  type: 'string',
105
180
  description: 'Entity ID or name',
@@ -114,6 +189,7 @@ const TOOLS = [
114
189
  inputSchema: {
115
190
  type: 'object',
116
191
  properties: {
192
+ project: projectParam,
117
193
  constraint: {
118
194
  type: 'string',
119
195
  description: 'Constraint tag to search for: AUTH, VALIDATED, PURE, THROWS, DB_ACCESS, NETWORK_IO, IMP, etc.',
@@ -128,6 +204,7 @@ const TOOLS = [
128
204
  inputSchema: {
129
205
  type: 'object',
130
206
  properties: {
207
+ project: projectParam,
131
208
  entity_ids: {
132
209
  type: 'array',
133
210
  items: { type: 'string' },
@@ -143,6 +220,7 @@ const TOOLS = [
143
220
  inputSchema: {
144
221
  type: 'object',
145
222
  properties: {
223
+ project: projectParam,
146
224
  group_by: {
147
225
  type: 'string',
148
226
  enum: ['layer', 'module', 'file', 'language'],
@@ -156,28 +234,140 @@ const TOOLS = [
156
234
  description: 'Get the API topology: routes, plugins, auth patterns, and database tables. Built from the χ (context) and ε (edges) dimensions across all entities.',
157
235
  inputSchema: {
158
236
  type: 'object',
159
- properties: {},
237
+ properties: {
238
+ project: projectParam,
239
+ },
240
+ },
241
+ },
242
+ // ─── Interpretation Functor Tools ────────────────────────────────
243
+ {
244
+ name: 'find_dead_code',
245
+ description: 'Find unreachable entities (dead code candidates). BFS from entry points (routes, exported functions, tests, plugins) through the ε call graph. Entities not reachable from any entry point are flagged.',
246
+ inputSchema: {
247
+ type: 'object',
248
+ properties: {
249
+ project: projectParam,
250
+ include_tests: {
251
+ type: 'boolean',
252
+ description: 'Include test entities in dead code results (default: false)',
253
+ },
254
+ },
255
+ },
256
+ },
257
+ {
258
+ name: 'find_layer_violations',
259
+ description: 'Detect architectural layer violations in the ε call graph. Finds backward calls (lower layer calling higher layer, e.g. repository → route) and skip-layer calls (jumping over multiple layers). Uses χ.layer for classification.',
260
+ inputSchema: {
261
+ type: 'object',
262
+ properties: {
263
+ project: projectParam,
264
+ },
265
+ },
266
+ },
267
+ {
268
+ name: 'get_coupling_metrics',
269
+ description: 'Compute coupling, cohesion, and instability metrics for modules or layers. Analyzes ε edges between and within groups. High coupling + low cohesion = candidates for refactoring.',
270
+ inputSchema: {
271
+ type: 'object',
272
+ properties: {
273
+ project: projectParam,
274
+ group_by: {
275
+ type: 'string',
276
+ enum: ['module', 'layer'],
277
+ description: 'Group entities by module or layer (default: module)',
278
+ },
279
+ },
280
+ },
281
+ },
282
+ {
283
+ name: 'get_auth_matrix',
284
+ description: 'Analyze authentication coverage across all API-facing entities. Shows which routes/controllers have auth requirements (from κ.auth) and which don\'t. Detects inconsistencies like DB access without auth.',
285
+ inputSchema: {
286
+ type: 'object',
287
+ properties: {
288
+ project: projectParam,
289
+ },
290
+ },
291
+ },
292
+ {
293
+ name: 'find_error_gaps',
294
+ description: 'Find error handling gaps: fallible entities (κ.throws, network/db side effects) whose callers lack try/catch (κ.errorHandling). These are crash risk points where exceptions can propagate unhandled.',
295
+ inputSchema: {
296
+ type: 'object',
297
+ properties: {
298
+ project: projectParam,
299
+ },
300
+ },
301
+ },
302
+ {
303
+ name: 'get_test_coverage',
304
+ description: 'Compute semantic test coverage: which production entities are exercised by test entities via the ε call graph. Optionally weight uncovered entities by blast radius to prioritize what to test first.',
305
+ inputSchema: {
306
+ type: 'object',
307
+ properties: {
308
+ project: projectParam,
309
+ weight_by_blast_radius: {
310
+ type: 'boolean',
311
+ description: 'Rank uncovered entities by blast radius to prioritize testing (default: false, slower)',
312
+ },
313
+ },
314
+ },
315
+ },
316
+ {
317
+ name: 'get_optimal_context',
318
+ description: 'Compute the optimal set of related entities to include in an LLM context window for a target entity. Uses greedy knapsack: maximizes relevance per token. Returns entities ranked by relevance with token estimates.',
319
+ inputSchema: {
320
+ type: 'object',
321
+ properties: {
322
+ project: projectParam,
323
+ target_entity: {
324
+ type: 'string',
325
+ description: 'Entity ID or name to build context around',
326
+ },
327
+ max_tokens: {
328
+ type: 'number',
329
+ description: 'Token budget for the context window (default: 8000)',
330
+ },
331
+ strategy: {
332
+ type: 'string',
333
+ enum: ['bfs', 'blast_radius'],
334
+ description: 'Traversal strategy: bfs (faster, local neighborhood) or blast_radius (full affected set)',
335
+ },
336
+ },
337
+ required: ['target_entity'],
160
338
  },
161
339
  },
162
340
  ];
163
341
  // ─── Server Setup ─────────────────────────────────────────────────
164
342
  async function main() {
165
- // Load the JSTF-T bundle
166
- const loader = new BundleLoader();
343
+ // Discover and load projects
344
+ const projectDirs = discoverProjects();
345
+ const loader = new MultiLoader(projectDirs);
167
346
  try {
168
347
  loader.load();
169
348
  }
170
349
  catch (err) {
171
- // Server will start but tools will return helpful errors
172
350
  process.stderr.write(`Warning: ${err.message}\n`);
173
351
  }
174
352
  initTools(loader);
175
- const manifest = loader.isLoaded() ? loader.getManifest() : null;
176
- const projectName = manifest?.projectName || 'unknown';
177
- const entityCount = manifest?.entityCount || 0;
353
+ // Build server name
354
+ const projectNames = loader.getProjectNames();
355
+ const totalEntities = loader.totalEntities();
356
+ const isMulti = loader.isMultiProject();
357
+ let serverLabel;
358
+ if (!loader.isLoaded()) {
359
+ serverLabel = 'seshat-mcp (no projects loaded)';
360
+ }
361
+ else if (isMulti) {
362
+ serverLabel = `seshat-mcp (${projectNames.length} projects, ${totalEntities} entities)`;
363
+ }
364
+ else {
365
+ const manifest = loader.getManifest();
366
+ serverLabel = `seshat-mcp (${manifest?.projectName || projectNames[0] || 'unknown'})`;
367
+ }
178
368
  const server = new Server({
179
- name: `seshat-mcp (${projectName})`,
180
- version: '0.1.0',
369
+ name: serverLabel,
370
+ version: '0.3.0',
181
371
  }, {
182
372
  capabilities: {
183
373
  tools: {},
@@ -196,13 +386,25 @@ async function main() {
196
386
  content: [{
197
387
  type: 'text',
198
388
  text: JSON.stringify({
199
- error: 'No .seshat/_bundle.json found in the current working directory. Run the Seshat extraction pipeline first.',
389
+ error: 'No projects loaded. Ensure .seshat/_bundle.json exists or set SESHAT_PROJECTS env var.',
200
390
  }, null, 2),
201
391
  }],
202
392
  };
203
393
  }
204
394
  let result;
205
395
  switch (name) {
396
+ // Meta
397
+ case 'list_projects':
398
+ result = {
399
+ multiProject: isMulti,
400
+ projects: loader.getProjectInfo(),
401
+ totalEntities,
402
+ hint: isMulti
403
+ ? 'Pass the project name as the "project" parameter to all other tools.'
404
+ : 'Single project mode — the "project" parameter is optional.',
405
+ };
406
+ break;
407
+ // Core tools
206
408
  case 'query_entities':
207
409
  result = queryEntities(args);
208
410
  break;
@@ -225,7 +427,29 @@ async function main() {
225
427
  result = listModules(args);
226
428
  break;
227
429
  case 'get_topology':
228
- result = getTopology();
430
+ result = getTopology(args);
431
+ break;
432
+ // Interpretation Functors
433
+ case 'find_dead_code':
434
+ result = findDeadCode(args);
435
+ break;
436
+ case 'find_layer_violations':
437
+ result = findLayerViolations(args);
438
+ break;
439
+ case 'get_coupling_metrics':
440
+ result = getCouplingMetrics(args);
441
+ break;
442
+ case 'get_auth_matrix':
443
+ result = getAuthMatrix(args);
444
+ break;
445
+ case 'find_error_gaps':
446
+ result = findErrorGaps(args);
447
+ break;
448
+ case 'get_test_coverage':
449
+ result = getTestCoverage(args);
450
+ break;
451
+ case 'get_optimal_context':
452
+ result = getOptimalContext(args);
229
453
  break;
230
454
  default:
231
455
  result = { error: `Unknown tool: ${name}` };
@@ -252,7 +476,13 @@ async function main() {
252
476
  // Start stdio transport
253
477
  const transport = new StdioServerTransport();
254
478
  await server.connect(transport);
255
- process.stderr.write(`Seshat MCP server started: ${projectName} (${entityCount} entities)\n`);
479
+ if (isMulti) {
480
+ process.stderr.write(`Seshat MCP server started: ${projectNames.length} projects (${totalEntities} entities) — ${projectNames.join(', ')}\n`);
481
+ }
482
+ else {
483
+ const manifest = loader.getManifest();
484
+ process.stderr.write(`Seshat MCP server started: ${manifest?.projectName || 'unknown'} (${totalEntities} entities)\n`);
485
+ }
256
486
  }
257
487
  main().catch((err) => {
258
488
  process.stderr.write(`Fatal: ${err.message}\n`);
package/dist/loader.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Discovers and loads .seshat/_bundle.json from the current working directory.
5
5
  * Falls back to checking common alternative locations.
6
6
  */
7
- import type { JstfEntity, Topology, Manifest } from './types.js';
7
+ import type { JstfEntity, Topology, Manifest, ProjectInfo } from './types.js';
8
8
  export declare class BundleLoader {
9
9
  private entities;
10
10
  private topology;
@@ -20,3 +20,31 @@ export declare class BundleLoader {
20
20
  getEntityByName(name: string): JstfEntity | undefined;
21
21
  isLoaded(): boolean;
22
22
  }
23
+ /**
24
+ * Wraps N BundleLoader instances for multi-project access.
25
+ * In single-project mode (1 dir), behaves identically to BundleLoader.
26
+ * In multi-project mode, `project` param is required on all accessors.
27
+ */
28
+ export declare class MultiLoader {
29
+ private projects;
30
+ private projectPaths;
31
+ private projectEntities;
32
+ private loaded;
33
+ private dirs;
34
+ constructor(projectDirs: string[]);
35
+ load(): void;
36
+ /** Get the resolved project name when only one project is loaded. */
37
+ private defaultProject;
38
+ private resolveProject;
39
+ getEntities(project?: string): JstfEntity[];
40
+ getTopology(project?: string): Topology | null;
41
+ getManifest(project?: string): Manifest | null;
42
+ getEntityById(id: string, project?: string): JstfEntity | undefined;
43
+ getEntityByName(name: string, project?: string): JstfEntity | undefined;
44
+ getProjectNames(): string[];
45
+ getProjectInfo(): ProjectInfo[];
46
+ isMultiProject(): boolean;
47
+ isLoaded(): boolean;
48
+ hasProject(name: string): boolean;
49
+ totalEntities(): number;
50
+ }
package/dist/loader.js CHANGED
@@ -67,3 +67,122 @@ export class BundleLoader {
67
67
  return this.loaded;
68
68
  }
69
69
  }
70
+ // ─── Multi-Project Loader ─────────────────────────────────────────
71
+ /**
72
+ * Wraps N BundleLoader instances for multi-project access.
73
+ * In single-project mode (1 dir), behaves identically to BundleLoader.
74
+ * In multi-project mode, `project` param is required on all accessors.
75
+ */
76
+ export class MultiLoader {
77
+ projects = new Map();
78
+ projectPaths = new Map();
79
+ projectEntities = new Map();
80
+ loaded = false;
81
+ dirs;
82
+ constructor(projectDirs) {
83
+ this.dirs = projectDirs.length > 0 ? projectDirs : [process.cwd()];
84
+ }
85
+ load() {
86
+ for (const dir of this.dirs) {
87
+ const loader = new BundleLoader(dir);
88
+ try {
89
+ loader.load();
90
+ const manifest = loader.getManifest();
91
+ const name = manifest?.projectName || path.basename(dir);
92
+ this.projects.set(name, loader);
93
+ this.projectPaths.set(name, dir);
94
+ // Stamp entities with _project
95
+ const entities = loader.getEntities();
96
+ for (const e of entities) {
97
+ e._project = name;
98
+ }
99
+ this.projectEntities.set(name, entities);
100
+ }
101
+ catch (err) {
102
+ process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
103
+ }
104
+ }
105
+ this.loaded = true;
106
+ }
107
+ /** Get the resolved project name when only one project is loaded. */
108
+ defaultProject() {
109
+ if (this.projects.size === 1)
110
+ return [...this.projects.keys()][0];
111
+ return undefined;
112
+ }
113
+ resolveProject(project) {
114
+ return project || this.defaultProject();
115
+ }
116
+ getEntities(project) {
117
+ if (!this.loaded)
118
+ this.load();
119
+ const p = this.resolveProject(project);
120
+ if (p)
121
+ return this.projectEntities.get(p) || [];
122
+ return []; // multi-project with no project specified
123
+ }
124
+ getTopology(project) {
125
+ if (!this.loaded)
126
+ this.load();
127
+ const p = this.resolveProject(project);
128
+ if (p)
129
+ return this.projects.get(p)?.getTopology() || null;
130
+ return null;
131
+ }
132
+ getManifest(project) {
133
+ if (!this.loaded)
134
+ this.load();
135
+ const p = this.resolveProject(project);
136
+ if (p)
137
+ return this.projects.get(p)?.getManifest() || null;
138
+ return null;
139
+ }
140
+ getEntityById(id, project) {
141
+ return this.getEntities(project).find(e => e.id === id);
142
+ }
143
+ getEntityByName(name, project) {
144
+ return this.getEntities(project).find(e => {
145
+ const structName = typeof e.struct === 'string' ? e.struct : e.struct?.name;
146
+ return e.id === name || structName === name;
147
+ });
148
+ }
149
+ getProjectNames() {
150
+ if (!this.loaded)
151
+ this.load();
152
+ return [...this.projects.keys()];
153
+ }
154
+ getProjectInfo() {
155
+ if (!this.loaded)
156
+ this.load();
157
+ const result = [];
158
+ for (const [name, loader] of this.projects) {
159
+ const manifest = loader.getManifest();
160
+ result.push({
161
+ name,
162
+ path: this.projectPaths.get(name) || '',
163
+ entityCount: loader.getEntities().length,
164
+ languages: manifest?.languages || [],
165
+ commitSha: manifest?.commitSha || '',
166
+ extractedAt: manifest?.extractedAt || '',
167
+ layers: manifest?.layers || {},
168
+ });
169
+ }
170
+ return result;
171
+ }
172
+ isMultiProject() {
173
+ return this.projects.size > 1;
174
+ }
175
+ isLoaded() {
176
+ return this.loaded && this.projects.size > 0;
177
+ }
178
+ hasProject(name) {
179
+ return this.projects.has(name);
180
+ }
181
+ totalEntities() {
182
+ let total = 0;
183
+ for (const entities of this.projectEntities.values()) {
184
+ total += entities.length;
185
+ }
186
+ return total;
187
+ }
188
+ }
@@ -0,0 +1,34 @@
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
+ export declare function findDeadCode(args: {
9
+ include_tests?: boolean;
10
+ project?: string;
11
+ }): unknown;
12
+ export declare function findLayerViolations(args?: {
13
+ project?: string;
14
+ }): unknown;
15
+ export declare function getCouplingMetrics(args: {
16
+ group_by?: 'module' | 'layer';
17
+ project?: string;
18
+ }): unknown;
19
+ export declare function getAuthMatrix(args?: {
20
+ project?: string;
21
+ }): unknown;
22
+ export declare function findErrorGaps(args?: {
23
+ project?: string;
24
+ }): unknown;
25
+ export declare function getTestCoverage(args: {
26
+ weight_by_blast_radius?: boolean;
27
+ project?: string;
28
+ }): unknown;
29
+ export declare function getOptimalContext(args: {
30
+ target_entity: string;
31
+ max_tokens?: number;
32
+ strategy?: 'bfs' | 'blast_radius';
33
+ project?: string;
34
+ }): unknown;