@papyruslabsai/seshat-mcp 0.10.0 → 0.11.1

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.js CHANGED
@@ -33,6 +33,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
33
33
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
34
34
  import { MultiLoader } from './loader.js';
35
35
  import { bootstrap } from './bootstrap.js';
36
+ import { logTelemetry, isSupabaseConfigured } from './supabase.js';
36
37
  import { initTools, queryEntities, getEntity, getDependencies, getDataFlow, findByConstraint, getBlastRadius, listModules, getTopology, } from './tools/index.js';
37
38
  import { findDeadCode, findLayerViolations, getCouplingMetrics, getAuthMatrix, findErrorGaps, getTestCoverage, getOptimalContext, estimateTaskCost, reportActualBurn, find_runtime_violations, find_ownership_violations, query_traits, simulate_mutation, query_data_targets, find_exposure_leaks, find_semantic_clones, create_symbol, } from './tools/functors.js';
38
39
  import { diffBundle, conflictMatrix, } from './tools/diff.js';
@@ -542,7 +543,7 @@ const TOOLS = [
542
543
  },
543
544
  {
544
545
  name: 'conflict_matrix',
545
- description: 'Given multiple tasks, classify every task pair into conflict tiers to determine parallelization safety. Tier 1 (different files, safe), Tier 2 (same file, different symbols, safe), Tier 3 (same symbol, orthogonal changes like imports vs logic, risky but parallelizable), Tier 4 (same symbol, overlapping changes, MUST sequence). Passing "scopes" per task enables Tier 3 downgrades.',
546
+ description: 'Given multiple tasks, classify every task pair into conflict tiers to determine parallelization safety. Tier 1 (different files, safe), Tier 2 (same file, different symbols, safe via surgical splicing), Tier 3 (same symbol, MUST sequence to maintain splice integrity). Returns a pairwise matrix and a suggested execution plan.',
546
547
  inputSchema: {
547
548
  type: 'object',
548
549
  properties: {
@@ -564,7 +565,7 @@ const TOOLS = [
564
565
  dimensions: {
565
566
  type: 'array',
566
567
  items: { type: 'string' },
567
- description: 'Optional: Code scopes this task will modify (e.g., "edges", "struct", "semantics", "constraints"). Used to downgrade conflicts via spatial separation.',
568
+ description: 'Optional: Code scopes this task will modify. (Legacy support, no longer required for Tier 2 safety).',
568
569
  },
569
570
  expand_blast_radius: {
570
571
  type: 'boolean',
@@ -647,6 +648,7 @@ async function main() {
647
648
  // Handle tool calls
648
649
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
649
650
  const { name, arguments: args } = request.params;
651
+ const startTime = Date.now();
650
652
  try {
651
653
  if (!loader.isLoaded()) {
652
654
  return {
@@ -659,8 +661,7 @@ async function main() {
659
661
  };
660
662
  }
661
663
  let result;
662
- switch (name) {
663
- // Meta
664
+ switch (name) { // Meta
664
665
  case 'list_projects':
665
666
  result = {
666
667
  multiProject: isMulti,
@@ -759,6 +760,21 @@ async function main() {
759
760
  default:
760
761
  result = { error: `Unknown tool: ${name}` };
761
762
  }
763
+ // Log telemetry for the tool execution
764
+ if (isSupabaseConfigured() && name !== 'list_projects') {
765
+ const executionMs = Date.now() - startTime;
766
+ const projectHash = args && typeof args === 'object' && 'project' in args
767
+ ? String(args.project)
768
+ : loader.getProjectNames()[0] || 'unknown';
769
+ // Fire-and-forget telemetry log
770
+ logTelemetry({
771
+ tool_name: name,
772
+ project_hash: projectHash,
773
+ execution_ms: executionMs,
774
+ // user_id will be added by the Ptah API gateway if passing through there,
775
+ // or stay null if running strictly local CLI without an injected token.
776
+ }).catch(() => { });
777
+ }
762
778
  return {
763
779
  content: [{
764
780
  type: 'text',
package/dist/loader.d.ts CHANGED
@@ -11,7 +11,13 @@ export declare class BundleLoader {
11
11
  private manifest;
12
12
  private loaded;
13
13
  private seshatDir;
14
+ private bundleMtime;
14
15
  constructor(cwd?: string);
16
+ /**
17
+ * Check if the bundle file on disk has changed since last load.
18
+ * If so, reload transparently. Called by all accessors.
19
+ */
20
+ private ensureFresh;
15
21
  load(): void;
16
22
  getEntities(): JstfEntity[];
17
23
  getTopology(): Topology | null;
@@ -33,6 +39,7 @@ export declare class MultiLoader {
33
39
  private dirs;
34
40
  constructor(projectDirs: string[]);
35
41
  load(): void;
42
+ private loadProject;
36
43
  /** Get the resolved project name when only one project is loaded. */
37
44
  private defaultProject;
38
45
  private resolveProject;
package/dist/loader.js CHANGED
@@ -12,10 +12,32 @@ export class BundleLoader {
12
12
  manifest = null;
13
13
  loaded = false;
14
14
  seshatDir;
15
+ bundleMtime = 0;
15
16
  constructor(cwd) {
16
17
  const root = cwd || process.cwd();
17
18
  this.seshatDir = path.join(root, '.seshat');
18
19
  }
20
+ /**
21
+ * Check if the bundle file on disk has changed since last load.
22
+ * If so, reload transparently. Called by all accessors.
23
+ */
24
+ ensureFresh() {
25
+ if (!this.loaded) {
26
+ this.load();
27
+ return;
28
+ }
29
+ const bundlePath = path.join(this.seshatDir, '_bundle.json');
30
+ try {
31
+ const currentMtime = fs.statSync(bundlePath).mtimeMs;
32
+ if (currentMtime !== this.bundleMtime) {
33
+ process.stderr.write(`[seshat] Bundle changed on disk, reloading…\n`);
34
+ this.load();
35
+ }
36
+ }
37
+ catch {
38
+ // File may have been deleted — keep stale data rather than crash
39
+ }
40
+ }
19
41
  load() {
20
42
  const bundlePath = path.join(this.seshatDir, '_bundle.json');
21
43
  if (!fs.existsSync(bundlePath)) {
@@ -24,6 +46,7 @@ export class BundleLoader {
24
46
  ` node ci/extract-and-generate.mjs <repo-path> .seshat <project-name>\n` +
25
47
  `Or add the CI workflow to auto-generate on push to main.`);
26
48
  }
49
+ this.bundleMtime = fs.statSync(bundlePath).mtimeMs;
27
50
  const raw = fs.readFileSync(bundlePath, 'utf-8');
28
51
  const bundle = JSON.parse(raw);
29
52
  this.entities = bundle.entities || [];
@@ -52,18 +75,15 @@ export class BundleLoader {
52
75
  this.loaded = true;
53
76
  }
54
77
  getEntities() {
55
- if (!this.loaded)
56
- this.load();
78
+ this.ensureFresh();
57
79
  return this.entities;
58
80
  }
59
81
  getTopology() {
60
- if (!this.loaded)
61
- this.load();
82
+ this.ensureFresh();
62
83
  return this.topology;
63
84
  }
64
85
  getManifest() {
65
- if (!this.loaded)
66
- this.load();
86
+ this.ensureFresh();
67
87
  return this.manifest;
68
88
  }
69
89
  getEntityById(id) {
@@ -96,26 +116,29 @@ export class MultiLoader {
96
116
  }
97
117
  load() {
98
118
  for (const dir of this.dirs) {
99
- const loader = new BundleLoader(dir);
100
- try {
101
- loader.load();
102
- const manifest = loader.getManifest();
103
- const name = manifest?.projectName || path.basename(dir);
104
- this.projects.set(name, loader);
105
- this.projectPaths.set(name, dir);
106
- // Stamp entities with _project
107
- const entities = loader.getEntities();
108
- for (const e of entities) {
109
- e._project = name;
110
- }
111
- this.projectEntities.set(name, entities);
112
- }
113
- catch (err) {
114
- process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
115
- }
119
+ this.loadProject(dir);
116
120
  }
117
121
  this.loaded = true;
118
122
  }
123
+ loadProject(dir) {
124
+ const loader = new BundleLoader(dir);
125
+ try {
126
+ loader.load();
127
+ const manifest = loader.getManifest();
128
+ const name = manifest?.projectName || path.basename(dir);
129
+ this.projects.set(name, loader);
130
+ this.projectPaths.set(name, dir);
131
+ // Stamp entities with _project
132
+ const entities = loader.getEntities();
133
+ for (const e of entities) {
134
+ e._project = name;
135
+ }
136
+ this.projectEntities.set(name, entities);
137
+ }
138
+ catch (err) {
139
+ process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
140
+ }
141
+ }
119
142
  /** Get the resolved project name when only one project is loaded. */
120
143
  defaultProject() {
121
144
  if (this.projects.size === 1)
@@ -129,9 +152,22 @@ export class MultiLoader {
129
152
  if (!this.loaded)
130
153
  this.load();
131
154
  const p = this.resolveProject(project);
132
- if (p)
133
- return this.projectEntities.get(p) || [];
134
- return []; // multi-project with no project specified
155
+ if (!p)
156
+ return [];
157
+ // Delegate to BundleLoader (which checks mtime via ensureFresh),
158
+ // then re-stamp and cache if the reference changed.
159
+ const loader = this.projects.get(p);
160
+ if (!loader)
161
+ return [];
162
+ const fresh = loader.getEntities();
163
+ const cached = this.projectEntities.get(p);
164
+ if (fresh !== cached) {
165
+ for (const e of fresh) {
166
+ e._project = p;
167
+ }
168
+ this.projectEntities.set(p, fresh);
169
+ }
170
+ return fresh;
135
171
  }
136
172
  getTopology(project) {
137
173
  if (!this.loaded)
@@ -55,6 +55,31 @@ export interface TokenPredictionRow {
55
55
  session_id: string | null;
56
56
  notes: string | null;
57
57
  }
58
+ export interface McpTelemetryLog {
59
+ user_id?: string;
60
+ tool_name: string;
61
+ project_hash: string;
62
+ execution_ms: number;
63
+ }
64
+ export interface JstfSnapshotInsert {
65
+ user_id?: string;
66
+ project_hash: string;
67
+ commit_sha: string;
68
+ language_primary: string;
69
+ total_entities: number;
70
+ metric_typeless_interfaces: number;
71
+ metric_max_in_degree: number;
72
+ metric_circular_dependencies: number;
73
+ metric_untracked_data_transformations: number;
74
+ metric_exposure_leaks: number;
75
+ metric_layer_violations: number;
76
+ metric_ownership_violations: number;
77
+ metric_impure_ratio: number;
78
+ metric_runtime_violations: number;
79
+ metric_semantic_clones: number;
80
+ metric_core_fragility_score: number;
81
+ bundle_payload: unknown;
82
+ }
58
83
  /**
59
84
  * Insert a prediction row. Returns the row ID or null on failure.
60
85
  */
@@ -70,4 +95,5 @@ export declare function abandonPrediction(predictionId: string): Promise<boolean
70
95
  /**
71
96
  * List recent predictions for a project (for calibration analysis).
72
97
  */
73
- export declare function listPredictions(project?: string, limit?: number): Promise<TokenPredictionRow[]>;
98
+ export declare function logTelemetry(log: McpTelemetryLog): Promise<void>;
99
+ export declare function insertJstfSnapshot(snapshot: JstfSnapshotInsert): Promise<boolean>;
package/dist/supabase.js CHANGED
@@ -18,7 +18,9 @@ export function isSupabaseConfigured() {
18
18
  return SUPABASE_URL.length > 0 && SUPABASE_KEY.length > 0;
19
19
  }
20
20
  // ─── REST helpers ────────────────────────────────────────────────
21
- const TABLE = 'mcp_token_predictions';
21
+ const TABLE_PREDICTIONS = 'mcp_token_predictions';
22
+ const TABLE_TELEMETRY = 'mcp_telemetry_logs';
23
+ const TABLE_SNAPSHOTS = 'jstf_snapshots';
22
24
  async function supabaseRequest(method, path, body, headers) {
23
25
  if (!isSupabaseConfigured()) {
24
26
  return { ok: false, status: 0, error: 'Supabase not configured' };
@@ -59,7 +61,7 @@ async function supabaseRequest(method, path, body, headers) {
59
61
  * Insert a prediction row. Returns the row ID or null on failure.
60
62
  */
61
63
  export async function insertPrediction(row) {
62
- const result = await supabaseRequest('POST', TABLE, row);
64
+ const result = await supabaseRequest('POST', TABLE_PREDICTIONS, row);
63
65
  if (!result.ok) {
64
66
  process.stderr.write(`[seshat] Prediction log failed: ${result.error}\n`);
65
67
  return null;
@@ -72,7 +74,7 @@ export async function insertPrediction(row) {
72
74
  */
73
75
  export async function updateActualBurn(predictionId, actual) {
74
76
  // First fetch the prediction to compute drift
75
- const fetchResult = await supabaseRequest('GET', `${TABLE}?id=eq.${predictionId}&select=predicted_total,status`);
77
+ const fetchResult = await supabaseRequest('GET', `${TABLE_PREDICTIONS}?id=eq.${predictionId}&select=predicted_total,status`);
76
78
  if (!fetchResult.ok) {
77
79
  process.stderr.write(`[seshat] Fetch prediction failed: ${fetchResult.error}\n`);
78
80
  return null;
@@ -98,7 +100,7 @@ export async function updateActualBurn(predictionId, actual) {
98
100
  status: 'completed',
99
101
  ...(actual.notes ? { notes: actual.notes } : {}),
100
102
  };
101
- const updateResult = await supabaseRequest('PATCH', `${TABLE}?id=eq.${predictionId}`, updateBody);
103
+ const updateResult = await supabaseRequest('PATCH', `${TABLE_PREDICTIONS}?id=eq.${predictionId}`, updateBody);
102
104
  if (!updateResult.ok) {
103
105
  process.stderr.write(`[seshat] Update actual burn failed: ${updateResult.error}\n`);
104
106
  return null;
@@ -109,16 +111,22 @@ export async function updateActualBurn(predictionId, actual) {
109
111
  * Abandon a prediction (task was cancelled or not completed).
110
112
  */
111
113
  export async function abandonPrediction(predictionId) {
112
- const result = await supabaseRequest('PATCH', `${TABLE}?id=eq.${predictionId}&status=eq.predicted`, { status: 'abandoned' });
114
+ const result = await supabaseRequest('PATCH', `${TABLE_PREDICTIONS}?id=eq.${predictionId}&status=eq.predicted`, { status: 'abandoned' });
113
115
  return result.ok;
114
116
  }
115
117
  /**
116
118
  * List recent predictions for a project (for calibration analysis).
117
119
  */
118
- export async function listPredictions(project, limit = 20) {
119
- const filter = project ? `&project=eq.${encodeURIComponent(project)}` : '';
120
- const result = await supabaseRequest('GET', `${TABLE}?select=*${filter}&order=created_at.desc&limit=${limit}`);
121
- if (!result.ok)
122
- return [];
123
- return result.data || [];
120
+ export async function logTelemetry(log) {
121
+ const result = await supabaseRequest('POST', TABLE_TELEMETRY, log);
122
+ if (!result.ok) {
123
+ process.stderr.write(`[seshat] Telemetry log failed: ${result.error}\n`);
124
+ }
125
+ }
126
+ export async function insertJstfSnapshot(snapshot) {
127
+ const result = await supabaseRequest('POST', TABLE_SNAPSHOTS, snapshot);
128
+ if (!result.ok) {
129
+ process.stderr.write(`[seshat] JSTF snapshot failed: ${result.error}\n`);
130
+ }
131
+ return result.ok;
124
132
  }
@@ -261,25 +261,8 @@ export async function diffBundle(args) {
261
261
  }
262
262
  return result;
263
263
  }
264
- // ─── Conflict Tier Classification ─────────────────────────────────
265
- const ZONE_1 = new Set(['edges', 'imports']); // Header / Dependencies
266
- const ZONE_2 = new Set(['struct', 'constraints', 'traits', 'ownership']); // Signature / Modifiers
267
- const ZONE_3 = new Set(['semantics', 'data', 'runtime']); // Logic / Implementation
268
- function getZones(scopes) {
269
- const zones = new Set();
270
- for (const s of scopes) {
271
- const lower = s.toLowerCase();
272
- if (ZONE_1.has(lower))
273
- zones.add(1);
274
- if (ZONE_2.has(lower))
275
- zones.add(2);
276
- if (ZONE_3.has(lower))
277
- zones.add(3);
278
- }
279
- return zones;
280
- }
281
264
  function classifyConflictTier(taskA, taskB) {
282
- // Check symbol overlap
265
+ // Check symbol overlap → Tier 3 (MUST sequence)
283
266
  const sharedEntities = [];
284
267
  for (const id of taskA.entityIds) {
285
268
  if (taskB.entityIds.has(id)) {
@@ -287,27 +270,9 @@ function classifyConflictTier(taskA, taskB) {
287
270
  }
288
271
  }
289
272
  if (sharedEntities.length > 0) {
290
- // Both touch same symbol. Check scopes to see if we can downgrade from Tier 4 (Sequential) to Tier 3 (Parallelizable Orthogonal)
291
- if (taskA.dimensions.size > 0 && taskB.dimensions.size > 0) {
292
- const zonesA = getZones(taskA.dimensions);
293
- const zonesB = getZones(taskB.dimensions);
294
- let spatialCollision = false;
295
- for (const z of zonesA) {
296
- if (zonesB.has(z))
297
- spatialCollision = true;
298
- }
299
- if (!spatialCollision) {
300
- return {
301
- tier: 3,
302
- reason: `${sharedEntities.length} shared symbols, but orthogonal change scopes (Tier 3) — safe to parallelize`,
303
- sharedFiles: [],
304
- sharedEntities,
305
- };
306
- }
307
- }
308
273
  return {
309
- tier: 4,
310
- reason: `${sharedEntities.length} shared symbols in same/unknown change scope (Tier 4) — MUST sequence to prevent merge conflict`,
274
+ tier: 3,
275
+ reason: `${sharedEntities.length} shared symbols — MUST sequence to maintain surgical splice integrity`,
311
276
  sharedFiles: [],
312
277
  sharedEntities,
313
278
  };
@@ -327,26 +292,27 @@ function classifyConflictTier(taskA, taskB) {
327
292
  sharedEntities: [],
328
293
  };
329
294
  }
295
+ // Same file, different symbols → Tier 2 (Safe via Surgical Splicer)
330
296
  return {
331
297
  tier: 2,
332
- reason: `${sharedFiles.length} shared files but different symbols — safe to parallelize`,
298
+ reason: `${sharedFiles.length} shared files but different symbols — safe to parallelize via surgical splicing`,
333
299
  sharedFiles,
334
300
  sharedEntities: [],
335
301
  };
336
302
  }
337
303
  // ─── Execution Plan Builder ───────────────────────────────────────
338
304
  /**
339
- * Build execution plan from tier-4 conflict graph using connected components.
340
- * Tasks with no tier-4 edges to each other run in parallel.
341
- * Tasks in the same tier-4 component run sequentially.
305
+ * Build execution plan from tier-3 conflict graph using connected components.
306
+ * Tasks with no tier-3 edges to each other run in parallel.
307
+ * Tasks in the same tier-3 component run sequentially.
342
308
  */
343
- function buildExecutionPlan(taskIds, tier4Edges) {
344
- // Build adjacency list for tier-4 conflicts
309
+ function buildExecutionPlan(taskIds, tier3Edges) {
310
+ // Build adjacency list for tier-3 conflicts
345
311
  const adj = new Map();
346
312
  for (const id of taskIds) {
347
313
  adj.set(id, new Set());
348
314
  }
349
- for (const [a, b] of tier4Edges) {
315
+ for (const [a, b] of tier3Edges) {
350
316
  adj.get(a)?.add(b);
351
317
  adj.get(b)?.add(a);
352
318
  }
@@ -454,7 +420,7 @@ export function conflictMatrix(args) {
454
420
  }
455
421
  // Pairwise comparison
456
422
  const matrix = [];
457
- const tier4Edges = [];
423
+ const tier3Edges = [];
458
424
  for (let i = 0; i < resolvedTasks.length; i++) {
459
425
  for (let j = i + 1; j < resolvedTasks.length; j++) {
460
426
  const taskA = resolvedTasks[i];
@@ -471,14 +437,14 @@ export function conflictMatrix(args) {
471
437
  if (result.sharedEntities.length > 0)
472
438
  entry.sharedEntities = result.sharedEntities;
473
439
  matrix.push(entry);
474
- if (result.tier === 4) {
475
- tier4Edges.push([taskA.id, taskB.id]);
440
+ if (result.tier === 3) {
441
+ tier3Edges.push([taskA.id, taskB.id]);
476
442
  }
477
443
  }
478
444
  }
479
445
  // Build execution plan
480
446
  const taskIds = resolvedTasks.map(t => t.id);
481
- const executionPlan = buildExecutionPlan(taskIds, tier4Edges);
447
+ const executionPlan = buildExecutionPlan(taskIds, tier3Edges);
482
448
  const result = {
483
449
  taskCount: tasks.length,
484
450
  matrix,
@@ -8,7 +8,7 @@
8
8
  import path from 'path';
9
9
  import { computeBlastRadius } from '../graph.js';
10
10
  import { getLoader, getGraph, validateProject, entityName, entityLayer, entitySummary, } from './index.js';
11
- import { isSupabaseConfigured, insertPrediction, updateActualBurn, abandonPrediction, listPredictions, } from '../supabase.js';
11
+ import { isSupabaseConfigured, insertPrediction, updateActualBurn, abandonPrediction, } from '../supabase.js';
12
12
  // ─── Layer ordering for violation detection ──────────────────────
13
13
  const LAYER_ORDER = {
14
14
  route: 0,
@@ -782,48 +782,9 @@ export async function reportActualBurn(args) {
782
782
  const { action = 'complete' } = args;
783
783
  // List mode: show recent predictions for calibration analysis
784
784
  if (action === 'list') {
785
- const rows = await listPredictions(args.project);
786
- if (rows.length === 0) {
787
- return { message: 'No predictions found.', predictions: [] };
788
- }
789
- const summary = rows.map((r) => ({
790
- id: r.id,
791
- project: r.project,
792
- targets: r.target_entities,
793
- predicted: r.predicted_total,
794
- actual: r.actual_total_tokens,
795
- drift: r.drift_ratio,
796
- estimator: r.estimator_used,
797
- status: r.status,
798
- createdAt: r.created_at,
799
- }));
800
- // Compute aggregate calibration stats for completed predictions
801
- const completed = rows.filter((r) => r.status === 'completed' && r.drift_ratio != null);
802
- let calibration;
803
- if (completed.length >= 3) {
804
- const drifts = completed.map((r) => r.drift_ratio);
805
- const meanDrift = drifts.reduce((a, b) => a + b, 0) / drifts.length;
806
- const sortedDrifts = [...drifts].sort((a, b) => a - b);
807
- const medianDrift = sortedDrifts[Math.floor(sortedDrifts.length / 2)];
808
- const maxOvershoot = Math.max(...drifts);
809
- const maxUndershoot = Math.min(...drifts);
810
- calibration = {
811
- completedSamples: completed.length,
812
- meanDrift: Math.round(meanDrift * 1000) / 1000,
813
- medianDrift: Math.round(medianDrift * 1000) / 1000,
814
- maxOvershoot: Math.round(maxOvershoot * 1000) / 1000,
815
- maxUndershoot: Math.round(maxUndershoot * 1000) / 1000,
816
- _interpretation: meanDrift > 0.2
817
- ? 'Predictions underestimate — consider increasing iteration multiplier.'
818
- : meanDrift < -0.2
819
- ? 'Predictions overestimate — consider decreasing iteration multiplier.'
820
- : 'Predictions are well-calibrated (within 20% mean drift).',
821
- };
822
- }
823
785
  return {
824
- total: rows.length,
825
- predictions: summary,
826
- ...(calibration ? { calibration } : {}),
786
+ message: 'List predictions has been migrated to the Ptah Dashboard.',
787
+ predictions: []
827
788
  };
828
789
  }
829
790
  // Complete or abandon requires prediction_id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Semantic MCP server — exposes a codebase's structure, dependencies, and constraints as queryable tools",
5
5
  "type": "module",
6
6
  "bin": {