@arvorco/relentless 0.3.1 → 0.4.2
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/.claude/commands/relentless.convert.md +25 -0
- package/.claude/skills/analyze/SKILL.md +113 -40
- package/.claude/skills/analyze/templates/analysis-report.md +138 -0
- package/.claude/skills/checklist/SKILL.md +143 -51
- package/.claude/skills/checklist/templates/checklist.md +43 -11
- package/.claude/skills/clarify/SKILL.md +70 -11
- package/.claude/skills/constitution/SKILL.md +61 -3
- package/.claude/skills/constitution/templates/constitution.md +241 -160
- package/.claude/skills/constitution/templates/prompt.md +150 -20
- package/.claude/skills/convert/SKILL.md +248 -0
- package/.claude/skills/implement/SKILL.md +82 -34
- package/.claude/skills/plan/SKILL.md +136 -27
- package/.claude/skills/plan/templates/plan.md +92 -9
- package/.claude/skills/specify/SKILL.md +110 -19
- package/.claude/skills/specify/templates/spec.md +40 -5
- package/.claude/skills/tasks/SKILL.md +75 -1
- package/.claude/skills/tasks/templates/tasks.md +5 -4
- package/CHANGELOG.md +63 -1
- package/MANUAL.md +40 -0
- package/README.md +262 -10
- package/bin/relentless.ts +292 -5
- package/package.json +2 -2
- package/relentless/config.json +46 -2
- package/relentless/constitution.md +2 -2
- package/relentless/prompt.md +97 -18
- package/src/agents/amp.ts +53 -13
- package/src/agents/claude.ts +70 -15
- package/src/agents/codex.ts +73 -14
- package/src/agents/droid.ts +68 -14
- package/src/agents/exec.ts +96 -0
- package/src/agents/gemini.ts +59 -16
- package/src/agents/opencode.ts +188 -9
- package/src/cli/fallback-order.ts +210 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/mode-flag.ts +198 -0
- package/src/cli/review-flags.ts +192 -0
- package/src/config/loader.ts +16 -1
- package/src/config/schema.ts +157 -2
- package/src/execution/runner.ts +144 -21
- package/src/init/scaffolder.ts +285 -25
- package/src/prd/parser.ts +92 -1
- package/src/prd/types.ts +136 -0
- package/src/review/index.ts +92 -0
- package/src/review/prompt.ts +293 -0
- package/src/review/runner.ts +337 -0
- package/src/review/tasks/docs.ts +529 -0
- package/src/review/tasks/index.ts +80 -0
- package/src/review/tasks/lint.ts +436 -0
- package/src/review/tasks/quality.ts +760 -0
- package/src/review/tasks/security.ts +452 -0
- package/src/review/tasks/test.ts +456 -0
- package/src/review/tasks/typecheck.ts +323 -0
- package/src/review/types.ts +139 -0
- package/src/routing/cascade.ts +310 -0
- package/src/routing/classifier.ts +338 -0
- package/src/routing/estimate.ts +270 -0
- package/src/routing/fallback.ts +512 -0
- package/src/routing/index.ts +124 -0
- package/src/routing/registry.ts +501 -0
- package/src/routing/report.ts +570 -0
- package/src/routing/router.ts +287 -0
- package/src/tui/App.tsx +2 -0
- package/src/tui/TUIRunner.tsx +103 -8
- package/src/tui/components/CurrentStory.tsx +23 -1
- package/src/tui/hooks/useTUI.ts +1 -0
- package/src/tui/types.ts +9 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Micro-Task
|
|
3
|
+
*
|
|
4
|
+
* Scans changed files for OWASP top security issues and generates
|
|
5
|
+
* fix tasks for critical and high severity vulnerabilities.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Retrieves changed files from git diff
|
|
9
|
+
* - Pattern-based scanning for common security vulnerabilities
|
|
10
|
+
* - Detects hardcoded passwords and API keys (critical)
|
|
11
|
+
* - Detects unsafe eval() and innerHTML (high)
|
|
12
|
+
* - Detects command injection and SQL injection risks (high/critical)
|
|
13
|
+
* - Generates fix tasks for critical/high issues only
|
|
14
|
+
* - Test files are reported with severity "info" only
|
|
15
|
+
* - Includes OWASP category classification
|
|
16
|
+
*
|
|
17
|
+
* @module src/review/tasks/security
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ReviewTaskResult, FixTask } from "../types";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Types of security vulnerabilities
|
|
24
|
+
*/
|
|
25
|
+
export type VulnerabilityType =
|
|
26
|
+
| "hardcoded_password"
|
|
27
|
+
| "hardcoded_api_key"
|
|
28
|
+
| "unsafe_eval"
|
|
29
|
+
| "xss_risk"
|
|
30
|
+
| "command_injection_risk"
|
|
31
|
+
| "sql_injection_risk";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Severity levels for vulnerabilities
|
|
35
|
+
*/
|
|
36
|
+
export type VulnerabilitySeverity = "critical" | "high" | "medium" | "low" | "info";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* OWASP category for vulnerability classification
|
|
40
|
+
*/
|
|
41
|
+
export type OwaspCategory =
|
|
42
|
+
| "A01:2021-Broken Access Control"
|
|
43
|
+
| "A02:2021-Cryptographic Failures"
|
|
44
|
+
| "A03:2021-Injection"
|
|
45
|
+
| "A04:2021-Insecure Design"
|
|
46
|
+
| "A05:2021-Security Misconfiguration"
|
|
47
|
+
| "A06:2021-Vulnerable and Outdated Components"
|
|
48
|
+
| "A07:2021-Identification and Authentication Failures"
|
|
49
|
+
| "A08:2021-Software and Data Integrity Failures"
|
|
50
|
+
| "A09:2021-Security Logging and Monitoring Failures"
|
|
51
|
+
| "A10:2021-Server-Side Request Forgery";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A detected security vulnerability
|
|
55
|
+
*/
|
|
56
|
+
export interface Vulnerability {
|
|
57
|
+
/** Type of vulnerability */
|
|
58
|
+
type: VulnerabilityType;
|
|
59
|
+
/** Severity level */
|
|
60
|
+
severity: VulnerabilitySeverity;
|
|
61
|
+
/** File path where found */
|
|
62
|
+
file: string;
|
|
63
|
+
/** Line number (1-based) */
|
|
64
|
+
line: number;
|
|
65
|
+
/** Description of the issue */
|
|
66
|
+
message: string;
|
|
67
|
+
/** OWASP category */
|
|
68
|
+
owaspCategory: OwaspCategory;
|
|
69
|
+
/** The matched pattern/code snippet */
|
|
70
|
+
match?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extended result type for security micro-task
|
|
75
|
+
*/
|
|
76
|
+
export interface SecurityResult extends ReviewTaskResult {
|
|
77
|
+
/** The command that was executed */
|
|
78
|
+
command: string;
|
|
79
|
+
/** Number of files scanned */
|
|
80
|
+
scannedFiles: number;
|
|
81
|
+
/** Detected vulnerabilities */
|
|
82
|
+
vulnerabilities?: Vulnerability[];
|
|
83
|
+
/** Human-readable summary */
|
|
84
|
+
summary?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Options for running security scan
|
|
89
|
+
*/
|
|
90
|
+
export interface SecurityOptions {
|
|
91
|
+
/** Working directory for the command */
|
|
92
|
+
cwd?: string;
|
|
93
|
+
/** Custom file reader for testing */
|
|
94
|
+
readFile?: (path: string) => Promise<string>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Security pattern definition
|
|
99
|
+
*/
|
|
100
|
+
interface SecurityPattern {
|
|
101
|
+
type: VulnerabilityType;
|
|
102
|
+
severity: VulnerabilitySeverity;
|
|
103
|
+
pattern: RegExp;
|
|
104
|
+
message: string;
|
|
105
|
+
owaspCategory: OwaspCategory;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Security patterns to scan for
|
|
110
|
+
*/
|
|
111
|
+
const SECURITY_PATTERNS: SecurityPattern[] = [
|
|
112
|
+
// Hardcoded passwords
|
|
113
|
+
{
|
|
114
|
+
type: "hardcoded_password",
|
|
115
|
+
severity: "critical",
|
|
116
|
+
pattern: /\b(password|pwd|passwd|secret)\s*[:=]\s*["'][^"']+["']/gi,
|
|
117
|
+
message: "Hardcoded password detected. Use environment variables instead.",
|
|
118
|
+
owaspCategory: "A07:2021-Identification and Authentication Failures",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "hardcoded_password",
|
|
122
|
+
severity: "critical",
|
|
123
|
+
pattern: /\bPASSWORD\s*[:=]\s*["'][^"']+["']/g,
|
|
124
|
+
message: "Hardcoded PASSWORD constant detected. Use environment variables instead.",
|
|
125
|
+
owaspCategory: "A07:2021-Identification and Authentication Failures",
|
|
126
|
+
},
|
|
127
|
+
// Hardcoded API keys
|
|
128
|
+
{
|
|
129
|
+
type: "hardcoded_api_key",
|
|
130
|
+
severity: "critical",
|
|
131
|
+
pattern: /\b(api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*["'][^"']+["']/gi,
|
|
132
|
+
message: "Hardcoded API key detected. Use environment variables instead.",
|
|
133
|
+
owaspCategory: "A02:2021-Cryptographic Failures",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "hardcoded_api_key",
|
|
137
|
+
severity: "critical",
|
|
138
|
+
pattern: /["'](sk-[a-zA-Z0-9]{20,}|AIza[a-zA-Z0-9_-]{35}|ghp_[a-zA-Z0-9]{36})["']/g,
|
|
139
|
+
message: "Hardcoded API key pattern detected. Use environment variables instead.",
|
|
140
|
+
owaspCategory: "A02:2021-Cryptographic Failures",
|
|
141
|
+
},
|
|
142
|
+
// Unsafe eval
|
|
143
|
+
{
|
|
144
|
+
type: "unsafe_eval",
|
|
145
|
+
severity: "high",
|
|
146
|
+
pattern: /\beval\s*\([^)]+\)/g,
|
|
147
|
+
message: "Unsafe eval() detected. Avoid using eval with dynamic content.",
|
|
148
|
+
owaspCategory: "A03:2021-Injection",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: "unsafe_eval",
|
|
152
|
+
severity: "high",
|
|
153
|
+
pattern: /new\s+Function\s*\([^)]+\)/g,
|
|
154
|
+
message: "Unsafe Function constructor detected. Avoid dynamic code execution.",
|
|
155
|
+
owaspCategory: "A03:2021-Injection",
|
|
156
|
+
},
|
|
157
|
+
// XSS risks
|
|
158
|
+
{
|
|
159
|
+
type: "xss_risk",
|
|
160
|
+
severity: "high",
|
|
161
|
+
pattern: /\.innerHTML\s*=/g,
|
|
162
|
+
message: "Direct innerHTML assignment detected. Use textContent or sanitize input.",
|
|
163
|
+
owaspCategory: "A03:2021-Injection",
|
|
164
|
+
},
|
|
165
|
+
// Command injection
|
|
166
|
+
{
|
|
167
|
+
type: "command_injection_risk",
|
|
168
|
+
severity: "high",
|
|
169
|
+
pattern: /\b(exec|execSync|spawn)\s*\(\s*["'`].*\+/g,
|
|
170
|
+
message: "Command with string concatenation detected. Use parameterized commands.",
|
|
171
|
+
owaspCategory: "A03:2021-Injection",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "command_injection_risk",
|
|
175
|
+
severity: "high",
|
|
176
|
+
pattern: /\b(exec|execSync|spawn)\s*\(\s*`[^`]*\$\{/g,
|
|
177
|
+
message: "Command with template literal interpolation detected. Sanitize input.",
|
|
178
|
+
owaspCategory: "A03:2021-Injection",
|
|
179
|
+
},
|
|
180
|
+
// SQL injection
|
|
181
|
+
{
|
|
182
|
+
type: "sql_injection_risk",
|
|
183
|
+
severity: "critical",
|
|
184
|
+
pattern: /["'`](SELECT|INSERT|UPDATE|DELETE|DROP)\s+.*["'`]\s*\+/gi,
|
|
185
|
+
message: "SQL query with string concatenation detected. Use parameterized queries.",
|
|
186
|
+
owaspCategory: "A03:2021-Injection",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: "sql_injection_risk",
|
|
190
|
+
severity: "critical",
|
|
191
|
+
pattern: /\.(query|execute)\s*\(\s*`[^`]*\$\{/g,
|
|
192
|
+
message: "SQL query with template literal interpolation detected. Use parameterized queries.",
|
|
193
|
+
owaspCategory: "A03:2021-Injection",
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Code file extensions to scan
|
|
199
|
+
*/
|
|
200
|
+
const CODE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if a file is a test file
|
|
204
|
+
*/
|
|
205
|
+
function isTestFile(path: string): boolean {
|
|
206
|
+
return (
|
|
207
|
+
path.includes(".test.") ||
|
|
208
|
+
path.includes(".spec.") ||
|
|
209
|
+
path.includes("/tests/") ||
|
|
210
|
+
path.includes("/test/") ||
|
|
211
|
+
path.includes("__tests__")
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if a file should be scanned
|
|
217
|
+
*/
|
|
218
|
+
function shouldScanFile(path: string): boolean {
|
|
219
|
+
return CODE_EXTENSIONS.some((ext) => path.endsWith(ext));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Scan a file's content for security vulnerabilities
|
|
224
|
+
*
|
|
225
|
+
* @param content - File content to scan
|
|
226
|
+
* @param filePath - Path to the file (for context)
|
|
227
|
+
* @returns Array of detected vulnerabilities
|
|
228
|
+
*/
|
|
229
|
+
export function scanFileForVulnerabilities(
|
|
230
|
+
content: string,
|
|
231
|
+
filePath: string
|
|
232
|
+
): Vulnerability[] {
|
|
233
|
+
const vulnerabilities: Vulnerability[] = [];
|
|
234
|
+
const lines = content.split("\n");
|
|
235
|
+
const isTest = isTestFile(filePath);
|
|
236
|
+
|
|
237
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
238
|
+
// Reset lastIndex for global regex patterns
|
|
239
|
+
pattern.pattern.lastIndex = 0;
|
|
240
|
+
|
|
241
|
+
// Search each line for the pattern
|
|
242
|
+
for (let i = 0; i < lines.length; i++) {
|
|
243
|
+
const line = lines[i];
|
|
244
|
+
pattern.pattern.lastIndex = 0;
|
|
245
|
+
const match = pattern.pattern.exec(line);
|
|
246
|
+
|
|
247
|
+
if (match) {
|
|
248
|
+
// Downgrade severity to "info" for test files
|
|
249
|
+
const severity = isTest ? "info" : pattern.severity;
|
|
250
|
+
|
|
251
|
+
vulnerabilities.push({
|
|
252
|
+
type: pattern.type,
|
|
253
|
+
severity,
|
|
254
|
+
file: filePath,
|
|
255
|
+
line: i + 1,
|
|
256
|
+
message: pattern.message,
|
|
257
|
+
owaspCategory: pattern.owaspCategory,
|
|
258
|
+
match: match[0],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return vulnerabilities;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create a fix task from a vulnerability
|
|
269
|
+
*
|
|
270
|
+
* @param vulnerability - The detected vulnerability
|
|
271
|
+
* @returns A fix task for the review system
|
|
272
|
+
*/
|
|
273
|
+
function createFixTask(vulnerability: Vulnerability): FixTask {
|
|
274
|
+
return {
|
|
275
|
+
type: "security_fix",
|
|
276
|
+
file: vulnerability.file,
|
|
277
|
+
line: vulnerability.line,
|
|
278
|
+
description: `Fix ${vulnerability.type.replace(/_/g, " ")} at line ${vulnerability.line}: ${vulnerability.message}`,
|
|
279
|
+
priority: "critical",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Generate human-readable summary
|
|
285
|
+
*
|
|
286
|
+
* @param vulnerabilities - Detected vulnerabilities
|
|
287
|
+
* @param scannedFiles - Number of files scanned
|
|
288
|
+
* @returns Human-readable summary string
|
|
289
|
+
*/
|
|
290
|
+
function generateSummary(
|
|
291
|
+
vulnerabilities: Vulnerability[],
|
|
292
|
+
scannedFiles: number
|
|
293
|
+
): string {
|
|
294
|
+
if (vulnerabilities.length === 0) {
|
|
295
|
+
return `${scannedFiles} file${scannedFiles !== 1 ? "s" : ""} scanned, no security issues found`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const bySeverity: Record<VulnerabilitySeverity, number> = {
|
|
299
|
+
critical: 0,
|
|
300
|
+
high: 0,
|
|
301
|
+
medium: 0,
|
|
302
|
+
low: 0,
|
|
303
|
+
info: 0,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
for (const vuln of vulnerabilities) {
|
|
307
|
+
bySeverity[vuln.severity]++;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const parts: string[] = [];
|
|
311
|
+
parts.push(`${scannedFiles} file${scannedFiles !== 1 ? "s" : ""} scanned`);
|
|
312
|
+
|
|
313
|
+
const issueParts: string[] = [];
|
|
314
|
+
if (bySeverity.critical > 0) issueParts.push(`${bySeverity.critical} critical`);
|
|
315
|
+
if (bySeverity.high > 0) issueParts.push(`${bySeverity.high} high`);
|
|
316
|
+
if (bySeverity.medium > 0) issueParts.push(`${bySeverity.medium} medium`);
|
|
317
|
+
if (bySeverity.low > 0) issueParts.push(`${bySeverity.low} low`);
|
|
318
|
+
if (bySeverity.info > 0) issueParts.push(`${bySeverity.info} info`);
|
|
319
|
+
|
|
320
|
+
if (issueParts.length > 0) {
|
|
321
|
+
parts.push(issueParts.join(", "));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return parts.join(", ");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Run the security micro-task
|
|
329
|
+
*
|
|
330
|
+
* Retrieves changed files from git diff, scans them for security issues,
|
|
331
|
+
* and generates fix tasks for critical/high vulnerabilities.
|
|
332
|
+
*
|
|
333
|
+
* @param options - Options including working directory and custom file reader
|
|
334
|
+
* @returns SecurityResult with success status, vulnerabilities, and fix tasks
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* const result = await runSecurity({ cwd: "/path/to/project" });
|
|
339
|
+
* if (!result.success) {
|
|
340
|
+
* console.log(`${result.vulnerabilities?.length} vulnerabilities found`);
|
|
341
|
+
* result.fixTasks.forEach(task => console.log(task.description));
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
export async function runSecurity(
|
|
346
|
+
options: SecurityOptions = {}
|
|
347
|
+
): Promise<SecurityResult> {
|
|
348
|
+
const cwd = options.cwd || process.cwd();
|
|
349
|
+
const command = "git diff --name-only HEAD~1";
|
|
350
|
+
const startTime = Date.now();
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
// Get list of changed files
|
|
354
|
+
const proc = Bun.spawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
355
|
+
cwd,
|
|
356
|
+
stdout: "pipe",
|
|
357
|
+
stderr: "pipe",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await proc.exited;
|
|
361
|
+
const stdout = await proc.stdout.text();
|
|
362
|
+
// stderr is captured but not used since git diff errors are rare
|
|
363
|
+
await proc.stderr.text();
|
|
364
|
+
|
|
365
|
+
// Parse changed files
|
|
366
|
+
const changedFiles = stdout
|
|
367
|
+
.split("\n")
|
|
368
|
+
.map((f) => f.trim())
|
|
369
|
+
.filter((f) => f.length > 0 && shouldScanFile(f));
|
|
370
|
+
|
|
371
|
+
const duration = Date.now() - startTime;
|
|
372
|
+
|
|
373
|
+
// If no code files changed, return success
|
|
374
|
+
if (changedFiles.length === 0) {
|
|
375
|
+
return {
|
|
376
|
+
taskType: "security",
|
|
377
|
+
success: true,
|
|
378
|
+
errorCount: 0,
|
|
379
|
+
warningCount: 0,
|
|
380
|
+
fixTasks: [],
|
|
381
|
+
duration,
|
|
382
|
+
command,
|
|
383
|
+
scannedFiles: 0,
|
|
384
|
+
vulnerabilities: [],
|
|
385
|
+
summary: "0 files scanned, no code files in diff",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Scan each file for vulnerabilities
|
|
390
|
+
const allVulnerabilities: Vulnerability[] = [];
|
|
391
|
+
|
|
392
|
+
for (const filePath of changedFiles) {
|
|
393
|
+
try {
|
|
394
|
+
let content: string;
|
|
395
|
+
if (options.readFile) {
|
|
396
|
+
content = await options.readFile(filePath);
|
|
397
|
+
} else {
|
|
398
|
+
const file = Bun.file(`${cwd}/${filePath}`);
|
|
399
|
+
content = await file.text();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const vulnerabilities = scanFileForVulnerabilities(content, filePath);
|
|
403
|
+
allVulnerabilities.push(...vulnerabilities);
|
|
404
|
+
} catch {
|
|
405
|
+
// Skip files that can't be read
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Generate fix tasks for critical/high vulnerabilities
|
|
411
|
+
const criticalOrHigh = allVulnerabilities.filter(
|
|
412
|
+
(v) => v.severity === "critical" || v.severity === "high"
|
|
413
|
+
);
|
|
414
|
+
const fixTasks = criticalOrHigh.map(createFixTask);
|
|
415
|
+
|
|
416
|
+
// Count issues by severity for error/warning counts
|
|
417
|
+
const errorCount = criticalOrHigh.length;
|
|
418
|
+
const warningCount = allVulnerabilities.length - criticalOrHigh.length;
|
|
419
|
+
|
|
420
|
+
// Success if no critical/high issues
|
|
421
|
+
const success = errorCount === 0;
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
taskType: "security",
|
|
425
|
+
success,
|
|
426
|
+
errorCount,
|
|
427
|
+
warningCount,
|
|
428
|
+
fixTasks,
|
|
429
|
+
duration: Date.now() - startTime,
|
|
430
|
+
command,
|
|
431
|
+
scannedFiles: changedFiles.length,
|
|
432
|
+
vulnerabilities: allVulnerabilities,
|
|
433
|
+
summary: generateSummary(allVulnerabilities, changedFiles.length),
|
|
434
|
+
};
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const duration = Date.now() - startTime;
|
|
437
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
taskType: "security",
|
|
441
|
+
success: false,
|
|
442
|
+
errorCount: 1,
|
|
443
|
+
warningCount: 0,
|
|
444
|
+
fixTasks: [],
|
|
445
|
+
duration,
|
|
446
|
+
command,
|
|
447
|
+
scannedFiles: 0,
|
|
448
|
+
vulnerabilities: [],
|
|
449
|
+
error: `Security scan failed: ${errorMessage}`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|