@securityreviewai/securityreview-kit 0.1.33 → 0.1.34
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 +4 -1
- package/package.json +1 -1
- package/src/cli.js +6 -2
- package/src/commands/init.js +55 -3
- package/src/generators/rules/guardrails-init-profile.md +9 -1
- package/src/generators/rules/guardrails-profiler/SKILL.md +3 -3
- package/src/generators/rules/vscode.js +5 -1
- package/src/utils/constants.js +2 -0
- package/src/utils/ide-cli-install.js +43 -2
- package/src/utils/profiler-agent.js +77 -2
- package/src/utils/profiler-agent.test.js +41 -0
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ npx @securityreviewai/securityreview-kit init --switch-project
|
|
|
29
29
|
|---|---|---|---|
|
|
30
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
31
|
| Claude Code | `claude` | `.claude/settings.json` | `CLAUDE.md` |
|
|
32
|
-
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
|
|
32
|
+
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md` |
|
|
33
33
|
| Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
|
|
34
34
|
| Codex | `codex` | `.codex/config.toml` | `AGENTS.md` |
|
|
35
35
|
| Gemini CLI | `gemini` | `.gemini/settings.json` | `GEMINI.md` |
|
|
@@ -51,6 +51,9 @@ Options:
|
|
|
51
51
|
--switch-project Fetch projects and only update mapped workspace rules
|
|
52
52
|
--skip-mcp Skip MCP server config installation
|
|
53
53
|
--skip-rules Skip workspace rule installation
|
|
54
|
+
--profile-repo Run the guardrails profiler after init
|
|
55
|
+
--profiler-copilot-login
|
|
56
|
+
Run GitHub Copilot CLI setup before VS Code Copilot profiling
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
### `@securityreviewai/securityreview-kit init --switch-project`
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -26,8 +26,8 @@ export function run() {
|
|
|
26
26
|
.option('--switch-project', 'Fetch projects and only update mapped workspace rules')
|
|
27
27
|
.option('--skip-mcp', 'Skip MCP server config installation')
|
|
28
28
|
.option('--skip-rules', 'Skip workspace rule installation')
|
|
29
|
-
.option('--skip-ide-cli-install', 'Do not install Cursor / Claude Code / Codex CLIs when those targets are selected')
|
|
30
|
-
.option('--profile-repo', 'After init, run the guardrails profiler agent (non-interactive; needs cursor, claude, or codex target)')
|
|
29
|
+
.option('--skip-ide-cli-install', 'Do not install Cursor / Copilot / Claude Code / Codex CLIs when those targets are selected')
|
|
30
|
+
.option('--profile-repo', 'After init, run the guardrails profiler agent (non-interactive; needs cursor, vscode, claude, or codex target)')
|
|
31
31
|
.option('--no-profile-repo', 'Skip the optional profiler agent step after init')
|
|
32
32
|
.option(
|
|
33
33
|
'--profiler-no-trust',
|
|
@@ -37,6 +37,10 @@ export function run() {
|
|
|
37
37
|
'--profiler-cursor-login',
|
|
38
38
|
'Before Cursor profiling, run `agent login` (or `cursor-agent login`) in this terminal (then profiling runs in the same init)',
|
|
39
39
|
)
|
|
40
|
+
.option(
|
|
41
|
+
'--profiler-copilot-login',
|
|
42
|
+
'Before VS Code Copilot profiling, run `copilot` in this terminal so you can complete /login and workspace trust',
|
|
43
|
+
)
|
|
40
44
|
.option(
|
|
41
45
|
'--profiler-quiet',
|
|
42
46
|
'When profiling, use the standard progress message (default; retained for compatibility)',
|
package/src/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ensureIdeClisForTargets } from '../utils/ide-cli-install.js';
|
|
|
6
6
|
import { writeGuardrailsSkillBundles } from '../utils/guardrails-profiler-bundle.js';
|
|
7
7
|
import {
|
|
8
8
|
pickProfilerAgentTarget,
|
|
9
|
+
runCopilotLogin,
|
|
9
10
|
runCursorAgentLogin,
|
|
10
11
|
runProfilerAgent,
|
|
11
12
|
} from '../utils/profiler-agent.js';
|
|
@@ -359,7 +360,7 @@ export async function initCommand(options) {
|
|
|
359
360
|
if (bundlePaths.length === 0) {
|
|
360
361
|
console.log(
|
|
361
362
|
chalk.dim(
|
|
362
|
-
' (Guardrails skills are only written for Cursor, Claude Code, and Codex targets.)',
|
|
363
|
+
' (Guardrails skills are only written for Cursor, VS Code Copilot, Claude Code, and Codex targets.)',
|
|
363
364
|
),
|
|
364
365
|
);
|
|
365
366
|
}
|
|
@@ -409,7 +410,7 @@ export async function initCommand(options) {
|
|
|
409
410
|
if (!agentTarget) {
|
|
410
411
|
console.log(
|
|
411
412
|
chalk.yellow(
|
|
412
|
-
' \u26a0 Profiling needs Cursor, Claude Code, or Codex in your targets. Add one and re-run, or run the guardrails-init-profile command in your IDE.',
|
|
413
|
+
' \u26a0 Profiling needs Cursor, VS Code Copilot, Claude Code, or Codex in your targets. Add one and re-run, or run the guardrails-init-profile command in your IDE.',
|
|
413
414
|
),
|
|
414
415
|
);
|
|
415
416
|
} else {
|
|
@@ -453,11 +454,44 @@ export async function initCommand(options) {
|
|
|
453
454
|
}
|
|
454
455
|
console.log('');
|
|
455
456
|
}
|
|
457
|
+
} else if (agentTarget === 'vscode') {
|
|
458
|
+
console.log(
|
|
459
|
+
chalk.dim(
|
|
460
|
+
' Copilot: profiling uses the VS Code MCP config and Copilot CLI tool approvals for this folder.',
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
let runLogin = Boolean(options.profilerCopilotLogin);
|
|
465
|
+
if (!runLogin && interactive) {
|
|
466
|
+
runLogin = await confirm({
|
|
467
|
+
message:
|
|
468
|
+
'Run GitHub Copilot CLI setup in this terminal now? Use /login if prompted, then exit Copilot to continue profiling.',
|
|
469
|
+
default: true,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (runLogin) {
|
|
473
|
+
console.log('');
|
|
474
|
+
console.log(chalk.bold.white(' GitHub Copilot CLI setup'));
|
|
475
|
+
console.log(chalk.dim(' Complete /login or workspace trust prompts, then exit Copilot to continue.\n'));
|
|
476
|
+
const loginResult = runCopilotLogin(cwd);
|
|
477
|
+
if (loginResult.ok) {
|
|
478
|
+
console.log(chalk.green(' \u2713 GitHub Copilot CLI setup step finished.'));
|
|
479
|
+
} else {
|
|
480
|
+
console.log(
|
|
481
|
+
chalk.yellow(
|
|
482
|
+
` \u26a0 Copilot setup exited with status ${loginResult.status ?? 'unknown'}. Profiling will still be attempted; sign in and re-run init if it fails.`,
|
|
483
|
+
),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
console.log('');
|
|
487
|
+
}
|
|
456
488
|
} else {
|
|
457
489
|
console.log(chalk.dim(' (Sign-in or approvals may be required in your terminal.)'));
|
|
458
490
|
}
|
|
459
491
|
console.log('');
|
|
460
|
-
const showProfilerOutput = Boolean(
|
|
492
|
+
const showProfilerOutput = Boolean(
|
|
493
|
+
options.profilerVerbose || (agentTarget === 'cursor' && options.profilerNoTrust),
|
|
494
|
+
);
|
|
461
495
|
if (showProfilerOutput) {
|
|
462
496
|
console.log(chalk.dim(' Profiling in progress. Agent output is visible for this run...'));
|
|
463
497
|
} else {
|
|
@@ -499,6 +533,24 @@ export async function initCommand(options) {
|
|
|
499
533
|
' • Want interactive trust instead of `--trust`: run `securityreview-kit init ... --profiler-no-trust` and answer the prompts, then profile again.',
|
|
500
534
|
),
|
|
501
535
|
);
|
|
536
|
+
} else if (agentTarget === 'vscode') {
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log(chalk.dim(' Typical fixes:'));
|
|
539
|
+
console.log(
|
|
540
|
+
chalk.dim(
|
|
541
|
+
' • Not signed in or workspace not trusted: re-run `securityreview-kit init` and choose Yes for GitHub Copilot CLI setup, or pass `--profiler-copilot-login` with `--profile-repo`.',
|
|
542
|
+
),
|
|
543
|
+
);
|
|
544
|
+
console.log(
|
|
545
|
+
chalk.dim(
|
|
546
|
+
' • CLI missing: install GitHub Copilot CLI and verify `copilot --version`.',
|
|
547
|
+
),
|
|
548
|
+
);
|
|
549
|
+
console.log(
|
|
550
|
+
chalk.dim(
|
|
551
|
+
' • MCP missing: re-run init with VS Code selected and MCP installation enabled so `.vscode/mcp.json` is written.',
|
|
552
|
+
),
|
|
553
|
+
);
|
|
502
554
|
}
|
|
503
555
|
}
|
|
504
556
|
}
|
|
@@ -17,7 +17,7 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
|
17
17
|
|
|
18
18
|
Do not skip MCP upload when credentials and MCP are available.
|
|
19
19
|
|
|
20
|
-
Do **not** ask the user to verbally approve MCP for `security-review-mcp`. The
|
|
20
|
+
Do **not** ask the user to verbally approve MCP for `security-review-mcp`. The init-time profiler runner passes the CLI-specific MCP configuration and approval settings where supported; call the MCP tools directly.
|
|
21
21
|
|
|
22
22
|
## Cursor CLI (scripted)
|
|
23
23
|
|
|
@@ -30,3 +30,11 @@ Add `--output-format stream-json --stream-partial-output` only when you need ver
|
|
|
30
30
|
During `securityreview-kit init`, choose **Yes** when asked to run Cursor login in-terminal, or pass **`--profiler-cursor-login`** with **`--profile-repo`** so login and profiling stay in one run.
|
|
31
31
|
|
|
32
32
|
You can still sign in manually with `agent login` (or `cursor-agent login`). To handle trust/login interactively in the terminal, omit `--trust` and `--approve-mcps`.
|
|
33
|
+
|
|
34
|
+
## GitHub Copilot CLI (scripted)
|
|
35
|
+
|
|
36
|
+
From the repo root, non-interactive runs should load the SRAI MCP server and allow the tools needed to scan, write profile files, and call MCP:
|
|
37
|
+
|
|
38
|
+
`copilot -p "<your profiling instructions>" --additional-mcp-config '{"mcpServers":{"security-review-mcp":{"type":"stdio","command":"npx","args":["-y","@securityreviewai/security-review-mcp@latest"]}}}' --allow-all`
|
|
39
|
+
|
|
40
|
+
During `securityreview-kit init`, choose **Yes** when asked to run GitHub Copilot CLI setup, or pass **`--profiler-copilot-login`** with **`--profile-repo`** so `/login`, workspace trust, and profiling stay in one run.
|
|
@@ -11,7 +11,7 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
|
11
11
|
|
|
12
12
|
## Canonical paths
|
|
13
13
|
|
|
14
|
-
- **This skill & signal registry (read-only):** `<GUARDRAILS_SKILL_DIR>/` — e.g. `.cursor/skills/guardrails-profiler`, `.claude/skills/guardrails-profiler`, or `.codex/skills/guardrails-profiler` depending on where this file was installed.
|
|
14
|
+
- **This skill & signal registry (read-only):** `<GUARDRAILS_SKILL_DIR>/` — e.g. `.cursor/skills/guardrails-profiler`, `.github/skills/guardrails-profiler`, `.claude/skills/guardrails-profiler`, or `.codex/skills/guardrails-profiler` depending on where this file was installed.
|
|
15
15
|
- **Signal registry file:** `<GUARDRAILS_SKILL_DIR>/references/signal-registry.json`
|
|
16
16
|
- **Local guardrails file:** `.guardrails/profile.json`
|
|
17
17
|
- **Combined manifest (project root):** `profile.json` — includes guardrails profile, vibe profile fields for MCP, and default pack payload
|
|
@@ -131,7 +131,7 @@ Write **`profile.json`** at the project root with **only** these parts:
|
|
|
131
131
|
|
|
132
132
|
3. Call **`write_default_pack`** with `project_id` and the payload from **`profile.json.default_guardrail_pack`** (match the MCP tool’s schema).
|
|
133
133
|
|
|
134
|
-
4. **MCP approval:** Do **not** ask the user to “approve MCP” or “say you approve” for `security-review-mcp`. Security Review Kit
|
|
134
|
+
4. **MCP approval:** Do **not** ask the user to “approve MCP” or “say you approve” for `security-review-mcp`. Security Review Kit passes the configured MCP server and approval settings during init-time profiling where the CLI supports it (for example Cursor CLI permissions and Copilot CLI `--additional-mcp-config` / `--allow-all`). Invoke `find_project_by_name`, `update_vibe_profile`, and `write_default_pack` directly. If a call still fails with permissions, report the exact CLI permission error — not a conversational approval step.
|
|
135
135
|
|
|
136
136
|
5. Confirm success: paths written (`profile.json`, `.guardrails/profile.json`) and whether both MCP calls succeeded, or the exact error.
|
|
137
137
|
|
|
@@ -149,4 +149,4 @@ If there are no signals:
|
|
|
149
149
|
|
|
150
150
|
## IDE-Specific Notes
|
|
151
151
|
|
|
152
|
-
When run from Cursor Agent CLI, Claude Code, or Codex CLI, set `profiled_by` to a stable id (`agent` or `cursor-agent`, `claude`, `codex`).
|
|
152
|
+
When run from Cursor Agent CLI, GitHub Copilot CLI, Claude Code, or Codex CLI, set `profiled_by` to a stable id (`agent` or `cursor-agent`, `copilot`, `claude`, `codex`).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { GUARDRAILS_SELECTION_SKILL_REL_DIR } from '../../utils/constants.js';
|
|
2
3
|
import { upsertSentinelBlock } from '../../utils/fs-helpers.js';
|
|
3
4
|
import { getRuleContent } from './content.js';
|
|
4
5
|
|
|
@@ -7,7 +8,10 @@ import { getRuleContent } from './content.js';
|
|
|
7
8
|
*/
|
|
8
9
|
export function generate(cwd, options = {}) {
|
|
9
10
|
const filePath = join(cwd, '.github', 'copilot-instructions.md');
|
|
10
|
-
const content = getRuleContent(
|
|
11
|
+
const content = getRuleContent({
|
|
12
|
+
...options,
|
|
13
|
+
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.vscode,
|
|
14
|
+
});
|
|
11
15
|
const action = upsertSentinelBlock(filePath, content);
|
|
12
16
|
return { filePath, action };
|
|
13
17
|
}
|
package/src/utils/constants.js
CHANGED
|
@@ -63,6 +63,7 @@ export const TARGET_NAMES = Object.keys(TARGETS);
|
|
|
63
63
|
export const GUARDRAILS_PROFILER_SKILL_REL_DIR = {
|
|
64
64
|
cursor: '.cursor/skills/guardrails-profiler',
|
|
65
65
|
claude: '.claude/skills/guardrails-profiler',
|
|
66
|
+
vscode: '.github/skills/guardrails-profiler',
|
|
66
67
|
codex: '.codex/skills/guardrails-profiler',
|
|
67
68
|
};
|
|
68
69
|
|
|
@@ -70,6 +71,7 @@ export const GUARDRAILS_PROFILER_SKILL_REL_DIR = {
|
|
|
70
71
|
export const GUARDRAILS_SELECTION_SKILL_REL_DIR = {
|
|
71
72
|
cursor: '.cursor/skills/guardrails-selection',
|
|
72
73
|
claude: '.claude/skills/guardrails-selection',
|
|
74
|
+
vscode: '.github/skills/guardrails-selection',
|
|
73
75
|
codex: '.codex/skills/guardrails-selection',
|
|
74
76
|
};
|
|
75
77
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { augmentPathEnv, resolveCursorAgentExecutable } from './cursor-agent-path.js';
|
|
3
3
|
|
|
4
|
-
const AGENT_CLI_TARGETS = new Set(['cursor', 'claude', 'codex']);
|
|
4
|
+
const AGENT_CLI_TARGETS = new Set(['cursor', 'claude', 'vscode', 'codex']);
|
|
5
5
|
|
|
6
6
|
function commandOk(cmd, args = ['--version'], env = process.env) {
|
|
7
7
|
const r = spawnSync(cmd, args, { stdio: 'ignore', env });
|
|
@@ -13,7 +13,7 @@ function runShell(script) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Ensure Cursor / Claude Code / Codex CLIs are present when those targets are selected.
|
|
16
|
+
* Ensure Cursor / Claude Code / Copilot / Codex CLIs are present when those targets are selected.
|
|
17
17
|
* Installation uses vendor scripts or npm where appropriate.
|
|
18
18
|
*/
|
|
19
19
|
export function ensureIdeCliForTarget(target, options = {}) {
|
|
@@ -89,6 +89,47 @@ export function ensureIdeCliForTarget(target, options = {}) {
|
|
|
89
89
|
: { target, ok: false, message: 'Codex CLI npm install failed' };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
if (target === 'vscode') {
|
|
93
|
+
if (commandOk('copilot', ['--version'], augmentPathEnv())) {
|
|
94
|
+
return { target, ok: true, already: true };
|
|
95
|
+
}
|
|
96
|
+
if (skipInstall) {
|
|
97
|
+
return {
|
|
98
|
+
target,
|
|
99
|
+
ok: false,
|
|
100
|
+
message:
|
|
101
|
+
'GitHub Copilot CLI not found (`copilot`). Install from https://docs.github.com/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli.',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (process.platform === 'win32') {
|
|
105
|
+
const r = runShell('winget install GitHub.Copilot');
|
|
106
|
+
if (r.status !== 0) {
|
|
107
|
+
return { target, ok: false, message: 'GitHub Copilot CLI winget install failed' };
|
|
108
|
+
}
|
|
109
|
+
return commandOk('copilot', ['--version'], augmentPathEnv())
|
|
110
|
+
? { target, ok: true }
|
|
111
|
+
: {
|
|
112
|
+
target,
|
|
113
|
+
ok: false,
|
|
114
|
+
message:
|
|
115
|
+
'GitHub Copilot CLI (`copilot`) not found after install; open a new shell or ensure it is on PATH and re-run init',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const r = runShell('curl -fsSL https://gh.io/copilot-install | bash');
|
|
119
|
+
if (r.status !== 0) {
|
|
120
|
+
return { target, ok: false, message: 'GitHub Copilot CLI install script failed' };
|
|
121
|
+
}
|
|
122
|
+
if (!commandOk('copilot', ['--version'], augmentPathEnv())) {
|
|
123
|
+
return {
|
|
124
|
+
target,
|
|
125
|
+
ok: false,
|
|
126
|
+
message:
|
|
127
|
+
'GitHub Copilot CLI (`copilot`) not found after install; open a new shell or ensure the install directory is on PATH and re-run init',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return { target, ok: true };
|
|
131
|
+
}
|
|
132
|
+
|
|
92
133
|
return { target, ok: true, skipped: true };
|
|
93
134
|
}
|
|
94
135
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { GUARDRAILS_PROFILER_SKILL_REL_DIR, MCP_SERVER_NAME } from './constants.js';
|
|
3
4
|
import { augmentPathEnv, resolveCursorAgentExecutable } from './cursor-agent-path.js';
|
|
5
|
+
import { readJson } from './fs-helpers.js';
|
|
4
6
|
|
|
5
|
-
const PREFERRED_ORDER = ['cursor', 'claude', 'codex'];
|
|
7
|
+
const PREFERRED_ORDER = ['cursor', 'vscode', 'claude', 'codex'];
|
|
6
8
|
|
|
7
9
|
function commandOk(cmd, args = ['--version'], env = process.env) {
|
|
8
10
|
const r = spawnSync(cmd, args, { stdio: 'ignore', env });
|
|
@@ -48,6 +50,27 @@ export function runCursorAgentLogin(cwd) {
|
|
|
48
50
|
return { ok: r.status === 0, status: r.status, message: r.status !== 0 ? spawnErr : undefined };
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Run GitHub Copilot CLI so the user can complete /login in this terminal.
|
|
55
|
+
*/
|
|
56
|
+
export function runCopilotLogin(cwd) {
|
|
57
|
+
const env = augmentPathEnv(process.env);
|
|
58
|
+
if (!commandOk('copilot', ['--version'], env)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
status: null,
|
|
62
|
+
message:
|
|
63
|
+
'GitHub Copilot CLI not found (`copilot`). Install from https://docs.github.com/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli.',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const r = spawnSync('copilot', [], { cwd, stdio: 'inherit', env });
|
|
67
|
+
const spawnErr = r.error ? r.error.message : null;
|
|
68
|
+
if (r.status === null && spawnErr) {
|
|
69
|
+
return { ok: false, status: null, message: spawnErr };
|
|
70
|
+
}
|
|
71
|
+
return { ok: r.status === 0, status: r.status, message: r.status !== 0 ? spawnErr : undefined };
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
export function pickProfilerAgentTarget(targets) {
|
|
52
75
|
for (const t of PREFERRED_ORDER) {
|
|
53
76
|
if (targets.includes(t)) {
|
|
@@ -128,9 +151,61 @@ export function runProfilerAgent(
|
|
|
128
151
|
return buildProfilerResult(r);
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
if (target === 'vscode') {
|
|
155
|
+
if (!commandOk('copilot', ['--version'], env)) {
|
|
156
|
+
return { ok: false, message: 'copilot not on PATH' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const mcpConfig = buildCopilotAdditionalMcpConfig(cwd);
|
|
160
|
+
if (!mcpConfig) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
message:
|
|
164
|
+
'VS Code MCP config not found for security-review-mcp. Re-run init with MCP installation enabled.',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const args = [
|
|
169
|
+
'-p',
|
|
170
|
+
prompt,
|
|
171
|
+
'--additional-mcp-config',
|
|
172
|
+
mcpConfig,
|
|
173
|
+
'--allow-all',
|
|
174
|
+
];
|
|
175
|
+
if (streamProgress) {
|
|
176
|
+
args.push('--output-format', 'json');
|
|
177
|
+
}
|
|
178
|
+
const r = spawnSync('copilot', args, opts);
|
|
179
|
+
return buildProfilerResult(r);
|
|
180
|
+
}
|
|
181
|
+
|
|
131
182
|
return { ok: false, message: 'unsupported agent target' };
|
|
132
183
|
}
|
|
133
184
|
|
|
185
|
+
export function buildCopilotAdditionalMcpConfig(cwd) {
|
|
186
|
+
const vscodeMcp = readJson(join(cwd, '.vscode', 'mcp.json'));
|
|
187
|
+
const sourceServer =
|
|
188
|
+
vscodeMcp?.mcpServers?.[MCP_SERVER_NAME] || vscodeMcp?.servers?.[MCP_SERVER_NAME];
|
|
189
|
+
|
|
190
|
+
if (!sourceServer || typeof sourceServer !== 'object') {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const server = { ...sourceServer };
|
|
195
|
+
if (!server.type) {
|
|
196
|
+
server.type = 'stdio';
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(server.tools)) {
|
|
199
|
+
server.tools = ['*'];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return JSON.stringify({
|
|
203
|
+
mcpServers: {
|
|
204
|
+
[MCP_SERVER_NAME]: server,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
134
209
|
function buildProfilerResult(result) {
|
|
135
210
|
if (result.error) {
|
|
136
211
|
return { ok: false, status: result.status, message: result.error.message };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import {
|
|
7
|
+
buildCopilotAdditionalMcpConfig,
|
|
8
|
+
pickProfilerAgentTarget,
|
|
9
|
+
} from './profiler-agent.js';
|
|
10
|
+
|
|
11
|
+
test('pickProfilerAgentTarget supports VS Code Copilot', () => {
|
|
12
|
+
assert.equal(pickProfilerAgentTarget(['vscode']), 'vscode');
|
|
13
|
+
assert.equal(pickProfilerAgentTarget(['codex', 'vscode']), 'vscode');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('buildCopilotAdditionalMcpConfig converts VS Code MCP config', () => {
|
|
17
|
+
const cwd = join(tmpdir(), `securityreview-kit-${Date.now()}`);
|
|
18
|
+
mkdirSync(join(cwd, '.vscode'), { recursive: true });
|
|
19
|
+
writeFileSync(
|
|
20
|
+
join(cwd, '.vscode', 'mcp.json'),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
servers: {
|
|
23
|
+
'security-review-mcp': {
|
|
24
|
+
type: 'stdio',
|
|
25
|
+
command: 'npx',
|
|
26
|
+
args: ['-y', '@securityreviewai/security-review-mcp@latest'],
|
|
27
|
+
env: {
|
|
28
|
+
SECURITY_REVIEW_API_URL: 'https://api.example.test',
|
|
29
|
+
SECURITY_REVIEW_API_TOKEN: 'token',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const converted = JSON.parse(buildCopilotAdditionalMcpConfig(cwd));
|
|
37
|
+
|
|
38
|
+
assert.deepEqual(Object.keys(converted.mcpServers), ['security-review-mcp']);
|
|
39
|
+
assert.equal(converted.mcpServers['security-review-mcp'].command, 'npx');
|
|
40
|
+
assert.deepEqual(converted.mcpServers['security-review-mcp'].tools, ['*']);
|
|
41
|
+
});
|