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