@securityreviewai/securityreview-kit 0.1.32 → 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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Bootstrap security-review-mcp for AI IDEs and CLI tools",
5
5
  "author": "Debarshi Das <debarshi.das@we45.com>",
6
6
  "license": "UNLICENSED",
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,9 +37,17 @@ 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
- 'When profiling, use minimal agent output (no streaming JSON / verbose progress)',
46
+ 'When profiling, use the standard progress message (default; retained for compatibility)',
47
+ )
48
+ .option(
49
+ '--profiler-verbose',
50
+ 'When profiling, show live agent output for troubleshooting',
43
51
  )
44
52
  .action(async (options) => {
45
53
  try {
@@ -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,15 +454,55 @@ 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('');
492
+ const showProfilerOutput = Boolean(
493
+ options.profilerVerbose || (agentTarget === 'cursor' && options.profilerNoTrust),
494
+ );
495
+ if (showProfilerOutput) {
496
+ console.log(chalk.dim(' Profiling in progress. Agent output is visible for this run...'));
497
+ } else {
498
+ console.log(chalk.dim(' Profiling in progress. This can take a few minutes...'));
499
+ }
460
500
  const pr = runProfilerAgent(cwd, {
461
501
  target: agentTarget,
462
502
  projectName: projectNameForSkill,
463
503
  cursorTrust: !options.profilerNoTrust,
464
- streamProgress: !options.profilerQuiet,
504
+ streamProgress: Boolean(options.profilerVerbose),
505
+ showOutput: showProfilerOutput,
465
506
  });
466
507
  if (pr.ok) {
467
508
  console.log(chalk.green(' \u2713 Profiler agent finished.'));
@@ -492,6 +533,24 @@ export async function initCommand(options) {
492
533
  ' • Want interactive trust instead of `--trust`: run `securityreview-kit init ... --profiler-no-trust` and answer the prompts, then profile again.',
493
534
  ),
494
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
+ );
495
554
  }
496
555
  }
497
556
  }
@@ -17,16 +17,24 @@ 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 repo’s **`.cursor/cli.json`** should list `Mcp(security-review-mcp:*)` under `permissions.allow`; call the MCP tools directly.
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
 
24
- From the repo root, non-interactive runs should include workspace trust, MCP approval, and **streaming progress** (matches default `securityreview-kit init`):
24
+ From the repo root, non-interactive runs should include workspace trust and MCP approval:
25
25
 
26
- `agent -p "<your profiling instructions>" --output-format stream-json --stream-partial-output --trust --approve-mcps` (or `cursor-agent` if that is what your install provides)
26
+ `agent -p "<your profiling instructions>" --trust --approve-mcps` (or `cursor-agent` if that is what your install provides)
27
27
 
28
- Omit `--output-format` / `--stream-partial-output` if you want less verbose terminal output (or use `securityreview-kit init` with `--profiler-quiet`).
28
+ Add `--output-format stream-json --stream-partial-output` only when you need verbose agent diagnostics (or use `securityreview-kit init` with `--profiler-verbose`).
29
29
 
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 installs **`.cursor/cli.json`** with `Mcp(security-review-mcp:*)` in `permissions.allow` so Cursor CLI runs tools without that prompt. Invoke `find_project_by_name`, `update_vibe_profile`, and `write_default_pack` directly. If a call still fails with permissions, report the error and suggest verifying Cursor **auto-run** / CLI permissions — not a conversational approval step.
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(options);
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
  }
