@salesforce/afv-skills 1.10.0 → 1.12.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/package.json +1 -1
- package/skills/applying-cms-brand/SKILL.md +170 -0
- package/skills/implementing-ui-bundle-agentforce-conversation-client/SKILL.md +114 -198
- package/skills/implementing-ui-bundle-agentforce-conversation-client/references/agent-id-resolution.md +46 -0
- package/skills/implementing-ui-bundle-agentforce-conversation-client/references/style-tokens.md +18 -6
- package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +166 -0
- package/skills/running-code-analyzer/SKILL.md +499 -0
- package/skills/running-code-analyzer/examples/README.md +38 -0
- package/skills/running-code-analyzer/examples/basic-scan-output.json +92 -0
- package/skills/running-code-analyzer/examples/command-variations.md +333 -0
- package/skills/running-code-analyzer/examples/fix-application-before-after.md +142 -0
- package/skills/running-code-analyzer/examples/large-scan-output.json +67 -0
- package/skills/running-code-analyzer/examples/security-focused-output.json +95 -0
- package/skills/running-code-analyzer/references/command-examples.md +27 -0
- package/skills/running-code-analyzer/references/engine-reference.md +34 -0
- package/skills/running-code-analyzer/references/error-handling.md +29 -0
- package/skills/running-code-analyzer/references/flag-reference.md +96 -0
- package/skills/running-code-analyzer/references/quick-start.md +28 -0
- package/skills/running-code-analyzer/references/special-behaviors.md +83 -0
- package/skills/running-code-analyzer/references/vendor-file-handling.md +239 -0
- package/skills/running-code-analyzer/scripts/apply-fixes.js +86 -0
- package/skills/running-code-analyzer/scripts/discover-fixes.js +34 -0
- package/skills/running-code-analyzer/scripts/filter-violations.js +405 -0
- package/skills/running-code-analyzer/scripts/parse-results.js +59 -0
- package/skills/running-code-analyzer/scripts/summarize-fixes.js +32 -0
- package/skills/running-code-analyzer/scripts/verify-execution.sh +28 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Intelligent vendor file detection for Code Analyzer results
|
|
4
|
+
* Uses multiple heuristics to classify files as vendor vs project code
|
|
5
|
+
*
|
|
6
|
+
* Usage: node filter-violations.js <input.json> <output.json> [--report]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
class VendorDetector {
|
|
13
|
+
constructor(projectRoot) {
|
|
14
|
+
this.projectRoot = projectRoot;
|
|
15
|
+
this.packageNames = this.loadPackageNames();
|
|
16
|
+
this.fileContentCache = new Map();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load known third-party package names from package.json
|
|
21
|
+
*/
|
|
22
|
+
loadPackageNames() {
|
|
23
|
+
const names = new Set();
|
|
24
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
29
|
+
Object.keys(pkg.dependencies || {}).forEach(d => names.add(d.toLowerCase()));
|
|
30
|
+
Object.keys(pkg.devDependencies || {}).forEach(d => names.add(d.toLowerCase()));
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Ignore parsing errors
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return names;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Main classification method - returns classification with confidence score
|
|
41
|
+
*/
|
|
42
|
+
classifyFile(filePath) {
|
|
43
|
+
const scores = {
|
|
44
|
+
pathBased: this.scoreByPath(filePath),
|
|
45
|
+
nameBased: this.scoreByName(filePath),
|
|
46
|
+
contentBased: this.scoreByContent(filePath),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Weighted average (content analysis is more reliable when available)
|
|
50
|
+
const weights = {
|
|
51
|
+
pathBased: 0.3,
|
|
52
|
+
nameBased: 0.3,
|
|
53
|
+
contentBased: 0.4,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const weightedScore = Object.entries(scores).reduce(
|
|
57
|
+
(sum, [key, score]) => sum + score * weights[key],
|
|
58
|
+
0
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const reasons = this.explainScores(scores, filePath);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
isVendor: weightedScore > 50,
|
|
65
|
+
confidence: weightedScore,
|
|
66
|
+
scores,
|
|
67
|
+
reasons,
|
|
68
|
+
classification: this.getClassificationLabel(weightedScore),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Score based on directory/path patterns (0-100)
|
|
74
|
+
*/
|
|
75
|
+
scoreByPath(filePath) {
|
|
76
|
+
let score = 0;
|
|
77
|
+
const normalizedPath = filePath.toLowerCase();
|
|
78
|
+
|
|
79
|
+
// Absolute vendor indicators
|
|
80
|
+
if (/node_modules/.test(normalizedPath)) return 100;
|
|
81
|
+
if (/bower_components/.test(normalizedPath)) return 100;
|
|
82
|
+
if (/vendor\//.test(normalizedPath)) return 95;
|
|
83
|
+
if (/third[_-]party/.test(normalizedPath)) return 95;
|
|
84
|
+
|
|
85
|
+
// Common vendor directory names
|
|
86
|
+
if (/\/lib\//i.test(normalizedPath)) score += 60;
|
|
87
|
+
if (/StaticResourceSources/i.test(normalizedPath)) score += 70;
|
|
88
|
+
if (/CumulusStaticResources/i.test(normalizedPath)) score += 70;
|
|
89
|
+
|
|
90
|
+
// Salesforce-specific: project source is typically in aura/ or lwc/
|
|
91
|
+
if (/force-app\/main\/default\/(aura|lwc)\/[^/]+\//.test(filePath)) {
|
|
92
|
+
// In an Aura or LWC component directory - likely project code
|
|
93
|
+
return Math.min(score, 20); // Cap score at 20 for project paths
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Outside of component directories but in staticresources
|
|
97
|
+
if (/staticresources/.test(normalizedPath)) score += 50;
|
|
98
|
+
|
|
99
|
+
return Math.min(score, 100);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Score based on filename patterns (0-100)
|
|
104
|
+
*/
|
|
105
|
+
scoreByName(filePath) {
|
|
106
|
+
let score = 0;
|
|
107
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
108
|
+
|
|
109
|
+
// Minified files are almost always vendor code
|
|
110
|
+
if (/\.min\.js$/.test(basename)) return 95;
|
|
111
|
+
if (/\.bundle\.js$/.test(basename)) score += 80;
|
|
112
|
+
if (/-min\.js$/.test(basename)) return 95;
|
|
113
|
+
|
|
114
|
+
// Version numbers in filename (e.g., jquery-1.12.1.js)
|
|
115
|
+
if (/-\d+\.\d+(\.\d+)?\.js$/.test(basename)) score += 85;
|
|
116
|
+
if (/\d+\.\d+\.\d+/.test(basename)) score += 70;
|
|
117
|
+
|
|
118
|
+
// Known library name patterns
|
|
119
|
+
const knownLibs = [
|
|
120
|
+
'jquery', 'lodash', 'underscore', 'moment', 'angular', 'react', 'vue',
|
|
121
|
+
'bootstrap', 'foundation', 'handlebars', 'backbone', 'ember', 'knockout',
|
|
122
|
+
'typeahead', 'select2', 'datatables', 'chart', 'arbor', 'd3', 'raphael',
|
|
123
|
+
'leaflet', 'mapbox', 'three', 'pixi', 'phaser', 'babylonjs',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const lib of knownLibs) {
|
|
127
|
+
if (new RegExp(`\\b${lib}\\b`, 'i').test(basename)) {
|
|
128
|
+
score += 85;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check against package.json dependencies
|
|
134
|
+
for (const pkgName of this.packageNames) {
|
|
135
|
+
if (basename.includes(pkgName)) {
|
|
136
|
+
score += 80;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return Math.min(score, 100);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Score based on file content analysis (0-100)
|
|
146
|
+
*/
|
|
147
|
+
scoreByContent(filePath) {
|
|
148
|
+
try {
|
|
149
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
150
|
+
? filePath
|
|
151
|
+
: path.join(this.projectRoot, filePath);
|
|
152
|
+
|
|
153
|
+
if (!fs.existsSync(absolutePath)) {
|
|
154
|
+
return 0; // Can't score if file doesn't exist
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
158
|
+
let score = 0;
|
|
159
|
+
|
|
160
|
+
// Check first 2000 characters for header information
|
|
161
|
+
const header = content.slice(0, 2000);
|
|
162
|
+
|
|
163
|
+
// License headers strongly indicate vendor code
|
|
164
|
+
if (/\b(MIT License|Apache License|BSD License|GPL|ISC License|Mozilla Public License)\b/i.test(header)) {
|
|
165
|
+
score += 80;
|
|
166
|
+
}
|
|
167
|
+
if (/@license\b/i.test(header)) score += 75;
|
|
168
|
+
|
|
169
|
+
// Copyright notices (but not from the current org)
|
|
170
|
+
if (/@copyright\b(?!.*salesforce\.com)/i.test(header)) score += 60;
|
|
171
|
+
|
|
172
|
+
// Version stamps
|
|
173
|
+
if (/@version\s+\d+\.\d+\.\d+/.test(header)) score += 65;
|
|
174
|
+
if (/\bv\d+\.\d+\.\d+\b/.test(header)) score += 55;
|
|
175
|
+
|
|
176
|
+
// Author field that's not project-specific
|
|
177
|
+
if (/@author\b/i.test(header) && !/salesforce/i.test(header)) score += 50;
|
|
178
|
+
|
|
179
|
+
// Check for minification indicators
|
|
180
|
+
const lines = content.split('\n');
|
|
181
|
+
const avgLineLength = content.length / lines.length;
|
|
182
|
+
|
|
183
|
+
if (avgLineLength > 500) score += 90; // Extremely long lines = minified
|
|
184
|
+
if (avgLineLength > 200) score += 70;
|
|
185
|
+
if (lines.length < 10 && content.length > 5000) score += 85; // Very dense
|
|
186
|
+
|
|
187
|
+
// UMD/AMD/CommonJS wrapper patterns (common in libraries)
|
|
188
|
+
if (/\(function\s*\([^)]*\)\s*\{[\s\S]{0,200}(typeof\s+define|typeof\s+module|typeof\s+exports)/.test(header)) {
|
|
189
|
+
score += 70;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// IIFE wrapping entire file (very common in libraries)
|
|
193
|
+
const trimmed = content.trim();
|
|
194
|
+
if (/^\(function\s*\(/.test(trimmed) && /\}\s*\)\s*\([^)]*\)\s*;?\s*$/.test(trimmed)) {
|
|
195
|
+
score += 60;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Banner comments with project URLs
|
|
199
|
+
if (/\bhttps?:\/\/(github\.com|npmjs\.com|unpkg\.com|cdnjs\.com)/i.test(header)) {
|
|
200
|
+
score += 75;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return Math.min(score, 100);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
// If we can't read the file, don't penalize it
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate human-readable reasons for the classification
|
|
212
|
+
*/
|
|
213
|
+
explainScores(scores, filePath) {
|
|
214
|
+
const reasons = [];
|
|
215
|
+
const basename = path.basename(filePath);
|
|
216
|
+
|
|
217
|
+
if (scores.pathBased > 70) {
|
|
218
|
+
reasons.push('located in vendor directory');
|
|
219
|
+
}
|
|
220
|
+
if (scores.nameBased > 70) {
|
|
221
|
+
if (/\.min\.js$/.test(basename)) {
|
|
222
|
+
reasons.push('minified file (.min.js)');
|
|
223
|
+
} else if (/\d+\.\d+\.\d+/.test(basename)) {
|
|
224
|
+
reasons.push('version number in filename');
|
|
225
|
+
} else {
|
|
226
|
+
reasons.push('matches known library name');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (scores.contentBased > 70) {
|
|
230
|
+
reasons.push('contains vendor markers (license, version, minification)');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (reasons.length === 0) {
|
|
234
|
+
if (scores.pathBased < 30 && scores.nameBased < 30) {
|
|
235
|
+
reasons.push('appears to be project source code');
|
|
236
|
+
} else {
|
|
237
|
+
reasons.push('unclear classification');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return reasons;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get classification label based on confidence score
|
|
246
|
+
*/
|
|
247
|
+
getClassificationLabel(score) {
|
|
248
|
+
if (score > 70) return 'vendor';
|
|
249
|
+
if (score < 30) return 'project';
|
|
250
|
+
return 'uncertain';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Filter violations from Code Analyzer results
|
|
256
|
+
*/
|
|
257
|
+
function filterViolations(inputFile, outputFile, options = {}) {
|
|
258
|
+
const results = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
|
259
|
+
const projectRoot = results.runDir || process.cwd();
|
|
260
|
+
|
|
261
|
+
const detector = new VendorDetector(projectRoot);
|
|
262
|
+
|
|
263
|
+
// Group violations by file
|
|
264
|
+
const fileViolations = {};
|
|
265
|
+
for (const v of results.violations) {
|
|
266
|
+
const file = v.locations[v.primaryLocationIndex].file;
|
|
267
|
+
if (!fileViolations[file]) {
|
|
268
|
+
fileViolations[file] = {
|
|
269
|
+
violations: [],
|
|
270
|
+
classification: null,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
fileViolations[file].violations.push(v);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Classify each file
|
|
277
|
+
const analysis = {
|
|
278
|
+
vendor: [],
|
|
279
|
+
project: [],
|
|
280
|
+
uncertain: [],
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
for (const [file, data] of Object.entries(fileViolations)) {
|
|
284
|
+
const classification = detector.classifyFile(file);
|
|
285
|
+
data.classification = classification;
|
|
286
|
+
|
|
287
|
+
const entry = {
|
|
288
|
+
file,
|
|
289
|
+
violationCount: data.violations.length,
|
|
290
|
+
...classification,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
if (classification.classification === 'vendor') {
|
|
294
|
+
analysis.vendor.push(entry);
|
|
295
|
+
} else if (classification.classification === 'project') {
|
|
296
|
+
analysis.project.push(entry);
|
|
297
|
+
} else {
|
|
298
|
+
analysis.uncertain.push(entry);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Filter violations to project files only
|
|
303
|
+
const projectFiles = new Set(analysis.project.map(e => e.file));
|
|
304
|
+
const filteredViolations = results.violations.filter(v => {
|
|
305
|
+
const file = v.locations[v.primaryLocationIndex].file;
|
|
306
|
+
return projectFiles.has(file);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Recalculate severity counts
|
|
310
|
+
const severityCounts = { sev1: 0, sev2: 0, sev3: 0, sev4: 0, sev5: 0 };
|
|
311
|
+
for (const v of filteredViolations) {
|
|
312
|
+
const sev = `sev${v.severity}`;
|
|
313
|
+
if (severityCounts[sev] !== undefined) {
|
|
314
|
+
severityCounts[sev]++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Create filtered results
|
|
319
|
+
const filteredResults = {
|
|
320
|
+
...results,
|
|
321
|
+
violations: filteredViolations,
|
|
322
|
+
violationCounts: {
|
|
323
|
+
total: filteredViolations.length,
|
|
324
|
+
...severityCounts,
|
|
325
|
+
},
|
|
326
|
+
filterMetadata: {
|
|
327
|
+
filteredAt: new Date().toISOString(),
|
|
328
|
+
originalViolations: results.violations.length,
|
|
329
|
+
filteredViolations: filteredViolations.length,
|
|
330
|
+
vendorFilesExcluded: analysis.vendor.length,
|
|
331
|
+
projectFilesIncluded: analysis.project.length,
|
|
332
|
+
uncertainFiles: analysis.uncertain.length,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
fs.writeFileSync(outputFile, JSON.stringify(filteredResults, null, 2));
|
|
337
|
+
|
|
338
|
+
// Print summary
|
|
339
|
+
printSummary(results, filteredResults, analysis, options);
|
|
340
|
+
|
|
341
|
+
return filteredResults;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Print detailed summary report
|
|
346
|
+
*/
|
|
347
|
+
function printSummary(original, filtered, analysis, options) {
|
|
348
|
+
console.log('\n=== INTELLIGENT VENDOR FILE DETECTION ===\n');
|
|
349
|
+
console.log(`Original violations: ${original.violations.length}`);
|
|
350
|
+
console.log(`Filtered violations: ${filtered.violations.length}`);
|
|
351
|
+
console.log(`Reduction: ${original.violations.length - filtered.violations.length} (${((1 - filtered.violations.length / original.violations.length) * 100).toFixed(1)}%)\n`);
|
|
352
|
+
|
|
353
|
+
console.log(`📦 Vendor files excluded: ${analysis.vendor.length}`);
|
|
354
|
+
if (analysis.vendor.length > 0 && options.report) {
|
|
355
|
+
const top = analysis.vendor.sort((a, b) => b.violationCount - a.violationCount).slice(0, 10);
|
|
356
|
+
top.forEach(e => {
|
|
357
|
+
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.confidence.toFixed(0)}% confidence | ${e.file}`);
|
|
358
|
+
console.log(` ${e.reasons.join(', ')}`);
|
|
359
|
+
});
|
|
360
|
+
if (analysis.vendor.length > 10) {
|
|
361
|
+
console.log(` ... and ${analysis.vendor.length - 10} more vendor files`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log(`\n✅ Project files included: ${analysis.project.length}`);
|
|
366
|
+
if (analysis.project.length > 0 && options.report) {
|
|
367
|
+
const top = analysis.project.sort((a, b) => b.violationCount - a.violationCount).slice(0, 10);
|
|
368
|
+
top.forEach(e => {
|
|
369
|
+
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.file}`);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (analysis.uncertain.length > 0) {
|
|
374
|
+
console.log(`\n⚠️ Uncertain files: ${analysis.uncertain.length}`);
|
|
375
|
+
console.log(' These files have 30-70% vendor confidence - review manually:');
|
|
376
|
+
analysis.uncertain.forEach(e => {
|
|
377
|
+
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.confidence.toFixed(0)}% vendor | ${e.file}`);
|
|
378
|
+
console.log(` ${e.reasons.join(', ')}`);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log(`\n✓ Filtered results written to: ${outputFile}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// CLI
|
|
386
|
+
const args = process.argv.slice(2);
|
|
387
|
+
if (args.length < 2) {
|
|
388
|
+
console.error('Usage: node filter-violations.js <input.json> <output.json> [--report]');
|
|
389
|
+
console.error('');
|
|
390
|
+
console.error('Options:');
|
|
391
|
+
console.error(' --report Show detailed file-by-file analysis');
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const [inputFile, outputFile] = args;
|
|
396
|
+
const options = {
|
|
397
|
+
report: args.includes('--report'),
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
filterViolations(inputFile, outputFile, options);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
console.error('Error:', err.message);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Version: v1.0 | SHA256: 077933925fea8efb4bcfd2c2fd59d4589a0b31ae34d5c1ac68c2080c8af7d74d
|
|
3
|
+
// Parse Code Analyzer JSON results and extract summary data
|
|
4
|
+
// Usage: node parse-results.js <path-to-results.json>
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
if (process.argv.length < 3) {
|
|
9
|
+
console.error("Usage: node parse-results.js <results-file.json>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const filePath = process.argv[2];
|
|
14
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
15
|
+
const c = data.violationCounts;
|
|
16
|
+
const runDir = data.runDir || "";
|
|
17
|
+
|
|
18
|
+
// Summary counts
|
|
19
|
+
const summary = {
|
|
20
|
+
total: c.total, sev1: c.sev1, sev2: c.sev2, sev3: c.sev3, sev4: c.sev4, sev5: c.sev5,
|
|
21
|
+
topViolations: [],
|
|
22
|
+
topRules: [],
|
|
23
|
+
topFiles: []
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Top 10 violations sorted by severity
|
|
27
|
+
const sorted = data.violations.slice().sort((a, b) => a.severity - b.severity || a.rule.localeCompare(b.rule));
|
|
28
|
+
sorted.slice(0, 10).forEach(v => {
|
|
29
|
+
const loc = v.locations && v.locations[0] || {};
|
|
30
|
+
let file = loc.file || "unknown";
|
|
31
|
+
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
|
|
32
|
+
file = file.split("/").pop();
|
|
33
|
+
summary.topViolations.push({ rule: v.rule, engine: v.engine, sev: v.severity, file: file, line: loc.startLine || 0 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Top 10 rules by frequency
|
|
37
|
+
const ruleCounts = {};
|
|
38
|
+
const ruleEngines = {};
|
|
39
|
+
data.violations.forEach(v => {
|
|
40
|
+
ruleCounts[v.rule] = (ruleCounts[v.rule] || 0) + 1;
|
|
41
|
+
if (!ruleEngines[v.rule]) ruleEngines[v.rule] = v.engine;
|
|
42
|
+
});
|
|
43
|
+
Object.entries(ruleCounts).sort((a, b) => b[1] - a[1]).slice(0, 10).forEach(([rule, count]) => {
|
|
44
|
+
summary.topRules.push({ rule, engine: ruleEngines[rule], count });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Top 5 files by violation count
|
|
48
|
+
const fileCounts = {};
|
|
49
|
+
data.violations.forEach(v => {
|
|
50
|
+
const loc = v.locations && v.locations[0] || {};
|
|
51
|
+
let file = loc.file || "unknown";
|
|
52
|
+
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
|
|
53
|
+
fileCounts[file] = (fileCounts[file] || 0) + 1;
|
|
54
|
+
});
|
|
55
|
+
Object.entries(fileCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).forEach(([file, count]) => {
|
|
56
|
+
summary.topFiles.push({ file, count });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(JSON.stringify(summary));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Summarize applied fixes by severity and rule
|
|
3
|
+
// Usage: node summarize-fixes.js <path-to-results.json>
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
if (process.argv.length < 3) {
|
|
8
|
+
console.error("Usage: node summarize-fixes.js <results-file.json>");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const filePath = process.argv[2];
|
|
13
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
14
|
+
|
|
15
|
+
const fixesByRule = {};
|
|
16
|
+
const fixesBySeverity = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
17
|
+
|
|
18
|
+
data.violations.forEach(v => {
|
|
19
|
+
if (v.fixes && v.fixes.length > 0) {
|
|
20
|
+
const fixCount = v.fixes.length;
|
|
21
|
+
fixesByRule[v.rule] = (fixesByRule[v.rule] || 0) + fixCount;
|
|
22
|
+
fixesBySeverity[v.severity] += fixCount;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const topRules = Object.entries(fixesByRule)
|
|
27
|
+
.sort((a, b) => b[1] - a[1])
|
|
28
|
+
.slice(0, 10)
|
|
29
|
+
.map(([rule, count]) => ({ rule, count }));
|
|
30
|
+
|
|
31
|
+
console.log(JSON.stringify({ fixesByRule: topRules, fixesBySeverity }));
|
|
32
|
+
console.error("SUCCESS: Summary script completed. Present results to user and offer re-scan (Step 6.7).");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Verification script to ensure scripts are executed from files, not inline
|
|
3
|
+
# Usage: source this at the start of SKILL.md execution
|
|
4
|
+
|
|
5
|
+
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
verify_script_execution() {
|
|
8
|
+
local script_name="$1"
|
|
9
|
+
local expected_path="${SKILL_DIR}/scripts/${script_name}"
|
|
10
|
+
|
|
11
|
+
if [[ ! -f "$expected_path" ]]; then
|
|
12
|
+
echo "❌ ERROR: Script file not found: $expected_path"
|
|
13
|
+
echo "This skill requires script files to be present in the deployment."
|
|
14
|
+
return 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check if script has expected header
|
|
18
|
+
if ! head -1 "$expected_path" | grep -q "#!/usr/bin/env node"; then
|
|
19
|
+
echo "⚠️ WARNING: Script missing proper header: $expected_path"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "✓ Script file verified: $script_name"
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Export function for use in skill execution
|
|
27
|
+
export -f verify_script_execution
|
|
28
|
+
export SKILL_DIR
|