Compare commits

...

4 Commits

Author SHA1 Message Date
Mark Vogelgesang
899b9f9b52
Merge 288e8cc19c into 26bae6cb4d 2026-05-25 15:39:12 +08:00
svc-idee-bot
26bae6cb4d chore(release): 1.11.0 [skip ci] 2026-05-24 14:56:15 +00:00
GitHub Action
30417ef445 feat: @W-22618269@ release 3 new skills: applying-cms-brand, integrating-b2b-commerce-open-code-components, running-code-analyzer 2026-05-24 14:55:56 +00:00
mvogelgesang
288e8cc19c feat: add skill for third-party MCP server registration
Adds skills/generating-third-party-mcp-server-registration to generate
the four metadata files needed to register Salesforce as a CLIENT of a
non-Salesforce-hosted MCP server: External Credential, Named Credential,
External Service Registration, and Permission Set.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 16:08:06 -04:00
25 changed files with 3031 additions and 1 deletions

View File

@ -1,3 +1,12 @@
# [1.11.0](https://github.com/forcedotcom/sf-skills/compare/1.10.0...1.11.0) (2026-05-24)
### Features
* @W-22618269@ release 3 new skills: applying-cms-brand, integrating-b2b-commerce-open-code-components, running-code-analyzer ([30417ef](https://github.com/forcedotcom/sf-skills/commit/30417ef445f184ff8cc91e2208e1dcf418b75907))
# [1.10.0](https://github.com/forcedotcom/sf-skills/compare/1.9.1...1.10.0) (2026-05-22)

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/afv-skills",
"version": "1.10.0",
"version": "1.11.0",
"description": "Salesforce skills for Agentforce Vibes",
"license": "CC-BY-NC-4.0",
"files": [

View File

@ -0,0 +1,170 @@
---
name: applying-cms-brand
description: "Extracts, retrieves, and applies CMS brand guidelines (voice, tone, style, colors, typography) to generated content. Use this skill ANY TIME a user request involves branding, brand voice, brand tone, brand guidelines, brand identity, brand styling, or applying a brand to content. Triggers for requests like \"apply my brand\", \"use our brand voice\", \"match our brand guidelines\", \"find my brand\", \"search for brand\", \"get brand instructions\", \"apply brand tone\". Handles the full workflow: searching for brands in Salesforce CMS, extracting brand instructions, and applying brand voice/tone/guidelines to generated content. Does not apply to media/image search (use searching-media skill), logo search, or creating new brand definitions."
compatibility: "Requires get_brand_instructions and/or search_brands MCP tools"
metadata:
version: "1.0"
---
# Applying CMS Brand
Universal skill for searching, extracting, and applying CMS brand guidelines to generated content.
## Scope
**This skill is for APPLYING existing brand guidelines from Salesforce CMS to content you generate.**
**Use this skill when the user wants to:**
- Apply their brand voice/tone to generated content
- Find and use brand guidelines stored in Salesforce CMS
- Search for an existing brand in their org
- Get brand instructions for content generation
- Ensure generated content matches their brand identity
- Apply brand styling, tone, or voice to a page, component, or app
**DO NOT use this skill when the user wants to:**
- Search for images or media (use searching-media skill)
- Create a new brand from scratch
- Edit brand definitions in CMS
- Generate logos or visual brand assets
## Before You Start
**CRITICAL: You must retrieve brand instructions BEFORE generating or modifying any brand.**
When a user requests branded content:
1. **Search for available brands** (if brand is not already identified)
2. **Extract brand instructions** for the selected brand
3. **Apply brand guidelines** to all content you generate
**Never generate content first and retrofit branding later.** Brand instructions must inform content generation from the start.
## Workflow Overview
Copy this checklist and track your progress:
```
CMS Branding Progress:
- [ ] Step 1: Determine if brand is already identified or needs search
- [ ] Step 2: Search for brands (if needed) and present options to user
- [ ] Step 3: Extract brand instructions for the selected brand
```
## Step 1: Determine Brand Context
Check if the user has already specified which brand to use:
**Brand is known** (user named it, or only one brand exists):
- Skip to Step 3 (Extract Brand Instructions)
**Brand is unknown** (user says "apply my brand" without specifying which):
- Proceed to Step 2 (Search for Brands)
## Step 2: Search for Brands
**Tool:** `search_brands`
**Process:**
1. **Determine search query** — Use the user's description, company name, or a general keyword
2. **Build the request:**
```json
{
"inputs": [{
"searchQuery": "keyword or brand name"
}]
}
```
3. **Call `search_brands`** with the query
4. **Parse the response** — Extract brand results:
- `managedContentId` — Unique ID (use this for extraction in Step 3)
- `managedContentKey` — Content key identifier
- `title` — Brand display name
- `contentUrl` — URL to the brand content
- `totalResults` — Number of brands found
### Presenting Brand Results
**If multiple brands found**, use `ask_followup_question` to present options:
```
I found [N] brands in your CMS. Which one should I apply?
1. [Brand Title 1]
2. [Brand Title 2]
3. [Brand Title 3]
Which brand would you like to use?
```
**If one brand found**, confirm with the user:
```
I found the brand "[Brand Title]". Should I apply this brand's guidelines to the content?
```
**If no brands found:**
```
No brands found in Salesforce CMS. To use branding:
1. Create a brand in Salesforce CMS (Content Type: sfdc_cms__brand)
2. Provide brand guidelines directly in this conversation
Would you like to proceed without CMS branding, or provide guidelines manually?
```
**Never auto-select a brand without confirmation.** Always wait for user choice.
## Step 3: Extract Brand Instructions
**Tool:** `get_brand_instructions`
**Process:**
1. **Call `get_brand_instructions`** — This retrieves the branding extraction prompt template
2. **Parse the response:**
- `promptBody` — Contains the full brand instruction prompt with extraction and application rules
3. **Follow the instructions in `promptBody`** — The prompt template contains specific guidance on:
- How to extract brand properties from the brand content
- Brand voice and tone rules
- Typography and color guidelines
- Content formatting rules
- Guardrails and restrictions
### What Brand Instructions Contain
The extracted brand instructions typically include:
| Property | Description |
|---|---|
| Brand Voice | How the brand speaks (e.g., professional, friendly, authoritative) |
| Brand Tone | Emotional quality of communication (e.g., confident, warm, empathetic) |
| Key Messages | Core messaging pillars and value propositions |
| Content Rules | Dos and don'ts for content generation |
| Style Guidelines | Typography, color, spacing preferences |
| Guardrails | Hard restrictions on language, topics, or claims |
## Error Handling
| Error | Response |
|---|---|
| `search_brands` unavailable | "Brand search is unavailable. Please provide your brand name or guidelines directly." |
| `get_brand_instructions` unavailable | "Cannot retrieve brand instructions. Please share your brand guidelines in this conversation and I'll apply them manually." |
| Org lacks Vibes branding | "CMS branding is not enabled for this org. Contact your admin to enable the Agentforce Vibes branding feature." |
| Permission denied | "You don't have permission to access CMS brands. Ensure you have Managed Content Authoring permission." |
| Brand extraction returns empty | "The brand exists but has no configured guidelines. Please add brand properties in CMS or provide guidelines here." |
**Never silently fail.** Always inform the user and offer alternatives.
## Key Principles
1. **Brand first, content second** — Always extract brand instructions before generating content
2. **Never assume brand guidelines** — Only apply what was explicitly retrieved from CMS
3. **Respect guardrails absolutely** — Brand content rules are hard constraints, not suggestions
4. **Confirm brand selection** — Never auto-select a brand without user confirmation
5. **Show your work** — Tell the user which guidelines you applied and how
6. **Graceful degradation** — If tools are unavailable, ask for manual guidelines rather than proceeding without branding

View File

@ -0,0 +1,239 @@
---
name: generating-third-party-mcp-server-registration
description: "Use this skill when a Salesforce org needs to act as a CLIENT of a non-Salesforce-hosted (third-party) MCP server. Generates four metadata files: External Credential, External Service Registration, Named Credential, and Permission Set. Trigger when users mention \"connect my org to a third-party MCP\", \"agent uses external MCP tools\", \"register an external MCP endpoint\", \"set up External Credential for MCP\", \"call out to an MCP server from Salesforce\". DO NOT use this skill when the user wants to (a) consume a Salesforce-hosted or first-party packaged MCP server, or (b) publish/expose an MCP server FROM Salesforce so external clients (Cursor, Claude, etc.) can call into the org."
compatibility: Salesforce Metadata API v65.0+
---
# Generating MCP Server Registration Metadata
## Scope
This skill covers exactly one MCP integration shape:
> **Salesforce as client → non-Salesforce-hosted (third-party) MCP server.**
The four files generated configure the org to make outbound calls to an MCP endpoint hosted outside Salesforce.
### Out of scope — STOP and do not generate files if any apply
- The MCP server is hosted on a `*.salesforce.com`, `*.force.com`, or `*.my.salesforce-sites.com` domain, or is delivered by a Salesforce-published managed package. These typically ship their own External Credential and only need permission-set assignment.
- The user wants to **publish** an MCP server from the org so external agentic clients (Cursor, Claude Desktop, Copilot, etc.) can consume it. That requires Apex action classes, GenAiFunction / GenAiPlugin metadata, an Experience Site or Connected App for the endpoint, and is NOT covered here.
- The user is asking about Agentforce **using** MCP tools that already exist in the org. That is an Agentforce configuration task, not a registration task.
If the request matches any of the above, surface the mismatch to the user instead of proceeding.
## Required Inputs
Resolve these values before generating files. Prefer reading from `sfdx-project.json` over asking the user.
1. `FILE_PATH` (Optional): Path under which the metadata folders (`externalCredentials/`, `namedCredentials/`, `externalServiceRegistrations/`, `permissionsets/`) will be created.
- Read the package directory from `packageDirectories` in `sfdx-project.json`.
- Default to the entry where `"default": true`. If multiple package directories exist and none is marked default, ask the user which to use.
- **Resolve the metadata root inside the package directory.** Salesforce DX projects commonly nest source under `main/default/` before the metadata folders. After identifying the package directory (e.g., `force-app`):
- If `<packageDir>/main/default` exists, set `FILE_PATH` to `<packageDir>/main/default` (e.g., `force-app/main/default`).
- Otherwise, set `FILE_PATH` to the package directory itself (e.g., `force-app`).
- The user may override the resolved value by explicitly providing one. When overridden, use the value as-is and skip the `main/default` check.
2. `NAMESPACE` (Optional): The package namespace.
- Read from the `namespace` field in `sfdx-project.json`.
- If a non-empty namespace is defined, append `__` to the end (e.g., `myns` becomes `myns__`).
- If `namespace` is missing or empty in `sfdx-project.json`, confirm with the user that no namespace is needed before proceeding. On confirmation, treat as an empty string `""`.
3. `MCP_SERVER_NAME` (Required): Developer name of the MCP service. Must satisfy ALL of the following:
- Must start with a letter.
- May contain only letters, numbers, and underscores.
- Must not contain consecutive underscores.
- Must not end with an underscore.
- Maximum 40 characters.
4. `MCP_SERVER_URL` (Required): The endpoint URL for the MCP server.
- The user may supply the value with an `http://` or `https://` prefix.
- If no protocol prefix is provided, prepend `https://` before substitution.
---
## Workflow
### Step 0: Confirm the integration shape (required)
Before resolving any inputs, ask the user to confirm exactly one of the following. If the answer is anything other than option 1, stop and direct the user appropriately rather than generating files.
1. **My org needs to call out to a third-party MCP server hosted outside Salesforce.** → Proceed with this skill.
2. **My org needs to call a Salesforce-hosted or packaged MCP server (1st-party).** → Stop. The third-party External Credential pattern this skill produces uses `<authenticationProtocol>Custom</authenticationProtocol>` with `NoAuthentication`, which is incorrect for first-party endpoints. Ask the user for the package or vendor's setup guide.
3. **I want to expose tools from my org as an MCP server that external clients consume.** → Stop. This is an MCP-host scenario and requires GenAiPlugin / GenAiFunction / Apex action metadata, not the registration files this skill generates.
If the user is unsure, ask: *"Are you trying to let your Salesforce org **call** an MCP server, or **be** an MCP server?"* Then map their answer to options 13.
### Step 1: Resolve inputs
- Replace instances of `{FILE_PATH}`, `{NAMESPACE}`, `{MCP_SERVER_NAME}`, and `{MCP_SERVER_URL}` in the file paths and file contents with the resolved values.
- For the `<schema>` and `<serviceBinding>` elements in File 2, do a textual substitution on the encoded string. Do not parse the value as JSON before substitution.
- Create these files relative to the project root directory.
### Step 2 (sanity check): Validate the endpoint domain
If `MCP_SERVER_URL` resolves to a Salesforce-owned domain (`*.salesforce.com`, `*.force.com`, `*.my.salesforce-sites.com`, `*.lightning.force.com`), pause and re-confirm with the user that this is genuinely a third-party server reachable on that domain (e.g., a Mulesoft proxy or a custom Site endpoint) rather than a packaged or platform MCP. The third-party `NoAuthentication` template is rarely correct for Salesforce-owned endpoints.
---
## Architecture
The four metadata files reference each other to form a single MCP server registration. Generate all four; any one missing breaks the chain.
```
External Service Registration (File 2)
<namedCredential>
Named Credential (File 3)
<externalCredential>
External Credential (File 1) ◀──── Permission Set (File 4)
│ grants access via
<externalCredentialPrincipalAccesses>
│ │
└── <parameterGroup> "MCPAuthentication" is
referenced as the trailing suffix of
<externalCredentialPrincipal>
```
Reference chain at deploy time:
- File 2 → File 3 via `<namedCredential>{NAMESPACE}{MCP_SERVER_NAME}</namedCredential>`
- File 3 → File 1 via `<externalCredential>{NAMESPACE}{MCP_SERVER_NAME}</externalCredential>`
- File 4 → File 1 via `<externalCredentialPrincipal>{NAMESPACE}{MCP_SERVER_NAME}-MCPAuthentication</externalCredentialPrincipal>` (the suffix after the dash must match File 1's `<parameterGroup>`)
### File 1: External Credential
**Path:** `{FILE_PATH}/externalCredentials/{MCP_SERVER_NAME}.externalCredential-meta.xml`
The `<parameterGroup>` value `MCPAuthentication` (line shown below) is referenced by File 4's `<externalCredentialPrincipal>` as the trailing segment after the dash. If you change the `<parameterGroup>` value here, you must change the matching suffix in File 4.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ExternalCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<authenticationProtocol>Custom</authenticationProtocol>
<externalCredentialParameters>
<parameterGroup>DefaultGroup</parameterGroup>
<parameterName>Custom</parameterName>
<parameterType>AuthProtocolVariant</parameterType>
<parameterValue>NoAuthentication</parameterValue>
</externalCredentialParameters>
<externalCredentialParameters>
<parameterGroup>MCPAuthentication</parameterGroup>
<parameterName>MCPAuthentication</parameterName>
<parameterType>NamedPrincipal</parameterType>
<sequenceNumber>1</sequenceNumber>
</externalCredentialParameters>
<label>{MCP_SERVER_NAME}</label>
</ExternalCredential>
```
### File 2: External Service Registration
**Path:** `{FILE_PATH}/externalServiceRegistrations/{MCP_SERVER_NAME}.externalServiceRegistration-meta.xml`
The `tools` and `resources` arrays inside `<schema>` are intentionally left empty at registration time. They are populated automatically once an authenticated connection is established with the MCP server. After establishing the connection, instruct the user to retrieve the updated metadata from the org so the populated arrays are reflected in source.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ExternalServiceRegistration xmlns="http://soap.sforce.com/2006/04/metadata">
<label>{MCP_SERVER_NAME}</label>
<namedCredential>{NAMESPACE}{MCP_SERVER_NAME}</namedCredential>
<namedCredentialReference>{MCP_SERVER_NAME}</namedCredentialReference>
<registrationProviderType>ModelContextProtocol</registrationProviderType>
<schema>{&quot;serverDescriptor&quot;:{&quot;protocolVersion&quot;:&quot;2025-06-18&quot;,&quot;serverInfo&quot;:{&quot;name&quot;:&quot;{MCP_SERVER_NAME}&quot;,&quot;version&quot;:&quot;1.0.0&quot;}},&quot;tools&quot;:[],&quot;resources&quot;:[]}</schema>
<schemaType>ModelContextProtocol</schemaType>
<serviceBinding>{&quot;protocolVersion&quot;:&quot;2025-06-18&quot;,&quot;serverInfo&quot;:{&quot;name&quot;:&quot;{MCP_SERVER_NAME}&quot;,&quot;version&quot;:&quot;1.0.0&quot;},&quot;instructions&quot;:null}</serviceBinding>
<status>Incomplete</status>
<systemVersion>8</systemVersion>
</ExternalServiceRegistration>
```
### File 3: Named Credential
**Path:** `{FILE_PATH}/namedCredentials/{MCP_SERVER_NAME}.namedCredential-meta.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<allowMergeFieldsInBody>true</allowMergeFieldsInBody>
<allowMergeFieldsInHeader>true</allowMergeFieldsInHeader>
<calloutStatus>Enabled</calloutStatus>
<generateAuthorizationHeader>true</generateAuthorizationHeader>
<label>{MCP_SERVER_NAME}</label>
<namedCredentialParameters>
<parameterName>Url</parameterName>
<parameterType>Url</parameterType>
<parameterValue>{MCP_SERVER_URL}</parameterValue>
</namedCredentialParameters>
<namedCredentialParameters>
<externalCredential>{NAMESPACE}{MCP_SERVER_NAME}</externalCredential>
<parameterName>ExternalCredential</parameterName>
<parameterType>Authentication</parameterType>
</namedCredentialParameters>
<namedCredentialType>SecuredEndpoint</namedCredentialType>
</NamedCredential>
```
### File 4: Permission Set
**Path:** `{FILE_PATH}/permissionsets/{MCP_SERVER_NAME}_Perm_Set.permissionset-meta.xml`
The `-MCPAuthentication` suffix in `<externalCredentialPrincipal>` must match the `<parameterGroup>` value defined in File 1 (the second `<externalCredentialParameters>` block). The two are linked: changing one without the other breaks the principal reference at deploy time.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Perm set for MCP Server "{MCP_SERVER_NAME}"</description>
<externalCredentialPrincipalAccesses>
<enabled>true</enabled>
<externalCredentialPrincipal>{NAMESPACE}{MCP_SERVER_NAME}-MCPAuthentication</externalCredentialPrincipal>
</externalCredentialPrincipalAccesses>
<hasActivationRequired>false</hasActivationRequired>
<label>{MCP_SERVER_NAME} - Permission Set</label>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>UserExternalCredential</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
</PermissionSet>
```
---
## Verification Checklist
Before deploying, verify:
- [ ] `MCP_SERVER_NAME` satisfies all naming rules: starts with a letter; contains only letters, numbers, and underscores; no consecutive underscores; no trailing underscore; no more than 40 characters.
- [ ] All four metadata files were created at the expected paths:
- [ ] `{FILE_PATH}/externalCredentials/{MCP_SERVER_NAME}.externalCredential-meta.xml`
- [ ] `{FILE_PATH}/externalServiceRegistrations/{MCP_SERVER_NAME}.externalServiceRegistration-meta.xml`
- [ ] `{FILE_PATH}/namedCredentials/{MCP_SERVER_NAME}.namedCredential-meta.xml`
- [ ] `{FILE_PATH}/permissionsets/{MCP_SERVER_NAME}_Perm_Set.permissionset-meta.xml`
- [ ] If any one of the four files failed to generate, revert ALL files. The four references form a chain (see [Architecture](#architecture)); a partial set is invalid and will fail to deploy.
- [ ] The `-MCPAuthentication` suffix in File 4's `<externalCredentialPrincipal>` matches the `<parameterGroup>` value in File 1.
---
## Deployment
After generating the four files, instruct the user to deploy them together. Substitute `{MCP_SERVER_NAME}` with the resolved value:
```bash
sf project deploy start \
--metadata "ExternalCredential:{MCP_SERVER_NAME}" \
--metadata "NamedCredential:{MCP_SERVER_NAME}" \
--metadata "ExternalServiceRegistration:{MCP_SERVER_NAME}" \
--metadata "PermissionSet:{MCP_SERVER_NAME}_Perm_Set"
```
Once the deployment succeeds, instruct the user to assign the permission set:
```bash
sf org assign permset -n {MCP_SERVER_NAME}_Perm_Set
```

View File

@ -0,0 +1,166 @@
---
name: integrating-b2b-commerce-open-code-components
description: "Integrate Salesforce B2B Commerce open source components from GitHub into B2B Commerce stores. Use when users mention \"integrate open code components\", \"open source B2B commerce\", \"add open code components\", \"forcedotcom/b2b-commerce-open-source-components\", or want to add open source commerce components to their store. Copies all components and labels so they become available in Experience Builder."
license: LICENSE.txt has complete terms
allowed-tools: Bash(git clone:*) Bash(cp:*) Read
metadata:
version: "1.0"
---
## When to Use This Skill
Use this skill when you need to:
- Integrate all open source B2B Commerce components into a store
- Add open source components to a new or existing B2B Commerce store
- Make open code components available in Experience Builder
## Rules
1. **Always explain before executing.** Before running any command, you MUST tell the user what the command does and why you are running it. Never just show a raw command and ask for permission. The user should be able to read your explanation and understand the purpose before approving.
## Overview
This skill copies all open source B2B Commerce components from the official Salesforce repository (https://github.com/forcedotcom/b2b-commerce-open-source-components) into a B2B Commerce store's site metadata. After integration, the components appear in the Experience Builder component palette.
---
## Startup Flow
When this skill is triggered, perform these checks automatically before copying.
### Check 0: Resolve Package Directory
Read `sfdx-project.json` and pick the active package directory. Extract `packageDirectories[]` and use the entry with `"default": true`; if no entry is flagged default, use the first entry. Use this value as `<package-dir>` everywhere below. If `sfdx-project.json` is missing or has no `packageDirectories`, tell the user and abort.
### Check 1: Open Source Repository
Verify the repo is cloned at `.tmp/b2b-commerce-open-source-components`:
1. **If directory does not exist:** Tell user: "I'm cloning the official B2B Commerce open source components repository from GitHub into a local `.tmp/` folder. This gives us access to all the open code components."
Then run: `git clone https://github.com/forcedotcom/b2b-commerce-open-source-components .tmp/b2b-commerce-open-source-components`
2. **If directory exists** and contains `force-app/main/default/sfdc_cms__lwc` and `sfdc_cms__label`, present options:
> "Open source repository is already cloned. How would you like to proceed?"
> 1. **Reuse existing** — Use the already cloned repository
> 2. **Re-clone** — Remove and clone fresh from GitHub
3. **If directory exists but structure is invalid:** Tell user: "The cloned repository has an unexpected structure. I'll remove it and clone a fresh copy."
Then remove and re-clone.
4. **If clone fails:** inform user and abort
### Check 2: Store and Site Metadata
Verify a store is selected and site metadata is available locally:
1. Tell user: "I'm checking if your project already has B2B store metadata locally."
Check if `<package-dir>/main/default/digitalExperiences/site/` contains any store directories.
2. **If store metadata exists:** use it. If multiple stores found, ask user to select one.
3. **If no store metadata found:** Try retrieving from the connected org before delegating:
1. Run `sf org list` (or check `sf config get target-org`) to find a connected org. Ask the user to confirm or pick one if more than one.
2. List `DigitalExperienceBundle` site bundles in that org with `sf org list metadata --metadata-type DigitalExperienceBundle --target-org <alias>`. Filter to `site/*` entries.
3. If at least one site bundle exists, ask the user which to use, then run:
`sf project retrieve start --metadata "DigitalExperienceBundle:site/<storeName>" --target-org <alias>`
The bundle lands at `<package-dir>/main/default/digitalExperiences/site/<storeName>/`.
4. **Only if no connected org is available, or no site bundles are found, or retrieve fails:** delegate to the **creating-b2b-commerce-store** skill.
**Required state** after all checks:
- **Package dir** — the value resolved in Check 0 (e.g., `force-app`)
- **Store name** — the selected `fullName` value (e.g., `My_B2B_Store1`)
- **Site metadata path**`<package-dir>/main/default/digitalExperiences/site/<store-name>/`
- **Repo path**`.tmp/b2b-commerce-open-source-components/`
---
## Integration Task
Copy all components and labels from cloned repo to site directory:
- **Source:** `.tmp/b2b-commerce-open-source-components/force-app/main/default/sfdc_cms__lwc/*` and `sfdc_cms__label/*` (the open source repo's own layout — always `force-app`)
- **Destination:** `<package-dir>/main/default/digitalExperiences/site/<store-name>/sfdc_cms__lwc/` and `sfdc_cms__label/` (`<package-dir>` resolved in Check 0)
**Steps:**
1. Tell user: "I'm checking if open code components already exist in your store's site metadata."
Check if destination directories already contain files.
2. If files exist, present options:
> "Components already exist in **{store-name}**. How would you like to proceed?"
> 1. **Overwrite all** — Replace all existing components with latest from repo
> 2. **Copy only new** — Skip existing components, copy only ones not yet present
3. Tell user: "I'm now copying all open code LWC components from the cloned repository into your store's site metadata directory."
Copy all component directories from source to destination.
4. Tell user: "I'm copying the associated label files that these components need."
Copy all label directories from source to destination.
5. Report: "Copied X components and Y label sets"
**Output:**
```
✅ Integration Complete!
Copied: X components and Y label sets to <store-name>
Next Steps:
1. Deploy: sf project deploy start -d <package-dir>/main/default/digitalExperiences/site/<store-name>
2. Open Experience Builder and use new components from the palette
3. Publish your site when ready
```
---
## Example Interaction
**User:** "Integrate open code components to my store"
**Agent:** "I'm checking if the open source components repository is already cloned locally..."
**Agent:** _(repo exists)_
> "Open source repository is already cloned. How would you like to proceed?"
> 1. **Reuse existing** — Use the already cloned repository
> 2. **Re-clone** — Remove and clone fresh from GitHub
**User:** "1"
**Agent:** "I'm checking if your project already has B2B store metadata locally..."
- ✓ Found store metadata for My_B2B_Store1
**Agent:** "I'm checking if open code components already exist in your store's site metadata..."
**Agent:** _(files exist)_
> "Components already exist in **My_B2B_Store1**. How would you like to proceed?"
> 1. **Overwrite all** — Replace all existing components with latest from repo
> 2. **Copy only new** — Skip existing components, copy only ones not yet present
**User:** "1"
**Agent:** "I'm now copying all open code LWC components from the cloned repository into your store's site metadata directory..."
**Agent:** "I'm copying the associated label files that these components need..."
- ✓ Copied 45 components and 38 label sets
```
✅ Integration Complete!
Copied: 45 components and 38 label sets to My_B2B_Store1
Next Steps:
1. Deploy: sf project deploy start -d force-app/main/default/digitalExperiences/site/My_B2B_Store1
2. Open Experience Builder and use new components from the palette
3. Publish your site when ready
```
---
## Error Handling
| Error | Message | Action |
|-------|---------|--------|
| Store not found | "Store '{name}' not found in org." | List stores again |
| Git clone failed | "Failed to clone repository. Check internet connection." | Retry or abort |
| Invalid repo structure | "Repository structure has changed. Expected sfdc_cms__lwc and sfdc_cms__label." | Warn user, abort |
| File copy failed | "Failed to copy files. Check file permissions." | Show error details |
---
## Verification Checklist
- [ ] Startup Flow completed: repo cloned, store metadata available
- [ ] Components copied to correct destination path (`sfdc_cms__lwc/`)
- [ ] Labels copied to correct destination path (`sfdc_cms__label/`)
- [ ] No file permission errors during copy
- [ ] Deployment command provided and user informed about testing

View File

@ -0,0 +1,499 @@
---
name: running-code-analyzer
description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. TRIGGER when: user says 'scan my code', 'check for security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). DO NOT TRIGGER when: user wants to fix code without scanning, or asks about installation/configuration."
allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit
license: LICENSE.txt has complete terms
metadata:
version: "1.0"
argument-hint: "[target-path] [--engine pmd|eslint|cpd|retire-js|regex|flow|sfge|apexguru] [--category Security|Performance|BestPractices|...] [--severity 1-5] [--diff]"
---
# Running Code Analyzer Skill
## ⚠️ CRITICAL: Tool Selection
**BEFORE DOING ANYTHING ELSE:**
This skill MUST use the **Bash tool** to execute `sf code-analyzer run` and Node.js scripts.
**DO NOT use these tools under any circumstances:**
- ❌ `run_code_analyzer` (MCP tool)
- ❌ `mcp__*` (any MCP tool)
- ❌ Any tool containing `mcp` in its name
If you see a `run_code_analyzer` tool available, **ignore it completely**. Use only the Bash tool with `sf code-analyzer run`.
---
## Overview
This skill translates natural language requests ("scan for security issues", "check my changes") into the correct `sf code-analyzer run` command, executes scans with 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 this skill for static analysis, security reviews, AppExchange certification, code quality checks, or finding duplicates/vulnerabilities in Salesforce projects.
---
## Scope
**In scope:**
- Running `sf code-analyzer run` with any combination of engines, targets, categories, severities
- Parsing and presenting scan results in actionable format
- Applying engine-provided auto-fixes when available
- Handling diff-based scans (scan only changed files)
- Supporting all output formats (JSON, HTML, SARIF, CSV, XML)
- Troubleshooting scan failures and prerequisite issues
**Out of scope:**
- Installing or configuring Salesforce CLI or Code Analyzer plugin (use setup documentation)
- Writing custom Code Analyzer rules or engines (separate skill needed)
- AI-generated code fixes beyond engine-provided deterministic fixes
- Deep code refactoring or architectural changes based on violations
- Setting up CI/CD integration for automated scanning (separate workflow skill)
---
## Command Syntax Rules (READ THIS FIRST)
**The following rules are ABSOLUTE and override any prior knowledge:**
1. **The command is `sf code-analyzer run`** — NOT `sf scanner run` (deprecated v3 command)
2. **There is NO `--format` flag** — use `--output-file <path>.<ext>` instead (extension determines format)
3. **ALWAYS use `--output-file`** to write results to a file — do NOT rely on terminal stdout
4. **ALWAYS include `--output-file`** with a timestamped filename (e.g., `./code-analyzer-results-20260512-143022.json`)
5. **Do NOT run in background** — use foreground with timeout of 1200000ms for large scans
6. **INVALID v3 flags:** `--format`, `--engine`, `--category`, `--json` — these cause errors, use `--rule-selector` and `--output-file` instead
7. **NEVER use MCP tools** — ONLY use the Bash tool to execute `sf code-analyzer run`
8. **Tool restriction:** This skill MUST use ONLY: Read, Bash, Write, Edit tools
9. **Forbidden tools:** Do NOT use any MCP tools (mcp__*), Agent tool, or web tools
10. **Script execution:** ALL scripts MUST be executed via `node <skill_dir>/scripts/*.js` using the Bash tool
**Why:** The v4+ CLI redesigned the flag interface. Old v3 flags cause "unknown flag" errors.
**For complete flag reference and rule selector syntax**, see `<skill_dir>/references/flag-reference.md`.
---
## Prerequisites
User must have: **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).
If a scan fails, read `<skill_dir>/references/error-handling.md`. For quick command examples, see `<skill_dir>/references/quick-start.md`.
---
## Tool Usage Rules
**Allowed:** Bash (sf code-analyzer, node, git, date), Read, Write, Edit
**Forbidden:** MCP tools, Agent tool, Web tools, other skills
This skill owns the complete scan-fix-verify workflow. Using MCP tools bypasses the validated script workflow.
---
## Quick Start: Common Patterns
Use this decision tree for fast pattern matching before going to Step 1 detailed parsing:
| User Says | Action | Rule Selector | Notes |
|-----------|--------|---------------|-------|
| "scan my code" / "run code analyzer" | Default scan | `Recommended` | Curated rule set, all file types |
| "check for security issues" / "security review" | Security scan | `all:Security:(1,2)` | All engines, Critical+High only |
| "scan my changes" / "check the diff" | Diff-based scan | Get changed files via `git diff`, filter to scannable types, use `--target` | See Step 1.5 for filtering logic |
| "run PMD" / "check my Apex" | PMD only | `pmd` | Apex classes and triggers |
| "lint my LWC" / "check my JavaScript" | ESLint only | `eslint` | JavaScript/TypeScript/LWC |
| "find duplicates" / "check for copy-paste" | CPD (Copy-Paste Detector) | `cpd` | Detects code clones |
| "check for vulnerabilities" / "scan libraries" | RetireJS | `retire-js` | JavaScript library CVEs |
| "deep analysis" / "data flow analysis" | SFGE (Graph Engine) | `sfge` | Requires Java 11+, 10-20min, use `--workspace "force-app"` |
| "performance analysis" / "governor limits" | ApexGuru | `apexguru` | Requires authenticated org |
| "analyze my Flows" | Flow engine | `flow` | Target: `**/*.flow-meta.xml`, requires Python 3 |
| "AppExchange security review" | AppExchange scan | `all:Security:(1,2)` | Read `<skill_dir>/references/special-behaviors.md` → AppExchange section |
**If the pattern matches above**, proceed directly to Step 3 (Build Command). Otherwise, continue to Step 1 for detailed parsing.
---
## Step 1: Parse the User's Intent
Analyze the user's request along these 7 dimensions. Any can be combined freely:
### 1.1 ENGINE — Which analysis engine(s)?
Map user keywords to `--rule-selector` values:
- PMD / Apex rules → `pmd`
- ESLint / JS/TS rules / lint → `eslint`
- Flows / Flow analysis → `flow`
- duplicates / copy-paste / CPD → `cpd`
- vulnerabilities / CVE / libraries / RetireJS → `retire-js`
- SFGE / data flow / deep analysis → `sfge`
- performance / ApexGuru → `apexguru`
- regex / pattern rules → `regex`
- all engines / everything → `all`
- Not specified / general "scan" → `Recommended` (default)
### 1.2 CATEGORY — What kind of issues?
Map user keywords to category tags:
- security / vulnerabilities / OWASP → `Security`
- performance / speed / optimization → `Performance`
- best practices / quality → `BestPractices`
- code style / formatting → `CodeStyle`
- design / complexity → `Design`
- error prone / bugs → `ErrorProne`
- documentation / comments → `Documentation`
### 1.3 SEVERITY — How critical?
**Severity levels:** 1=Critical (must fix), 2=High (should fix), 3=Moderate (recommended), 4=Low (nice to fix), 5=Info (FYI)
Map user keywords:
- "critical only" / "sev 1" → `1`
- "critical and high" / "sev 1-2" → `(1,2)`
- "moderate and above" / "sev 1-3" → `(1,2,3)`
### 1.4 SPECIFIC RULE — Named rule?
If the user mentions a specific rule by name (e.g., "ApexCRUDViolation", "no-unused-vars"):
- Map to: `--rule-selector <engine>:<ruleName>`
- If engine is ambiguous, use just the rule name: `--rule-selector <ruleName>`
**⚠️ IMPORTANT — Partial Rule Names:** The `--rule-selector` flag requires the EXACT full rule name (e.g., `@salesforce-ux/slds/no-hardcoded-values-slds2`, not `no-hardcoded-values`). It does NOT support wildcards or partial matches.
**When you are NOT 100% certain of the full rule name:**
- **Do NOT guess** — a wrong name returns 0 results and wastes a scan cycle
- Instead, **look up the rule first** using the `sf code-analyzer rules` command with grep:
```bash
sf code-analyzer rules --rule-selector all 2>&1 | grep -i "USER_KEYWORD"
```
- Extract the full rule name from the output, then use it in your scan command
- If grep returns multiple matches, present them to the user and ask which one they meant
- If grep returns 0 matches, tell the user no rule matched their keyword
### 1.5 TARGET — What files to scan?
Map user keywords:
- Specific file/folder → `--target <path>`
- Glob pattern / "all Apex classes" → `--target **/*.cls,**/*.trigger`
- "my changes" / "diff" → Run `git diff --name-only [base]...HEAD`, filter to scannable types, pass as `--target`
- "LWC" → `--target **/lwc/**`
- "Flows" → `--target **/*.flow-meta.xml`
- Not specified → Entire workspace (omit `--target`)
**For diff filtering details:** See `<skill_dir>/references/special-behaviors.md`.
### 1.6 OUTPUT — What format?
**DEFAULT:** Always JSON. Only change if user EXPLICITLY requests another format.
**Naming:** `./code-analyzer-results-<YYYYMMDD-HHmmss>.<ext>` (timestamp via `TIMESTAMP=$(date +%Y%m%d-%H%M%S)`)
Formats: `.json` (default), `.html` (report), `.sarif` (GitHub/IDE), `.csv` (spreadsheet), `.xml`
### 1.7 COMPARISON — Delta/trend analysis?
Map user keywords:
- "new since main" → `git diff --name-only main...HEAD` → scan those files
- "new since last commit" → `git diff --name-only HEAD~1`
- "compared to develop" → `git diff --name-only develop...HEAD`
---
## Step 2: Build the Rule Selector
**Syntax:** `:` = AND, `,` = OR, `()` = grouping
**Examples:**
- 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 rules: `all`
**More examples:** `<skill_dir>/references/command-examples.md`
---
## Step 3: Build the Full Command
Generate timestamp: `TIMESTAMP=$(date +%Y%m%d-%H%M%S)`
Build command:
```bash
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
```
**Key decisions:**
- DEFAULT: timestamped JSON (`.json`). Only change format if user explicitly requests HTML/SARIF/CSV/XML.
- Always include `--include-fixes` (enables Step 6 auto-fix)
- Omit `--target` to scan entire workspace
- For diff-based scans: get files via `git diff --name-only`, filter to scannable types, pass as `--target`
**Special cases:** See `<skill_dir>/references/special-behaviors.md` for SFGE/ApexGuru/AppExchange/diff filtering.
---
## Step 4: Execute the Scan
**⚠️ TOOL REQUIREMENT: Use Bash tool ONLY. DO NOT use run_code_analyzer (MCP tool) or any MCP tool.**
**Rules:** Foreground only (no `run_in_background`), hardcoded filename (not `$TIMESTAMP`), timeout 1200000ms, no `sleep`, log output to timestamped file.
**Steps:**
1. Generate timestamp: `date +%Y%m%d-%H%M%S` → capture output (e.g., `20260512-143022`) **using Bash tool**
2. Tell 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 command with literal timestamp in filename and `tee` to capture log (timeout: 1200000):
⚠️ **IMPORTANT:** Use the Bash tool, NOT the run_code_analyzer MCP tool.
```bash
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. After completion: Exit 0 = success. Error output → check both the log file and `<skill_dir>/references/error-handling.md`.
5. IMMEDIATELY parse results (Step 5). Do NOT ask user what they want.
---
## Step 5: Parse and Present Results
### Parsing Rules:
1. **Execute the parse script using `<skill_dir>`** — see below
2. **NEVER use `jq` to parse results** — jq one-liners WILL fail due to shell quoting issues
3. **Run it IMMEDIATELY after the scan** — do NOT ask the user "what would you like next?"
### Script Execution
All scripts are bundled in the `scripts/` subdirectory of the same directory that contains this SKILL.md file. Use the absolute path to that directory — do NOT use `./scripts/` as that resolves relative to the current working directory, not the skill directory.
```bash
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 `jq` as a substitute for the parse script
### How to Present Results:
**ALWAYS present a concise summary, then point to the output file for full details.**
```
## 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 |
| 2 | ApexSOQLInjection | pmd | 1 | QueryHelper.cls | 18 |
| ... (show up to 10 most critical) |
### Top Rules by Frequency
| Rule | Engine | Count |
|------|--------|-------|
| no-var | eslint | 170 |
| ApexDoc | pmd | 165 |
| ... |
Full results: `./code-analyzer-results-20260512-143022.json`
```
### Result Presentation Rules:
- **0 violations**: "Scan complete — no violations found! Output: `<path>`"
- **1-10**: Show all violations in table
- **11-50**: Show severity counts + top 10 violations
- **50-5000**: Show counts + top 10 violations + top 10 rules + top 5 files
- **5000+**: Same as 50-5000, plus suggest narrowing scope (severity/category/folder)
**Always end with:** Output file path + next-action offers (explain rules / apply fixes)
**For large result sets:** See `<skill_dir>/references/special-behaviors.md`.
---
## Step 6: Apply Engine-Provided Fixes (Post-Scan)
After presenting results, check if violations have **engine-provided fixes** (deterministic, not AI-generated).
**Rules:** NEVER apply without confirmation. Use EXACT scripts from `<skill_dir>/scripts/`. Filter vendor files if needed, then: Discover → Apply → Summarize.
**Flow:** Filter vendor (6.1 if needed) → discover (6.2) → present (6.3) → ASK user → apply (6.4) → summarize (6.5) → present results.
### 6.1 — Check for vendor files (if needed)
If user said "fix my code" or "project source", or if top files by violation count are vendor libraries (jQuery, Bootstrap, *.min.js), run:
```bash
node "<skill_dir>/scripts/filter-violations.js" \
"./code-analyzer-results-TIMESTAMP.json" \
"./code-analyzer-results-TIMESTAMP-filtered.json" \
--report
```
Present: "Excluded X vendor files (Y violations) - jQuery, Bootstrap, etc. Applying fixes to Z project files only."
Use filtered file for Step 6.3+. **See:** `<skill_dir>/references/vendor-file-handling.md` for detailed logic.
### 6.2 — Discover fixable violations
```bash
node "<skill_dir>/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
```
(Use filtered file from Step 6.1 if created.)
### 6.3 — Present fixable violations and ASK for confirmation
After running the discovery script, present results:
```
### 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 |
| no-hardcoded-values-slds2 | eslint | 4 | 76 |
| ... |
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 HERE AND WAIT FOR USER RESPONSE.
**Even if the user originally said "scan and fix everything", you MUST still stop here and wait.** Present the table, ask the question, and WAIT for a response in the NEXT turn.
### 6.4 — Apply fixes ONLY after user confirms
**Only proceed after user says "yes", "apply", "go ahead" IN A SEPARATE RESPONSE.**
```bash
node "<skill_dir>/scripts/apply-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
```
(Use filtered file if Step 6.1 created one.)
### 6.5 — After applying, ALWAYS run the summary script
⚠️ **MANDATORY**: After the apply script completes, you MUST run the summary script as your VERY NEXT action.
```bash
node "<skill_dir>/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP.json"
```
Then present to the user:
```
### Engine-Provided Fixes Applied Successfully ✓
**Applied X auto-fixes across Y files.**
| Severity | Fixes Applied |
|----------|---------------|
| Critical (1) | X |
| High (2) | X |
| ... |
| Rule | Fixes Applied |
|------|---------------|
| no-var | 169 |
| ... |
Want me to re-run the scan to verify the fixes resolved the violations?
```
### 6.6 — If user declines: Skip. If selects rules: filter. If "all": run as-is.
### 6.7 — Re-scan (optional): Re-run with new timestamp, compare before/after counts.
---
## Rules / Constraints
| Constraint | Rationale |
|-----------|-----------|
| Timestamped output (JSON + log) | Prevents overwrite; enables history tracking |
| Use `tee` for logs | Keeps logs in working dir with matching timestamp |
| Never use `--format` flag | Removed in v4+; use `--output-file <path>.<ext>` instead |
| Foreground scans, 1200000ms timeout | SFGE takes 10-20min; backgrounding loses output |
| Execute scripts from `<skill_dir>/scripts/` | Never write inline scripts or heredocs |
| Never apply fixes without confirmation | User must explicitly approve code modifications |
| Check for vendor files before fixes | If 50%+ vendor (jQuery, Bootstrap), filter first |
| Run fix scripts in order | Filter (if needed) → Discover → Apply → Summarize |
| SFGE needs explicit `--workspace` | Prevents template file compilation errors |
| Look up partial rule names first | Guessing fails; use `sf code-analyzer rules` to find exact name |
| ONLY Bash tool, never MCP | run_code_analyzer MCP tool bypasses script workflow |
| Never invoke other skills for fixes | This skill owns complete workflow end-to-end |
---
## Gotchas
| Issue | Why It Happens | Solution |
|-------|---------------|----------|
| `--format` flag error | Removed in v4+ | Use `--output-file <path>.<ext>` |
| Scan returns 0 results | Invalid rule selector | Run `sf code-analyzer rules --rule-selector <selector>` to verify |
| SFGE compilation error | Template files in workspace | Set `--workspace "force-app"` |
| jq parsing fails | Shell quoting issues | Use `node "<skill_dir>/scripts/parse-results.js"` |
| Inline scripts written | LLM generates custom code | NEVER write scripts — use existing from <skill_dir>/scripts/ |
| Scan times out | Large SFGE | Increase timeout to 1200000ms |
| run_code_analyzer MCP used | LLM prefers MCP over Bash | Use Bash tool ONLY |
| Other skills invoked | LLM delegates to other skills | Use apply-fixes.js from this skill only |
| Most violations are vendor | Includes jQuery, Bootstrap, *.min.js | Run filter-violations.js before applying fixes |
---
## Output Expectations
Every scan produces: timestamped JSON file, concise summary (severity/top violations/rules/files), next-action offers. If fixes applied: summary by severity/rule, offer verification.
---
## Reference File Index
`<skill_dir>` is the absolute path to the directory containing this SKILL.md file.
### Scripts (Always execute, 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 |
### References (Read when needed)
| File | When to read |
|------|-------------|
| `<skill_dir>/references/quick-start.md` | Command syntax templates |
| `<skill_dir>/references/flag-reference.md` | Flag docs, rule selector syntax |
| `<skill_dir>/references/error-handling.md` | Scan failure diagnosis |
| `<skill_dir>/references/engine-reference.md` | Engine capabilities, file types, rule tags |
| `<skill_dir>/references/command-examples.md` | Uncommon command scenarios |
| `<skill_dir>/references/special-behaviors.md` | SFGE/ApexGuru/AppExchange/diff/large scans |
| `<skill_dir>/references/vendor-file-handling.md` | Vendor file detection and filtering logic |
Examples in `<skill_dir>/examples/` show output structure validation and command patterns (basic/large/security scans, fix workflows).

View File

@ -0,0 +1,38 @@
# Examples Directory
Sample outputs and command patterns for the running-code-analyzer skill.
## Files
| File | Purpose |
|------|---------|
| [`basic-scan-output.json`](basic-scan-output.json) | Small scan (~127 violations) showing typical structure for personal projects |
| [`large-scan-output.json`](large-scan-output.json) | Large scan (~69k violations) from real NPSP project, demonstrates scale handling |
| [`security-focused-output.json`](security-focused-output.json) | Security-only scan with `all:Security:(1,2)` selector, shows critical issues |
| [`fix-application-before-after.md`](fix-application-before-after.md) | Before/after comparison showing engine-provided fixes in action |
| [`command-variations.md`](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:
```markdown
**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.
```

View File

@ -0,0 +1,92 @@
{
"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}
]
}
}

View File

@ -0,0 +1,333 @@
# 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)
```bash
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.log
```
**When:** User says "scan my code" with no specifics.
---
### 2. Security-Focused Scan
```bash
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.log
```
**When:** 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
```bash
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.log
```
**When:** User says "run PMD", "check my Apex code".
---
### 4. Multiple Engines
```bash
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.log
```
**When:** User says "scan Apex and JavaScript", "run PMD and ESLint".
**Selector breakdown:** Parentheses + comma = OR logic.
---
## Target-Specific Scans
### 5. Scan Specific File
```bash
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.log
```
**When:** User says "scan AccountService.cls".
---
### 6. Scan Specific Folder
```bash
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.log
```
**When:** User says "scan my LWC components", "check the lwc folder".
---
### 7. Scan Multiple Paths
```bash
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.log
```
**When:** User says "scan classes and triggers".
**Note:** Comma-separated paths in a single `--target` value.
---
### 8. Scan Using Glob Pattern
```bash
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.log
```
**When:** 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
```bash
git diff --name-only main...HEAD
```
**Step 2:** Filter to scannable types (`.cls`, `.trigger`, `.js`, `.ts`, `.flow-meta.xml`, etc.)
**Step 3:** Pass as `--target`
```bash
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.log
```
**When:** User says "scan my changes", "check what I modified", "analyze the diff".
---
## Advanced Scenarios
### 10. Deep Analysis with SFGE (Data Flow)
```bash
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.log
```
**When:** 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)
```bash
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.log
```
**When:** User says "find duplicates", "check for copy-paste", "detect code clones".
---
### 12. Check Vulnerable Libraries (RetireJS)
```bash
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.log
```
**When:** User says "check for vulnerable libraries", "scan dependencies", "find CVEs".
---
### 13. Analyze Flows
```bash
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.log
```
**When:** User says "analyze my Flows", "check Flow best practices".
**Note:** Requires Python 3.
---
### 14. Performance Analysis (ApexGuru)
```bash
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.log
```
**When:** 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
```bash
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.log
```
**When:** User explicitly requests HTML format.
**Note:** Extension determines format. JSON is default.
---
### 16. SARIF (GitHub/IDE Integration)
```bash
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.log
```
**When:** User says "SARIF format", "GitHub integration", "IDE integration".
---
### 17. CSV (Spreadsheet)
```bash
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.log
```
**When:** User says "CSV format", "export to spreadsheet", "Excel format".
---
## Complex Rule Selectors
### 18. Multiple Categories
```bash
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.log
```
**Selector breakdown:** All engines, Security OR Performance categories, Severity 1-3 (Critical to Moderate).
---
### 19. Specific Rule by Name
```bash
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.log
```
**When:** 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
```bash
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.log
```
**Selector 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
```bash
# WRONG - v3 syntax, does not exist in v4+
sf code-analyzer run --format json
```
**Why:** The `--format` flag was removed in v4+. Use `--output-file` with extension instead.
---
### ❌ Using `$TIMESTAMP` variable in command
```bash
# 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
```bash
# 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
```bash
# 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"`

View File

@ -0,0 +1,142 @@
# Fix Application: Before & After
This example demonstrates engine-provided auto-fix behavior on a small codebase.
## Initial Scan Results
**Command:**
```bash
sf code-analyzer run --rule-selector Recommended --output-file ./results.json --include-fixes
```
**Summary:**
- 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`
```javascript
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`
```javascript
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`
```css
.account-card {
border-radius: 4px; /* ← violation */
padding: 16px; /* ← violation */
}
```
---
## Apply Fixes
**Command:**
```bash
node <skill_dir>/scripts/apply-fixes.js ./results.json
```
**Output:**
```json
{
"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`
```javascript
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`
```javascript
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`
```css
.account-card {
border-radius: var(--slds-c-card-radius-border); /* ✓ fixed */
padding: var(--slds-c-card-spacing-block); /* ✓ fixed */
}
```
---
## Verification Scan
**Command:**
```bash
sf code-analyzer run --rule-selector Recommended --output-file ./results-after.json --include-fixes
```
**Summary:**
- 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.

View File

@ -0,0 +1,67 @@
{
"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
}
}

View File

@ -0,0 +1,95 @@
{
"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}
]
}
}

View File

@ -0,0 +1,27 @@
# 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 \| grep -E '...' → sf code-analyzer run --target <files> --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes` |
| "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` |

View File

@ -0,0 +1,34 @@
# 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 |

View File

@ -0,0 +1,29 @@
# 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

View File

@ -0,0 +1,96 @@
# 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
```bash
# 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:
```bash
--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-selector` expression syntax
- Output format is now determined by file extension rather than a separate flag
- Terminal display was separated into `--view` flag
- 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.

View File

@ -0,0 +1,28 @@
# 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:
```bash
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
```
Then use it:
```bash
# 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-fixes
```
**After the command completes**, read the output file and present a summary to the user.

View File

@ -0,0 +1,83 @@
# 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`, `.trigger` files)
### 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:**
```bash
sf code-analyzer run --rule-selector sfge --workspace "force-app" --target "force-app" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
```
Or if the user specifies a subfolder target like `force-app/main`:
```bash
sf code-analyzer run --rule-selector sfge --workspace "force-app" --target "force-app/main" --output-file "./code-analyzer-results-${TIMESTAMP}.json" --include-fixes
```
The `--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 display` first
- If no org authenticated: Guide user to `sf org login web` or `sf 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 all` to 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) or `git 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:
```bash
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

View File

@ -0,0 +1,239 @@
# 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 `var` declarations)
- **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:
```bash
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-fixes
```
**Pros:**
- 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:
```bash
# 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)
```javascript
// 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/ → Project
```
### 2. Name-Based Signals (30% weight)
```javascript
// 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 dependencies
```
### 3. Content-Based Signals (40% weight)
```javascript
// 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% vendor
```
### Final 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.json
```
## Workflow 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
```markdown
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. Summarize
```
## Edge 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:
```bash
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
```bash
# 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.json
```
Compare before/after violation counts to verify filtering accuracy.

View File

@ -0,0 +1,86 @@
#!/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 }));

View File

@ -0,0 +1,34 @@
#!/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 }));

View File

@ -0,0 +1,405 @@
#!/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);
}

View File

@ -0,0 +1,59 @@
#!/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));

View File

@ -0,0 +1,32 @@
#!/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).");

View File

@ -0,0 +1,28 @@
#!/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