From qe-framework
Develops Salesforce applications: writes/debugs Apex code, builds Lightning Web Components, optimizes SOQL queries, implements triggers, batch jobs, platform events, and Salesforce DX CI/CD pipelines.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qe-framework:Qsalesforce-developerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Analyze requirements** - Understand business needs, data model, governor limits, scalability
Load detailed guidance based on context:
| Topic | Reference | Load When |
|---|---|---|
| Apex Development | references/apex-development.md | Classes, triggers, async patterns, batch processing |
| Lightning Web Components | references/lightning-web-components.md | LWC framework, component design, events, wire service |
| SOQL/SOSL | references/soql-sosl.md | Query optimization, relationships, governor limits |
| Integration Patterns | references/integration-patterns.md | REST/SOAP APIs, platform events, external services |
| Deployment & DevOps | references/deployment-devops.md | Salesforce DX, CI/CD, scratch orgs, metadata API |
Database.update(scope, false) for partial success// CORRECT: collect IDs, query once outside the loop
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsert(Trigger.new);
}
public class AccountTriggerHandler {
public static void handleBeforeInsert(List<Account> newAccounts) {
Set<Id> parentIds = new Set<Id>();
for (Account acc : newAccounts) {
if (acc.ParentId != null) parentIds.add(acc.ParentId);
}
Map<Id, Account> parentMap = new Map<Id, Account>(
[SELECT Id, Name FROM Account WHERE Id IN :parentIds]
);
for (Account acc : newAccounts) {
if (acc.ParentId != null && parentMap.containsKey(acc.ParentId)) {
acc.Description = 'Child of: ' + parentMap.get(acc.ParentId).Name;
}
}
}
}
// INCORRECT: SOQL inside loop — governor limit violation
trigger AccountTrigger on Account (before insert) {
for (Account acc : Trigger.new) {
Account parent = [SELECT Id, Name FROM Account WHERE Id = :acc.ParentId]; // BAD
acc.Description = 'Child of: ' + parent.Name;
}
}
public class ContactBatchUpdate implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id, Email FROM Contact WHERE Email = null]);
}
public void execute(Database.BatchableContext bc, List<Contact> scope) {
for (Contact c : scope) {
c.Email = '[email protected]';
}
Database.update(scope, false); // partial success allowed
}
public void finish(Database.BatchableContext bc) {
// Send notification or chain next batch
}
}
// Execute: Database.executeBatch(new ContactBatchUpdate(), 200);
@IsTest
private class AccountTriggerHandlerTest {
@TestSetup
static void makeData() {
Account parent = new Account(Name = 'Parent Co');
insert parent;
Account child = new Account(Name = 'Child Co', ParentId = parent.Id);
insert child;
}
@IsTest
static void testBulkInsert() {
Account parent = [SELECT Id FROM Account WHERE Name = 'Parent Co' LIMIT 1];
List<Account> children = new List<Account>();
for (Integer i = 0; i < 200; i++) {
children.add(new Account(Name = 'Child ' + i, ParentId = parent.Id));
}
Test.startTest();
insert children;
Test.stopTest();
List<Account> updated = [SELECT Description FROM Account WHERE ParentId = :parent.Id];
System.assert(!updated.isEmpty(), 'Children should have descriptions set');
System.assert(updated[0].Description.startsWith('Child of:'), 'Description format mismatch');
}
}
// Selective query — use indexed fields in WHERE clause
List<Opportunity> opps = [
SELECT Id, Name, Amount, StageName
FROM Opportunity
WHERE AccountId IN :accountIds // indexed field
AND CloseDate >= :Date.today() // indexed field
ORDER BY CloseDate ASC
LIMIT 200
];
// Relationship query to avoid extra round-trips
List<Account> accounts = [
SELECT Id, Name,
(SELECT Id, LastName, Email FROM Contacts WHERE Email != null)
FROM Account
WHERE Id IN :accountIds
];
<!-- counterComponent.html -->
<template>
<lightning-card title="Counter">
<div class="slds-p-around_medium">
<p>Count: {count}</p>
<lightning-button label="Increment" onclick={handleIncrement}></lightning-button>
</div>
</lightning-card>
</template>
// counterComponent.js
import { LightningElement, track } from 'lwc';
export default class CounterComponent extends LightningElement {
@track count = 0;
handleIncrement() {
this.count += 1;
}
}
<!-- counterComponent.js-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
Database.Batchable<SObject> for 1000+ records with Database.update(scope, false)/**
* @description Brief method summary (imperative: "Updates account status based on...")
* @param accountIds Set of Account Ids to process
* @return Map<Id, Account> processed accounts keyed by Id
* @throws QueryException if SOQL returns no results; CalloutException on API failure
*/
public static Map<Id, Account> getAccountsByIds(Set<Id> accountIds) {
// Implementation with bulkified queries
}
Run: sfdx scanner:run --target='.' --severity-threshold=3
:variable binding (never string concatenation)SObjectAccessDecision before read/write on sensitive fieldswith sharing on classes accessing Account/Opportunity; use inherited sharing for helpersDatabase.update(..., false) with error handling for restricted records| Wrong | Correct |
|---|---|
for (Account a : allAccounts) { [SELECT ... WHERE Id = :a.Id]; } | Collect IDs, query once: [SELECT ... WHERE Id IN :accountIds] |
trigger Tr on Account (after update) { /* 100 lines */ } | Move logic to handler class: AccountTriggerHandler.handle(Trigger.new) |
if (acc.ParentId != null) { } then LATER query parent | Query parent upfront in bulkified collection: [SELECT ... WHERE Id IN :parentIds] |
Recursive trigger with no guard: if (Trigger.isExecuting) { /* more DML */ } | Use static flag: static Boolean isRecursing = false; |
new Apex class with 500+ lines | Max 300 lines per class; split into focused handlers and helpers |
npx claudepluginhub inho-team/qe-framework --plugin qe-frameworkCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.