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.
Files changed (271) hide show
  1. package/.claude-plugin/plugin.json +44 -0
  2. package/.cursor/agents/deep-researcher.md +142 -0
  3. package/.cursor/agents/doc-updater.md +219 -0
  4. package/.cursor/agents/eval-runner.md +335 -0
  5. package/.cursor/agents/learning-engine.md +210 -0
  6. package/.cursor/agents/loop-operator.md +245 -0
  7. package/.cursor/agents/refactor-cleaner.md +119 -0
  8. package/.cursor/agents/sf-admin-agent.md +127 -0
  9. package/.cursor/agents/sf-agentforce-agent.md +126 -0
  10. package/.cursor/agents/sf-apex-agent.md +117 -0
  11. package/.cursor/agents/sf-architect.md +426 -0
  12. package/.cursor/agents/sf-aura-reviewer.md +369 -0
  13. package/.cursor/agents/sf-bugfix-agent.md +101 -0
  14. package/.cursor/agents/sf-flow-agent.md +155 -0
  15. package/.cursor/agents/sf-integration-agent.md +141 -0
  16. package/.cursor/agents/sf-lwc-agent.md +123 -0
  17. package/.cursor/agents/sf-review-agent.md +357 -0
  18. package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
  19. package/.cursor/hooks/adapter.js +81 -0
  20. package/.cursor/hooks/after-file-edit.js +26 -0
  21. package/.cursor/hooks/after-mcp-execution.js +12 -0
  22. package/.cursor/hooks/after-shell-execution.js +30 -0
  23. package/.cursor/hooks/after-tab-file-edit.js +12 -0
  24. package/.cursor/hooks/before-mcp-execution.js +11 -0
  25. package/.cursor/hooks/before-read-file.js +13 -0
  26. package/.cursor/hooks/before-shell-execution.js +29 -0
  27. package/.cursor/hooks/before-submit-prompt.js +23 -0
  28. package/.cursor/hooks/pre-compact.js +7 -0
  29. package/.cursor/hooks/session-end.js +10 -0
  30. package/.cursor/hooks/session-start.js +10 -0
  31. package/.cursor/hooks/stop.js +18 -0
  32. package/.cursor/hooks/subagent-start.js +10 -0
  33. package/.cursor/hooks/subagent-stop.js +10 -0
  34. package/.cursor/hooks.json +107 -0
  35. package/.cursor/skills/aside/SKILL.md +115 -0
  36. package/.cursor/skills/checkpoint/SKILL.md +50 -0
  37. package/.cursor/skills/configure-scc/SKILL.md +160 -0
  38. package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
  39. package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
  40. package/.cursor/skills/model-route/SKILL.md +81 -0
  41. package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
  42. package/.cursor/skills/refactor-clean/SKILL.md +133 -0
  43. package/.cursor/skills/resume-session/SKILL.md +111 -0
  44. package/.cursor/skills/save-session/SKILL.md +183 -0
  45. package/.cursor/skills/search-first/SKILL.md +140 -0
  46. package/.cursor/skills/security-scan/SKILL.md +142 -0
  47. package/.cursor/skills/sessions/SKILL.md +124 -0
  48. package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
  49. package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
  50. package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
  51. package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
  52. package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
  53. package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
  54. package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
  55. package/.cursor/skills/sf-api-design/SKILL.md +237 -0
  56. package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
  57. package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
  58. package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
  59. package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
  60. package/.cursor/skills/sf-debugging/SKILL.md +362 -0
  61. package/.cursor/skills/sf-deployment/SKILL.md +291 -0
  62. package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
  63. package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
  64. package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
  65. package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
  66. package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
  67. package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
  68. package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
  69. package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
  70. package/.cursor/skills/sf-help/SKILL.md +156 -0
  71. package/.cursor/skills/sf-integration/SKILL.md +479 -0
  72. package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
  73. package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
  74. package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
  75. package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
  76. package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
  77. package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
  78. package/.cursor/skills/sf-security/SKILL.md +330 -0
  79. package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
  80. package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
  81. package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
  82. package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
  83. package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
  84. package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
  85. package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
  86. package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
  87. package/.cursor/skills/strategic-compact/SKILL.md +205 -0
  88. package/.cursor/skills/update-docs/SKILL.md +162 -0
  89. package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
  90. package/.cursor-plugin/plugin.json +26 -0
  91. package/LICENSE +21 -0
  92. package/README.md +522 -0
  93. package/agents/deep-researcher.md +145 -0
  94. package/agents/doc-updater.md +222 -0
  95. package/agents/eval-runner.md +340 -0
  96. package/agents/learning-engine.md +211 -0
  97. package/agents/loop-operator.md +247 -0
  98. package/agents/refactor-cleaner.md +122 -0
  99. package/agents/sf-admin-agent.md +131 -0
  100. package/agents/sf-agentforce-agent.md +132 -0
  101. package/agents/sf-apex-agent.md +124 -0
  102. package/agents/sf-architect.md +435 -0
  103. package/agents/sf-aura-reviewer.md +372 -0
  104. package/agents/sf-bugfix-agent.md +105 -0
  105. package/agents/sf-flow-agent.md +159 -0
  106. package/agents/sf-integration-agent.md +146 -0
  107. package/agents/sf-lwc-agent.md +127 -0
  108. package/agents/sf-review-agent.md +366 -0
  109. package/agents/sf-visualforce-reviewer.md +468 -0
  110. package/assets/logo.svg +18 -0
  111. package/docs/ARCHITECTURE.md +133 -0
  112. package/docs/authoring-guide.md +373 -0
  113. package/docs/hook-development.md +578 -0
  114. package/docs/token-optimization.md +139 -0
  115. package/docs/workflow-examples.md +645 -0
  116. package/examples/agentforce-action/README.md +227 -0
  117. package/examples/apex-trigger-handler/README.md +114 -0
  118. package/examples/devops-pipeline/README.md +325 -0
  119. package/examples/flow-automation/README.md +188 -0
  120. package/examples/integration-pattern/README.md +416 -0
  121. package/examples/lwc-component/README.md +180 -0
  122. package/examples/platform-events/README.md +492 -0
  123. package/examples/scratch-org-setup/README.md +138 -0
  124. package/examples/security-audit/README.md +244 -0
  125. package/examples/visualforce-migration/README.md +314 -0
  126. package/hooks/hooks.json +338 -0
  127. package/hooks/memory-persistence/README.md +73 -0
  128. package/manifests/install-modules.json +217 -0
  129. package/manifests/install-profiles.json +17 -0
  130. package/mcp-configs/mcp-servers.json +19 -0
  131. package/package.json +89 -0
  132. package/schemas/hooks.schema.json +123 -0
  133. package/schemas/install-modules.schema.json +76 -0
  134. package/schemas/install-profiles.schema.json +28 -0
  135. package/schemas/install-state.schema.json +73 -0
  136. package/schemas/package-manager.schema.json +18 -0
  137. package/schemas/plugin.schema.json +112 -0
  138. package/schemas/scc-install-config.schema.json +29 -0
  139. package/schemas/state-store.schema.json +111 -0
  140. package/scripts/cli/install-apply.js +170 -0
  141. package/scripts/cli/uninstall.js +193 -0
  142. package/scripts/hooks/check-console-log.js +101 -0
  143. package/scripts/hooks/check-hook-enabled.js +17 -0
  144. package/scripts/hooks/check-platform-docs-age.js +48 -0
  145. package/scripts/hooks/cost-tracker.js +78 -0
  146. package/scripts/hooks/doc-file-warning.js +63 -0
  147. package/scripts/hooks/evaluate-session.js +98 -0
  148. package/scripts/hooks/governor-check.js +220 -0
  149. package/scripts/hooks/learning-observe.sh +206 -0
  150. package/scripts/hooks/mcp-health-check.js +588 -0
  151. package/scripts/hooks/post-bash-build-complete.js +34 -0
  152. package/scripts/hooks/post-bash-pr-created.js +43 -0
  153. package/scripts/hooks/post-edit-console-warn.js +61 -0
  154. package/scripts/hooks/post-edit-format.js +79 -0
  155. package/scripts/hooks/post-edit-typecheck.js +98 -0
  156. package/scripts/hooks/post-write.js +168 -0
  157. package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
  158. package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
  159. package/scripts/hooks/pre-compact.js +51 -0
  160. package/scripts/hooks/pre-tool-use.js +163 -0
  161. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  162. package/scripts/hooks/quality-gate.js +251 -0
  163. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  164. package/scripts/hooks/run-with-flags.js +135 -0
  165. package/scripts/hooks/session-end-marker.js +29 -0
  166. package/scripts/hooks/session-end.js +311 -0
  167. package/scripts/hooks/session-start.js +202 -0
  168. package/scripts/hooks/sfdx-scanner-check.js +142 -0
  169. package/scripts/hooks/sfdx-validate.js +119 -0
  170. package/scripts/hooks/stop-hook.js +170 -0
  171. package/scripts/hooks/suggest-compact.js +67 -0
  172. package/scripts/lib/agent-adapter.js +82 -0
  173. package/scripts/lib/apex-analysis.js +194 -0
  174. package/scripts/lib/hook-flags.js +74 -0
  175. package/scripts/lib/install-config.js +73 -0
  176. package/scripts/lib/install-executor.js +363 -0
  177. package/scripts/lib/install-state.js +121 -0
  178. package/scripts/lib/orchestration-session.js +299 -0
  179. package/scripts/lib/package-manager.js +124 -0
  180. package/scripts/lib/project-detect.js +228 -0
  181. package/scripts/lib/schema-validator.js +190 -0
  182. package/scripts/lib/skill-adapter.js +100 -0
  183. package/scripts/lib/state-store.js +376 -0
  184. package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
  185. package/scripts/lib/utils.js +313 -0
  186. package/scripts/scc.js +164 -0
  187. package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
  188. package/skills/_reference/APEX_CURSOR.md +159 -0
  189. package/skills/_reference/API_VERSIONS.md +78 -0
  190. package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
  191. package/skills/_reference/ASYNC_PATTERNS.md +163 -0
  192. package/skills/_reference/AURA_COMPONENTS.md +146 -0
  193. package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
  194. package/skills/_reference/DATA_MODELING.md +124 -0
  195. package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
  196. package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
  197. package/skills/_reference/DEPRECATIONS.md +79 -0
  198. package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
  199. package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
  200. package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
  201. package/skills/_reference/FLOW_PATTERNS.md +113 -0
  202. package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
  203. package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
  204. package/skills/_reference/LWC_PATTERNS.md +79 -0
  205. package/skills/_reference/METADATA_TYPES.md +115 -0
  206. package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
  207. package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
  208. package/skills/_reference/PLATFORM_EVENTS.md +121 -0
  209. package/skills/_reference/REPORTING_API.md +143 -0
  210. package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
  211. package/skills/_reference/SECURITY_PATTERNS.md +127 -0
  212. package/skills/_reference/SHARING_MODEL.md +120 -0
  213. package/skills/_reference/SOQL_PATTERNS.md +119 -0
  214. package/skills/_reference/TESTING_STANDARDS.md +96 -0
  215. package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
  216. package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
  217. package/skills/aside/SKILL.md +118 -0
  218. package/skills/checkpoint/SKILL.md +53 -0
  219. package/skills/configure-scc/SKILL.md +163 -0
  220. package/skills/continuous-agent-loop/SKILL.md +264 -0
  221. package/skills/mcp-server-patterns/SKILL.md +146 -0
  222. package/skills/model-route/SKILL.md +84 -0
  223. package/skills/prompt-optimizer/SKILL.md +369 -0
  224. package/skills/refactor-clean/SKILL.md +136 -0
  225. package/skills/resume-session/SKILL.md +114 -0
  226. package/skills/save-session/SKILL.md +186 -0
  227. package/skills/search-first/SKILL.md +144 -0
  228. package/skills/security-scan/SKILL.md +146 -0
  229. package/skills/sessions/SKILL.md +127 -0
  230. package/skills/sf-agentforce-development/SKILL.md +450 -0
  231. package/skills/sf-apex-async-patterns/SKILL.md +326 -0
  232. package/skills/sf-apex-best-practices/SKILL.md +425 -0
  233. package/skills/sf-apex-constraints/SKILL.md +81 -0
  234. package/skills/sf-apex-cursor/SKILL.md +338 -0
  235. package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
  236. package/skills/sf-apex-testing/SKILL.md +409 -0
  237. package/skills/sf-api-design/SKILL.md +238 -0
  238. package/skills/sf-approval-processes/SKILL.md +315 -0
  239. package/skills/sf-aura-development/SKILL.md +263 -0
  240. package/skills/sf-build-fix/SKILL.md +121 -0
  241. package/skills/sf-data-modeling/SKILL.md +278 -0
  242. package/skills/sf-debugging/SKILL.md +363 -0
  243. package/skills/sf-deployment/SKILL.md +295 -0
  244. package/skills/sf-deployment-constraints/SKILL.md +155 -0
  245. package/skills/sf-devops-ci-cd/SKILL.md +325 -0
  246. package/skills/sf-docs-lookup/SKILL.md +103 -0
  247. package/skills/sf-e2e-testing/SKILL.md +324 -0
  248. package/skills/sf-experience-cloud/SKILL.md +249 -0
  249. package/skills/sf-flow-development/SKILL.md +377 -0
  250. package/skills/sf-governor-limits/SKILL.md +323 -0
  251. package/skills/sf-harness-audit/SKILL.md +142 -0
  252. package/skills/sf-help/SKILL.md +159 -0
  253. package/skills/sf-integration/SKILL.md +483 -0
  254. package/skills/sf-lwc-constraints/SKILL.md +130 -0
  255. package/skills/sf-lwc-development/SKILL.md +303 -0
  256. package/skills/sf-lwc-testing/SKILL.md +388 -0
  257. package/skills/sf-metadata-management/SKILL.md +288 -0
  258. package/skills/sf-platform-events-cdc/SKILL.md +375 -0
  259. package/skills/sf-quickstart/SKILL.md +173 -0
  260. package/skills/sf-security/SKILL.md +334 -0
  261. package/skills/sf-security-constraints/SKILL.md +127 -0
  262. package/skills/sf-soql-constraints/SKILL.md +131 -0
  263. package/skills/sf-soql-optimization/SKILL.md +354 -0
  264. package/skills/sf-tdd-workflow/SKILL.md +336 -0
  265. package/skills/sf-testing-constraints/SKILL.md +200 -0
  266. package/skills/sf-trigger-constraints/SKILL.md +90 -0
  267. package/skills/sf-trigger-frameworks/SKILL.md +347 -0
  268. package/skills/sf-visualforce-development/SKILL.md +260 -0
  269. package/skills/strategic-compact/SKILL.md +208 -0
  270. package/skills/update-docs/SKILL.md +165 -0
  271. package/skills/update-platform-docs/SKILL.md +90 -0
