Dx Apexguru Scan
Run an ApexGuru performance scan on a Salesforce Apex project via the ApexGuru SFAP Scan API. Zips the project's Apex (any layout), submits it, polls to completion, decodes the base64 report, and presents performance antipattern violations (SOQL in loop, DML in loop, Schema.getGlobalDescribe(), SOQL without WHERE/LIMIT, unused SOQL fields) grouped by rule with severity, file:line, and suggested fixes — clearly attributed as 'Static only' or 'Production insights'. TRIGGER when the user says 'run ApexGuru', 'ApexGuru scan', 'check Apex performance', 'find governor-limit / performance antipatterns', 'SOQL in loop', 'scan my Apex for performance', or 'ApexGuru performance insights'. DO NOT TRIGGER for general static analysis or security scans (use dx-code-analyzer-run), for fixing code without scanning, or for onboarding an org to ApexGuru.
- Skill ID
- forcedotcom/sf-skills/dx-apexguru-scan
- Publisher
- forcedotcom
- Repository
- sf-skills
- Installs
- 0
- Files
- 15
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
forcedotcom/sf-skills/dx-apexguru-scanInstalls these files- SKILL.md
- examples/README.md
- examples/sample-decoded-summary.json
- examples/sample-full-no-runtime-response.json
- examples/sample-succeeded-response.json
- references/api-reference.md
- references/authentication.md
- references/error-handling.md
- references/violation-catalog.md
- scripts/build-zip.sh
- scripts/decode-report.js
- scripts/resolve-token.sh
- scripts/run-scan.sh
- scripts/scan.sh
- scripts/validate-token.js
What this skill tells the agent
ApexGuru Performance Scan Skill
CRITICAL: Mandatory Script Usage
Every step — token resolution, zipping, API calls, and report decoding — MUST go through the bundled scripts in <skill_dir>/scripts/. No exceptions.
WRONG — never do this:
# WRONG: hand-rolled curl to the API
curl -X POST https://api.salesforce.com/... -F file=@x.zip
# WRONG: inline base64 + jq to read the report
cat raw.json | jq -r .report | base64 -d | jq '.[]'
# WRONG: reading the raw result file directly (report is a large base64 blob)
Read tool → apexguru-raw-*.json
# WRONG: inline node/python to parse violations
node -e "const r = require('./raw.json'); ..."RIGHT — always do this:
# PREFERRED — one command runs all three steps (package → submit+poll →
# decode+present) and prints the ready-to-show report as its final stdout.
# Use this for every initial scan: it cannot be left half-finished.
bash "<skill_dir>/scripts/scan.sh" "<project-root>"
# Optionally persist the presented markdown to a file as well:
bash "<skill_dir>/scripts/scan.sh" "<project-root>" --out ./apexguru-report.mdThe three underlying scripts still exist and scan.sh calls them in order. Invoke them individually only for drill-downs on an already-scanned result (Step 5), or when you deliberately need to inspect an intermediate artifact:
# Equivalent manual chain (scan.sh runs exactly these, in this order):
bash "<skill_dir>/scripts/build-zip.sh" "<project-root>" "./apexguru-<TS>.zip"
bash "<skill_dir>/scripts/run-scan.sh" "./apexguru-<TS>.zip" "./apexguru-raw-<TS>.json"
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --present
# Drill into a subset WITHOUT re-scanning (reuse the raw file scan.sh left, or
# pass --raw to scan.sh to keep it at a known path):
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --rule SOQL_IN_LOOP --full
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --group file --top 5<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 filter/rank/group question ("which file has the most issues?", "show only SOQL-in-loop", "break down by severity") is answered by re-running decode-report.js with flags against the same raw result file — never re-scan, never parse the JSON by hand.
CRITICAL: Present --present output verbatim — never condense it
decode-report.js --present (Step 4) already produces the final, ready-to-show markdown: severity legend, one detail card per violation (message, code, fix, resource link), and a closing summary table. That stdout is the response. Print it to the user exactly as printed — do not rewrite it into a shorter table, do not drop the per-issue cards down to just the summary table, and do not wait for the user to ask "explain a violation" before including message/fix/resource. Condensing it defeats the entire point of --present.
The attribution is already in that stdout — the summary line is the exact output that states the mode (e.g. "ApexGuru (static analysis) is active. To unlock runtime intelligence…"). Do NOT prepend or append your own attribution sentence (no "Attribution: analysisMode: static…", no naming the org, no restating "static-only findings"). The script's line is the complete, approved wording; adding your own makes the output non-deterministic and off-message.
WRONG — never do this:
Top Issues (worst first)
# Severity Rule Method Line
1 Major UsingTheTestMethodKeyword legacy... 136
...
Key Antipatterns Detected:
- SOQL/DML in loops (3 violations)(a hand-built summary that drops every message/code/fix — even for violations that had one)
Attribution: analysisMode: static — source-only analysis. The scanned org
(ag-skills-org) is not onboarded to ApexGuru's full runtime metrics, so
these are static-only findings.(an agent-authored attribution line prepended to the report — the script's own summary line already states the mode; this duplicate is non-deterministic and names an org the script never had access to)
RIGHT — always do this:
Paste the full stdout from decode-report.js --present — every ### Issue N card and the closing ## Summary table — unedited, in one response.
Overview
ApexGuru detects performance antipatterns in Apex (SOQL/DML in loops, Schema.getGlobalDescribe(), SOQL without WHERE/LIMIT, unused SOQL fields). This skill drives the ApexGuru SFAP Scan API: it packages the user's Apex (every .cls/.trigger under the project root, any layout) into a zip, submits it, polls until the scan finishes, decodes the base64-encoded report, and presents violations grouped by rule with severity, file:line, and suggested fixes.
Attribution is mandatory. The API returns analysisMode:
static→ source-only analysis → label results "Static only".full→ enriched with runtime metrics from an org onboarded to ApexGuru → label results "Production insights".
decode-report.js --present already renders this attribution into its summary line and title ("Static only" / "Production insights") — that satisfies the mandatory-attribution requirement. Print that line as the exact output; do not author your own attribution sentence or name the org. If the user expected full but got static, the script's static-mode line already explains the org is not onboarded — point them to it rather than restating it (see error handling).
In scope: zipping a project's Apex, submitting/polling the scan, decoding + presenting violations, filtering/grouping existing results, troubleshooting API errors.
Out of scope: general static analysis / security / lint (→ dx-code-analyzer-run, which lists ApexGuru as an engine), applying fixes to code, onboarding an org to ApexGuru, minting SFAP tokens.
Prerequisites
- An authenticated `sf` CLI org (
sf org login web ...).resolve-token.shderives the SFAP JWT from it via<instanceUrl>/ide/auth— this is the normal IDE-session path. Alternatively, setAPEXGURU_SFAP_TOKEN/APEXGURU_SFAP_TOKEN_FILEto supply a JWT directly (CI/headless). The org is derived from the token'stnkclaim — no org id is passed. Pass--org <alias>to pick a specific org. See<skill_dir>/references/authentication.md. If no token can be resolved, the script returns a clear error with a hint. - `sf`, `bash`, `curl`, `zip`, `jq`, `node` on PATH (standard on macOS/Linux dev boxes).
- A folder containing Apex — an sfdx project, a
force-app/subtree, or any folder with.cls/.triggerfiles.build-zip.shcollects all Apex beneath it regardless of layout; the API walks the whole archive.
Workflow
Step 1: Identify the project root
The project root is any folder that contains Apex somewhere beneath it (usually an sfdx project root next to sfdx-project.json, but a force-app/ subtree or a loose folder of .cls files works too). If the user gave a path, use it; otherwise use the current working directory. build-zip.sh collects every .cls/.trigger under it (any layout) and fails clearly if none exists.
Step 2: Package the project
TS=$(date +%Y%m%d-%H%M%S)
bash "<skill_dir>/scripts/build-zip.sh" "<project-root>" "./apexguru-${TS}.zip"Output JSON gives zip, bytes, humanSize, apexFileCount, scanRoot. The script enforces the 200MB compressed limit and fails fast if exceeded. On error (error/hint fields), relay the hint and stop.
Step 3: Submit and poll
bash "<skill_dir>/scripts/run-scan.sh" "./apexguru-${TS}.zip" "./apexguru-raw-${TS}.json"- Add
--fastif the user wants a quicker/cheaper run (skips LLM-heavy fix generation).
More from forcedotcom
Use this skill in a project
Open any RiverX project, search the Skills panel for forcedotcom/sf-skills/dx-apexguru-scan, and install it in one click.
