@securityreviewai/securityreview-kit 0.1.11

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 ADDED
@@ -0,0 +1,90 @@
1
+ # securityreview-kit
2
+
3
+ > Bootstrap [security-review-mcp](https://www.npmjs.com/package/security-review-mcp) for AI IDEs and CLI tools in one command.
4
+
5
+ **securityreview-kit** configures the SRAI security review MCP server and installs workspace rules so your AI assistant consults security threat models and countermeasures *before* generating code.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Interactive mode (recommended)
11
+ npx securityreview-kit init
12
+
13
+ # Or specify targets directly
14
+ npx securityreview-kit init --target cursor --api-url https://api.example.com --api-key YOUR_TOKEN
15
+
16
+ # Install for multiple targets
17
+ npx securityreview-kit init --target cursor claude vscode
18
+
19
+ # Install for all supported targets
20
+ npx securityreview-kit init --all --api-url https://api.example.com --api-key YOUR_TOKEN
21
+
22
+ # Re-open project selection menu and update installed rules
23
+ npx securityreview-kit init --switch-project
24
+ ```
25
+
26
+ ## Supported Targets
27
+
28
+ | Target | Flag | MCP Config | Workspace Rule |
29
+ |---|---|---|---|
30
+ | Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/ctm_sync_rule.mdc`, `.cursor/commands/ctm_sync.md`, `.cursor/agents/ctm_sync.md`, `.cursor/commands/create-ide-workflow.md`, `.cursor/commands/srai-profile.md`, `.cursor/skills/threat-modelling/SKILL.md` |
31
+ | Claude Code | `claude` | `.claude/settings.json` | `CLAUDE.md` |
32
+ | VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
33
+ | Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
34
+ | Codex | `codex` | `.codex/config.toml` | `AGENTS.md` |
35
+ | Gemini CLI | `gemini` | `.gemini/settings.json` | `GEMINI.md` |
36
+ | Antigravity | `antigravity` | `.gemini/settings.json` | `.agents/rules/srai-security-review.md` |
37
+
38
+ ## Commands
39
+
40
+ ### `securityreview-kit init`
41
+
42
+ Configure security-review-mcp for your IDE/CLI. Runs interactively when no flags are provided.
43
+
44
+ ```
45
+ Options:
46
+ -t, --target <name...> Target IDE/CLI (cursor, claude, vscode, windsurf, codex, gemini, antigravity)
47
+ -a, --all Install for all supported targets
48
+ --project-name <name> (Optional) Preselect project name from fetched API project list
49
+ --api-url <url> SRAI API URL (or set SECURITY_REVIEW_API_URL env var)
50
+ --api-key <token> SRAI API Token (or set SECURITY_REVIEW_API_TOKEN env var)
51
+ --switch-project Fetch projects and only update mapped workspace rules
52
+ --skip-mcp Skip MCP server config installation
53
+ --skip-rules Skip workspace rule installation
54
+ ```
55
+
56
+ ### `securityreview-kit init --switch-project`
57
+
58
+ Fetches projects from `https://<api-url>/api/projects/` using `Authorization: Bearer <api-key>`, shows a single-select menu, and updates installed workspace rules with the selected project.
59
+
60
+ ### `securityreview-kit status`
61
+
62
+ Show current configuration status for all supported targets in the workspace.
63
+
64
+ ## Environment Variables
65
+
66
+ | Variable | Description |
67
+ |---|---|
68
+ | `SECURITY_REVIEW_PROJECT_NAME` | Optional default project name to preselect in the project menu |
69
+ | `SECURITY_REVIEW_API_URL` | SRAI platform API endpoint |
70
+ | `SECURITY_REVIEW_API_TOKEN` | Your SRAI API token |
71
+
72
+ These can be provided via CLI flags, environment variables, or interactive prompts.
73
+
74
+ ## What Gets Installed
75
+
76
+ **MCP Server Config** — tells your IDE how to launch the `security-review-mcp` server via `npx`.
77
+
78
+ **Workspace Rules** — instructs the AI assistant to consult SRAI threat models and countermeasures before generating security-relevant code. If configured, the selected SRAI project name is injected into the MCP workflow instructions in the installed rule content.
79
+
80
+ ## How It Works
81
+
82
+ 1. Run `securityreview-kit init`
83
+ 2. Select your IDE/CLI target(s)
84
+ 3. Choose whether to install workspace rules and MCP config
85
+ 4. If MCP is selected, enter your SRAI credentials (API URL, token)
86
+ 5. The tool fetches `/api/projects/` and you select exactly one SRAI project from the menu
87
+ 6. The tool creates/merges MCP config and workspace rule files
88
+ 7. Your AI assistant now has access to SRAI security reviews
89
+
90
+ The tool is **idempotent** — running it multiple times safely updates existing configs without duplicating content.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/cli.js';
4
+
5
+ run();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@securityreviewai/securityreview-kit",
3
+ "version": "0.1.11",
4
+ "description": "Bootstrap security-review-mcp for AI IDEs and CLI tools",
5
+ "author": "Debarshi Das <debarshi.das@we45.com>",
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "bin": {
9
+ "securityreview-kit": "./bin/securityreview-kit.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "src/",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "test": "node --test src/**/*.test.js",
21
+ "start": "node bin/securityreview-kit.js"
22
+ },
23
+ "keywords": [
24
+ "security",
25
+ "mcp",
26
+ "security-review",
27
+ "srai",
28
+ "ai-ide",
29
+ "cursor",
30
+ "claude",
31
+ "codex",
32
+ "gemini",
33
+ "windsurf",
34
+ "vscode"
35
+ ],
36
+ "dependencies": {
37
+ "chalk": "^5.4.0",
38
+ "commander": "^13.0.0",
39
+ "inquirer": "^12.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
package/src/cli.js ADDED
@@ -0,0 +1,54 @@
1
+ import { Command } from 'commander';
2
+ import { initCommand } from './commands/init.js';
3
+ import { statusCommand } from './commands/status.js';
4
+ import { switchProjectCommand } from './commands/switch-project.js';
5
+ import { TARGET_NAMES } from './utils/constants.js';
6
+
7
+ export function run() {
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('securityreview-kit')
12
+ .description('Bootstrap security-review-mcp for AI IDEs and CLI tools')
13
+ .version('0.1.0');
14
+
15
+ program
16
+ .command('init')
17
+ .description('Configure security-review-mcp for your IDE / CLI tool')
18
+ .option(
19
+ '-t, --target <name...>',
20
+ `Target IDE/CLI (${TARGET_NAMES.join(', ')}). Omit for interactive mode.`,
21
+ )
22
+ .option('-a, --all', 'Install for all supported targets')
23
+ .option('--project-name <name>', 'Default SRAI project name to preselect in project menu')
24
+ .option('--api-url <url>', 'SRAI API URL (or set SECURITY_REVIEW_API_URL env var)')
25
+ .option('--api-key <token>', 'SRAI API Token (or set SECURITY_REVIEW_API_TOKEN env var)')
26
+ .option('--switch-project', 'Fetch projects and only update mapped workspace rules')
27
+ .option('--skip-mcp', 'Skip MCP server config installation')
28
+ .option('--skip-rules', 'Skip workspace rule installation')
29
+ .action(async (options) => {
30
+ try {
31
+ if (options.switchProject) {
32
+ await switchProjectCommand(options);
33
+ } else {
34
+ await initCommand(options);
35
+ }
36
+ } catch (err) {
37
+ if (err.name === 'ExitPromptError') {
38
+ // User cancelled interactive prompt
39
+ console.log('\n Cancelled.\n');
40
+ process.exit(0);
41
+ }
42
+ throw err;
43
+ }
44
+ });
45
+
46
+ program
47
+ .command('status')
48
+ .description('Show current security-review-mcp configuration status')
49
+ .action(async () => {
50
+ await statusCommand();
51
+ });
52
+
53
+ program.parse();
54
+ }
@@ -0,0 +1,332 @@
1
+ import chalk from 'chalk';
2
+ import { input, checkbox, confirm, select } from '@inquirer/prompts';
3
+ import { TARGETS, TARGET_NAMES } from '../utils/constants.js';
4
+ import { detectTargets } from '../utils/detect.js';
5
+ import { fetchProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
6
+
7
+ // Dynamic imports for generators (avoids loading all at startup)
8
+ const mcpGenerators = {
9
+ cursor: () => import('../generators/mcp/cursor.js'),
10
+ claude: () => import('../generators/mcp/claude.js'),
11
+ vscode: () => import('../generators/mcp/vscode.js'),
12
+ windsurf: () => import('../generators/mcp/windsurf.js'),
13
+ codex: () => import('../generators/mcp/codex.js'),
14
+ gemini: () => import('../generators/mcp/gemini.js'),
15
+ antigravity: () => import('../generators/mcp/gemini.js'),
16
+ };
17
+
18
+ const ruleGenerators = {
19
+ cursor: () => import('../generators/rules/cursor.js'),
20
+ claude: () => import('../generators/rules/claude.js'),
21
+ vscode: () => import('../generators/rules/vscode.js'),
22
+ windsurf: () => import('../generators/rules/windsurf.js'),
23
+ codex: () => import('../generators/rules/codex.js'),
24
+ gemini: () => import('../generators/rules/gemini.js'),
25
+ antigravity: () => import('../generators/rules/antigravity.js'),
26
+ };
27
+
28
+ function normalizeRuleResults(rawResult) {
29
+ const entries = Array.isArray(rawResult) ? rawResult : [rawResult];
30
+
31
+ return entries.map((entry) => {
32
+ if (typeof entry === 'string') {
33
+ return { filePath: entry, action: 'created', kind: 'rule' };
34
+ }
35
+
36
+ if (entry && typeof entry.filePath === 'string') {
37
+ const allowedKinds = new Set(['rule', 'command', 'agent', 'skill']);
38
+ const kind = allowedKinds.has(entry.kind) ? entry.kind : 'rule';
39
+ return { filePath: entry.filePath, action: entry.action || 'created', kind };
40
+ }
41
+
42
+ throw new Error('Rule generator returned an invalid result.');
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Resolve environment variables from flags, env, or interactive prompt.
48
+ */
49
+ async function resolveEnvVars(options, interactive, cwd) {
50
+ let apiUrl = normalizeApiUrl(options.apiUrl || process.env.SECURITY_REVIEW_API_URL || '');
51
+ let apiToken = options.apiKey || process.env.SECURITY_REVIEW_API_TOKEN || '';
52
+
53
+ if (!apiUrl || !apiToken) {
54
+ const stored = getStoredCredentials(cwd);
55
+ if (!apiUrl && stored.apiUrl) {
56
+ apiUrl = stored.apiUrl;
57
+ }
58
+ if (!apiToken && stored.apiToken) {
59
+ apiToken = stored.apiToken;
60
+ }
61
+ }
62
+
63
+ if (interactive) {
64
+ if (!apiUrl) {
65
+ apiUrl = await input({
66
+ message: '🔗 SRAI API URL:',
67
+ default: 'app.demo.securityreview.ai',
68
+ validate: (v) => {
69
+ const normalized = normalizeApiUrl(v);
70
+ if (!normalized) return 'Must be a valid URL';
71
+
72
+ try {
73
+ new URL(normalized);
74
+ return true;
75
+ } catch {
76
+ return 'Must be a valid URL';
77
+ }
78
+ },
79
+ });
80
+ apiUrl = normalizeApiUrl(apiUrl);
81
+ } else {
82
+ console.log(chalk.dim(` API URL: ${apiUrl} (from saved config/env/flags)`));
83
+ }
84
+
85
+ if (!apiToken) {
86
+ apiToken = await input({
87
+ message: '🔑 SRAI API Token:',
88
+ validate: (v) => (v.length > 0 ? true : 'Token is required'),
89
+ });
90
+ } else {
91
+ console.log(chalk.dim(` API Token: ${'•'.repeat(8)} (from saved config/env/flags)`));
92
+ }
93
+ } else {
94
+ if (!apiUrl || !apiToken) {
95
+ console.log(
96
+ chalk.yellow(
97
+ '⚠ Missing credentials. Set SECURITY_REVIEW_API_URL and SECURITY_REVIEW_API_TOKEN\n' +
98
+ ' environment variables, pass --api-url and --api-key flags, or run in interactive mode.',
99
+ ),
100
+ );
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ return { apiUrl, apiToken };
106
+ }
107
+
108
+ /**
109
+ * Resolve project mapping from SRAI API.
110
+ */
111
+ async function resolveProjectName(options, interactive, apiUrl, apiToken) {
112
+ const pinnedProject = (options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '').trim();
113
+
114
+ if (!interactive && pinnedProject) {
115
+ return pinnedProject;
116
+ }
117
+
118
+ const projectNames = await fetchProjectNames(apiUrl, apiToken);
119
+
120
+ if (!interactive) {
121
+ if (projectNames.length === 1) {
122
+ return projectNames[0];
123
+ }
124
+
125
+ console.log(
126
+ chalk.yellow(
127
+ '⚠ Multiple projects available. Pass --project-name or run interactive mode to choose one.',
128
+ ),
129
+ );
130
+ process.exit(1);
131
+ }
132
+
133
+ const selected = await select({
134
+ message: '🧩 Select SRAI project to connect:',
135
+ choices: projectNames.map((name) => ({
136
+ name,
137
+ value: name,
138
+ })),
139
+ default: projectNames.includes(pinnedProject) ? pinnedProject : undefined,
140
+ pageSize: 12,
141
+ });
142
+
143
+ return selected;
144
+ }
145
+
146
+ /**
147
+ * Resolve targets from flags or interactive prompt.
148
+ */
149
+ async function resolveTargets(options, interactive, cwd) {
150
+ // Flag mode: explicit target(s)
151
+ if (options.target) {
152
+ const targets = Array.isArray(options.target) ? options.target : [options.target];
153
+ for (const t of targets) {
154
+ if (!TARGET_NAMES.includes(t)) {
155
+ console.log(chalk.red(`✗ Unknown target: ${t}`));
156
+ console.log(chalk.dim(` Valid targets: ${TARGET_NAMES.join(', ')}`));
157
+ process.exit(1);
158
+ }
159
+ }
160
+ return targets;
161
+ }
162
+
163
+ if (options.all) {
164
+ return [...TARGET_NAMES];
165
+ }
166
+
167
+ if (!interactive) {
168
+ console.log(chalk.red('✗ No target specified. Use --target <name> or --all.'));
169
+ process.exit(1);
170
+ }
171
+
172
+ // Interactive: detect and offer choices
173
+ const detected = detectTargets(cwd);
174
+
175
+ console.log('');
176
+ if (detected.length > 0) {
177
+ console.log(
178
+ chalk.dim(` Detected IDEs in workspace: ${detected.map((d) => TARGETS[d].name).join(', ')}`),
179
+ );
180
+ }
181
+
182
+ const selected = await checkbox({
183
+ message: '🎯 Select target IDE(s) / CLI(s) to configure:',
184
+ choices: TARGET_NAMES.map((key) => ({
185
+ name: `${TARGETS[key].name}`,
186
+ value: key,
187
+ checked: detected.includes(key),
188
+ })),
189
+ validate: (v) => (v.length > 0 ? true : 'Select at least one target'),
190
+ });
191
+
192
+ return selected;
193
+ }
194
+
195
+ /**
196
+ * Main init command handler.
197
+ */
198
+ export async function initCommand(options) {
199
+ const cwd = process.cwd();
200
+ const interactive = !options.target && !options.all;
201
+
202
+ // Banner
203
+ console.log('');
204
+ console.log(chalk.bold.cyan(' ╔══════════════════════════════════════╗'));
205
+ console.log(chalk.bold.cyan(' ║') + chalk.bold(' 🛡️ Security Review Kit — Init ') + chalk.bold.cyan(' ║'));
206
+ console.log(chalk.bold.cyan(' ╚══════════════════════════════════════╝'));
207
+ console.log('');
208
+
209
+ if (interactive) {
210
+ console.log(chalk.dim(' Interactive setup — follow the prompts below.\n'));
211
+ }
212
+
213
+ // Step 1: Resolve targets
214
+ console.log(chalk.bold.white(' Step 1 of 4: Select Targets'));
215
+ console.log(chalk.dim(' ─────────────────────────────────'));
216
+ const targets = await resolveTargets(options, interactive, cwd);
217
+ console.log(chalk.green(` ✓ Targets: ${targets.map((t) => TARGETS[t].name).join(', ')}`));
218
+ console.log('');
219
+
220
+ // Step 2: What to install (rules question first, then MCP)
221
+ let installMcp = !options.skipMcp;
222
+ let installRules = !options.skipRules;
223
+
224
+ if (interactive) {
225
+ console.log(chalk.bold.white(' Step 2 of 4: Installation Options'));
226
+ console.log(chalk.dim(' ─────────────────────────────────'));
227
+ installRules = await confirm({
228
+ message: '📋 Install workspace security rules?',
229
+ default: true,
230
+ });
231
+ installMcp = await confirm({
232
+ message: '📡 Install MCP server configuration?',
233
+ default: true,
234
+ });
235
+ }
236
+
237
+ if (!installMcp && !installRules) {
238
+ console.log(chalk.yellow(' ⚠ Nothing to install. Exiting.'));
239
+ return;
240
+ }
241
+ console.log('');
242
+
243
+ // Step 3: Resolve credentials (only needed when MCP is being installed)
244
+ let envVars = { apiUrl: '', apiToken: '' };
245
+ if (installMcp) {
246
+ console.log(chalk.bold.white(' Step 3 of 4: SRAI Credentials'));
247
+ console.log(chalk.dim(' ─────────────────────────────────'));
248
+ envVars = await resolveEnvVars(options, interactive, cwd);
249
+ console.log(chalk.green(' ✓ Credentials configured'));
250
+ } else {
251
+ console.log(chalk.dim(' Step 3 of 4: Skipping SRAI credentials (MCP not selected).'));
252
+ }
253
+ console.log('');
254
+
255
+ // Step 4: Resolve project name (only needed when MCP is being installed)
256
+ let projectName = options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '';
257
+ if (installMcp) {
258
+ console.log(chalk.bold.white(' Step 4 of 4: SRAI Project Mapping'));
259
+ console.log(chalk.dim(' ─────────────────────────────────'));
260
+ projectName = await resolveProjectName(options, interactive, envVars.apiUrl, envVars.apiToken);
261
+ if (projectName) {
262
+ console.log(chalk.green(` ✓ Project mapped: ${projectName}`));
263
+ } else {
264
+ console.log(chalk.dim(' No project name provided. Rules will use a generic project lookup instruction.'));
265
+ }
266
+ } else {
267
+ console.log(chalk.dim(' Step 4 of 4: Skipping SRAI project mapping (MCP not selected).'));
268
+ }
269
+ console.log('');
270
+
271
+ console.log(chalk.bold.white(' Installing...'));
272
+ console.log(chalk.dim(' ─────────────────────────────────'));
273
+
274
+ const results = [];
275
+
276
+ for (const target of targets) {
277
+ const targetInfo = TARGETS[target];
278
+ console.log('');
279
+ console.log(chalk.bold(` ${targetInfo.name}`));
280
+
281
+ if (installMcp) {
282
+ try {
283
+ const gen = await mcpGenerators[target]();
284
+ const mcpPath = gen.generate(cwd, envVars);
285
+ console.log(chalk.green(` ✓ MCP config → ${typeof mcpPath === 'string' ? mcpPath : mcpPath}`));
286
+ results.push({ target, type: 'mcp', status: 'ok', path: mcpPath });
287
+ } catch (err) {
288
+ console.log(chalk.red(` ✗ MCP config failed: ${err.message}`));
289
+ results.push({ target, type: 'mcp', status: 'error', error: err.message });
290
+ }
291
+ }
292
+
293
+ if (installRules) {
294
+ try {
295
+ const gen = await ruleGenerators[target]();
296
+ const generatedRules = normalizeRuleResults(gen.generate(cwd, { projectName }));
297
+
298
+ for (const rule of generatedRules) {
299
+ const labelByKind = {
300
+ rule: 'Workspace rule',
301
+ command: 'Workspace command',
302
+ agent: 'Workspace agent',
303
+ skill: 'Workspace skill',
304
+ };
305
+ const label = labelByKind[rule.kind] || 'Workspace rule';
306
+ console.log(chalk.green(` ✓ ${label} → ${rule.filePath} (${rule.action})`));
307
+ results.push({ target, type: rule.kind, status: 'ok', path: rule.filePath, action: rule.action });
308
+ }
309
+ } catch (err) {
310
+ console.log(chalk.red(` ✗ Workspace rule failed: ${err.message}`));
311
+ results.push({ target, type: 'rule', status: 'error', error: err.message });
312
+ }
313
+ }
314
+ }
315
+
316
+ // Summary
317
+ const ok = results.filter((r) => r.status === 'ok').length;
318
+ const errors = results.filter((r) => r.status === 'error').length;
319
+
320
+ console.log('');
321
+ console.log(chalk.dim(' ─────────────────────────────────'));
322
+ if (errors === 0) {
323
+ console.log(chalk.bold.green(` ✅ Done! ${ok} configuration(s) installed successfully.`));
324
+ } else {
325
+ console.log(
326
+ chalk.bold.yellow(` ⚠ Done with ${errors} error(s). ${ok} configuration(s) installed.`),
327
+ );
328
+ }
329
+ console.log('');
330
+ console.log(chalk.dim(' Run `securityreview-kit status` to verify your setup.'));
331
+ console.log('');
332
+ }
@@ -0,0 +1,86 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { TARGETS, TARGET_NAMES, SENTINEL_START } from '../utils/constants.js';
5
+ import { readText, readJson } from '../utils/fs-helpers.js';
6
+
7
+ /**
8
+ * Status command — show what's configured in the current workspace.
9
+ */
10
+ export async function statusCommand() {
11
+ const cwd = process.cwd();
12
+
13
+ console.log('');
14
+ console.log(chalk.bold.cyan(' ╔══════════════════════════════════════╗'));
15
+ console.log(chalk.bold.cyan(' ║') + chalk.bold(' 🛡️ Security Review Kit — Status ') + chalk.bold.cyan(' ║'));
16
+ console.log(chalk.bold.cyan(' ╚══════════════════════════════════════╝'));
17
+ console.log('');
18
+ console.log(chalk.dim(` Workspace: ${cwd}`));
19
+ console.log('');
20
+
21
+ let anyFound = false;
22
+
23
+ for (const key of TARGET_NAMES) {
24
+ const target = TARGETS[key];
25
+ const mcpPath = join(cwd, target.mcpConfigPath);
26
+ const rulePath = join(cwd, target.rulePath);
27
+
28
+ const mcpExists = existsSync(mcpPath);
29
+ const ruleExists = existsSync(rulePath);
30
+
31
+ // Check if the MCP config actually has our server
32
+ let mcpHasServer = false;
33
+ if (mcpExists) {
34
+ if (target.mcpConfigPath.endsWith('.toml')) {
35
+ const content = readText(mcpPath);
36
+ mcpHasServer = content.includes('[mcp_servers.security-review-mcp]');
37
+ } else {
38
+ const json = readJson(mcpPath);
39
+ const servers = json?.mcpServers || json?.servers || {};
40
+ mcpHasServer = 'security-review-mcp' in servers;
41
+ }
42
+ }
43
+
44
+ // Check if rule file has our sentinel
45
+ let ruleHasSrai = false;
46
+ if (ruleExists) {
47
+ if (target.ruleMode === 'append') {
48
+ const content = readText(rulePath);
49
+ ruleHasSrai = content.includes(SENTINEL_START) || content.includes('SRAI Security Review');
50
+ } else {
51
+ // Standalone rule file — existence is enough
52
+ ruleHasSrai = true;
53
+ }
54
+ }
55
+
56
+ if (!mcpHasServer && !ruleHasSrai) continue;
57
+
58
+ anyFound = true;
59
+ console.log(chalk.bold(` ${target.name}`));
60
+ console.log(
61
+ ` MCP Config: ${mcpHasServer ? chalk.green('✓ Configured') : chalk.dim('✗ Not found')} ${chalk.dim(target.mcpConfigPath)}`,
62
+ );
63
+ console.log(
64
+ ` Workspace Rule: ${ruleHasSrai ? chalk.green('✓ Installed') : chalk.dim('✗ Not found')} ${chalk.dim(target.rulePath)}`,
65
+ );
66
+ console.log('');
67
+ }
68
+
69
+ if (!anyFound) {
70
+ console.log(chalk.yellow(' No security-review-mcp configurations found in this workspace.'));
71
+ console.log(chalk.dim(' Run `securityreview-kit init` to set up.'));
72
+ console.log('');
73
+ }
74
+
75
+ // Check env vars
76
+ console.log(chalk.bold(' Environment'));
77
+ const apiUrl = process.env.SECURITY_REVIEW_API_URL;
78
+ const apiToken = process.env.SECURITY_REVIEW_API_TOKEN;
79
+ console.log(
80
+ ` SECURITY_REVIEW_API_URL: ${apiUrl ? chalk.green('✓ Set') : chalk.yellow('✗ Not set')}`,
81
+ );
82
+ console.log(
83
+ ` SECURITY_REVIEW_API_TOKEN: ${apiToken ? chalk.green('✓ Set') : chalk.yellow('✗ Not set')}`,
84
+ );
85
+ console.log('');
86
+ }