@@ -0,0 +1,332 @@
1
+ ---
2
+ name: sf-tdd-workflow
3
+ description: >-
4
+ Use when doing test-driven Salesforce Apex development — RED-GREEN-REFACTOR cycle for classes, triggers, and LWC Jest. Do NOT use for test patterns only.
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ # Salesforce TDD Workflow
9
+
10
+ The test-driven development process adapted for the Salesforce platform. Test implementation patterns (mocks, factories, coverage strategies) live in `sf-apex-testing`. This skill covers the TDD _process_ — RED-GREEN-REFACTOR cycle and how to apply it to Apex, LWC, and Flows.
11
+
12
+ Reference: @../_reference/TESTING_STANDARDS.md
13
+
14
+ ---
15
+
16
+ ## When to Use
17
+
18
+ - When starting new Apex class or trigger development using the RED-GREEN-REFACTOR cycle
19
+ - When writing LWC Jest tests before building component logic
20
+ - When refactoring existing untested Apex code and needing a safety net
21
+ - When establishing TDD practices and coverage targets for a team
22
+ - When adding tests for a class below the 75% coverage deployment requirement
23
+
24
+ > **Related:** For test implementation details (@TestSetup, mocks, bulk testing, coverage), see `sf-apex-testing`.
25
+
26
+ ---
27
+
28
+ ## Core Workflow: RED-GREEN-REFACTOR
29
+
30
+ ### Step 1: RED — Write Failing Tests First
31
+
32
+ Write the test before the production code exists. The test should compile but fail.
33
+
34
+ **Apex:**
35
+
36
+ ```apex
37
+ @IsTest
38
+ private class AccountServiceTest {
39
+
40
+ @TestSetup
41
+ static void makeData() {
42
+ Account acc = new Account(Name = 'Test Account', Industry = 'Technology');
43
+ insert acc;
44
+ }
45
+
46
+ @IsTest
47
+ static void shouldCalculateAnnualRevenue() {
48
+ Account acc = [SELECT Id FROM Account LIMIT 1];
49
+
50
+ Test.startTest();
51
+ Decimal revenue = AccountService.calculateAnnualRevenue(acc.Id);
52
+ Test.stopTest();
53
+
54
+ Assert.isNotNull(revenue, 'Revenue should not be null');
55
+ Assert.isTrue(revenue >= 0, 'Revenue should be non-negative');
56
+ }
57
+
58
+ @IsTest
59
+ static void shouldHandleNullInput() {
60
+ Test.startTest();
61
+ try {
62
+ AccountService.calculateAnnualRevenue(null);
63
+ Assert.fail('Should have thrown exception');
64
+ } catch (AccountService.AccountServiceException e) {
65
+ Assert.isTrue(e.getMessage().contains('Account Id'),
66
+ 'Error should mention Account Id');
67
+ }
68
+ Test.stopTest();
69
+ }
70
+
71
+ @IsTest
72
+ static void shouldHandleBulkRecords() {
73
+ List<Account> accounts = new List<Account>();
74
+ for (Integer i = 0; i < 200; i++) {
75
+ accounts.add(new Account(Name = 'Bulk Test ' + i));
76
+ }
77
+ insert accounts;
78
+
79
+ Test.startTest();
80
+ List<Decimal> revenues = AccountService.calculateAnnualRevenueBulk(
81
+ new Map<Id, Account>(accounts).keySet()
82
+ );
83
+ Test.stopTest();
84
+
85
+ System.assertEquals(200, revenues.size(), 'Should process all 200 records');
86
+ }
87
+ }
88
+ ```
89
+
90
+ **LWC Jest:**
91
+
92
+ ```javascript
93
+ import { createElement } from 'lwc';
94
+ import AccountCard from 'c/accountCard';
95
+ import getAccount from '@salesforce/apex/AccountController.getAccount';
96
+
97
+ jest.mock('@salesforce/apex/AccountController.getAccount',
98
+ () => ({ default: jest.fn() }), { virtual: true });
99
+
100
+ describe('c-account-card', () => {
101
+ afterEach(() => {
102
+ while (document.body.firstChild) document.body.removeChild(document.body.firstChild);
103
+ });
104
+
105
+ it('displays account name when data is loaded', async () => {
106
+ getAccount.mockResolvedValue({ Name: 'Acme Corp', Industry: 'Technology' });
107
+ const element = createElement('c-account-card', { is: AccountCard });
108
+ element.recordId = '001xx000003ABCDEF';
109
+ document.body.appendChild(element);
110
+
111
+ await Promise.resolve();
112
+ const nameEl = element.shadowRoot.querySelector('.account-name');
113
+ expect(nameEl.textContent).toBe('Acme Corp');
114
+ });
115
+ });
116
+ ```
117
+
118
+ ### Step 2: GREEN — Implement Minimum Code
119
+
120
+ Write only enough code to make the tests pass. No premature optimization.
121
+
122
+ ### Step 3: REFACTOR — Clean Up
123
+
124
+ - Extract common patterns into utility classes
125
+ - Apply enterprise patterns (Service Layer, Selector Layer) if warranted
126
+ - Ensure bulkification
127
+ - Run full test suite to verify no regressions
128
+
129
+ ---
130
+
131
+ ## Trigger TDD
132
+
133
+ Create the handler class stub first (empty method signatures so the test compiles), then write the test, then implement the handler, then wire the trigger.
134
+
135
+ ```apex
136
+ // RED: Write test for trigger handler
137
+ @IsTest
138
+ private class AccountTriggerHandlerTest {
139
+
140
+ @IsTest
141
+ static void shouldSetDefaultIndustryOnInsert() {
142
+ Account acc = new Account(Name = 'TDD Account');
143
+
144
+ Test.startTest();
145
+ insert acc;
146
+ Test.stopTest();
147
+
148
+ Account result = [SELECT Industry FROM Account WHERE Id = :acc.Id];
149
+ System.assertEquals('Other', result.Industry, 'Should default Industry to Other');
150
+ }
151
+
152
+ @IsTest
153
+ static void shouldNotOverrideExistingIndustry() {
154
+ Account acc = new Account(Name = 'TDD Account', Industry = 'Technology');
155
+
156
+ Test.startTest();
157
+ insert acc;
158
+ Test.stopTest();
159
+
160
+ Account result = [SELECT Industry FROM Account WHERE Id = :acc.Id];
161
+ System.assertEquals('Technology', result.Industry, 'Should keep existing Industry');
162
+ }
163
+ }
164
+
165
+ // GREEN: Implement handler
166
+ public with sharing class AccountTriggerHandler {
167
+ public static void onBeforeInsert(List<Account> newAccounts) {
168
+ for (Account acc : newAccounts) {
169
+ if (acc.Industry == null) acc.Industry = 'Other';
170
+ }
171
+ }
172
+ }
173
+
174
+ // Wire trigger (last step)
175
+ trigger AccountTrigger on Account (before insert) {
176
+ AccountTriggerHandler.onBeforeInsert(Trigger.new);
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Async TDD Patterns
183
+
184
+ ### Queueable TDD
185
+
186
+ ```apex
187
+ @IsTest
188
+ private class AccountEnrichmentJobTest {
189
+
190
+ @IsTest
191
+ static void shouldEnrichAccountsWithExternalData() {
192
+ Account acc = new Account(Name = 'Enrich Me', BillingCity = 'San Francisco');
193
+ insert acc;
194
+
195
+ Test.startTest();
196
+ System.enqueueJob(new AccountEnrichmentJob(new Set<Id>{ acc.Id }));
197
+ Test.stopTest(); // Forces Queueable to execute synchronously
198
+
199
+ Account result = [SELECT Description FROM Account WHERE Id = :acc.Id];
200
+ System.assertNotEquals(null, result.Description, 'Should have enrichment data');
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Batch TDD
206
+
207
+ ```apex
208
+ @IsTest
209
+ private class DataCleanupBatchTest {
210
+
211
+ @IsTest
212
+ static void shouldDeactivateStaleAccounts() {
213
+ List<Account> accounts = new List<Account>();
214
+ for (Integer i = 0; i < 200; i++) {
215
+ accounts.add(new Account(Name = 'Stale ' + i));
216
+ }
217
+ insert accounts;
218
+
219
+ // Create old Tasks so LastActivityDate is set automatically
220
+ List<Task> oldTasks = new List<Task>();
221
+ for (Account acc : accounts) {
222
+ oldTasks.add(new Task(
223
+ WhatId = acc.Id, Subject = 'Old Activity',
224
+ ActivityDate = Date.today().addDays(-365), Status = 'Completed'
225
+ ));
226
+ }
227
+ insert oldTasks;
228
+
229
+ Test.startTest();
230
+ Database.executeBatch(new DataCleanupBatch(), 200);
231
+ Test.stopTest();
232
+
233
+ Integer activeCount = [SELECT COUNT() FROM Account
234
+ WHERE Active__c = 'Yes' AND Name LIKE 'Stale%'];
235
+ Assert.areEqual(0, activeCount, 'All stale accounts should be deactivated');
236
+ }
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Flow Testing in TDD
243
+
244
+ Test Flows by triggering them through DML and verifying outcomes.
245
+
246
+ ```apex
247
+ @IsTest
248
+ private class OpportunityFlowTest {
249
+
250
+ @IsTest
251
+ static void shouldCreateFollowUpTaskWhenOppClosedWon() {
252
+ Account acc = new Account(Name = 'Flow Test');
253
+ insert acc;
254
+
255
+ Opportunity opp = new Opportunity(
256
+ AccountId = acc.Id, Name = 'Flow Test Opp',
257
+ StageName = 'Prospecting', CloseDate = Date.today().addDays(30)
258
+ );
259
+ insert opp;
260
+
261
+ Test.startTest();
262
+ opp.StageName = 'Closed Won';
263
+ update opp;
264
+ Test.stopTest();
265
+
266
+ List<Task> tasks = [SELECT Subject FROM Task WHERE WhatId = :opp.Id];
267
+ System.assert(!tasks.isEmpty(), 'Flow should have created a follow-up task');
268
+ }
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## TDD in CI/CD Pipeline
275
+
276
+ ```yaml
277
+ # GitHub Actions: Run TDD suite on every PR
278
+ - name: Run Apex Tests and Check Coverage
279
+ run: |
280
+ sf apex run test \
281
+ --test-level RunLocalTests \
282
+ --code-coverage \
283
+ --result-format json \
284
+ --wait 15 \
285
+ --output-dir test-results \
286
+ --target-org ci-scratch
287
+
288
+ RESULT_FILE=$(ls test-results/test-run-*.json 2>/dev/null | head -1)
289
+ if [ -z "$RESULT_FILE" ]; then
290
+ echo "No test result file found"
291
+ exit 1
292
+ fi
293
+ COVERAGE=$(node -e "const r=JSON.parse(require('fs').readFileSync('$RESULT_FILE','utf8')); \
294
+ console.log(r.result?.summary?.orgWideCoverage?.replace('%',''))")
295
+ echo "Org-wide coverage: ${COVERAGE}%"
296
+ node -e "if (parseFloat('$COVERAGE') < 75) { process.exit(1); }"
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Coverage Targets
302
+
303
+ | Type | Minimum | Target |
304
+ |------|---------|--------|
305
+ | Apex Classes | 75% | 85%+ |
306
+ | Apex Triggers | 75% | 90%+ |
307
+ | LWC Components | N/A | 80%+ |
308
+ | Integration Tests | N/A | Key paths |
309
+
310
+ ---
311
+
312
+ ## Common TDD Mistakes
313
+
314
+ | Mistake | Problem | Fix |
315
+ |---------|---------|-----|
316
+ | Testing platform behavior | Tests Salesforce, not your code | Test business logic outcomes only |
317
+ | Testing getters/setters | No business value | Skip trivial accessors |
318
+ | No bulk test | Passes with 1 record, fails with 200 | Always include 200+ record test |
319
+ | Hardcoded record IDs | Breaks across orgs | Use @TestSetup or TestDataFactory |
320
+ | Skipping negative tests | Misses error handling gaps | Test invalid input, missing permissions |
321
+ | Testing inside try/catch without re-throw | Test passes even when assertion fails | Use `Assert.fail('Should have thrown')` pattern |
322
+
323
+ ---
324
+
325
+ ## Related
326
+
327
+ - **Agent**: `sf-apex-agent` — For interactive, in-depth guidance
328
+ - **Skills**: `sf-apex-testing` — Test implementation patterns
329
+
330
+ ### Guardrails
331
+
332
+ - `sf-testing-constraints` — Enforces test isolation, assertion requirements, SeeAllData prohibition, and coverage thresholds
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: sf-testing-constraints
3
+ description: >-
4
+ Enforce Apex testing standards — 75% coverage minimum, test isolation, assertions, TestDataFactory. Use when writing or reviewing ANY Apex test class or method. Do NOT use for LWC Jest or Flow tests.
5
+ ---
6
+
7
+ # Apex Testing Constraints
8
+
9
+ ## When to Use
10
+
11
+ This skill auto-activates when writing, reviewing, or modifying any Apex test class or method. It enforces coverage minimums, test isolation, assertion requirements, and TestDataFactory patterns for all test artifacts.
12
+
13
+ Hard rules for every Apex test class and method. Violating any NEVER rule is a blocking defect. Violating any ALWAYS rule requires justification in a code comment.
14
+
15
+ Reference: @../_reference/TESTING_STANDARDS.md
16
+
17
+ ---
18
+
19
+ ## NEVER Rules
20
+
21
+ These are non-negotiable. Flag violations immediately.
22
+
23
+ ### N1 — Never use `@isTest(SeeAllData=true)`
24
+
25
+ Tests must create their own data. `SeeAllData=true` couples tests to org state and breaks on every sandbox refresh. **Sole exception:** Standard Pricebook tests (prefer `Test.getStandardPricebookId()` even then).
26
+
27
+ ```apex
28
+ // VIOLATION
29
+ @isTest(SeeAllData=true)
30
+ private class BadTest { ... }
31
+
32
+ // CORRECT
33
+ @isTest
34
+ private class GoodTest { ... }
35
+ ```
36
+
37
+ ### N2 — Never write a test without assertions
38
+
39
+ A test method with zero assertions always passes and catches nothing. Every `@isTest` method must contain at least one meaningful assertion that verifies business logic, not just that DML succeeded.
40
+
41
+ ```apex
42
+ // VIOLATION — no assertion
43
+ @isTest
44
+ static void testCreate() {
45
+ Account a = new Account(Name = 'X');
46
+ insert a;
47
+ // method ends without any assert
48
+ }
49
+
50
+ // CORRECT — asserts business outcome
51
+ @isTest
52
+ static void testCreate_setsDefaultTier() {
53
+ Account a = new Account(Name = 'X');
54
+ insert a;
55
+ Account result = [SELECT Customer_Tier__c FROM Account WHERE Id = :a.Id];
56
+ Assert.areEqual('Standard', result.Customer_Tier__c,
57
+ 'Trigger should set default tier on insert');
58
+ }
59
+ ```
60
+
61
+ ### N3 — Never hardcode Record IDs
62
+
63
+ IDs differ between orgs, sandboxes, and scratch orgs. Hardcoded IDs cause silent test failures after a refresh.
64
+
65
+ ```apex
66
+ // VIOLATION
67
+ Id accountId = '0015g00000ABC12AAA';
68
+
69
+ // CORRECT
70
+ Account acc = TestDataFactory.createAccount();
71
+ Id accountId = acc.Id;
72
+ ```
73
+
74
+ ### N4 — Never test only SOQL
75
+
76
+ A test that queries records without exercising any service, trigger, or business logic method is not a test. It verifies that Salesforce can read its own database.
77
+
78
+ ```apex
79
+ // VIOLATION — tests the platform, not your code
80
+ @isTest
81
+ static void testQuery() {
82
+ insert new Account(Name = 'X');
83
+ List<Account> accs = [SELECT Id FROM Account];
84
+ Assert.areEqual(1, accs.size());
85
+ }
86
+ ```
87
+
88
+ ### N5 — Never use `System.debug` as a substitute for assertions
89
+
90
+ Debug statements produce no test signal. They cannot fail and cannot catch regressions.
91
+
92
+ ### N6 — Never test multiple unrelated scenarios in one method
93
+
94
+ One scenario per test method. Multi-scenario methods mask which scenario broke.
95
+
96
+ ---
97
+
98
+ ## ALWAYS Rules
99
+
100
+ Required in every test class unless explicitly justified.
101
+
102
+ ### A1 — Always use `@TestSetup` for shared test data
103
+
104
+ Runs once per class; each test method gets its own rollback. Omitting it duplicates DML and wastes governor limits.
105
+
106
+ ```apex
107
+ @TestSetup
108
+ static void makeData() {
109
+ Account acc = TestDataFactory.createAccount();
110
+ TestDataFactory.createOpportunity(acc.Id);
111
+ }
112
+ ```
113
+
114
+ ### A2 — Always use TestDataFactory
115
+
116
+ All test record creation goes through a centralized `TestDataFactory` class. Inline `new SObject(...)` / `insert` is acceptable only for one-off scenario overrides the factory cannot express. Single maintenance point, override maps, consistent defaults.
117
+
118
+ ### A3 — Always assert positive AND negative cases
119
+
120
+ Every service method needs at least:
121
+
122
+ - One positive test (valid input produces expected output)
123
+ - One negative test (invalid input throws expected exception or returns expected error)
124
+
125
+ ```apex
126
+ // Positive
127
+ @isTest
128
+ static void testUpgrade_validAccount_succeeds() { ... }
129
+
130
+ // Negative
131
+ @isTest
132
+ static void testUpgrade_insufficientRevenue_throwsException() { ... }
133
+ ```
134
+
135
+ ### A4 — Always include a bulk test with 200+ records
136
+
137
+ 200 is the standard trigger batch size. Code that passes with 1 record can hit governor limits at 200. Every trigger handler and service method operating on collections must have a 200-record test.
138
+
139
+ ```apex
140
+ @isTest
141
+ static void testHandler_bulkInsert_200Records_noLimitException() {
142
+ List<Account> accounts = TestDataFactory.createAccounts(200);
143
+ // Trigger already fired on insert; verify outcomes
144
+ Assert.areEqual(200,
145
+ [SELECT COUNT() FROM Account WHERE Customer_Tier__c = 'Standard'],
146
+ 'All 200 accounts should have default tier set');
147
+ }
148
+ ```
149
+
150
+ ### A5 — Always use `Test.startTest()` / `Test.stopTest()`
151
+
152
+ Resets governor limit counters and forces async work (@future, Queueable, Batch) to execute synchronously. One pair per test method.
153
+
154
+ ### A6 — Always use descriptive test method names
155
+
156
+ Format: `test{MethodName}_{scenario}_{expectedResult}` (e.g., `testCalculateDiscount_premiumTier_returns20Percent`).
157
+
158
+ ### A7 — Always add `@testFor` on test classes (see @../_reference/API_VERSIONS.md for minimum version)
159
+
160
+ Maps test classes to production classes for `RunRelevantTests`. Missing `@testFor` means the test may be skipped on relevant changes.
161
+
162
+ ---
163
+
164
+ ## Anti-Pattern Quick Reference
165
+
166
+ | # | Anti-Pattern | Why It Breaks | Required Fix |
167
+ |---|---|---|---|
168
+ | N1 | `SeeAllData=true` | Fails on sandbox refresh; couples test to org data | Create test data via TestDataFactory |
169
+ | N2 | No assertions | Test always passes; catches zero regressions | Add `Assert.areEqual` / `Assert.isTrue` for business logic |
170
+ | N3 | Hardcoded Record IDs | IDs differ between orgs | Query or create records in test |
171
+ | N4 | SOQL-only test | Tests the platform, not your code | Exercise a service/trigger method, then assert outcomes |
172
+ | N5 | `System.debug` instead of assert | No test signal; cannot fail | Replace with assertions |
173
+ | N6 | One method, 10 scenarios | Masks which scenario fails | One scenario per test method |
174
+ | A1 | No `@TestSetup` | Duplicated DML, wasted governor limits | Add `@TestSetup` with TestDataFactory calls |
175
+ | A2 | Inline record creation | Maintenance burden when fields change | Use TestDataFactory with override maps |
176
+ | A3 | Only happy-path tests | Error handling never verified | Add negative-case tests |
177
+ | A4 | No bulk test | Governor limit violations hidden | Test with 200 records |
178
+ | A5 | Missing `startTest/stopTest` | Governor limits not reset; async not executed | Wrap code under test in the pair |
179
+ | A6 | Vague method names | Cannot identify failing scenario from name | Use `test{Method}_{scenario}_{expected}` format |
180
+ | A7 | Missing `@testFor` | `RunRelevantTests` may skip the test | Add `@testFor(ClassName)` annotation |
181
+
182
+ ---
183
+
184
+ ## Coverage Targets
185
+
186
+ | Context | Minimum | Recommended |
187
+ |---|---|---|
188
+ | Production deployment (org-wide) | 75% | 85%+ per class |
189
+ | Managed package (AppExchange) | 75% | 90%+ per class |
190
+ | Triggers | 75% | 90%+ |
191
+
192
+ Coverage measures lines executed, not branches. Test every branch, not just every line.
193
+
194
+ ## Related
195
+
196
+ - `sf-apex-testing` — Full test implementation patterns (mocks, async, permissions)
197
+ - `sf-tdd-workflow` — RED-GREEN-REFACTOR process and TDD workflow
198
+ - @../_reference/TESTING_STANDARDS.md — Platform testing standards (see @../_reference/API_VERSIONS.md)
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: sf-trigger-constraints
3
+ description: >-
4
+ Enforce one-trigger-per-object, handler delegation, bulkification, and recursion prevention. Use when writing or reviewing ANY Apex trigger or handler. Do NOT use for non-trigger Apex, LWC, or Flow.
5
+ ---
6
+
7
+ # Trigger Constraints
8
+
9
+ ## When to Use
10
+
11
+ This skill auto-activates when writing, reviewing, or modifying any Apex trigger or trigger handler. It enforces one-trigger-per-object, handler delegation, bulkification, and recursion prevention rules for all trigger artifacts.
12
+
13
+ Hard rules that every Apex trigger and trigger handler must satisfy. Violations are blockers -- flag them before any other review feedback.
14
+
15
+ Reference: @../_reference/TRIGGER_PATTERNS.md (order of execution, context variables, framework comparison).
16
+
17
+ ---
18
+
19
+ ## Never Rules
20
+
21
+ These are absolute prohibitions. Any occurrence is a defect.
22
+
23
+ | ID | Rule | Why |
24
+ |----|------|-----|
25
+ | N1 | **No logic in the trigger body** | Trigger files contain only the handler invocation (`new Handler().run()` or `fflib_SObjectDomain.triggerHandler(Domain.class)`). Zero conditionals, zero loops, zero DML. |
26
+ | N2 | **No multiple triggers per object** | Multiple triggers on the same sObject have no guaranteed execution order (see @../_reference/TRIGGER_PATTERNS.md, Step 5/9). Consolidate into one trigger file per object. |
27
+ | N3 | **No DML inside loops** | `insert`/`update`/`delete`/`upsert`/`Database.*` calls inside `for` loops hit governor limits. Collect records first, DML once outside the loop. |
28
+ | N4 | **No SOQL inside loops** | Queries inside `for` loops risk the per-transaction SOQL limit (see @../_reference/GOVERNOR_LIMITS.md). Query before the loop, store results in a `Map<Id, SObject>`. |
29
+ | N5 | **No hardcoded IDs** | Record IDs, profile IDs, or record-type IDs must never appear as string literals. Use `Schema.SObjectType.*.getRecordTypeInfosByDeveloperName()`, Custom Metadata, or Custom Labels. |
30
+ | N6 | **No direct callouts** | Apex triggers cannot make HTTP callouts synchronously. Use `@future(callout=true)` or `Queueable` with `Database.AllowsCallouts`. |
31
+ | N7 | **No `Trigger.new` modification in after triggers** | `Trigger.new` is read-only in after contexts. Field updates in after triggers must go through a separate DML statement on queried/cloned records. |
32
+
33
+ ---
34
+
35
+ ## Always Rules
36
+
37
+ Every trigger implementation must include these elements.
38
+
39
+ | ID | Rule | How |
40
+ |----|------|-----|
41
+ | A1 | **Delegate to a handler class** | Trigger body calls handler: `new AccountTriggerHandler().run();`. All logic lives in the handler or in service classes the handler calls. |
42
+ | A2 | **Bulkify all logic** | Every method must handle `List<SObject>` (up to 200 records per chunk). No assumption of single-record input. Iterate `Trigger.new` / `Trigger.old`, never index `[0]` alone. |
43
+ | A3 | **Use a recursion guard** | Prevent infinite re-entry. Recommended: static `Set<Id>` of processed IDs (allows workflow re-fire for unprocessed records while blocking true recursion). Alternatives: depth counter in base class, `setMaxLoopCount()`. See @../_reference/TRIGGER_PATTERNS.md recursion patterns. |
44
+ | A4 | **Use `Trigger.newMap` / `Trigger.oldMap` for comparisons** | When detecting field changes in update triggers, compare `Trigger.newMap.get(id).Field__c` against `Trigger.oldMap.get(id).Field__c`. Never rely on list index alignment. |
45
+ | A5 | **Register all events in one trigger** | The single trigger file should subscribe to all seven events (`before insert, before update, before delete, after insert, after update, after delete, after undelete`) even if the handler only overrides a subset today. This prevents needing a trigger file redeploy when new events are handled later. |
46
+ | A6 | **Include a bypass mechanism** | Support disabling the handler without a code deploy. Use `TriggerHandler.bypass()` / `.clearBypass()`, Custom Metadata (`Trigger_Setting__mdt`), or Hierarchy Custom Settings. Always reset bypass state in a `finally` block. |
47
+ | A7 | **Keep handler methods focused** | Each `onBeforeInsert()`, `onAfterUpdate()`, etc. should call named service methods. If a handler method exceeds ~30 lines, extract to a service class. |
48
+
49
+ ---
50
+
51
+ ## Anti-Pattern Table
52
+
53
+ | Anti-Pattern | Example | Correct Alternative |
54
+ |---|---|---|
55
+ | Logic in trigger body | `trigger T on Account (before insert) { for (Account a : Trigger.new) { a.Name = 'X'; } }` | `trigger T on Account (...) { new AccountTriggerHandler().run(); }` with logic in handler |
56
+ | Two triggers on same object | `AccountTrigger.trigger` + `AccountOwnerTrigger.trigger` | Single `AccountTrigger.trigger` delegating to one handler |
57
+ | DML in loop | `for (Account a : accts) { update a; }` | `update accts;` outside loop |
58
+ | SOQL in loop | `for (Account a : accts) { Contact c = [SELECT ...]; }` | `Map<Id, Contact> cMap = new Map<Id, Contact>([SELECT ...]); // before loop` |
59
+ | Hardcoded ID | `if (acc.RecordTypeId == '012000000000001')` | `Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName().get('Customer').getRecordTypeId()` |
60
+ | No recursion guard | After-update handler updates same records with no static check | `private static Set<Id> processedIds = new Set<Id>();` -- skip IDs already in set |
61
+ | Boolean recursion flag | `static Boolean hasRun = false; if (hasRun) return;` | `Set<Id>` -- boolean flag blocks legitimate workflow re-fire for unprocessed records |
62
+ | Modifying `Trigger.new` in after context | `for (Account a : Trigger.new) { a.Status__c = 'Done'; }` in `onAfterInsert` | Query records, update separately: `update [SELECT Id FROM Account WHERE Id IN :newMap.keySet()]` |
63
+
64
+ ---
65
+
66
+ ## Quick Checklist
67
+
68
+ Use when writing or reviewing a trigger PR:
69
+
70
+ - [ ] Exactly one `.trigger` file per sObject
71
+ - [ ] Trigger body is a single handler call (no logic)
72
+ - [ ] Handler extends `TriggerHandler` (or FFLIB `fflib_SObjectDomain`)
73
+ - [ ] All seven events registered in trigger definition
74
+ - [ ] Every loop processes `List<SObject>`, not a single record
75
+ - [ ] Zero SOQL or DML inside any loop
76
+ - [ ] No hardcoded IDs anywhere
77
+ - [ ] Recursion guard present (prefer `Set<Id>` pattern)
78
+ - [ ] Bypass mechanism available
79
+ - [ ] No `Trigger.new` mutation in after-trigger methods
80
+ - [ ] No synchronous callouts
81
+
82
+ ---
83
+
84
+ ## Related
85
+
86
+ - **Skill**: `sf-trigger-frameworks` -- Framework patterns, base class code, migration guide
87
+ - **Reference**: @../_reference/TRIGGER_PATTERNS.md -- Order of execution, context variables, framework comparison
88
+ - **Agent**: `sf-architect` -- Interactive trigger design guidance