@securityreviewai/vibereview-cli 0.1.0
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/LICENSE +201 -0
- package/README.md +189 -0
- package/SECURITY.md +46 -0
- package/bin/vibereview.js +7 -0
- package/dist/catalog/guardrails.br +0 -0
- package/dist/src/cli.js +103 -0
- package/dist/src/commands/generate.js +36 -0
- package/dist/src/commands/init.js +142 -0
- package/dist/src/core/assets.js +19 -0
- package/dist/src/core/catalog.js +51 -0
- package/dist/src/core/code-guardrails.js +154 -0
- package/dist/src/core/detector.js +214 -0
- package/dist/src/core/evidence.js +107 -0
- package/dist/src/core/fs.js +16 -0
- package/dist/src/core/generation-prompt.js +35 -0
- package/dist/src/core/generator.js +42 -0
- package/dist/src/core/hash.js +18 -0
- package/dist/src/core/integration.js +197 -0
- package/dist/src/core/matcher.js +33 -0
- package/dist/src/core/repository.js +36 -0
- package/dist/src/core/workspace.js +157 -0
- package/dist/src/providers/claude.js +27 -0
- package/dist/src/providers/codex.js +27 -0
- package/dist/src/providers/copilot.js +44 -0
- package/dist/src/providers/cursor.js +27 -0
- package/dist/src/providers/index.js +23 -0
- package/dist/src/providers/process.js +16 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/runners/claude.js +22 -0
- package/dist/src/runners/codex.js +25 -0
- package/dist/src/runners/copilot.js +21 -0
- package/dist/src/runners/cursor.js +20 -0
- package/dist/src/runners/index.js +26 -0
- package/dist/src/runners/process.js +34 -0
- package/dist/src/runners/types.js +2 -0
- package/dist/src/types.js +2 -0
- package/dist/src/ui.js +22 -0
- package/docs/provider-contracts.md +55 -0
- package/package.json +49 -0
- package/runtime/hook-context.cjs +44 -0
- package/schemas/code-guardrails-v1.json +45 -0
- package/skills/guardrail-generator/SKILL.md +237 -0
- package/skills/guardrail-generator/agents/openai.yaml +3 -0
- package/skills/guardrail-generator/evals/evals.json +23 -0
- package/skills/guardrail-generator/references/output-contract.md +40 -0
- package/skills/vibereview-guardrails/SKILL.md +58 -0
- package/skills/vibereview-guardrails/agents/openai.yaml +3 -0
- package/skills/vibereview-osv-scan/SKILL.md +60 -0
- package/skills/vibereview-osv-scan/agents/openai.yaml +3 -0
- package/skills/vibereview-osv-scan/scripts/osv-scan.mjs +77 -0
- package/skills/vibereview-report/SKILL.md +49 -0
- package/skills/vibereview-report/agents/openai.yaml +3 -0
- package/skills/vibereview-report/references/report-contract.md +100 -0
- package/skills/vibereview-secure-code/SKILL.md +57 -0
- package/skills/vibereview-secure-code/agents/openai.yaml +3 -0
- package/skills/vibereview-threat-model/SKILL.md +60 -0
- package/skills/vibereview-threat-model/agents/openai.yaml +3 -0
- package/skills/vibereview-threat-model/references/pwnisms.md +46 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cleanVersion, runProviderCommand } from "./process.js";
|
|
2
|
+
export async function checkClaudePreflight(run = runProviderCommand) {
|
|
3
|
+
let version;
|
|
4
|
+
try {
|
|
5
|
+
version = cleanVersion(await run("claude", ["--version"], 10_000));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error("Claude Code was not found. Install it, then run `claude --version` to verify the installation.");
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const output = await run("claude", ["auth", "status"]);
|
|
12
|
+
if (/not logged in|not authenticated|logged out|\berror:/i.test(output))
|
|
13
|
+
throw new Error("not authenticated");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("Claude Code authentication could not be verified. Run `claude auth status` or `claude auth login`, then retry `vibereview init`.");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
provider: "claude",
|
|
20
|
+
displayName: "Claude Code",
|
|
21
|
+
executable: "claude",
|
|
22
|
+
version,
|
|
23
|
+
authentication: "verified",
|
|
24
|
+
authenticationMessage: "Claude Code authentication verified",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cleanVersion, runProviderCommand } from "./process.js";
|
|
2
|
+
export async function checkCodexPreflight(run = runProviderCommand) {
|
|
3
|
+
let version;
|
|
4
|
+
try {
|
|
5
|
+
version = cleanVersion(await run("codex", ["--version"], 10_000));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error("Codex CLI was not found. Install Codex, then run `codex --version` to verify the installation.");
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const output = await run("codex", ["login", "status"]);
|
|
12
|
+
if (/not logged in|not authenticated|logged out|\berror:/i.test(output))
|
|
13
|
+
throw new Error("not authenticated");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("Codex authentication could not be verified. Run `codex login status` or `codex login`, then retry `vibereview init`.");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
provider: "codex",
|
|
20
|
+
displayName: "Codex",
|
|
21
|
+
executable: "codex",
|
|
22
|
+
version,
|
|
23
|
+
authentication: "verified",
|
|
24
|
+
authenticationMessage: "Codex authentication verified",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cleanVersion, runProviderCommand } from "./process.js";
|
|
2
|
+
const TOKEN_ENV_NAMES = ["COPILOT_PROVIDER_API_KEY", "COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
|
3
|
+
export async function checkCopilotPreflight(run = runProviderCommand) {
|
|
4
|
+
let version;
|
|
5
|
+
try {
|
|
6
|
+
version = cleanVersion(await run("copilot", ["version"], 10_000));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new Error("GitHub Copilot CLI could not be started. Run `copilot version`; if authentication is required, run `copilot login`, then retry `vibereview init`.");
|
|
10
|
+
}
|
|
11
|
+
const configuredEnvironment = TOKEN_ENV_NAMES.find((name) => Boolean(process.env[name]));
|
|
12
|
+
if (configuredEnvironment) {
|
|
13
|
+
return {
|
|
14
|
+
provider: "copilot",
|
|
15
|
+
displayName: "GitHub Copilot",
|
|
16
|
+
executable: "copilot",
|
|
17
|
+
version,
|
|
18
|
+
authentication: "configured",
|
|
19
|
+
authenticationMessage: `GitHub Copilot credentials configured through ${configuredEnvironment}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await run("gh", ["auth", "status"], 10_000);
|
|
24
|
+
return {
|
|
25
|
+
provider: "copilot",
|
|
26
|
+
displayName: "GitHub Copilot",
|
|
27
|
+
executable: "copilot",
|
|
28
|
+
version,
|
|
29
|
+
authentication: "configured",
|
|
30
|
+
authenticationMessage: "GitHub CLI authentication available to Copilot",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {
|
|
35
|
+
provider: "copilot",
|
|
36
|
+
displayName: "GitHub Copilot",
|
|
37
|
+
executable: "copilot",
|
|
38
|
+
version,
|
|
39
|
+
authentication: "deferred",
|
|
40
|
+
authenticationMessage: "GitHub Copilot authentication will be verified on its first agent run",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=copilot.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cleanVersion, runProviderCommand } from "./process.js";
|
|
2
|
+
export async function checkCursorPreflight(run = runProviderCommand) {
|
|
3
|
+
let version;
|
|
4
|
+
try {
|
|
5
|
+
version = cleanVersion(await run("cursor-agent", ["--version"], 10_000));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error("Cursor CLI was not found. Install it, then run `cursor-agent --version` to verify the installation.");
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const output = await run("cursor-agent", ["status"]);
|
|
12
|
+
if (/not authenticated|logged out|unauthenticated|\berror:/i.test(output))
|
|
13
|
+
throw new Error("authentication check failed");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("Cursor CLI authentication could not be verified. Run `cursor-agent status` or `cursor-agent login`, then retry `vibereview init`.");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
provider: "cursor",
|
|
20
|
+
displayName: "Cursor",
|
|
21
|
+
executable: "cursor-agent",
|
|
22
|
+
version,
|
|
23
|
+
authentication: "verified",
|
|
24
|
+
authenticationMessage: "Cursor authentication verified",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { checkClaudePreflight } from "./claude.js";
|
|
2
|
+
import { checkCodexPreflight } from "./codex.js";
|
|
3
|
+
import { checkCopilotPreflight } from "./copilot.js";
|
|
4
|
+
import { checkCursorPreflight } from "./cursor.js";
|
|
5
|
+
export const PROVIDER_NAMES = {
|
|
6
|
+
cursor: "Cursor",
|
|
7
|
+
codex: "Codex",
|
|
8
|
+
claude: "Claude Code",
|
|
9
|
+
copilot: "GitHub Copilot",
|
|
10
|
+
};
|
|
11
|
+
export const PROVIDERS = Object.keys(PROVIDER_NAMES);
|
|
12
|
+
export async function checkProviderPreflight(provider) {
|
|
13
|
+
switch (provider) {
|
|
14
|
+
case "cursor": return checkCursorPreflight();
|
|
15
|
+
case "codex": return checkCodexPreflight();
|
|
16
|
+
case "claude": return checkClaudePreflight();
|
|
17
|
+
case "copilot": return checkCopilotPreflight();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function isProvider(value) {
|
|
21
|
+
return PROVIDERS.includes(value);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function runProviderCommand(executable, args, timeout = 15_000) {
|
|
5
|
+
const result = await execFileAsync(executable, args, {
|
|
6
|
+
timeout,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
env: process.env,
|
|
9
|
+
});
|
|
10
|
+
return `${result.stdout}${result.stderr}`.trim();
|
|
11
|
+
}
|
|
12
|
+
export function cleanVersion(output, fallback = "unknown version") {
|
|
13
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
14
|
+
return lines.find((line) => /\d+\.\d+/.test(line) && !/^warning:/i.test(line)) ?? lines.at(-1) ?? fallback;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { executeAgent } from "./process.js";
|
|
2
|
+
export function claudeArguments(input) {
|
|
3
|
+
return [
|
|
4
|
+
"-p",
|
|
5
|
+
"--output-format", "json",
|
|
6
|
+
"--json-schema", JSON.stringify(input.schema),
|
|
7
|
+
"--permission-mode", "plan",
|
|
8
|
+
"--tools", "",
|
|
9
|
+
"--no-session-persistence",
|
|
10
|
+
"--strict-mcp-config",
|
|
11
|
+
"--mcp-config", JSON.stringify({ mcpServers: {} }),
|
|
12
|
+
input.prompt,
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
export const claudeRunner = {
|
|
16
|
+
provider: "claude",
|
|
17
|
+
async run(input) {
|
|
18
|
+
const { stdout } = await executeAgent("claude", claudeArguments(input), input.temporaryDirectory);
|
|
19
|
+
return stdout;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { executeAgent } from "./process.js";
|
|
4
|
+
export function codexArguments(input, outputPath) {
|
|
5
|
+
return [
|
|
6
|
+
"exec",
|
|
7
|
+
"--sandbox", "read-only",
|
|
8
|
+
"--ephemeral",
|
|
9
|
+
"--ignore-user-config",
|
|
10
|
+
"--skip-git-repo-check",
|
|
11
|
+
"--output-schema", input.schemaPath,
|
|
12
|
+
"--output-last-message", outputPath,
|
|
13
|
+
"-C", input.temporaryDirectory,
|
|
14
|
+
input.prompt,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
export const codexRunner = {
|
|
18
|
+
provider: "codex",
|
|
19
|
+
async run(input) {
|
|
20
|
+
const outputPath = path.join(input.temporaryDirectory, "guardrails-output.json");
|
|
21
|
+
await executeAgent("codex", codexArguments(input, outputPath), input.temporaryDirectory);
|
|
22
|
+
return readFile(outputPath, "utf8");
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { executeAgent } from "./process.js";
|
|
2
|
+
export function copilotArguments(input) {
|
|
3
|
+
return [
|
|
4
|
+
"-C", input.temporaryDirectory,
|
|
5
|
+
"-p", input.prompt,
|
|
6
|
+
"-s",
|
|
7
|
+
"--no-ask-user",
|
|
8
|
+
"--no-custom-instructions",
|
|
9
|
+
"--available-tools=read",
|
|
10
|
+
"--allow-tool=read",
|
|
11
|
+
"--output-format=text",
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export const copilotRunner = {
|
|
15
|
+
provider: "copilot",
|
|
16
|
+
async run(input) {
|
|
17
|
+
const { stdout } = await executeAgent("copilot", copilotArguments(input), input.temporaryDirectory);
|
|
18
|
+
return stdout;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=copilot.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { executeAgent } from "./process.js";
|
|
2
|
+
export function cursorArguments(input) {
|
|
3
|
+
return [
|
|
4
|
+
"-p",
|
|
5
|
+
"--mode", "plan",
|
|
6
|
+
"--sandbox", "enabled",
|
|
7
|
+
"--output-format", "json",
|
|
8
|
+
"--workspace", input.temporaryDirectory,
|
|
9
|
+
"--trust",
|
|
10
|
+
input.prompt,
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
export const cursorRunner = {
|
|
14
|
+
provider: "cursor",
|
|
15
|
+
async run(input) {
|
|
16
|
+
const { stdout } = await executeAgent("cursor-agent", cursorArguments(input), input.temporaryDirectory);
|
|
17
|
+
return stdout;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { claudeRunner } from "./claude.js";
|
|
5
|
+
import { codexRunner } from "./codex.js";
|
|
6
|
+
import { copilotRunner } from "./copilot.js";
|
|
7
|
+
import { cursorRunner } from "./cursor.js";
|
|
8
|
+
const RUNNERS = {
|
|
9
|
+
cursor: cursorRunner,
|
|
10
|
+
codex: codexRunner,
|
|
11
|
+
claude: claudeRunner,
|
|
12
|
+
copilot: copilotRunner,
|
|
13
|
+
};
|
|
14
|
+
export async function runProviderGeneration(provider, input) {
|
|
15
|
+
const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), `vibereview-${provider}-`));
|
|
16
|
+
try {
|
|
17
|
+
return await RUNNERS[provider].run({ ...input, temporaryDirectory });
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function getRunner(provider) {
|
|
24
|
+
return RUNNERS[provider];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60_000;
|
|
5
|
+
export async function executeAgent(executable, args, cwd) {
|
|
6
|
+
try {
|
|
7
|
+
const result = await execFileAsync(executable, args, {
|
|
8
|
+
cwd,
|
|
9
|
+
timeout: agentTimeout(),
|
|
10
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
env: { ...process.env, VIBEREVIEW_INTERNAL: "1" },
|
|
13
|
+
});
|
|
14
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const details = commandErrorDetails(error);
|
|
18
|
+
throw new Error(`${executable} guardrail generation failed${details ? `: ${details}` : "."}`, { cause: error });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function agentTimeout() {
|
|
22
|
+
const configured = Number(process.env.VIBEREVIEW_AGENT_TIMEOUT_MS);
|
|
23
|
+
return Number.isFinite(configured) && configured >= 10_000 ? configured : DEFAULT_TIMEOUT_MS;
|
|
24
|
+
}
|
|
25
|
+
function commandErrorDetails(error) {
|
|
26
|
+
if (!error || typeof error !== "object")
|
|
27
|
+
return "";
|
|
28
|
+
const value = error;
|
|
29
|
+
const text = typeof value.stderr === "string" && value.stderr.trim()
|
|
30
|
+
? value.stderr.trim()
|
|
31
|
+
: typeof value.message === "string" ? value.message : "";
|
|
32
|
+
return text.replace(/\s+/g, " ").slice(-600);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=process.js.map
|
package/dist/src/ui.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2
|
+
function paint(code, text) {
|
|
3
|
+
return color ? `\u001b[${code}m${text}\u001b[0m` : text;
|
|
4
|
+
}
|
|
5
|
+
export const ui = {
|
|
6
|
+
title(text) {
|
|
7
|
+
process.stdout.write(`\n${paint(1, text)}\n\n`);
|
|
8
|
+
},
|
|
9
|
+
step(text) {
|
|
10
|
+
process.stdout.write(`${paint(32, "✓")} ${text}\n`);
|
|
11
|
+
},
|
|
12
|
+
info(text) {
|
|
13
|
+
process.stdout.write(`${text}\n`);
|
|
14
|
+
},
|
|
15
|
+
detail(text) {
|
|
16
|
+
process.stdout.write(`${paint(2, text)}\n`);
|
|
17
|
+
},
|
|
18
|
+
warning(text) {
|
|
19
|
+
process.stdout.write(`${paint(33, "⚠")} ${text}\n`);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Provider CLI contracts
|
|
2
|
+
|
|
3
|
+
These commands are the installation, authentication, and Milestone 2 generation contract. Re-verify them before changing minimum supported provider versions.
|
|
4
|
+
|
|
5
|
+
| Provider | Executable | Version check | Authentication check | Login recovery |
|
|
6
|
+
|---|---|---|---|---|
|
|
7
|
+
| Cursor | `cursor-agent` | `cursor-agent --version` | `cursor-agent status` | `cursor-agent login` |
|
|
8
|
+
| Codex | `codex` | `codex --version` | `codex login status` | `codex login` |
|
|
9
|
+
| Claude Code | `claude` | `claude --version` | `claude auth status` | `claude auth login` |
|
|
10
|
+
| GitHub Copilot | `copilot` | `copilot version` | Best-effort token/GitHub CLI discovery; definitive check deferred | `copilot login` |
|
|
11
|
+
|
|
12
|
+
Copilot is deliberately different. Its documentation describes OAuth, environment-token, GitHub CLI fallback, and BYOK authentication, but does not expose a documented non-interactive status command equivalent to the other three. VibeReview does not spend an AI request merely to test Copilot authentication.
|
|
13
|
+
|
|
14
|
+
## Generation isolation
|
|
15
|
+
|
|
16
|
+
All providers receive the bundled skill, compact profile, baseline rules, and bounded source evidence as one prompt. Their working directory is a new temporary empty directory, removed after the run.
|
|
17
|
+
|
|
18
|
+
| Provider | Non-interactive generation controls |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Cursor | `cursor-agent -p --mode plan --sandbox enabled --output-format json --workspace <temporary> --trust <prompt>` |
|
|
21
|
+
| Codex | `codex exec --sandbox read-only --ephemeral --ignore-user-config --skip-git-repo-check --output-schema <schema> --output-last-message <temporary-output> -C <temporary> <prompt>` |
|
|
22
|
+
| Claude Code | `claude -p --output-format json --json-schema <schema> --permission-mode plan --tools "" --no-session-persistence --strict-mcp-config --mcp-config <empty> <prompt>` |
|
|
23
|
+
| GitHub Copilot | `copilot -C <temporary> -p <prompt> -s --no-ask-user --no-custom-instructions --available-tools=read --allow-tool=read --output-format=text` |
|
|
24
|
+
|
|
25
|
+
The default execution timeout is five minutes and can be changed with `VIBEREVIEW_AGENT_TIMEOUT_MS` (minimum ten seconds). VibeReview performs one additional provider request only when the first response executes successfully but fails the structured-output contract.
|
|
26
|
+
|
|
27
|
+
## Milestone 3 workspace integration
|
|
28
|
+
|
|
29
|
+
VibeReview uses provider-native always-on instructions as the enforcement layer and hooks for session/startup context. This split is intentional: not every provider permits a command hook to modify each submitted prompt.
|
|
30
|
+
|
|
31
|
+
| Provider | Integration contract |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Cursor | An always-applied `.mdc` rule, project skills, and `sessionStart` context injection in `.cursor/hooks.json`. Cursor's `beforeSubmitPrompt` command hook is not used as a context injector. |
|
|
34
|
+
| Codex | A managed root `AGENTS.md` block, project skills, and `.codex/hooks.json` session/prompt context entries. |
|
|
35
|
+
| Claude Code | A managed `.claude/CLAUDE.md` block, project skills, and merged `SessionStart` / `UserPromptSubmit` entries in `.claude/settings.json`. |
|
|
36
|
+
| GitHub Copilot | A managed `.github/copilot-instructions.md` block, project skills, and a repository `sessionStart` prompt hook in `.github/hooks/vibereview.json`. Command-hook prompt modifications are not relied upon. |
|
|
37
|
+
|
|
38
|
+
The local hook command is `node .vibereview/hooks/context.cjs <provider> <event>`. It accepts the provider's JSON hook payload on stdin, sanitizes a supplied native session ID, and emits only VibeReview workflow context. It performs no source scan or network operation.
|
|
39
|
+
|
|
40
|
+
Primary hook references:
|
|
41
|
+
|
|
42
|
+
- [Cursor hooks](https://cursor.com/docs/hooks)
|
|
43
|
+
- [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks)
|
|
44
|
+
- [GitHub Copilot hooks](https://docs.github.com/en/copilot/reference/hooks-reference)
|
|
45
|
+
- [Codex documentation](https://developers.openai.com/codex/)
|
|
46
|
+
|
|
47
|
+
Primary references:
|
|
48
|
+
|
|
49
|
+
- [Cursor CLI authentication](https://docs.cursor.com/en/cli/reference/authentication)
|
|
50
|
+
- [OpenAI Codex documentation](https://developers.openai.com/codex)
|
|
51
|
+
- [Claude Code CLI reference](https://docs.anthropic.com/en/docs/claude-code/cli-usage)
|
|
52
|
+
- [GitHub Copilot CLI authentication](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli)
|
|
53
|
+
- [GitHub Copilot CLI command reference](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference)
|
|
54
|
+
|
|
55
|
+
The Codex `login status` and Claude Code `auth status` subcommands were also confirmed through the installed CLIs' built-in help on 2026-08-25.
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@securityreviewai/vibereview-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first security guardrails for AI coding agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibereview": "bin/vibereview.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist/catalog",
|
|
12
|
+
"dist/src/**/*.js",
|
|
13
|
+
"docs",
|
|
14
|
+
"runtime",
|
|
15
|
+
"schemas",
|
|
16
|
+
"skills/guardrail-generator",
|
|
17
|
+
"skills/vibereview-guardrails",
|
|
18
|
+
"skills/vibereview-threat-model",
|
|
19
|
+
"skills/vibereview-osv-scan",
|
|
20
|
+
"skills/vibereview-secure-code",
|
|
21
|
+
"skills/vibereview-report",
|
|
22
|
+
"SECURITY.md"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.12"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"catalog:import": "node scripts/import-catalog.mjs",
|
|
29
|
+
"build:catalog": "node scripts/build-catalog.mjs",
|
|
30
|
+
"build": "npm run build:catalog && tsc -p tsconfig.json",
|
|
31
|
+
"test": "npm run build && node --test dist/test/*.test.js",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"start": "node bin/vibereview.js",
|
|
34
|
+
"prepublishOnly": "npm test"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org/"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@inquirer/prompts": "^7.8.4",
|
|
42
|
+
"yaml": "^2.8.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.17.2",
|
|
46
|
+
"typescript": "^5.9.2"
|
|
47
|
+
},
|
|
48
|
+
"license": "Apache-2.0"
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const provider = process.argv[2] || "unknown";
|
|
5
|
+
const eventName = process.argv[3] || "SessionStart";
|
|
6
|
+
let input = "";
|
|
7
|
+
|
|
8
|
+
process.stdin.setEncoding("utf8");
|
|
9
|
+
process.stdin.on("data", (chunk) => {
|
|
10
|
+
if (input.length < 1_000_000) input += chunk;
|
|
11
|
+
});
|
|
12
|
+
process.stdin.on("end", () => {
|
|
13
|
+
let payload = {};
|
|
14
|
+
try { payload = input.trim() ? JSON.parse(input) : {}; } catch { payload = {}; }
|
|
15
|
+
const rawId = payload.session_id || payload.sessionId || payload.conversation_id || payload.conversationId || "";
|
|
16
|
+
const sessionId = String(rawId).replace(/[^A-Za-z0-9_-]/g, "").slice(0, 160);
|
|
17
|
+
const reportPath = sessionId ? `.vibereview/reports/${sessionId}.md` : "";
|
|
18
|
+
const identity = sessionId
|
|
19
|
+
? `Use chat_session_id ${sessionId} and report_path ${reportPath} for this IDE chat.`
|
|
20
|
+
: "No native session ID was supplied; create one UUID on the first security-relevant task, use .vibereview/reports/<uuid>.md, and retain that exact ID and path in conversation context.";
|
|
21
|
+
const context = [
|
|
22
|
+
"VibeReview's local security workflow is active for this workspace.",
|
|
23
|
+
identity,
|
|
24
|
+
"For every security-relevant prompt, follow the installed VibeReview guardrail, threat-model, secure-code, and report skills before finalizing.",
|
|
25
|
+
"Maintain exactly one Markdown report for this chat. On every follow-up, read and rewrite that same file to reflect current state; add, revise, or remove stale content as needed. Never create a second report or emit a JSON scan artifact.",
|
|
26
|
+
].join(" ");
|
|
27
|
+
|
|
28
|
+
if (provider === "cursor") {
|
|
29
|
+
process.stdout.write(JSON.stringify({
|
|
30
|
+
...(sessionId ? { env: { VIBEREVIEW_CHAT_SESSION_ID: sessionId, VIBEREVIEW_REPORT_PATH: reportPath } } : {}),
|
|
31
|
+
additional_context: context,
|
|
32
|
+
}));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (provider === "claude" || provider === "codex") {
|
|
36
|
+
process.stdout.write(JSON.stringify({
|
|
37
|
+
hookSpecificOutput: { hookEventName: eventName, additionalContext: context },
|
|
38
|
+
}));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write("{}");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
process.stdin.resume();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"additionalProperties": false,
|
|
4
|
+
"required": ["schema_version", "summary", "guardrails"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"schema_version": { "const": "1" },
|
|
7
|
+
"summary": { "type": "string" },
|
|
8
|
+
"guardrails": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"required": ["title", "type", "category", "instruction", "rationale", "confidence", "evidence", "cwe_ids", "owasp_top10"],
|
|
14
|
+
"properties": {
|
|
15
|
+
"title": { "type": "string" },
|
|
16
|
+
"type": { "enum": ["must", "must_not"] },
|
|
17
|
+
"category": { "type": "string" },
|
|
18
|
+
"instruction": { "type": "string" },
|
|
19
|
+
"rationale": { "type": "string" },
|
|
20
|
+
"confidence": { "enum": ["high", "medium"] },
|
|
21
|
+
"evidence": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["path", "reason"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"path": { "type": "string" },
|
|
29
|
+
"reason": { "type": "string" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"cwe_ids": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": { "type": "string" }
|
|
36
|
+
},
|
|
37
|
+
"owasp_top10": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "type": "string" }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|