ghagga-core 2.4.0 → 2.5.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/dist/enhance/enhance.d.ts +31 -0
- package/dist/enhance/enhance.d.ts.map +1 -0
- package/dist/enhance/enhance.js +140 -0
- package/dist/enhance/enhance.js.map +1 -0
- package/dist/enhance/index.d.ts +5 -0
- package/dist/enhance/index.d.ts.map +1 -0
- package/dist/enhance/index.js +3 -0
- package/dist/enhance/index.js.map +1 -0
- package/dist/enhance/prompt.d.ts +22 -0
- package/dist/enhance/prompt.d.ts.map +1 -0
- package/dist/enhance/prompt.js +71 -0
- package/dist/enhance/prompt.js.map +1 -0
- package/dist/enhance/types.d.ts +55 -0
- package/dist/enhance/types.d.ts.map +1 -0
- package/dist/enhance/types.js +6 -0
- package/dist/enhance/types.js.map +1 -0
- package/dist/health/index.d.ts +4 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +4 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/recommendations.d.ts +17 -0
- package/dist/health/recommendations.d.ts.map +1 -0
- package/dist/health/recommendations.js +81 -0
- package/dist/health/recommendations.js.map +1 -0
- package/dist/health/score.d.ts +32 -0
- package/dist/health/score.d.ts.map +1 -0
- package/dist/health/score.js +73 -0
- package/dist/health/score.js.map +1 -0
- package/dist/health/trends.d.ts +31 -0
- package/dist/health/trends.d.ts.map +1 -0
- package/dist/health/trends.js +88 -0
- package/dist/health/trends.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +41 -0
- package/dist/pipeline.js.map +1 -1
- package/dist/sarif/builder.d.ts +17 -0
- package/dist/sarif/builder.d.ts.map +1 -0
- package/dist/sarif/builder.js +88 -0
- package/dist/sarif/builder.js.map +1 -0
- package/dist/sarif/index.d.ts +3 -0
- package/dist/sarif/index.d.ts.map +1 -0
- package/dist/sarif/index.js +2 -0
- package/dist/sarif/index.js.map +1 -0
- package/dist/sarif/types.d.ts +52 -0
- package/dist/sarif/types.d.ts.map +1 -0
- package/dist/sarif/types.js +8 -0
- package/dist/sarif/types.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/runner.d.ts +7 -14
- package/dist/tools/runner.d.ts.map +1 -1
- package/dist/tools/runner.js +10 -60
- package/dist/tools/runner.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Enhance — post-analysis intelligence layer.
|
|
3
|
+
*
|
|
4
|
+
* Calls an LLM to group, prioritize, and filter static analysis findings.
|
|
5
|
+
* Failures are non-blocking — returns empty result on any error.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReviewFinding } from '../types.js';
|
|
8
|
+
import type { EnhanceInput, EnhanceMetadata, EnhanceResult } from './types.js';
|
|
9
|
+
/** A ReviewFinding augmented with AI enhance metadata. */
|
|
10
|
+
export interface EnhancedReviewFinding extends ReviewFinding {
|
|
11
|
+
groupId?: string;
|
|
12
|
+
aiPriority?: number;
|
|
13
|
+
aiFiltered?: boolean;
|
|
14
|
+
filterReason?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Enhance static analysis findings using AI.
|
|
18
|
+
*
|
|
19
|
+
* @param input - Findings and LLM configuration
|
|
20
|
+
* @returns Enhanced result with groups, priorities, suggestions, and filtered findings
|
|
21
|
+
*/
|
|
22
|
+
export declare function enhanceFindings(input: EnhanceInput): Promise<{
|
|
23
|
+
result: EnhanceResult;
|
|
24
|
+
metadata: EnhanceMetadata;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Merge enhance results back onto the original findings.
|
|
28
|
+
* Assigns groupId, aiPriority, aiFiltered, and filterReason.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mergeEnhanceResult(findings: ReviewFinding[], enhanceResult: EnhanceResult): EnhancedReviewFinding[];
|
|
31
|
+
//# sourceMappingURL=enhance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhance.d.ts","sourceRoot":"","sources":["../../src/enhance/enhance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,aAAa,CAAC;AAO9D,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAaD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,CAAC,CAuD/D;AA+BD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,aAAa,EAAE,EACzB,aAAa,EAAE,aAAa,GAC3B,qBAAqB,EAAE,CAmCzB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Enhance — post-analysis intelligence layer.
|
|
3
|
+
*
|
|
4
|
+
* Calls an LLM to group, prioritize, and filter static analysis findings.
|
|
5
|
+
* Failures are non-blocking — returns empty result on any error.
|
|
6
|
+
*/
|
|
7
|
+
import { generateText } from 'ai';
|
|
8
|
+
import { createModel } from '../providers/index.js';
|
|
9
|
+
import { buildEnhancePrompt, ENHANCE_SYSTEM_PROMPT, truncateByTokenBudget, } from './prompt.js';
|
|
10
|
+
/** Default token budget for the enhance prompt (4K tokens). */
|
|
11
|
+
const DEFAULT_TOKEN_BUDGET = 4000;
|
|
12
|
+
/** Empty result returned on failure or zero findings. */
|
|
13
|
+
const EMPTY_RESULT = {
|
|
14
|
+
groups: [],
|
|
15
|
+
priorities: {},
|
|
16
|
+
suggestions: {},
|
|
17
|
+
filtered: [],
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Enhance static analysis findings using AI.
|
|
21
|
+
*
|
|
22
|
+
* @param input - Findings and LLM configuration
|
|
23
|
+
* @returns Enhanced result with groups, priorities, suggestions, and filtered findings
|
|
24
|
+
*/
|
|
25
|
+
export async function enhanceFindings(input) {
|
|
26
|
+
// Skip AI call if no findings
|
|
27
|
+
if (input.findings.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
result: EMPTY_RESULT,
|
|
30
|
+
metadata: {
|
|
31
|
+
model: input.model,
|
|
32
|
+
tokenUsage: { input: 0, output: 0 },
|
|
33
|
+
groupCount: 0,
|
|
34
|
+
filteredCount: 0,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
// Truncate to fit token budget
|
|
40
|
+
const truncated = truncateByTokenBudget(input.findings, DEFAULT_TOKEN_BUDGET);
|
|
41
|
+
const prompt = buildEnhancePrompt(truncated);
|
|
42
|
+
// Call LLM
|
|
43
|
+
const model = createModel(input.provider, input.model, input.apiKey);
|
|
44
|
+
const response = await generateText({
|
|
45
|
+
model,
|
|
46
|
+
system: ENHANCE_SYSTEM_PROMPT,
|
|
47
|
+
prompt,
|
|
48
|
+
maxOutputTokens: 2000,
|
|
49
|
+
});
|
|
50
|
+
// Parse response
|
|
51
|
+
const parsed = parseEnhanceResponse(response.text);
|
|
52
|
+
return {
|
|
53
|
+
result: parsed,
|
|
54
|
+
metadata: {
|
|
55
|
+
model: input.model,
|
|
56
|
+
tokenUsage: {
|
|
57
|
+
input: response.usage?.inputTokens ?? 0,
|
|
58
|
+
output: response.usage?.outputTokens ?? 0,
|
|
59
|
+
},
|
|
60
|
+
groupCount: parsed.groups.length,
|
|
61
|
+
filteredCount: parsed.filtered.length,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Non-blocking — return empty result on any error
|
|
67
|
+
return {
|
|
68
|
+
result: EMPTY_RESULT,
|
|
69
|
+
metadata: {
|
|
70
|
+
model: input.model,
|
|
71
|
+
tokenUsage: { input: 0, output: 0 },
|
|
72
|
+
groupCount: 0,
|
|
73
|
+
filteredCount: 0,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse the LLM response text into an EnhanceResult.
|
|
80
|
+
* Gracefully handles malformed JSON.
|
|
81
|
+
*/
|
|
82
|
+
function parseEnhanceResponse(text) {
|
|
83
|
+
try {
|
|
84
|
+
// Extract JSON from response (may have markdown code fences)
|
|
85
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
86
|
+
if (!jsonMatch)
|
|
87
|
+
return EMPTY_RESULT;
|
|
88
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
89
|
+
return {
|
|
90
|
+
groups: Array.isArray(parsed.groups) ? parsed.groups : [],
|
|
91
|
+
priorities: typeof parsed.priorities === 'object' && parsed.priorities !== null
|
|
92
|
+
? parsed.priorities
|
|
93
|
+
: {},
|
|
94
|
+
suggestions: typeof parsed.suggestions === 'object' && parsed.suggestions !== null
|
|
95
|
+
? parsed.suggestions
|
|
96
|
+
: {},
|
|
97
|
+
filtered: Array.isArray(parsed.filtered) ? parsed.filtered : [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return EMPTY_RESULT;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Merge enhance results back onto the original findings.
|
|
106
|
+
* Assigns groupId, aiPriority, aiFiltered, and filterReason.
|
|
107
|
+
*/
|
|
108
|
+
export function mergeEnhanceResult(findings, enhanceResult) {
|
|
109
|
+
// Build lookup maps
|
|
110
|
+
const groupByFindingId = new Map();
|
|
111
|
+
for (const group of enhanceResult.groups) {
|
|
112
|
+
for (const id of group.findingIds) {
|
|
113
|
+
groupByFindingId.set(id, group.groupId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const filteredMap = new Map();
|
|
117
|
+
for (const f of enhanceResult.filtered) {
|
|
118
|
+
filteredMap.set(f.findingId, f.reason);
|
|
119
|
+
}
|
|
120
|
+
return findings.map((finding, index) => {
|
|
121
|
+
const id = index + 1; // IDs are 1-based from serializeFindings
|
|
122
|
+
const enhanced = { ...finding };
|
|
123
|
+
const groupId = groupByFindingId.get(id);
|
|
124
|
+
if (groupId)
|
|
125
|
+
enhanced.groupId = groupId;
|
|
126
|
+
const priority = enhanceResult.priorities[id];
|
|
127
|
+
if (priority !== undefined)
|
|
128
|
+
enhanced.aiPriority = priority;
|
|
129
|
+
const suggestion = enhanceResult.suggestions[id];
|
|
130
|
+
if (suggestion)
|
|
131
|
+
enhanced.suggestion = suggestion;
|
|
132
|
+
const filterReason = filteredMap.get(id);
|
|
133
|
+
if (filterReason) {
|
|
134
|
+
enhanced.aiFiltered = true;
|
|
135
|
+
enhanced.filterReason = filterReason;
|
|
136
|
+
}
|
|
137
|
+
return enhanced;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=enhance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhance.js","sourceRoot":"","sources":["../../src/enhance/enhance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { EnhancedReviewFinding } from './enhance.js';
|
|
2
|
+
export { enhanceFindings, mergeEnhanceResult } from './enhance.js';
|
|
3
|
+
export { serializeFindings, truncateByTokenBudget } from './prompt.js';
|
|
4
|
+
export type { EnhanceInput, EnhanceMetadata, EnhanceResult, FilteredFinding, FindingGroup, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/enhance/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EACV,YAAY,EACZ,eAAe,EACf,aAAa,EACb,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/enhance/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Enhance prompt template and serialization utilities.
|
|
3
|
+
*/
|
|
4
|
+
import type { ReviewFinding } from '../types.js';
|
|
5
|
+
import type { EnhanceFindingSummary } from './types.js';
|
|
6
|
+
/** System prompt for the enhance AI call. */
|
|
7
|
+
export declare const ENHANCE_SYSTEM_PROMPT = "You are a code review assistant analyzing static analysis findings.\nYour job is to make the findings MORE actionable by:\n1. Grouping related findings that share a root cause\n2. Prioritizing findings by real-world impact (1-10 scale, 10 = most critical)\n3. Suggesting concrete fixes for the highest-priority findings\n4. Identifying likely false positives\n\nRespond with ONLY valid JSON matching this schema:\n{\n \"groups\": [{ \"groupId\": \"g1\", \"label\": \"Description of related issue\", \"findingIds\": [1, 2] }],\n \"priorities\": { \"1\": 8, \"2\": 6 },\n \"suggestions\": { \"1\": \"Use parameterized queries instead of string concatenation\" },\n \"filtered\": [{ \"findingId\": 3, \"reason\": \"Test file, not production code\" }]\n}\n\nRules:\n- Every finding must appear in exactly one group\n- Priority scores: 10=critical security flaw, 7-9=high impact, 4-6=moderate, 1-3=low impact/noise\n- Only suggest fixes for findings with priority >= 7\n- Only filter findings you are >90% confident are false positives\n- Keep suggestions concise (1-2 sentences)";
|
|
8
|
+
/**
|
|
9
|
+
* Build the user prompt with serialized findings.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildEnhancePrompt(findings: EnhanceFindingSummary[]): string;
|
|
12
|
+
/**
|
|
13
|
+
* Map full ReviewFindings to compact summaries with sequential IDs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function serializeFindings(findings: ReviewFinding[]): EnhanceFindingSummary[];
|
|
16
|
+
/**
|
|
17
|
+
* Truncate findings to fit within a token budget.
|
|
18
|
+
* Drops lowest-severity findings first.
|
|
19
|
+
* Rough estimate: ~20 tokens per finding.
|
|
20
|
+
*/
|
|
21
|
+
export declare function truncateByTokenBudget(summaries: EnhanceFindingSummary[], maxTokens: number): EnhanceFindingSummary[];
|
|
22
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/enhance/prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,6CAA6C;AAC7C,eAAO,MAAM,qBAAqB,2jCAoBS,CAAC;AAE5C;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,MAAM,CAS5E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,qBAAqB,EAAE,CAUpF;AAWD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,qBAAqB,EAAE,EAClC,SAAS,EAAE,MAAM,GAChB,qBAAqB,EAAE,CAYzB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Enhance prompt template and serialization utilities.
|
|
3
|
+
*/
|
|
4
|
+
/** System prompt for the enhance AI call. */
|
|
5
|
+
export const ENHANCE_SYSTEM_PROMPT = `You are a code review assistant analyzing static analysis findings.
|
|
6
|
+
Your job is to make the findings MORE actionable by:
|
|
7
|
+
1. Grouping related findings that share a root cause
|
|
8
|
+
2. Prioritizing findings by real-world impact (1-10 scale, 10 = most critical)
|
|
9
|
+
3. Suggesting concrete fixes for the highest-priority findings
|
|
10
|
+
4. Identifying likely false positives
|
|
11
|
+
|
|
12
|
+
Respond with ONLY valid JSON matching this schema:
|
|
13
|
+
{
|
|
14
|
+
"groups": [{ "groupId": "g1", "label": "Description of related issue", "findingIds": [1, 2] }],
|
|
15
|
+
"priorities": { "1": 8, "2": 6 },
|
|
16
|
+
"suggestions": { "1": "Use parameterized queries instead of string concatenation" },
|
|
17
|
+
"filtered": [{ "findingId": 3, "reason": "Test file, not production code" }]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Rules:
|
|
21
|
+
- Every finding must appear in exactly one group
|
|
22
|
+
- Priority scores: 10=critical security flaw, 7-9=high impact, 4-6=moderate, 1-3=low impact/noise
|
|
23
|
+
- Only suggest fixes for findings with priority >= 7
|
|
24
|
+
- Only filter findings you are >90% confident are false positives
|
|
25
|
+
- Keep suggestions concise (1-2 sentences)`;
|
|
26
|
+
/**
|
|
27
|
+
* Build the user prompt with serialized findings.
|
|
28
|
+
*/
|
|
29
|
+
export function buildEnhancePrompt(findings) {
|
|
30
|
+
const serialized = findings
|
|
31
|
+
.map((f) => `[${f.id}] ${f.severity} | ${f.source}/${f.category} | ${f.file}${f.line ? `:${f.line}` : ''} | ${f.message}`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
return `Analyze these ${findings.length} static analysis findings:\n\n${serialized}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Map full ReviewFindings to compact summaries with sequential IDs.
|
|
37
|
+
*/
|
|
38
|
+
export function serializeFindings(findings) {
|
|
39
|
+
return findings.map((f, index) => ({
|
|
40
|
+
id: index + 1,
|
|
41
|
+
file: f.file,
|
|
42
|
+
line: f.line,
|
|
43
|
+
severity: f.severity,
|
|
44
|
+
category: f.category ?? 'general',
|
|
45
|
+
message: f.message,
|
|
46
|
+
source: f.source ?? 'unknown',
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/** Severity priority order for truncation (drop lowest first). */
|
|
50
|
+
const SEVERITY_ORDER = {
|
|
51
|
+
info: 0,
|
|
52
|
+
low: 1,
|
|
53
|
+
medium: 2,
|
|
54
|
+
high: 3,
|
|
55
|
+
critical: 4,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Truncate findings to fit within a token budget.
|
|
59
|
+
* Drops lowest-severity findings first.
|
|
60
|
+
* Rough estimate: ~20 tokens per finding.
|
|
61
|
+
*/
|
|
62
|
+
export function truncateByTokenBudget(summaries, maxTokens) {
|
|
63
|
+
const tokensPerFinding = 20;
|
|
64
|
+
const maxFindings = Math.floor(maxTokens / tokensPerFinding);
|
|
65
|
+
if (summaries.length <= maxFindings)
|
|
66
|
+
return summaries;
|
|
67
|
+
// Sort by severity descending (keep highest severity)
|
|
68
|
+
const sorted = [...summaries].sort((a, b) => (SEVERITY_ORDER[b.severity] ?? 0) - (SEVERITY_ORDER[a.severity] ?? 0));
|
|
69
|
+
return sorted.slice(0, maxFindings);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/enhance/prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,6CAA6C;AAC7C,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;2CAoBM,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAiC;IAClE,MAAM,UAAU,GAAG,QAAQ;SACxB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAChH;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO,iBAAiB,QAAQ,CAAC,MAAM,iCAAiC,UAAU,EAAE,CAAC;AACvF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACjC,EAAE,EAAE,KAAK,GAAG,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,SAAS;QACjC,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;KAC9B,CAAC,CAAC,CAAC;AACN,CAAC;AAED,kEAAkE;AAClE,MAAM,cAAc,GAA2B;IAC7C,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAAkC,EAClC,SAAiB;IAEjB,MAAM,gBAAgB,GAAG,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,gBAAgB,CAAC,CAAC;IAE7D,IAAI,SAAS,CAAC,MAAM,IAAI,WAAW;QAAE,OAAO,SAAS,CAAC;IAEtD,sDAAsD;IACtD,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAChF,CAAC;IAEF,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Enhance types — used by the enhance module to group,
|
|
3
|
+
* prioritize, and filter static analysis findings.
|
|
4
|
+
*/
|
|
5
|
+
import type { FindingSeverity } from '../types.js';
|
|
6
|
+
/** Input for the enhance function. */
|
|
7
|
+
export interface EnhanceInput {
|
|
8
|
+
findings: EnhanceFindingSummary[];
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
}
|
|
13
|
+
/** Compact finding representation for the AI prompt (token-efficient). */
|
|
14
|
+
export interface EnhanceFindingSummary {
|
|
15
|
+
id: number;
|
|
16
|
+
file: string;
|
|
17
|
+
line?: number;
|
|
18
|
+
severity: FindingSeverity;
|
|
19
|
+
category: string;
|
|
20
|
+
message: string;
|
|
21
|
+
source: string;
|
|
22
|
+
}
|
|
23
|
+
/** Result from the AI enhance pass. */
|
|
24
|
+
export interface EnhanceResult {
|
|
25
|
+
/** Grouped findings by root cause. */
|
|
26
|
+
groups: FindingGroup[];
|
|
27
|
+
/** AI-assigned priority scores (findingId → 1-10). */
|
|
28
|
+
priorities: Record<number, number>;
|
|
29
|
+
/** Fix suggestions for top findings (findingId → suggestion text). */
|
|
30
|
+
suggestions: Record<number, string>;
|
|
31
|
+
/** Findings flagged as likely false positives. */
|
|
32
|
+
filtered: FilteredFinding[];
|
|
33
|
+
}
|
|
34
|
+
/** A group of related findings. */
|
|
35
|
+
export interface FindingGroup {
|
|
36
|
+
groupId: string;
|
|
37
|
+
label: string;
|
|
38
|
+
findingIds: number[];
|
|
39
|
+
}
|
|
40
|
+
/** A finding flagged as a likely false positive. */
|
|
41
|
+
export interface FilteredFinding {
|
|
42
|
+
findingId: number;
|
|
43
|
+
reason: string;
|
|
44
|
+
}
|
|
45
|
+
/** Metadata from the enhance pass. */
|
|
46
|
+
export interface EnhanceMetadata {
|
|
47
|
+
model: string;
|
|
48
|
+
tokenUsage: {
|
|
49
|
+
input: number;
|
|
50
|
+
output: number;
|
|
51
|
+
};
|
|
52
|
+
groupCount: number;
|
|
53
|
+
filteredCount: number;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/enhance/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,kDAAkD;IAClD,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,mCAAmC;AACnC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/enhance/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateRecommendations, type HealthRecommendation, } from './recommendations.js';
|
|
2
|
+
export { computeHealthScore, formatTopIssues, getScoreColor, type HealthScore, SEVERITY_WEIGHTS, } from './score.js';
|
|
3
|
+
export { computeTrend, type HealthHistoryEntry, type HealthTrend, loadHistory, saveHistory, } from './trends.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/health/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,KAAK,WAAW,EAChB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateRecommendations, } from './recommendations.js';
|
|
2
|
+
export { computeHealthScore, formatTopIssues, getScoreColor, SEVERITY_WEIGHTS, } from './score.js';
|
|
3
|
+
export { computeTrend, loadHistory, saveHistory, } from './trends.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/health/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,GAExB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,aAAa,EAEb,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EAGZ,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health recommendations — actionable advice from findings.
|
|
3
|
+
*/
|
|
4
|
+
import type { ReviewFinding } from '../types.js';
|
|
5
|
+
/** A single recommendation. */
|
|
6
|
+
export interface HealthRecommendation {
|
|
7
|
+
category: string;
|
|
8
|
+
action: string;
|
|
9
|
+
impact: 'high' | 'medium' | 'low';
|
|
10
|
+
findingCount: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate actionable recommendations from findings.
|
|
14
|
+
* Groups by category, sorts by impact, returns top N.
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateRecommendations(findings: ReviewFinding[], limit?: number): HealthRecommendation[];
|
|
17
|
+
//# sourceMappingURL=recommendations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommendations.d.ts","sourceRoot":"","sources":["../../src/health/recommendations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,+BAA+B;AAC/B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC;CACtB;AA8CD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,aAAa,EAAE,EACzB,KAAK,SAAI,GACR,oBAAoB,EAAE,CAuCxB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health recommendations — actionable advice from findings.
|
|
3
|
+
*/
|
|
4
|
+
/** Map finding source -> health category. */
|
|
5
|
+
const SOURCE_CATEGORY = {
|
|
6
|
+
semgrep: 'security',
|
|
7
|
+
trivy: 'dependencies',
|
|
8
|
+
gitleaks: 'secrets',
|
|
9
|
+
shellcheck: 'scripts',
|
|
10
|
+
lizard: 'complexity',
|
|
11
|
+
cpd: 'duplication',
|
|
12
|
+
ruff: 'quality',
|
|
13
|
+
eslint: 'quality',
|
|
14
|
+
hadolint: 'containers',
|
|
15
|
+
bandit: 'security',
|
|
16
|
+
phpstan: 'quality',
|
|
17
|
+
checkstyle: 'quality',
|
|
18
|
+
detekt: 'quality',
|
|
19
|
+
clippy: 'quality',
|
|
20
|
+
};
|
|
21
|
+
/** Recommendation templates per category. */
|
|
22
|
+
const TEMPLATES = {
|
|
23
|
+
security: 'Review and fix {count} security finding(s). Run static analysis regularly to catch vulnerabilities early.',
|
|
24
|
+
dependencies: 'Address {count} dependency issue(s). Run `npm audit fix` or update vulnerable packages.',
|
|
25
|
+
secrets: 'Remove {count} exposed secret(s) immediately! Rotate any compromised credentials.',
|
|
26
|
+
scripts: 'Fix {count} shell script issue(s). Consider using ShellCheck in your CI pipeline.',
|
|
27
|
+
complexity: 'Refactor {count} overly complex function(s). Break large functions into smaller units.',
|
|
28
|
+
duplication: 'Reduce {count} instance(s) of code duplication. Extract shared logic into reusable functions.',
|
|
29
|
+
quality: 'Address {count} code quality issue(s). Configure linters in CI to prevent regressions.',
|
|
30
|
+
containers: 'Fix {count} Dockerfile issue(s). Follow Dockerfile best practices for smaller, more secure images.',
|
|
31
|
+
};
|
|
32
|
+
/** Severity weights for impact ranking. */
|
|
33
|
+
const IMPACT_WEIGHTS = {
|
|
34
|
+
critical: 20,
|
|
35
|
+
high: 10,
|
|
36
|
+
medium: 3,
|
|
37
|
+
low: 1,
|
|
38
|
+
info: 0,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Generate actionable recommendations from findings.
|
|
42
|
+
* Groups by category, sorts by impact, returns top N.
|
|
43
|
+
*/
|
|
44
|
+
export function generateRecommendations(findings, limit = 5) {
|
|
45
|
+
if (findings.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
// Group findings by category
|
|
48
|
+
const categories = new Map();
|
|
49
|
+
for (const finding of findings) {
|
|
50
|
+
const category = SOURCE_CATEGORY[finding.source] ?? 'quality';
|
|
51
|
+
if (!categories.has(category)) {
|
|
52
|
+
categories.set(category, { findings: [], impact: 0 });
|
|
53
|
+
}
|
|
54
|
+
const cat = categories.get(category);
|
|
55
|
+
cat.findings.push(finding);
|
|
56
|
+
cat.impact += IMPACT_WEIGHTS[finding.severity] ?? 0;
|
|
57
|
+
}
|
|
58
|
+
// Filter out categories with only info findings (zero impact)
|
|
59
|
+
const meaningful = Array.from(categories.entries()).filter(([, data]) => data.impact > 0);
|
|
60
|
+
// Sort by impact (highest first)
|
|
61
|
+
meaningful.sort((a, b) => b[1].impact - a[1].impact);
|
|
62
|
+
// Generate recommendations
|
|
63
|
+
return meaningful.slice(0, limit).map(([category, data]) => {
|
|
64
|
+
const template = TEMPLATES[category] ?? `Address {count} ${category} issue(s).`;
|
|
65
|
+
const action = template.replace('{count}', String(data.findings.length));
|
|
66
|
+
let impact;
|
|
67
|
+
if (data.impact >= 20)
|
|
68
|
+
impact = 'high';
|
|
69
|
+
else if (data.impact >= 5)
|
|
70
|
+
impact = 'medium';
|
|
71
|
+
else
|
|
72
|
+
impact = 'low';
|
|
73
|
+
return {
|
|
74
|
+
category,
|
|
75
|
+
action,
|
|
76
|
+
impact,
|
|
77
|
+
findingCount: data.findings.length,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=recommendations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommendations.js","sourceRoot":"","sources":["../../src/health/recommendations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health scoring — deterministic score from static analysis findings.
|
|
3
|
+
*/
|
|
4
|
+
import type { FindingSeverity, ReviewFinding } from '../types.js';
|
|
5
|
+
/** Severity weights for health score computation. */
|
|
6
|
+
export declare const SEVERITY_WEIGHTS: Record<FindingSeverity, number>;
|
|
7
|
+
/** Health score result. */
|
|
8
|
+
export interface HealthScore {
|
|
9
|
+
/** Score 0-100 (higher is better). */
|
|
10
|
+
score: number;
|
|
11
|
+
/** Letter grade: A (80+), B (60-79), C (40-59), D (20-39), F (0-19). */
|
|
12
|
+
grade: string;
|
|
13
|
+
/** Finding counts per severity level. */
|
|
14
|
+
findingCounts: Record<FindingSeverity, number>;
|
|
15
|
+
/** Total number of findings. */
|
|
16
|
+
totalFindings: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compute health score from findings.
|
|
20
|
+
* Base score = 100. Each finding subtracts its severity weight.
|
|
21
|
+
* Result clamped to [0, 100].
|
|
22
|
+
*/
|
|
23
|
+
export declare function computeHealthScore(findings: ReviewFinding[]): HealthScore;
|
|
24
|
+
/**
|
|
25
|
+
* Get color category for a health score.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getScoreColor(score: number): 'green' | 'yellow' | 'red';
|
|
28
|
+
/**
|
|
29
|
+
* Get top N issues sorted by severity (critical first).
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatTopIssues(findings: ReviewFinding[], limit: number): ReviewFinding[];
|
|
32
|
+
//# sourceMappingURL=score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/health/score.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAElE,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAM5D,CAAC;AAEF,2BAA2B;AAC3B,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC/C,gCAAgC;IAChC,aAAa,EAAE,MAAM,CAAC;CACvB;AAWD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,WAAW,CAwBzE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAIvE;AAWD;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAIzF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health scoring — deterministic score from static analysis findings.
|
|
3
|
+
*/
|
|
4
|
+
/** Severity weights for health score computation. */
|
|
5
|
+
export const SEVERITY_WEIGHTS = {
|
|
6
|
+
critical: -20,
|
|
7
|
+
high: -10,
|
|
8
|
+
medium: -3,
|
|
9
|
+
low: -1,
|
|
10
|
+
info: 0,
|
|
11
|
+
};
|
|
12
|
+
/** Grade thresholds. */
|
|
13
|
+
const GRADES = [
|
|
14
|
+
[80, 'A'],
|
|
15
|
+
[60, 'B'],
|
|
16
|
+
[40, 'C'],
|
|
17
|
+
[20, 'D'],
|
|
18
|
+
[0, 'F'],
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Compute health score from findings.
|
|
22
|
+
* Base score = 100. Each finding subtracts its severity weight.
|
|
23
|
+
* Result clamped to [0, 100].
|
|
24
|
+
*/
|
|
25
|
+
export function computeHealthScore(findings) {
|
|
26
|
+
const findingCounts = {
|
|
27
|
+
critical: 0,
|
|
28
|
+
high: 0,
|
|
29
|
+
medium: 0,
|
|
30
|
+
low: 0,
|
|
31
|
+
info: 0,
|
|
32
|
+
};
|
|
33
|
+
let deductions = 0;
|
|
34
|
+
for (const finding of findings) {
|
|
35
|
+
findingCounts[finding.severity] = (findingCounts[finding.severity] ?? 0) + 1;
|
|
36
|
+
deductions += Math.abs(SEVERITY_WEIGHTS[finding.severity] ?? 0);
|
|
37
|
+
}
|
|
38
|
+
const score = Math.max(0, Math.min(100, 100 - deductions));
|
|
39
|
+
const grade = GRADES.find(([threshold]) => score >= threshold)?.[1] ?? 'F';
|
|
40
|
+
return {
|
|
41
|
+
score,
|
|
42
|
+
grade,
|
|
43
|
+
findingCounts,
|
|
44
|
+
totalFindings: findings.length,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get color category for a health score.
|
|
49
|
+
*/
|
|
50
|
+
export function getScoreColor(score) {
|
|
51
|
+
if (score >= 80)
|
|
52
|
+
return 'green';
|
|
53
|
+
if (score >= 50)
|
|
54
|
+
return 'yellow';
|
|
55
|
+
return 'red';
|
|
56
|
+
}
|
|
57
|
+
/** Severity sort order (critical first). */
|
|
58
|
+
const SEVERITY_ORDER = {
|
|
59
|
+
critical: 0,
|
|
60
|
+
high: 1,
|
|
61
|
+
medium: 2,
|
|
62
|
+
low: 3,
|
|
63
|
+
info: 4,
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Get top N issues sorted by severity (critical first).
|
|
67
|
+
*/
|
|
68
|
+
export function formatTopIssues(findings, limit) {
|
|
69
|
+
return [...findings]
|
|
70
|
+
.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 5) - (SEVERITY_ORDER[b.severity] ?? 5))
|
|
71
|
+
.slice(0, limit);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/health/score.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,qDAAqD;AACrD,MAAM,CAAC,MAAM,gBAAgB,GAAoC;IAC/D,QAAQ,EAAE,CAAC,EAAE;IACb,IAAI,EAAE,CAAC,EAAE;IACT,MAAM,EAAE,CAAC,CAAC;IACV,GAAG,EAAE,CAAC,CAAC;IACP,IAAI,EAAE,CAAC;CACR,CAAC;AAcF,wBAAwB;AACxB,MAAM,MAAM,GAAuB;IACjC,CAAC,EAAE,EAAE,GAAG,CAAC;IACT,CAAC,EAAE,EAAE,GAAG,CAAC;IACT,CAAC,EAAE,EAAE,GAAG,CAAC;IACT,CAAC,EAAE,EAAE,GAAG,CAAC;IACT,CAAC,CAAC,EAAE,GAAG,CAAC;CACT,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAyB;IAC1D,MAAM,aAAa,GAAoC;QACrD,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;KACR,CAAC;IAEF,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7E,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAE3E,OAAO;QACL,KAAK;QACL,KAAK;QACL,aAAa;QACb,aAAa,EAAE,QAAQ,CAAC,MAAM;KAC/B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC;IAChC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,QAAQ,CAAC;IACjC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4CAA4C;AAC5C,MAAM,cAAc,GAA2B;IAC7C,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACR,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAyB,EAAE,KAAa;IACtE,OAAO,CAAC,GAAG,QAAQ,CAAC;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;SACrF,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrB,CAAC"}
|