scc-universal 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-apex-best-practices
|
|
3
|
+
description: >-
|
|
4
|
+
Use when writing production-ready Salesforce Apex classes — organization, error handling, collection patterns. Do NOT use for test classes or triggers.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Apex Best Practices
|
|
8
|
+
|
|
9
|
+
Procedures for writing production-ready Apex. Constraint rules (never/always lists) live in `sf-apex-constraints`. This skill covers the _how_ — class organization, error handling patterns, null safety techniques, and collection usage.
|
|
10
|
+
|
|
11
|
+
Reference files:
|
|
12
|
+
|
|
13
|
+
@../_reference/GOVERNOR_LIMITS.md
|
|
14
|
+
@../_reference/NAMING_CONVENTIONS.md
|
|
15
|
+
@../_reference/SECURITY_PATTERNS.md
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- When writing new Apex classes, triggers, or test classes for a Salesforce org
|
|
22
|
+
- When reviewing existing Apex code for structure or error handling issues
|
|
23
|
+
- When onboarding new developers to Salesforce Apex coding standards
|
|
24
|
+
- When refactoring legacy Apex code to improve readability and maintainability
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Class Organization
|
|
29
|
+
|
|
30
|
+
Organize class members in this order:
|
|
31
|
+
|
|
32
|
+
1. Constants (`static final`)
|
|
33
|
+
2. Static variables
|
|
34
|
+
3. Instance variables (fields)
|
|
35
|
+
4. Constructors
|
|
36
|
+
5. Public methods
|
|
37
|
+
6. Private/Protected methods
|
|
38
|
+
7. Inner classes
|
|
39
|
+
|
|
40
|
+
```apex
|
|
41
|
+
public with sharing class OrderProcessor {
|
|
42
|
+
|
|
43
|
+
// 1. Constants
|
|
44
|
+
private static final String STATUS_PENDING = 'Pending';
|
|
45
|
+
private static final String STATUS_PROCESSING = 'Processing';
|
|
46
|
+
private static final String STATUS_COMPLETE = 'Complete';
|
|
47
|
+
private static final Integer MAX_LINE_ITEMS = 500;
|
|
48
|
+
|
|
49
|
+
// 2. Static variables
|
|
50
|
+
private static Boolean isProcessing = false;
|
|
51
|
+
|
|
52
|
+
// 3. Instance variables
|
|
53
|
+
private List<Order__c> orders;
|
|
54
|
+
private Map<Id, Account> accountMap;
|
|
55
|
+
private OrderValidator validator;
|
|
56
|
+
|
|
57
|
+
// 4. Constructor
|
|
58
|
+
public OrderProcessor(List<Order__c> orders) {
|
|
59
|
+
this.orders = orders;
|
|
60
|
+
this.accountMap = new Map<Id, Account>();
|
|
61
|
+
this.validator = new OrderValidator();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 5. Public methods
|
|
65
|
+
public List<ProcessResult> processAll() {
|
|
66
|
+
List<ProcessResult> results = new List<ProcessResult>();
|
|
67
|
+
loadRelatedAccounts();
|
|
68
|
+
for (Order__c order : orders) {
|
|
69
|
+
results.add(processSingleOrder(order));
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 6. Private methods
|
|
75
|
+
private void loadRelatedAccounts() {
|
|
76
|
+
Set<Id> accountIds = new Set<Id>();
|
|
77
|
+
for (Order__c order : orders) {
|
|
78
|
+
if (order.AccountId != null) {
|
|
79
|
+
accountIds.add(order.AccountId);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (Account acc : [SELECT Id, Name, CreditLimit__c FROM Account WHERE Id IN :accountIds]) {
|
|
83
|
+
accountMap.put(acc.Id, acc);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private ProcessResult processSingleOrder(Order__c order) {
|
|
88
|
+
if (!validator.isValid(order)) {
|
|
89
|
+
return new ProcessResult(order.Id, false, validator.getLastError());
|
|
90
|
+
}
|
|
91
|
+
order.Status__c = STATUS_PROCESSING;
|
|
92
|
+
return new ProcessResult(order.Id, true, null);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 7. Inner classes
|
|
96
|
+
public class ProcessResult {
|
|
97
|
+
public Id orderId { get; private set; }
|
|
98
|
+
public Boolean success { get; private set; }
|
|
99
|
+
public String message { get; private set; }
|
|
100
|
+
|
|
101
|
+
public ProcessResult(Id orderId, Boolean success, String message) {
|
|
102
|
+
this.orderId = orderId;
|
|
103
|
+
this.success = success;
|
|
104
|
+
this.message = message;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Error Handling
|
|
113
|
+
|
|
114
|
+
### Custom Exception Classes
|
|
115
|
+
|
|
116
|
+
Create domain-specific exception classes instead of using generic exceptions. This lets callers catch specifically what they care about.
|
|
117
|
+
|
|
118
|
+
```apex
|
|
119
|
+
// Define exceptions in their own files or as inner classes
|
|
120
|
+
public class AccountServiceException extends Exception {}
|
|
121
|
+
public class OrderValidationException extends Exception {}
|
|
122
|
+
public class IntegrationCalloutException extends Exception {}
|
|
123
|
+
|
|
124
|
+
// Inner exception (acceptable for tight coupling)
|
|
125
|
+
public class AccountService {
|
|
126
|
+
public class AccountNotFoundException extends Exception {}
|
|
127
|
+
public class DuplicateAccountException extends Exception {}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Catch Scope
|
|
132
|
+
|
|
133
|
+
Catch the most specific exception type available. Catching `Exception` hides programming errors.
|
|
134
|
+
|
|
135
|
+
```apex
|
|
136
|
+
// Correct — catch what you expect, let others propagate
|
|
137
|
+
try {
|
|
138
|
+
processAccount(account);
|
|
139
|
+
} catch (DmlException e) {
|
|
140
|
+
throw new AccountServiceException('Failed to save account: ' + e.getDmlMessage(0), e);
|
|
141
|
+
} catch (CalloutException e) {
|
|
142
|
+
throw new IntegrationCalloutException('External service unavailable: ' + e.getMessage(), e);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Database.SaveResult Checking
|
|
147
|
+
|
|
148
|
+
When using partial-success DML, check every result.
|
|
149
|
+
|
|
150
|
+
```apex
|
|
151
|
+
List<Database.SaveResult> results = Database.insert(accounts, false);
|
|
152
|
+
List<String> errors = new List<String>();
|
|
153
|
+
|
|
154
|
+
for (Integer i = 0; i < results.size(); i++) {
|
|
155
|
+
Database.SaveResult result = results[i];
|
|
156
|
+
if (!result.isSuccess()) {
|
|
157
|
+
for (Database.Error err : result.getErrors()) {
|
|
158
|
+
errors.add(
|
|
159
|
+
'Record ' + accounts[i].Name + ': ' +
|
|
160
|
+
err.getStatusCode() + ' - ' + err.getMessage()
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!errors.isEmpty()) {
|
|
167
|
+
throw new AccountServiceException(
|
|
168
|
+
'Partial DML failure. Errors:\n' + String.join(errors, '\n')
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Meaningful Error Messages
|
|
174
|
+
|
|
175
|
+
Include context in exception messages — what was being done, what record was involved, what the actual error was.
|
|
176
|
+
|
|
177
|
+
```apex
|
|
178
|
+
throw new AccountServiceException(
|
|
179
|
+
String.format(
|
|
180
|
+
'Failed to update Account {0} (Id: {1}) during credit limit recalculation. ' +
|
|
181
|
+
'DML error: {2}',
|
|
182
|
+
new List<Object>{ account.Name, account.Id, dmlError.getMessage() }
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Single Responsibility Principle
|
|
190
|
+
|
|
191
|
+
Each class should have one reason to change. Split classes by responsibility, not by object type.
|
|
192
|
+
|
|
193
|
+
```apex
|
|
194
|
+
// Correct — each class has one job
|
|
195
|
+
public class AccountService { public void createAccount() {} }
|
|
196
|
+
public class AccountNotificationService { public void sendWelcomeEmail() {} }
|
|
197
|
+
public class OpportunityService { public void createFromAccount() {} }
|
|
198
|
+
public class ERPSyncService { public void syncAccount() {} }
|
|
199
|
+
public class AccountDocumentService { public void generateOnboardingPDF() {} }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Method Length
|
|
203
|
+
|
|
204
|
+
Methods longer than ~50 lines are doing too much. Extract private helper methods.
|
|
205
|
+
|
|
206
|
+
```apex
|
|
207
|
+
// Correct — orchestrator calling focused helpers
|
|
208
|
+
public void processNewCustomer(Account account) {
|
|
209
|
+
validateNewCustomer(account);
|
|
210
|
+
enrichFromExternalData(account);
|
|
211
|
+
Account inserted = insertAccount(account);
|
|
212
|
+
createDefaultOpportunity(inserted);
|
|
213
|
+
sendWelcomeNotification(inserted);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Access Modifiers
|
|
220
|
+
|
|
221
|
+
Start with `private`. Promote to `protected`, then `public`, only when necessary. Use `global` only for managed package APIs.
|
|
222
|
+
|
|
223
|
+
```apex
|
|
224
|
+
public with sharing class DiscountCalculator {
|
|
225
|
+
|
|
226
|
+
// Private — internal state
|
|
227
|
+
private Decimal baseRate;
|
|
228
|
+
private Map<String, Decimal> tierRates;
|
|
229
|
+
|
|
230
|
+
// Private — internal logic
|
|
231
|
+
private Decimal lookupTierRate(String tier) {
|
|
232
|
+
return tierRates.containsKey(tier) ? tierRates.get(tier) : baseRate;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Protected — available to subclasses for extension
|
|
236
|
+
protected Decimal applyMinimumDiscount(Decimal calculated) {
|
|
237
|
+
return Math.max(calculated, 0.05);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Public — the contract
|
|
241
|
+
public Decimal calculateDiscount(String customerTier, Decimal orderAmount) {
|
|
242
|
+
Decimal rate = lookupTierRate(customerTier);
|
|
243
|
+
return applyMinimumDiscount(rate * orderAmount);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Null Safety
|
|
251
|
+
|
|
252
|
+
Check for null before dereferencing. Salesforce returns null (not empty collections) for uninitialized parent relationship fields. Child relationship sub-queries return empty lists, not null.
|
|
253
|
+
|
|
254
|
+
```apex
|
|
255
|
+
// Preferred — null-safe navigation operator (?.)
|
|
256
|
+
String city = account?.BillingAddress?.City;
|
|
257
|
+
String ownerEmail = contact?.Account?.Owner?.Email;
|
|
258
|
+
|
|
259
|
+
// Null-safe Map retrieval with null coalescing (requires minimum API version — see @../_reference/API_VERSIONS.md)
|
|
260
|
+
String value = myMap.get('key')?.toLowerCase() ?? '';
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
> **Note:** The `?.` operator prevents NullPointerException when the object reference is null. It does NOT prevent SObjectException when accessing fields not included in the SOQL query. Always ensure queried fields are in the SELECT clause.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Collection Patterns
|
|
268
|
+
|
|
269
|
+
### Choosing List vs Set vs Map
|
|
270
|
+
|
|
271
|
+
```apex
|
|
272
|
+
// List — ordered, allows duplicates, use for DML and output
|
|
273
|
+
List<Account> accountsToInsert = new List<Account>();
|
|
274
|
+
|
|
275
|
+
// Set — unordered, no duplicates, use for Id lookup sets and deduplication
|
|
276
|
+
Set<Id> processedIds = new Set<Id>();
|
|
277
|
+
|
|
278
|
+
// Map — key-value lookup, use for joining data across queries
|
|
279
|
+
Map<Id, Account> accountById = new Map<Id, Account>(
|
|
280
|
+
[SELECT Id, Name FROM Account WHERE Id IN :accountIds]
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Build Maps Inline from Queries
|
|
285
|
+
|
|
286
|
+
```apex
|
|
287
|
+
// Idiomatic Apex — Map constructor from query
|
|
288
|
+
Map<Id, Account> accountMap = new Map<Id, Account>(
|
|
289
|
+
[SELECT Id, Name, OwnerId FROM Account WHERE Id IN :accountIds]
|
|
290
|
+
);
|
|
291
|
+
Account acc = accountMap.get(someId);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
> **Note:** In Apex, `new List<String>(n)` creates a list pre-filled with n nulls (unlike Java). Use `new List<String>()` for an empty list.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Comments
|
|
299
|
+
|
|
300
|
+
### Javadoc-Style for Public Methods
|
|
301
|
+
|
|
302
|
+
Document the contract, not the implementation.
|
|
303
|
+
|
|
304
|
+
```apex
|
|
305
|
+
/**
|
|
306
|
+
* Calculates the renewal opportunity amount based on the original contract value
|
|
307
|
+
* and the customer's tier-based renewal discount.
|
|
308
|
+
*
|
|
309
|
+
* @param contract The original contract record. Must not be null. Must have
|
|
310
|
+
* Amount__c and Customer_Tier__c populated.
|
|
311
|
+
* @param renewalDate The target renewal date. Used to determine active pricing tiers.
|
|
312
|
+
* @return The calculated renewal amount. Never negative. Returns 0 if contract
|
|
313
|
+
* amount is null.
|
|
314
|
+
* @throws RenewalCalculationException If no pricing tier is found for the contract's
|
|
315
|
+
* customer tier value.
|
|
316
|
+
*/
|
|
317
|
+
public Decimal calculateRenewalAmount(Contract__c contract, Date renewalDate) {
|
|
318
|
+
// implementation
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Inline Comments
|
|
323
|
+
|
|
324
|
+
Only when logic is not obvious. Explain _why_, not _what_.
|
|
325
|
+
|
|
326
|
+
```apex
|
|
327
|
+
// Salesforce does not enforce uniqueness on Name by default; we enforce it
|
|
328
|
+
// here because duplicate account names break downstream ERP sync.
|
|
329
|
+
if (existingAccountNames.contains(acc.Name)) {
|
|
330
|
+
acc.addError('An account with this name already exists. Use a unique trading name.');
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Complete Well-Structured Class Example
|
|
337
|
+
|
|
338
|
+
```apex
|
|
339
|
+
/**
|
|
340
|
+
* Service class for credit limit management operations on Account records.
|
|
341
|
+
* Enforces sharing rules; operates within the running user's data visibility.
|
|
342
|
+
*/
|
|
343
|
+
public with sharing class CreditLimitService {
|
|
344
|
+
|
|
345
|
+
private static final Decimal DEFAULT_CREDIT_LIMIT = 10000.00;
|
|
346
|
+
private static final Decimal PREMIUM_CREDIT_LIMIT = 100000.00;
|
|
347
|
+
private static final String TIER_PREMIUM = 'Premium';
|
|
348
|
+
private static final String TIER_STANDARD = 'Standard';
|
|
349
|
+
|
|
350
|
+
private final List<Account> accounts;
|
|
351
|
+
|
|
352
|
+
public CreditLimitService(List<Account> accounts) {
|
|
353
|
+
if (accounts == null || accounts.isEmpty()) {
|
|
354
|
+
throw new CreditLimitException('Account list must not be null or empty.');
|
|
355
|
+
}
|
|
356
|
+
this.accounts = accounts;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public Map<Id, Decimal> recalculateLimits() {
|
|
360
|
+
Map<Id, Decimal> results = new Map<Id, Decimal>();
|
|
361
|
+
for (Account acc : accounts) {
|
|
362
|
+
results.put(acc.Id, calculateLimitForAccount(acc));
|
|
363
|
+
}
|
|
364
|
+
return results;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
public List<String> saveLimits(Map<Id, Decimal> limitsByAccountId) {
|
|
368
|
+
List<Account> toUpdate = buildUpdateRecords(limitsByAccountId);
|
|
369
|
+
return executeDmlWithErrorCollection(toUpdate);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private Decimal calculateLimitForAccount(Account acc) {
|
|
373
|
+
if (acc.Customer_Tier__c == TIER_PREMIUM) return PREMIUM_CREDIT_LIMIT;
|
|
374
|
+
if (acc.Customer_Tier__c == TIER_STANDARD) return calculateStandardLimit(acc);
|
|
375
|
+
return DEFAULT_CREDIT_LIMIT;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private Decimal calculateStandardLimit(Account acc) {
|
|
379
|
+
if (acc.AnnualRevenue == null || acc.AnnualRevenue <= 0) return DEFAULT_CREDIT_LIMIT;
|
|
380
|
+
return Math.min(acc.AnnualRevenue * 0.05, 50000.00);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private List<Account> buildUpdateRecords(Map<Id, Decimal> limitsByAccountId) {
|
|
384
|
+
List<Account> records = new List<Account>();
|
|
385
|
+
for (Id accId : limitsByAccountId.keySet()) {
|
|
386
|
+
records.add(new Account(
|
|
387
|
+
Id = accId,
|
|
388
|
+
CreditLimit__c = limitsByAccountId.get(accId),
|
|
389
|
+
Last_Credit_Review_Date__c = Date.today()
|
|
390
|
+
));
|
|
391
|
+
}
|
|
392
|
+
return records;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private List<String> executeDmlWithErrorCollection(List<Account> records) {
|
|
396
|
+
List<Database.SaveResult> results = Database.update(records, false);
|
|
397
|
+
List<String> errors = new List<String>();
|
|
398
|
+
for (Integer i = 0; i < results.size(); i++) {
|
|
399
|
+
if (!results[i].isSuccess()) {
|
|
400
|
+
for (Database.Error err : results[i].getErrors()) {
|
|
401
|
+
errors.add('Account Id ' + records[i].Id + ': ' +
|
|
402
|
+
err.getStatusCode() + ' — ' + err.getMessage());
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return errors;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
public class CreditLimitException extends Exception {}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Related
|
|
416
|
+
|
|
417
|
+
- **Agent**: `sf-review-agent` — For interactive, in-depth guidance
|
|
418
|
+
|
|
419
|
+
### Guardrails
|
|
420
|
+
|
|
421
|
+
- `sf-apex-constraints` — Enforces governor limits, naming rules, security requirements, and bulkification rules that apply to all Apex code
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-apex-constraints
|
|
3
|
+
description: >-
|
|
4
|
+
Enforce Apex governor limits, naming, bulkification, and security rules. Use when writing or reviewing ANY Apex class, trigger, or batch job. Do NOT use for LWC, Flow, or Visualforce.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Apex Constraints
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
This skill auto-activates when writing, reviewing, or modifying any Apex class, trigger, or batch job. It enforces governor limits, naming conventions, bulkification rules, and security requirements for all Apex artifacts.
|
|
12
|
+
|
|
13
|
+
Hard rules that every Apex class, trigger, and batch job must satisfy. Violations here cause governor failures, security review rejections, or production incidents. Reference files contain the full data; this skill contains only the enforcement rules.
|
|
14
|
+
|
|
15
|
+
## Core Rules
|
|
16
|
+
|
|
17
|
+
@../_reference/GOVERNOR_LIMITS.md
|
|
18
|
+
@../_reference/NAMING_CONVENTIONS.md
|
|
19
|
+
@../_reference/SECURITY_PATTERNS.md
|
|
20
|
+
@../_reference/DEPRECATIONS.md
|
|
21
|
+
|
|
22
|
+
## Apex-Specific Rules
|
|
23
|
+
|
|
24
|
+
### Never Do
|
|
25
|
+
|
|
26
|
+
- **SOQL inside a loop** — exceeds the per-transaction SOQL query limit (see @../_reference/GOVERNOR_LIMITS.md); query once outside the loop, store in Map
|
|
27
|
+
- **DML inside a loop** — exceeds the per-transaction DML limit (see @../_reference/GOVERNOR_LIMITS.md); collect records in a List, single DML after loop
|
|
28
|
+
- **Catch generic `Exception`** — masks programming bugs (NullPointerException, TypeException); catch specific types only
|
|
29
|
+
- **Omit sharing keyword** — classes without a sharing keyword default to `without sharing`; always declare `with sharing`, `without sharing`, or `inherited sharing` explicitly
|
|
30
|
+
- **Use `without sharing` on user-facing classes** — bypasses record-level security; must be `with sharing`
|
|
31
|
+
- **Hardcode Record IDs** — IDs differ per org/sandbox; use SOQL, Custom Metadata, or Custom Settings
|
|
32
|
+
- **Hardcode credentials or endpoint URLs** — use Named Credentials / External Credentials
|
|
33
|
+
- **Use `global` access modifier** — locks managed package API surface; use `public` unless building a package API
|
|
34
|
+
- **Leave `System.debug` in production code** — fills debug logs, can expose sensitive data
|
|
35
|
+
- **Use string concatenation in dynamic SOQL** — SOQL injection risk; use bind variables or `Database.queryWithBinds()`
|
|
36
|
+
- **Ignore `Database.SaveResult`** — partial-success DML silently drops failures; always inspect every result
|
|
37
|
+
- **Use `element.innerHTML = userInput`** — XSS vulnerability; use `textContent` or sanitized components
|
|
38
|
+
- **Write methods longer than 50 lines** — extract private helper methods for testability
|
|
39
|
+
- **Use `List<sObject>` as a parameter type** — loses type information; use concrete types like `List<Account>`
|
|
40
|
+
- **Use Hungarian notation** (`strName`, `lstAccounts`) — use descriptive camelCase names instead
|
|
41
|
+
|
|
42
|
+
### Always Do
|
|
43
|
+
|
|
44
|
+
- **Declare `with sharing` by default** — only use `without sharing` with a documented justification
|
|
45
|
+
- **Enforce CRUD/FLS** — use `WITH USER_MODE` for SOQL, `AccessLevel.USER_MODE` for DML on user-facing operations
|
|
46
|
+
- **Bulkify all triggers** — test with 200 records (standard trigger batch size); no per-record SOQL/DML
|
|
47
|
+
- **One trigger per object** — delegate all logic to a handler class (`{Object}TriggerHandler`)
|
|
48
|
+
- **Use PascalCase for classes**, camelCase for methods/variables, UPPER_SNAKE_CASE for constants
|
|
49
|
+
- **Suffix classes by role** — `Service`, `Selector`, `TriggerHandler`, `Batch`, `Job`, `Scheduler`, `Controller`, `Test`, `Exception`
|
|
50
|
+
- **Suffix test classes with `Test`** (not prefix) — `AccountServiceTest`, not `TestAccountService`
|
|
51
|
+
- **Name test methods** as `test{Method}_{scenario}_{expectedResult}`
|
|
52
|
+
- **Create domain-specific exception classes** — not generic `Exception` throws
|
|
53
|
+
- **Check limits programmatically** before expensive operations — use `Limits.getQueries()`, `Limits.getCpuTime()`, etc.
|
|
54
|
+
- **Use Map/Set for lookups** — O(1) vs O(n) nested loops; prevents CPU time exhaustion
|
|
55
|
+
- **Use `String.join()` for string building** — not concatenation in loops (heap + CPU cost)
|
|
56
|
+
- **Null-check before dereferencing** — use `?.` (null-safe navigation) for parent relationship fields
|
|
57
|
+
- **Offload to async** when processing >200 records or when CPU exceeds 8,000ms threshold
|
|
58
|
+
- **Organize class members** in order: constants, static variables, instance variables, constructors, public methods, private methods, inner classes
|
|
59
|
+
|
|
60
|
+
## Anti-Pattern Reference
|
|
61
|
+
|
|
62
|
+
| Anti-Pattern | Problem | Correct Pattern |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| SOQL in loop | Exceeds per-transaction SOQL limit (see @../_reference/GOVERNOR_LIMITS.md) | Query once, store in Map |
|
|
65
|
+
| DML in loop | Exceeds per-transaction DML limit (see @../_reference/GOVERNOR_LIMITS.md) | Collect records, single DML after loop |
|
|
66
|
+
| Nested loops for matching | CPU time exhaustion (O(n^2)) | Map/Set lookup (O(1)) |
|
|
67
|
+
| String concat in loop | Heap growth + CPU waste | `List<String>` + `String.join()` |
|
|
68
|
+
| SELECT * (all fields) | Heap exhaustion | SELECT only required fields |
|
|
69
|
+
| No sharing keyword | Silent `without sharing` default | Explicit `with sharing` declaration |
|
|
70
|
+
| Missing CRUD/FLS check | Security review failure | `WITH USER_MODE` / `AccessLevel.USER_MODE` |
|
|
71
|
+
| Dynamic SOQL via concat | SOQL injection | Bind variables / `queryWithBinds()` |
|
|
72
|
+
| Catching `Exception` | Masks real bugs | Catch specific exception types |
|
|
73
|
+
| Ignoring SaveResult | Silent data loss | Inspect every `Database.SaveResult` |
|
|
74
|
+
| Hardcoded IDs | Breaks across orgs | SOQL / Custom Metadata lookup |
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
|
|
78
|
+
- **Pattern skill**: `sf-apex-best-practices` — implementation examples for the rules above (class organization, error handling, collection patterns)
|
|
79
|
+
- Reference files: @../_reference/GOVERNOR_LIMITS.md, @../_reference/NAMING_CONVENTIONS.md, @../_reference/SECURITY_PATTERNS.md
|