@latentforce/shift 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -302,9 +302,11 @@ When left as-is (single entry with `language: null` and `path: ""`), the server
302
302
 
303
303
  | Tool | Description |
304
304
  |------|-------------|
305
- | `blast_radius` | Analyze what files would be affected if a file is modified or deleted |
306
- | `dependencies` | Get all dependencies for a file with relationship summaries |
305
+ | `project_overview` | Get the highest-level context for the project: architecture, tech stack, entry points, and top-level modules |
307
306
  | `file_summary` | Get a summary of a file with optional parent directory context |
307
+ | `module_summary` | Get full documentation for the module that owns a given file, including DRG cluster metadata and parent/child module relationships |
308
+ | `dependencies` | Get all dependencies for a file with relationship summaries |
309
+ | `blast_radius` | Analyze what files would be affected if a file is modified or deleted |
308
310
 
309
311
  Each tool accepts an optional `project_id` parameter. If not provided, it falls back to the `SHIFT_PROJECT_ID` environment variable.
310
312
 
@@ -31,53 +31,118 @@ function normalizePath(filePath) {
31
31
  p = p.replace(/^\//, '');
32
32
  return p;
33
33
  }
34
- // helper
34
+ function getAuthHeaders() {
35
+ const apiKey = getApiKeyFromEnv();
36
+ const headers = { 'Content-Type': 'application/json' };
37
+ if (apiKey)
38
+ headers['Authorization'] = `Bearer ${apiKey}`;
39
+ return headers;
40
+ }
41
+ function handleApiError(response, text) {
42
+ if (response.status === 404) {
43
+ if (text.includes('Knowledge graph not found') || text.includes('knowledge graph')) {
44
+ throw new Error("No knowledge graph exists for this project. Run 'shift-cli update-drg' to build it.");
45
+ }
46
+ if (text.includes('File not found') || text.includes('not found in')) {
47
+ const pathMatch = text.match(/['"]([^'"]+)['"]/);
48
+ const filePath = pathMatch ? pathMatch[1] : 'the requested file';
49
+ throw new Error(`File '${filePath}' not found in knowledge graph. Possible causes:\n` +
50
+ ` 1. The path may be incorrect (check casing and slashes)\n` +
51
+ ` 2. The file was added after the last 'shift-cli update-drg'\n` +
52
+ ` 3. The file type is not supported (only JS/TS files are indexed)`);
53
+ }
54
+ }
55
+ throw new Error(`API call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
56
+ }
57
+ /** POST to backend API */
35
58
  async function callBackendAPI(endpoint, data) {
36
- try {
37
- const apiKey = getApiKeyFromEnv();
38
- const headers = {
39
- 'Content-Type': 'application/json',
40
- };
41
- if (apiKey) {
42
- headers['Authorization'] = `Bearer ${apiKey}`;
43
- }
44
- const response = await fetch(`${BASE_URL}${endpoint}`, {
45
- method: 'POST',
46
- headers,
47
- body: JSON.stringify(data),
48
- });
49
- if (!response.ok) {
50
- const text = await response.text();
51
- // Provide actionable error messages
52
- if (response.status === 404) {
53
- if (text.includes('Knowledge graph not found') || text.includes('knowledge graph')) {
54
- throw new Error("No knowledge graph exists for this project. Run 'shift-cli update-drg' to build it.");
55
- }
56
- if (text.includes('File not found') || text.includes('not found in')) {
57
- // Extract path from error if possible
58
- const pathMatch = text.match(/['"]([^'"]+)['"]/);
59
- const filePath = pathMatch ? pathMatch[1] : 'the requested file';
60
- throw new Error(`File '${filePath}' not found in knowledge graph. Possible causes:\n` +
61
- ` 1. The path may be incorrect (check casing and slashes)\n` +
62
- ` 2. The file was added after the last 'shift-cli update-drg'\n` +
63
- ` 3. The file type is not supported (only JS/TS files are indexed)`);
64
- }
65
- }
66
- throw new Error(`API call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
67
- }
68
- return await response.json();
69
- }
70
- catch (error) {
71
- throw error;
59
+ const response = await fetch(`${BASE_URL}${endpoint}`, {
60
+ method: 'POST',
61
+ headers: getAuthHeaders(),
62
+ body: JSON.stringify(data),
63
+ });
64
+ if (!response.ok) {
65
+ const text = await response.text();
66
+ handleApiError(response, text);
72
67
  }
68
+ return await response.json();
73
69
  }
70
+ /** GET from backend API with query params */
71
+ async function callBackendAPIGet(endpoint, params) {
72
+ const qs = new URLSearchParams(params).toString();
73
+ const url = `${BASE_URL}${endpoint}${qs ? `?${qs}` : ''}`;
74
+ const response = await fetch(url, {
75
+ method: 'GET',
76
+ headers: getAuthHeaders(),
77
+ });
78
+ if (!response.ok) {
79
+ const text = await response.text();
80
+ handleApiError(response, text);
81
+ }
82
+ return await response.json();
83
+ }
84
+ // ============= FORMATTERS =============
74
85
  /** Format file_summary response as readable markdown */
75
86
  function formatFileSummary(data) {
76
87
  const lines = [];
77
88
  lines.push(`## File: ${data.path}`);
89
+ if (data.module_name) {
90
+ lines.push(`**Module:** ${data.module_name}`);
91
+ }
78
92
  lines.push('');
79
93
  lines.push('### Summary');
80
94
  lines.push(data.summary || 'No summary available.');
95
+ if (data.exports && Object.keys(data.exports).length > 0) {
96
+ lines.push('');
97
+ lines.push('### Exports');
98
+ for (const [category, items] of Object.entries(data.exports)) {
99
+ if (items?.length > 0) {
100
+ lines.push(`**${category}:** ${items.join(', ')}`);
101
+ }
102
+ }
103
+ }
104
+ if (data.key_components?.length > 0) {
105
+ lines.push('');
106
+ lines.push('### Key Components');
107
+ for (const comp of data.key_components) {
108
+ if (typeof comp === 'object' && comp !== null) {
109
+ const name = comp.name || comp.component || Object.keys(comp)[0] || '';
110
+ const desc = comp.description || comp.purpose || Object.values(comp)[0] || '';
111
+ lines.push(`- **${name}**: ${desc}`);
112
+ }
113
+ else {
114
+ lines.push(`- ${comp}`);
115
+ }
116
+ }
117
+ }
118
+ if (data.internal_state) {
119
+ lines.push('');
120
+ lines.push(`**Internal State:** ${data.internal_state}`);
121
+ }
122
+ if (data.error_handling) {
123
+ lines.push(`**Error Handling:** ${data.error_handling}`);
124
+ }
125
+ if (data.constraints?.length > 0) {
126
+ lines.push('');
127
+ lines.push('### Constraints');
128
+ for (const c of data.constraints) {
129
+ lines.push(`- ${c}`);
130
+ }
131
+ }
132
+ if (data.dependencies?.length > 0) {
133
+ lines.push('');
134
+ lines.push(`### Dependencies (${data.dependency_count ?? data.dependencies.length} imports)`);
135
+ for (const dep of data.dependencies) {
136
+ lines.push(`- ${dep}`);
137
+ }
138
+ }
139
+ if (data.dependents?.length > 0) {
140
+ lines.push('');
141
+ lines.push(`### Dependents (${data.dependent_count ?? data.dependents.length} files import this)`);
142
+ for (const dep of data.dependents) {
143
+ lines.push(`- ${dep}`);
144
+ }
145
+ }
81
146
  if (data.parent_summaries?.length > 0) {
82
147
  lines.push('');
83
148
  lines.push('### Directory Context');
@@ -91,41 +156,73 @@ function formatFileSummary(data) {
91
156
  function formatDependencies(data) {
92
157
  const lines = [];
93
158
  const deps = data.dependencies || [];
94
- const edges = data.edge_summaries || {};
159
+ // edge_details is now a dict of {imports, usage_pattern, data_flow, dependency_summary}
160
+ const edgeDetails = data.edge_details || {};
95
161
  lines.push(`## Dependencies: ${data.path}`);
162
+ if (data.module_name) {
163
+ lines.push(`**Module:** ${data.module_name}`);
164
+ }
96
165
  lines.push('');
166
+ // --- Forward dependencies ---
97
167
  if (deps.length === 0) {
98
168
  lines.push('This file has no dependencies.');
99
169
  }
100
170
  else {
101
- lines.push(`This file depends on **${deps.length}** file(s):`);
171
+ lines.push(`### Imports (${deps.length} file(s) this file depends on)`);
102
172
  lines.push('');
103
173
  for (const dep of deps) {
104
- const summary = edges[dep];
105
- if (summary) {
106
- lines.push(`- **${dep}**: ${summary}`);
174
+ const edge = edgeDetails[dep];
175
+ if (edge) {
176
+ lines.push(`- **${dep}**`);
177
+ if (edge.usage_pattern)
178
+ lines.push(` - Usage: ${edge.usage_pattern}`);
179
+ if (edge.data_flow)
180
+ lines.push(` - Data flow: ${edge.data_flow}`);
181
+ if (edge.imports?.length > 0)
182
+ lines.push(` - Imports: ${edge.imports.join(', ')}`);
183
+ if (edge.dependency_summary)
184
+ lines.push(` - Summary: ${edge.dependency_summary}`);
107
185
  }
108
186
  else {
109
187
  lines.push(`- **${dep}**`);
110
188
  }
111
189
  }
112
190
  }
191
+ // --- Reverse dependencies ---
192
+ const dependents = data.dependents || [];
193
+ const dependentEdges = data.dependent_edge_details || {};
194
+ if (dependents.length > 0) {
195
+ lines.push('');
196
+ lines.push(`### Dependents (${dependents.length} file(s) that import this file)`);
197
+ lines.push('');
198
+ for (const dep of dependents) {
199
+ const edge = dependentEdges[dep];
200
+ if (edge) {
201
+ lines.push(`- **${dep}**`);
202
+ if (edge.usage_pattern)
203
+ lines.push(` - Usage: ${edge.usage_pattern}`);
204
+ if (edge.data_flow)
205
+ lines.push(` - Data flow: ${edge.data_flow}`);
206
+ if (edge.imports?.length > 0)
207
+ lines.push(` - Imports: ${edge.imports.join(', ')}`);
208
+ if (edge.dependency_summary)
209
+ lines.push(` - Summary: ${edge.dependency_summary}`);
210
+ }
211
+ else {
212
+ lines.push(`- **${dep}**`);
213
+ }
214
+ }
215
+ }
216
+ // --- Implicit dependencies ---
113
217
  const implicitDeps = data.implicit_dependencies || [];
114
218
  if (implicitDeps.length > 0) {
115
219
  lines.push('');
116
- lines.push('## Implicit / external dependencies');
117
- lines.push('');
118
- lines.push(`Implicit dependencies (**${implicitDeps.length}**):`);
220
+ lines.push(`### Implicit / External Dependencies (${implicitDeps.length})`);
119
221
  lines.push('');
120
222
  const implicitEdges = data.implicit_edge_summaries || {};
121
223
  for (const dep of implicitDeps) {
122
224
  const summary = implicitEdges[dep];
123
- if (summary) {
124
- lines.push(`- **${dep}**: ${summary}`);
125
- }
126
- else {
127
- lines.push(`- **${dep}**`);
128
- }
225
+ lines.push(summary ? `- **${dep}**: ${summary}` : `- **${dep}**`);
129
226
  }
130
227
  }
131
228
  return lines.join('\n');
@@ -135,6 +232,9 @@ function formatBlastRadius(data) {
135
232
  const lines = [];
136
233
  const affected = data.affected_files || [];
137
234
  lines.push(`## Blast Radius: ${data.path}`);
235
+ if (data.module_name) {
236
+ lines.push(`**Module:** ${data.module_name}`);
237
+ }
138
238
  lines.push('');
139
239
  if (affected.length === 0) {
140
240
  lines.push('No other files are affected by changes to this file.');
@@ -152,26 +252,155 @@ function formatBlastRadius(data) {
152
252
  }
153
253
  const levels = Object.keys(byLevel).map(Number).sort((a, b) => a - b);
154
254
  for (const level of levels) {
155
- const label = level === 1 ? 'Level 1 (direct)' : `Level ${level}`;
156
- lines.push(`### ${label}`);
255
+ lines.push(`### ${level === 1 ? 'Level 1 (direct)' : `Level ${level}`}`);
157
256
  for (const file of byLevel[level]) {
158
257
  lines.push(`- **${file.path}**: ${file.summary || 'No summary'}`);
159
258
  }
160
259
  lines.push('');
161
260
  }
162
261
  }
262
+ // --- Affected clusters ---
263
+ const clusters = data.cluster_blast_radius || [];
264
+ if (clusters.length > 0) {
265
+ lines.push('### Affected Modules/Folders');
266
+ for (const cluster of clusters) {
267
+ lines.push(`- **${cluster.path}**: ${cluster.summary}`);
268
+ }
269
+ }
270
+ return lines.join('\n');
271
+ }
272
+ /** Format module_summary response as readable markdown */
273
+ function formatModuleSummary(data) {
274
+ const lines = [];
275
+ lines.push(`## Module: ${data.module_name}`);
276
+ if (data.parent_module) {
277
+ lines.push(`**Parent module:** ${data.parent_module}`);
278
+ }
279
+ if (data.child_modules?.length > 0) {
280
+ lines.push(`**Sub-modules:** ${data.child_modules.join(', ')}`);
281
+ }
282
+ if (data.drg_cluster_summary) {
283
+ lines.push('');
284
+ lines.push('### Architecture Summary');
285
+ lines.push(data.drg_cluster_summary);
286
+ }
287
+ if (data.drg_cluster_metadata && Object.keys(data.drg_cluster_metadata).length > 0) {
288
+ const meta = data.drg_cluster_metadata;
289
+ if (meta.architecture_layers?.length > 0) {
290
+ lines.push('');
291
+ lines.push('**Architecture Layers:**');
292
+ for (const layer of meta.architecture_layers) {
293
+ lines.push(`- ${layer}`);
294
+ }
295
+ }
296
+ if (meta.technology_stack?.length > 0) {
297
+ lines.push('');
298
+ lines.push('**Technology Stack:**');
299
+ for (const tech of meta.technology_stack) {
300
+ lines.push(`- ${tech}`);
301
+ }
302
+ }
303
+ }
304
+ if (data.components?.length > 0) {
305
+ lines.push('');
306
+ lines.push(`### Files in this module (${data.components.length})`);
307
+ for (const comp of data.components) {
308
+ lines.push(`- ${comp}`);
309
+ }
310
+ }
311
+ if (data.content) {
312
+ lines.push('');
313
+ lines.push('### Full Documentation');
314
+ lines.push(data.content);
315
+ }
163
316
  return lines.join('\n');
164
317
  }
318
+ /** Format project_overview response as readable markdown */
319
+ function formatProjectOverview(data) {
320
+ const lines = [];
321
+ lines.push('## Project Overview');
322
+ lines.push('');
323
+ if (data.architecture_summary) {
324
+ lines.push('### Architecture Summary');
325
+ lines.push(data.architecture_summary);
326
+ lines.push('');
327
+ }
328
+ if (data.architecture_layers?.length > 0) {
329
+ lines.push('### Architecture Layers');
330
+ for (const layer of data.architecture_layers) {
331
+ if (typeof layer === 'string') {
332
+ lines.push(`- ${layer}`);
333
+ }
334
+ else if (layer && typeof layer === 'object') {
335
+ const name = layer.name || layer.layer || layer.title || Object.values(layer)[0];
336
+ const desc = layer.description || layer.desc || (Object.values(layer).length > 1 ? Object.values(layer)[1] : '');
337
+ lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
338
+ }
339
+ }
340
+ lines.push('');
341
+ }
342
+ if (data.technology_stack?.length > 0) {
343
+ lines.push('### Technology Stack');
344
+ for (const tech of data.technology_stack) {
345
+ if (typeof tech === 'string') {
346
+ lines.push(`- ${tech}`);
347
+ }
348
+ else if (tech && typeof tech === 'object') {
349
+ const name = tech.name || tech.technology || tech.title || Object.values(tech)[0];
350
+ const desc = tech.description || tech.desc || (Object.values(tech).length > 1 ? Object.values(tech)[1] : '');
351
+ lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
352
+ }
353
+ }
354
+ lines.push('');
355
+ }
356
+ if (data.entry_points?.length > 0) {
357
+ lines.push('### Entry Points');
358
+ for (const ep of data.entry_points) {
359
+ if (typeof ep === 'string') {
360
+ lines.push(`- ${ep}`);
361
+ }
362
+ else if (ep && typeof ep === 'object') {
363
+ const name = ep.name || ep.file || ep.path || Object.values(ep)[0];
364
+ const desc = ep.description || ep.desc || (Object.values(ep).length > 1 ? Object.values(ep)[1] : '');
365
+ lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
366
+ }
367
+ }
368
+ lines.push('');
369
+ }
370
+ if (data.constraints?.length > 0) {
371
+ lines.push('### Constraints');
372
+ for (const c of data.constraints) {
373
+ lines.push(`- ${c}`);
374
+ }
375
+ lines.push('');
376
+ }
377
+ if (data.top_level_modules?.length > 0) {
378
+ lines.push('### Top-level Modules');
379
+ lines.push('');
380
+ for (const mod of data.top_level_modules) {
381
+ lines.push(`#### ${mod.path}`);
382
+ lines.push(mod.summary || '');
383
+ if (mod.files?.length > 0) {
384
+ lines.push(`Files: ${mod.files.join(', ')}`);
385
+ }
386
+ lines.push('');
387
+ }
388
+ }
389
+ if (data.codewiki_overview) {
390
+ lines.push('### Project Documentation');
391
+ lines.push(data.codewiki_overview);
392
+ }
393
+ return lines.join('\n');
394
+ }
395
+ // ============= MCP SERVER =============
165
396
  export async function startMcpServer() {
166
- // Create server instance
167
397
  const server = new McpServer({
168
398
  name: "shift",
169
399
  version,
170
400
  });
171
- // Tools:
172
- // Blast radius
401
+ // ---- blast_radius ----
173
402
  server.registerTool("blast_radius", {
174
- description: "Use this BEFORE editing or refactoring any source file. Returns every file in the project that imports or depends on the given file, grouped by dependency depth (level 1 = direct importers, level 2 = their importers, etc.). Use this to understand the full impact of a change before making it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
403
+ description: "Use this BEFORE editing or refactoring any source file. Returns every file in the project that imports or depends on the given file, grouped by dependency depth (level 1 = direct importers, level 2 = their importers, etc.). Also returns the affected modules/folders. Use this to understand the full impact of a change before making it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
175
404
  inputSchema: z.object({
176
405
  file_path: z.string().describe("Path to the file (relative to project root)"),
177
406
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
@@ -184,18 +413,11 @@ export async function startMcpServer() {
184
413
  project_id: projectId,
185
414
  ...(args.level != null && { level: args.level }),
186
415
  });
187
- return {
188
- content: [
189
- {
190
- type: "text",
191
- text: formatBlastRadius(data)
192
- },
193
- ],
194
- };
416
+ return { content: [{ type: "text", text: formatBlastRadius(data) }] };
195
417
  });
196
- // Dependencies
418
+ // ---- dependencies ----
197
419
  server.registerTool("dependencies", {
198
- description: "Returns every file that the given source file imports or depends on, along with a short summary of why each dependency is used. Use this to trace data flow, understand module relationships, follow a bug through the call chain, or map out what a file relies on before modifying it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
420
+ description: "Returns bidirectional dependencies of a source file: what it imports (with usage pattern, data flow, and summary per dependency) AND what imports it (reverse dependencies). Use this to trace data flow, understand module relationships, follow a bug through the call chain, or map out what a file relies on before modifying it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
199
421
  inputSchema: z.object({
200
422
  file_path: z.string().describe("Path to the file"),
201
423
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
@@ -206,18 +428,11 @@ export async function startMcpServer() {
206
428
  path: normalizePath(args.file_path),
207
429
  project_id: projectId,
208
430
  });
209
- return {
210
- content: [
211
- {
212
- type: "text",
213
- text: formatDependencies(data)
214
- },
215
- ],
216
- };
431
+ return { content: [{ type: "text", text: formatDependencies(data) }] };
217
432
  });
218
- // File summary (maps to what-is-this-file)
433
+ // ---- file_summary ----
219
434
  server.registerTool("file_summary", {
220
- description: "Returns an AI-generated summary of a source file: what it does, its key exports and functions, its role in the project, and how it fits into the surrounding architecture. Always call this before reading a raw file — it gives you the essential context without having to parse the full source. Use the 'level' parameter to include summaries of parent directories for broader context. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — for .json, .yaml, .md, or other non-source files, read them directly instead.",
435
+ description: "Returns an AI-generated summary of a source file: what it does, its key exports and functions, internal state, error handling, constraints, what it imports, who imports it, and which module it belongs to. Always call this before reading a raw file — it gives you the essential context without having to parse the full source. Use the 'level' parameter to include summaries of parent directories for broader context. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — for .json, .yaml, .md, or other non-source files, read them directly instead.",
221
436
  inputSchema: z.object({
222
437
  file_path: z.string().describe("Path to the file to summarize"),
223
438
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
@@ -230,14 +445,35 @@ export async function startMcpServer() {
230
445
  project_id: projectId,
231
446
  level: args.level ?? 0,
232
447
  });
233
- return {
234
- content: [
235
- {
236
- type: "text",
237
- text: formatFileSummary(data)
238
- },
239
- ],
240
- };
448
+ return { content: [{ type: "text", text: formatFileSummary(data) }] };
449
+ });
450
+ // ---- module_summary ----
451
+ server.registerTool("module_summary", {
452
+ description: "Given any file path, returns the full documentation for the module that owns it, the list of files in that module, the corresponding DRG cluster summary and metadata, and parent/child module relationships. Use this after file_summary to get broader module-level context before editing — especially useful when a change may affect an entire module rather than just one file.",
453
+ inputSchema: z.object({
454
+ file_path: z.string().describe("Path to any file in the module (relative to project root)"),
455
+ project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
456
+ })
457
+ }, async (args) => {
458
+ const projectId = resolveProjectId(args);
459
+ const data = await callBackendAPI('/api/v1/mcp/module-summary', {
460
+ path: normalizePath(args.file_path),
461
+ project_id: projectId,
462
+ });
463
+ return { content: [{ type: "text", text: formatModuleSummary(data) }] };
464
+ });
465
+ // ---- project_overview ----
466
+ server.registerTool("project_overview", {
467
+ description: "Returns the highest-level context for the project: architecture summary, layers, tech stack, entry points, constraints, top-level modules with their files, and the project documentation overview. Call this first at the start of a session — before exploring any specific file — to understand the overall structure and where things live.",
468
+ inputSchema: z.object({
469
+ project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
470
+ })
471
+ }, async (args) => {
472
+ const projectId = resolveProjectId(args);
473
+ const data = await callBackendAPIGet('/api/v1/mcp/project-overview', {
474
+ project_id: projectId,
475
+ });
476
+ return { content: [{ type: "text", text: formatProjectOverview(data) }] };
241
477
  });
242
478
  const transport = new StdioServerTransport();
243
479
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latentforce/shift",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Shift CLI - AI-powered code intelligence with MCP support",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",