@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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/skills/applying-cms-brand/SKILL.md +170 -0
  3. package/skills/implementing-ui-bundle-agentforce-conversation-client/SKILL.md +114 -198
  4. package/skills/implementing-ui-bundle-agentforce-conversation-client/references/agent-id-resolution.md +46 -0
  5. package/skills/implementing-ui-bundle-agentforce-conversation-client/references/style-tokens.md +18 -6
  6. package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +166 -0
  7. package/skills/running-code-analyzer/SKILL.md +499 -0
  8. package/skills/running-code-analyzer/examples/README.md +38 -0
  9. package/skills/running-code-analyzer/examples/basic-scan-output.json +92 -0
  10. package/skills/running-code-analyzer/examples/command-variations.md +333 -0
  11. package/skills/running-code-analyzer/examples/fix-application-before-after.md +142 -0
  12. package/skills/running-code-analyzer/examples/large-scan-output.json +67 -0
  13. package/skills/running-code-analyzer/examples/security-focused-output.json +95 -0
  14. package/skills/running-code-analyzer/references/command-examples.md +27 -0
  15. package/skills/running-code-analyzer/references/engine-reference.md +34 -0
  16. package/skills/running-code-analyzer/references/error-handling.md +29 -0
  17. package/skills/running-code-analyzer/references/flag-reference.md +96 -0
  18. package/skills/running-code-analyzer/references/quick-start.md +28 -0
  19. package/skills/running-code-analyzer/references/special-behaviors.md +83 -0
  20. package/skills/running-code-analyzer/references/vendor-file-handling.md +239 -0
  21. package/skills/running-code-analyzer/scripts/apply-fixes.js +86 -0
  22. package/skills/running-code-analyzer/scripts/discover-fixes.js +34 -0
  23. package/skills/running-code-analyzer/scripts/filter-violations.js +405 -0
  24. package/skills/running-code-analyzer/scripts/parse-results.js +59 -0
  25. package/skills/running-code-analyzer/scripts/summarize-fixes.js +32 -0
  26. 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