@saiteja1123/mcp-server 1.1.4 → 1.1.6
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/package.json +59 -55
- package/src/api-scan.mjs +362 -93
- package/src/cli.js +771 -322
- package/src/deep-scan/contracts.js +201 -0
- package/src/deep-scan/deterministic-scan.js +337 -0
- package/src/deep-scan/index.js +109 -0
- package/src/deep-scan/project-map.js +507 -0
- package/src/deep-scan/ralph-accept.js +510 -0
- package/src/deep-scan/ralph-compare.js +498 -0
- package/src/deep-scan/ralph-tasks.js +598 -0
- package/src/deep-scan/ralph-track.js +548 -0
- package/src/deep-scan/registry.js +159 -0
- package/src/deep-scan/runtime.js +275 -0
- package/src/deep-scan/sample-steppers.js +128 -0
- package/src/deep-scan/sourceSafe.js +73 -0
- package/src/deep-scan/status.js +70 -0
- package/src/deep-scan/store.js +57 -0
- package/src/deep-scan/test-plan.js +760 -0
- package/src/index.js +6 -5
- package/src/lock.mjs +55 -14
- package/src/mcp-config.mjs +161 -0
- package/src/middleware/governance.js +135 -0
- package/src/orchestrator/runScan.js +211 -0
- package/src/project-bindings.mjs +215 -0
- package/src/rule-engine/index.js +2 -1
- package/src/rule-engine/localScan.js +39 -12
- package/src/rule-engine/metadata.js +20 -0
- package/src/rule-engine/prompt.js +6 -5
- package/src/rule-engine/rules.js +71 -43
- package/src/rule-engine/score.js +5 -4
- package/src/security/pathGuard.js +170 -0
- package/src/selftest.js +2473 -0
- package/src/server.js +109 -150
- package/src/tools/deepScan.js +286 -0
- package/src/tools/localScan.js +85 -0
- package/src/tools/projects.js +124 -0
- package/src/tools/scanFile.js +131 -0
package/src/cli.js
CHANGED
|
@@ -1,322 +1,771 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* vibesecur-mcp CLI
|
|
4
|
-
*
|
|
5
|
-
* Quick start: npx -y @saiteja1123/mcp-server init
|
|
6
|
-
*
|
|
7
|
-
* Commands:
|
|
8
|
-
* init [folder] bind folder + print IDE configs (optional --write=...)
|
|
9
|
-
* doctor [folder] check lock, env hints, backend reachability
|
|
10
|
-
* bind <folder>
|
|
11
|
-
* rebind <folder>
|
|
12
|
-
* status [folder]
|
|
13
|
-
* config [folder]
|
|
14
|
-
* start MCP stdio server
|
|
15
|
-
*
|
|
16
|
-
* Flags (init): --api-base=URL --skip-bind --write=cursor|vscode|windsurf|all
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import fs from 'fs/promises';
|
|
20
|
-
import path from 'path';
|
|
21
|
-
import
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vibesecur-mcp CLI
|
|
4
|
+
*
|
|
5
|
+
* Quick start: npx -y @saiteja1123/mcp-server init
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* init [folder] bind folder + print IDE configs (optional --write=...)
|
|
9
|
+
* doctor [folder] check lock, env hints, backend reachability
|
|
10
|
+
* bind <folder>
|
|
11
|
+
* rebind <folder>
|
|
12
|
+
* status [folder]
|
|
13
|
+
* config [folder]
|
|
14
|
+
* start MCP stdio server
|
|
15
|
+
*
|
|
16
|
+
* Flags (init): --api-base=URL --skip-bind --write=cursor|vscode|windsurf|all
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'fs/promises';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { createRequire } from 'module';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
import {
|
|
24
|
+
createLock,
|
|
25
|
+
rebindLock,
|
|
26
|
+
diagnosticLock,
|
|
27
|
+
readLock,
|
|
28
|
+
validateScanPath,
|
|
29
|
+
isRuntimeCompatibleLock,
|
|
30
|
+
} from './lock.mjs';
|
|
31
|
+
import { pingBackend, requestServerBind, verifyInstallBinding } from './api-scan.mjs';
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_API_BASE,
|
|
34
|
+
NPM_PACKAGE_NAME,
|
|
35
|
+
buildLegacyEnv,
|
|
36
|
+
buildUniversalEnv,
|
|
37
|
+
formatIdeConfigBlocks,
|
|
38
|
+
IDE_CONFIG_TARGETS,
|
|
39
|
+
isUniversalMode,
|
|
40
|
+
publishedServerCmd,
|
|
41
|
+
} from './mcp-config.mjs';
|
|
42
|
+
import {
|
|
43
|
+
DeepScanRuntime,
|
|
44
|
+
LocalDeepScanStore,
|
|
45
|
+
StepperRegistry,
|
|
46
|
+
buildDeepScanStatus,
|
|
47
|
+
createSampleStepperRegistry,
|
|
48
|
+
appendAcceptedRiskRecord,
|
|
49
|
+
} from './deep-scan/index.js';
|
|
50
|
+
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const mcpPkg = require('../package.json');
|
|
53
|
+
const NPM_PACKAGE = NPM_PACKAGE_NAME;
|
|
54
|
+
|
|
55
|
+
const serverPath = fileURLToPath(new URL('./server.js', import.meta.url));
|
|
56
|
+
|
|
57
|
+
const BOLD = '\x1b[1m';
|
|
58
|
+
const GREEN = '\x1b[32m';
|
|
59
|
+
const RED = '\x1b[31m';
|
|
60
|
+
const CYAN = '\x1b[36m';
|
|
61
|
+
const DIM = '\x1b[2m';
|
|
62
|
+
const RESET = '\x1b[0m';
|
|
63
|
+
|
|
64
|
+
function h(t) { return `${BOLD}${CYAN}${t}${RESET}`; }
|
|
65
|
+
function ok(t) { return `${GREEN}✓ ${t}${RESET}`; }
|
|
66
|
+
function err(t) { return `${RED}✗ ${t}${RESET}`; }
|
|
67
|
+
function dim(t) { return `${DIM}${t}${RESET}`; }
|
|
68
|
+
|
|
69
|
+
function parseArgv(argv) {
|
|
70
|
+
const flags = {};
|
|
71
|
+
const positional = [];
|
|
72
|
+
for (const a of argv) {
|
|
73
|
+
if (a.startsWith('--')) {
|
|
74
|
+
const eq = a.indexOf('=');
|
|
75
|
+
if (eq === -1) flags[a.slice(2)] = true;
|
|
76
|
+
else flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
77
|
+
} else {
|
|
78
|
+
positional.push(a);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { flags, positional };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { flags, positional } = parseArgv(process.argv.slice(2));
|
|
85
|
+
const command = positional[0];
|
|
86
|
+
const arg = positional[1];
|
|
87
|
+
const arg2 = positional[2];
|
|
88
|
+
|
|
89
|
+
function allowLocalFallback() {
|
|
90
|
+
return flags['allow-local-fallback'] === true || flags['allow-local-fallback'] === 'true';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function serverCmd() {
|
|
94
|
+
const normalizedPath = serverPath.replace(/\\/g, '/');
|
|
95
|
+
if (normalizedPath.includes('/node_modules/')) {
|
|
96
|
+
return publishedServerCmd();
|
|
97
|
+
}
|
|
98
|
+
return { command: 'node', args: [serverPath] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildEnv(folder, token, apiBase) {
|
|
102
|
+
return buildLegacyEnv(folder, token, apiBase);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printIdeBlocks(sc, env, options = {}) {
|
|
106
|
+
for (const block of formatIdeConfigBlocks(sc, env, options)) {
|
|
107
|
+
if (block.title === 'Mode') {
|
|
108
|
+
console.log(`${DIM}${block.body}${RESET}\n`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
console.log(`${BOLD}-- ${block.title} --${RESET}`);
|
|
112
|
+
console.log(block.body);
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function printLocalDiagnosticNotice(folder) {
|
|
118
|
+
console.log(err('Local diagnostic lock created, but it is not a runtime MCP binding.'));
|
|
119
|
+
console.log(
|
|
120
|
+
'Runtime startup still requires backend verification through /mcp/verify-install. ' +
|
|
121
|
+
'Run a server-issued bind before using this in an IDE:',
|
|
122
|
+
);
|
|
123
|
+
console.log(` VIBESECUR_AUTH_TOKEN=<jwt> vibesecur-mcp bind ${folder}`);
|
|
124
|
+
console.log();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function createServerIssuedLock({ folder, account, authToken, apiBase, action = 'bind' }) {
|
|
128
|
+
const remote = await requestServerBind({
|
|
129
|
+
lockedRootPath: folder,
|
|
130
|
+
authToken,
|
|
131
|
+
apiBase,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (remote.ok && remote.json?.success && remote.json?.data?.installToken) {
|
|
135
|
+
const data = remote.json.data;
|
|
136
|
+
const lock = await createLock({
|
|
137
|
+
rootPath: folder,
|
|
138
|
+
account,
|
|
139
|
+
installToken: data.installToken,
|
|
140
|
+
lockedRootHash: data.lockedRootHash,
|
|
141
|
+
source: 'server',
|
|
142
|
+
});
|
|
143
|
+
console.log(ok(`Server-issued ${action} created`));
|
|
144
|
+
return { ok: true, lock, remote };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!remote.skipped) {
|
|
148
|
+
console.log(err(`Server ${action} failed (HTTP ${remote.status}): ${remote.json?.error || 'unknown'}`));
|
|
149
|
+
console.log(dim('Tip: set VIBESECUR_AUTH_TOKEN or pass --auth-token=<jwt>'));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(err(`Server ${action} skipped: ${remote.error}`));
|
|
152
|
+
}
|
|
153
|
+
return { ok: false, remote };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function printIdeBlocksLegacy(sc, env) {
|
|
157
|
+
printIdeBlocks(sc, env, { universal: false });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function runUniversalSetup({ apiBase, authToken, folder, write }) {
|
|
161
|
+
const sc = serverCmd();
|
|
162
|
+
const env = buildUniversalEnv(apiBase, authToken);
|
|
163
|
+
|
|
164
|
+
console.log(ok('Universal account MCP config (works in Cursor, Windsurf, VS Code, Claude Desktop, Continue)'));
|
|
165
|
+
console.log(dim('No per-folder bind required. Use projectUpsert in the IDE before scanning a new codebase.\n'));
|
|
166
|
+
|
|
167
|
+
console.log(`${BOLD}Next:${RESET} paste ONE block below into your IDE MCP settings, then reload the IDE.\n`);
|
|
168
|
+
printIdeBlocks(sc, env, { universal: true });
|
|
169
|
+
|
|
170
|
+
if (write) {
|
|
171
|
+
const w = String(write).toLowerCase();
|
|
172
|
+
const allowed = ['cursor', 'vscode', 'windsurf', 'claude', 'all'];
|
|
173
|
+
if (!allowed.includes(w)) {
|
|
174
|
+
console.log(err(`Invalid --write=${write}. Use: ${allowed.join(' | ')}`));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
await writeIdeConfig(w, sc, env, folder);
|
|
178
|
+
console.log(dim('Reload your IDE so it picks up the new MCP config.'));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function mergeJsonFile(filePath, merger) {
|
|
183
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
184
|
+
let data = {};
|
|
185
|
+
try {
|
|
186
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
187
|
+
data = JSON.parse(raw);
|
|
188
|
+
} catch {
|
|
189
|
+
data = {};
|
|
190
|
+
}
|
|
191
|
+
const next = merger(data);
|
|
192
|
+
await fs.writeFile(filePath, JSON.stringify(next, null, 2), 'utf8');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function writeIdeConfig(which, sc, env, folder = process.cwd()) {
|
|
196
|
+
const writeOne = async (id) => {
|
|
197
|
+
const target = IDE_CONFIG_TARGETS[id];
|
|
198
|
+
if (!target) return false;
|
|
199
|
+
if (target.needsFolder && !folder) {
|
|
200
|
+
console.log(err(`Skipping ${id}: pass a project folder for .vscode/mcp.json`));
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const filePath = target.needsFolder ? target.path(folder) : target.path();
|
|
204
|
+
const built = target.build(sc, env);
|
|
205
|
+
const entry = built[target.mergeKey][target.entryKey];
|
|
206
|
+
await mergeJsonFile(filePath, (data) => {
|
|
207
|
+
const out = { ...data };
|
|
208
|
+
out[target.mergeKey] = { ...(out[target.mergeKey] || {}), [target.entryKey]: entry };
|
|
209
|
+
return out;
|
|
210
|
+
});
|
|
211
|
+
console.log(ok(`Wrote ${target.label}: ${filePath}`));
|
|
212
|
+
return true;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (which === 'all') {
|
|
216
|
+
await writeOne('cursor');
|
|
217
|
+
await writeOne('windsurf');
|
|
218
|
+
await writeOne('claude');
|
|
219
|
+
await writeOne('vscode');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
await writeOne(which);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function createDeepScanRuntime(folder) {
|
|
226
|
+
const registry = createSampleStepperRegistry(new StepperRegistry());
|
|
227
|
+
const store = new LocalDeepScanStore({ rootPath: folder });
|
|
228
|
+
return new DeepScanRuntime({ registry, store });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function localDeepScanProfile({
|
|
232
|
+
requireHumanApproval = false,
|
|
233
|
+
previousDeterministicScanRef = null,
|
|
234
|
+
previousFailureStateRef = null,
|
|
235
|
+
} = {}) {
|
|
236
|
+
return {
|
|
237
|
+
id: 'local-deep-scan-v1',
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
checkpoint: true,
|
|
240
|
+
ralphLoop: {
|
|
241
|
+
retry: {
|
|
242
|
+
maxAttempts: 3,
|
|
243
|
+
escalateAt: 2,
|
|
244
|
+
trackSeverities: ['critical', 'high'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
steppers: [
|
|
248
|
+
...(requireHumanApproval ? [{ id: 'sample.needsHuman', required: true }] : []),
|
|
249
|
+
{ id: 'project.map', required: true },
|
|
250
|
+
{ id: 'rules.scan', required: true },
|
|
251
|
+
{ id: 'tests.plan', required: true },
|
|
252
|
+
{ id: 'ralph.tasks', required: true },
|
|
253
|
+
{ id: 'ralph.compare', required: true, config: previousDeterministicScanRef ? { previousDeterministicScanRef } : {} },
|
|
254
|
+
{ id: 'ralph.accept', required: true },
|
|
255
|
+
{ id: 'ralph.track', required: true, config: previousFailureStateRef ? { previousFailureStateRef } : {} },
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function artifactRefFromRun(artifacts, { stepperId, type, fallbackId }) {
|
|
261
|
+
const ref = artifacts[stepperId]
|
|
262
|
+
|| Object.values(artifacts).find((item) => item?.type === type);
|
|
263
|
+
if (!ref?.uri || !ref?.hash) return null;
|
|
264
|
+
return {
|
|
265
|
+
id: ref.id || fallbackId,
|
|
266
|
+
type,
|
|
267
|
+
storage: ref.storage || 'local',
|
|
268
|
+
uri: ref.uri,
|
|
269
|
+
hash: ref.hash,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function resolvePreviousRunRefs(folder) {
|
|
274
|
+
const previousRunId = flags['previous-run-id'];
|
|
275
|
+
const previousScanUri = flags['previous-scan-uri'];
|
|
276
|
+
const previousScanHash = flags['previous-scan-hash'];
|
|
277
|
+
const previousScanId = flags['previous-scan-id'] || 'artifact-previous-deterministic-scan';
|
|
278
|
+
|
|
279
|
+
if (!previousRunId && !previousScanUri && !previousScanHash) {
|
|
280
|
+
return { previousDeterministicScanRef: null, previousFailureStateRef: null };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (previousRunId) {
|
|
284
|
+
const store = new LocalDeepScanStore({ rootPath: folder });
|
|
285
|
+
const previousRun = await store.getRun(previousRunId);
|
|
286
|
+
const artifacts = previousRun?.artifacts || {};
|
|
287
|
+
const previousDeterministicScanRef = artifactRefFromRun(artifacts, {
|
|
288
|
+
stepperId: 'rules.scan',
|
|
289
|
+
type: 'deterministic_scan',
|
|
290
|
+
fallbackId: `artifact-${previousRunId}-deterministic-scan`,
|
|
291
|
+
});
|
|
292
|
+
if (!previousDeterministicScanRef) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`Run ${previousRunId} does not contain a deterministic_scan artifact with uri/hash for ralph.compare baseline.`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const previousFailureStateRef = artifactRefFromRun(artifacts, {
|
|
298
|
+
stepperId: 'ralph.track',
|
|
299
|
+
type: 'ralph_failure_state',
|
|
300
|
+
fallbackId: `artifact-${previousRunId}-ralph-failure-state`,
|
|
301
|
+
});
|
|
302
|
+
return { previousDeterministicScanRef, previousFailureStateRef };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!previousScanUri || !previousScanHash) {
|
|
306
|
+
throw new Error('Both --previous-scan-uri and --previous-scan-hash are required when baseline run id is not provided.');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
previousDeterministicScanRef: {
|
|
311
|
+
id: previousScanId,
|
|
312
|
+
type: 'deterministic_scan',
|
|
313
|
+
storage: 'local',
|
|
314
|
+
uri: previousScanUri,
|
|
315
|
+
hash: previousScanHash,
|
|
316
|
+
},
|
|
317
|
+
previousFailureStateRef: null,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function requireDeepScanBoundary(folder) {
|
|
322
|
+
const lock = await readLock(folder);
|
|
323
|
+
if (flags.demo === true || flags.demo === 'true') {
|
|
324
|
+
console.log(dim('Deep Scan demo mode: explicit unbound local run; no server-issued MCP binding is required.'));
|
|
325
|
+
return { folder: path.resolve(folder), lock: null, demo: true };
|
|
326
|
+
}
|
|
327
|
+
if (!lock || !isRuntimeCompatibleLock(lock)) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
'Deep Scan runtime requires a server-issued MCP binding. ' +
|
|
330
|
+
`Run: vibesecur-mcp bind ${folder} (or pass --demo=true for explicit unbound local demo mode).`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const local = await validateScanPath(folder, lock.installToken);
|
|
334
|
+
if (!local.ok) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`${local.message} Deep Scan runtime requires a verified server-issued MCP binding. ` +
|
|
337
|
+
`Run: ${local.rebindHint || `vibesecur-mcp bind ${folder}`}.`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (!isRuntimeCompatibleLock(local.lock)) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
'Deep Scan runtime requires a server-issued MCP binding. ' +
|
|
343
|
+
`Run: vibesecur-mcp bind ${local.boundRoot || folder}.`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
const lockedRootHash = local.lockedRootHash || local.lock?.lockedRootHash || local.lock?.rootHash;
|
|
347
|
+
const apiBase = flags['api-base'] || process.env.VIBESECUR_API_BASE || process.env.VIBESECUR_API_URL || DEFAULT_API_BASE;
|
|
348
|
+
const verify = await verifyInstallBinding({
|
|
349
|
+
installToken: lock.installToken,
|
|
350
|
+
lockedRootHash,
|
|
351
|
+
apiBase,
|
|
352
|
+
});
|
|
353
|
+
if (!verify.ok || !verify.json?.success) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
'Deep Scan runtime requires a verified server-issued MCP binding. ' +
|
|
356
|
+
`${verify.json?.error || verify.error || 'Unable to verify MCP install binding.'} ` +
|
|
357
|
+
`Run: vibesecur-mcp rebind ${local.boundRoot || folder}.`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return { folder: local.boundRoot || path.resolve(folder), lock: local.lock, lockedRootHash };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function cmdDeepScanStart() {
|
|
364
|
+
const folder = path.resolve(arg || process.cwd());
|
|
365
|
+
const boundary = await requireDeepScanBoundary(folder);
|
|
366
|
+
const runtime = createDeepScanRuntime(boundary.folder);
|
|
367
|
+
const { previousDeterministicScanRef, previousFailureStateRef } = await resolvePreviousRunRefs(boundary.folder);
|
|
368
|
+
const run = await runtime.createRun({
|
|
369
|
+
projectId: flags['project-id'] || 'local-project',
|
|
370
|
+
projectHash: flags['project-hash'] || null,
|
|
371
|
+
graphProfile: localDeepScanProfile({
|
|
372
|
+
requireHumanApproval: flags['human-approval'] === true || flags['human-approval'] === 'true',
|
|
373
|
+
previousDeterministicScanRef,
|
|
374
|
+
previousFailureStateRef,
|
|
375
|
+
}),
|
|
376
|
+
metadata: {
|
|
377
|
+
runtimeBoundary: 'mcp-local',
|
|
378
|
+
artifactStorage: 'refs-only',
|
|
379
|
+
trustModel: 'No raw source is stored in Deep Scan checkpoints.',
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const state = await runtime.startRun(run.runId);
|
|
383
|
+
console.log(JSON.stringify(buildDeepScanStatus(state), null, 2));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function cmdDeepScanStatus() {
|
|
387
|
+
const runId = arg;
|
|
388
|
+
if (!runId) throw new Error('Usage: vibesecur-mcp deep-scan-status <runId> [folder]');
|
|
389
|
+
const folder = path.resolve(arg2 || process.cwd());
|
|
390
|
+
const boundary = await requireDeepScanBoundary(folder);
|
|
391
|
+
const store = new LocalDeepScanStore({ rootPath: boundary.folder });
|
|
392
|
+
console.log(JSON.stringify(buildDeepScanStatus(await store.getRun(runId)), null, 2));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function cmdDeepScanApprove() {
|
|
396
|
+
const runId = arg;
|
|
397
|
+
if (!runId) throw new Error('Usage: vibesecur-mcp deep-scan-approve <runId> [folder] --requirement-id=<id> --approved-by=<who> --reason=<why>');
|
|
398
|
+
const folder = path.resolve(arg2 || process.cwd());
|
|
399
|
+
const boundary = await requireDeepScanBoundary(folder);
|
|
400
|
+
const runtime = createDeepScanRuntime(boundary.folder);
|
|
401
|
+
const approval = await runtime.recordApproval({
|
|
402
|
+
runId,
|
|
403
|
+
requirementId: flags['requirement-id'] || 'sample.needsHuman',
|
|
404
|
+
decision: flags.decision || 'approved',
|
|
405
|
+
approvedBy: flags['approved-by'] || process.env.USER || process.env.USERNAME || 'local-user',
|
|
406
|
+
reason: flags.reason || 'Approved from local CLI.',
|
|
407
|
+
relatedFindingId: flags['related-finding-id'],
|
|
408
|
+
relatedArtifactId: flags['related-artifact-id'],
|
|
409
|
+
metadata: { source: 'cli' },
|
|
410
|
+
});
|
|
411
|
+
console.log(JSON.stringify({ approval, nextAction: 'Run vibesecur-mcp deep-scan-resume.' }, null, 2));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function cmdDeepScanResume() {
|
|
415
|
+
const runId = arg;
|
|
416
|
+
if (!runId) throw new Error('Usage: vibesecur-mcp deep-scan-resume <runId> [folder]');
|
|
417
|
+
const folder = path.resolve(arg2 || process.cwd());
|
|
418
|
+
const boundary = await requireDeepScanBoundary(folder);
|
|
419
|
+
const runtime = createDeepScanRuntime(boundary.folder);
|
|
420
|
+
console.log(JSON.stringify(buildDeepScanStatus(await runtime.resumeRun(runId)), null, 2));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function cmdDeepScanAcceptRisk() {
|
|
424
|
+
const folder = path.resolve(arg || process.cwd());
|
|
425
|
+
const boundary = await requireDeepScanBoundary(folder);
|
|
426
|
+
if (!flags.severity) {
|
|
427
|
+
throw new Error('Usage: vibesecur-mcp deep-scan-accept-risk [folder] --severity=<critical|high|medium|low> --finding-key=<sha256|--finding-id=<id> [--reason=<why> --reviewer=<who> --expires-at=<iso>]');
|
|
428
|
+
}
|
|
429
|
+
if (!flags['finding-key'] && !flags['finding-id']) {
|
|
430
|
+
throw new Error('deep-scan-accept-risk requires --finding-key=<sha256> or --finding-id=<id> to attach the accepted risk to a tracked finding.');
|
|
431
|
+
}
|
|
432
|
+
const result = await appendAcceptedRiskRecord({
|
|
433
|
+
rootPath: boundary.folder,
|
|
434
|
+
record: {
|
|
435
|
+
riskId: flags['risk-id'],
|
|
436
|
+
findingKey: flags['finding-key'],
|
|
437
|
+
findingId: flags['finding-id'],
|
|
438
|
+
taskId: flags['task-id'],
|
|
439
|
+
severity: flags.severity,
|
|
440
|
+
reason: flags.reason,
|
|
441
|
+
reviewer: flags.reviewer || process.env.USER || process.env.USERNAME || undefined,
|
|
442
|
+
acceptedAt: flags['accepted-at'],
|
|
443
|
+
expiresAt: flags['expires-at'],
|
|
444
|
+
reviewBy: flags['review-by'],
|
|
445
|
+
reportVisibility: flags['report-visibility'] || 'visible',
|
|
446
|
+
metadata: { source: 'cli' },
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
console.log(JSON.stringify({
|
|
450
|
+
record: result.record,
|
|
451
|
+
registerRef: { uri: result.uri, hash: result.hash },
|
|
452
|
+
activeRecordCount: result.register.records.length,
|
|
453
|
+
nextAction: 'Rerun vibesecur-mcp deep-scan-start so ralph.compare and ralph.accept apply this accepted risk.',
|
|
454
|
+
}, null, 2));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function cmdBind() {
|
|
458
|
+
const folder = path.resolve(arg || process.cwd());
|
|
459
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
460
|
+
const authToken = flags['auth-token'] || process.env.VIBESECUR_AUTH_TOKEN || '';
|
|
461
|
+
const account = process.env.VIBESECUR_ACCOUNT || 'anonymous';
|
|
462
|
+
const canFallbackLocal = allowLocalFallback();
|
|
463
|
+
|
|
464
|
+
console.log(`\n${h('Vibesecur MCP - Bind Install')}`);
|
|
465
|
+
console.log(`Folder : ${folder}`);
|
|
466
|
+
console.log(`API : ${apiBase}`);
|
|
467
|
+
console.log(`Account: ${account}\n`);
|
|
468
|
+
|
|
469
|
+
let lock;
|
|
470
|
+
const serverBind = await createServerIssuedLock({ folder, account, authToken, apiBase, action: 'bind' });
|
|
471
|
+
if (serverBind.ok) {
|
|
472
|
+
lock = serverBind.lock;
|
|
473
|
+
} else {
|
|
474
|
+
if (!canFallbackLocal) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
'Server-issued bind is required. Pass --allow-local-fallback=true only to create a diagnostic local lock.',
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
console.log(dim('Creating local diagnostic lock. It will not pass runtime server verification.\n'));
|
|
480
|
+
lock = await createLock({ rootPath: folder, account, source: 'local-diagnostic' });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log(ok(`Lock created: ${folder}/.vibesecur/lock.json`));
|
|
484
|
+
console.log(`Source: ${lock.source}`);
|
|
485
|
+
if (!isRuntimeCompatibleLock(lock)) {
|
|
486
|
+
printLocalDiagnosticNotice(folder);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
console.log(`\n${BOLD}Install token (keep private):${RESET}`);
|
|
490
|
+
console.log(` ${lock.installToken}\n`);
|
|
491
|
+
console.log('Add to MCP config env:');
|
|
492
|
+
console.log(` VIBESECUR_INSTALL_TOKEN=${lock.installToken}`);
|
|
493
|
+
console.log(` VIBESECUR_BOUND_ROOT=${folder}`);
|
|
494
|
+
console.log(` VIBESECUR_API_BASE=${apiBase}`);
|
|
495
|
+
console.log(`\n${dim(`Run: vibesecur-mcp config ${folder} for IDE snippets`)}\n`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function cmdRebind() {
|
|
499
|
+
const folder = path.resolve(arg || process.cwd());
|
|
500
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
501
|
+
const authToken = flags['auth-token'] || process.env.VIBESECUR_AUTH_TOKEN || '';
|
|
502
|
+
const account = process.env.VIBESECUR_ACCOUNT || 'anonymous';
|
|
503
|
+
const canFallbackLocal = allowLocalFallback();
|
|
504
|
+
|
|
505
|
+
console.log(`\n${h('Vibesecur MCP - Rebind Install')}`);
|
|
506
|
+
console.log(`Folder : ${folder}`);
|
|
507
|
+
console.log(`API : ${apiBase}\n`);
|
|
508
|
+
|
|
509
|
+
const oldLock = await readLock(folder);
|
|
510
|
+
|
|
511
|
+
let result;
|
|
512
|
+
const serverBind = await createServerIssuedLock({ folder, account, authToken, apiBase, action: 'rebind' });
|
|
513
|
+
if (serverBind.ok) {
|
|
514
|
+
const newLock = serverBind.lock;
|
|
515
|
+
result = {
|
|
516
|
+
previousToken: oldLock?.installToken || null,
|
|
517
|
+
newToken: newLock.installToken,
|
|
518
|
+
boundRoot: newLock.boundRoot,
|
|
519
|
+
source: newLock.source,
|
|
520
|
+
};
|
|
521
|
+
} else {
|
|
522
|
+
if (!canFallbackLocal) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
'Server-issued rebind is required. Pass --allow-local-fallback=true only to create a diagnostic local lock.',
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
console.log(dim('Falling back to local diagnostic rebind. It will not pass runtime server verification.\n'));
|
|
528
|
+
result = await rebindLock({ rootPath: folder, account, source: 'local-diagnostic' });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
console.log(ok('Rebind complete. Previous token is now invalid.'));
|
|
532
|
+
console.log(`Previous: ${dim(result.previousToken || 'none')}`);
|
|
533
|
+
if (result.source !== 'server') {
|
|
534
|
+
printLocalDiagnosticNotice(result.boundRoot);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
console.log(`\n${BOLD}New install token:${RESET}`);
|
|
538
|
+
console.log(` ${result.newToken}\n`);
|
|
539
|
+
console.log('Update MCP config env:');
|
|
540
|
+
console.log(` VIBESECUR_INSTALL_TOKEN=${result.newToken}`);
|
|
541
|
+
console.log(` VIBESECUR_BOUND_ROOT=${result.boundRoot}`);
|
|
542
|
+
console.log(` VIBESECUR_API_BASE=${apiBase}\n`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function cmdStatus() {
|
|
546
|
+
const folder = path.resolve(arg || process.cwd());
|
|
547
|
+
console.log(`\n${h('Vibesecur MCP - Lock Diagnostic')}`);
|
|
548
|
+
const diag = await diagnosticLock(folder);
|
|
549
|
+
console.log(diag.healthy ? ok('Lock healthy') : err('Lock has issues'));
|
|
550
|
+
console.log(`Bound root : ${diag.boundRoot || 'unknown'}`);
|
|
551
|
+
console.log(`Account : ${diag.account || 'unknown'}`);
|
|
552
|
+
console.log(`Created : ${diag.createdAt || 'unknown'}`);
|
|
553
|
+
console.log(`Source : ${diag.source || 'unknown'}`);
|
|
554
|
+
console.log(`Runtime OK : ${diag.runtimeCompatible ? `${GREEN}yes${RESET}` : `${RED}NO${RESET}`}`);
|
|
555
|
+
console.log(`RootHash OK : ${diag.rootHash?.ok ? `${GREEN}yes${RESET}` : `${RED}NO${RESET}`}`);
|
|
556
|
+
console.log(`In scope : ${diag.pathInBoundFolder ? `${GREEN}yes${RESET}` : `${RED}NO${RESET}`}`);
|
|
557
|
+
if (diag.issues?.length) {
|
|
558
|
+
console.log(`\n${RED}Issues:${RESET}`);
|
|
559
|
+
diag.issues.forEach((i) => console.log(` - ${i}`));
|
|
560
|
+
console.log(`\nFix: vibesecur-mcp rebind ${diag.boundRoot || folder}`);
|
|
561
|
+
}
|
|
562
|
+
console.log();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function cmdConfig() {
|
|
566
|
+
const folder = path.resolve(arg || process.cwd());
|
|
567
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
568
|
+
const authToken = flags['auth-token'] || process.env.VIBESECUR_AUTH_TOKEN || '';
|
|
569
|
+
|
|
570
|
+
console.log(`\n${h('Vibesecur MCP - IDE Config Snippets')}`);
|
|
571
|
+
|
|
572
|
+
if (isUniversalMode(flags, authToken)) {
|
|
573
|
+
if (!authToken) {
|
|
574
|
+
console.log(err('Universal config requires VIBESECUR_AUTH_TOKEN or --auth-token=<jwt>'));
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
console.log(`Mode: ${BOLD}universal (account-wide)${RESET}`);
|
|
578
|
+
console.log(`API: ${apiBase}\n`);
|
|
579
|
+
const sc = serverCmd();
|
|
580
|
+
printIdeBlocks(sc, buildUniversalEnv(apiBase, authToken), { universal: true });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
console.log(`Mode: ${BOLD}legacy (single folder)${RESET}`);
|
|
585
|
+
console.log(`Folder: ${folder}`);
|
|
586
|
+
const lock = await readLock(folder);
|
|
587
|
+
const token = lock?.installToken || 'YOUR_INSTALL_TOKEN';
|
|
588
|
+
if (!lock) console.log(err(`No lock found. Run: vibesecur-mcp init ${folder} --mode=legacy`));
|
|
589
|
+
else if (!isRuntimeCompatibleLock(lock)) {
|
|
590
|
+
console.log(err('Existing lock is local diagnostic-only and will not start the MCP runtime.'));
|
|
591
|
+
console.log(`Run: VIBESECUR_AUTH_TOKEN=<jwt> vibesecur-mcp init --mode=universal`);
|
|
592
|
+
console.log();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
console.log();
|
|
596
|
+
|
|
597
|
+
const sc = serverCmd();
|
|
598
|
+
printIdeBlocks(sc, buildEnv(folder, token, apiBase), { universal: false });
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function cmdInit() {
|
|
602
|
+
const folder = path.resolve(arg || process.cwd());
|
|
603
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
604
|
+
const account = process.env.VIBESECUR_ACCOUNT || 'anonymous';
|
|
605
|
+
const skipBind = flags['skip-bind'] === true || flags['skip-bind'] === 'true';
|
|
606
|
+
const canFallbackLocal = allowLocalFallback();
|
|
607
|
+
const authToken = flags['auth-token'] || process.env.VIBESECUR_AUTH_TOKEN || '';
|
|
608
|
+
const write = flags.write;
|
|
609
|
+
|
|
610
|
+
console.log(`\n${h('Vibesecur MCP - Init')}`);
|
|
611
|
+
console.log(`Project folder: ${folder}`);
|
|
612
|
+
console.log(`API base: ${apiBase}`);
|
|
613
|
+
console.log(`Package: ${NPM_PACKAGE_NAME}@${mcpPkg.version}\n`);
|
|
614
|
+
|
|
615
|
+
if (isUniversalMode(flags, authToken)) {
|
|
616
|
+
if (!authToken) {
|
|
617
|
+
console.log(err('Universal init requires VIBESECUR_AUTH_TOKEN or --auth-token=<jwt> from the dashboard login.'));
|
|
618
|
+
console.log(dim('Example: npx -y @saiteja1123/mcp-server init --auth-token=<jwt> --write=all'));
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
await runUniversalSetup({ apiBase, authToken, folder, write });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
let lock = await readLock(folder);
|
|
626
|
+
if (lock && !isRuntimeCompatibleLock(lock) && !skipBind) {
|
|
627
|
+
console.log(err(`Existing lock source "${lock.source || 'unknown'}" is diagnostic-only; requesting server-issued binding.`));
|
|
628
|
+
lock = null;
|
|
629
|
+
}
|
|
630
|
+
if (!lock) {
|
|
631
|
+
if (skipBind) {
|
|
632
|
+
console.log(err('No lock in this folder. Run without --skip-bind, or: vibesecur-mcp bind <folder>'));
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const serverBind = await createServerIssuedLock({ folder, account, authToken, apiBase, action: 'bind' });
|
|
637
|
+
if (serverBind.ok) {
|
|
638
|
+
lock = serverBind.lock;
|
|
639
|
+
} else {
|
|
640
|
+
if (!canFallbackLocal) {
|
|
641
|
+
throw new Error(
|
|
642
|
+
'Server-issued bind is required. Pass --allow-local-fallback=true only to create a diagnostic local lock.',
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
console.log(dim('Creating local diagnostic lock. It will not pass runtime server verification.'));
|
|
646
|
+
lock = await createLock({ rootPath: folder, account, source: 'local-diagnostic' });
|
|
647
|
+
}
|
|
648
|
+
console.log(ok(`Created lock: ${path.join(folder, '.vibesecur', 'lock.json')}`));
|
|
649
|
+
} else {
|
|
650
|
+
console.log(ok('Existing lock found (reusing token). Use rebind to rotate.'));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (!isRuntimeCompatibleLock(lock)) {
|
|
654
|
+
printLocalDiagnosticNotice(folder);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const env = buildEnv(folder, lock.installToken, apiBase);
|
|
659
|
+
const sc = serverCmd();
|
|
660
|
+
|
|
661
|
+
console.log(`\n${BOLD}Next:${RESET} paste ONE block below into your IDE MCP settings, then reload the IDE.\n`);
|
|
662
|
+
printIdeBlocks(sc, env, { universal: false });
|
|
663
|
+
|
|
664
|
+
if (write) {
|
|
665
|
+
const w = String(write).toLowerCase();
|
|
666
|
+
if (!['cursor', 'vscode', 'windsurf', 'claude', 'all'].includes(w)) {
|
|
667
|
+
console.log(err(`Invalid --write=${write}. Use: cursor | vscode | windsurf | claude | all`));
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
await writeIdeConfig(w, sc, env, folder);
|
|
671
|
+
console.log(dim('Reload your IDE so it picks up the new MCP config.'));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async function cmdDoctor() {
|
|
676
|
+
const folder = path.resolve(arg || process.cwd());
|
|
677
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
678
|
+
const authToken = flags['auth-token'] || process.env.VIBESECUR_AUTH_TOKEN || '';
|
|
679
|
+
const universal = isUniversalMode(flags, authToken);
|
|
680
|
+
|
|
681
|
+
console.log(`\n${h('Vibesecur MCP - Doctor')}`);
|
|
682
|
+
console.log(`Folder: ${folder}`);
|
|
683
|
+
console.log(`API base: ${apiBase}`);
|
|
684
|
+
console.log(`Mode: ${universal ? 'universal (account-wide)' : 'legacy (single folder)'}\n`);
|
|
685
|
+
|
|
686
|
+
if (universal) {
|
|
687
|
+
if (authToken) {
|
|
688
|
+
console.log(ok('VIBESECUR_AUTH_TOKEN present (universal mode ready)'));
|
|
689
|
+
} else {
|
|
690
|
+
console.log(err('VIBESECUR_AUTH_TOKEN missing — set it in IDE MCP env or pass --auth-token=<jwt>'));
|
|
691
|
+
}
|
|
692
|
+
console.log(dim('Per-project locks are optional in universal mode. Use projectUpsert before scanning a codebase.\n'));
|
|
693
|
+
} else {
|
|
694
|
+
const lock = await readLock(folder);
|
|
695
|
+
if (!lock) {
|
|
696
|
+
console.log(err('No lock file. Run: vibesecur-mcp init --auth-token=<jwt> for universal mode'));
|
|
697
|
+
} else {
|
|
698
|
+
console.log(ok('Lock file present'));
|
|
699
|
+
const diag = await diagnosticLock(folder);
|
|
700
|
+
console.log(diag.healthy ? ok('Lock diagnostic: healthy') : err('Lock diagnostic: issues'));
|
|
701
|
+
console.log(diag.runtimeCompatible ? ok('Runtime binding: server-issued') : err('Runtime binding: not server-issued'));
|
|
702
|
+
if (diag.issues?.length) diag.issues.forEach((i) => console.log(` - ${i}`));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const tokenEnv = process.env.VIBESECUR_INSTALL_TOKEN;
|
|
707
|
+
const rootEnv = process.env.VIBESECUR_BOUND_ROOT;
|
|
708
|
+
const authEnv = process.env.VIBESECUR_AUTH_TOKEN;
|
|
709
|
+
console.log(`\n${BOLD}Shell env (IDE injects these when MCP runs):${RESET}`);
|
|
710
|
+
console.log(` VIBESECUR_AUTH_TOKEN: ${authEnv ? dim('set') : dim('not set in this shell')}`);
|
|
711
|
+
console.log(` VIBESECUR_INSTALL_TOKEN: ${tokenEnv ? dim('set') : dim('not set (OK in universal mode)')}`);
|
|
712
|
+
console.log(` VIBESECUR_BOUND_ROOT: ${rootEnv ? dim('set') : dim('not set (OK in universal mode)')}`);
|
|
713
|
+
console.log(` VIBESECUR_API_BASE: ${process.env.VIBESECUR_API_BASE ? dim(process.env.VIBESECUR_API_BASE) : dim(`${DEFAULT_API_BASE} (default)`)}`);
|
|
714
|
+
|
|
715
|
+
const ping = await pingBackend(apiBase);
|
|
716
|
+
if (ping.skipped) {
|
|
717
|
+
console.log(`\n${err('Backend ping skipped')}`);
|
|
718
|
+
} else if (ping.ok) {
|
|
719
|
+
const note = ping.status >= 400 ? ' (server responded; check path if unexpected)' : '';
|
|
720
|
+
console.log(`\n${ok(`Backend responded HTTP ${ping.status}${note}`)}`);
|
|
721
|
+
console.log(dim(` ${ping.url || ''}`));
|
|
722
|
+
} else {
|
|
723
|
+
console.log(`\n${err('Backend not reachable')}`);
|
|
724
|
+
if (ping.status !== undefined) console.log(` HTTP ${ping.status}`);
|
|
725
|
+
if (ping.error) console.log(` ${ping.error}`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
console.log(`\n${BOLD}CLI:${RESET} ${NPM_PACKAGE_NAME}@${mcpPkg.version}`);
|
|
729
|
+
console.log(dim('Universal setup: npx -y @saiteja1123/mcp-server init --auth-token=<jwt> --write=all --api-base=URL'));
|
|
730
|
+
console.log(dim('If IDE still fails: reload IDE after pasting config, then run projectUpsert for your codebase.\n'));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function usage() {
|
|
734
|
+
console.log(`\n${h('Vibesecur MCP')}`);
|
|
735
|
+
console.log(` ${dim('Quick start (universal):')} VIBESECUR_AUTH_TOKEN=<jwt> npx -y ${NPM_PACKAGE_NAME} init --write=all`);
|
|
736
|
+
console.log(' init [folder] print IDE configs [--auth-token=JWT] [--api-base=URL] [--mode=universal|legacy] [--write=cursor|vscode|windsurf|claude|all]');
|
|
737
|
+
console.log(' doctor [folder] check env + backend [--auth-token=JWT] [--api-base=URL] [--mode=universal]');
|
|
738
|
+
console.log(' bind <folder> legacy single-folder bind [--auth-token=JWT]');
|
|
739
|
+
console.log(' rebind <folder> rotate legacy folder token');
|
|
740
|
+
console.log(' status [folder] lock health (legacy mode)');
|
|
741
|
+
console.log(' config [folder] print IDE snippets [--auth-token=JWT] [--mode=universal]');
|
|
742
|
+
console.log(' deep-scan-start [folder] create and run local Deep Scan Project Map, deterministic scan, metadata-only test plan, Ralph tasks, and rerun comparison [--demo=true] [--human-approval=true] [--previous-run-id=<runId>] [--previous-scan-uri=<uri> --previous-scan-hash=<sha256>]');
|
|
743
|
+
console.log(' deep-scan-status <runId> [folder] inspect local Deep Scan status');
|
|
744
|
+
console.log(' deep-scan-approve <runId> [folder] record approval metadata');
|
|
745
|
+
console.log(' deep-scan-resume <runId> [folder] resume a checkpointed local Deep Scan run');
|
|
746
|
+
console.log(' deep-scan-accept-risk [folder] record accepted risk --severity=<level> (--finding-key=<sha256>|--finding-id=<id>) [--reason= --reviewer= --expires-at=<iso>]');
|
|
747
|
+
console.log(' start MCP server (stdio)\n');
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
try {
|
|
752
|
+
switch (command) {
|
|
753
|
+
case 'init': await cmdInit(); break;
|
|
754
|
+
case 'doctor': await cmdDoctor(); break;
|
|
755
|
+
case 'bind': await cmdBind(); break;
|
|
756
|
+
case 'rebind': await cmdRebind(); break;
|
|
757
|
+
case 'status': await cmdStatus(); break;
|
|
758
|
+
case 'config': await cmdConfig(); break;
|
|
759
|
+
case 'deep-scan-start': await cmdDeepScanStart(); break;
|
|
760
|
+
case 'deep-scan-status': await cmdDeepScanStatus(); break;
|
|
761
|
+
case 'deep-scan-approve': await cmdDeepScanApprove(); break;
|
|
762
|
+
case 'deep-scan-resume': await cmdDeepScanResume(); break;
|
|
763
|
+
case 'deep-scan-accept-risk': await cmdDeepScanAcceptRisk(); break;
|
|
764
|
+
case 'start':
|
|
765
|
+
case undefined: await import('./server.js'); break;
|
|
766
|
+
default: usage();
|
|
767
|
+
}
|
|
768
|
+
} catch (e) {
|
|
769
|
+
console.error(`\x1b[31m✗ ${e.message}\x1b[0m`);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|