
Running Code Analyzer
- 2k installs
- 787 repo stars
- Updated August 5, 2026
- forcedotcom/sf-skills
running-code-analyzer is an agent skill that Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports .
About
Every interaction with Code Analyzer results MUST go through the bundled scripts in skill_dir scripts No exceptions bash WRONG inline Python to parse results python3 c import json data json load open results json WRONG inline Node js to parse results node e const data require results json WRONG jq to filter results cat results json jq violations select engine pmd WRONG reading the results file directly it can be 10MB Read tool code analyzer results json The running code analyzer agent skill provides documented workflows prerequisites triggers and safety guidance from its SKILL md source Agents load it when user requests match the description and follow step by step instructions without inventing capabilities It integrates with standard agent tooling for the tasks inputs outputs and failure modes described in the repository documentation
- description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violati
- allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit
- argument-hint: "[target-path] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--category Security|Performa
- Follow running-code-analyzer SKILL.md steps and documented constraints.
- Follow running-code-analyzer SKILL.md steps and documented constraints.
Running Code Analyzer by the numbers
- 1,995 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #290 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
running-code-analyzer capabilities & compatibility
- Capabilities
- description: "run salesforce code analyzer to sc · allowed tools: read, bash(sf code analyzer), bas · argument hint: "[target path] [ engine pmd|esli · follow running code analyzer skill.md steps and
- Use cases
- orchestration
What running-code-analyzer says it does
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), ta
allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit
argument-hint: "[target-path] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--category Security|Performance|BestPractices|...] [--severity 1-5] [--diff]"
npx skills add https://github.com/forcedotcom/sf-skills --skill running-code-analyzerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2k |
|---|---|
| repo stars | ★ 787 |
| Security audit | 1 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | forcedotcom/sf-skills ↗ |
When should an agent use running-code-analyzer and what problem does it solve?
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,
Who is it for?
Developers invoking running-code-analyzer as documented in the skill source.
Skip if: Skip when requirements fall outside running-code-analyzer documented scope.
When should I use this skill?
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,
What you get
Outputs aligned with the running-code-analyzer SKILL.md workflow and stated deliverables.
- Code Analyzer JSON report
- Violation list with file coordinates
By the numbers
- Example scan output reports 45 filesAnalyzed and 127 violationCount
- Uses PMD and ESLint engines for Apex and LWC JavaScript rules
Files
Running Code Analyzer Skill
⚠️ CRITICAL: Mandatory Script Usage
Every interaction with Code Analyzer results MUST go through the bundled scripts in <skill_dir>/scripts/. No exceptions.
❌ WRONG — never do this:
# WRONG: inline Python to parse results
python3 -c "import json; data = json.load(open('results.json'))..."
# WRONG: inline Node.js to parse results
node -e "const data = require('./results.json')..."
# WRONG: jq to filter results
cat results.json | jq '.violations[] | select(.engine=="pmd")'
# WRONG: reading the results file directly (it can be 10MB+)
Read tool → code-analyzer-results-*.jsonAlso forbidden: run_code_analyzer and any mcp__* tool — Bash only.
✅ RIGHT — always do this:
# Summarize scan results
node "<skill_dir>/scripts/parse-results.js" "./code-analyzer-results-TIMESTAMP.json"
# Filter/rank/query results (by engine, severity, file, rule, category)
node "<skill_dir>/scripts/query-results.js" "./code-analyzer-results-TIMESTAMP.json" --engine pmd --summary
# List/browse available rules (by engine, category, language, severity)
node "<skill_dir>/scripts/list-rules.js" "Security" --top 10
# Look up what a rule means
node "<skill_dir>/scripts/describe-rule.js" "ApexCRUDViolation" --engine pmd
# Discover fixable violations
node "<skill_dir>/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
# Apply fixes (after user confirms)
node "<skill_dir>/scripts/apply-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
# Summarize applied fixes
node "<skill_dir>/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
# Filter vendor files (jQuery, Bootstrap, *.min.js) before applying fixes
node "<skill_dir>/scripts/filter-violations.js" "./code-analyzer-results-TIMESTAMP.json" "./code-analyzer-results-TIMESTAMP-filtered.json" --report<skill_dir> is the absolute path to the directory containing this SKILL.md. Never use ./scripts/ — that resolves against the user's CWD, not the skill dir.
Any aggregation, filter, or rank question ("which file has the most violations?", "how many PMD issues?", "top rules by count", "break down by severity") is answered by query-results.js — its output already includes topRules, topFiles, and severityCounts.
---
Overview
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.
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.
Out of scope: installing/configuring sf or the plugin (→ configuring-code-analyzer), writing custom rules/engines, AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ configuring-code-analyzer).
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.
---
Command Syntax Rules (READ FIRST — ABSOLUTE)
1. The command is `sf code-analyzer run` — NOT sf scanner run (deprecated v3). 2. No `--format` flag. Use --output-file <path>.<ext>; the extension determines the format. 3. Always pass --output-file with a timestamped name (e.g., ./code-analyzer-results-20260512-143022.json) — do not rely on stdout. 4. Foreground only (no run_in_background); timeout 1200000ms for large scans. 5. Invalid v3 flags that cause errors: --format, --engine, --category, --json. Use --rule-selector + --output-file instead. 6. Tool restriction: Bash, Read, Write, Edit only. No MCP tools, no Agent tool, no web tools, no other skills.
Why: the v4+ CLI redesigned the flag interface; v3 flags now error.
Full flag/selector docs: <skill_dir>/references/flag-reference.md.
---
Prerequisites
User needs: Salesforce CLI (sf), @salesforce/plugin-code-analyzer (v5.x+), Java 11+ (PMD/CPD/SFGE), Node.js 18+ (ESLint/RetireJS), Python 3 (Flow), authenticated org (ApexGuru).
Pre-flight: run sf code-analyzer --help 2>&1 | head -1. If that fails, or if a scan reports an engine startup error (e.g., "PMD failed to start", "java: command not found", "SFGE failed"):
1. Stop — do not attempt to install/diagnose prerequisites yourself. 2. Delegate to `configuring-code-analyzer` — it handles all setup. 3. After it finishes, return here and re-run the scan.
If a scan fails for other reasons, see <skill_dir>/references/error-handling.md.
---
Quick Start: Common Patterns
Match the request below; if it matches, jump to Step 3 (Build Command). Otherwise, walk Step 1.
| User Says | Rule Selector | Notes |
|---|---|---|
| "scan my code" / "run code analyzer" | Recommended | Curated set, all file types |
| "check for security issues" / "security review" | all:Security:(1,2) | All engines, Critical+High |
| "scan my changes" / "check the diff" | (see Step 1.5) | Get files via git diff, filter to scannable types, pass via --target |
| "run PMD" / "check my Apex" | pmd | Apex classes and triggers |
| "lint my LWC" / "check my JavaScript" | eslint | JavaScript/TypeScript/LWC |
| "find duplicates" / "check for copy-paste" | cpd | Code clones |
| "check for vulnerabilities" / "scan libraries" | retire-js | JavaScript library CVEs |
| "deep analysis" / "data flow analysis" | sfge | Java 11+, 10–20 min, use --workspace "force-app" |
| "performance analysis" / "governor limits" | apexguru | Authenticated org required |
| "analyze my Flows" | flow | --target **/*.flow-meta.xml, Python 3 |
| "AppExchange security review" | all:Security:(1,2) | See <skill_dir>/references/special-behaviors.md → AppExchange |
---
Step 1: Parse the User's Intent
Analyze the request along these 7 dimensions; any can combine.
1.1 ENGINE
PMD/Apex → pmd · ESLint/JS/TS/lint → eslint · Flows → flow · duplicates/CPD → cpd · vulnerabilities/CVE/RetireJS → retire-js · SFGE/data flow → sfge · performance/ApexGuru → apexguru · regex → regex · everything → all · unspecified → Recommended.
1.2 CATEGORY
security/OWASP → Security · performance → Performance · best practices → BestPractices · style/format → CodeStyle · design/complexity → Design · bugs → ErrorProne · docs → Documentation.
1.3 SEVERITY
1=Critical · 2=High · 3=Moderate · 4=Low · 5=Info. "critical only" → 1 · "critical+high" → (1,2) · "moderate and above" → (1,2,3).
1.4 SPECIFIC RULE
If the user names a rule (e.g., "ApexCRUDViolation", "no-unused-vars"): --rule-selector <engine>:<ruleName>, or just <ruleName> if engine is ambiguous.
⚠️ Partial names: --rule-selector requires the exact full rule name (e.g., @salesforce-ux/slds/no-hardcoded-values-slds2, not no-hardcoded-values). No wildcards. If you are not 100% certain, look it up first — do not guess:
sf code-analyzer rules --rule-selector all 2>&1 | grep -i "USER_KEYWORD"Multiple matches → ask the user which. Zero matches → tell the user nothing matched.
1.5 TARGET
specific path → --target <path> · glob ("all Apex") → --target **/*.cls,**/*.trigger · "my changes"/"diff" → git diff --name-only [base]...HEAD, filter to scannable types, pass as --target · "LWC" → --target **/lwc/** · "Flows" → --target **/*.flow-meta.xml · unspecified → omit (entire workspace).
Diff-filtering details: <skill_dir>/references/special-behaviors.md.
1.6 OUTPUT
Default JSON. Only change if the user explicitly asks. Name: ./code-analyzer-results-<YYYYMMDD-HHmmss>.<ext> via TIMESTAMP=$(date +%Y%m%d-%H%M%S). Formats: .json (default), .html, .sarif, .csv, .xml.
1.7 COMPARISON / DELTA
"new since main" → git diff --name-only main...HEAD → scan those · "since last commit" → HEAD~1 · "vs develop" → develop...HEAD.
---
Step 2: Build the Rule Selector
Syntax: : = AND, , = OR, () = grouping.
- Engine only:
pmd - Engine + category:
pmd:Security - Engine + severity:
pmd:2 - Complex:
(pmd,eslint):Security:(1,2)= (PMD or ESLint) AND Security AND sev (1 or 2) - Specific rule:
pmd:ApexCRUDViolation - All:
all
More: <skill_dir>/references/command-examples.md.
---
Step 3: Build the Full Command
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
sf code-analyzer run \
--rule-selector <selector> \
--target <targets> \ # optional
--output-file "./code-analyzer-results-${TIMESTAMP}.json" \ # default JSON
--include-fixes \ # always
--workspace <path> # optional- Default to timestamped JSON; only change format on explicit request.
- Always pass
--include-fixes(enables Step 6 auto-fix). - Omit
--targetto scan the whole workspace. - Diff scans:
git diff --name-only→ filter scannable types → pass as--target.
Special cases (SFGE/ApexGuru/AppExchange/diff): <skill_dir>/references/special-behaviors.md.
---
Step 4: Execute the Scan
Use the Bash tool only — never the run_code_analyzer MCP tool.
1. Generate the timestamp via Bash: date +%Y%m%d-%H%M%S → e.g. 20260512-143022. 2. Tell the user:
Starting scan...
Results: ./code-analyzer-results-20260512-143022.json
Log: ./code-analyzer-results-20260512-143022.log
May take several minutes for large codebases.3. Run with the literal timestamp baked in (not $TIMESTAMP), foreground, timeout 1200000ms, tee to a .log:
sf code-analyzer run --rule-selector Recommended \
--output-file "./code-analyzer-results-20260512-143022.json" \
--include-fixes 2>&1 | tee "./code-analyzer-results-20260512-143022.log"4. Exit 0 = success. On error, read both the log file and <skill_dir>/references/error-handling.md. 5. Immediately parse results (Step 5) — do not ask the user what to do next.
---
Step 5: Parse and Present Results
Run the parse script straight after the scan — do not pause to ask:
node "<skill_dir>/scripts/parse-results.js" "./code-analyzer-results-TIMESTAMP.json"⚠️ DO NOT:
- ❌ Invent or generate script code yourself
- ❌ Use bare relative paths like
node scripts/parse-results.js(won't resolve from user's CWD) - ❌ Use heredocs or inline script content
- ❌ Use
jqas a substitute for the parse script (shell quoting will break) - ❌ Read the JSON file directly
Presentation template
## Scan Complete
**Found X violations** across Y files.
| Severity | Count |
|----------|-------|
| Critical (1) | X |
| High (2) | X |
| Moderate (3) | X |
| Low (4) | X |
| Info (5) | X |
### Top Issues
| # | Rule | Engine | Sev | File | Line |
|---|------|--------|-----|------|------|
| 1 | ApexCRUDViolation | pmd | 2 | AccountService.cls | 42 |
| ... up to 10 most critical |
### Top Rules by Frequency
| Rule | Engine | Count |
|------|--------|-------|
| no-var | eslint | 170 |
| ... |
Full results: `./code-analyzer-results-20260512-143022.json`Scale to result size: 0 → "no violations found"; 1–10 → all in one table; 11–50 → severity counts + top 10; 50–5000 → counts + top 10 violations + top 10 rules + top 5 files; 5000+ → same, plus suggest narrowing scope (severity/category/folder). Always end with the output path and offer next actions: filter / explain rule / apply fixes.
Large-result handling: <skill_dir>/references/special-behaviors.md.
---
Step 6: Apply Engine-Provided Fixes (Post-Scan)
Engine-provided fixes are deterministic (not AI-generated). Flow: vendor filter (if needed) → discover → present → wait for user confirmation → apply → summarize.
6.1 Vendor file filter (when needed)
Run if the user said "fix my code" / "project source", or if top-violation files are vendor libs (jQuery, Bootstrap, *.min.js):
node "<skill_dir>/scripts/filter-violations.js" \
"./code-analyzer-results-TIMESTAMP.json" \
"./code-analyzer-results-TIMESTAMP-filtered.json" \
--reportReport: "Excluded X vendor files (Y violations) — jQuery, Bootstrap, etc. Applying fixes to Z project files only." Use the filtered file in 6.2+. Detection logic: <skill_dir>/references/vendor-file-handling.md.
6.2 Discover
node "<skill_dir>/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP.json"6.3 Present + ASK (then STOP)
### Engine-Provided Fixes Available
**X of Y violations** have auto-fixes provided by the analysis engine:
| Rule | Engine | Sev | Fixable Count |
|------|--------|-----|---------------|
| no-var | eslint | 3 | 170 |
| ... |
These are safe, deterministic fixes generated by the engines (not AI-generated).
Would you like me to apply these fixes? (yes / no / select specific rules)⚠️ Stop and wait for the user's reply, even if they originally said "scan and fix everything". Apply only on a fresh "yes" / "apply" / "go ahead" in the next turn.
6.4 Apply
node "<skill_dir>/scripts/apply-fixes.js" "./code-analyzer-results-TIMESTAMP.json"(Filtered file if 6.1 created one.)
6.5 Summarize (MANDATORY immediately after 6.4)
node "<skill_dir>/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP.json"Then present:
### Engine-Provided Fixes Applied Successfully ✓
**Applied X auto-fixes across Y files.**
| Severity | Fixes Applied |
|----------|---------------|
| Critical (1) | X |
| ... |
| Rule | Fixes Applied |
|------|---------------|
| no-var | 169 |
| ... |
Want me to re-run the scan to verify the fixes resolved the violations?6.6 — Handling the user's choice
- Decline / "no": skip apply, skip summarize. Do not re-scan.
- "Select rules": filter the discovery list to those rules and pass the filtered file to
apply-fixes.js. - "All" / "yes": run
apply-fixes.jsagainst the full (or vendor-filtered) results file as-is.
6.7 — Optional re-scan for verification
If the user accepts the offer in 6.5, re-run the same scan with a new timestamp (do not overwrite the original). Compare violation counts before vs. after and show the delta — fixes that resolved cleanly will drop out; remaining violations either need manual remediation or are unrelated.
---
Step 7: Query and Filter Existing Results
After Step 5, the user may want to drill into specific subsets without re-running the entire scan. This step handles all result-exploration requests.
When to trigger
Activate when the user asks to slice, filter, rank, or explore existing results:
- "Show me just the security violations"
- "What's in AccountService.cls?"
- "Show only PMD issues" / "Filter to critical and high"
- "What ESLint rules fired?" / "Show violations in the lwc folder"
- "Top 20 most severe" / "Which file has the most violations?"
- "What are the most common rules?" / "How many violations per engine?" / "Break it down by severity"
Important: Any question about existing scan results — filtering, ranking, counting, aggregating — MUST use query-results.js. NEVER write inline Python, jq, or ad-hoc scripts to parse the results JSON. The query script already provides topRules, topFiles, and severityCounts in its output.
How to execute
Run the query script against the same results file from Step 4 (no re-scan needed):
node "<skill_dir>/scripts/query-results.js" "./code-analyzer-results-TIMESTAMP.json" [options]| User says | Options |
|---|---|
| "security violations" | --category Security |
| "PMD issues only" | --engine pmd |
| "critical and high" / "sev 1-2" | --severity 1,2 |
| "in AccountService.cls" | --file AccountService.cls |
| "the ApexCRUDViolation rule" | --rule ApexCRUDViolation |
| "top 20" | --top 20 |
| "sort by file" | --sort file |
| "just give me counts" | --summary |
| "which file has the most violations?" | --sort file --summary (read topFiles) |
| "which file has most PMD violations?" | --engine pmd --summary (read topFiles) |
| "most common rules?" | --summary (read topRules) |
| "how many per engine?" | use Step 5's summary, or run with --engine X --summary per engine |
| Combinations | --engine pmd --severity 1,2 --top 5 |
Output format and presentation templates: <skill_dir>/references/post-scan-workflows.md.
---
Step 8: Describe a Rule
When the user asks "what does this rule mean?" or "how do I fix this?", use this step to look up and explain a specific rule.
When to trigger
- "What is ApexCRUDViolation?"
- "Explain this rule" / "Why is this flagged?"
- "What does no-var mean?"
- "How do I fix OperationWithLimitsInLoop?"
- "Tell me about this violation"
How to execute
node "<skill_dir>/scripts/describe-rule.js" "<rule-name>" [--engine <engine>]Pass --engine when known (from scan context); omit for a broader search. Returns one of success / multiple_matches / not_found. Status handling and templates: <skill_dir>/references/post-scan-workflows.md.
---
Step 9: List Available Rules
Triggers: "what security rules are available?", "list all PMD rules", "rules for JavaScript", "Recommended rules", "how many ESLint rules?", "rules for Apex".
node "<skill_dir>/scripts/list-rules.js" "<selector>" [options]| User says | Selector | Options |
|---|---|---|
| "security rules" | Security | |
| "PMD rules" | pmd | |
| "ESLint security rules" | eslint:Security | |
| "JavaScript rules" | JavaScript | |
| "Apex rules" | Apex | |
| "Recommended rules" | Recommended | |
| "high severity rules" | (1,2) | |
| "just give me counts" | Recommended | --count-only |
| "top 10 security rules" | Security | --top 10 |
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.
---
Constraints & Gotchas
| Item | Why / Fix |
|---|---|
Use timestamped JSON + .log via tee | Prevents overwrite; matches log to results |
--format flag | Removed in v4+; use --output-file <path>.<ext> |
| Foreground, 1200000ms timeout | SFGE can take 10–20 min; backgrounding loses output |
Run scripts with absolute <skill_dir> path | ./scripts/ resolves against the user's CWD, not the skill dir |
| Never apply fixes without confirmation | User must approve code modifications |
| Vendor file check before fixes | If 50%+ vendor (jQuery/Bootstrap/*.min.js), filter first |
| Fix-script order: filter (if needed) → discover → apply → summarize | Skipping summary leaves the user without an outcome report |
SFGE needs explicit --workspace | Otherwise template files cause compilation errors |
| Look up partial rule names first | Guessing returns 0 results; use sf code-analyzer rules |
ONLY Bash tool, never MCP | run_code_analyzer and other MCP tools bypass the script workflow |
| Never invoke other skills for fixes | This skill owns the full workflow end-to-end |
| Query existing results, don't re-scan | Step 7 filters existing JSON instantly |
| Scan returns 0 results | Invalid rule selector — verify with sf code-analyzer rules --rule-selector <selector> |
jq parsing fails | Shell quoting — use parse-results.js / query-results.js instead |
| Inline scripts written by LLM | Never write scripts — use existing ones in <skill_dir>/scripts/ |
| Ranking/aggregation answered by ad-hoc Python | Always use query-results.js; output already has topFiles/topRules/severityCounts |
---
Reference File Index
Scripts (always execute via node with the absolute <skill_dir>/ prefix, never Read):
| File | When to use |
|---|---|
<skill_dir>/scripts/parse-results.js | Step 5 — extract summary from scan JSON |
<skill_dir>/scripts/filter-violations.js | Step 6.1 — exclude vendor files (jQuery, Bootstrap) from fixes |
<skill_dir>/scripts/discover-fixes.js | Step 6.2 — identify fixable violations |
<skill_dir>/scripts/apply-fixes.js | Step 6.4 — apply engine fixes after user confirms |
<skill_dir>/scripts/summarize-fixes.js | Step 6.5 — summarize applied changes |
<skill_dir>/scripts/query-results.js | Step 7 — filter/drill into existing results without re-scanning |
<skill_dir>/scripts/describe-rule.js | Step 8 — look up rule description and documentation |
<skill_dir>/scripts/list-rules.js | Step 9 — list/browse available rules by selector with validation |
References (read on demand):
| File | When to read |
|---|---|
references/quick-start.md | Command-syntax templates |
references/flag-reference.md | Full flag docs, rule-selector syntax |
references/error-handling.md | Scan-failure diagnosis |
references/engine-reference.md | Engine capabilities, file types, rule tags |
references/command-examples.md | Less-common command scenarios |
references/special-behaviors.md | SFGE/ApexGuru/AppExchange/diff/large scans |
references/vendor-file-handling.md | Vendor-file detection and filtering |
references/post-scan-workflows.md | Steps 7–9 — querying, rule description, rule listing |
examples/ contains output-structure validation and command patterns (basic/large/security scans, fix workflows).
{
"metadata": {
"engine": "Recommended",
"executedAt": "2026-05-19T10:15:30.123Z",
"filesAnalyzed": 45,
"violationCount": 127
},
"violations": [
{
"rule": "ApexCRUDViolation",
"engine": "pmd",
"severity": 2,
"message": "Validate CRUD permission before SOQL/DML operation",
"file": "force-app/main/default/classes/AccountService.cls",
"line": 42,
"column": 9,
"fix": null
},
{
"rule": "no-var",
"engine": "eslint",
"severity": 3,
"message": "Unexpected var, use let or const instead.",
"file": "force-app/main/default/lwc/accountCard/accountCard.js",
"line": 12,
"column": 5,
"fix": {
"range": [180, 183],
"text": "let"
}
},
{
"rule": "ApexDoc",
"engine": "pmd",
"severity": 3,
"message": "Missing ApexDoc comment",
"file": "force-app/main/default/classes/AccountService.cls",
"line": 15,
"column": 1,
"fix": null
},
{
"rule": "@lwc/lwc/no-inner-html",
"engine": "eslint",
"severity": 2,
"message": "Disallow use of innerHTML",
"file": "force-app/main/default/lwc/riskComponent/riskComponent.js",
"line": 28,
"column": 9,
"fix": null
},
{
"rule": "prefer-const",
"engine": "eslint",
"severity": 3,
"message": "'data' is never reassigned. Use 'const' instead.",
"file": "force-app/main/default/lwc/accountCard/accountCard.js",
"line": 18,
"column": 5,
"fix": {
"range": [245, 248],
"text": "const"
}
}
],
"summary": {
"bySeverity": {
"1": 0,
"2": 32,
"3": 78,
"4": 15,
"5": 2
},
"byEngine": {
"pmd": 65,
"eslint": 58,
"regex": 4
},
"topRules": [
{"rule": "ApexDoc", "count": 45},
{"rule": "no-var", "count": 28},
{"rule": "prefer-const", "count": 19},
{"rule": "ApexCRUDViolation", "count": 12},
{"rule": "@lwc/lwc/no-inner-html", "count": 8}
],
"topFiles": [
{"file": "force-app/main/default/classes/AccountService.cls", "count": 23},
{"file": "force-app/main/default/lwc/accountCard/accountCard.js", "count": 18},
{"file": "force-app/main/default/classes/ContactTriggerHandler.cls", "count": 15}
]
}
}
Common Command Variations
Real-world command patterns with explanations. Use these as reference when building commands for specific scenarios.
---
Basic Scans
1. Scan Entire Workspace (Default)
sf code-analyzer run \
--rule-selector Recommended \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan my code" with no specifics.
---
2. Security-Focused Scan
sf code-analyzer run \
--rule-selector "all:Security:(1,2)" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "check for security issues", "find vulnerabilities", "AppExchange security review". Selector breakdown: all = all engines, :Security = Security category only, :(1,2) = Critical and High severity only.
---
3. Specific Engine
sf code-analyzer run \
--rule-selector "pmd" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "run PMD", "check my Apex code".
---
4. Multiple Engines
sf code-analyzer run \
--rule-selector "(pmd,eslint)" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan Apex and JavaScript", "run PMD and ESLint". Selector breakdown: Parentheses + comma = OR logic.
---
Target-Specific Scans
5. Scan Specific File
sf code-analyzer run \
--rule-selector Recommended \
--target "force-app/main/default/classes/AccountService.cls" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan AccountService.cls".
---
6. Scan Specific Folder
sf code-analyzer run \
--rule-selector Recommended \
--target "force-app/main/default/lwc" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan my LWC components", "check the lwc folder".
---
7. Scan Multiple Paths
sf code-analyzer run \
--rule-selector Recommended \
--target "force-app/main/default/classes,force-app/main/default/triggers" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan classes and triggers". Note: Comma-separated paths in a single --target value.
---
8. Scan Using Glob Pattern
sf code-analyzer run \
--rule-selector Recommended \
--target "**/*.cls,**/*.trigger" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan all Apex files", "check all classes and triggers". Note: Glob patterns must match from workspace root.
---
Diff-Based Scans
9. Scan Changed Files (Git Diff)
Step 1: Get changed files
git diff --name-only main...HEADStep 2: Filter to scannable types (.cls, .trigger, .js, .ts, .flow-meta.xml, etc.)
Step 3: Pass as --target
sf code-analyzer run \
--rule-selector Recommended \
--target "force-app/main/default/classes/AccountService.cls,force-app/main/default/lwc/accountCard/accountCard.js" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "scan my changes", "check what I modified", "analyze the diff".
---
Advanced Scenarios
10. Deep Analysis with SFGE (Data Flow)
sf code-analyzer run \
--rule-selector "sfge" \
--workspace "force-app" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "deep analysis", "data flow analysis", "path-based analysis", "find CRUD violations with certainty". Note: Requires Java 11+. May take 10-20 minutes. Use --workspace to avoid compiling template files. Timeout: Set to 1200000ms (20 minutes).
---
11. Find Code Duplicates (CPD)
sf code-analyzer run \
--rule-selector "cpd" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "find duplicates", "check for copy-paste", "detect code clones".
---
12. Check Vulnerable Libraries (RetireJS)
sf code-analyzer run \
--rule-selector "retire-js" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "check for vulnerable libraries", "scan dependencies", "find CVEs".
---
13. Analyze Flows
sf code-analyzer run \
--rule-selector "flow" \
--target "**/*.flow-meta.xml" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "analyze my Flows", "check Flow best practices". Note: Requires Python 3.
---
14. Performance Analysis (ApexGuru)
sf code-analyzer run \
--rule-selector "apexguru" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "performance analysis", "find slow code", "check governor limits". Note: Requires authenticated Salesforce org. See references/special-behaviors.md for auth setup.
---
Output Format Variations
15. HTML Report
sf code-analyzer run \
--rule-selector Recommended \
--output-file ./code-analyzer-results-20260519-101030.html \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User explicitly requests HTML format. Note: Extension determines format. JSON is default.
---
16. SARIF (GitHub/IDE Integration)
sf code-analyzer run \
--rule-selector Recommended \
--output-file ./code-analyzer-results-20260519-101030.sarif \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "SARIF format", "GitHub integration", "IDE integration".
---
17. CSV (Spreadsheet)
sf code-analyzer run \
--rule-selector Recommended \
--output-file ./code-analyzer-results-20260519-101030.csv \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "CSV format", "export to spreadsheet", "Excel format".
---
Complex Rule Selectors
18. Multiple Categories
sf code-analyzer run \
--rule-selector "all:(Security,Performance):(1,2,3)" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logSelector breakdown: All engines, Security OR Performance categories, Severity 1-3 (Critical to Moderate).
---
19. Specific Rule by Name
sf code-analyzer run \
--rule-selector "pmd:ApexCRUDViolation" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logWhen: User says "check for ApexCRUDViolation", "run the CRUD rule". Note: Must be exact full rule name. If uncertain, look up first: sf code-analyzer rules --rule-selector all 2>&1 | grep -i "CRUD"
---
20. Engine + Category + Severity
sf code-analyzer run \
--rule-selector "(pmd,eslint):Security:(1,2)" \
--output-file ./code-analyzer-results-20260519-101030.json \
--include-fixes \
2>&1 | tee ./code-analyzer-results-20260519-101030.logSelector breakdown: (PMD OR ESLint) AND Security AND (Sev 1 OR Sev 2).
---
Key Patterns
| Pattern | Meaning | Example |
|---|---|---|
: | AND | pmd:Security = PMD and Security |
, | OR | (pmd,eslint) = PMD or ESLint |
() | Grouping | (pmd,eslint):Security = (PMD or ESLint) and Security |
(1,2) | Severity range | :(1,2) = Severity 1 or 2 |
--target <path> | Specific files/folders | Comma-separated in single arg |
--workspace <path> | Compilation scope (SFGE only) | Prevents compiling template files |
.json, .html, .sarif, .csv, .xml | Output format | Extension of --output-file |
---
Anti-Patterns (DO NOT USE)
❌ Using --format flag
# WRONG - v3 syntax, does not exist in v4+
sf code-analyzer run --format jsonWhy: The --format flag was removed in v4+. Use --output-file with extension instead.
---
❌ Using $TIMESTAMP variable in command
# WRONG - variable substitution fails in permission prompts
sf code-analyzer run --output-file "./results-${TIMESTAMP}.json"Why: Generate timestamp first, then use literal string in command.
---
❌ Running in background for long scans
# WRONG - loses output stream
sf code-analyzer run --rule-selector sfge &Why: Use foreground with high timeout (1200000ms). Backgrounding loses the output.
---
❌ Partial rule names
# WRONG - returns 0 results
sf code-analyzer run --rule-selector "no-hardcoded-values"Why: Rule names must be exact. Look up first: sf code-analyzer rules --rule-selector all | grep -i "hardcoded" Correct: --rule-selector "@salesforce-ux/slds/no-hardcoded-values-slds2"
Fix Application: Before & After
This example demonstrates engine-provided auto-fix behavior on a small codebase.
Initial Scan Results
Command:
sf code-analyzer run --rule-selector Recommended --output-file ./results.json --include-fixesSummary:
- Total violations: 248
- Fixable violations: 67 (27%)
Fixable Rules
| Rule | Engine | Severity | Count |
|---|---|---|---|
| no-var | eslint | 3 | 42 |
| prefer-const | eslint | 3 | 18 |
| @salesforce-ux/slds/no-hardcoded-values-slds2 | eslint | 4 | 5 |
| no-extra-boolean-cast | eslint | 3 | 2 |
---
Before Fix: Sample Violations
Violation 1: no-var
File: force-app/main/default/lwc/accountCard/accountCard.js:12
export default class AccountCard extends LightningElement {
handleClick() {
var accountId = this.recordId; // ← violation
var data = this.fetchData(accountId); // ← violation
this.processData(data);
}
}Violation 2: prefer-const
File: force-app/main/default/lwc/utils/dataProcessor.js:8
export function processRecords(records) {
let result = []; // ← violation (never reassigned)
records.forEach(r => result.push(transform(r)));
return result;
}Violation 3: @salesforce-ux/slds/no-hardcoded-values-slds2
File: force-app/main/default/lwc/accountCard/accountCard.css:4
.account-card {
border-radius: 4px; /* ← violation */
padding: 16px; /* ← violation */
}---
Apply Fixes
Command:
node <skill_dir>/scripts/apply-fixes.js ./results.jsonOutput:
{
"success": true,
"filesModified": 15,
"fixesApplied": 67,
"fixesSkipped": 0
}---
After Fix: Corrected Code
Fix 1: no-var → let
File: force-app/main/default/lwc/accountCard/accountCard.js:12
export default class AccountCard extends LightningElement {
handleClick() {
let accountId = this.recordId; // ✓ fixed
let data = this.fetchData(accountId); // ✓ fixed
this.processData(data);
}
}Fix 2: let → const
File: force-app/main/default/lwc/utils/dataProcessor.js:8
export function processRecords(records) {
const result = []; // ✓ fixed
records.forEach(r => result.push(transform(r)));
return result;
}Fix 3: Hardcoded values → SLDS tokens
File: force-app/main/default/lwc/accountCard/accountCard.css:4
.account-card {
border-radius: var(--slds-c-card-radius-border); /* ✓ fixed */
padding: var(--slds-c-card-spacing-block); /* ✓ fixed */
}---
Verification Scan
Command:
sf code-analyzer run --rule-selector Recommended --output-file ./results-after.json --include-fixesSummary:
- Total violations: 181 (↓ 67 from 248)
- Fixable violations: 0
Result: All 67 fixable violations resolved. Remaining 181 violations require manual fixes (e.g., ApexDoc comments, CRUD checks).
---
Key Takeaways
1. Engine-provided fixes are safe: They're deterministic transformations, not AI-generated code. 2. Apply, then verify: Always re-scan after applying fixes to confirm no regressions. 3. Not all violations are fixable: Security issues like CRUD violations require manual code review. 4. Files modified count ≠ fixes count: Multiple violations in one file count as one file modification.
{
"metadata": {
"engine": "Recommended",
"executedAt": "2026-05-19T14:22:45.789Z",
"filesAnalyzed": 2818,
"violationCount": 69545
},
"violations": [
{
"rule": "@lwc/lwc/no-inner-html",
"engine": "eslint",
"severity": 2,
"message": "Disallow use of innerHTML",
"file": "StaticResourceSources/js/BDE_jqtablesorter.min.js",
"line": 3,
"column": 245,
"fix": null
},
{
"rule": "@salesforce-ux/slds/no-hardcoded-values-slds2",
"engine": "eslint",
"severity": 4,
"message": "Replace hardcoded value with SLDS design token",
"file": "StaticResourceSources/Bootstrap/css/bootstrap-s1.css",
"line": 156,
"column": 12,
"fix": {
"range": [4521, 4527],
"text": "var(--slds-c-button-radius-border)"
}
}
],
"summary": {
"bySeverity": {
"1": 0,
"2": 6164,
"3": 24341,
"4": 30230,
"5": 8810
},
"byEngine": {
"eslint": 38542,
"pmd": 18234,
"regex": 12769
},
"topRules": [
{"rule": "@salesforce-ux/slds/no-hardcoded-values-slds2", "count": 18081},
{"rule": "no-var", "count": 9714},
{"rule": "NoTrailingWhitespace", "count": 8073},
{"rule": "ApexDoc", "count": 5533},
{"rule": "ApexUnitTestClassShouldHaveRunAs", "count": 3856},
{"rule": "NoMixedIndentation", "count": 3493},
{"rule": "AnnotationsNamingConventions", "count": 2418},
{"rule": "no-undef", "count": 1891},
{"rule": "AvoidOldSalesforceApiVersions", "count": 1336},
{"rule": "IfElseStmtsMustUseBraces", "count": 1112}
],
"topFiles": [
{"file": "StaticResourceSources/Bootstrap/css/bootstrap-namespaced-s1.css", "count": 1354},
{"file": "force-app/main/default/staticresources/CumulusStaticResources/Bootstrap/css/bootstrap-namespaced-s1.css", "count": 1354},
{"file": "StaticResourceSources/Bootstrap/css/bootstrap-s1.css", "count": 1349},
{"file": "StaticResourceSources/Bootstrap/css/bootstrap-s1.min.css", "count": 1349},
{"file": "force-app/main/default/staticresources/CumulusStaticResources/Bootstrap/css/bootstrap-s1.css", "count": 1349}
],
"fixableCount": 12298
}
}
Examples Directory
Sample outputs and command patterns for the running-code-analyzer skill.
Files
| File | Purpose |
|---|---|
| `basic-scan-output.json` | Small scan (~127 violations) showing typical structure for personal projects |
| `large-scan-output.json` | Large scan (~69k violations) from real NPSP project, demonstrates scale handling |
| `security-focused-output.json` | Security-only scan with all:Security:(1,2) selector, shows critical issues |
| `fix-application-before-after.md` | Before/after comparison showing engine-provided fixes in action |
| `command-variations.md` | 20+ real command patterns with explanations and anti-patterns |
When to Use
As a User/Developer
- Validate your scan output matches expected format
- See real-world command examples for common scenarios
- Understand fix application before running it on your code
As the Agent
- Compare output structure when parsing scan results
- Verify fix format when applying auto-fixes
- Reference command patterns when building complex rule selectors
- Use as templates when explaining results to users
Usage from SKILL.md
These files are reference examples, not loaded by default. Reference them in specific scenarios:
**For large result sets (5000+ violations)**, compare against `examples/large-scan-output.json` to verify your summary format matches the expected structure.
**Before applying fixes**, show the user the before/after comparison from `examples/fix-application-before-after.md` to set expectations.
**For complex command construction**, reference `examples/command-variations.md` to find the pattern that matches the user's intent.{
"metadata": {
"engine": "all:Security:(1,2)",
"executedAt": "2026-05-19T16:45:12.456Z",
"filesAnalyzed": 156,
"violationCount": 43
},
"violations": [
{
"rule": "ApexCRUDViolation",
"engine": "pmd",
"severity": 2,
"message": "Validate CRUD permission before SOQL/DML operation",
"file": "force-app/main/default/classes/AccountService.cls",
"line": 42,
"column": 9,
"fix": null
},
{
"rule": "ApexSOQLInjection",
"engine": "pmd",
"severity": 1,
"message": "Avoid untrusted/unescaped variables in DML query",
"file": "force-app/main/default/classes/SearchController.cls",
"line": 18,
"column": 24,
"fix": null
},
{
"rule": "@lwc/lwc/no-inner-html",
"engine": "eslint",
"severity": 2,
"message": "Disallow use of innerHTML (XSS risk)",
"file": "force-app/main/default/lwc/riskComponent/riskComponent.js",
"line": 28,
"column": 9,
"fix": null
},
{
"rule": "ApexInsecureEndpoint",
"engine": "pmd",
"severity": 2,
"message": "Endpoint protocol should be https",
"file": "force-app/main/default/classes/ExternalApiClient.cls",
"line": 56,
"column": 20,
"fix": null
},
{
"rule": "ApexOpenRedirect",
"engine": "pmd",
"severity": 2,
"message": "Potential open redirect from user-controlled input",
"file": "force-app/main/default/classes/RedirectController.cls",
"line": 34,
"column": 16,
"fix": null
},
{
"rule": "ApexXSSFromEscapeFalse",
"engine": "pmd",
"severity": 2,
"message": "Avoid using escape=false in Visualforce pages",
"file": "force-app/main/default/pages/AccountDetail.page",
"line": 23,
"column": 45,
"fix": null
}
],
"summary": {
"bySeverity": {
"1": 8,
"2": 35,
"3": 0,
"4": 0,
"5": 0
},
"byEngine": {
"pmd": 38,
"eslint": 5
},
"topRules": [
{"rule": "ApexCRUDViolation", "count": 18},
{"rule": "ApexInsecureEndpoint", "count": 9},
{"rule": "ApexSOQLInjection", "count": 8},
{"rule": "@lwc/lwc/no-inner-html", "count": 5},
{"rule": "ApexOpenRedirect", "count": 3}
],
"topFiles": [
{"file": "force-app/main/default/classes/AccountService.cls", "count": 12},
{"file": "force-app/main/default/classes/SearchController.cls", "count": 8},
{"file": "force-app/main/default/classes/ExternalApiClient.cls", "count": 7}
]
}
}
Command Construction Examples
Full command examples for common scanning scenarios.
Note: All commands use ${TIMESTAMP} which should be generated via TIMESTAMP=$(date +%Y%m%d-%H%M%S) before running the scan.
| User Request | Constructed Command |
|---|---|
| "Scan my code" | sf code-analyzer run --rule-selector Recommended --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Check for security issues" | sf code-analyzer run --rule-selector Security --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Run PMD on my Apex" | sf code-analyzer run --rule-selector pmd --target "**/*.cls,**/*.trigger" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Check only my changed files" | `git diff --name-only main...HEAD \ |
| "Find duplicate code" | sf code-analyzer run --rule-selector cpd --output-file "./code-analyzer-results-${TIMESTAMP}.json" |
| "Check vulnerable libraries" | sf code-analyzer run --rule-selector retire-js --output-file "./code-analyzer-results-${TIMESTAMP}.json" |
| "Run deep security analysis" | sf code-analyzer run --rule-selector sfge --workspace "force-app" --target "force-app" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Critical PMD violations in this file" | sf code-analyzer run --rule-selector "pmd:1" --target <file> --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "ESLint performance on LWC" | sf code-analyzer run --rule-selector "eslint:Performance" --target "**/lwc/**" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "AppExchange security review" | sf code-analyzer run --rule-selector all --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Generate HTML report" | sf code-analyzer run --rule-selector Recommended --output-file "./code-analyzer-results-${TIMESTAMP}.html" --include-fixes |
| "Scan with severity threshold 2" | sf code-analyzer run --rule-selector Recommended --severity-threshold 2 --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Run ApexCRUDViolation rule" | sf code-analyzer run --rule-selector "pmd:ApexCRUDViolation" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Scan my Flows" | sf code-analyzer run --rule-selector flow --output-file "./code-analyzer-results-${TIMESTAMP}.json" |
| "Check ESLint recommended rules" | sf code-analyzer run --rule-selector "eslint:Recommended" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Scan all with fail on high" | sf code-analyzer run --rule-selector all --severity-threshold 2 --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "What rules are available for security?" | sf code-analyzer rules --rule-selector Security --view detail |
| "Scan this file for performance" | sf code-analyzer run --rule-selector Performance --target <file> --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
| "Run all rules, no suppressions" | sf code-analyzer run --rule-selector all --no-suppressions --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes |
Engine Reference
Engine File Type Support
| Engine | File Extensions |
|---|---|
| pmd | .cls, .trigger, .js, .html, .htm, .vfp, .component, .page, .xml |
| eslint | .js, .ts, .jsx, .tsx |
| cpd | .cls, .trigger, .js, .ts, .html, .htm, .vfp, .component, .page, .xml |
| retire-js | .js, .ts, package.json, package-lock.json |
| regex | Configurable per rule via file_extensions |
| flow | .flow-meta.xml |
| sfge | .cls, .trigger |
| apexguru | .cls, .trigger |
Common Rule Tags
These tags can be used in rule selectors:
| Tag | Meaning |
|---|---|
Recommended | Default ruleset — curated for most projects |
Security | Security vulnerabilities (CRUD, XSS, injection, crypto) |
Performance | Performance anti-patterns (SOQL in loops, limits) |
BestPractices | Coding standards and conventions |
CodeStyle | Naming, formatting, braces |
Design | Complexity, coupling, architecture |
ErrorProne | Common bug patterns |
Documentation | Missing docs, comments |
Apex | Rules applying to Apex language |
JavaScript | Rules applying to JavaScript |
TypeScript | Rules applying to TypeScript |
HTML | Rules applying to HTML/Visualforce |
Custom | User-defined rules |
Error Handling Guide
Common Code Analyzer errors and their resolutions.
Common Errors and Resolutions
| Error Pattern | Likely Cause | Resolution |
|---|---|---|
command not found: sf | SF CLI not installed | "Install Salesforce CLI: npm install -g @salesforce/cli" |
plugin-code-analyzer not found | Plugin not installed | "Install: sf plugins install @salesforce/plugin-code-analyzer" |
Java not found / JAVA_HOME not set | Java missing/misconfigured | "Install Java 11+. Set JAVA_HOME or add engines.pmd.java_command to config" |
Node.js version error | Old Node version | "Upgrade Node.js to v18+" |
Python not found (Flow engine) | Python not installed | "Install Python 3. Or set engines.flow.python_command in config" |
Config file error / YAML parse error | Invalid code-analyzer.yml | "Your config file has a syntax error. Run sf code-analyzer config to validate" |
No rules matched selector | Invalid rule selector | Check selector syntax. Run sf code-analyzer rules --rule-selector <selector> to verify |
Target file does not exist | File path typo or deleted | Verify file path exists |
Org not authenticated (ApexGuru) | No default org | "Authenticate: sf org login web --alias myorg" |
| Timeout / heap space | Large project + SFGE | "Increase heap: add engines.sfge.java_max_heap_size: '4g' to code-analyzer.yml" |
Diagnosis Steps
If the scan command fails:
1. Check the error message for hints 2. Run sf --version to verify CLI 3. Run sf plugins --core | grep code-analyzer to verify plugin 4. Run java -version to verify Java 5. Run sf code-analyzer rules --rule-selector <selector> to verify the selector matches rules 6. If config error: run sf code-analyzer config to validate
Flag Reference for sf code-analyzer run
Complete reference for all flags available in the sf code-analyzer run command (v4+).
Valid Flags
| Flag | Short | Type | Description | Default |
|---|---|---|---|---|
--rule-selector | -r | String | Rule selection expression (engine, category, severity, or specific rule) | Recommended |
--target | -t | String[] | Files/folders/globs to scan (comma-separated) | Current directory |
--workspace | -w | String | Workspace root directory | . (current directory) |
--output-file | -f | String[] | Output file path(s) — format determined by extension (.json, .html, .sarif, .csv, .xml) | None (terminal only) |
--view | -v | String | Terminal display format: table or detail | None |
--severity-threshold | -s | Number | Exit non-zero if violations at or above this level (1-5) | None |
--config-file | -c | String | Path to code-analyzer.yml configuration file | None |
--include-fixes | Boolean | Include fix data in results (enables auto-fix capability) | false | |
--include-suggestions | Boolean | Include suggestion data in results | false | |
--no-suppressions | Boolean | Ignore suppression markers in code | false | |
--target-org | -o | String | Salesforce org username or alias (required for ApexGuru engine) | None |
Invalid Flags (DO NOT USE)
These flags existed in v3 but were removed in v4+. Using them causes errors:
| Deprecated Flag | Error Message | Replacement |
|---|---|---|
--format | Unknown flag: --format | Use --output-file <path>.<ext> where extension determines format |
--format table | Unknown flag: --format | Use --view table or --view detail |
--engine | Unknown flag: --engine | Use --rule-selector <engine> |
--category | Unknown flag: --category | Use --rule-selector <category> |
--json | Unknown flag: --json | Use --output-file "./results.json" |
Rule Selector Syntax
The --rule-selector flag uses a flexible expression syntax:
| Syntax | Description | Example |
|---|---|---|
<engine> | Select all rules from an engine | pmd, eslint, cpd |
<category> | Select rules by category | Security, Performance |
<severity> | Select rules by severity (1-5) | 1, 2, (1,2) |
<engine>:<category> | Engine AND category | pmd:Security |
<engine>:<severity> | Engine AND severity | eslint:2 |
(<a>,<b>) | OR grouping | (pmd,eslint) for PMD OR ESLint |
<a>:<b>:<c> | Multiple AND conditions | pmd:Security:1 for PMD AND Security AND Severity 1 |
<engine>:<ruleName> | Specific rule | pmd:ApexCRUDViolation |
all | All available rules | all |
Recommended | Default recommended rule set | Recommended (default) |
Complex Rule Selector Examples
# PMD OR ESLint, Security category, Severity 1 or 2
--rule-selector "(pmd,eslint):Security:(1,2)"
# All Security rules across all engines
--rule-selector "Security"
# Specific rule from PMD
--rule-selector "pmd:ApexCRUDViolation"
# All rules from CPD (duplicate detection)
--rule-selector "cpd"
# High and Critical severity across all engines
--rule-selector "(1,2)"Output Format Extensions
The --output-file flag determines format by file extension:
| Extension | Format | Use Case |
|---|---|---|
.json | JSON | Programmatic parsing, default for this skill |
.html | HTML | Human-readable report for browser viewing |
.sarif | SARIF | IDE integration (VS Code, IntelliJ) or GitHub Advanced Security |
.csv | CSV | Spreadsheet import (Excel, Google Sheets) |
.xml | XML | Legacy CI/CD systems |
You can specify multiple output files in a single run:
--output-file "./results.json" --output-file "./report.html"Why These Constraints Exist
v4+ CLI redesign: The Code Analyzer plugin underwent a major redesign from v3 to v4+:
- Old flags (
--format,--engine,--category) were removed for a more flexible--rule-selectorexpression syntax - Output format is now determined by file extension rather than a separate flag
- Terminal display was separated into
--viewflag - These changes provide more flexibility but require different syntax than v3 documentation shows
Always use `--output-file` for results: Terminal stdout can be truncated, interrupted, or mixed with other output. Writing to a file ensures complete, parseable results.
Foreground execution with timeout: SFGE (Salesforce Graph Engine) scans can take 10-20 minutes for large codebases. Running in foreground with timeout: 1200000 (20 minutes) ensures the scan completes and output is captured.
Post-Scan Workflows
After presenting initial scan results (Step 5), the user may ask follow-up questions to explore results or understand violations. This reference covers three post-scan workflows:
1. Result Querying — filter/drill into existing results without re-scanning 2. Rule Description — explain what a rule does and how to fix violations 3. Rule Listing — browse available rules without running a scan
---
Result Querying (Step 7)
When to Use
Trigger this workflow when the user asks to explore existing results:
- "Show me just the security violations"
- "What's in AccountService.cls?"
- "Show only PMD issues"
- "Filter to severity 1 and 2"
- "What ESLint rules fired?"
- "Show violations in the lwc folder"
- "Top 20 by file"
How It Works
The query-results.js script re-filters the SAME results JSON file (from Step 4) with different criteria. No re-scan is needed — it is instant.
Script Reference
node "<skill_dir>/scripts/query-results.js" "<results-file.json>" [options]Filter options (combine any):
| Option | Description | Example |
|---|---|---|
--engine <name> | Filter by engine | --engine pmd |
--severity <n> | Filter by severity (comma-separated) | --severity 1,2 |
--category <tag> | Filter by category/tag | --category Security |
--rule <name> | Filter by exact rule name | --rule ApexCRUDViolation |
--file <substring> | Filter by file path substring | --file AccountService |
--top <n> | Return top N results (default: 10) | --top 20 |
--sort <field> | Sort by: severity, rule, engine, file | --sort file |
--sort-dir <dir> | Sort direction: asc, desc | --sort-dir desc |
--summary | Show only counts (no individual violations) | --summary |
Options can be combined freely:
# Security violations in PMD, top 5
node "<skill_dir>/scripts/query-results.js" "./results.json" --engine pmd --category Security --top 5
# All Critical+High in a specific file
node "<skill_dir>/scripts/query-results.js" "./results.json" --severity 1,2 --file AccountService.cls
# Summary of ESLint issues only
node "<skill_dir>/scripts/query-results.js" "./results.json" --engine eslint --summaryOutput Format
The script outputs JSON with this structure:
{
"query": { "engine": "pmd", "severity": [1,2], ... },
"totalViolations": 500,
"totalMatches": 23,
"severityCounts": { "1": 5, "2": 18, "3": 0, "4": 0, "5": 0 },
"topRules": [{ "rule": "ApexCRUDViolation", "engine": "pmd", "count": 12 }, ...],
"topFiles": [{ "file": "AccountService.cls", "count": 8 }, ...],
"violations": [
{ "rule": "...", "engine": "...", "severity": 1, "message": "...", "file": "...", "startLine": 42, "tags": [...] },
...
]
}When --summary is used, the violations array is omitted.
Presentation Rules
Present query results using the same format as Step 5, but with a header indicating the active filter:
## Filtered Results: [description of filter]
**X matches** out of Y total violations.
| Severity | Count |
|----------|-------|
| Critical (1) | X |
| High (2) | X |
| ... |
### Matching Violations
| # | Rule | Engine | Sev | File | Line |
|---|------|--------|-----|------|------|
| 1 | ... | ... | ... | ... | ... |
### Top Rules (within filter)
| Rule | Engine | Count |
|------|--------|-------|
| ... | ... | ... |
Full results: `<original-results-file>`Follow-Up Offers
After presenting filtered results, offer:
- "Want me to narrow further?" (add more filters)
- "Want me to explain any of these rules?" (→ Step 8)
- "Want me to apply fixes for these?" (→ Step 6, scoped to matched rules)
---
Rule Description (Step 8)
When to Use
Trigger this workflow when the user asks about a specific rule:
- "What is ApexCRUDViolation?"
- "Explain this rule"
- "What does no-var mean?"
- "How do I fix OperationWithLimitsInLoop?"
- "Tell me about the ApexSOQLInjection rule"
- "Why is this flagged?"
How It Works
The describe-rule.js script calls sf code-analyzer rules with a targeted selector to extract rule metadata including description and documentation links.
Script Reference
node "<skill_dir>/scripts/describe-rule.js" "<rule-name>" [--engine <engine>]Arguments:
| Argument | Description | Example |
|---|---|---|
<rule-name> | The rule name to look up | ApexCRUDViolation |
--engine <engine> | Narrow to a specific engine (optional) | --engine pmd |
Examples:
node "<skill_dir>/scripts/describe-rule.js" "ApexCRUDViolation" --engine pmd
node "<skill_dir>/scripts/describe-rule.js" "no-var" --engine eslint
node "<skill_dir>/scripts/describe-rule.js" "OperationWithLimitsInLoop"Output Format
Success — single rule found:
{
"status": "success",
"rule": {
"name": "ApexCRUDViolation",
"engine": "pmd",
"severity": "2 (High)",
"tags": ["Security", "Recommended", "Apex"],
"description": "Validates that CRUD and FLS checks are performed before DML operations...",
"resources": ["https://pmd.github.io/latest/pmd_rules_apex_security.html#apexcrudviolation"]
}
}Multiple matches (partial name):
{
"status": "multiple_matches",
"message": "Rule \"CRUD\" not found as exact match. Found 3 potential matches:",
"candidates": [
{ "name": "ApexCRUDViolation", "engine": "pmd", "severity": "2", "tags": "Security, Recommended" },
...
]
}Not found:
{
"status": "not_found",
"message": "Rule \"FakeRule\" not found. Verify the rule name with: sf code-analyzer rules ..."
}Presentation Rules
For a successful lookup, present:
## Rule: ApexCRUDViolation
| Property | Value |
|----------|-------|
| Engine | pmd |
| Severity | 2 (High) |
| Tags | Security, Recommended, Apex |
### Description
Validates that CRUD and FLS checks are performed before DML operations. Without these
checks, data may be accessed or modified without proper user permissions, violating
the Salesforce security model.
### How to Fix
[Provide actionable fix guidance based on the description. If the description mentions
a fix pattern, elaborate. If resources are available, include the link.]
### Resources
- [PMD Documentation](https://pmd.github.io/...)
---
Want me to show all violations of this rule in your scan results?For multiple matches, present:
I found multiple rules matching "CRUD":
| # | Rule | Engine | Severity |
|---|------|--------|----------|
| 1 | ApexCRUDViolation | pmd | 2 (High) |
| 2 | ... | ... | ... |
Which rule would you like details on?For not found, present:
I couldn't find a rule named "FakeRule". Would you like me to:
- Search for similar rules? (I'll grep the full rule list)
- List all rules for a specific engine or category?After Describing a Rule
Offer next steps:
- "Want me to show all violations of this rule in your results?" (→ Step 7 with
--rule) - "Want me to apply the engine fix for this rule?" (→ Step 6)
- "Want me to explain another rule?"
---
Rule Listing (Step 9)
Presentation Rules
Present available rules in this format:
## Available Rules: Security
**Found X rules** across Y engines.
| Engine | Count |
|--------|-------|
| pmd | 12 |
| eslint | 6 |
| Severity | Count |
|----------|-------|
| Critical (1) | 3 |
| High (2) | 15 |
### Rules (top 25)
| # | Rule | Engine | Severity | Tags |
|---|------|--------|----------|------|
| 1 | ApexCRUDViolation | pmd | 2 (High) | Security, Recommended |
| 2 | ApexSOQLInjection | pmd | 1 (Critical) | Security, Recommended |
| ... |
Want me to explain any of these rules? Or run a scan with this selector?Follow-Up Offers
After listing rules:
- "Want me to explain any of these?" (→ Step 8)
- "Want me to scan with this selector?" (→ Steps 1-5 with the same selector)
- "Narrow to just high severity?" (re-run with
--severity 1,2)
Quick Start: Minimum Viable Commands
If you're unsure about anything, use these EXACT commands as starting points.
IMPORTANT: Always generate a timestamp variable FIRST, then use it in the output filename:
TIMESTAMP=$(date +%Y%m%d-%H%M%S)Then use it:
# Simplest scan (entire workspace, recommended rules)
sf code-analyzer run --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
# Scan specific target
sf code-analyzer run --target "force-app/main/default" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
# Scan for security
sf code-analyzer run --rule-selector Security --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
# Scan specific engine
sf code-analyzer run --rule-selector pmd --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
# Scan with HTML report (only if user explicitly asks for HTML)
sf code-analyzer run --output-file "./code-analyzer-results-${TIMESTAMP}.html" --include-fixesAfter the command completes, read the output file and present a summary to the user.
Special Behaviors
Advanced scanning scenarios and engine-specific considerations.
SFGE (Salesforce Graph Engine) Scans
When --rule-selector sfge is requested:
- WARN the user: "SFGE performs deep data-flow analysis and can be resource-intensive. It may take several minutes and use significant memory. Proceed?"
- If project is large (>100 Apex classes), suggest increasing heap: "Consider setting
engines.sfge.java_max_heap_size: '4g'in your code-analyzer.yml" - SFGE only analyzes Apex (
.cls,.triggerfiles)
SFGE Workspace Compilation Behavior
CRITICAL: SFGE compiles ALL .cls and .trigger files found anywhere in the --workspace directory (default: . = project root), NOT just files under --target. The --target flag only controls which files are used as entry points for data-flow analysis, but SFGE builds a complete inter-procedural call graph from the entire workspace.
This means: 1. If there are invalid/template Apex files ANYWHERE in the project (e.g., datasets/, scripts/, templates/), SFGE will try to compile them and CRASH with compilation errors. 2. The `--target` flag does NOT prevent this — even --target "force-app" still causes SFGE to compile files outside force-app/.
To avoid compilation failures, ALWAYS set `--workspace` explicitly for SFGE scans:
sf code-analyzer run --rule-selector sfge --workspace "force-app" --target "force-app" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixesOr if the user specifies a subfolder target like force-app/main:
sf code-analyzer run --rule-selector sfge --workspace "force-app" --target "force-app/main" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixesThe --workspace flag restricts which files SFGE compiles into its graph. Set it to the narrowest directory that contains all valid, deployable Apex source code (typically force-app or src).
ApexGuru Scans
When --rule-selector apexguru is requested:
- Check org authentication: Run
sf org displayfirst - If no org authenticated: Guide user to
sf org login weborsf org login jwt - Add
--target-org <alias>flag if user has specified an org - ApexGuru analyzes Apex performance patterns via cloud service
AppExchange Security Review Scans
When user mentions "AppExchange", "security review", "ISV", "partner":
- Use
--rule-selector allto run comprehensive scan - Output both JSON and HTML:
--output-file "./code-analyzer-results-${TIMESTAMP}.json" --output-file "./code-analyzer-results-${TIMESTAMP}.html" - In results, categorize violations as:
- Blockers (sev 1-2, Security tag): MUST fix before submission
- Warnings (sev 3, Security/BestPractices): Strongly recommended to fix
- Informational (sev 4-5): Good to fix but won't block review
- Highlight specific AppExchange-critical rules:
ApexCRUDViolation(CRUD/FLS enforcement)ApexSharingViolations(sharing model)ApexSOQLInjection(injection prevention)ApexCSRF(CSRF protection)ApexXSSFromEscapeFalse/ApexXSSFromURLParam(XSS prevention)ApexInsecureEndpoint(HTTPS enforcement)ApexBadCrypto(crypto standards)ApexSuggestUsingNamedCred(credential management)
Diff-Based Scans
When user wants to scan only changed files: 1. Determine the base reference:
- "my changes" / "what I changed" →
git diff --name-only(unstaged) orgit diff --name-only --cached(staged) - "branch changes" / "since main" →
git diff --name-only main...HEAD - "last commit" →
git diff --name-only HEAD~1
2. Filter to scannable file types:
git diff --name-only main...HEAD | grep -E '\.(cls|trigger|js|ts|html|css|xml|flow-meta\.xml)$'3. If no scannable files changed: "No scannable files in your diff. Code Analyzer supports: .cls, .trigger, .js, .ts, .html, .css, .xml, .flow-meta.xml" 4. Pass filtered files as comma-separated --target value
Large Result Sets (500+ violations)
- Summarize: top 10 rules by frequency, top 10 files by violation count
- Offer: "Want me to export the full results? Or focus on a specific category/file?"
- Don't try to display all 500+ violations inline
Mega Result Sets (5000+ violations)
- Same as above, but also proactively suggest narrowing scope:
- "This is a very large number of violations. Want me to focus on just Critical/High severity, a specific category like Security, or a specific folder?"
- If the user originally said "scan and fix everything", still follow the full flow (scan → present → discover fixes → ask → apply → summarize) — do NOT shortcut any steps just because the result set is large
Vendor File Handling
Problem
Code Analyzer scans all JavaScript files, including third-party vendor libraries like jQuery, Bootstrap, Lodash, Handlebars, etc. These libraries often trigger thousands of violations, especially:
- no-var (legacy
vardeclarations) - prefer-const (variables that could be const)
- code style (indentation, quotes, semicolons)
A typical scan might find:
- 9,714 total violations
- 9,089 in vendor files (jQuery UI, Bootstrap, tablesorter)
- 634 in project source (your Aura/LWC components)
Why You Shouldn't Fix Vendor Files
| Risk | Impact |
|---|---|
| Breaks upgrades | Modified vendor files can't be cleanly upgraded to newer versions |
| Untested changes | Libraries weren't designed for strict mode or modern JS patterns |
| Subtle bugs | Converting var to let/const can change scope/hoisting behavior in legacy code |
| Maintainability | Future developers won't know the file was modified and why |
| Wasted effort | The next library upgrade will overwrite your fixes anyway |
Solutions
Solution 1: Re-scan with --target (Fastest)
If you know your project source locations upfront:
sf code-analyzer run --rule-selector <selector> \
--target "force-app/main/default/aura,force-app/main/default/lwc" \
--output-file "./results-project-only.json" \
--include-fixesPros:
- Only scans what you need
- Faster execution
- Cleaner results
Cons:
- Must know target directories upfront
- Doesn't show you what violations exist in vendor files (for awareness)
Solution 2: Intelligent Filtering (Most Accurate)
Scan everything first, then use the intelligent filter script to separate vendor from project:
# 1. Run full scan
sf code-analyzer run --rule-selector <selector> \
--output-file "./results-all.json" --include-fixes
# 2. Filter to project files only
node "<skill_dir>/scripts/filter-violations.js" \
"./results-all.json" \
"./results-project.json" \
--report
# 3. Apply fixes to filtered results
node "<skill_dir>/scripts/apply-fixes.js" "./results-project.json"Pros:
- Intelligent classification using multiple heuristics
- Shows you vendor vs project breakdown
- Handles uncertain files (30-70% confidence)
- No manual pattern maintenance
Cons:
- Scans more files than necessary
- Takes longer for large codebases
How the Intelligent Filter Works
The filter-violations.js script uses a multi-heuristic confidence scoring system:
1. Path-Based Signals (30% weight)
// High confidence vendor indicators
node_modules/ → 100% vendor
bower_components/ → 100% vendor
vendor/ → 95% vendor
third-party/ → 95% vendor
StaticResourceSources/ → 70% vendor
// Project source indicators
force-app/main/default/aura/ → Project
force-app/main/default/lwc/ → Project2. Name-Based Signals (30% weight)
// Filename patterns
*.min.js → 95% vendor (minified)
*-1.12.1.js → 85% vendor (version in name)
jquery*.js → 85% vendor (known library)
bootstrap*.js → 85% vendor (known library)
// Checked against package.json dependencies3. Content-Based Signals (40% weight)
// License headers
MIT License, Apache, BSD, GPL → 80% vendor
// Minification indicators
Average line length > 500 chars → 90% vendor
< 10 lines but > 5KB file → 85% vendor
// Library patterns
UMD/AMD/CommonJS wrapper → 70% vendor
@version x.x.x → 65% vendor
@author (non-project) → 50% vendorFinal Score
Weighted Score = (PathScore × 0.3) + (NameScore × 0.3) + (ContentScore × 0.4)
> 70% = Vendor file
< 30% = Project file
30-70% = Uncertain (manual review)Example Output
=== INTELLIGENT VENDOR FILE DETECTION ===
Original violations: 9714
Filtered violations: 634
Reduction: 9080 (93.5%)
📦 Vendor files excluded: 127
610 violations | 95% confidence | jquery-ui-1.12.1.js
located in vendor directory, version number in filename, minified file
525 violations | 98% confidence | jquery-ui-1.12.1.min.js
located in vendor directory, minified file (.min.js)
... and 125 more vendor files
✅ Project files included: 39
157 violations | CRLP_RollupHelper.js
103 violations | HH_ContainerHelper.js
84 violations | CRLP_FilterGroupHelper.js
...
⚠️ Uncertain files: 2
These files have 30-70% vendor confidence - review manually:
45 violations | 55% vendor | customUtility.js
located in vendor directory
✓ Filtered results written to: ./results-project.jsonWorkflow Integration
When to Use Each Approach
| Scenario | Recommended Approach |
|---|---|
| User says "fix no-var in my code" | Use intelligent filter (excludes vendor by default) |
| User says "fix all no-var" | Ask: "Including vendor files (jQuery, Bootstrap)?" |
| User specifies path | Use --target directly |
| User wants report first | Full scan → intelligent filter → show breakdown |
Step-by-Step Workflow
1. Run Code Analyzer scan
2. Parse results
3. **Check violation distribution:**
- If 50%+ are in vendor files → offer intelligent filtering
- If user said "my code" or "project" → automatically filter
4. Discover fixes (on filtered or unfiltered results)
5. Apply fixes
6. SummarizeEdge Cases
Case 1: Vendored Modified Libraries
Scenario: Your org has modified a copy of jQuery
Solution: The intelligent filter will classify it as vendor, but violations may be legitimate. Options: 1. Fix manually after filter identifies it 2. Re-run with --target excluding that specific file 3. Add to project exceptions in filter script
Case 2: Project Code in Static Resources
Scenario: Your custom JavaScript is in staticresources/ alongside vendor libs
Solution: The filter checks content + name, not just path. Custom code without vendor markers scores as "project" or "uncertain" for manual review.
Case 3: Uncertain Classifications
Scenario: File scores 30-70% vendor confidence
Action: Filter script reports these separately. Review manually:
- Check file purpose
- Look for original source/documentation
- Decide whether to fix or exclude
Configuration (Future Enhancement)
The filter script could accept custom patterns:
node filter-violations.js results.json filtered.json \
--exclude-patterns "*.min.js,jquery*,bootstrap*" \
--include-patterns "force-app/main/default/aura/**,force-app/main/default/lwc/**"Currently uses intelligent defaults and doesn't require configuration.
Testing the Filter
# Run with detailed report
node scripts/filter-violations.js \
./code-analyzer-results-20260519-133252.json \
./filtered-output.json \
--report
# Check the output
node scripts/parse-results.js ./filtered-output.jsonCompare before/after violation counts to verify filtering accuracy.
#!/usr/bin/env node
// Apply engine-provided auto-fixes to source files
// Usage: node apply-fixes.js <path-to-results.json>
// WARNING: This modifies files in place. Ensure you have backups or are using version control.
const fs = require("fs");
const path = require("path");
if (process.argv.length < 3) {
console.error("Usage: node apply-fixes.js <results-file.json>");
process.exit(1);
}
const filePath = process.argv[2];
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const runDir = data.runDir || "";
// Group fixes by file
const fileFixesMap = new Map();
data.violations.forEach(v => {
if (v.fixes && v.fixes.length > 0) {
v.fixes.forEach(fix => {
const loc = fix.location;
let filePath = loc.file;
if (runDir && filePath.startsWith(runDir)) filePath = filePath.substring(runDir.length + 1);
if (!fileFixesMap.has(filePath)) fileFixesMap.set(filePath, []);
fileFixesMap.get(filePath).push({
startLine: loc.startLine,
startColumn: loc.startColumn,
endLine: loc.endLine,
endColumn: loc.endColumn,
fixedCode: fix.fixedCode,
rule: v.rule
});
});
}
});
// Sort fixes by line/column (descending) to apply bottom-up
// This ensures earlier fixes don't shift line numbers for later ones
fileFixesMap.forEach((fixes, file) => {
fixes.sort((a, b) => {
if (b.startLine !== a.startLine) return b.startLine - a.startLine;
return b.startColumn - a.startColumn;
});
});
// Apply fixes to each file
let filesModified = 0;
let fixesApplied = 0;
let fixesSkipped = 0;
fileFixesMap.forEach((fixes, filePath) => {
try {
const content = fs.readFileSync(filePath, "utf8");
const lines = content.split("\n");
fixes.forEach(fix => {
const startIdx = fix.startLine - 1;
const endIdx = fix.endLine - 1;
if (startIdx < 0 || endIdx >= lines.length || startIdx > endIdx) {
fixesSkipped++;
return;
}
// Handle multi-line replacements: splice out old lines, insert new content
const firstLine = lines[startIdx];
const lastLine = lines[endIdx];
const before = firstLine.substring(0, fix.startColumn - 1);
const after = lastLine.substring(fix.endColumn - 1);
const replacement = before + fix.fixedCode + after;
// Remove the spanned lines and insert the replacement
lines.splice(startIdx, endIdx - startIdx + 1, replacement);
fixesApplied++;
});
fs.writeFileSync(filePath, lines.join("\n"), "utf8");
filesModified++;
} catch (err) {
console.error("Error fixing " + filePath + ": " + err.message);
}
});
console.log(JSON.stringify({ success: true, filesModified, fixesApplied, fixesSkipped, totalFixableFiles: fileFixesMap.size }));
#!/usr/bin/env node
// Version: v1.1 | SHA256: placeholder
// Get detailed description and documentation for a Code Analyzer rule
// Usage: node describe-rule.js <rule-name> [--engine <engine>]
//
// This script runs `sf code-analyzer rules --view detail` with a targeted
// selector and parses the output to extract rule details including description,
// severity, tags, and documentation resources.
//
// The CLI output format is:
// === 1. RuleName
// severity: 2 (High)
// engine: pmd
// tags: Recommended, Security, Apex
// resource: https://...
// description: Some description text
const { execSync } = require("child_process");
function printUsage() {
console.error(`Usage: node describe-rule.js <rule-name> [--engine <engine>]
Arguments:
<rule-name> The rule name to look up (case-insensitive partial match)
Options:
--engine <engine> Narrow lookup to a specific engine (pmd, eslint, cpd, etc.)
Examples:
node describe-rule.js ApexCRUDViolation
node describe-rule.js ApexCRUDViolation --engine pmd
node describe-rule.js no-var --engine eslint
node describe-rule.js OperationWithLimitsInLoop`);
process.exit(1);
}
// Parse CLI arguments
const args = process.argv.slice(2);
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
printUsage();
}
const ruleName = args[0];
let engine = null;
for (let i = 1; i < args.length; i++) {
if (args[i] === "--engine" && args[i + 1]) {
engine = args[++i].toLowerCase();
}
}
// Build the rule selector for the lookup
const selector = engine ? `${engine}:${ruleName}` : ruleName;
// Run `sf code-analyzer rules` with --view detail to get full rule info
let rawOutput;
try {
const cmd = `sf code-analyzer rules --rule-selector "${selector}" --view detail 2>&1`;
rawOutput = execSync(cmd, {
encoding: "utf8",
timeout: 60000,
maxBuffer: 2 * 1024 * 1024,
});
} catch (err) {
// execSync throws on non-zero exit, but we still want the output
rawOutput = err.stdout || err.stderr || (err.output && err.output.join("")) || "";
if (!rawOutput) {
console.log(JSON.stringify({
status: "error",
message: `Failed to run sf code-analyzer rules: ${err.message}`,
}));
process.exit(0);
}
}
// Parse the detail view output
// Format: === N. RuleName\n key: value\n key: value\n
const rules = parseDetailOutput(rawOutput);
if (rules.length === 0) {
// Try grep fallback for partial/substring match
const grepResult = tryGrepFallback(ruleName, engine);
if (grepResult) {
console.log(JSON.stringify(grepResult));
process.exit(0);
}
// Try fuzzy match as final fallback (catches typos like "Violtion" → "Violation")
const fuzzyResult = tryFuzzyFallback(ruleName, engine);
if (fuzzyResult) {
console.log(JSON.stringify(fuzzyResult));
process.exit(0);
}
console.log(JSON.stringify({
status: "not_found",
message: `Rule "${ruleName}" not found${engine ? ` in engine "${engine}"` : ""}. Verify the rule name with: sf code-analyzer rules --rule-selector ${engine || "all"} 2>&1 | grep -i "${ruleName}"`,
}));
process.exit(0);
}
// Find exact match (case-insensitive)
const exactMatch = rules.find(
(r) => r.name.toLowerCase() === ruleName.toLowerCase()
);
if (exactMatch) {
console.log(JSON.stringify({
status: "success",
rule: exactMatch,
}));
} else if (rules.length === 1) {
// Single result, use it
console.log(JSON.stringify({
status: "success",
rule: rules[0],
}));
} else {
// Multiple matches
console.log(JSON.stringify({
status: "multiple_matches",
message: `Found ${rules.length} rules matching "${ruleName}":`,
candidates: rules.map((r) => ({
name: r.name,
engine: r.engine,
severity: r.severity,
tags: r.tags.join(", "),
})),
}));
}
/**
* Parse the `sf code-analyzer rules --view detail` output format.
*
* Expected format:
* === 1. RuleName
* severity: 2 (High)
* engine: pmd
* tags: Recommended, Security, Apex
* resource: https://pmd.github.io/...
* description: Validates that CRUD permissions...
*/
function parseDetailOutput(output) {
const rules = [];
const lines = output.split("\n");
let currentRule = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Match rule header: === N. RuleName (must have a number prefix)
// Skip "=== Summary" which is the footer section
const headerMatch = line.match(/^===\s+(\d+)\.\s+(.+)$/);
if (headerMatch) {
if (currentRule && currentRule.name) {
rules.push(currentRule);
}
currentRule = {
name: headerMatch[2].trim(),
engine: "unknown",
severity: "unknown",
tags: [],
description: "",
resources: [],
};
continue;
}
// Skip lines if no current rule context
if (!currentRule) continue;
// Match key-value pairs (indented with spaces):
// severity: 3 (Moderate)
// engine: eslint
// tags: Recommended, BestPractices, JavaScript
// resource: https://...
// description: Some text here
const kvMatch = line.match(/^\s{2,}(\w+):\s+(.+)$/);
if (kvMatch) {
const key = kvMatch[1].toLowerCase();
const value = kvMatch[2].trim();
switch (key) {
case "severity":
currentRule.severity = value;
break;
case "engine":
currentRule.engine = value;
break;
case "tags":
currentRule.tags = value.split(",").map((t) => t.trim()).filter(Boolean);
break;
case "resource":
currentRule.resources.push(value);
break;
case "description":
currentRule.description = value;
// Description may continue on next lines (indented further)
while (
i + 1 < lines.length &&
lines[i + 1].match(/^\s{14,}/) &&
!lines[i + 1].match(/^\s{2,}\w+:/)
) {
i++;
currentRule.description += " " + lines[i].trim();
}
break;
}
}
}
// Push the last rule
if (currentRule && currentRule.name) {
rules.push(currentRule);
}
return rules;
}
/**
* Fallback: grep the full rule list for partial matches
*/
function tryGrepFallback(ruleName, engine) {
try {
const sel = engine || "Recommended";
const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1 | grep -i "${ruleName}"`;
const grepOutput = execSync(cmd, {
encoding: "utf8",
timeout: 60000,
maxBuffer: 2 * 1024 * 1024,
});
if (!grepOutput.trim()) return null;
// Parse table output lines
// Format: index name engine severity tags
const candidates = grepOutput
.trim()
.split("\n")
.filter((line) => line.trim() && !line.startsWith("─") && !line.startsWith("="))
.slice(0, 10)
.map((line) => {
const parts = line.trim().split(/\s{2,}/);
// Try to identify which part is the rule name (usually index 1 after the row number)
if (parts.length >= 4) {
return {
name: parts[1] || parts[0],
engine: parts[2] || "unknown",
severity: parts[3] || "unknown",
tags: parts[4] || "",
};
}
return { name: line.trim(), engine: "unknown", severity: "unknown", tags: "" };
});
if (candidates.length === 0) return null;
return {
status: "multiple_matches",
message: `Rule "${ruleName}" not found as exact match. Found ${candidates.length} potential matches:`,
candidates,
};
} catch (err) {
return null;
}
}
/**
* Fuzzy fallback: get all rule names and find closest matches by edit distance.
* Catches typos like "ApexCRUDVioltion" → "ApexCRUDViolation"
*/
function tryFuzzyFallback(ruleName, engine) {
try {
const sel = engine || "Recommended";
const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1`;
const output = execSync(cmd, {
encoding: "utf8",
timeout: 60000,
maxBuffer: 2 * 1024 * 1024,
});
// Extract rule names from table output
// Lines with rule data have: index name engine severity tags
const ruleNames = [];
const ruleInfo = {};
output.split("\n").forEach((line) => {
const parts = line.trim().split(/\s{2,}/);
if (parts.length >= 4 && /^\d+$/.test(parts[0])) {
const name = parts[1];
ruleNames.push(name);
ruleInfo[name] = {
name: name,
engine: parts[2] || "unknown",
severity: parts[3] || "unknown",
tags: parts[4] || "",
};
}
});
if (ruleNames.length === 0) return null;
// Score each rule by edit distance to the query
const queryLower = ruleName.toLowerCase();
const scored = ruleNames
.map((name) => ({
name,
distance: levenshtein(queryLower, name.toLowerCase()),
// Also check if query is a subsequence (handles missing chars)
containsSubseq: isSubsequence(queryLower, name.toLowerCase()),
}))
.filter((r) => {
// Only include if distance is reasonable (within 30% of query length)
const maxDistance = Math.max(3, Math.floor(ruleName.length * 0.3));
return r.distance <= maxDistance || r.containsSubseq;
})
.sort((a, b) => a.distance - b.distance)
.slice(0, 5);
if (scored.length === 0) return null;
const candidates = scored.map((s) => ({
...ruleInfo[s.name],
distance: s.distance,
}));
// If the best match is very close (distance <= 2), mark as likely match
const best = scored[0];
if (best.distance <= 2) {
return {
status: "multiple_matches",
message: `Rule "${ruleName}" not found. Did you mean "${best.name}"? (${best.distance} character${best.distance === 1 ? "" : "s"} different)`,
candidates,
};
}
return {
status: "multiple_matches",
message: `Rule "${ruleName}" not found. Closest matches by name similarity:`,
candidates,
};
} catch (err) {
return null;
}
}
/**
* Levenshtein edit distance between two strings
*/
function levenshtein(a, b) {
const m = a.length;
const n = b.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (a[i - 1] === b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
}
return dp[m][n];
}
/**
* Check if 'query' is a subsequence of 'target' (handles missing chars)
* e.g., "CRUDVioltion" is a subsequence of "CRUDViolation"
*/
function isSubsequence(query, target) {
let qi = 0;
for (let ti = 0; ti < target.length && qi < query.length; ti++) {
if (query[qi] === target[ti]) qi++;
}
// Consider it a match if at least 80% of query chars appear in order
return qi >= query.length * 0.8;
}
#!/usr/bin/env node
// Version: v1.0 | SHA256: 19ec035f7132dc162b54a931cfdd882aa473ad80aa21a8a4be1f1127d75168e5
// Discover which violations have engine-provided auto-fixes
// Usage: node discover-fixes.js <path-to-results.json>
const fs = require("fs");
if (process.argv.length < 3) {
console.error("Usage: node discover-fixes.js <results-file.json>");
process.exit(1);
}
const filePath = process.argv[2];
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const runDir = data.runDir || "";
const fixesByRule = {};
let totalFixable = 0;
data.violations.forEach(v => {
if (v.fixes && v.fixes.length > 0) {
totalFixable++;
const rule = v.rule;
if (!fixesByRule[rule]) fixesByRule[rule] = { engine: v.engine, count: 0, severity: v.severity };
fixesByRule[rule].count++;
}
});
const topRules = Object.entries(fixesByRule)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 10)
.map(([rule, info]) => ({ rule, ...info }));
console.log(JSON.stringify({ totalFixable, totalViolations: data.violations.length, topRules }));
#!/usr/bin/env node
/**
* Intelligent vendor file detection for Code Analyzer results
* Uses multiple heuristics to classify files as vendor vs project code
*
* Usage: node filter-violations.js <input.json> <output.json> [--report]
*/
const fs = require('fs');
const path = require('path');
class VendorDetector {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.packageNames = this.loadPackageNames();
this.fileContentCache = new Map();
}
/**
* Load known third-party package names from package.json
*/
loadPackageNames() {
const names = new Set();
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (fs.existsSync(packageJsonPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
Object.keys(pkg.dependencies || {}).forEach(d => names.add(d.toLowerCase()));
Object.keys(pkg.devDependencies || {}).forEach(d => names.add(d.toLowerCase()));
} catch (err) {
// Ignore parsing errors
}
}
return names;
}
/**
* Main classification method - returns classification with confidence score
*/
classifyFile(filePath) {
const scores = {
pathBased: this.scoreByPath(filePath),
nameBased: this.scoreByName(filePath),
contentBased: this.scoreByContent(filePath),
};
// Weighted average (content analysis is more reliable when available)
const weights = {
pathBased: 0.3,
nameBased: 0.3,
contentBased: 0.4,
};
const weightedScore = Object.entries(scores).reduce(
(sum, [key, score]) => sum + score * weights[key],
0
);
const reasons = this.explainScores(scores, filePath);
return {
isVendor: weightedScore > 50,
confidence: weightedScore,
scores,
reasons,
classification: this.getClassificationLabel(weightedScore),
};
}
/**
* Score based on directory/path patterns (0-100)
*/
scoreByPath(filePath) {
let score = 0;
const normalizedPath = filePath.toLowerCase();
// Absolute vendor indicators
if (/node_modules/.test(normalizedPath)) return 100;
if (/bower_components/.test(normalizedPath)) return 100;
if (/vendor\//.test(normalizedPath)) return 95;
if (/third[_-]party/.test(normalizedPath)) return 95;
// Common vendor directory names
if (/\/lib\//i.test(normalizedPath)) score += 60;
if (/StaticResourceSources/i.test(normalizedPath)) score += 70;
if (/CumulusStaticResources/i.test(normalizedPath)) score += 70;
// Salesforce-specific: project source is typically in aura/ or lwc/
if (/force-app\/main\/default\/(aura|lwc)\/[^/]+\//.test(filePath)) {
// In an Aura or LWC component directory - likely project code
return Math.min(score, 20); // Cap score at 20 for project paths
}
// Outside of component directories but in staticresources
if (/staticresources/.test(normalizedPath)) score += 50;
return Math.min(score, 100);
}
/**
* Score based on filename patterns (0-100)
*/
scoreByName(filePath) {
let score = 0;
const basename = path.basename(filePath).toLowerCase();
// Minified files are almost always vendor code
if (/\.min\.js$/.test(basename)) return 95;
if (/\.bundle\.js$/.test(basename)) score += 80;
if (/-min\.js$/.test(basename)) return 95;
// Version numbers in filename (e.g., jquery-1.12.1.js)
if (/-\d+\.\d+(\.\d+)?\.js$/.test(basename)) score += 85;
if (/\d+\.\d+\.\d+/.test(basename)) score += 70;
// Known library name patterns
const knownLibs = [
'jquery', 'lodash', 'underscore', 'moment', 'angular', 'react', 'vue',
'bootstrap', 'foundation', 'handlebars', 'backbone', 'ember', 'knockout',
'typeahead', 'select2', 'datatables', 'chart', 'arbor', 'd3', 'raphael',
'leaflet', 'mapbox', 'three', 'pixi', 'phaser', 'babylonjs',
];
for (const lib of knownLibs) {
if (new RegExp(`\\b${lib}\\b`, 'i').test(basename)) {
score += 85;
break;
}
}
// Check against package.json dependencies
for (const pkgName of this.packageNames) {
if (basename.includes(pkgName)) {
score += 80;
break;
}
}
return Math.min(score, 100);
}
/**
* Score based on file content analysis (0-100)
*/
scoreByContent(filePath) {
try {
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(this.projectRoot, filePath);
if (!fs.existsSync(absolutePath)) {
return 0; // Can't score if file doesn't exist
}
const content = fs.readFileSync(absolutePath, 'utf8');
let score = 0;
// Check first 2000 characters for header information
const header = content.slice(0, 2000);
// License headers strongly indicate vendor code
if (/\b(MIT License|Apache License|BSD License|GPL|ISC License|Mozilla Public License)\b/i.test(header)) {
score += 80;
}
if (/@license\b/i.test(header)) score += 75;
// Copyright notices (but not from the current org)
if (/@copyright\b(?!.*salesforce\.com)/i.test(header)) score += 60;
// Version stamps
if (/@version\s+\d+\.\d+\.\d+/.test(header)) score += 65;
if (/\bv\d+\.\d+\.\d+\b/.test(header)) score += 55;
// Author field that's not project-specific
if (/@author\b/i.test(header) && !/salesforce/i.test(header)) score += 50;
// Check for minification indicators
const lines = content.split('\n');
const avgLineLength = content.length / lines.length;
if (avgLineLength > 500) score += 90; // Extremely long lines = minified
if (avgLineLength > 200) score += 70;
if (lines.length < 10 && content.length > 5000) score += 85; // Very dense
// UMD/AMD/CommonJS wrapper patterns (common in libraries)
if (/\(function\s*\([^)]*\)\s*\{[\s\S]{0,200}(typeof\s+define|typeof\s+module|typeof\s+exports)/.test(header)) {
score += 70;
}
// IIFE wrapping entire file (very common in libraries)
const trimmed = content.trim();
if (/^\(function\s*\(/.test(trimmed) && /\}\s*\)\s*\([^)]*\)\s*;?\s*$/.test(trimmed)) {
score += 60;
}
// Banner comments with project URLs
if (/\bhttps?:\/\/(github\.com|npmjs\.com|unpkg\.com|cdnjs\.com)/i.test(header)) {
score += 75;
}
return Math.min(score, 100);
} catch (err) {
// If we can't read the file, don't penalize it
return 0;
}
}
/**
* Generate human-readable reasons for the classification
*/
explainScores(scores, filePath) {
const reasons = [];
const basename = path.basename(filePath);
if (scores.pathBased > 70) {
reasons.push('located in vendor directory');
}
if (scores.nameBased > 70) {
if (/\.min\.js$/.test(basename)) {
reasons.push('minified file (.min.js)');
} else if (/\d+\.\d+\.\d+/.test(basename)) {
reasons.push('version number in filename');
} else {
reasons.push('matches known library name');
}
}
if (scores.contentBased > 70) {
reasons.push('contains vendor markers (license, version, minification)');
}
if (reasons.length === 0) {
if (scores.pathBased < 30 && scores.nameBased < 30) {
reasons.push('appears to be project source code');
} else {
reasons.push('unclear classification');
}
}
return reasons;
}
/**
* Get classification label based on confidence score
*/
getClassificationLabel(score) {
if (score > 70) return 'vendor';
if (score < 30) return 'project';
return 'uncertain';
}
}
/**
* Filter violations from Code Analyzer results
*/
function filterViolations(inputFile, outputFile, options = {}) {
const results = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
const projectRoot = results.runDir || process.cwd();
const detector = new VendorDetector(projectRoot);
// Group violations by file
const fileViolations = {};
for (const v of results.violations) {
const file = v.locations[v.primaryLocationIndex].file;
if (!fileViolations[file]) {
fileViolations[file] = {
violations: [],
classification: null,
};
}
fileViolations[file].violations.push(v);
}
// Classify each file
const analysis = {
vendor: [],
project: [],
uncertain: [],
};
for (const [file, data] of Object.entries(fileViolations)) {
const classification = detector.classifyFile(file);
data.classification = classification;
const entry = {
file,
violationCount: data.violations.length,
...classification,
};
if (classification.classification === 'vendor') {
analysis.vendor.push(entry);
} else if (classification.classification === 'project') {
analysis.project.push(entry);
} else {
analysis.uncertain.push(entry);
}
}
// Filter violations to project files only
const projectFiles = new Set(analysis.project.map(e => e.file));
const filteredViolations = results.violations.filter(v => {
const file = v.locations[v.primaryLocationIndex].file;
return projectFiles.has(file);
});
// Recalculate severity counts
const severityCounts = { sev1: 0, sev2: 0, sev3: 0, sev4: 0, sev5: 0 };
for (const v of filteredViolations) {
const sev = `sev${v.severity}`;
if (severityCounts[sev] !== undefined) {
severityCounts[sev]++;
}
}
// Create filtered results
const filteredResults = {
...results,
violations: filteredViolations,
violationCounts: {
total: filteredViolations.length,
...severityCounts,
},
filterMetadata: {
filteredAt: new Date().toISOString(),
originalViolations: results.violations.length,
filteredViolations: filteredViolations.length,
vendorFilesExcluded: analysis.vendor.length,
projectFilesIncluded: analysis.project.length,
uncertainFiles: analysis.uncertain.length,
},
};
fs.writeFileSync(outputFile, JSON.stringify(filteredResults, null, 2));
// Print summary
printSummary(results, filteredResults, analysis, options);
return filteredResults;
}
/**
* Print detailed summary report
*/
function printSummary(original, filtered, analysis, options) {
console.log('\n=== INTELLIGENT VENDOR FILE DETECTION ===\n');
console.log(`Original violations: ${original.violations.length}`);
console.log(`Filtered violations: ${filtered.violations.length}`);
console.log(`Reduction: ${original.violations.length - filtered.violations.length} (${((1 - filtered.violations.length / original.violations.length) * 100).toFixed(1)}%)\n`);
console.log(`📦 Vendor files excluded: ${analysis.vendor.length}`);
if (analysis.vendor.length > 0 && options.report) {
const top = analysis.vendor.sort((a, b) => b.violationCount - a.violationCount).slice(0, 10);
top.forEach(e => {
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.confidence.toFixed(0)}% confidence | ${e.file}`);
console.log(` ${e.reasons.join(', ')}`);
});
if (analysis.vendor.length > 10) {
console.log(` ... and ${analysis.vendor.length - 10} more vendor files`);
}
}
console.log(`\n✅ Project files included: ${analysis.project.length}`);
if (analysis.project.length > 0 && options.report) {
const top = analysis.project.sort((a, b) => b.violationCount - a.violationCount).slice(0, 10);
top.forEach(e => {
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.file}`);
});
}
if (analysis.uncertain.length > 0) {
console.log(`\n⚠️ Uncertain files: ${analysis.uncertain.length}`);
console.log(' These files have 30-70% vendor confidence - review manually:');
analysis.uncertain.forEach(e => {
console.log(` ${e.violationCount.toString().padStart(4)} violations | ${e.confidence.toFixed(0)}% vendor | ${e.file}`);
console.log(` ${e.reasons.join(', ')}`);
});
}
console.log(`\n✓ Filtered results written to: ${outputFile}`);
}
// CLI
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: node filter-violations.js <input.json> <output.json> [--report]');
console.error('');
console.error('Options:');
console.error(' --report Show detailed file-by-file analysis');
process.exit(1);
}
const [inputFile, outputFile] = args;
const options = {
report: args.includes('--report'),
};
try {
filterViolations(inputFile, outputFile, options);
} catch (err) {
console.error('Error:', err.message);
process.exit(1);
}
#!/usr/bin/env node
// Version: v1.0 | SHA256: placeholder
// List Code Analyzer rules matching a selector and return structured JSON
// Usage: node list-rules.js <selector> [options]
//
// This script runs `sf code-analyzer rules --rule-selector <selector>` and
// parses the table output into structured JSON for presentation.
//
// The CLI table format is:
// # Name Engine Severity Tags
// 1 @lwc/lwc/no-inner-html eslint 2 (High) Recommended, LWC, Security
//
// Output: JSON with {status, totalRules, rules: [{name, engine, severity, severityNum, tags}], engines, summary}
const { execSync } = require("child_process");
function printUsage() {
console.error(`Usage: node list-rules.js <selector> [options]
Arguments:
<selector> Rule selector (same syntax as --rule-selector)
Examples: "Security", "pmd", "eslint:Recommended",
"(pmd,eslint):Security:(1,2)", "Apex", "JavaScript"
Options:
--engine <name> Filter results to a specific engine after listing
--severity <n> Filter results to specific severity (1-5, comma-separated)
--top <n> Return at most N rules (default: 100)
--count-only Return only counts by engine/severity/category (no rule list)
Valid selector tokens:
Engines: eslint, regex, retire-js, flow, pmd, cpd, sfge
Severities: Critical/1, High/2, Moderate/3, Low/4, Info/5
Categories: Security, Performance, BestPractices, CodeStyle, Design, ErrorProne, Documentation
Languages: Apex, JavaScript, TypeScript, HTML, CSS, Visualforce, XML
Tags: Recommended, Custom, All, DevPreview, LWC, Fixable
Examples:
node list-rules.js "Security"
node list-rules.js "pmd:Security"
node list-rules.js "eslint:Recommended"
node list-rules.js "(pmd,eslint):Security:(1,2)"
node list-rules.js "Apex"
node list-rules.js "JavaScript:BestPractices"
node list-rules.js "Recommended" --count-only
node list-rules.js "all" --engine pmd --severity 1,2 --top 10`);
process.exit(1);
}
// Parse CLI arguments
const args = process.argv.slice(2);
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
printUsage();
}
const selector = args[0];
const options = {
engine: null,
severity: null,
top: 100,
countOnly: false,
};
for (let i = 1; i < args.length; i++) {
switch (args[i]) {
case "--engine":
options.engine = (args[++i] || "").toLowerCase();
break;
case "--severity":
options.severity = (args[++i] || "")
.split(",")
.map((s) => parseInt(s.trim(), 10))
.filter((n) => n >= 1 && n <= 5);
break;
case "--top":
options.top = parseInt(args[++i] || "25", 10);
break;
case "--count-only":
options.countOnly = true;
break;
default:
console.error(`Unknown option: ${args[i]}`);
printUsage();
}
}
// Validate selector tokens before running CLI
const validationError = validateSelector(selector);
if (validationError) {
console.log(JSON.stringify({
status: "invalid_selector",
message: validationError,
hint: "Valid tokens: engines (pmd, eslint, cpd, retire-js, regex, flow, sfge), severities (1-5 or Critical/High/Moderate/Low/Info), categories (Security, Performance, BestPractices, CodeStyle, Design, ErrorProne, Documentation), languages (Apex, JavaScript, TypeScript, HTML, CSS, Visualforce, XML), tags (Recommended, Custom, All, DevPreview, LWC, Fixable)",
}));
process.exit(0);
}
// Run `sf code-analyzer rules`
let rawOutput;
try {
const cmd = `sf code-analyzer rules --rule-selector "${selector}" 2>&1`;
rawOutput = execSync(cmd, {
encoding: "utf8",
timeout: 60000,
maxBuffer: 2 * 1024 * 1024,
});
} catch (err) {
rawOutput = err.stdout || err.stderr || (err.output && err.output.join("")) || "";
if (!rawOutput) {
console.log(JSON.stringify({
status: "error",
message: `Failed to run sf code-analyzer rules: ${err.message}`,
}));
process.exit(0);
}
}
// Parse the table output
const rules = parseTableOutput(rawOutput);
if (rules.length === 0) {
console.log(JSON.stringify({
status: "no_rules_found",
message: `No rules matched selector "${selector}". Check the selector syntax or try a broader query.`,
hint: "Use tokens like: Security, pmd, eslint:Recommended, (1,2), Apex",
}));
process.exit(0);
}
// Apply post-filters
let filtered = rules;
if (options.engine) {
filtered = filtered.filter((r) => r.engine.toLowerCase() === options.engine);
}
if (options.severity) {
filtered = filtered.filter((r) => options.severity.includes(r.severityNum));
}
// Compute summary stats
const engineCounts = {};
const severityCounts = {};
const categoryCounts = {};
filtered.forEach((r) => {
engineCounts[r.engine] = (engineCounts[r.engine] || 0) + 1;
const sevKey = `${r.severityNum} (${severityName(r.severityNum)})`;
severityCounts[sevKey] = (severityCounts[sevKey] || 0) + 1;
(r.tags || []).forEach((tag) => {
const t = tag.trim();
if (["Security", "Performance", "BestPractices", "CodeStyle", "Design", "ErrorProne", "Documentation"].includes(t)) {
categoryCounts[t] = (categoryCounts[t] || 0) + 1;
}
});
});
// Build result
const result = {
status: "success",
selector,
totalRules: filtered.length,
summary: {
byEngine: engineCounts,
bySeverity: severityCounts,
byCategory: categoryCounts,
},
};
if (!options.countOnly) {
result.rules = filtered.slice(0, options.top).map((r) => ({
name: r.name,
engine: r.engine,
severity: r.severity,
severityNum: r.severityNum,
tags: r.tags,
}));
if (filtered.length > options.top) {
result.truncated = true;
result.showing = options.top;
}
}
console.log(JSON.stringify(result));
// --- Helper Functions ---
function parseTableOutput(output) {
const rules = [];
const lines = output.split("\n");
for (const line of lines) {
// Match table rows: starts with spaces + number
// Format: " 1 ruleName engine severity tags"
const match = line.match(/^\s+(\d+)\s{2,}(\S+)\s{2,}(\S+)\s{2,}(\d+\s*\([^)]+\))\s{2,}(.+)$/);
if (match) {
const severityStr = match[4].trim();
const sevNumMatch = severityStr.match(/^(\d)/);
rules.push({
name: match[2].trim(),
engine: match[3].trim(),
severity: severityStr,
severityNum: sevNumMatch ? parseInt(sevNumMatch[1], 10) : 0,
tags: match[5].trim().split(",").map((t) => t.trim()).filter(Boolean),
});
}
}
return rules;
}
function validateSelector(selector) {
if (!selector || !selector.trim()) {
return "Selector cannot be empty.";
}
const VALID_TOKENS = new Set([
// Engines
"eslint", "regex", "retire-js", "flow", "pmd", "cpd", "sfge",
// Severity names
"critical", "high", "moderate", "low", "info",
// Severity numbers
"1", "2", "3", "4", "5",
// General tags
"recommended", "custom", "all",
// Categories
"bestpractices", "codestyle", "design", "documentation", "errorprone", "security", "performance",
// Languages
"apex", "css", "html", "javascript", "typescript", "visualforce", "xml",
// Engine-specific
"devpreview", "lwc", "fixable",
]);
// Split by : (AND), then handle () groups (OR)
const groups = selector.split(":").map((s) => s.trim()).filter(Boolean);
const invalid = [];
for (const group of groups) {
let tokens;
if (group.startsWith("(") && group.endsWith(")")) {
// OR group: (token1,token2)
tokens = group.slice(1, -1).split(",").map((t) => t.trim()).filter(Boolean);
} else {
tokens = [group];
}
for (const token of tokens) {
if (!VALID_TOKENS.has(token.toLowerCase())) {
invalid.push(token);
}
}
}
if (invalid.length > 0) {
return `Invalid selector token(s): ${invalid.join(", ")}. Did you misspell a token?`;
}
return null;
}
function severityName(num) {
const names = { 1: "Critical", 2: "High", 3: "Moderate", 4: "Low", 5: "Info" };
return names[num] || "Unknown";
}
#!/usr/bin/env node
// Version: v1.0 | SHA256: 077933925fea8efb4bcfd2c2fd59d4589a0b31ae34d5c1ac68c2080c8af7d74d
// Parse Code Analyzer JSON results and extract summary data
// Usage: node parse-results.js <path-to-results.json>
const fs = require("fs");
if (process.argv.length < 3) {
console.error("Usage: node parse-results.js <results-file.json>");
process.exit(1);
}
const filePath = process.argv[2];
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const c = data.violationCounts;
const runDir = data.runDir || "";
// Summary counts
const summary = {
total: c.total, sev1: c.sev1, sev2: c.sev2, sev3: c.sev3, sev4: c.sev4, sev5: c.sev5,
topViolations: [],
topRules: [],
topFiles: []
};
// Top 10 violations sorted by severity
const sorted = data.violations.slice().sort((a, b) => a.severity - b.severity || a.rule.localeCompare(b.rule));
sorted.slice(0, 10).forEach(v => {
const loc = v.locations && v.locations[0] || {};
let file = loc.file || "unknown";
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
file = file.split("/").pop();
summary.topViolations.push({ rule: v.rule, engine: v.engine, sev: v.severity, file: file, line: loc.startLine || 0 });
});
// Top 10 rules by frequency
const ruleCounts = {};
const ruleEngines = {};
data.violations.forEach(v => {
ruleCounts[v.rule] = (ruleCounts[v.rule] || 0) + 1;
if (!ruleEngines[v.rule]) ruleEngines[v.rule] = v.engine;
});
Object.entries(ruleCounts).sort((a, b) => b[1] - a[1]).slice(0, 10).forEach(([rule, count]) => {
summary.topRules.push({ rule, engine: ruleEngines[rule], count });
});
// Top 5 files by violation count
const fileCounts = {};
data.violations.forEach(v => {
const loc = v.locations && v.locations[0] || {};
let file = loc.file || "unknown";
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
fileCounts[file] = (fileCounts[file] || 0) + 1;
});
Object.entries(fileCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).forEach(([file, count]) => {
summary.topFiles.push({ file, count });
});
console.log(JSON.stringify(summary));
#!/usr/bin/env node
// Version: v1.0 | SHA256: placeholder
// Query and filter Code Analyzer results JSON with rich filtering capabilities
// Usage: node query-results.js <results-file.json> [options]
//
// Options:
// --engine <name> Filter by engine (pmd, eslint, cpd, retire-js, etc.)
// --severity <n> Filter by severity (1-5, comma-separated for multiple)
// --category <tag> Filter by category/tag (Security, Performance, etc.)
// --rule <name> Filter by exact rule name (case-insensitive)
// --file <substring> Filter by file path substring
// --top <n> Return top N results (default: 10)
// --sort <field> Sort by: severity, rule, engine, file (default: severity)
// --sort-dir <dir> Sort direction: asc, desc (default: asc)
// --summary Show only summary counts (no individual violations)
const fs = require("fs");
const path = require("path");
function printUsage() {
console.error(`Usage: node query-results.js <results-file.json> [options]
Options:
--engine <name> Filter by engine (pmd, eslint, cpd, retire-js, etc.)
--severity <n> Filter by severity (1-5, comma-separated for multiple: 1,2)
--category <tag> Filter by category/tag (Security, Performance, BestPractices, etc.)
--rule <name> Filter by exact rule name (case-insensitive)
--file <substring> Filter by file path substring (case-insensitive)
--top <n> Return top N results (default: 10)
--sort <field> Sort by: severity, rule, engine, file (default: severity)
--sort-dir <dir> Sort direction: asc, desc (default: asc)
--summary Show only summary counts (no individual violations)
Examples:
node query-results.js results.json --engine pmd --severity 1,2
node query-results.js results.json --category Security --top 20
node query-results.js results.json --file AccountService.cls
node query-results.js results.json --rule ApexCRUDViolation
node query-results.js results.json --summary`);
process.exit(1);
}
// Parse CLI arguments
const args = process.argv.slice(2);
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
printUsage();
}
const filePath = args[0];
const options = {
engine: null,
severity: null,
category: null,
rule: null,
file: null,
top: 10,
sort: "severity",
sortDir: "asc",
summary: false,
};
// Parse named options
for (let i = 1; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "--engine":
options.engine = (args[++i] || "").toLowerCase();
break;
case "--severity":
options.severity = (args[++i] || "")
.split(",")
.map((s) => parseInt(s.trim(), 10))
.filter((n) => n >= 1 && n <= 5);
break;
case "--category":
options.category = (args[++i] || "").toLowerCase();
break;
case "--rule":
options.rule = (args[++i] || "").toLowerCase();
break;
case "--file":
options.file = (args[++i] || "").toLowerCase();
break;
case "--top":
options.top = parseInt(args[++i] || "10", 10);
break;
case "--sort":
options.sort = args[++i] || "severity";
break;
case "--sort-dir":
options.sortDir = args[++i] || "asc";
break;
case "--summary":
options.summary = true;
break;
default:
console.error(`Unknown option: ${arg}`);
printUsage();
}
}
// Read and parse results file
let data;
try {
data = JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (err) {
console.error(`Error reading results file: ${err.message}`);
process.exit(1);
}
const runDir = data.runDir || "";
const violations = data.violations || [];
// Apply filters
let filtered = violations.filter((v) => {
if (options.engine && v.engine.toLowerCase() !== options.engine) return false;
if (options.severity && !options.severity.includes(v.severity)) return false;
if (options.category) {
const tags = (v.tags || []).map((t) => t.toLowerCase());
if (!tags.includes(options.category)) return false;
}
if (options.rule && v.rule.toLowerCase() !== options.rule) return false;
if (options.file) {
const loc = v.locations && v.locations[v.primaryLocationIndex || 0];
const fileLower = ((loc && loc.file) || "").toLowerCase();
if (!fileLower.includes(options.file)) return false;
}
return true;
});
// Sort
const sortMul = options.sortDir === "desc" ? -1 : 1;
filtered.sort((a, b) => {
let cmp = 0;
switch (options.sort) {
case "severity":
cmp = a.severity - b.severity;
break;
case "rule":
cmp = a.rule.localeCompare(b.rule);
break;
case "engine":
cmp = a.engine.localeCompare(b.engine);
break;
case "file": {
const aLoc = a.locations && a.locations[a.primaryLocationIndex || 0];
const bLoc = b.locations && b.locations[b.primaryLocationIndex || 0];
const aFile = (aLoc && aLoc.file) || "";
const bFile = (bLoc && bLoc.file) || "";
cmp = aFile.localeCompare(bFile);
break;
}
}
if (cmp !== 0) return cmp * sortMul;
// Secondary sort: severity ascending
return (a.severity - b.severity) * sortMul;
});
// Build output
const totalMatches = filtered.length;
const limited = filtered.slice(0, options.top);
// Compute severity breakdown of matches
const sevCounts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
filtered.forEach((v) => {
if (sevCounts[v.severity] !== undefined) sevCounts[v.severity]++;
});
// Compute rule frequency for matches
const ruleCounts = {};
const ruleEngines = {};
filtered.forEach((v) => {
ruleCounts[v.rule] = (ruleCounts[v.rule] || 0) + 1;
if (!ruleEngines[v.rule]) ruleEngines[v.rule] = v.engine;
});
const topRules = Object.entries(ruleCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, options.top)
.map(([rule, count]) => ({ rule, engine: ruleEngines[rule], count }));
// Compute file frequency for matches
const fileCounts = {};
filtered.forEach((v) => {
const loc = v.locations && v.locations[v.primaryLocationIndex || 0];
let file = (loc && loc.file) || "unknown";
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
fileCounts[file] = (fileCounts[file] || 0) + 1;
});
const topFiles = Object.entries(fileCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, options.top)
.map(([file, count]) => ({ file, count }));
// Build result object
const result = {
query: {
engine: options.engine,
severity: options.severity,
category: options.category,
rule: options.rule,
file: options.file,
top: options.top,
sort: options.sort,
sortDir: options.sortDir,
},
totalViolations: violations.length,
totalMatches,
severityCounts: sevCounts,
topRules,
topFiles,
};
if (!options.summary) {
result.violations = limited.map((v) => {
const loc = v.locations && v.locations[v.primaryLocationIndex || 0];
let file = (loc && loc.file) || "unknown";
if (runDir && file.startsWith(runDir)) file = file.substring(runDir.length + 1);
return {
rule: v.rule,
engine: v.engine,
severity: v.severity,
message: v.message,
file: file,
startLine: (loc && loc.startLine) || 0,
tags: v.tags || [],
};
});
}
console.log(JSON.stringify(result));
#!/usr/bin/env node
// Summarize applied fixes by severity and rule
// Usage: node summarize-fixes.js <path-to-results.json>
const fs = require("fs");
if (process.argv.length < 3) {
console.error("Usage: node summarize-fixes.js <results-file.json>");
process.exit(1);
}
const filePath = process.argv[2];
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const fixesByRule = {};
const fixesBySeverity = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
data.violations.forEach(v => {
if (v.fixes && v.fixes.length > 0) {
const fixCount = v.fixes.length;
fixesByRule[v.rule] = (fixesByRule[v.rule] || 0) + fixCount;
fixesBySeverity[v.severity] += fixCount;
}
});
const topRules = Object.entries(fixesByRule)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([rule, count]) => ({ rule, count }));
console.log(JSON.stringify({ fixesByRule: topRules, fixesBySeverity }));
console.error("SUCCESS: Summary script completed. Present results to user and offer re-scan (Step 6.7).");
#!/bin/bash
# Verification script to ensure scripts are executed from files, not inline
# Usage: source this at the start of SKILL.md execution
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
verify_script_execution() {
local script_name="$1"
local expected_path="${SKILL_DIR}/scripts/${script_name}"
if [[ ! -f "$expected_path" ]]; then
echo "❌ ERROR: Script file not found: $expected_path"
echo "This skill requires script files to be present in the deployment."
return 1
fi
# Check if script has expected header
if ! head -1 "$expected_path" | grep -q "#!/usr/bin/env node"; then
echo "⚠️ WARNING: Script missing proper header: $expected_path"
fi
echo "✓ Script file verified: $script_name"
return 0
}
# Export function for use in skill execution
export -f verify_script_execution
export SKILL_DIR
Related skills
Forks & variants (1)
Running Code Analyzer has 1 known copy in the catalog totaling 297 installs. They canonicalize to this original listing.
- forcedotcom - 297 installs
How it compares
Pick running-code-analyzer over generic lint skills when scanning Salesforce-specific Apex PMD rules and LWC JavaScript inside force-app projects.
FAQ
What is running-code-analyzer?
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, ApexGur
When should I use running-code-analyzer?
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, ApexGur
Is running-code-analyzer safe to install?
Review the Security Audits panel on this page before production use.