@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 +4 -1
- package/package.json +1 -1
- package/src/cli.js +11 -3
- package/src/commands/init.js +62 -3
- package/src/generators/rules/guardrails-init-profile.md +12 -4
- 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 +125 -10
- 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,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
|
|
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 {
|
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,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:
|
|
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
|
|
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
|
|
24
|
+
From the repo root, non-interactive runs should include workspace trust and MCP approval:
|
|
25
25
|
|
|
26
|
-
`agent -p "<your profiling instructions>" --
|
|
26
|
+
`agent -p "<your profiling instructions>" --trust --approve-mcps` (or `cursor-agent` if that is what your install provides)
|
|
27
27
|
|
|
28
|
-
|
|
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
|
|
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)) {
|
|
@@ -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=
|
|
68
|
-
*
|
|
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(
|
|
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 =
|
|
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).
|
|
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
|
|
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
|
|
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
|
|
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
|
+
});
|