@salesforce/afv-skills 1.28.0 → 1.29.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 (31) hide show
  1. package/package.json +1 -1
  2. package/skills/dx-code-analyzer-configure/SKILL.md +31 -13
  3. package/skills/dx-code-analyzer-custom-rule-create/SKILL.md +484 -0
  4. package/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml +31 -0
  5. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md +87 -0
  6. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md +105 -0
  7. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md +95 -0
  8. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md +84 -0
  9. package/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md +127 -0
  10. package/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md +227 -0
  11. package/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md +288 -0
  12. package/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md +127 -0
  13. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md +247 -0
  14. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md +188 -0
  15. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md +114 -0
  16. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md +113 -0
  17. package/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md +285 -0
  18. package/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md +174 -0
  19. package/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md +141 -0
  20. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md +83 -0
  21. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md +108 -0
  22. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md +45 -0
  23. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md +127 -0
  24. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md +131 -0
  25. package/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js +209 -0
  26. package/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js +220 -0
  27. package/skills/dx-code-analyzer-run/SKILL.md +41 -8
  28. package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +3 -3
  29. package/skills/platform-custom-field-generate/SKILL.md +86 -126
  30. package/skills/platform-custom-field-generate/references/advanced-picklists.md +590 -0
  31. package/skills/platform-value-set-generate/SKILL.md +305 -0
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ // Creates a PMD XPath custom rule (XML ruleset file + config reference)
3
+ // Usage: node create-pmd-rule.js --name <name> --xpath <expression> --message <msg> [options]
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ function printUsage() {
9
+ console.error(`Usage: node create-pmd-rule.js --name <name> --xpath <expression> --message <msg> [options]
10
+
11
+ Required:
12
+ --name <name> Rule name (PascalCase, no spaces)
13
+ --xpath <expression> XPath expression to match violations
14
+ --message <msg> Violation message shown to users
15
+
16
+ Optional:
17
+ --description <desc> Detailed rule description (default: same as message)
18
+ --language <lang> PMD language (default: apex)
19
+ --priority <1-5> PMD priority (default: 3)
20
+ --example <code> Example violating code snippet
21
+ --config-file <path> Path to code-analyzer.yml (default: ./code-analyzer.yml)
22
+ --ruleset-dir <dir> Directory for ruleset XML (default: ./custom-rules)
23
+
24
+ Examples:
25
+ node create-pmd-rule.js --name NoSystemDebug --xpath "//MethodCallExpression[@FullMethodName='System.debug']" --message "System.debug not allowed" --priority 3
26
+ node create-pmd-rule.js --name SoqlInLoop --xpath "//ForEachStatement//SoqlExpression" --message "SOQL inside loop" --priority 2`);
27
+ process.exit(1);
28
+ }
29
+
30
+ // Parse arguments
31
+ const args = process.argv.slice(2);
32
+ if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
33
+ printUsage();
34
+ }
35
+
36
+ const options = {
37
+ name: null,
38
+ xpath: null,
39
+ message: null,
40
+ description: null,
41
+ language: "apex",
42
+ priority: 3,
43
+ example: null,
44
+ configFile: "./code-analyzer.yml",
45
+ rulesetDir: "./custom-rules",
46
+ };
47
+
48
+ for (let i = 0; i < args.length; i++) {
49
+ switch (args[i]) {
50
+ case "--name": options.name = args[++i]; break;
51
+ case "--xpath": options.xpath = args[++i]; break;
52
+ case "--message": options.message = args[++i]; break;
53
+ case "--description": options.description = args[++i]; break;
54
+ case "--language": options.language = args[++i]; break;
55
+ case "--priority": options.priority = parseInt(args[++i], 10); break;
56
+ case "--example": options.example = args[++i]; break;
57
+ case "--config-file": options.configFile = args[++i]; break;
58
+ case "--ruleset-dir": options.rulesetDir = args[++i]; break;
59
+ default:
60
+ console.error(`Unknown option: ${args[i]}`);
61
+ printUsage();
62
+ }
63
+ }
64
+
65
+ // Validate required fields
66
+ if (!options.name) { console.error("Error: --name is required"); process.exit(1); }
67
+ if (!options.xpath) { console.error("Error: --xpath is required"); process.exit(1); }
68
+ if (!options.message) { console.error("Error: --message is required"); process.exit(1); }
69
+
70
+ // Validate rule name
71
+ const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/;
72
+ if (!RULE_NAME_PATTERN.test(options.name)) {
73
+ console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ // Validate priority
78
+ if (options.priority < 1 || options.priority > 5) {
79
+ console.error("Error: Priority must be 1-5");
80
+ process.exit(1);
81
+ }
82
+
83
+ // Validate language
84
+ const VALID_LANGUAGES = ["apex", "visualforce", "html", "xml", "javascript"];
85
+ if (!VALID_LANGUAGES.includes(options.language.toLowerCase())) {
86
+ console.error(`Error: Invalid language "${options.language}". Supported: ${VALID_LANGUAGES.join(", ")}`);
87
+ process.exit(1);
88
+ }
89
+
90
+ // Set defaults
91
+ if (!options.description) {
92
+ options.description = options.message;
93
+ }
94
+
95
+ // Generate the PMD ruleset XML
96
+ function buildRulesetXml() {
97
+ const escXpath = options.xpath.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
98
+ const escMessage = options.message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
99
+ const escDescription = options.description.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
100
+
101
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
102
+ <ruleset name="${options.name}CustomRules"
103
+ xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
104
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
105
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
106
+
107
+ <description>Custom rules for ${options.name}</description>
108
+
109
+ <rule name="${options.name}"
110
+ language="${options.language}"
111
+ message="${escMessage}"
112
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
113
+
114
+ <description>${escDescription}</description>
115
+ <priority>${options.priority}</priority>
116
+
117
+ <properties>
118
+ <property name="xpath">
119
+ <value><![CDATA[
120
+ ${options.xpath}
121
+ ]]></value>
122
+ </property>
123
+ </properties>`;
124
+
125
+ if (options.example) {
126
+ xml += `
127
+
128
+ <example>
129
+ <![CDATA[
130
+ ${options.example}
131
+ ]]>
132
+ </example>`;
133
+ }
134
+
135
+ xml += `
136
+ </rule>
137
+ </ruleset>
138
+ `;
139
+
140
+ return xml;
141
+ }
142
+
143
+ // Create ruleset directory if needed
144
+ const rulesetDir = path.resolve(options.rulesetDir);
145
+ if (!fs.existsSync(rulesetDir)) {
146
+ fs.mkdirSync(rulesetDir, { recursive: true });
147
+ }
148
+
149
+ // Write ruleset XML
150
+ const rulesetFileName = `${options.name}-pmd-ruleset.xml`;
151
+ const rulesetPath = path.join(rulesetDir, rulesetFileName);
152
+
153
+ if (fs.existsSync(rulesetPath)) {
154
+ console.error(`Error: Ruleset file already exists: ${rulesetPath}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ const rulesetXml = buildRulesetXml();
159
+ fs.writeFileSync(rulesetPath, rulesetXml, "utf8");
160
+
161
+ // Update code-analyzer.yml to reference the ruleset
162
+ const configPath = path.resolve(options.configFile);
163
+ const rulesetRelativePath = path.relative(path.dirname(configPath), rulesetPath);
164
+ let configContent = "";
165
+
166
+ if (fs.existsSync(configPath)) {
167
+ configContent = fs.readFileSync(configPath, "utf8");
168
+ }
169
+
170
+ // Check if this ruleset path is already referenced in config (deduplication)
171
+ const alreadyReferenced = configContent.includes(rulesetRelativePath);
172
+
173
+ if (!configContent) {
174
+ // Create new config
175
+ configContent = `engines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`;
176
+ } else if (alreadyReferenced) {
177
+ // Ruleset path already in config — skip adding duplicate entry
178
+ // (This happens when the XML was deleted and recreated during iteration)
179
+ } else if (configContent.includes("custom_rulesets:") && configContent.includes("pmd:")) {
180
+ // Add to existing custom_rulesets
181
+ const insertPoint = configContent.indexOf("custom_rulesets:");
182
+ const afterLine = configContent.indexOf("\n", insertPoint) + 1;
183
+ configContent = configContent.slice(0, afterLine) + ` - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
184
+ } else if (configContent.includes("pmd:")) {
185
+ // Add custom_rulesets under pmd
186
+ const insertPoint = configContent.indexOf("pmd:");
187
+ const afterLine = configContent.indexOf("\n", insertPoint) + 1;
188
+ configContent = configContent.slice(0, afterLine) + ` custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
189
+ } else if (configContent.includes("engines:")) {
190
+ // Add pmd section under engines
191
+ const insertPoint = configContent.indexOf("engines:");
192
+ const afterLine = configContent.indexOf("\n", insertPoint) + 1;
193
+ configContent = configContent.slice(0, afterLine) + ` pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
194
+ } else {
195
+ // Append engines section
196
+ configContent += `\nengines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`;
197
+ }
198
+
199
+ fs.writeFileSync(configPath, configContent, "utf8");
200
+
201
+ console.log(JSON.stringify({
202
+ status: "success",
203
+ ruleName: options.name,
204
+ engine: "pmd",
205
+ language: options.language,
206
+ rulesetFile: rulesetPath,
207
+ configFile: configPath,
208
+ message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector pmd:${options.name}`
209
+ }));
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ // Creates a regex custom rule in code-analyzer.yml
3
+ // Usage: node create-regex-rule.js --name <name> --regex <pattern> --description <desc> [options]
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ function printUsage() {
9
+ console.error(`Usage: node create-regex-rule.js --name <name> --regex <pattern> --description <desc> [options]
10
+
11
+ Required:
12
+ --name <name> Rule name (PascalCase, no spaces)
13
+ --regex <pattern> Regex in /pattern/flags format
14
+ --description <desc> What the rule checks
15
+
16
+ Optional:
17
+ --violation-message <msg> Message shown on violation
18
+ --severity <1-5> Severity level (default: 3)
19
+ --tags <tag1,tag2> Comma-separated tags (default: Recommended,Custom)
20
+ --file-extensions <exts> Comma-separated extensions (e.g., .cls,.trigger)
21
+ --regex-ignore <pattern> Negative pattern to exclude matches
22
+ --config-file <path> Path to code-analyzer.yml (default: ./code-analyzer.yml)
23
+
24
+ Examples:
25
+ node create-regex-rule.js --name NoHardcodedIds --regex "/[0-9a-zA-Z]{18}/g" --description "Detects hardcoded IDs" --severity 2 --file-extensions ".cls,.trigger"
26
+ node create-regex-rule.js --name NoTodos --regex "/TODO|FIXME/gi" --description "Flags TODO comments" --severity 4`);
27
+ process.exit(1);
28
+ }
29
+
30
+ // Parse arguments
31
+ const args = process.argv.slice(2);
32
+ if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
33
+ printUsage();
34
+ }
35
+
36
+ const options = {
37
+ name: null,
38
+ regex: null,
39
+ description: null,
40
+ violationMessage: null,
41
+ severity: 3,
42
+ tags: ["Recommended", "Custom"],
43
+ fileExtensions: null,
44
+ regexIgnore: null,
45
+ configFile: "./code-analyzer.yml",
46
+ };
47
+
48
+ for (let i = 0; i < args.length; i++) {
49
+ switch (args[i]) {
50
+ case "--name": options.name = args[++i]; break;
51
+ case "--regex": options.regex = args[++i]; break;
52
+ case "--description": options.description = args[++i]; break;
53
+ case "--violation-message": options.violationMessage = args[++i]; break;
54
+ case "--severity": options.severity = parseInt(args[++i], 10); break;
55
+ case "--tags": options.tags = args[++i].split(",").map(t => t.trim()); break;
56
+ case "--file-extensions": options.fileExtensions = args[++i].split(",").map(e => e.trim()); break;
57
+ case "--regex-ignore": options.regexIgnore = args[++i]; break;
58
+ case "--config-file": options.configFile = args[++i]; break;
59
+ default:
60
+ console.error(`Unknown option: ${args[i]}`);
61
+ printUsage();
62
+ }
63
+ }
64
+
65
+ // Validate required fields
66
+ if (!options.name) { console.error("Error: --name is required"); process.exit(1); }
67
+ if (!options.regex) { console.error("Error: --regex is required"); process.exit(1); }
68
+ if (!options.description) { console.error("Error: --description is required"); process.exit(1); }
69
+
70
+ // Validate rule name
71
+ const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/;
72
+ if (!RULE_NAME_PATTERN.test(options.name)) {
73
+ console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ // Validate regex format — must be /pattern/flags with no surrounding whitespace
78
+ // and the flags portion must contain only valid JavaScript regex flag characters.
79
+ options.regex = options.regex.trim();
80
+ if (!options.regex.startsWith("/") || options.regex.lastIndexOf("/") <= 0) {
81
+ console.error(`Error: Regex must be in /pattern/flags format. Got: "${options.regex}"`);
82
+ process.exit(1);
83
+ }
84
+ const lastSlash = options.regex.lastIndexOf("/");
85
+ const flags = options.regex.slice(lastSlash + 1);
86
+ if (!flags) {
87
+ console.error(`Error: Regex must include flags after the closing /. Use /pattern/g at minimum. Got: "${options.regex}"`);
88
+ process.exit(1);
89
+ }
90
+ // Strict flags validation — only valid JS regex flag chars allowed, no spaces, no junk.
91
+ if (!/^[gimsuy]+$/.test(flags)) {
92
+ console.error(`Error: Invalid regex flags "${flags}". Allowed: g, i, m, s, u, y (no spaces, no other characters). Got: "${options.regex}"`);
93
+ process.exit(1);
94
+ }
95
+ // Code Analyzer regex rules require the global flag.
96
+ if (!flags.includes("g")) {
97
+ console.error(`Error: Regex must include the global flag 'g'. Got flags: "${flags}"`);
98
+ process.exit(1);
99
+ }
100
+
101
+ // Validate severity
102
+ if (options.severity < 1 || options.severity > 5) {
103
+ console.error("Error: Severity must be 1-5");
104
+ process.exit(1);
105
+ }
106
+
107
+ // Validate file extensions
108
+ if (options.fileExtensions) {
109
+ for (const ext of options.fileExtensions) {
110
+ if (!ext.startsWith(".")) {
111
+ console.error(`Error: File extension must start with dot: "${ext}"`);
112
+ process.exit(1);
113
+ }
114
+ }
115
+ }
116
+
117
+ // Safely quote a string for YAML output.
118
+ // CRITICAL: regex patterns contain backslashes (`\.`, `\d`, `\\`, etc.).
119
+ // Double-quoted YAML treats `\` as an escape introducer — `\.` is an unknown
120
+ // escape and YAML rejects the file with "unknown escape sequence". Single-quoted
121
+ // YAML treats backslash as literal, which is exactly what regex needs.
122
+ // Strategy:
123
+ // - If value contains a backslash → ALWAYS use single quotes (escape ' as '')
124
+ // - Else if value has no quotes → double quotes (simplest, most readable)
125
+ // - Else → single quotes (escape ' as '')
126
+ function yamlQuote(value) {
127
+ const hasBackslash = value.includes("\\");
128
+ const hasSingle = value.includes("'");
129
+ const hasDouble = value.includes('"');
130
+
131
+ if (!hasBackslash && !hasSingle && !hasDouble) {
132
+ return `"${value}"`;
133
+ }
134
+
135
+ // Single-quoted YAML: only ' needs escaping (as ''). Backslashes pass through.
136
+ const escaped = value.replace(/'/g, "''");
137
+ return `'${escaped}'`;
138
+ }
139
+
140
+ // Build the rule YAML block
141
+ function buildRuleYaml() {
142
+ const indent = " ";
143
+ const lines = [];
144
+ lines.push(` ${options.name}:`);
145
+ lines.push(`${indent} regex: ${yamlQuote(options.regex)}`);
146
+ lines.push(`${indent} description: ${yamlQuote(options.description)}`);
147
+
148
+ if (options.violationMessage) {
149
+ lines.push(`${indent} violation_message: ${yamlQuote(options.violationMessage)}`);
150
+ }
151
+
152
+ lines.push(`${indent} severity: ${options.severity}`);
153
+
154
+ if (options.tags && options.tags.length > 0) {
155
+ lines.push(`${indent} tags:`);
156
+ options.tags.forEach(tag => lines.push(`${indent} - "${tag}"`));
157
+ }
158
+
159
+ if (options.fileExtensions && options.fileExtensions.length > 0) {
160
+ lines.push(`${indent} file_extensions:`);
161
+ options.fileExtensions.forEach(ext => lines.push(`${indent} - "${ext}"`));
162
+ }
163
+
164
+ if (options.regexIgnore) {
165
+ lines.push(`${indent} regex_ignore: ${yamlQuote(options.regexIgnore)}`);
166
+ }
167
+
168
+ return lines.join("\n");
169
+ }
170
+
171
+ // Read or create config file
172
+ const configPath = path.resolve(options.configFile);
173
+ let configContent = "";
174
+
175
+ if (fs.existsSync(configPath)) {
176
+ configContent = fs.readFileSync(configPath, "utf8");
177
+
178
+ // Check if rule already exists
179
+ if (configContent.includes(`${options.name}:`)) {
180
+ console.error(`Error: Rule "${options.name}" already exists in ${configPath}`);
181
+ process.exit(1);
182
+ }
183
+ }
184
+
185
+ const ruleYaml = buildRuleYaml();
186
+
187
+ // Upsert into config
188
+ if (!configContent) {
189
+ // Create new file
190
+ configContent = `engines:\n regex:\n custom_rules:\n${ruleYaml}\n`;
191
+ } else if (configContent.includes("custom_rules:") && configContent.includes("regex:")) {
192
+ // Add to existing custom_rules section
193
+ const insertPoint = configContent.indexOf("custom_rules:");
194
+ const afterCustomRules = configContent.indexOf("\n", insertPoint) + 1;
195
+ configContent = configContent.slice(0, afterCustomRules) + ruleYaml + "\n" + configContent.slice(afterCustomRules);
196
+ } else if (configContent.includes("regex:")) {
197
+ // Add custom_rules section under regex
198
+ const insertPoint = configContent.indexOf("regex:");
199
+ const afterRegex = configContent.indexOf("\n", insertPoint) + 1;
200
+ configContent = configContent.slice(0, afterRegex) + " custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterRegex);
201
+ } else if (configContent.includes("engines:")) {
202
+ // Add regex section under engines
203
+ const insertPoint = configContent.indexOf("engines:");
204
+ const afterEngines = configContent.indexOf("\n", insertPoint) + 1;
205
+ configContent = configContent.slice(0, afterEngines) + " regex:\n custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterEngines);
206
+ } else {
207
+ // Append engines section
208
+ configContent += "\nengines:\n regex:\n custom_rules:\n" + ruleYaml + "\n";
209
+ }
210
+
211
+ // Write config
212
+ fs.writeFileSync(configPath, configContent, "utf8");
213
+
214
+ console.log(JSON.stringify({
215
+ status: "success",
216
+ ruleName: options.name,
217
+ engine: "regex",
218
+ configFile: configPath,
219
+ message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector regex:${options.name}`
220
+ }));
@@ -1,10 +1,18 @@
1
1
  ---
2
2
  name: dx-code-analyzer-run
3
- description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what specific rules mean. TRIGGER when: user says 'scan my code', 'check for security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', 'show only security violations', 'what is this rule', 'explain ApexCRUDViolation', 'filter results', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). Use this skill for scanning, exploring results, understanding rules, and listing available rules. DO NOT TRIGGER when: user wants to fix code without scanning, or asks ONLY about installation/configuration."
4
- allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit
3
+ description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what rules mean. TRIGGER when: user says 'scan my code', 'check security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', 'show security violations', 'what is this rule', 'explain ApexCRUDViolation', 'filter results', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). Use this skill for scanning, exploring results, and listing rules. DO NOT TRIGGER when: user asks only about installation/configuration (use dx-code-analyzer-configure), or wants to create a custom rule (use dx-code-analyzer-custom-rule-create)."
5
4
  metadata:
6
5
  version: "1.0"
7
- argument-hint: "[target-path] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--category Security|Performance|BestPractices|...] [--severity 1-5] [--diff]"
6
+ relatedSkills:
7
+ - "dx-code-analyzer-configure"
8
+ - "dx-code-analyzer-custom-rule-create"
9
+ cliTools:
10
+ - tool: ["sf"]
11
+ semver: ">=2.0.0"
12
+ - tool: ["node"]
13
+ semver: ">=18.0.0"
14
+ - tool: ["git"]
15
+ semver: ">=2.0.0"
8
16
  ---
9
17
 
10
18
  # Running Code Analyzer Skill
@@ -67,11 +75,13 @@ Any aggregation, filter, or rank question ("which file has the most violations?"
67
75
 
68
76
  ## Overview
69
77
 
78
+ > **Ecosystem:** This skill is part of a 3-skill Code Analyzer suite — `dx-code-analyzer-run` (scans & results) · `dx-code-analyzer-configure` (setup, config, CI/CD) · `dx-code-analyzer-custom-rule-create` (custom rule authoring).
79
+
70
80
  This skill translates natural-language requests ("scan for security issues", "check my changes") into the correct `sf code-analyzer run` command, executes scans across any combination of engines/targets/severities, and presents actionable results. When engine-provided fixes are available, it discovers them, asks for user confirmation, applies them safely, and offers verification. Use it for static analysis, security reviews, AppExchange certification, code-quality checks, and finding duplicates/vulnerabilities in Salesforce projects.
71
81
 
72
82
  **In scope:** running scans, parsing/filtering/ranking results, applying engine auto-fixes, diff-based scans, all output formats (JSON/HTML/SARIF/CSV/XML), describing/listing rules, scan-failure troubleshooting.
73
83
 
74
- **Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines, AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`).
84
+ **Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines (→ `dx-code-analyzer-custom-rule-create`), AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`).
75
85
 
76
86
  **Allowed tools:** Bash (`sf code-analyzer`, `node`, `git diff`, `date`), Read, Write, Edit. **Forbidden:** any MCP tool, Agent tool, web tools, other skills, Python, `jq`, inline scripts/heredocs. This skill owns the complete scan-fix-verify-query-explain workflow end-to-end.
77
87
 
@@ -203,7 +213,7 @@ Use the **Bash tool only** — never the `run_code_analyzer` MCP tool.
203
213
 
204
214
  1. Generate the timestamp via Bash: `date +%Y%m%d-%H%M%S` → e.g. `20260512-143022`.
205
215
  2. Tell the user:
206
- ```
216
+ ```text
207
217
  Starting scan...
208
218
  Results: ./code-analyzer-results-20260512-143022.json
209
219
  Log: ./code-analyzer-results-20260512-143022.log
@@ -237,7 +247,7 @@ node "<skill_dir>/scripts/parse-results.js" "./code-analyzer-results-TIMESTAMP.j
237
247
 
238
248
  ### Presentation template
239
249
 
240
- ```
250
+ ```text
241
251
  ## Scan Complete
242
252
 
243
253
  **Found X violations** across Y files.
@@ -296,7 +306,7 @@ node "<skill_dir>/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP.
296
306
 
297
307
  ### 6.3 Present + ASK (then STOP)
298
308
 
299
- ```
309
+ ```text
300
310
  ### Engine-Provided Fixes Available
301
311
  **X of Y violations** have auto-fixes provided by the analysis engine:
302
312
 
@@ -327,7 +337,7 @@ node "<skill_dir>/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP
327
337
 
328
338
  Then present:
329
339
 
330
- ```
340
+ ```text
331
341
  ### Engine-Provided Fixes Applied Successfully ✓
332
342
  **Applied X auto-fixes across Y files.**
333
343
 
@@ -439,6 +449,29 @@ node "<skill_dir>/scripts/list-rules.js" "<selector>" [options]
439
449
 
440
450
  Filters: `--engine`, `--severity`, `--top` (default 100), `--count-only`. The script pre-validates selector tokens (catches typos like `secruity`) before calling the CLI. Presentation: `<skill_dir>/references/post-scan-workflows.md`.
441
451
 
452
+ ---
453
+ ## Cross-Skill Integration
454
+
455
+ This skill is part of a 3-skill Code Analyzer ecosystem. Hand off cleanly rather than attempting work that belongs to another skill.
456
+
457
+ ### When THIS skill delegates to `dx-code-analyzer-configure`:
458
+
459
+ - Pre-flight check fails (CLI missing, plugin not installed, engine prereqs broken) → stop, delegate, return here after fix
460
+ - User asks to set up CI/CD, edit `code-analyzer.yml`, change severities, or disable engines → delegate entirely
461
+
462
+ ### When THIS skill delegates to `dx-code-analyzer-custom-rule-create`:
463
+
464
+ - User asks to create a new rule, write XPath, write a regex rule, or enforce a pattern not covered by built-in rules → delegate entirely. Do NOT attempt to create rules here.
465
+
466
+ ### When other skills hand off HERE:
467
+
468
+ - `dx-code-analyzer-configure` completes setup → proceed with scan (Step 1–5)
469
+ - `dx-code-analyzer-custom-rule-create` finishes creating a rule → proceed with scan targeting the new rule (e.g., `--rule-selector pmd:<RuleName>`) to verify it works
470
+
471
+ ### Ownership boundary
472
+
473
+ This skill owns the complete **scan → explore → fix** workflow end-to-end. It does NOT own installation, config file management, or rule authoring.
474
+
442
475
  ---
443
476
 
444
477
  ## Constraints & Gotchas
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mobile-platform-native-capabilities-integrate
3
- description: "Build a Salesforce LWC that uses native mobile device capabilities — barcode scanner, biometrics, location, NFC, calendar, contacts, document scanner, geofencing, AR space capture, app review, and payments. Use this skill when the user asks for an LWC that scans a barcode, captures a photo of a document, reads location or geofences, prompts for biometrics, reads/writes the device calendar or contacts, taps NFC, takes a payment, prompts for an app review, or scans an AR space. Also triggers on \"lightning/mobileCapabilities\", \"mobile capability\", \"Nimbus\", \"device capability\". Do not use for mobile offline / Komaci priming reviews (use `mobile-platform-offline-validate`) or for picking generic Lightning base components (use a generic Lightning base components skill)."
3
+ description: "Build a Salesforce LWC that uses native mobile device capabilities — barcode scanner, biometrics, location, NFC, calendar, contacts, document scanner, geofencing, AR space capture, app review, and payments. Use this skill when the user asks for an LWC that scans a barcode, captures a photo of a document, reads location or geofences, prompts for biometrics, reads/writes the device calendar or contacts, taps NFC, takes a payment, prompts for an app review, or scans an AR space. Also triggers on \"lightning/mobileCapabilities\", \"mobile capability\", \"Nimbus\", \"device capability\". Do not use for mobile offline / Komaci priming reviews (use `mobile-platform-offline-validate`) or for picking generic Lightning base components (use `design-systems-slds-apply`)."
4
4
  metadata:
5
5
  version: "1.0"
6
6
  ---
@@ -32,8 +32,8 @@ Do NOT use this skill for:
32
32
 
33
33
  - Mobile-offline review of an LWC (lwc:if, inline GraphQL, Komaci-priming
34
34
  violations) — use `mobile-platform-offline-validate`.
35
- - Picking generic Lightning Base Components — use
36
- `using-lightning-base-components`.
35
+ - Choosing or styling generic Lightning Base Components / SLDS blueprints
36
+ use `design-systems-slds-apply`.
37
37
 
38
38
  ## Prerequisites
39
39