acek-skills 1.0.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.
@@ -0,0 +1,374 @@
1
+ ---
2
+ name: sf-data-migration
3
+ description: >
4
+ Use this skill for Salesforce data migration, bulk data operations, and data management tasks:
5
+ designing import/export strategies, writing Data Loader automation scripts, upsert operations
6
+ with External IDs, bulk SOQL exports, data cleansing before migration, error handling for bulk
7
+ loads, and generating data migration plans. Trigger when the user mentions "data migration",
8
+ "bulk import", "data loader", "upsert", "external ID", "migrate records", "export data",
9
+ "data cleansing", "bulk delete", "mass update", or asks how to move large volumes of records
10
+ between orgs or from external systems into Salesforce.
11
+ ---
12
+
13
+ # Salesforce Data Migration Skill
14
+
15
+ ## Environment Context
16
+ - API Version: **61.0**
17
+ - Tooling: **SF CLI** (`sf data` commands) for dev/test volumes; Data Loader for production volumes
18
+ - Always migrate to **sandbox first**, verify, then production
19
+ - Single org: sandbox → production
20
+
21
+ ---
22
+
23
+ ## Migration Planning — Before Touching Any Data
24
+
25
+ ### Step 1: Discovery Checklist
26
+ - [ ] What objects and how many records? (run COUNT queries first)
27
+ - [ ] Are there required fields that source data may not have?
28
+ - [ ] Are there lookup/relationship fields? (parent records must exist first)
29
+ - [ ] Are there Record Types? (map source values to Salesforce Record Type IDs)
30
+ - [ ] Are there Validation Rules that will block import? (consider deactivating temporarily)
31
+ - [ ] Are there Triggers/Flows that fire on insert/update? (decide: bypass or let fire?)
32
+ - [ ] Does the source data have a unique identifier for upsert (External ID)?
33
+ - [ ] What is the target volume? (under 10K → SF CLI; over 10K → Data Loader / Bulk API)
34
+
35
+ ### Step 2: Dependency Order
36
+ Always load in parent → child order:
37
+ ```
38
+ 1. Custom Metadata / Reference Data (e.g. picklist-driving records)
39
+ 2. Accounts
40
+ 3. Contacts (requires Account)
41
+ 4. Opportunities (requires Account)
42
+ 5. Opportunity Products (requires Opportunity + Pricebook Entry)
43
+ 6. Cases (requires Account + Contact)
44
+ 7. Custom Objects (based on lookup dependency)
45
+ ```
46
+
47
+ ### Step 3: External ID Strategy
48
+ Every migrated object should have an External ID field to enable upsert and relationship mapping.
49
+
50
+ ```apex
51
+ // External ID field naming convention:
52
+ ExternalId__c // generic
53
+ LegacySystemId__c // named after source system
54
+ SapId__c, OracleId__c // system-specific
55
+ ```
56
+
57
+ Field settings:
58
+ - Type: Text (or Number if IDs are numeric)
59
+ - Mark as: **External ID** ✓ and **Unique** ✓
60
+ - Max length: match source system ID length
61
+
62
+ ---
63
+
64
+ ## SF CLI Data Commands — Dev/Test Volumes (<50K records)
65
+
66
+ ### Query & Export
67
+ ```bash
68
+ # Basic query to CSV
69
+ sf data query \
70
+ --query "SELECT Id, Name, Industry, Phone FROM Account WHERE CreatedDate = THIS_YEAR" \
71
+ --target-org <alias> \
72
+ --result-format csv > exports/accounts.csv
73
+
74
+ # Export with relationships
75
+ sf data query \
76
+ --query "SELECT Id, Name, Account.Name, Email FROM Contact WHERE AccountId != null" \
77
+ --target-org <alias> \
78
+ --result-format csv > exports/contacts.csv
79
+
80
+ # Count records before migration
81
+ sf data query \
82
+ --query "SELECT COUNT() FROM Account" \
83
+ --target-org <alias>
84
+
85
+ # Export with Bulk API (for larger volumes)
86
+ sf data export bulk \
87
+ --query "SELECT Id, Name, ExternalId__c FROM Account" \
88
+ --output-file exports/accounts-bulk.csv \
89
+ --target-org <alias>
90
+ ```
91
+
92
+ ### Import / Upsert
93
+ ```bash
94
+ # Insert new records
95
+ sf data import bulk \
96
+ --sobject Account \
97
+ --file imports/accounts.csv \
98
+ --target-org <alias>
99
+
100
+ # Upsert (insert + update based on External ID)
101
+ sf data upsert bulk \
102
+ --sobject Account \
103
+ --file imports/accounts.csv \
104
+ --external-id ExternalId__c \
105
+ --target-org <alias>
106
+
107
+ # Delete records
108
+ sf data delete bulk \
109
+ --sobject Account \
110
+ --file exports/accounts-to-delete.csv \
111
+ --target-org <alias>
112
+ ```
113
+
114
+ ### Check Job Status
115
+ ```bash
116
+ sf data bulk results \
117
+ --job-id <JobId> \
118
+ --target-org <alias>
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Data Loader — Production Volumes (>50K records)
124
+
125
+ Data Loader uses Bulk API 2.0 by default. Use for production migration.
126
+
127
+ ### CSV Requirements
128
+ - UTF-8 encoding (no BOM)
129
+ - First row: field API names (e.g. `Name,ExternalId__c,Industry__c`)
130
+ - `null` to clear a field: leave cell empty OR use `#N/A` for some fields
131
+ - Date format: `YYYY-MM-DDThh:mm:ss.000Z` (ISO 8601)
132
+ - Boolean: `true` / `false` (lowercase)
133
+ - Currency: numeric only, no currency symbols
134
+
135
+ ### Batch Size Recommendations
136
+ | Volume | Batch Size | Concurrency |
137
+ |---|---|---|
138
+ | < 10K | 200 | Serial |
139
+ | 10K – 100K | 2,000 | Parallel |
140
+ | 100K – 1M | 10,000 | Parallel |
141
+ | > 1M | 10,000 | Parallel + schedule overnight |
142
+
143
+ ### Error Handling
144
+ - Always save the **success file** and **error file** per run
145
+ - Error file contains failed rows + error message — fix and re-run only errors
146
+ - Common errors and fixes:
147
+
148
+ | Error | Cause | Fix |
149
+ |---|---|---|
150
+ | `REQUIRED_FIELD_MISSING` | Required field empty in CSV | Fill required fields; check validation rules |
151
+ | `INVALID_FIELD` | Field API name wrong | Check with `sf sobject describe --sobject <Object>` |
152
+ | `FIELD_INTEGRITY_EXCEPTION` | Lookup ID doesn't exist in org | Load parent records first |
153
+ | `DUPLICATE_VALUE` | Unique field already exists | Switch from insert to upsert with External ID |
154
+ | `STRING_TOO_LONG` | Value exceeds field length | Truncate or increase field length |
155
+ | `CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY` | Trigger/validation blocking | Check trigger logic; may need to deactivate VR temporarily |
156
+
157
+ ---
158
+
159
+ ## Upsert Pattern — External ID Relationship Mapping
160
+
161
+ When loading child records and parent exists via External ID (not Salesforce ID):
162
+
163
+ ### CSV Format
164
+ Instead of using Salesforce `AccountId`, reference the parent's External ID:
165
+
166
+ ```csv
167
+ Name,Email,Account.ExternalId__c
168
+ John Doe,john@example.com,ACC-12345
169
+ Jane Smith,jane@example.com,ACC-67890
170
+ ```
171
+
172
+ Salesforce resolves `Account.ExternalId__c` to the Account's Salesforce ID automatically during upsert.
173
+
174
+ ### Upsert Command
175
+ ```bash
176
+ sf data upsert bulk \
177
+ --sobject Contact \
178
+ --file imports/contacts-with-account-ref.csv \
179
+ --external-id ExternalId__c \
180
+ --target-org <alias>
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Data Cleansing — Before Migration
186
+
187
+ ### Common Issues to Fix in Source Data
188
+
189
+ ```python
190
+ # Python script example — clean CSV before Salesforce import
191
+
192
+ import pandas as pd
193
+
194
+ df = pd.read_csv('raw_export.csv')
195
+
196
+ # Remove duplicates on External ID
197
+ df = df.drop_duplicates(subset=['external_id'])
198
+
199
+ # Truncate strings to Salesforce field limits
200
+ df['name'] = df['name'].str[:255] # Text field max
201
+ df['description'] = df['description'].str[:32000] # Long Text max
202
+
203
+ # Normalize phone numbers
204
+ df['phone'] = df['phone'].str.replace(r'\D', '', regex=True)
205
+
206
+ # Map picklist values to Salesforce API values
207
+ industry_map = {
208
+ 'Tech': 'Technology',
209
+ 'Fin': 'Finance',
210
+ 'Health': 'Healthcare'
211
+ }
212
+ df['industry'] = df['industry'].map(industry_map)
213
+
214
+ # Convert date format to ISO 8601
215
+ df['created_date'] = pd.to_datetime(df['created_date']).dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
216
+
217
+ # Replace NaN with empty string
218
+ df = df.fillna('')
219
+
220
+ df.to_csv('cleaned_export.csv', index=False, encoding='utf-8')
221
+ print(f"Cleaned: {len(df)} records")
222
+ ```
223
+
224
+ ### SOQL-Based Deduplication Check
225
+ ```bash
226
+ # Find duplicates in Salesforce by External ID
227
+ sf data query \
228
+ --query "SELECT ExternalId__c, COUNT(Id) total FROM Account GROUP BY ExternalId__c HAVING COUNT(Id) > 1" \
229
+ --target-org <alias>
230
+
231
+ # Find records without External ID (not upsert-safe)
232
+ sf data query \
233
+ --query "SELECT Id, Name FROM Account WHERE ExternalId__c = null LIMIT 200" \
234
+ --target-org <alias>
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Apex Batch for Large-Scale Updates
240
+
241
+ When you need to transform/update data already in Salesforce at scale:
242
+
243
+ ```apex
244
+ public class AccountMigrationBatch implements Database.Batchable<SObject>, Database.Stateful {
245
+ private Integer successCount = 0;
246
+ private Integer errorCount = 0;
247
+ private List<String> errors = new List<String>();
248
+
249
+ public Database.QueryLocator start(Database.BatchableContext bc) {
250
+ return Database.getQueryLocator(
251
+ 'SELECT Id, Name, LegacyStatus__c, Status__c ' +
252
+ 'FROM Account WHERE MigrationComplete__c = false'
253
+ );
254
+ }
255
+
256
+ public void execute(Database.BatchableContext bc, List<Account> scope) {
257
+ List<Account> toUpdate = new List<Account>();
258
+
259
+ for (Account acc : scope) {
260
+ // Transform logic
261
+ acc.Status__c = mapStatus(acc.LegacyStatus__c);
262
+ acc.MigrationComplete__c = true;
263
+ toUpdate.add(acc);
264
+ }
265
+
266
+ Database.SaveResult[] results = Database.update(toUpdate, false); // allOrNone=false
267
+ for (Integer i = 0; i < results.size(); i++) {
268
+ if (results[i].isSuccess()) {
269
+ successCount++;
270
+ } else {
271
+ errorCount++;
272
+ errors.add(toUpdate[i].Id + ': ' + results[i].getErrors()[0].getMessage());
273
+ }
274
+ }
275
+ }
276
+
277
+ public void finish(Database.BatchableContext bc) {
278
+ // Send summary email or create log record
279
+ System.debug('Migration complete. Success: ' + successCount + ', Errors: ' + errorCount);
280
+ for (String err : errors) {
281
+ System.debug('ERROR: ' + err);
282
+ }
283
+ }
284
+
285
+ private String mapStatus(String legacyStatus) {
286
+ Map<String, String> statusMap = new Map<String, String>{
287
+ 'A' => 'Active',
288
+ 'I' => 'Inactive',
289
+ 'P' => 'Pending'
290
+ };
291
+ return statusMap.containsKey(legacyStatus) ? statusMap.get(legacyStatus) : 'Unknown';
292
+ }
293
+ }
294
+ ```
295
+
296
+ ### Execute Batch
297
+ ```apex
298
+ // In Anonymous Apex
299
+ Database.executeBatch(new AccountMigrationBatch(), 200); // 200 records per batch
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Migration Runbook Template
305
+
306
+ File: `instructions/migration/YYYYMMDD-<migration-name>-runbook.md`
307
+
308
+ ```markdown
309
+ # Migration Runbook: [Migration Name]
310
+
311
+ **Date:** YYYY-MM-DD
312
+ **Target Org:** [{{DEV_ORG_ALIAS}} / {{PROD_ORG_ALIAS}}]
313
+ **Estimated Volume:** [X records across Y objects]
314
+ **Estimated Duration:** [X hours]
315
+
316
+ ---
317
+
318
+ ## Pre-Migration Checklist
319
+ - [ ] Backup current data (export all affected objects)
320
+ - [ ] Verify External ID field exists on all target objects
321
+ - [ ] Test migration in sandbox with 10% sample — confirm success rate > 99%
322
+ - [ ] Identify Validation Rules to deactivate temporarily (list below)
323
+ - [ ] Identify Triggers/Flows that will fire — confirm acceptable behavior
324
+ - [ ] Communicate downtime window to affected users (if any)
325
+ - [ ] Confirm Data Loader / SF CLI version is current
326
+
327
+ ## Validation Rules to Deactivate During Migration
328
+ | Object | Validation Rule | Reason to Deactivate |
329
+ |---|---|---|
330
+ | Account | ACCOUNT_RequireIndustry | Source data may not have Industry |
331
+
332
+ ## Load Order
333
+ 1. [Object 1] — [file name] — [estimated records]
334
+ 2. [Object 2] — [file name] — [estimated records]
335
+
336
+ ## Rollback Plan
337
+ If error rate > 5% or critical data issue found:
338
+ 1. Stop migration immediately
339
+ 2. Export all records inserted this session (filter by CreatedDate = TODAY)
340
+ 3. Delete inserted records using error-free export CSV + sf data delete bulk
341
+ 4. Restore Validation Rules
342
+ 5. Investigate root cause before re-attempting
343
+
344
+ ## Post-Migration Verification
345
+ - [ ] Record counts match source system
346
+ - [ ] Sample 10 records manually — verify all fields correct
347
+ - [ ] Lookup relationships intact (spot-check Contacts → Accounts)
348
+ - [ ] Validation Rules re-activated
349
+ - [ ] Users notified migration complete
350
+
351
+ ## Results
352
+ | Object | Inserted | Updated | Failed | Notes |
353
+ |---|---|---|---|---|
354
+ | Account | | | | |
355
+ | Contact | | | | |
356
+ ```
357
+
358
+ ---
359
+
360
+ ## SF CLI Quick Reference — Data Commands
361
+
362
+ ```bash
363
+ # Describe object fields (use to verify API names before import)
364
+ sf sobject describe --sobject Account --target-org <alias>
365
+
366
+ # Query to verify post-migration counts
367
+ sf data query --query "SELECT COUNT() FROM Account WHERE ExternalId__c != null" --target-org <alias>
368
+
369
+ # Get Bulk API job results
370
+ sf data bulk results --job-id <JobId> --target-org <alias>
371
+
372
+ # List all Bulk API jobs (recent)
373
+ sf data bulk status --target-org <alias>
374
+ ```
@@ -0,0 +1,328 @@
1
+ ---
2
+ name: sf-dev
3
+ description: >
4
+ Use this skill for ANY Salesforce custom development task: writing or reviewing Apex classes,
5
+ triggers, batch jobs, test classes, SOQL queries, LWC components (HTML/JS/CSS), Aura components,
6
+ and integration patterns. Trigger this skill whenever the user mentions Apex, LWC, SOQL, triggers,
7
+ @AuraEnabled, @wire, REST/SOAP callouts, platform events, or asks to build/fix/refactor any
8
+ Salesforce code. Also use when the user asks about governor limits, test coverage, or debugging
9
+ Salesforce-specific runtime errors.
10
+ ---
11
+
12
+ # Salesforce Developer Skill
13
+
14
+ ## Environment Context
15
+ - API Version: **61.0**
16
+ - Tooling: **SF CLI** (`sf` commands)
17
+ - Apex LSP: **offline** — never use compile-time SObject type references that require org metadata
18
+ - Version control: **GitHub** (branching: `main → staging → develop → feature/*`)
19
+
20
+ ---
21
+
22
+ ## Apex — Core Rules
23
+
24
+ ### Governor Limits (non-negotiable)
25
+ - **No SOQL inside loops** — always bulkify; query outside the loop, store in Map/List
26
+ - **No DML inside loops** — collect records, DML once after loop
27
+ - **No callouts inside loops**
28
+ - Minimum test coverage: **85%** (aim for 90%+)
29
+
30
+ ### Security Patterns
31
+ ```apex
32
+ // ✅ CRUD check — use Schema.getGlobalDescribe() (LSP-safe)
33
+ if (!Schema.getGlobalDescribe().get('ObjectName').getDescribe().isCreateable()) {
34
+ throw new AuraHandledException('Insufficient permissions.');
35
+ }
36
+
37
+ // ❌ NEVER use compile-time reference (LSP offline can't resolve)
38
+ // if (!ContentVersion.SObjectType.getDescribe().isCreateable()) { ... }
39
+ ```
40
+
41
+ - `ContentDocumentLink`: skip explicit CRUD check — junction object protected by `with sharing` + try-catch
42
+ - FLS check via `evDescribe.fields` not available offline — use object-level `isUpdateable()` only
43
+ - All classes handling user data: use **`with sharing`**
44
+
45
+ ### @AuraEnabled Methods
46
+ ```apex
47
+ @AuraEnabled
48
+ public static Result myMethod(String param) {
49
+ try {
50
+ // business logic
51
+ } catch (Exception e) {
52
+ throw new AuraHandledException(e.getMessage());
53
+ }
54
+ }
55
+ ```
56
+ - Always wrap DML in try-catch → re-throw as `AuraHandledException`
57
+ - Private helper methods: throw `CalloutException` (caller wraps it)
58
+
59
+ ### Exception Behavior (Salesforce by design)
60
+ - `AuraHandledException.getMessage()` in test context **always** returns `'Script-thrown exception'`
61
+ - In test assertions: `System.assertEquals('Script-thrown exception', e.getMessage())` — **do not change this**
62
+
63
+ ### Naming Conventions
64
+ | Type | Convention | Example |
65
+ |---|---|---|
66
+ | Apex Class | PascalCase | `AccountHelper`, `OpportunityTrigger` |
67
+ | Test Class | PascalCase + `Test` suffix | `AccountHelperTest` |
68
+ | Custom Field | PascalCase__c | `ProjectStatus__c` |
69
+ | Variable | camelCase | `accountList`, `oppMap` |
70
+
71
+ ### ApexDoc (required on all public methods)
72
+ ```apex
73
+ /**
74
+ * @description Brief description of what this method does
75
+ * @param recordId The ID of the record to process
76
+ * @return List of processed results
77
+ * @throws AuraHandledException if user lacks required permissions
78
+ */
79
+ @AuraEnabled
80
+ public static List<Result__c> processRecord(Id recordId) { ... }
81
+ ```
82
+
83
+ ### Test Class Pattern
84
+ ```apex
85
+ @isTest
86
+ private class AccountHelperTest {
87
+ @TestSetup
88
+ static void makeData() {
89
+ // create test records once for all test methods
90
+ }
91
+
92
+ @isTest
93
+ static void testPositivePath() {
94
+ Test.startTest();
95
+ // call method
96
+ Test.stopTest();
97
+ // assert
98
+ }
99
+
100
+ @isTest
101
+ static void testNegativePath_insufficientPermission() {
102
+ // test exception path
103
+ try {
104
+ AccountHelper.myMethod(null);
105
+ System.assert(false, 'Expected exception not thrown');
106
+ } catch (AuraHandledException e) {
107
+ System.assertEquals('Script-thrown exception', e.getMessage());
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## SOQL — Best Practices
116
+
117
+ ```apex
118
+ // ✅ Bulkified pattern
119
+ Map<Id, Account> accountMap = new Map<Id, Account>(
120
+ [SELECT Id, Name, OwnerId FROM Account WHERE Id IN :accountIds]
121
+ );
122
+
123
+ // ✅ Aggregate query
124
+ List<AggregateResult> results = [
125
+ SELECT StageName, COUNT(Id) total
126
+ FROM Opportunity
127
+ WHERE CloseDate = THIS_YEAR
128
+ GROUP BY StageName
129
+ ];
130
+
131
+ // ❌ Never SOQL in loop
132
+ for (Account acc : accounts) {
133
+ List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // WRONG
134
+ }
135
+ ```
136
+
137
+ - Selective WHERE clauses: always filter on indexed fields (Id, Name, ExternalId__c, lookup fields)
138
+ - Avoid `SELECT *` — always specify fields explicitly
139
+ - Use `LIMIT` on unbounded queries
140
+
141
+ ---
142
+
143
+ ## LWC — Core Rules
144
+
145
+ ### File Structure
146
+ ```
147
+ force-app/main/default/lwc/
148
+ └── myComponent/
149
+ ├── myComponent.html
150
+ ├── myComponent.js
151
+ ├── myComponent.css
152
+ └── myComponent.js-meta.xml
153
+ ```
154
+
155
+ ### Naming
156
+ - Component folder: **camelCase** (`accountCard`, `opportunityList`)
157
+ - Never PascalCase for LWC folders
158
+
159
+ ### Brand Theme — MANDATORY `:host` block
160
+ Every LWC CSS file must include this exact token block:
161
+
162
+ ```css
163
+ :host {
164
+ /* ── Brand ── */
165
+ --brand: #f37e20;
166
+ --brand-hover: #d86a13;
167
+ --brand-light: #ff9f4d;
168
+ --brand-dim: rgba(243, 126, 32, 0.08);
169
+ --brand-border: rgba(243, 126, 32, 0.22);
170
+ --brand-shadow: rgba(243, 126, 32, 0.2);
171
+
172
+ /* ── User Accent ── */
173
+ --user-color: #0070d2;
174
+
175
+ /* ── Text ── */
176
+ --text-primary: #1d1c1d;
177
+ --text-secondary: #616061;
178
+ --text-muted: #9b9a9b;
179
+
180
+ /* ── Surface ── */
181
+ --surface: #ffffff;
182
+ --surface-dim: #f8f8f8;
183
+ --border: #e8e8e8;
184
+ --border-light: #f0f0f0;
185
+
186
+ /* ── Typography ── */
187
+ --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
188
+ --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
189
+
190
+ /* ── Radius ── */
191
+ --r-sm: 4px;
192
+ --r-md: 8px;
193
+ --r-lg: 12px;
194
+ --r-xl: 18px;
195
+ --r-pill: 999px;
196
+
197
+ /* ── Transition ── */
198
+ --t: 0.15s ease;
199
+ }
200
+ ```
201
+
202
+ **Token rules:**
203
+ - ❌ Never hardcode `#f37e20` — always `var(--brand)`
204
+ - ❌ Never use `var(--lwc-colorTextDefault)` for brand colors
205
+ - ✅ Border-radius: always `var(--r-sm/md/lg/xl/r-pill)`
206
+ - ✅ Font: `var(--font)` for UI text, `var(--mono)` for code/IDs
207
+ - ✅ Transitions: `var(--t)` for hover/active
208
+
209
+ ### Version Badge — MANDATORY on every LWC
210
+ Every LWC must display a version badge in its header/initiate screen.
211
+
212
+ **Format:** `vMAJOR.MINOR.PATCH+SEASON'YEAR` → e.g. `v2.1.0+S'26`
213
+ - SEASON codes: `S` = Summer, `Sp` = Spring, `W` = Winter
214
+ - YEAR: last 2 digits of Salesforce release year
215
+
216
+ ```html
217
+ <!-- HTML pattern -->
218
+ <span class="myComponent-version-badge">v1.0.0<span class="badge-season">+S'26</span></span>
219
+ ```
220
+
221
+ ```css
222
+ /* CSS pattern */
223
+ .myComponent-version-badge {
224
+ display: inline-block;
225
+ font-size: 9px;
226
+ font-weight: 700;
227
+ color: var(--brand);
228
+ background: var(--brand-dim);
229
+ border: 1px solid var(--brand-border);
230
+ border-radius: var(--r-pill);
231
+ padding: 1px 7px;
232
+ letter-spacing: 0.5px;
233
+ text-transform: uppercase;
234
+ }
235
+ .myComponent-version-badge .badge-season {
236
+ margin-left: 2px;
237
+ font-weight: 800;
238
+ }
239
+ ```
240
+
241
+ **Version increment rules:**
242
+ - New feature in a season → bump MINOR, reset PATCH to 0
243
+ - Bugfix same season → bump PATCH only
244
+ - Next Salesforce season → update SEASON+YEAR, evaluate MINOR bump
245
+
246
+ ### JS Best Practices
247
+ ```javascript
248
+ import { LightningElement, api, wire, track } from 'lwc';
249
+
250
+ export default class MyComponent extends LightningElement {
251
+ // @api for public properties (parent → child)
252
+ @api recordId;
253
+
254
+ // @track only needed for nested object/array mutation detection
255
+ // primitives are reactive by default
256
+
257
+ // ❌ Never use @wire adapter inside a loop
258
+ // ✅ Wire at component level, filter in getter
259
+ @wire(getRelatedItems, { recordId: '$recordId' })
260
+ wiredItems;
261
+
262
+ get processedItems() {
263
+ return this.wiredItems.data?.filter(i => i.Active__c) ?? [];
264
+ }
265
+ }
266
+ ```
267
+
268
+ - JSDoc required on all public methods and `@api` properties
269
+ - Use optional chaining (`?.`) and nullish coalescing (`??`) for safety
270
+ - Fire events upward with `CustomEvent`, never call parent methods directly
271
+
272
+ ---
273
+
274
+ ## Integration Patterns
275
+
276
+ ### REST Callout
277
+ ```apex
278
+ HttpRequest req = new HttpRequest();
279
+ req.setEndpoint('callout:My_Named_Credential/api/endpoint');
280
+ req.setMethod('GET');
281
+ req.setTimeout(10000);
282
+
283
+ HttpResponse res = new Http().send(req);
284
+ if (res.getStatusCode() != 200) {
285
+ throw new CalloutException('HTTP ' + res.getStatusCode() + ': ' + res.getBody());
286
+ }
287
+ ```
288
+ - Always use Named Credentials — never hardcode URLs or tokens
289
+ - Set timeout explicitly
290
+ - Throw `CalloutException` in helper; let `@AuraEnabled` caller wrap as `AuraHandledException`
291
+
292
+ ---
293
+
294
+ ## SF CLI — Common Dev Commands
295
+
296
+ ```bash
297
+ # Pull metadata from org
298
+ sf project retrieve start --metadata ApexClass:MyClass --target-org <alias>
299
+
300
+ # Push local source to scratch org
301
+ sf project deploy start --target-org <alias>
302
+
303
+ # Run specific test
304
+ sf apex run test --class-names MyClassTest --target-org <alias> --result-format human
305
+
306
+ # Run all tests
307
+ sf apex run test --test-level RunAllTestsInOrg --target-org <alias>
308
+
309
+ # Open org in browser
310
+ sf org open --target-org <alias>
311
+
312
+ # Execute anonymous Apex
313
+ sf apex run --target-org <alias> --file scripts/apex/myScript.apex
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Code Review Checklist (before every PR)
319
+ - [ ] No SOQL/DML inside loops
320
+ - [ ] Test coverage ≥ 85%
321
+ - [ ] No `@wire` in loops (LWC)
322
+ - [ ] All public methods have ApexDoc/JSDoc
323
+ - [ ] No hardcoded IDs, credentials, or org-specific values
324
+ - [ ] `with sharing` declared on all Apex classes
325
+ - [ ] CRUD check present where applicable (using `Schema.getGlobalDescribe()`)
326
+ - [ ] All `@AuraEnabled` methods wrapped in try-catch → `AuraHandledException`
327
+ - [ ] LWC has `:host` brand token block
328
+ - [ ] LWC has version badge