argus-ci 1.0.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/.cursorrules +29 -0
- package/CLAUDE.md +39 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/bin/argus-ci.js +2 -0
- package/dist/agent/index.d.ts +15 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +208 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +133 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/detector.d.ts +11 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +69 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/github.d.ts +41 -0
- package/dist/core/github.d.ts.map +1 -0
- package/dist/core/github.js +97 -0
- package/dist/core/github.js.map +1 -0
- package/dist/core/reporter.d.ts +8 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.js +98 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/scanner.d.ts +19 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +173 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/hooks/setup.d.ts +7 -0
- package/dist/hooks/setup.d.ts.map +1 -0
- package/dist/hooks/setup.js +117 -0
- package/dist/hooks/setup.js.map +1 -0
- package/dist/mcp/server.d.ts +18 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +141 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* argus-ci CLI
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* argus-ci — start MCP server (default, for Cursor/Claude)
|
|
6
|
+
* argus-ci chat — start conversational agent REPL
|
|
7
|
+
* argus-ci scan [files] — scan files / staged / branch from terminal
|
|
8
|
+
* argus-ci pr <url> — scan a PR directly
|
|
9
|
+
* argus-ci setup — install pre-commit hook in current repo
|
|
10
|
+
*/
|
|
11
|
+
import { scanFiles, scanStaged, scanBranch } from "../core/scanner.js";
|
|
12
|
+
import { detectRulesets } from "../core/detector.js";
|
|
13
|
+
import { toMarkdown } from "../core/reporter.js";
|
|
14
|
+
import { runAgent, startRepl } from "../agent/index.js";
|
|
15
|
+
import { setupHook } from "../hooks/setup.js";
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const command = args[0];
|
|
18
|
+
async function main() {
|
|
19
|
+
switch (command) {
|
|
20
|
+
// ── chat — conversational agent REPL ─────────────────────────────────────
|
|
21
|
+
case "chat": {
|
|
22
|
+
const prompt = args.slice(1).join(" ");
|
|
23
|
+
if (prompt) {
|
|
24
|
+
await runAgent(prompt);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
await startRepl();
|
|
28
|
+
}
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
// ── scan — scan files, staged, or a branch ────────────────────────────────
|
|
32
|
+
case "scan": {
|
|
33
|
+
const cwd = process.cwd();
|
|
34
|
+
const detected = detectRulesets(cwd);
|
|
35
|
+
const config = { rulesets: detected.rulesets };
|
|
36
|
+
let result;
|
|
37
|
+
const subArg = args[1];
|
|
38
|
+
if (!subArg || subArg === "--staged") {
|
|
39
|
+
// scan staged files
|
|
40
|
+
result = await scanStaged(cwd, config);
|
|
41
|
+
}
|
|
42
|
+
else if (subArg === "--branch" || subArg === "-b") {
|
|
43
|
+
const branch = args[2];
|
|
44
|
+
const base = args[3] ?? "main";
|
|
45
|
+
if (!branch) {
|
|
46
|
+
console.error("Usage: argus-ci scan --branch <branch> [base]");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
result = await scanBranch(cwd, branch, base, config);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// treat remaining args as file paths
|
|
53
|
+
const files = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
54
|
+
result = await scanFiles(files, cwd, config);
|
|
55
|
+
}
|
|
56
|
+
const markdown = toMarkdown(result);
|
|
57
|
+
console.log(markdown);
|
|
58
|
+
const hasErrors = result.issues.some((i) => i.severity === "error");
|
|
59
|
+
process.exit(hasErrors ? 1 : 0);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
// ── pr — scan a GitHub PR ─────────────────────────────────────────────────
|
|
63
|
+
case "pr": {
|
|
64
|
+
const prUrl = args[1];
|
|
65
|
+
if (!prUrl) {
|
|
66
|
+
console.error("Usage: argus-ci pr <github-pr-url>");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
await runAgent(`Review PR ${prUrl} and give me a full security and quality report`);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
// ── setup — install pre-commit hook ───────────────────────────────────────
|
|
73
|
+
case "setup": {
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
await setupHook(cwd);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
// ── version ───────────────────────────────────────────────────────────────
|
|
79
|
+
case "--version":
|
|
80
|
+
case "-v": {
|
|
81
|
+
const { createRequire } = await import("module");
|
|
82
|
+
const require = createRequire(import.meta.url);
|
|
83
|
+
const pkg = require("../../package.json");
|
|
84
|
+
console.log(`argus-ci v${pkg.version}`);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
// ── help ──────────────────────────────────────────────────────────────────
|
|
88
|
+
case "--help":
|
|
89
|
+
case "-h":
|
|
90
|
+
case "help":
|
|
91
|
+
case undefined: {
|
|
92
|
+
// No command = start MCP server (main use case when added to Cursor/Claude)
|
|
93
|
+
await import("../mcp/server.js");
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
console.error(`Unknown command: ${command}`);
|
|
98
|
+
printHelp();
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function printHelp() {
|
|
103
|
+
console.log(`
|
|
104
|
+
argus-ci — Semgrep quality agent with MCP server and conversational interface
|
|
105
|
+
|
|
106
|
+
USAGE
|
|
107
|
+
argus-ci Start MCP server (add to Cursor / Claude Code)
|
|
108
|
+
argus-ci chat Start conversational agent REPL
|
|
109
|
+
argus-ci chat "review PR <url>" Run a one-shot agent request
|
|
110
|
+
argus-ci scan Scan staged files
|
|
111
|
+
argus-ci scan --staged Scan staged files (explicit)
|
|
112
|
+
argus-ci scan --branch <name> Scan a branch vs main
|
|
113
|
+
argus-ci scan file1 file2 Scan specific files
|
|
114
|
+
argus-ci pr <github-url> Scan a GitHub PR
|
|
115
|
+
argus-ci setup Install pre-commit hook in current repo
|
|
116
|
+
argus-ci --version Show version
|
|
117
|
+
|
|
118
|
+
ENVIRONMENT VARIABLES
|
|
119
|
+
ANTHROPIC_API_KEY Required for the chat / agent interface
|
|
120
|
+
GITHUB_TOKEN Required for private repo PR scanning
|
|
121
|
+
|
|
122
|
+
ADD TO CURSOR (Settings → MCP):
|
|
123
|
+
{ "argus-ci": { "command": "npx", "args": ["argus-ci"] } }
|
|
124
|
+
|
|
125
|
+
ADD TO CLAUDE CODE (~/.claude/settings.json):
|
|
126
|
+
{ "mcpServers": { "semgrep": { "command": "npx", "args": ["argus-ci"] } } }
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
main().catch((err) => {
|
|
130
|
+
console.error("Fatal:", err);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detects the tech stack from package.json / file extensions
|
|
3
|
+
* and returns the appropriate Semgrep rulesets.
|
|
4
|
+
*/
|
|
5
|
+
interface StackInfo {
|
|
6
|
+
rulesets: string[];
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function detectRulesets(cwd: string): StackInfo;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/core/detector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,UAAU,SAAS;IACjB,QAAQ,EAAK,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAyErD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detects the tech stack from package.json / file extensions
|
|
3
|
+
* and returns the appropriate Semgrep rulesets.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
export function detectRulesets(cwd) {
|
|
8
|
+
const rulesets = new Set();
|
|
9
|
+
// Always-on: secrets and OWASP top 10
|
|
10
|
+
rulesets.add("p/secrets");
|
|
11
|
+
rulesets.add("p/owasp-top-ten");
|
|
12
|
+
const pkgPath = join(cwd, "package.json");
|
|
13
|
+
if (existsSync(pkgPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
16
|
+
const allDeps = {
|
|
17
|
+
...(pkg.dependencies ?? {}),
|
|
18
|
+
...(pkg.devDependencies ?? {}),
|
|
19
|
+
};
|
|
20
|
+
// JavaScript / TypeScript — always for any JS project
|
|
21
|
+
rulesets.add("p/javascript");
|
|
22
|
+
// TypeScript
|
|
23
|
+
if (allDeps["typescript"] || existsSync(join(cwd, "tsconfig.json"))) {
|
|
24
|
+
rulesets.add("p/typescript");
|
|
25
|
+
}
|
|
26
|
+
// React
|
|
27
|
+
if (allDeps["react"] || allDeps["react-dom"]) {
|
|
28
|
+
rulesets.add("p/react");
|
|
29
|
+
}
|
|
30
|
+
// Vue
|
|
31
|
+
if (allDeps["vue"] || allDeps["@vue/core"]) {
|
|
32
|
+
rulesets.add("p/javascript"); // no official p/vue but js covers it
|
|
33
|
+
}
|
|
34
|
+
// Node.js server-side (Express, Fastify, Koa, NestJS etc.)
|
|
35
|
+
const serverFrameworks = ["express", "fastify", "koa", "@nestjs/core", "hapi", "restify"];
|
|
36
|
+
if (serverFrameworks.some((f) => f in allDeps)) {
|
|
37
|
+
rulesets.add("p/nodejs");
|
|
38
|
+
rulesets.add("p/sql-injection");
|
|
39
|
+
}
|
|
40
|
+
// Next.js
|
|
41
|
+
if (allDeps["next"]) {
|
|
42
|
+
rulesets.add("p/nextjs");
|
|
43
|
+
}
|
|
44
|
+
// Generic CI / config security
|
|
45
|
+
rulesets.add("p/ci");
|
|
46
|
+
}
|
|
47
|
+
catch { /* ignore malformed package.json */ }
|
|
48
|
+
}
|
|
49
|
+
// Python project
|
|
50
|
+
const pyFiles = ["requirements.txt", "setup.py", "pyproject.toml", "Pipfile"];
|
|
51
|
+
if (pyFiles.some((f) => existsSync(join(cwd, f)))) {
|
|
52
|
+
rulesets.add("p/python");
|
|
53
|
+
rulesets.add("p/bandit"); // Python security
|
|
54
|
+
}
|
|
55
|
+
// Go
|
|
56
|
+
if (existsSync(join(cwd, "go.mod"))) {
|
|
57
|
+
rulesets.add("p/golang");
|
|
58
|
+
}
|
|
59
|
+
// Java / Kotlin
|
|
60
|
+
if (existsSync(join(cwd, "pom.xml")) || existsSync(join(cwd, "build.gradle"))) {
|
|
61
|
+
rulesets.add("p/java");
|
|
62
|
+
}
|
|
63
|
+
const list = [...rulesets];
|
|
64
|
+
return {
|
|
65
|
+
rulesets: list,
|
|
66
|
+
description: list.join(", "),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/core/detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API helpers.
|
|
3
|
+
* Fetches changed files from a PR or branch comparison.
|
|
4
|
+
*
|
|
5
|
+
* Requires GITHUB_TOKEN env var for private repos (public repos work without it).
|
|
6
|
+
*/
|
|
7
|
+
interface PRMeta {
|
|
8
|
+
title: string;
|
|
9
|
+
number: number;
|
|
10
|
+
base: string;
|
|
11
|
+
head: string;
|
|
12
|
+
state: string;
|
|
13
|
+
author: string;
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PRInfo {
|
|
17
|
+
meta: PRMeta;
|
|
18
|
+
files: string[];
|
|
19
|
+
owner: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses a GitHub PR URL into owner/repo/number.
|
|
24
|
+
* Accepts: https://github.com/owner/repo/pull/123
|
|
25
|
+
* github.com/owner/repo/pull/123
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePRUrl(url: string): {
|
|
28
|
+
owner: string;
|
|
29
|
+
repo: string;
|
|
30
|
+
number: number;
|
|
31
|
+
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* Fetches PR metadata and list of changed files.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchPRFiles(prUrl: string, token?: string): Promise<PRInfo | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Posts a review comment to a PR with the scan results.
|
|
38
|
+
*/
|
|
39
|
+
export declare function postPRComment(owner: string, repo: string, prNumber: number, body: string, token?: string): Promise<boolean>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/core/github.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,UAAU,MAAM;IACd,KAAK,EAAI,MAAM,CAAC;IAChB,MAAM,EAAG,MAAM,CAAC;IAChB,IAAI,EAAK,MAAM,CAAC;IAChB,IAAI,EAAK,MAAM,CAAC;IAChB,KAAK,EAAI,MAAM,CAAC;IAChB,MAAM,EAAG,MAAM,CAAC;IAChB,GAAG,EAAM,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAG,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAG,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI9F;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqExF;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAI,MAAM,EACf,IAAI,EAAK,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAK,MAAM,EACf,KAAK,CAAC,EAAG,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAmBlB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API helpers.
|
|
3
|
+
* Fetches changed files from a PR or branch comparison.
|
|
4
|
+
*
|
|
5
|
+
* Requires GITHUB_TOKEN env var for private repos (public repos work without it).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parses a GitHub PR URL into owner/repo/number.
|
|
9
|
+
* Accepts: https://github.com/owner/repo/pull/123
|
|
10
|
+
* github.com/owner/repo/pull/123
|
|
11
|
+
*/
|
|
12
|
+
export function parsePRUrl(url) {
|
|
13
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
14
|
+
if (!match)
|
|
15
|
+
return null;
|
|
16
|
+
return { owner: match[1], repo: match[2], number: parseInt(match[3], 10) };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetches PR metadata and list of changed files.
|
|
20
|
+
*/
|
|
21
|
+
export async function fetchPRFiles(prUrl, token) {
|
|
22
|
+
const parsed = parsePRUrl(prUrl);
|
|
23
|
+
if (!parsed)
|
|
24
|
+
return null;
|
|
25
|
+
const { owner, repo, number } = parsed;
|
|
26
|
+
const headers = {
|
|
27
|
+
Accept: "application/vnd.github+json",
|
|
28
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
29
|
+
};
|
|
30
|
+
const githubToken = token ?? process.env.GITHUB_TOKEN;
|
|
31
|
+
if (githubToken)
|
|
32
|
+
headers["Authorization"] = `Bearer ${githubToken}`;
|
|
33
|
+
// Fetch PR metadata
|
|
34
|
+
const prRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${number}`, { headers });
|
|
35
|
+
if (!prRes.ok) {
|
|
36
|
+
if (prRes.status === 401 || prRes.status === 403) {
|
|
37
|
+
throw new Error(`GitHub API auth failed — set GITHUB_TOKEN for private repos`);
|
|
38
|
+
}
|
|
39
|
+
if (prRes.status === 404) {
|
|
40
|
+
throw new Error(`PR not found: ${prUrl}`);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`GitHub API error ${prRes.status}`);
|
|
43
|
+
}
|
|
44
|
+
const pr = await prRes.json();
|
|
45
|
+
// Fetch changed files (paginated — GitHub caps at 300 files per PR)
|
|
46
|
+
const allFiles = [];
|
|
47
|
+
let page = 1;
|
|
48
|
+
while (true) {
|
|
49
|
+
const filesRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${number}/files?per_page=100&page=${page}`, { headers });
|
|
50
|
+
if (!filesRes.ok)
|
|
51
|
+
break;
|
|
52
|
+
const batch = await filesRes.json();
|
|
53
|
+
if (batch.length === 0)
|
|
54
|
+
break;
|
|
55
|
+
allFiles.push(...batch);
|
|
56
|
+
if (batch.length < 100)
|
|
57
|
+
break;
|
|
58
|
+
page++;
|
|
59
|
+
}
|
|
60
|
+
// Only keep files that exist (not deleted)
|
|
61
|
+
const files = allFiles
|
|
62
|
+
.filter((f) => f.status !== "removed")
|
|
63
|
+
.map((f) => f.filename);
|
|
64
|
+
return {
|
|
65
|
+
owner, repo,
|
|
66
|
+
meta: {
|
|
67
|
+
title: pr.title,
|
|
68
|
+
number: pr.number,
|
|
69
|
+
base: pr.base.ref,
|
|
70
|
+
head: pr.head.ref,
|
|
71
|
+
state: pr.state,
|
|
72
|
+
author: pr.user.login,
|
|
73
|
+
url: pr.html_url,
|
|
74
|
+
},
|
|
75
|
+
files,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Posts a review comment to a PR with the scan results.
|
|
80
|
+
*/
|
|
81
|
+
export async function postPRComment(owner, repo, prNumber, body, token) {
|
|
82
|
+
const githubToken = token ?? process.env.GITHUB_TOKEN;
|
|
83
|
+
if (!githubToken)
|
|
84
|
+
return false;
|
|
85
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${githubToken}`,
|
|
89
|
+
Accept: "application/vnd.github+json",
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({ body }),
|
|
94
|
+
});
|
|
95
|
+
return res.ok;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/core/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats ScanResult into human-readable markdown or compact JSON.
|
|
3
|
+
*/
|
|
4
|
+
import type { ScanResult } from "../types.js";
|
|
5
|
+
export declare function toMarkdown(result: ScanResult, context?: string): string;
|
|
6
|
+
export declare function toCompact(result: ScanResult): string;
|
|
7
|
+
export declare function toPRComment(result: ScanResult, prTitle: string, prUrl: string): string;
|
|
8
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAS,UAAU,EAAY,MAAM,aAAa,CAAC;AAc/D,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAsDvE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAOpD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBtF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats ScanResult into human-readable markdown or compact JSON.
|
|
3
|
+
*/
|
|
4
|
+
const SEVERITY_EMOJI = {
|
|
5
|
+
error: "🔴",
|
|
6
|
+
warning: "🟡",
|
|
7
|
+
info: "🔵",
|
|
8
|
+
};
|
|
9
|
+
const SEVERITY_LABEL = {
|
|
10
|
+
error: "ERROR",
|
|
11
|
+
warning: "WARNING",
|
|
12
|
+
info: "INFO",
|
|
13
|
+
};
|
|
14
|
+
export function toMarkdown(result, context) {
|
|
15
|
+
if (result.skipped) {
|
|
16
|
+
return `> ⚠️ Scan skipped: ${result.skipReason}`;
|
|
17
|
+
}
|
|
18
|
+
const { issues, filesScanned, durationMs, rulesets } = result;
|
|
19
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
20
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
21
|
+
const infos = issues.filter((i) => i.severity === "info");
|
|
22
|
+
const lines = [];
|
|
23
|
+
// Header
|
|
24
|
+
if (context)
|
|
25
|
+
lines.push(`## Semgrep scan — ${context}\n`);
|
|
26
|
+
else
|
|
27
|
+
lines.push(`## Semgrep scan results\n`);
|
|
28
|
+
// Summary bar
|
|
29
|
+
if (issues.length === 0) {
|
|
30
|
+
lines.push(`✅ **No issues found** — ${filesScanned} file${filesScanned !== 1 ? "s" : ""} scanned in ${durationMs}ms`);
|
|
31
|
+
lines.push(`\n_Rulesets: ${rulesets.join(", ")}_`);
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
lines.push(`| Severity | Count |`, `|----------|-------|`, `| 🔴 Error | ${errors.length} |`, `| 🟡 Warning | ${warnings.length} |`, `| 🔵 Info | ${infos.length} |`, ``, `_${filesScanned} file${filesScanned !== 1 ? "s" : ""} scanned · ${durationMs}ms · rulesets: ${rulesets.join(", ")}_`, ``);
|
|
35
|
+
// Group by file
|
|
36
|
+
const byFile = groupByFile(issues);
|
|
37
|
+
for (const [file, fileIssues] of Object.entries(byFile)) {
|
|
38
|
+
lines.push(`### \`${file}\``);
|
|
39
|
+
for (const issue of fileIssues) {
|
|
40
|
+
const emoji = SEVERITY_EMOJI[issue.severity];
|
|
41
|
+
const label = SEVERITY_LABEL[issue.severity];
|
|
42
|
+
lines.push(`\n**${emoji} ${label}** — Line ${issue.line}`);
|
|
43
|
+
lines.push(`> ${issue.message}`);
|
|
44
|
+
if (issue.sourceLine) {
|
|
45
|
+
lines.push(`\`\`\`\n${issue.sourceLine}\n\`\`\``);
|
|
46
|
+
}
|
|
47
|
+
lines.push(`_Rule: \`${issue.ruleId}\`_`);
|
|
48
|
+
if (issue.cwe?.length)
|
|
49
|
+
lines.push(`_CWE: ${issue.cwe.join(", ")}_`);
|
|
50
|
+
if (issue.owasp?.length)
|
|
51
|
+
lines.push(`_OWASP: ${issue.owasp.join(", ")}_`);
|
|
52
|
+
}
|
|
53
|
+
lines.push("");
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
export function toCompact(result) {
|
|
58
|
+
if (result.skipped)
|
|
59
|
+
return `SKIPPED: ${result.skipReason}`;
|
|
60
|
+
if (result.issues.length === 0)
|
|
61
|
+
return `CLEAN — ${result.filesScanned} files scanned`;
|
|
62
|
+
const errors = result.issues.filter((i) => i.severity === "error").length;
|
|
63
|
+
const warns = result.issues.filter((i) => i.severity === "warning").length;
|
|
64
|
+
return `FOUND ${result.issues.length} issues (${errors} errors, ${warns} warnings) in ${result.filesScanned} files`;
|
|
65
|
+
}
|
|
66
|
+
export function toPRComment(result, prTitle, prUrl) {
|
|
67
|
+
const lines = [
|
|
68
|
+
`## 🔍 Semgrep Quality Scan`,
|
|
69
|
+
`> **PR:** [${prTitle}](${prUrl})`,
|
|
70
|
+
``,
|
|
71
|
+
];
|
|
72
|
+
if (result.skipped) {
|
|
73
|
+
lines.push(`> ⚠️ Scan skipped: ${result.skipReason}`);
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
if (result.issues.length === 0) {
|
|
77
|
+
lines.push(`✅ **No issues found** — ${result.filesScanned} files scanned in ${result.durationMs}ms`);
|
|
78
|
+
lines.push(`\n_Rulesets: ${result.rulesets.join(", ")}_`);
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
// Append full markdown report
|
|
82
|
+
lines.push(toMarkdown(result).replace(/^## Semgrep scan results\n/, ""));
|
|
83
|
+
lines.push(`\n---\n_Generated by [argus-ci](https://github.com) · ${new Date().toISOString()}_`);
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
}
|
|
86
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
87
|
+
function groupByFile(issues) {
|
|
88
|
+
const result = {};
|
|
89
|
+
for (const issue of issues) {
|
|
90
|
+
(result[issue.path] ??= []).push(issue);
|
|
91
|
+
}
|
|
92
|
+
// Sort each file's issues by line number
|
|
93
|
+
for (const issues of Object.values(result)) {
|
|
94
|
+
issues.sort((a, b) => a.line - b.line);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep CLI wrapper.
|
|
3
|
+
* Runs `semgrep --json` on a set of files and returns normalised Issue[].
|
|
4
|
+
*
|
|
5
|
+
* Semgrep must be installed: pip install semgrep OR brew install semgrep
|
|
6
|
+
* We check for it on first run and give a clear install message if missing.
|
|
7
|
+
*/
|
|
8
|
+
import type { ScanConfig, ScanResult } from "../types.js";
|
|
9
|
+
export declare function scanFiles(files: string[], cwd: string, config?: ScanConfig): Promise<ScanResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Scans only the git-staged files in cwd.
|
|
12
|
+
* Used by the pre-commit hook.
|
|
13
|
+
*/
|
|
14
|
+
export declare function scanStaged(cwd: string, config?: ScanConfig): Promise<ScanResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Scans files changed on a branch vs a base branch.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scanBranch(cwd: string, branch: string, base?: string, config?: ScanConfig): Promise<ScanResult>;
|
|
19
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAS,UAAU,EAAE,UAAU,EAA8C,MAAM,aAAa,CAAC;AAO7G,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,EAAI,MAAM,EACb,MAAM,GAAE,UAAe,GACtB,OAAO,CAAC,UAAU,CAAC,CAgFrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,UAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAc1F;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAK,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,GAAI,MAAe,EACvB,MAAM,GAAE,UAAe,GACtB,OAAO,CAAC,UAAU,CAAC,CA6BrB"}
|