sf-forcekit 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.
Files changed (32) hide show
  1. package/License +21 -0
  2. package/README.md +106 -0
  3. package/bin/cli.js +85 -0
  4. package/package.json +44 -0
  5. package/templates/ai-dir/README.md +155 -0
  6. package/templates/ai-dir/agentforce.md +213 -0
  7. package/templates/ai-dir/architecture.md +123 -0
  8. package/templates/ai-dir/commands.md +276 -0
  9. package/templates/ai-dir/context-snapshots/TEMPLATE.md +64 -0
  10. package/templates/ai-dir/conventions.md +242 -0
  11. package/templates/ai-dir/current-state.md +113 -0
  12. package/templates/ai-dir/debugging-notes.md +165 -0
  13. package/templates/ai-dir/deployment.md +161 -0
  14. package/templates/ai-dir/integrations.md +199 -0
  15. package/templates/ai-dir/inventory.md +209 -0
  16. package/templates/ai-dir/known-issues.md +124 -0
  17. package/templates/ai-dir/org-context.md +110 -0
  18. package/templates/ai-dir/performance.md +312 -0
  19. package/templates/ai-dir/prompts/agentforce.md +163 -0
  20. package/templates/ai-dir/prompts/apex.md +165 -0
  21. package/templates/ai-dir/prompts/flows.md +125 -0
  22. package/templates/ai-dir/prompts/lwc.md +230 -0
  23. package/templates/ai-dir/prompts/security.md +181 -0
  24. package/templates/ai-dir/prompts/testing.md +269 -0
  25. package/templates/ai-dir/rules.md +238 -0
  26. package/templates/ai-dir/scripts/update_state.py +1406 -0
  27. package/templates/ai-dir/source-of-truth.md +180 -0
  28. package/templates/ai-dir/templates/Selector.cls +113 -0
  29. package/templates/ai-dir/templates/Service.cls +132 -0
  30. package/templates/ai-dir/templates/TestClass.cls +143 -0
  31. package/templates/ai-dir/templates/TriggerHandler.cls +67 -0
  32. package/templates/ai-dir/testing-strategy.md +342 -0
