npx skills add ...
npx skills add forcedotcom/sf-skills --skill dx-org-analyze
Compare two Salesforce orgs side-by-side and produce a comparison report with drift score, or analyze a single org to produce an inventory covering metadata components, org permissions, system permissions, profiles, installed packages, licenses, and org limits. Use this skill when the user wants to compare orgs, diff orgs, audit configuration drift, find what changed between two environments, compare sandbox against production, or analyze a single org. Trigger phrases include: 'compare orgs', 'compare these 2 orgs', 'compare my orgs', 'diff orgs', 'org diff', 'what is different between these orgs', 'compare metadata', 'org comparison', 'audit org differences', 'compare sandbox to production', 'introspect org', 'org inventory', 'what is in my org', 'analyze org', 'org analysis', 'analyze my org', 'analyze my Salesforce org'. Do NOT use for retrieving metadata (use platform-metadata-retrieve), deploying metadata (use platform-metadata-deploy), or switching orgs (use dx-org-switch).
npx skills add forcedotcom/sf-skills --skill dx-org-analyze
Use ONLY the Bash tool to execute all sf CLI commands and Python scripts. Always include --json for sf commands. Do NOT use mcp__salesforce_dx__* tools.
Output artifacts for eval/testing: When an output directory is available, write results there. After executing the skill: (1) if the user specified an output path, write there immediately; (2) otherwise run [ -d force-app/main/adk-eval-output/ ] && echo 'force-app/main/adk-eval-output' to detect the eval directory; (3) write the full JSON result to <output-dir>/dx-org-analyze-result.json and the markdown report to <output-dir>/dx-org-analyze-result.md.
When querying org data, always follow this priority order:
sf org list metadata-types, sf org list metadata, sf org display, sf data query, sf limits api display, etc.Never bypass this hierarchy. If an SF CLI command exists for the operation, use it even if a direct API call would be simpler.
sf org login web, sf org login jwt, sf org login access-token).sf org display --json.Run this command to discover all authenticated orgs:
Parse the JSON output. Collect orgs from all buckets (devHubs, nonScratchOrgs, scratchOrgs, sandboxes, other). Present authenticated orgs in a readable table:
| # | Alias | Username | Instance URL | Org ID | Type |
|---|
If fewer than 2 orgs are authenticated but at least 1 is available, offer the single-org introspect mode (see Introspect Workflow below). If no orgs are authenticated, STOP and advise:
You need at least 1 authenticated org. Run
sf org login web --alias <name>to authenticate.
If the user explicitly requests a single-org introspection or inventory, use the Introspect Workflow regardless of how many orgs are available.
Ask the user to select two orgs from the list. Both must be explicitly named — do NOT allow implicit/default orgs.
Select two orgs to compare. Which is the source (reference/expected state)? Which is the target (to compare against)?
Accept: alias, username, or number from the list. Resolve each selection to a concrete username. If an org lacks an alias, prompt the user to assign one. Confirm:
Comparing:
- Source:
<alias>(<username>)- Target:
<alias>(<username>)
For each org, confirm reachability with a lightweight query that does not expose secrets:
If the query succeeds (exit 0 and a record is returned), the org is connected. If it fails with INVALID_SESSION_ID or auth errors:
Generate a unique run ID and run the collection script for each org:
Exit codes: 0 = success, 1 = fatal error (report stderr to user), 2 = session expired (re-authenticate Step 2 and retry).
For details on what the collection script gathers, see references/collection-details.md.
Use --show-shared if the user wants shared components listed in detail.
Read /tmp/dx-org-comparison-${RUN_ID}.md and present to the user. If the user asks follow-up questions, use /tmp/dx-org-comparison-${RUN_ID}.json for data lookups. Then resolve the output directory and copy both files there:
Use this workflow when the user wants to inspect a single org's configuration, or when only one org is authenticated.
Read /tmp/dx-org-analysis-${RUN_ID}.md and present to the user. The report covers: metadata inventory, org settings, org limits, installed packages, licenses, system permissions, and deep data records. Then resolve the output directory and copy both files there:
The generated report includes:
| Constraint | Rationale |
|---|---|
Always use --json with sf commands | Structured output for reliable parsing |
| Resolve orgs to usernames, not aliases | Aliases can be ambiguous; usernames are unique |
| Skip expired and disconnected orgs | Cannot query metadata from inaccessible orgs |
| Read-only — never deploy or modify | This skill compares only, never mutates either org |
| SF CLI auth only | All authentication through SF CLI credential store |
| Both orgs must be explicit | Never compare unnamed or implicit/default orgs |
| Issue | Resolution |
|---|---|
| "No org found for <alias>" | Org not authenticated — run sf org login web --alias <name> |
| Fewer than 2 authenticated orgs | Authenticate additional orgs before comparing |
| "INVALID_SESSION_ID" or auth errors | Session expired — re-run sf org login web --alias <name> |
| Collection script exits with code 2 | Session expired — re-authenticate and retry |
| API limit errors during metadata listing | Use --skip-deep-data for a faster pass with less detail |
| Edition differences (DE vs EE) | Many differences are edition-inherent, not configuration drift |
| Large orgs timeout on deep data | Use --skip-deep-data flag; run full deep data on targeted follow-ups |
| Need | Delegate to |
|---|---|
| Retrieve specific metadata from an org | platform-metadata-retrieve |
| Deploy metadata to an org | platform-metadata-deploy |
| Create a scratch org for comparison | dx-org-manage |
| Switch default org after comparison | dx-org-switch |
| File | When to read |
|---|---|
references/collection-details.md | For details on what the collection script gathers and how |
references/report-format.md | For drift score formula and report section details |
scripts/collect_org_data.py | Data collection script (per org) |
scripts/compute_diff.py | Diff computation and report generation script |
scripts/introspect_org.py | Single-org introspection report script |
sf org login web --alias <alias>RUN_ID=$(date +%Y%m%d-%H%M%S)
python3 ./scripts/collect_org_data.py \
--org-alias "$SOURCE_ORG" \
--output /tmp/dx-org-comparison-${RUN_ID}-source
python3 ./scripts/collect_org_data.py \
--org-alias "$TARGET_ORG" \
--output /tmp/dx-org-comparison-${RUN_ID}-targetpython3 ./scripts/compute_diff.py \
--org-a /tmp/dx-org-comparison-${RUN_ID}-source \
--org-b /tmp/dx-org-comparison-${RUN_ID}-target \
--output /tmp/dx-org-comparison-${RUN_ID} \
--format both \
--org-a-label "Source" \
--org-b-label "Target"OUTPUT_DIR=""
if [ -n "$USER_OUTPUT_PATH" ]; then
OUTPUT_DIR="$USER_OUTPUT_PATH"
elif [ -d "force-app/main/adk-eval-output" ]; then
OUTPUT_DIR="force-app/main/adk-eval-output"
fi
if [ -n "$OUTPUT_DIR" ]; then
cp /tmp/dx-org-comparison-${RUN_ID}.json "$OUTPUT_DIR/dx-org-analyze-result.json"
cp /tmp/dx-org-comparison-${RUN_ID}.md "$OUTPUT_DIR/dx-org-analyze-result.md"
fiRUN_ID=$(date +%Y%m%d-%H%M%S)
python3 ./scripts/collect_org_data.py \
--org-alias "$ORG" \
--output /tmp/dx-org-analysis-${RUN_ID}python3 ./scripts/introspect_org.py \
--org /tmp/dx-org-analysis-${RUN_ID} \
--output /tmp/dx-org-analysis-${RUN_ID} \
--format both \
--label "OrgName"OUTPUT_DIR=""
if [ -n "$USER_OUTPUT_PATH" ]; then
OUTPUT_DIR="$USER_OUTPUT_PATH"
elif [ -d "force-app/main/adk-eval-output" ]; then
OUTPUT_DIR="force-app/main/adk-eval-output"
fi
if [ -n "$OUTPUT_DIR" ]; then
cp /tmp/dx-org-analysis-${RUN_ID}.json "$OUTPUT_DIR/dx-org-analyze-result.json"
cp /tmp/dx-org-analysis-${RUN_ID}.md "$OUTPUT_DIR/dx-org-analyze-result.md"
fi