@latentforce/shift 1.0.12 → 1.0.13

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.
@@ -26,7 +26,119 @@ function getMcpConfig(projectId) {
26
26
  env: { SHIFT_PROJECT_ID: projectId },
27
27
  };
28
28
  }
29
- async function addClaudeCode(projectId) {
29
+ const CLAUDE_MD_SECTION_MARKER = '<!-- shift-mcp-instructions -->';
30
+ const CLAUDE_MD_CONTENT = `${CLAUDE_MD_SECTION_MARKER}
31
+ ## Shift MCP Tools — MANDATORY USAGE RULES
32
+
33
+ This project has the **Shift MCP server** (\`shift\`) configured with a pre-built knowledge graph of the entire codebase. You MUST use its tools as the primary way to understand code. Do NOT rely solely on reading raw files.
34
+
35
+ ### Tools
36
+
37
+ | Tool | Purpose |
38
+ |------|---------|
39
+ | \`file_summary\` | Get an summary of any file |
40
+ | \`dependencies\` | List every file that a given file imports/depends on |
41
+ | \`blast_radius\` | List every file that would be affected if a given file changed |
42
+
43
+ ### Supported file types
44
+
45
+ The knowledge graph is built **only** from source files. The tools will return an error for any other file type.
46
+
47
+ **Indexed (use MCP tools):**
48
+ \`.js\` \`.jsx\` \`.ts\` \`.tsx\` \`.py\` \`.java\` \`.cpp\` \`.cs\` \`.go\` \`.c\` \`.h\` \`.css\` \`.scss\` \`.html\`
49
+
50
+ **NOT indexed (read directly with normal file tools):**
51
+ \`.json\` \`.yaml\` \`.yml\` \`.toml\` \`.env\` \`.md\` \`.txt\` \`.pdf\` \`.png\` \`.lock\` \`.xml\` \`.csv\` and any other non-source format.
52
+
53
+ Do NOT call \`file_summary\`, \`dependencies\`, or \`blast_radius\` on non-indexed files — it will fail. Read those files directly instead.
54
+
55
+ ---
56
+
57
+ ### RULE 1 — Codebase exploration (e.g. "explain the codebase", "how does this project work")
58
+
59
+ You MUST call \`file_summary\` on **multiple key files** to build a complete picture. Do not stop after one call.
60
+
61
+ Follow this protocol:
62
+ 1. Identify the likely entry points (e.g. \`main.ts\`, \`index.ts\`, \`app.py\`, \`server.ts\`, \`main.py\`, route files, CLI entry files).
63
+ 2. Call \`file_summary\` on each entry point (run calls in parallel where possible).
64
+ 3. Call \`dependencies\` on the most central files to discover the module graph.
65
+ 4. Call \`file_summary\` on the key modules discovered in step 3.
66
+ 5. Synthesise all results into a coherent explanation.
67
+
68
+ Never answer a "explain the whole project" question from a single \`file_summary\` call.
69
+
70
+ ---
71
+
72
+ ### RULE 2 — Before reading any file
73
+
74
+ ALWAYS call \`file_summary\` first. Only open the raw file if you need implementation details that the summary does not cover.
75
+
76
+ ---
77
+
78
+ ### RULE 3 — Before editing any file
79
+
80
+ ALWAYS call \`blast_radius\` on that file first. List the affected files in your plan before making any changes.
81
+
82
+ ---
83
+
84
+ ### RULE 4 — When tracing a bug or data flow
85
+
86
+ Use \`dependencies\` to follow the chain rather than grepping manually. Start from the file where the symptom appears and walk the dependency graph.
87
+
88
+ ---
89
+
90
+ ### Usage reference
91
+
92
+ \`\`\`
93
+ # Understand a file
94
+ file_summary(file_path="src/server.ts")
95
+
96
+ # Understand with folder context (level = number of parent dirs to include)
97
+ file_summary(file_path="src/server.ts", level=1)
98
+
99
+ # See what a file imports
100
+ dependencies(file_path="src/server.ts")
101
+
102
+ # See what breaks if this file changes
103
+ blast_radius(file_path="src/server.ts")
104
+ \`\`\`
105
+
106
+ > Run \`shift-cli update-drg\` after significant code changes to keep the knowledge graph current.
107
+ <!-- end-shift-mcp-instructions -->
108
+ `;
109
+ const CLAUDE_MD_END_MARKER = '<!-- end-shift-mcp-instructions -->';
110
+ function createClaudeMd(projectRoot) {
111
+ const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
112
+ if (fs.existsSync(claudeMdPath)) {
113
+ const existing = fs.readFileSync(claudeMdPath, 'utf-8');
114
+ if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
115
+ // Replace the existing section (start marker to end marker inclusive)
116
+ const start = existing.indexOf(CLAUDE_MD_SECTION_MARKER);
117
+ const end = existing.indexOf(CLAUDE_MD_END_MARKER);
118
+ if (end !== -1) {
119
+ const before = existing.slice(0, start).trimEnd();
120
+ const after = existing.slice(end + CLAUDE_MD_END_MARKER.length).trimStart();
121
+ const updated = (before ? before + '\n\n' : '') + CLAUDE_MD_CONTENT + (after ? '\n\n' + after : '');
122
+ fs.writeFileSync(claudeMdPath, updated);
123
+ }
124
+ else {
125
+ // Malformed — just overwrite from the marker onwards
126
+ const before = existing.slice(0, existing.indexOf(CLAUDE_MD_SECTION_MARKER)).trimEnd();
127
+ fs.writeFileSync(claudeMdPath, (before ? before + '\n\n' : '') + CLAUDE_MD_CONTENT);
128
+ }
129
+ console.log(' Updated Shift MCP instructions in CLAUDE.md.');
130
+ return;
131
+ }
132
+ // Append section to existing file
133
+ fs.writeFileSync(claudeMdPath, existing.trimEnd() + '\n\n' + CLAUDE_MD_CONTENT);
134
+ console.log(' Updated CLAUDE.md with Shift MCP instructions.');
135
+ }
136
+ else {
137
+ fs.writeFileSync(claudeMdPath, CLAUDE_MD_CONTENT);
138
+ console.log(' Created CLAUDE.md with Shift MCP instructions.');
139
+ }
140
+ }
141
+ async function addClaudeCode(projectId, projectRoot) {
30
142
  const jsonPayload = JSON.stringify({
31
143
  type: 'stdio',
32
144
  command: 'shift-cli',
@@ -55,6 +167,7 @@ async function addClaudeCode(projectId) {
55
167
  console.log(` claude mcp add-json shift '${jsonPayload}'`);
56
168
  }
57
169
  }
170
+ createClaudeMd(projectRoot);
58
171
  }
59
172
  async function addCodex(projectId) {
60
173
  const config = getMcpConfig(projectId);
@@ -190,7 +303,7 @@ export async function addCommand(tool) {
190
303
  console.log(`\n Configuring Shift MCP for ${tool}...\n`);
191
304
  switch (tool) {
192
305
  case 'claude-code':
193
- await addClaudeCode(projectId);
306
+ await addClaudeCode(projectId, projectRoot);
194
307
  break;
195
308
  case 'opencode':
196
309
  addOpencode(projectId, projectRoot);
@@ -13,7 +13,7 @@ export async function configCommand(action, key, value) {
13
13
  await clearConfig(key);
14
14
  break;
15
15
  default:
16
- console.log('Usage: shift config [show|set|clear] [key] [value]');
16
+ console.log('Usage: shift-cli config [show|set|clear] [key] [value]');
17
17
  console.log('');
18
18
  console.log('Commands:');
19
19
  console.log(' show Show current configuration');
@@ -22,16 +22,16 @@ export async function configCommand(action, key, value) {
22
22
  console.log('');
23
23
  console.log('Keys:');
24
24
  console.log(' api-key Your Shift API key');
25
- console.log(' api-url SHIFT_API_URL (default: http://localhost:9000)');
26
- console.log(' orch-url SHIFT_ORCH_URL (default: http://localhost:9999)');
27
- console.log(' ws-url SHIFT_WS_URL (default: ws://localhost:9999)');
25
+ console.log(' api-url SHIFT_API_URL');
26
+ console.log(' orch-url SHIFT_ORCH_URL');
27
+ console.log(' ws-url SHIFT_WS_URL');
28
28
  console.log('');
29
29
  console.log('Examples:');
30
- console.log(' shift config');
31
- console.log(' shift config set api-url https://api.latentforce.ai');
32
- console.log(' shift config set orch-url https://orch.latentforce.ai');
33
- console.log(' shift config set ws-url wss://ws.latentforce.ai');
34
- console.log(' shift config clear urls');
30
+ console.log(' shift-cli config');
31
+ console.log(' shift-cli config set api-url http://localhost:9000');
32
+ console.log(' shift-cli config set orch-url http://localhost:9999');
33
+ console.log(' shift-cli config set ws-url ws://localhost:9999');
34
+ console.log(' shift-cli config clear urls');
35
35
  break;
36
36
  }
37
37
  }
@@ -66,7 +66,13 @@ function showConfig() {
66
66
  }
67
67
  async function setConfigValue(key, value) {
68
68
  if (!key) {
69
- console.error('Error: Key is required. Run "shift config" to see available keys.');
69
+ console.error('Error: Key is required. Run "shift-cli config" to see available keys.');
70
+ process.exit(1);
71
+ }
72
+ const validKeys = ['api-key', 'api-url', 'orch-url', 'ws-url'];
73
+ if (!validKeys.includes(key)) {
74
+ console.error(`Unknown key: ${key}`);
75
+ console.log(`Available keys: ${validKeys.join(', ')}`);
70
76
  process.exit(1);
71
77
  }
72
78
  // If no value provided, prompt for it
@@ -126,7 +132,7 @@ async function clearConfig(key) {
126
132
  case 'api-url':
127
133
  case 'orch-url':
128
134
  case 'ws-url':
129
- console.log('Use "shift config clear urls" to clear all URLs');
135
+ console.log('Use "shift-cli config clear urls" to clear all URLs');
130
136
  break;
131
137
  default:
132
138
  console.error(`Unknown key: ${key}`);
@@ -226,8 +226,7 @@ export async function initCommand(options = {}) {
226
226
  try {
227
227
  const response = await sendInitScan(apiKey, project.projectId, payload);
228
228
  console.log('[Init] ✓ Backend initialization completed');
229
- console.log(`[Init] Files read: ${response.files_read}`);
230
- console.log(`[Init] Files failed: ${response.files_failed}`);
229
+ console.log(`[Init] Source files queued: ${response.source_files_count ?? response.files_read}`);
231
230
  // Update local config with agent info (matching extension)
232
231
  if (response.agents_created?.theme_planner) {
233
232
  const agentInfo = {
package/build/index.js CHANGED
@@ -137,8 +137,8 @@ const updateDrgCmd = program
137
137
  });
138
138
  updateDrgCmd.addHelpText('after', `
139
139
  Details:
140
- Scans JavaScript/TypeScript files (.js, .jsx, .ts, .tsx, .mjs, .cjs),
141
- detects git changes, and sends file contents to the backend for
140
+ Scans files, detects git changes,
141
+ and sends file contents to the backend for
142
142
  dependency analysis.
143
143
 
144
144
  Modes:
@@ -186,9 +186,9 @@ Actions:
186
186
 
187
187
  Configurable keys:
188
188
  api-key Your Shift API key
189
- api-url Backend API URL (default: http://localhost:9000)
190
- orch-url Orchestrator URL (default: http://localhost:9999)
191
- ws-url WebSocket URL (default: ws://localhost:9999)
189
+ api-url Backend API URL
190
+ orch-url Orchestrator URL
191
+ ws-url WebSocket URL
192
192
 
193
193
  URLs can also be set via environment variables:
194
194
  SHIFT_API_URL, SHIFT_ORCH_URL, SHIFT_WS_URL
@@ -196,7 +196,7 @@ URLs can also be set via environment variables:
196
196
  Examples:
197
197
  shift-cli config Show config
198
198
  shift-cli config set api-key sk-abc123 Set API key
199
- shift-cli config set api-url https://api.shift.ai Set API URL
199
+ shift-cli config set api-url http://localhost:9000 Set API URL
200
200
  shift-cli config clear api-key Clear API key
201
201
  shift-cli config clear Clear all config
202
202
  `);
@@ -2,9 +2,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { z } from 'zod';
4
4
  import { createRequire } from 'module';
5
+ import { getApiKey } from './utils/config.js';
5
6
  const require = createRequire(import.meta.url);
6
7
  const { version } = require('../package.json');
7
8
  const BASE_URL = process.env.SHIFT_BACKEND_URL || "https://dev-shift-lite.latentforce.ai";
9
+ function getApiKeyFromEnv() {
10
+ return process.env.SHIFT_API_KEY?.trim() || getApiKey();
11
+ }
8
12
  function getProjectIdFromEnv() {
9
13
  const projectId = process.env.SHIFT_PROJECT_ID;
10
14
  if (!projectId || projectId.trim() === "") {
@@ -30,11 +34,16 @@ function normalizePath(filePath) {
30
34
  // helper
31
35
  async function callBackendAPI(endpoint, data) {
32
36
  try {
37
+ const apiKey = getApiKeyFromEnv();
38
+ const headers = {
39
+ 'Content-Type': 'application/json',
40
+ };
41
+ if (apiKey) {
42
+ headers['Authorization'] = `Bearer ${apiKey}`;
43
+ }
33
44
  const response = await fetch(`${BASE_URL}${endpoint}`, {
34
45
  method: 'POST',
35
- headers: {
36
- 'Content-Type': 'application/json',
37
- },
46
+ headers,
38
47
  body: JSON.stringify(data),
39
48
  });
40
49
  if (!response.ok) {
@@ -144,7 +153,7 @@ export async function startMcpServer() {
144
153
  // Tools:
145
154
  // Blast radius
146
155
  server.registerTool("blast_radius", {
147
- description: "Analyzes the blast radius of a file or component - shows what would be affected if this file were modified or deleted",
156
+ 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.",
148
157
  inputSchema: z.object({
149
158
  file_path: z.string().describe("Path to the file (relative to project root)"),
150
159
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
@@ -168,7 +177,7 @@ export async function startMcpServer() {
168
177
  });
169
178
  // Dependencies
170
179
  server.registerTool("dependencies", {
171
- description: "Retrieves all dependencies for a given file or component, including direct and transitive dependencies",
180
+ 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.",
172
181
  inputSchema: z.object({
173
182
  file_path: z.string().describe("Path to the file"),
174
183
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
@@ -190,7 +199,7 @@ export async function startMcpServer() {
190
199
  });
191
200
  // File summary (maps to what-is-this-file)
192
201
  server.registerTool("file_summary", {
193
- description: "Generates a comprehensive summary of a file including its purpose, exports, imports, and key functions, with optional parent directory context",
202
+ 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.",
194
203
  inputSchema: z.object({
195
204
  file_path: z.string().describe("Path to the file to summarize"),
196
205
  project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latentforce/shift",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Shift CLI - AI-powered code intelligence with MCP support",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",