@@ -0,0 +1,269 @@
1
+ # Test Class Generation Rules
2
+
3
+ > Instructions for AI agents when generating Apex test classes.
4
+
5
+ ---
6
+
7
+ ## Absolute Rules
8
+
9
+ | Rule | Enforcement |
10
+ |------|------------|
11
+ | `SeeAllData=true` | **BANNED** — never use, no exceptions |
12
+ | Minimum coverage | **75%** (target **90%+**) |
13
+ | Test data | Always use `@TestSetup` or `TestDataFactory` |
14
+ | Assertions | Every test must have `System.assert*` with meaningful message |
15
+ | Bulk testing | At least one test with **200+ records** |
16
+ | Negative paths | At least one test for error/exception scenarios |
17
+
18
+ ---
19
+
20
+ ## Test Class Structure
21
+
22
+ ```apex
23
+ @IsTest
24
+ private class {ClassName}Test {
25
+
26
+ // ─── Shared Test Data ───────────────────────────────────────
27
+ @TestSetup
28
+ static void setupTestData() {
29
+ // Create all shared test data here
30
+ // This runs once, data is rolled back per test method
31
+ List<Account> accounts = TestDataFactory.createAccounts(200);
32
+ insert accounts;
33
+
34
+ List<Contact> contacts = TestDataFactory.createContacts(accounts, 2); // 2 per account
35
+ insert contacts;
36
+ }
37
+
38
+ // ─── Positive Tests ─────────────────────────────────────────
39
+
40
+ @IsTest
41
+ static void testMethodName_PositiveScenario() {
42
+ // Arrange
43
+ List<Account> accounts = [SELECT Id, Name FROM Account];
44
+
45
+ // Act
46
+ Test.startTest();
47
+ List<Account> result = AccountService.processAccounts(accounts);
48
+ Test.stopTest();
49
+
50
+ // Assert
51
+ System.assertEquals(200, result.size(), 'Should process all 200 accounts');
52
+ System.assertNotEquals(null, result[0].Name, 'Account name should not be null');
53
+ }
54
+
55
+ // ─── Bulk Tests ─────────────────────────────────────────────
56
+
57
+ @IsTest
58
+ static void testMethodName_Bulk200Records() {
59
+ // Arrange
60
+ List<Account> accounts = [SELECT Id FROM Account];
61
+ System.assertEquals(200, accounts.size(), 'Test setup should create 200 accounts');
62
+
63
+ // Act
64
+ Test.startTest();
65
+ AccountService.bulkOperation(accounts);
66
+ Test.stopTest();
67
+
68
+ // Assert — verify no governor limit exceptions occurred
69
+ List<Account> updated = [SELECT Id, Status__c FROM Account WHERE Id IN :accounts];
70
+ for (Account a : updated) {
71
+ System.assertEquals('Processed', a.Status__c, 'All accounts should be processed');
72
+ }
73
+ }
74
+
75
+ // ─── Negative Tests ─────────────────────────────────────────
76
+
77
+ @IsTest
78
+ static void testMethodName_NullInput() {
79
+ Boolean exceptionThrown = false;
80
+
81
+ Test.startTest();
82
+ try {
83
+ AccountService.processAccounts(null);
84
+ } catch (AccountService.AccountProcessingException e) {
85
+ exceptionThrown = true;
86
+ System.assert(e.getMessage().contains('cannot be null'),
87
+ 'Exception message should indicate null input');
88
+ }
89
+ Test.stopTest();
90
+
91
+ System.assert(exceptionThrown, 'Should throw exception for null input');
92
+ }
93
+
94
+ @IsTest
95
+ static void testMethodName_InvalidData() {
96
+ // Arrange
97
+ Account invalidAccount = new Account(); // Missing required fields
98
+
99
+ // Act & Assert
100
+ Test.startTest();
101
+ try {
102
+ insert invalidAccount;
103
+ System.assert(false, 'Should have thrown DmlException');
104
+ } catch (DmlException e) {
105
+ System.assert(e.getMessage().contains('REQUIRED_FIELD_MISSING'),
106
+ 'Should fail on required field validation');
107
+ }
108
+ Test.stopTest();
109
+ }
110
+
111
+ // ─── Permission Tests ───────────────────────────────────────
112
+
113
+ @IsTest
114
+ static void testMethodName_RestrictedUser() {
115
+ // Create a user with minimal permissions
116
+ User restrictedUser = TestDataFactory.createStandardUser();
117
+ insert restrictedUser;
118
+
119
+ System.runAs(restrictedUser) {
120
+ Test.startTest();
121
+ try {
122
+ AccountService.processAccounts([SELECT Id FROM Account]);
123
+ // If security is properly enforced, this should throw
124
+ } catch (System.NoAccessException e) {
125
+ System.assert(true, 'Restricted user should not have access');
126
+ }
127
+ Test.stopTest();
128
+ }
129
+ }
130
+
131
+ // ─── Callout Mock Tests ─────────────────────────────────────
132
+
133
+ @IsTest
134
+ static void testCallout_Success() {
135
+ Test.setMock(HttpCalloutMock.class, new ExternalApiMockSuccess());
136
+
137
+ Test.startTest();
138
+ ApiResponseDTO response = ExternalApiService.callExternalApi('/endpoint', 'GET', null);
139
+ Test.stopTest();
140
+
141
+ System.assertNotEquals(null, response, 'Response should not be null');
142
+ System.assertEquals('success', response.status, 'API call should succeed');
143
+ }
144
+
145
+ @IsTest
146
+ static void testCallout_Failure() {
147
+ Test.setMock(HttpCalloutMock.class, new ExternalApiMockFailure());
148
+
149
+ Boolean exceptionThrown = false;
150
+ Test.startTest();
151
+ try {
152
+ ExternalApiService.callExternalApi('/endpoint', 'GET', null);
153
+ } catch (IntegrationException e) {
154
+ exceptionThrown = true;
155
+ }
156
+ Test.stopTest();
157
+
158
+ System.assert(exceptionThrown, 'Should throw IntegrationException on API failure');
159
+ }
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## TestDataFactory Pattern
166
+
167
+ ```apex
168
+ @IsTest
169
+ public class TestDataFactory {
170
+
171
+ public static List<Account> createAccounts(Integer count) {
172
+ List<Account> accounts = new List<Account>();
173
+ for (Integer i = 0; i < count; i++) {
174
+ accounts.add(new Account(
175
+ Name = 'Test Account ' + i,
176
+ Industry = 'Technology'
177
+ ));
178
+ }
179
+ return accounts; // Don't insert — let caller decide
180
+ }
181
+
182
+ public static List<Contact> createContacts(List<Account> accounts, Integer perAccount) {
183
+ List<Contact> contacts = new List<Contact>();
184
+ for (Account a : accounts) {
185
+ for (Integer i = 0; i < perAccount; i++) {
186
+ contacts.add(new Contact(
187
+ FirstName = 'Test',
188
+ LastName = 'Contact ' + i,
189
+ AccountId = a.Id,
190
+ Email = 'test' + i + '@' + a.Id + '.test'
191
+ ));
192
+ }
193
+ }
194
+ return contacts;
195
+ }
196
+
197
+ public static User createStandardUser() {
198
+ Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
199
+ return new User(
200
+ FirstName = 'Test',
201
+ LastName = 'User',
202
+ Email = 'testuser@test.salesforce.com',
203
+ Username = 'testuser' + DateTime.now().getTime() + '@test.salesforce.com',
204
+ Alias = 'tuser',
205
+ TimeZoneSidKey = 'America/Los_Angeles',
206
+ LocaleSidKey = 'en_US',
207
+ EmailEncodingKey = 'UTF-8',
208
+ LanguageLocaleKey = 'en_US',
209
+ ProfileId = p.Id
210
+ );
211
+ }
212
+ }
213
+ ```
214
+
215
+ ---
216
+
217
+ ## HttpCalloutMock Pattern
218
+
219
+ ```apex
220
+ @IsTest
221
+ public class ExternalApiMockSuccess implements HttpCalloutMock {
222
+ public HTTPResponse respond(HTTPRequest req) {
223
+ HttpResponse res = new HttpResponse();
224
+ res.setStatusCode(200);
225
+ res.setHeader('Content-Type', 'application/json');
226
+ res.setBody('{"status":"success","data":[]}');
227
+ return res;
228
+ }
229
+ }
230
+
231
+ @IsTest
232
+ public class ExternalApiMockFailure implements HttpCalloutMock {
233
+ public HTTPResponse respond(HTTPRequest req) {
234
+ HttpResponse res = new HttpResponse();
235
+ res.setStatusCode(500);
236
+ res.setBody('{"error":"Internal Server Error"}');
237
+ return res;
238
+ }
239
+ }
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Assertion Best Practices
245
+
246
+ ```apex
247
+ // ✅ GOOD — Meaningful messages
248
+ System.assertEquals(200, accounts.size(), 'Should return 200 accounts for bulk test');
249
+ System.assertNotEquals(null, account.Name, 'Account name should be populated after processing');
250
+ System.assert(result.isSuccess(), 'DML should succeed for valid records: ' + result.getErrors());
251
+
252
+ // ❌ BAD — No context
253
+ System.assertEquals(200, accounts.size());
254
+ System.assert(result.isSuccess());
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Checklist Before Returning Test Code
260
+
261
+ - [ ] `@IsTest` annotation on class (private class)
262
+ - [ ] `@TestSetup` for shared data
263
+ - [ ] NO `SeeAllData=true` anywhere
264
+ - [ ] `Test.startTest()` / `Test.stopTest()` wrapping the operation under test
265
+ - [ ] At least one bulk test (200+ records)
266
+ - [ ] At least one negative/error path test
267
+ - [ ] All assertions have descriptive messages
268
+ - [ ] HttpCalloutMock used for callout tests
269
+ - [ ] Follows naming convention: `{ClassName}Test`
@@ -0,0 +1,238 @@
1
+ # Agent Rules — READ THIS FIRST
2
+
3
+ > This is the master instruction file for any AI agent working in this Salesforce project.
4
+ > Read this file BEFORE doing anything else. Follow every rule without exception.
5
+
6
+ ---
7
+
8
+ ## 1. Read Order
9
+
10
+ When starting a new session, read files by tier. Read T0 always. Read T1–T3 as needed.
11
+
12
+ ### T0 — Always (Every Session)
13
+
14
+ 1. **This file** (`rules.md`) — Agent behavior rules
15
+ 2. **`org-context.local.md`** (or **`org-context.md`** if local does not exist) — Active Org details, CLI config, limits
16
+ 3. **`inventory.md`** — What actually exists in this project (objects, classes, components)
17
+ 4. **`current-state.md`** — What's happening NOW (session log, active work, blockers, next steps)
18
+
19
+ ### T1 — Before Writing Code
20
+
21
+ 5. **`conventions.md`** — Coding standards (Apex, LWC, Flow)
22
+ 6. **`source-of-truth.md`** — How to verify before you generate
23
+ 7. **`prompts/{relevant}.md`** — Generation rules for the specific task
24
+
25
+ ### T2 — When Task Involves Design, Tests, or Performance
26
+
27
+ 8. **`architecture.md`** — Data model, layers, integrations
28
+ 9. **`testing-strategy.md`** — Test philosophy, patterns, coverage rules
29
+ 10. **`performance.md`** — Governor limits, SOQL optimization, async patterns
30
+
31
+ ### T3 — Reference (Read When Specifically Needed)
32
+
33
+ 11. **`commands.md`** — sf CLI cookbook
34
+ 12. **`deployment.md`** — Deploy/retrieve workflows, CI/CD
35
+ 13. **`integrations.md`** — External systems, callout patterns
36
+ 14. **`debugging-notes.md`** — Debug techniques, log levels
37
+ 15. **`known-issues.md`** — Platform bugs, workarounds
38
+
39
+ ---
40
+
41
+ ## 2. Auto-Update Protocol (MANDATORY)
42
+
43
+ You MUST keep `current-state.md` and `inventory.md` updated as you work. These files are the project's living memory — they ensure continuity across sessions and agents.
44
+
45
+ ### When to Update `current-state.md`
46
+
47
+ | Trigger | What to Update |
48
+ |---------|---------------|
49
+ | **Session start** | Add entry to `## Session Log` with date, agent, goal |
50
+ | **Starting a task** | Move item to `In Progress` in `## Active Work`, mark `[/]` |
51
+ | **Completing a task** | Move item to `Recently Completed`, mark `[x]`, note files changed |
52
+ | **Hitting a blocker** | Add to `## Blockers` with impact and context |
53
+ | **Resolving a blocker** | Remove from `## Blockers`, note resolution in session log |
54
+ | **Making a design decision** | Add to `## Decisions` with rationale |
55
+ | **Discovering an issue** | Add to `## Blockers` or update `known-issues.md` |
56
+ | **Changing files** | Append to `## Files Changed This Session` |
57
+ | **Session end** | Write summary in session log, update status |
58
+
59
+ ### When to Update `inventory.md`
60
+
61
+ | Trigger | What to Update |
62
+ |---------|---------------|
63
+ | **First session on project** | Run populate commands, fill all sections |
64
+ | **Creating a new class** | Add to the appropriate layer section (Service, Selector, etc.) |
65
+ | **Creating a new LWC** | Add to LWC Components section |
66
+ | **Creating a new Flow** | Add to Flows section |
67
+ | **Creating a new object/field** | Add to Custom Objects section |
68
+ | **Deleting anything** | Remove from inventory |
69
+
70
+ ### How to Update
71
+
72
+ - **Method A (Recommended):** Use the helper script `.ai/scripts/update_state.py`. This ensures perfect formatting and saves time:
73
+ - `python3 .ai/scripts/update_state.py scan` (scans project and updates `inventory.md` automatically)
74
+ - `python3 .ai/scripts/update_state.py session-start --agent "AgentName" --goal "Goal description"`
75
+ - `python3 .ai/scripts/update_state.py task-start "Task description"`
76
+ - `python3 .ai/scripts/update_state.py task-complete "Task description" --files "comma,separated,files"`
77
+ - `python3 .ai/scripts/update_state.py session-end --summary "Summary of work" --files "comma,separated,files"`
78
+ - **Method B (Manual):** If the script is unavailable or you have no command execution capabilities, edit the files manually using file replacement tools. Follow the format guidelines:
79
+ - Keep entries concise — one line per item.
80
+ - Use ISO dates: `2026-05-22`
81
+ - Use status emojis: 🟢 On Track, 🟡 At Risk, 🔴 Blocked, ✅ Done
82
+ - Prepend new entries (newest first) in the session log.
83
+
84
+ ### Example Session Log Entry
85
+
86
+ ```markdown
87
+ ### 2026-05-22 | Claude | Implement Account dedup service
88
+ - ✅ Created `AccountSelector.findDuplicates()` with fuzzy matching
89
+ - ✅ Created `AccountDedupService` with merge logic
90
+ - ✅ Test class with 200+ records, 94% coverage
91
+ - 🟡 Open question: Should we auto-merge or flag for review?
92
+ - **Files:** AccountSelector.cls, AccountDedupService.cls, AccountDedupServiceTest.cls
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 3. Anti-Hallucination Rules (NON-NEGOTIABLE)
98
+
99
+ > **NEVER GUESS. ALWAYS VERIFY.**
100
+ > Hallucinated field names, fake API methods, and invented CLI flags are the #1 cause of broken Salesforce code from AI agents.
101
+
102
+ ### The Verification Mandate
103
+
104
+ Before writing code that references ANY of the following, you MUST verify it exists:
105
+
106
+ | What | How to Verify |
107
+ |------|--------------|
108
+ | **Custom field** (`*__c`) | Check `inventory.md` → or run `FieldDefinition` SOQL (see `source-of-truth.md`) |
109
+ | **Custom object** (`*__c`) | Check `inventory.md` → or run `EntityDefinition` SOQL |
110
+ | **Apex class** | Check `inventory.md` → or `ls force-app/main/default/classes/` |
111
+ | **LWC component** | Check `inventory.md` → or `ls force-app/main/default/lwc/` |
112
+ | **Named Credential** | Check `inventory.md` → or query `NamedCredential` via Tooling API |
113
+ | **Custom Label** | Check `inventory.md` → or query `ExternalString` via Tooling API |
114
+ | **Custom Metadata** | Check `inventory.md` → or query the CMDT object directly |
115
+ | **Standard Apex method** | Check `source-of-truth.md` standard library table |
116
+ | **CLI flag** | Run the command with `--help` first |
117
+ | **Governor limit number** | Check `org-context.local.md` (or `org-context.md`) — don't guess the numbers |
118
+
119
+ ### Verification Workflow
120
+
121
+ ```
122
+ About to reference something?
123
+
124
+ ├── Is it in inventory.md?
125
+ │ ├── YES → Safe to use ✅
126
+ │ └── NO ──→ Can you verify it?
127
+ │ ├── YES (run CLI/SOQL/ls) → Verify, then use ✅
128
+ │ │ Update inventory.md
129
+ │ └── NO (no org access) → Flag it ⚠️
130
+ │ Tell the user:
131
+ │ "⚠️ Assumed: X exists — please verify"
132
+
133
+ └── Is it a standard Apex class/method?
134
+ ├── In source-of-truth.md table? → Safe to use ✅
135
+ └── Not in table? → Don't invent it ❌
136
+ Search documentation or ask the user
137
+ ```
138
+
139
+ ### Confidence Signals (REQUIRED)
140
+
141
+ When referencing Salesforce metadata in your response, prefix with:
142
+
143
+ | Signal | Meaning | When to Use |
144
+ |--------|---------|-------------|
145
+ | ✅ **Verified** | Confirmed it exists via query, file check, or inventory | You checked and it's real |
146
+ | ⚠️ **Assumed** | Reasonable assumption, not verified | You think it exists but didn't check |
147
+ | ❓ **Uncertain** | You don't know — ask the developer | Don't fake it |
148
+
149
+ **Example in code comments:**
150
+ ```apex
151
+ // ✅ Verified: Account.Industry (standard field)
152
+ // ⚠️ Assumed: Account.Health_Score__c — listed in architecture.md but not verified in org
153
+ // ❓ Uncertain: Account.Risk_Rating__c — need to confirm this field exists
154
+ ```
155
+
156
+ ### Absolute Don'ts
157
+
158
+ - ❌ **NEVER invent a field name** — If you're not sure `MyObject__c.MyField__c` exists, say so
159
+ - ❌ **NEVER fabricate an Apex method** — If `Database.upsertImmediate()` isn't in the standard library table, it doesn't exist
160
+ - ❌ **NEVER guess a CLI flag** — Run `--help` first
161
+ - ❌ **NEVER assume org features** — Check `org-context.local.md` (or `org-context.md` if local does not exist) for enabled features
162
+ - ❌ **NEVER make up governor limit numbers** — Reference `org-context.local.md` (or `org-context.md` if local does not exist)
163
+ - ❌ **NEVER invent trigger events** — `before undelete` does not exist
164
+
165
+ ### When in Doubt
166
+
167
+ Say this instead of guessing:
168
+
169
+ > "I'm referencing `Account.Health_Score__c` based on the architecture doc, but I haven't verified this field exists in the org. Please confirm before deploying, or I can run a Tooling API query to check."
170
+
171
+ ---
172
+
173
+ ## 4. Next-Session Handoff Protocol
174
+
175
+ At end of every session, update `current-state.md` → `## Next Session` section with:
176
+
177
+ 1. **What to do first** — The single most important task for the next agent
178
+ 2. **Context needed** — Files, decisions, or blockers the next agent must know
179
+ 3. **What NOT to do** — Anything that's blocked, waiting on human input, or risky
180
+
181
+ This replaces the need for a separate `next-session.md` file.
182
+
183
+ ## 5. Context Snapshot Protocol
184
+
185
+ At the end of long sessions or before a major context switch:
186
+
187
+ 1. Copy `context-snapshots/TEMPLATE.md` → `context-snapshots/YYYY-MM-DD.md`
188
+ 2. Fill in what was done, what's next, and key decisions
189
+ 3. This preserves deep context that `current-state.md` summarizes
190
+
191
+ ---
192
+
193
+ ## 6. Code Generation Rules
194
+
195
+ Before generating ANY Salesforce code:
196
+
197
+ 1. **Check `inventory.md`** — Does the class/object/field you're referencing actually exist?
198
+ 2. **Check `source-of-truth.md`** — Are the Apex methods you're using real?
199
+ 3. Read the relevant `prompts/*.md` file for the code type
200
+ 4. Follow `conventions.md` — especially:
201
+ - `with sharing` on every class
202
+ - `WITH USER_MODE` on every SOQL query
203
+ - No SOQL/DML in loops
204
+ - One trigger per object → Handler → Service → Selector
205
+ 5. After generating code:
206
+ - Update `current-state.md` with files changed
207
+ - Update `inventory.md` with new classes/components created
208
+
209
+ ---
210
+
211
+ ## 7. Performance Rules
212
+
213
+ Before writing code that processes data:
214
+
215
+ 1. **Check `performance.md`** — Know governor limits for the operation type
216
+ 2. **Choose the right async pattern** — See the selection table in `performance.md`
217
+ 3. **For 50K+ records** — Use Batch Apex, not collections in memory
218
+ 4. **For CPU-heavy logic** — Use Map lookups, not nested loops
219
+ 5. **Monitor in code** — Add `Limits.getQueries()` / `Limits.getCpuTime()` checks for critical methods
220
+
221
+ ---
222
+
223
+ ## 8. Pre-Deploy Self-Check
224
+
225
+ Before telling the user code is ready to deploy:
226
+
227
+ - [ ] All referenced fields/objects verified (via `inventory.md` or Tooling API)
228
+ - [ ] All Apex methods used are from the standard library (no invented methods)
229
+ - [ ] All classes use `with sharing`
230
+ - [ ] All SOQL uses `WITH USER_MODE`
231
+ - [ ] No SOQL or DML inside loops
232
+ - [ ] Handles 200+ records (bulkified)
233
+ - [ ] Error handling with meaningful messages (no empty catches)
234
+ - [ ] No hardcoded IDs or credentials
235
+ - [ ] Test coverage target ≥ 90% with meaningful assertions
236
+ - [ ] `current-state.md` is updated with what was done
237
+ - [ ] `inventory.md` is updated with any new metadata created
238
+ - [ ] All assumptions flagged with ⚠️ for human review