@latentforce/shift 1.0.9 → 1.0.10
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 +144 -16
- package/build/cli/commands/init.js +14 -0
- package/build/cli/commands/start.js +5 -0
- package/build/cli/commands/update-drg.js +13 -0
- package/build/index.js +120 -25
- package/build/utils/shiftignore.js +108 -0
- package/build/utils/tree-scanner.js +11 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
AI-powered code intelligence CLI and MCP server for Claude.
|
|
4
4
|
|
|
5
|
+
Shift indexes your codebase, builds a dependency relationship graph (DRG), and provides AI-powered insights via MCP tools — enabling Claude to understand your project's structure, dependencies, and blast radius of changes.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install -g @latentforce/shift
|
|
9
11
|
```
|
|
10
12
|
|
|
13
|
+
Requires Node.js >= 18.
|
|
14
|
+
|
|
11
15
|
## Quick Start
|
|
12
16
|
|
|
13
17
|
### Guest mode (zero-config)
|
|
@@ -34,13 +38,15 @@ shift-cli start # Configure API key and project interactively
|
|
|
34
38
|
shift-cli init # Index project files
|
|
35
39
|
```
|
|
36
40
|
|
|
41
|
+
## MCP Integration
|
|
42
|
+
|
|
37
43
|
### Add to Claude Code
|
|
38
44
|
|
|
39
45
|
```bash
|
|
40
46
|
claude mcp add-json shift '{"type":"stdio","command":"shift-cli","args":["mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
|
|
41
47
|
```
|
|
42
48
|
|
|
43
|
-
Or using npx:
|
|
49
|
+
Or using npx (no global install needed):
|
|
44
50
|
|
|
45
51
|
```bash
|
|
46
52
|
claude mcp add-json shift '{"type":"stdio","command":"npx","args":["@latentforce/shift","mcp"],"env":{"SHIFT_PROJECT_ID":"YOUR_PROJECT_ID"}}'
|
|
@@ -68,52 +74,170 @@ Add to your config file (`%APPDATA%\Claude\claude_desktop_config.json` on Window
|
|
|
68
74
|
|
|
69
75
|
| Command | Description |
|
|
70
76
|
|---------|-------------|
|
|
71
|
-
| `shift-cli start` | Start daemon and configure project |
|
|
77
|
+
| `shift-cli start` | Start the daemon and configure the project |
|
|
72
78
|
| `shift-cli init` | Scan and index project files |
|
|
73
79
|
| `shift-cli update-drg` | Update the dependency relationship graph |
|
|
74
80
|
| `shift-cli stop` | Stop the daemon |
|
|
75
81
|
| `shift-cli status` | Show current status |
|
|
76
82
|
| `shift-cli config` | Manage configuration |
|
|
77
83
|
|
|
78
|
-
###
|
|
84
|
+
### `shift-cli init`
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
Performs a full project scan — collects the file tree, categorizes files (source, config, assets), gathers git info, and sends everything to the Shift backend for indexing. Automatically starts the daemon if not running and creates a default `.shiftignore` if one doesn't exist.
|
|
87
|
+
|
|
88
|
+
If the project is already indexed, you will be prompted to re-index. Use `--force` to skip the prompt.
|
|
81
89
|
|
|
82
90
|
| Flag | Description |
|
|
83
91
|
|------|-------------|
|
|
84
|
-
| `--guest` | Use guest authentication (auto-creates project) |
|
|
92
|
+
| `--guest` | Use guest authentication (auto-creates a temporary project) |
|
|
85
93
|
| `--api-key <key>` | Provide API key directly |
|
|
86
94
|
| `--project-name <name>` | Create new project or match existing by name |
|
|
87
95
|
| `--project-id <id>` | Use existing project UUID |
|
|
88
96
|
| `--template <id>` | Migration template ID for project creation |
|
|
89
97
|
| `-f, --force` | Force re-indexing even if already indexed |
|
|
98
|
+
| `--no-ignore` | Skip `.shiftignore` rules for this run (send all files) |
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
```bash
|
|
101
|
+
shift-cli init # Interactive initialization
|
|
102
|
+
shift-cli init --force # Force re-index without prompt
|
|
103
|
+
shift-cli init --no-ignore # Index all files, skip .shiftignore
|
|
104
|
+
shift-cli init --guest # Quick init with guest auth
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `shift-cli start`
|
|
108
|
+
|
|
109
|
+
Resolves authentication, configures the project, and launches a background daemon that maintains a WebSocket connection to the Shift backend. Creates a default `.shiftignore` if one doesn't exist.
|
|
92
110
|
|
|
93
111
|
| Flag | Description |
|
|
94
112
|
|------|-------------|
|
|
95
|
-
| `--guest` | Use guest authentication (auto-creates project) |
|
|
113
|
+
| `--guest` | Use guest authentication (auto-creates a temporary project) |
|
|
96
114
|
| `--api-key <key>` | Provide API key directly |
|
|
97
115
|
| `--project-name <name>` | Create new project or match existing by name |
|
|
98
116
|
| `--project-id <id>` | Use existing project UUID |
|
|
99
117
|
| `--template <id>` | Migration template ID for project creation |
|
|
100
118
|
|
|
101
|
-
|
|
119
|
+
```bash
|
|
120
|
+
shift-cli start # Interactive setup
|
|
121
|
+
shift-cli start --guest # Quick start without API key
|
|
122
|
+
shift-cli start --api-key <key> --project-name "My App"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `shift-cli update-drg`
|
|
126
|
+
|
|
127
|
+
Scans JavaScript/TypeScript files (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`), detects git changes, and sends file contents to the backend for dependency analysis. Respects `.shiftignore` rules.
|
|
102
128
|
|
|
103
129
|
| Flag | Description |
|
|
104
130
|
|------|-------------|
|
|
105
131
|
| `-m, --mode <mode>` | `baseline` (all files) or `incremental` (git-changed only, default) |
|
|
132
|
+
| `--no-ignore` | Skip `.shiftignore` rules for this run (send all files) |
|
|
133
|
+
|
|
134
|
+
**Modes:**
|
|
135
|
+
- **incremental** (default) — sends only git-changed files for faster updates
|
|
136
|
+
- **baseline** — sends all JS/TS files for a full re-analysis
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
shift-cli update-drg # Incremental update (default)
|
|
140
|
+
shift-cli update-drg -m baseline # Full re-analysis
|
|
141
|
+
shift-cli update-drg --no-ignore # Include ignored files
|
|
142
|
+
```
|
|
106
143
|
|
|
107
|
-
###
|
|
144
|
+
### `shift-cli config`
|
|
108
145
|
|
|
109
|
-
|
|
146
|
+
Manage Shift configuration (URLs, API key).
|
|
147
|
+
|
|
148
|
+
| Action | Description |
|
|
149
|
+
|--------|-------------|
|
|
150
|
+
| `show` | Display current configuration (default) |
|
|
151
|
+
| `set <key> <value>` | Set a configuration value |
|
|
152
|
+
| `clear [key]` | Clear a specific key or all configuration |
|
|
153
|
+
|
|
154
|
+
**Configurable keys:** `api-key`, `api-url`, `orch-url`, `ws-url`
|
|
155
|
+
|
|
156
|
+
URLs can also be set via environment variables: `SHIFT_API_URL`, `SHIFT_ORCH_URL`, `SHIFT_WS_URL`.
|
|
110
157
|
|
|
111
158
|
```bash
|
|
112
|
-
shift-cli
|
|
113
|
-
shift-cli
|
|
159
|
+
shift-cli config # Show config
|
|
160
|
+
shift-cli config set api-key sk-abc123 # Set API key
|
|
161
|
+
shift-cli config set api-url https://api.shift.ai # Set API URL
|
|
162
|
+
shift-cli config clear api-key # Clear API key
|
|
163
|
+
shift-cli config clear # Clear all config
|
|
114
164
|
```
|
|
115
165
|
|
|
116
|
-
|
|
166
|
+
## `.shiftignore`
|
|
167
|
+
|
|
168
|
+
Shift uses a `.shiftignore` file to control which files and directories are excluded from indexing and dependency analysis. It follows the same syntax as `.gitignore`.
|
|
169
|
+
|
|
170
|
+
A default `.shiftignore` is **automatically created** the first time you run `shift-cli init` or `shift-cli start`. You can edit it to customize which files are excluded.
|
|
171
|
+
|
|
172
|
+
### Syntax
|
|
173
|
+
|
|
174
|
+
- Blank lines are ignored
|
|
175
|
+
- Lines starting with `#` are comments
|
|
176
|
+
- Standard glob patterns (`*`, `**`, `?`)
|
|
177
|
+
- Trailing `/` matches directories only
|
|
178
|
+
- Leading `/` anchors to the project root
|
|
179
|
+
- `!` negates a pattern (re-includes a previously ignored path)
|
|
180
|
+
|
|
181
|
+
### Default `.shiftignore`
|
|
182
|
+
|
|
183
|
+
The auto-generated file excludes common non-source directories and files:
|
|
184
|
+
|
|
185
|
+
```gitignore
|
|
186
|
+
# Dependencies
|
|
187
|
+
node_modules/
|
|
188
|
+
vendor/
|
|
189
|
+
bower_components/
|
|
190
|
+
|
|
191
|
+
# Build output
|
|
192
|
+
dist/
|
|
193
|
+
build/
|
|
194
|
+
out/
|
|
195
|
+
.next/
|
|
196
|
+
|
|
197
|
+
# Test & coverage
|
|
198
|
+
coverage/
|
|
199
|
+
.nyc_output/
|
|
200
|
+
|
|
201
|
+
# Environment & secrets
|
|
202
|
+
.env
|
|
203
|
+
.env.*
|
|
204
|
+
*.pem
|
|
205
|
+
*.key
|
|
206
|
+
|
|
207
|
+
# Logs
|
|
208
|
+
*.log
|
|
209
|
+
logs/
|
|
210
|
+
|
|
211
|
+
# OS files
|
|
212
|
+
.DS_Store
|
|
213
|
+
Thumbs.db
|
|
214
|
+
|
|
215
|
+
# IDE
|
|
216
|
+
.vscode/
|
|
217
|
+
.idea/
|
|
218
|
+
*.swp
|
|
219
|
+
*.swo
|
|
220
|
+
|
|
221
|
+
# Python
|
|
222
|
+
__pycache__/
|
|
223
|
+
*.pyc
|
|
224
|
+
venv/
|
|
225
|
+
.venv/
|
|
226
|
+
|
|
227
|
+
# Misc
|
|
228
|
+
*.min.js
|
|
229
|
+
*.min.css
|
|
230
|
+
*.map
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Bypassing `.shiftignore`
|
|
234
|
+
|
|
235
|
+
Use the `--no-ignore` flag to skip `.shiftignore` rules for a single run:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
shift-cli init --no-ignore # Index all files
|
|
239
|
+
shift-cli update-drg --no-ignore # Include ignored files in DRG update
|
|
240
|
+
```
|
|
117
241
|
|
|
118
242
|
## MCP Tools
|
|
119
243
|
|
|
@@ -125,6 +249,10 @@ Scans `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` files.
|
|
|
125
249
|
|
|
126
250
|
Each tool accepts an optional `project_id` parameter. If not provided, it falls back to the `SHIFT_PROJECT_ID` environment variable.
|
|
127
251
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
252
|
+
## Configuration Files
|
|
253
|
+
|
|
254
|
+
| File | Location | Description |
|
|
255
|
+
|------|----------|-------------|
|
|
256
|
+
| Global config | `~/.shift/config.json` | API key and server URLs |
|
|
257
|
+
| Project config | `.shift/config.json` | Project ID, name, and agent info |
|
|
258
|
+
| Ignore rules | `.shiftignore` | Files/directories to exclude (auto-created) |
|
|
@@ -7,6 +7,7 @@ import { fetchProjectStatus, sendInitScan } from '../../utils/api-client.js';
|
|
|
7
7
|
import { resolveApiKey, resolveProject } from '../../utils/auth-resolver.js';
|
|
8
8
|
import { getDaemonStatus, startDaemon } from '../../daemon/daemon-manager.js';
|
|
9
9
|
import { getProjectTree, extractAllFilePaths, categorizeFiles } from '../../utils/tree-scanner.js';
|
|
10
|
+
import { loadShiftIgnore, scaffoldShiftIgnore } from '../../utils/shiftignore.js';
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const { version } = require('../../../package.json');
|
|
12
13
|
const execAsync = promisify(exec);
|
|
@@ -120,6 +121,18 @@ export async function initCommand(options = {}) {
|
|
|
120
121
|
}
|
|
121
122
|
// Step 4: Scan project structure (matching extension's Step 6)
|
|
122
123
|
console.log('[Init] Step 4/5: Scanning project structure...');
|
|
124
|
+
// Scaffold .shiftignore if it doesn't exist
|
|
125
|
+
if (scaffoldShiftIgnore(projectRoot)) {
|
|
126
|
+
console.log('[Init] ✓ Created default .shiftignore (edit it to customize ignored files)\n');
|
|
127
|
+
}
|
|
128
|
+
// Load .shiftignore (skip if --no-ignore)
|
|
129
|
+
const shiftIgnore = options.noIgnore ? null : loadShiftIgnore(projectRoot);
|
|
130
|
+
if (options.noIgnore) {
|
|
131
|
+
console.log('[Init] ⚠️ --no-ignore flag set — skipping .shiftignore rules\n');
|
|
132
|
+
}
|
|
133
|
+
else if (shiftIgnore) {
|
|
134
|
+
console.log('[Init] ✓ Applying .shiftignore rules\n');
|
|
135
|
+
}
|
|
123
136
|
// Get project tree (matching extension's getProjectTree)
|
|
124
137
|
const treeData = getProjectTree(projectRoot, {
|
|
125
138
|
depth: 0, // Unlimited depth
|
|
@@ -136,6 +149,7 @@ export async function initCommand(options = {}) {
|
|
|
136
149
|
'venv',
|
|
137
150
|
'env',
|
|
138
151
|
],
|
|
152
|
+
shiftIgnore,
|
|
139
153
|
});
|
|
140
154
|
console.log(`[Init] Files: ${treeData.file_count}`);
|
|
141
155
|
console.log(`[Init] Directories: ${treeData.dir_count}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readProjectConfig, setProject } from '../../utils/config.js';
|
|
2
2
|
import { startDaemon, getDaemonStatus } from '../../daemon/daemon-manager.js';
|
|
3
3
|
import { resolveApiKey, resolveProject } from '../../utils/auth-resolver.js';
|
|
4
|
+
import { scaffoldShiftIgnore } from '../../utils/shiftignore.js';
|
|
4
5
|
export async function startCommand(options = {}) {
|
|
5
6
|
const projectRoot = process.cwd();
|
|
6
7
|
const isAuthInteractive = !options.guest && !options.apiKey;
|
|
@@ -37,6 +38,10 @@ export async function startCommand(options = {}) {
|
|
|
37
38
|
console.error('❌ Failed to configure project');
|
|
38
39
|
process.exit(1);
|
|
39
40
|
}
|
|
41
|
+
// Scaffold .shiftignore if it doesn't exist
|
|
42
|
+
if (scaffoldShiftIgnore(projectRoot)) {
|
|
43
|
+
console.log('[Start] ✓ Created default .shiftignore (edit it to customize ignored files)\n');
|
|
44
|
+
}
|
|
40
45
|
// Step 3: Check if daemon is already running
|
|
41
46
|
console.log('[Start] Step 3/4: Checking daemon status...');
|
|
42
47
|
const status = getDaemonStatus(projectRoot);
|
|
@@ -4,6 +4,7 @@ import { exec } from 'child_process';
|
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { getApiKey, readProjectConfig } from '../../utils/config.js';
|
|
6
6
|
import { getProjectTree, extractAllFilePaths } from '../../utils/tree-scanner.js';
|
|
7
|
+
import { loadShiftIgnore, filterPaths } from '../../utils/shiftignore.js';
|
|
7
8
|
import { sendUpdateDrg } from '../../utils/api-client.js';
|
|
8
9
|
const execAsync = promisify(exec);
|
|
9
10
|
async function detectGitChanges(projectRoot, extensions) {
|
|
@@ -77,6 +78,13 @@ export async function updateDrgCommand(options = {}) {
|
|
|
77
78
|
console.log(`[DRG] ✓ Project: ${projectConfig.project_name} (${projectConfig.project_id})\n`);
|
|
78
79
|
// Step 3: Scan project for JS/TS files
|
|
79
80
|
console.log('[DRG] Step 3/4: Scanning project for JS/TS files...');
|
|
81
|
+
const shiftIgnore = options.noIgnore ? null : loadShiftIgnore(projectRoot);
|
|
82
|
+
if (options.noIgnore) {
|
|
83
|
+
console.log('[DRG] ⚠️ --no-ignore flag set — skipping .shiftignore rules\n');
|
|
84
|
+
}
|
|
85
|
+
else if (shiftIgnore) {
|
|
86
|
+
console.log('[DRG] ✓ Found .shiftignore — applying custom ignore rules\n');
|
|
87
|
+
}
|
|
80
88
|
const treeData = getProjectTree(projectRoot, {
|
|
81
89
|
depth: 0,
|
|
82
90
|
exclude_patterns: [
|
|
@@ -92,6 +100,7 @@ export async function updateDrgCommand(options = {}) {
|
|
|
92
100
|
'venv',
|
|
93
101
|
'env',
|
|
94
102
|
],
|
|
103
|
+
shiftIgnore,
|
|
95
104
|
});
|
|
96
105
|
const allFiles = extractAllFilePaths(treeData.tree);
|
|
97
106
|
const jstsFiles = allFiles.filter((filePath) => {
|
|
@@ -123,6 +132,10 @@ export async function updateDrgCommand(options = {}) {
|
|
|
123
132
|
// Always use git to compute added/modified/deleted (single source of truth)
|
|
124
133
|
console.log('[DRG] Detecting git changes...');
|
|
125
134
|
const gitChanges = await detectGitChanges(projectRoot, JS_TS_EXTENSIONS);
|
|
135
|
+
// Filter git changes through .shiftignore
|
|
136
|
+
gitChanges.added = filterPaths(shiftIgnore, gitChanges.added);
|
|
137
|
+
gitChanges.modified = filterPaths(shiftIgnore, gitChanges.modified);
|
|
138
|
+
gitChanges.deleted = filterPaths(shiftIgnore, gitChanges.deleted);
|
|
126
139
|
console.log(`[DRG] Git changes — added: ${gitChanges.added.length}, modified: ${gitChanges.modified.length}, deleted: ${gitChanges.deleted.length}`);
|
|
127
140
|
// Which files to send: incremental = only changed; baseline = all (full re-analysis)
|
|
128
141
|
const changedPaths = new Set([...gitChanges.added, ...gitChanges.modified]);
|
package/build/index.js
CHANGED
|
@@ -6,16 +6,25 @@ const { version } = require('../package.json');
|
|
|
6
6
|
const program = new Command();
|
|
7
7
|
program
|
|
8
8
|
.name('shift-cli')
|
|
9
|
-
.description('Shift CLI - AI-powered code intelligence'
|
|
9
|
+
.description('Shift CLI - AI-powered code intelligence and dependency analysis.\n\n' +
|
|
10
|
+
'Shift indexes your codebase, builds a dependency relationship graph (DRG),\n' +
|
|
11
|
+
'and provides AI-powered insights via MCP tools.\n\n' +
|
|
12
|
+
'Quick start:\n' +
|
|
13
|
+
' shift-cli start Start the daemon and configure project\n' +
|
|
14
|
+
' shift-cli init Scan and index the project\n' +
|
|
15
|
+
' shift-cli update-drg Build/update the dependency graph\n' +
|
|
16
|
+
' shift-cli status Check current status\n\n' +
|
|
17
|
+
'Configuration:\n' +
|
|
18
|
+
' Global config: ~/.shift/config.json\n' +
|
|
19
|
+
' Project config: .shift/config.json\n' +
|
|
20
|
+
' Ignore rules: .shiftignore (auto-created on first run)')
|
|
10
21
|
.version(version);
|
|
11
22
|
// MCP server mode (default when run via MCP host)
|
|
12
23
|
program
|
|
13
24
|
.command('mcp', { isDefault: true, hidden: true })
|
|
14
25
|
.description('Start MCP server on stdio')
|
|
15
26
|
.action(async () => {
|
|
16
|
-
// Check if 'mcp' was explicitly passed as argument
|
|
17
27
|
const mcpExplicitlyRequested = process.argv.includes('mcp');
|
|
18
|
-
// If running interactively (TTY) and mcp wasn't explicitly requested, show help
|
|
19
28
|
if (process.stdin.isTTY && !mcpExplicitlyRequested) {
|
|
20
29
|
program.outputHelp();
|
|
21
30
|
return;
|
|
@@ -23,15 +32,15 @@ program
|
|
|
23
32
|
const { startMcpServer } = await import('./mcp-server.js');
|
|
24
33
|
await startMcpServer();
|
|
25
34
|
});
|
|
26
|
-
//
|
|
27
|
-
program
|
|
35
|
+
// --- start ---
|
|
36
|
+
const startCmd = program
|
|
28
37
|
.command('start')
|
|
29
38
|
.description('Start the Shift daemon for this project')
|
|
30
|
-
.option('--guest', 'Use guest authentication (auto-creates project)')
|
|
31
|
-
.option('--api-key <key>', 'Provide API key directly')
|
|
32
|
-
.option('--project-name <name>', 'Create new project or match existing by name')
|
|
33
|
-
.option('--project-id <id>', '
|
|
34
|
-
.option('--template <id>', '
|
|
39
|
+
.option('--guest', 'Use guest authentication (auto-creates a temporary project)')
|
|
40
|
+
.option('--api-key <key>', 'Provide your Shift API key directly instead of interactive prompt')
|
|
41
|
+
.option('--project-name <name>', 'Create a new project or match an existing one by name')
|
|
42
|
+
.option('--project-id <id>', 'Link to an existing project by its UUID')
|
|
43
|
+
.option('--template <id>', 'Use a specific migration template when creating the project')
|
|
35
44
|
.action(async (options) => {
|
|
36
45
|
const { startCommand } = await import('./cli/commands/start.js');
|
|
37
46
|
await startCommand({
|
|
@@ -42,15 +51,28 @@ program
|
|
|
42
51
|
template: options.template,
|
|
43
52
|
});
|
|
44
53
|
});
|
|
45
|
-
|
|
54
|
+
startCmd.addHelpText('after', `
|
|
55
|
+
Details:
|
|
56
|
+
Resolves authentication, configures the project, and launches a
|
|
57
|
+
background daemon that maintains a WebSocket connection to the
|
|
58
|
+
Shift backend. Creates a default .shiftignore if one doesn't exist.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
shift-cli start Interactive setup
|
|
62
|
+
shift-cli start --guest Quick start without API key
|
|
63
|
+
shift-cli start --api-key <key> --project-name "My App"
|
|
64
|
+
`);
|
|
65
|
+
// --- init ---
|
|
66
|
+
const initCmd = program
|
|
46
67
|
.command('init')
|
|
47
68
|
.description('Initialize and scan the project for file indexing')
|
|
48
|
-
.option('-f, --force', 'Force re-indexing even if project is already indexed')
|
|
49
|
-
.option('--guest', 'Use guest authentication (auto-creates project)')
|
|
50
|
-
.option('--api-key <key>', 'Provide API key directly')
|
|
51
|
-
.option('--project-name <name>', 'Create new project or match existing by name')
|
|
52
|
-
.option('--project-id <id>', '
|
|
53
|
-
.option('--template <id>', '
|
|
69
|
+
.option('-f, --force', 'Force re-indexing even if the project is already indexed')
|
|
70
|
+
.option('--guest', 'Use guest authentication (auto-creates a temporary project)')
|
|
71
|
+
.option('--api-key <key>', 'Provide your Shift API key directly instead of interactive prompt')
|
|
72
|
+
.option('--project-name <name>', 'Create a new project or match an existing one by name')
|
|
73
|
+
.option('--project-id <id>', 'Link to an existing project by its UUID')
|
|
74
|
+
.option('--template <id>', 'Use a specific migration template when creating the project')
|
|
75
|
+
.option('--no-ignore', 'Skip .shiftignore rules for this run (send all files)')
|
|
54
76
|
.action(async (options) => {
|
|
55
77
|
const { initCommand } = await import('./cli/commands/init.js');
|
|
56
78
|
await initCommand({
|
|
@@ -60,35 +82,108 @@ program
|
|
|
60
82
|
projectName: options.projectName,
|
|
61
83
|
projectId: options.projectId,
|
|
62
84
|
template: options.template,
|
|
85
|
+
noIgnore: options.noIgnore,
|
|
63
86
|
});
|
|
64
87
|
});
|
|
65
|
-
|
|
88
|
+
initCmd.addHelpText('after', `
|
|
89
|
+
Details:
|
|
90
|
+
Performs a full project scan — collects the file tree, categorizes files
|
|
91
|
+
(source, config, assets), gathers git info, and sends everything to the
|
|
92
|
+
Shift backend for indexing. Automatically starts the daemon if not running.
|
|
93
|
+
Creates a default .shiftignore if one doesn't exist.
|
|
94
|
+
|
|
95
|
+
If the project is already indexed, you will be prompted to re-index.
|
|
96
|
+
Use --force to skip the prompt.
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
shift-cli init Interactive initialization
|
|
100
|
+
shift-cli init --force Force re-index without prompt
|
|
101
|
+
shift-cli init --no-ignore Index all files, ignore .shiftignore
|
|
102
|
+
shift-cli init --guest Quick init with guest auth
|
|
103
|
+
`);
|
|
104
|
+
// --- stop ---
|
|
105
|
+
const stopCmd = program
|
|
66
106
|
.command('stop')
|
|
67
|
-
.description('Stop the Shift daemon')
|
|
107
|
+
.description('Stop the Shift daemon for this project')
|
|
68
108
|
.action(async () => {
|
|
69
109
|
const { stopCommand } = await import('./cli/commands/stop.js');
|
|
70
110
|
await stopCommand();
|
|
71
111
|
});
|
|
72
|
-
|
|
112
|
+
stopCmd.addHelpText('after', `
|
|
113
|
+
Details:
|
|
114
|
+
Terminates the background daemon process. The daemon can be
|
|
115
|
+
restarted later with "shift-cli start".
|
|
116
|
+
`);
|
|
117
|
+
// --- status ---
|
|
118
|
+
const statusCmd = program
|
|
73
119
|
.command('status')
|
|
74
120
|
.description('Show the current Shift status')
|
|
75
121
|
.action(async () => {
|
|
76
122
|
const { statusCommand } = await import('./cli/commands/status.js');
|
|
77
123
|
await statusCommand();
|
|
78
124
|
});
|
|
79
|
-
|
|
125
|
+
statusCmd.addHelpText('after', `
|
|
126
|
+
Details:
|
|
127
|
+
Displays a comprehensive overview of:
|
|
128
|
+
- API key configuration (guest or authenticated)
|
|
129
|
+
- Project details (name, ID)
|
|
130
|
+
- Registered agents
|
|
131
|
+
- Backend indexing status and file count
|
|
132
|
+
- Daemon process status (PID, WebSocket connection, uptime)
|
|
133
|
+
`);
|
|
134
|
+
// --- update-drg ---
|
|
135
|
+
const updateDrgCmd = program
|
|
80
136
|
.command('update-drg')
|
|
81
|
-
.description('Update the dependency relationship graph (DRG)
|
|
82
|
-
.option('-m, --mode <mode>', 'Update mode: baseline (all files) or incremental (git-changed only)', 'incremental')
|
|
137
|
+
.description('Update the dependency relationship graph (DRG)')
|
|
138
|
+
.option('-m, --mode <mode>', 'Update mode: "baseline" (all files) or "incremental" (git-changed only)', 'incremental')
|
|
139
|
+
.option('--no-ignore', 'Skip .shiftignore rules for this run (send all files)')
|
|
83
140
|
.action(async (options) => {
|
|
84
141
|
const { updateDrgCommand } = await import('./cli/commands/update-drg.js');
|
|
85
|
-
await updateDrgCommand({ mode: options.mode });
|
|
142
|
+
await updateDrgCommand({ mode: options.mode, noIgnore: options.noIgnore });
|
|
86
143
|
});
|
|
87
|
-
|
|
144
|
+
updateDrgCmd.addHelpText('after', `
|
|
145
|
+
Details:
|
|
146
|
+
Scans JavaScript/TypeScript files (.js, .jsx, .ts, .tsx, .mjs, .cjs),
|
|
147
|
+
detects git changes, and sends file contents to the backend for
|
|
148
|
+
dependency analysis. Respects .shiftignore rules.
|
|
149
|
+
|
|
150
|
+
Modes:
|
|
151
|
+
incremental Send only git-changed files (default, faster)
|
|
152
|
+
baseline Send all JS/TS files (full re-analysis)
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
shift-cli update-drg Incremental update
|
|
156
|
+
shift-cli update-drg -m baseline Full re-analysis
|
|
157
|
+
shift-cli update-drg --no-ignore Include ignored files
|
|
158
|
+
`);
|
|
159
|
+
// --- config ---
|
|
160
|
+
const configCmd = program
|
|
88
161
|
.command('config [action] [key] [value]')
|
|
89
162
|
.description('Manage Shift configuration (URLs, API key)')
|
|
90
163
|
.action(async (action, key, value) => {
|
|
91
164
|
const { configCommand } = await import('./cli/commands/config.js');
|
|
92
165
|
await configCommand(action, key, value);
|
|
93
166
|
});
|
|
167
|
+
configCmd.addHelpText('after', `
|
|
168
|
+
Actions:
|
|
169
|
+
show Display current configuration (default)
|
|
170
|
+
set <key> <val> Set a configuration value
|
|
171
|
+
clear [key] Clear a specific key or all configuration
|
|
172
|
+
|
|
173
|
+
Configurable keys:
|
|
174
|
+
api-key Your Shift API key
|
|
175
|
+
api-url Backend API URL (default: http://localhost:9000)
|
|
176
|
+
orch-url Orchestrator URL (default: http://localhost:9999)
|
|
177
|
+
ws-url WebSocket URL (default: ws://localhost:9999)
|
|
178
|
+
|
|
179
|
+
URLs can also be set via environment variables:
|
|
180
|
+
SHIFT_API_URL, SHIFT_ORCH_URL, SHIFT_WS_URL
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
shift-cli config Show config
|
|
184
|
+
shift-cli config set api-key sk-abc123 Set API key
|
|
185
|
+
shift-cli config set api-url https://api.shift.ai Set API URL
|
|
186
|
+
shift-cli config clear api-key Clear API key
|
|
187
|
+
shift-cli config clear Clear all config
|
|
188
|
+
`);
|
|
94
189
|
program.parse();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import ignore from 'ignore';
|
|
4
|
+
const SHIFTIGNORE_FILE = '.shiftignore';
|
|
5
|
+
/**
|
|
6
|
+
* Load and parse a .shiftignore file from the project root.
|
|
7
|
+
* Returns an `ignore` instance that can test paths, or null if no .shiftignore exists.
|
|
8
|
+
*
|
|
9
|
+
* .shiftignore uses the same syntax as .gitignore:
|
|
10
|
+
* - Blank lines are ignored
|
|
11
|
+
* - Lines starting with # are comments
|
|
12
|
+
* - Standard glob patterns (*, **, ?)
|
|
13
|
+
* - Trailing / matches directories only
|
|
14
|
+
* - Leading / anchors to root
|
|
15
|
+
* - ! negates a pattern
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_SHIFTIGNORE = `# .shiftignore — files and directories to exclude from Shift indexing
|
|
18
|
+
# Uses the same syntax as .gitignore
|
|
19
|
+
|
|
20
|
+
# Dependencies
|
|
21
|
+
node_modules/
|
|
22
|
+
vendor/
|
|
23
|
+
bower_components/
|
|
24
|
+
|
|
25
|
+
# Build output
|
|
26
|
+
dist/
|
|
27
|
+
build/
|
|
28
|
+
out/
|
|
29
|
+
.next/
|
|
30
|
+
|
|
31
|
+
# Test & coverage
|
|
32
|
+
coverage/
|
|
33
|
+
.nyc_output/
|
|
34
|
+
|
|
35
|
+
# Environment & secrets
|
|
36
|
+
.env
|
|
37
|
+
.env.*
|
|
38
|
+
*.pem
|
|
39
|
+
*.key
|
|
40
|
+
|
|
41
|
+
# Logs
|
|
42
|
+
*.log
|
|
43
|
+
logs/
|
|
44
|
+
|
|
45
|
+
# OS files
|
|
46
|
+
.DS_Store
|
|
47
|
+
Thumbs.db
|
|
48
|
+
|
|
49
|
+
# IDE
|
|
50
|
+
.vscode/
|
|
51
|
+
.idea/
|
|
52
|
+
*.swp
|
|
53
|
+
*.swo
|
|
54
|
+
|
|
55
|
+
# Python
|
|
56
|
+
__pycache__/
|
|
57
|
+
*.pyc
|
|
58
|
+
venv/
|
|
59
|
+
.venv/
|
|
60
|
+
|
|
61
|
+
# Misc
|
|
62
|
+
*.min.js
|
|
63
|
+
*.min.css
|
|
64
|
+
*.map
|
|
65
|
+
`;
|
|
66
|
+
/**
|
|
67
|
+
* Create a default .shiftignore file in the project root if one doesn't exist.
|
|
68
|
+
* Returns true if a new file was created, false if it already exists.
|
|
69
|
+
*/
|
|
70
|
+
export function scaffoldShiftIgnore(projectRoot) {
|
|
71
|
+
const ignorePath = path.join(projectRoot, SHIFTIGNORE_FILE);
|
|
72
|
+
if (fs.existsSync(ignorePath)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
fs.writeFileSync(ignorePath, DEFAULT_SHIFTIGNORE, 'utf-8');
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.warn(`[ShiftIgnore] Warning: Could not create ${SHIFTIGNORE_FILE}: ${err.message}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function loadShiftIgnore(projectRoot) {
|
|
85
|
+
const ignorePath = path.join(projectRoot, SHIFTIGNORE_FILE);
|
|
86
|
+
if (!fs.existsSync(ignorePath)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(ignorePath, 'utf-8');
|
|
91
|
+
const ig = ignore();
|
|
92
|
+
ig.add(content);
|
|
93
|
+
return ig;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.warn(`[ShiftIgnore] Warning: Could not read ${SHIFTIGNORE_FILE}: ${err.message}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Filter an array of file paths through the shiftIgnore rules.
|
|
102
|
+
* Returns only paths that are NOT ignored.
|
|
103
|
+
*/
|
|
104
|
+
export function filterPaths(ig, paths) {
|
|
105
|
+
if (!ig)
|
|
106
|
+
return paths;
|
|
107
|
+
return paths.filter(p => !ig.ignores(p.replace(/\\/g, '/')));
|
|
108
|
+
}
|
|
@@ -21,7 +21,7 @@ const DEFAULT_EXCLUDE_PATTERNS = [
|
|
|
21
21
|
* Get project tree - matching extension's getProjectTree function
|
|
22
22
|
*/
|
|
23
23
|
export function getProjectTree(workspaceRoot, options = {}) {
|
|
24
|
-
const { depth = 0, exclude_patterns = DEFAULT_EXCLUDE_PATTERNS, } = options;
|
|
24
|
+
const { depth = 0, exclude_patterns = DEFAULT_EXCLUDE_PATTERNS, shiftIgnore = null, } = options;
|
|
25
25
|
let file_count = 0;
|
|
26
26
|
let dir_count = 0;
|
|
27
27
|
let total_size = 0;
|
|
@@ -34,12 +34,21 @@ export function getProjectTree(workspaceRoot, options = {}) {
|
|
|
34
34
|
try {
|
|
35
35
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
36
36
|
for (const entry of entries) {
|
|
37
|
-
// Check if should be excluded
|
|
37
|
+
// Check if should be excluded by built-in patterns
|
|
38
38
|
if (exclude_patterns.some(pattern => entry.name.includes(pattern))) {
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
41
|
const itemPath = path.join(dirPath, entry.name);
|
|
42
42
|
const itemRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
43
|
+
// Check if should be excluded by .shiftignore
|
|
44
|
+
if (shiftIgnore) {
|
|
45
|
+
// Use forward slashes for ignore matching; add trailing / for directories
|
|
46
|
+
const testPath = itemRelativePath.replace(/\\/g, '/');
|
|
47
|
+
const isDir = entry.isDirectory();
|
|
48
|
+
if (shiftIgnore.ignores(isDir ? testPath + '/' : testPath)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
43
52
|
if (entry.isDirectory()) {
|
|
44
53
|
dir_count++;
|
|
45
54
|
const children = scanDirectory(itemPath, currentDepth + 1, itemRelativePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latentforce/shift",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Shift CLI - AI-powered code intelligence with MCP support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
40
40
|
"commander": "^12.0.0",
|
|
41
|
+
"ignore": "^7.0.5",
|
|
41
42
|
"ws": "^8.14.0",
|
|
42
43
|
"zod": "^3.25.76"
|
|
43
44
|
},
|