@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.
- package/build/cli/commands/add.js +115 -2
- package/build/cli/commands/config.js +17 -11
- package/build/cli/commands/init.js +1 -2
- package/build/index.js +6 -6
- package/build/mcp-server.js +15 -6
- package/package.json +1 -1
|
@@ -26,7 +26,119 @@ function getMcpConfig(projectId) {
|
|
|
26
26
|
env: { SHIFT_PROJECT_ID: projectId },
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
|
|
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
|
|
26
|
-
console.log(' orch-url SHIFT_ORCH_URL
|
|
27
|
-
console.log(' ws-url SHIFT_WS_URL
|
|
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
|
|
32
|
-
console.log(' shift config set orch-url
|
|
33
|
-
console.log(' shift config set ws-url
|
|
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]
|
|
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
|
|
141
|
-
|
|
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
|
|
190
|
-
orch-url Orchestrator URL
|
|
191
|
-
ws-url WebSocket URL
|
|
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
|
|
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
|
`);
|
package/build/mcp-server.js
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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"),
|