@@ -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 { GUARDRAILS_PROFILER_SKILL_REL_DIR } from './constants.js';
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)) {
@@ -64,18 +87,24 @@ export function pickProfilerAgentTarget(targets) {
64
87
  * @param {boolean} [opts.cursorTrust=true] When true, passes `--trust` and `--approve-mcps` so headless init is not blocked by
65
88
  * workspace trust or MCP approval (user confirmed profiling in the kit). Set false with `--profiler-no-trust`
66
89
  * if you need an interactive trust/login/MCP flow in the same terminal.
67
- * @param {boolean} [opts.streamProgress=true] When true, pass each CLI’s streaming / verbose flags so the terminal shows live progress
68
- * (JSON event lines on Cursor/Codex; stream-json + partial messages + verbose on Claude). Disable with `--profiler-quiet`.
90
+ * @param {boolean} [opts.streamProgress=false] When true, pass each CLI’s streaming / verbose flags.
91
+ * @param {boolean} [opts.showOutput=false] When true, inherit stdio from the child process.
92
+ * Keep both false for init-time profiling so the agent does not flood the terminal with JSON/progress logs.
69
93
  */
70
- export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true, streamProgress = true }) {
94
+ export function runProfilerAgent(
95
+ cwd,
96
+ { target, projectName, cursorTrust = true, streamProgress = false, showOutput = streamProgress },
97
+ ) {
71
98
  const prompt = buildProfilerAgentPrompt(projectName, target);
72
99
  const env = augmentPathEnv(process.env);
73
- const opts = { cwd, stdio: 'inherit', env };
100
+ const opts = showOutput
101
+ ? { cwd, stdio: 'inherit', env }
102
+ : { cwd, stdio: ['ignore', 'pipe', 'pipe'], env, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 };
74
103
 
75
104
  if (streamProgress) {
76
105
  console.error(
77
106
  '\n[securityreview-kit] Profiler live output: you should see streaming progress below ' +
78
- '(JSON lines are normal). Use --profiler-quiet for minimal output.\n',
107
+ '(JSON lines are normal). Omit --profiler-verbose for minimal output.\n',
79
108
  );
80
109
  }
81
110
 
@@ -99,7 +128,7 @@ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true,
99
128
  if (r.error) {
100
129
  return { ok: false, message: r.error.message };
101
130
  }
102
- return { ok: r.status === 0, status: r.status };
131
+ return buildProfilerResult(r);
103
132
  }
104
133
 
105
134
  if (target === 'claude') {
@@ -110,7 +139,7 @@ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true,
110
139
  ? ['-p', '--output-format', 'stream-json', '--include-partial-messages', '--verbose', prompt]
111
140
  : ['-p', prompt];
112
141
  const r = spawnSync('claude', args, opts);
113
- return { ok: r.status === 0, status: r.status };
142
+ return buildProfilerResult(r);
114
143
  }
115
144
 
116
145
  if (target === 'codex') {
@@ -119,8 +148,94 @@ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true,
119
148
  }
120
149
  const args = streamProgress ? ['exec', '--json', prompt] : ['exec', prompt];
121
150
  const r = spawnSync('codex', args, opts);
122
- return { ok: r.status === 0, status: r.status };
151
+ return buildProfilerResult(r);
152
+ }
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);
123
180
  }
124
181
 
125
182
  return { ok: false, message: 'unsupported agent target' };
126
183
  }
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
+
209
+ function buildProfilerResult(result) {
210
+ if (result.error) {
211
+ return { ok: false, status: result.status, message: result.error.message };
212
+ }
213
+
214
+ if (result.status === 0) {
215
+ return { ok: true, status: result.status };
216
+ }
217
+
218
+ const outputTail = summarizeProfilerOutput(result.stderr || result.stdout || '');
219
+ const exitDetail =
220
+ typeof result.status === 'number'
221
+ ? `exit status ${result.status}`
222
+ : result.signal
223
+ ? `signal ${result.signal}`
224
+ : 'unknown error';
225
+
226
+ return {
227
+ ok: false,
228
+ status: result.status,
229
+ message: outputTail ? `${exitDetail}; last output: ${outputTail}` : exitDetail,
230
+ };
231
+ }
232
+
233
+ function summarizeProfilerOutput(output) {
234
+ return String(output || '')
235
+ .replace(/\u001b\[[0-9;]*m/g, '')
236
+ .split(/\r?\n/)
237
+ .map((line) => line.trim())
238
+ .filter(Boolean)
239
+ .slice(-3)
240
+ .join(' | ');
241
+ }
@@ -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
+ });