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,336 @@
1
+ ---
2
+ name: sf-tdd-workflow
3
+ description: >-
4
+ Use when doing test-driven Salesforce Apex development — RED-GREEN-REFACTOR
5
+ cycle for classes, triggers, and LWC Jest. Do NOT use for test patterns only.
6
+ origin: SCC
7
+ user-invocable: false
8
+ allowed-tools: Read, Grep, Glob, Edit, Write, Bash
9
+ disable-model-invocation: true
10
+ ---
11
+
12
+ # Salesforce TDD Workflow
13
+
14
+ 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.
15
+
16
+ Reference: @../_reference/TESTING_STANDARDS.md
17
+
18
+ ---
19
+
20
+ ## When to Use
21
+
22
+ - When starting new Apex class or trigger development using the RED-GREEN-REFACTOR cycle
23
+ - When writing LWC Jest tests before building component logic
24
+ - When refactoring existing untested Apex code and needing a safety net
25
+ - When establishing TDD practices and coverage targets for a team
26
+ - When adding tests for a class below the 75% coverage deployment requirement
27
+
28
+ > **Related:** For test implementation details (@TestSetup, mocks, bulk testing, coverage), see `sf-apex-testing`.
29
+
30
+ ---
31
+
32
+ ## Core Workflow: RED-GREEN-REFACTOR
33
+
34
+ ### Step 1: RED — Write Failing Tests First
35
+
36
+ Write the test before the production code exists. The test should compile but fail.
37
+
38
+ **Apex:**
39
+
40
+ ```apex
41
+ @IsTest
42
+ private class AccountServiceTest {
43
+
44
+ @TestSetup
45
+ static void makeData() {
46
+ Account acc = new Account(Name = 'Test Account', Industry = 'Technology');
47
+ insert acc;
48
+ }
49
+
50
+ @IsTest
51
+ static void shouldCalculateAnnualRevenue() {
52
+ Account acc = [SELECT Id FROM Account LIMIT 1];
53
+
54
+ Test.startTest();
55
+ Decimal revenue = AccountService.calculateAnnualRevenue(acc.Id);
56
+ Test.stopTest();
57
+
58
+ Assert.isNotNull(revenue, 'Revenue should not be null');
59
+ Assert.isTrue(revenue >= 0, 'Revenue should be non-negative');
60
+ }
61
+
62
+ @IsTest
63
+ static void shouldHandleNullInput() {
64
+ Test.startTest();
65
+ try {
66
+ AccountService.calculateAnnualRevenue(null);
67
+ Assert.fail('Should have thrown exception');
68
+ } catch (AccountService.AccountServiceException e) {
69
+ Assert.isTrue(e.getMessage().contains('Account Id'),
70
+ 'Error should mention Account Id');
71
+ }
72
+ Test.stopTest();
73
+ }
74
+
75
+ @IsTest
76
+ static void shouldHandleBulkRecords() {
77
+ List<Account> accounts = new List<Account>();
78
+ for (Integer i = 0; i < 200; i++) {
79
+ accounts.add(new Account(Name = 'Bulk Test ' + i));
80
+ }
81
+ insert accounts;
82
+
83
+ Test.startTest();
84
+ List<Decimal> revenues = AccountService.calculateAnnualRevenueBulk(
85
+ new Map<Id, Account>(accounts).keySet()
86
+ );
87
+ Test.stopTest();
88
+
89
+ System.assertEquals(200, revenues.size(), 'Should process all 200 records');
90
+ }
91
+ }
92
+ ```
93
+
94
+ **LWC Jest:**
95
+
96
+ ```javascript
97
+ import { createElement } from 'lwc';
98
+ import AccountCard from 'c/accountCard';
99
+ import getAccount from '@salesforce/apex/AccountController.getAccount';
100
+
101
+ jest.mock('@salesforce/apex/AccountController.getAccount',
102
+ () => ({ default: jest.fn() }), { virtual: true });
103
+
104
+ describe('c-account-card', () => {
105
+ afterEach(() => {
106
+ while (document.body.firstChild) document.body.removeChild(document.body.firstChild);
107
+ });
108
+
109
+ it('displays account name when data is loaded', async () => {
110
+ getAccount.mockResolvedValue({ Name: 'Acme Corp', Industry: 'Technology' });
111
+ const element = createElement('c-account-card', { is: AccountCard });
112
+ element.recordId = '001xx000003ABCDEF';
113
+ document.body.appendChild(element);
114
+
115
+ await Promise.resolve();
116
+ const nameEl = element.shadowRoot.querySelector('.account-name');
117
+ expect(nameEl.textContent).toBe('Acme Corp');
118
+ });
119
+ });
120
+ ```
121
+
122
+ ### Step 2: GREEN — Implement Minimum Code
123
+
124
+ Write only enough code to make the tests pass. No premature optimization.
125
+
126
+ ### Step 3: REFACTOR — Clean Up
127
+
128
+ - Extract common patterns into utility classes
129
+ - Apply enterprise patterns (Service Layer, Selector Layer) if warranted
130
+ - Ensure bulkification
131
+ - Run full test suite to verify no regressions
132
+
133
+ ---
134
+
135
+ ## Trigger TDD
136
+
137
+ 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.
138
+
139
+ ```apex
140
+ // RED: Write test for trigger handler
141
+ @IsTest
142
+ private class AccountTriggerHandlerTest {
143
+
144
+ @IsTest
145
+ static void shouldSetDefaultIndustryOnInsert() {
146
+ Account acc = new Account(Name = 'TDD Account');
147
+
148
+ Test.startTest();
149
+ insert acc;
150
+ Test.stopTest();
151
+
152
+ Account result = [SELECT Industry FROM Account WHERE Id = :acc.Id];
153
+ System.assertEquals('Other', result.Industry, 'Should default Industry to Other');
154
+ }
155
+
156
+ @IsTest
157
+ static void shouldNotOverrideExistingIndustry() {
158
+ Account acc = new Account(Name = 'TDD Account', Industry = 'Technology');
159
+
160
+ Test.startTest();
161
+ insert acc;
162
+ Test.stopTest();
163
+
164
+ Account result = [SELECT Industry FROM Account WHERE Id = :acc.Id];
165
+ System.assertEquals('Technology', result.Industry, 'Should keep existing Industry');
166
+ }
167
+ }
168
+
169
+ // GREEN: Implement handler
170
+ public with sharing class AccountTriggerHandler {
171
+ public static void onBeforeInsert(List<Account> newAccounts) {
172
+ for (Account acc : newAccounts) {
173
+ if (acc.Industry == null) acc.Industry = 'Other';
174
+ }
175
+ }
176
+ }
177
+
178
+ // Wire trigger (last step)
179
+ trigger AccountTrigger on Account (before insert) {
180
+ AccountTriggerHandler.onBeforeInsert(Trigger.new);
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Async TDD Patterns
187
+
188
+ ### Queueable TDD
189
+
190
+ ```apex
191
+ @IsTest
192
+ private class AccountEnrichmentJobTest {
193
+
194
+ @IsTest
195
+ static void shouldEnrichAccountsWithExternalData() {
196
+ Account acc = new Account(Name = 'Enrich Me', BillingCity = 'San Francisco');
197
+ insert acc;
198
+
199
+ Test.startTest();
200
+ System.enqueueJob(new AccountEnrichmentJob(new Set<Id>{ acc.Id }));
201
+ Test.stopTest(); // Forces Queueable to execute synchronously
202
+
203
+ Account result = [SELECT Description FROM Account WHERE Id = :acc.Id];
204
+ System.assertNotEquals(null, result.Description, 'Should have enrichment data');
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Batch TDD
210
+
211
+ ```apex
212
+ @IsTest
213
+ private class DataCleanupBatchTest {
214
+
215
+ @IsTest
216
+ static void shouldDeactivateStaleAccounts() {
217
+ List<Account> accounts = new List<Account>();
218
+ for (Integer i = 0; i < 200; i++) {
219
+ accounts.add(new Account(Name = 'Stale ' + i));
220
+ }
221
+ insert accounts;
222
+
223
+ // Create old Tasks so LastActivityDate is set automatically
224
+ List<Task> oldTasks = new List<Task>();
225
+ for (Account acc : accounts) {
226
+ oldTasks.add(new Task(
227
+ WhatId = acc.Id, Subject = 'Old Activity',
228
+ ActivityDate = Date.today().addDays(-365), Status = 'Completed'
229
+ ));
230
+ }
231
+ insert oldTasks;
232
+
233
+ Test.startTest();
234
+ Database.executeBatch(new DataCleanupBatch(), 200);
235
+ Test.stopTest();
236
+
237
+ Integer activeCount = [SELECT COUNT() FROM Account
238
+ WHERE Active__c = 'Yes' AND Name LIKE 'Stale%'];
239
+ Assert.areEqual(0, activeCount, 'All stale accounts should be deactivated');
240
+ }
241
+ }
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Flow Testing in TDD
247
+
248
+ Test Flows by triggering them through DML and verifying outcomes.
249
+
250
+ ```apex
251
+ @IsTest
252
+ private class OpportunityFlowTest {
253
+
254
+ @IsTest
255
+ static void shouldCreateFollowUpTaskWhenOppClosedWon() {
256
+ Account acc = new Account(Name = 'Flow Test');
257
+ insert acc;
258
+
259
+ Opportunity opp = new Opportunity(
260
+ AccountId = acc.Id, Name = 'Flow Test Opp',
261
+ StageName = 'Prospecting', CloseDate = Date.today().addDays(30)
262
+ );
263
+ insert opp;
264
+
265
+ Test.startTest();
266
+ opp.StageName = 'Closed Won';
267
+ update opp;
268
+ Test.stopTest();
269
+
270
+ List<Task> tasks = [SELECT Subject FROM Task WHERE WhatId = :opp.Id];
271
+ System.assert(!tasks.isEmpty(), 'Flow should have created a follow-up task');
272
+ }
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## TDD in CI/CD Pipeline
279
+
280
+ ```yaml
281
+ # GitHub Actions: Run TDD suite on every PR
282
+ - name: Run Apex Tests and Check Coverage
283
+ run: |
284
+ sf apex run test \
285
+ --test-level RunLocalTests \
286
+ --code-coverage \
287
+ --result-format json \
288
+ --wait 15 \
289
+ --output-dir test-results \
290
+ --target-org ci-scratch
291
+
292
+ RESULT_FILE=$(ls test-results/test-run-*.json 2>/dev/null | head -1)
293
+ if [ -z "$RESULT_FILE" ]; then
294
+ echo "No test result file found"
295
+ exit 1
296
+ fi
297
+ COVERAGE=$(node -e "const r=JSON.parse(require('fs').readFileSync('$RESULT_FILE','utf8')); \
298
+ console.log(r.result?.summary?.orgWideCoverage?.replace('%',''))")
299
+ echo "Org-wide coverage: ${COVERAGE}%"
300
+ node -e "if (parseFloat('$COVERAGE') < 75) { process.exit(1); }"
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Coverage Targets
306
+
307
+ | Type | Minimum | Target |
308
+ |------|---------|--------|
309
+ | Apex Classes | 75% | 85%+ |
310
+ | Apex Triggers | 75% | 90%+ |
311
+ | LWC Components | N/A | 80%+ |
312
+ | Integration Tests | N/A | Key paths |
313
+
314
+ ---
315
+
316
+ ## Common TDD Mistakes
317
+
318
+ | Mistake | Problem | Fix |
319
+ |---------|---------|-----|
320
+ | Testing platform behavior | Tests Salesforce, not your code | Test business logic outcomes only |
321
+ | Testing getters/setters | No business value | Skip trivial accessors |
322
+ | No bulk test | Passes with 1 record, fails with 200 | Always include 200+ record test |
323
+ | Hardcoded record IDs | Breaks across orgs | Use @TestSetup or TestDataFactory |
324
+ | Skipping negative tests | Misses error handling gaps | Test invalid input, missing permissions |
325
+ | Testing inside try/catch without re-throw | Test passes even when assertion fails | Use `Assert.fail('Should have thrown')` pattern |
326
+
327
+ ---
328
+
329
+ ## Related
330
+
331
+ - **Agent**: `sf-apex-agent` — For interactive, in-depth guidance
332
+ - **Skills**: `sf-apex-testing` — Test implementation patterns
333
+
334
+ ### Guardrails
335
+
336
+ - `sf-testing-constraints` — Enforces test isolation, assertion requirements, SeeAllData prohibition, and coverage thresholds
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: sf-testing-constraints
3
+ description: "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."
4
+ origin: SCC
5
+ user-invocable: false
6
+ allowed-tools: Read, Grep, Glob
7
+ ---
8
+
9
+ # Apex Testing Constraints
10
+
11
+ ## When to Use
12
+
13
+ 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.
14
+
15
+ 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.
16
+
17
+ Reference: @../_reference/TESTING_STANDARDS.md
18
+
19
+ ---
20
+
21
+ ## NEVER Rules
22
+
23
+ These are non-negotiable. Flag violations immediately.
24
+
25
+ ### N1 — Never use `@isTest(SeeAllData=true)`
26
+
27
+ 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).
28
+
29
+ ```apex
30
+ // VIOLATION
31
+ @isTest(SeeAllData=true)
32
+ private class BadTest { ... }
33
+
34
+ // CORRECT
35
+ @isTest
36
+ private class GoodTest { ... }
37
+ ```
38
+
39
+ ### N2 — Never write a test without assertions
40
+
41
+ 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.
42
+
43
+ ```apex
44
+ // VIOLATION — no assertion
45
+ @isTest
46
+ static void testCreate() {
47
+ Account a = new Account(Name = 'X');
48
+ insert a;
49
+ // method ends without any assert
50
+ }
51
+
52
+ // CORRECT — asserts business outcome
53
+ @isTest
54
+ static void testCreate_setsDefaultTier() {
55
+ Account a = new Account(Name = 'X');
56
+ insert a;
57
+ Account result = [SELECT Customer_Tier__c FROM Account WHERE Id = :a.Id];
58
+ Assert.areEqual('Standard', result.Customer_Tier__c,
59
+ 'Trigger should set default tier on insert');
60
+ }
61
+ ```
62
+
63
+ ### N3 — Never hardcode Record IDs
64
+
65
+ IDs differ between orgs, sandboxes, and scratch orgs. Hardcoded IDs cause silent test failures after a refresh.
66
+
67
+ ```apex
68
+ // VIOLATION
69
+ Id accountId = '0015g00000ABC12AAA';
70
+
71
+ // CORRECT
72
+ Account acc = TestDataFactory.createAccount();
73
+ Id accountId = acc.Id;
74
+ ```
75
+
76
+ ### N4 — Never test only SOQL
77
+
78
+ 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.
79
+
80
+ ```apex
81
+ // VIOLATION — tests the platform, not your code
82
+ @isTest
83
+ static void testQuery() {
84
+ insert new Account(Name = 'X');
85
+ List<Account> accs = [SELECT Id FROM Account];
86
+ Assert.areEqual(1, accs.size());
87
+ }
88
+ ```
89
+
90
+ ### N5 — Never use `System.debug` as a substitute for assertions
91
+
92
+ Debug statements produce no test signal. They cannot fail and cannot catch regressions.
93
+
94
+ ### N6 — Never test multiple unrelated scenarios in one method
95
+
96
+ One scenario per test method. Multi-scenario methods mask which scenario broke.
97
+
98
+ ---
99
+
100
+ ## ALWAYS Rules
101
+
102
+ Required in every test class unless explicitly justified.
103
+
104
+ ### A1 — Always use `@TestSetup` for shared test data
105
+
106
+ Runs once per class; each test method gets its own rollback. Omitting it duplicates DML and wastes governor limits.
107
+
108
+ ```apex
109
+ @TestSetup
110
+ static void makeData() {
111
+ Account acc = TestDataFactory.createAccount();
112
+ TestDataFactory.createOpportunity(acc.Id);
113
+ }
114
+ ```
115
+
116
+ ### A2 — Always use TestDataFactory
117
+
118
+ 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.
119
+
120
+ ### A3 — Always assert positive AND negative cases
121
+
122
+ Every service method needs at least:
123
+
124
+ - One positive test (valid input produces expected output)
125
+ - One negative test (invalid input throws expected exception or returns expected error)
126
+
127
+ ```apex
128
+ // Positive
129
+ @isTest
130
+ static void testUpgrade_validAccount_succeeds() { ... }
131
+
132
+ // Negative
133
+ @isTest
134
+ static void testUpgrade_insufficientRevenue_throwsException() { ... }
135
+ ```
136
+
137
+ ### A4 — Always include a bulk test with 200+ records
138
+
139
+ 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.
140
+
141
+ ```apex
142
+ @isTest
143
+ static void testHandler_bulkInsert_200Records_noLimitException() {
144
+ List<Account> accounts = TestDataFactory.createAccounts(200);
145
+ // Trigger already fired on insert; verify outcomes
146
+ Assert.areEqual(200,
147
+ [SELECT COUNT() FROM Account WHERE Customer_Tier__c = 'Standard'],
148
+ 'All 200 accounts should have default tier set');
149
+ }
150
+ ```
151
+
152
+ ### A5 — Always use `Test.startTest()` / `Test.stopTest()`
153
+
154
+ Resets governor limit counters and forces async work (@future, Queueable, Batch) to execute synchronously. One pair per test method.
155
+
156
+ ### A6 — Always use descriptive test method names
157
+
158
+ Format: `test{MethodName}_{scenario}_{expectedResult}` (e.g., `testCalculateDiscount_premiumTier_returns20Percent`).
159
+
160
+ ### A7 — Always add `@testFor` on test classes (see @../_reference/API_VERSIONS.md for minimum version)
161
+
162
+ Maps test classes to production classes for `RunRelevantTests`. Missing `@testFor` means the test may be skipped on relevant changes.
163
+
164
+ ---
165
+
166
+ ## Anti-Pattern Quick Reference
167
+
168
+ | # | Anti-Pattern | Why It Breaks | Required Fix |
169
+ |---|---|---|---|
170
+ | N1 | `SeeAllData=true` | Fails on sandbox refresh; couples test to org data | Create test data via TestDataFactory |
171
+ | N2 | No assertions | Test always passes; catches zero regressions | Add `Assert.areEqual` / `Assert.isTrue` for business logic |
172
+ | N3 | Hardcoded Record IDs | IDs differ between orgs | Query or create records in test |
173
+ | N4 | SOQL-only test | Tests the platform, not your code | Exercise a service/trigger method, then assert outcomes |
174
+ | N5 | `System.debug` instead of assert | No test signal; cannot fail | Replace with assertions |
175
+ | N6 | One method, 10 scenarios | Masks which scenario fails | One scenario per test method |
176
+ | A1 | No `@TestSetup` | Duplicated DML, wasted governor limits | Add `@TestSetup` with TestDataFactory calls |
177
+ | A2 | Inline record creation | Maintenance burden when fields change | Use TestDataFactory with override maps |
178
+ | A3 | Only happy-path tests | Error handling never verified | Add negative-case tests |
179
+ | A4 | No bulk test | Governor limit violations hidden | Test with 200 records |
180
+ | A5 | Missing `startTest/stopTest` | Governor limits not reset; async not executed | Wrap code under test in the pair |
181
+ | A6 | Vague method names | Cannot identify failing scenario from name | Use `test{Method}_{scenario}_{expected}` format |
182
+ | A7 | Missing `@testFor` | `RunRelevantTests` may skip the test | Add `@testFor(ClassName)` annotation |
183
+
184
+ ---
185
+
186
+ ## Coverage Targets
187
+
188
+ | Context | Minimum | Recommended |
189
+ |---|---|---|
190
+ | Production deployment (org-wide) | 75% | 85%+ per class |
191
+ | Managed package (AppExchange) | 75% | 90%+ per class |
192
+ | Triggers | 75% | 90%+ |
193
+
194
+ Coverage measures lines executed, not branches. Test every branch, not just every line.
195
+
196
+ ## Related
197
+
198
+ - `sf-apex-testing` — Full test implementation patterns (mocks, async, permissions)
199
+ - `sf-tdd-workflow` — RED-GREEN-REFACTOR process and TDD workflow
200
+ - @../_reference/TESTING_STANDARDS.md — Platform testing standards (see @../_reference/API_VERSIONS.md)
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: sf-trigger-constraints
3
+ description: "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."
4
+ origin: SCC
5
+ user-invocable: false
6
+ allowed-tools: Read, Grep, Glob
7
+ ---
8
+
9
+ # Trigger Constraints
10
+
11
+ ## When to Use
12
+
13
+ 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.
14
+
15
+ Hard rules that every Apex trigger and trigger handler must satisfy. Violations are blockers -- flag them before any other review feedback.
16
+
17
+ Reference: @../_reference/TRIGGER_PATTERNS.md (order of execution, context variables, framework comparison).
18
+
19
+ ---
20
+
21
+ ## Never Rules
22
+
23
+ These are absolute prohibitions. Any occurrence is a defect.
24
+
25
+ | ID | Rule | Why |
26
+ |----|------|-----|
27
+ | 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. |
28
+ | 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. |
29
+ | 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. |
30
+ | 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>`. |
31
+ | 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. |
32
+ | N6 | **No direct callouts** | Apex triggers cannot make HTTP callouts synchronously. Use `@future(callout=true)` or `Queueable` with `Database.AllowsCallouts`. |
33
+ | 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. |
34
+
35
+ ---
36
+
37
+ ## Always Rules
38
+
39
+ Every trigger implementation must include these elements.
40
+
41
+ | ID | Rule | How |
42
+ |----|------|-----|
43
+ | 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. |
44
+ | 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. |
45
+ | 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. |
46
+ | 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. |
47
+ | 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. |
48
+ | 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. |
49
+ | 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. |
50
+
51
+ ---
52
+
53
+ ## Anti-Pattern Table
54
+
55
+ | Anti-Pattern | Example | Correct Alternative |
56
+ |---|---|---|
57
+ | 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 |
58
+ | Two triggers on same object | `AccountTrigger.trigger` + `AccountOwnerTrigger.trigger` | Single `AccountTrigger.trigger` delegating to one handler |
59
+ | DML in loop | `for (Account a : accts) { update a; }` | `update accts;` outside loop |
60
+ | SOQL in loop | `for (Account a : accts) { Contact c = [SELECT ...]; }` | `Map<Id, Contact> cMap = new Map<Id, Contact>([SELECT ...]); // before loop` |
61
+ | Hardcoded ID | `if (acc.RecordTypeId == '012000000000001')` | `Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName().get('Customer').getRecordTypeId()` |
62
+ | 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 |
63
+ | Boolean recursion flag | `static Boolean hasRun = false; if (hasRun) return;` | `Set<Id>` -- boolean flag blocks legitimate workflow re-fire for unprocessed records |
64
+ | 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()]` |
65
+
66
+ ---
67
+
68
+ ## Quick Checklist
69
+
70
+ Use when writing or reviewing a trigger PR:
71
+
72
+ - [ ] Exactly one `.trigger` file per sObject
73
+ - [ ] Trigger body is a single handler call (no logic)
74
+ - [ ] Handler extends `TriggerHandler` (or FFLIB `fflib_SObjectDomain`)
75
+ - [ ] All seven events registered in trigger definition
76
+ - [ ] Every loop processes `List<SObject>`, not a single record
77
+ - [ ] Zero SOQL or DML inside any loop
78
+ - [ ] No hardcoded IDs anywhere
79
+ - [ ] Recursion guard present (prefer `Set<Id>` pattern)
80
+ - [ ] Bypass mechanism available
81
+ - [ ] No `Trigger.new` mutation in after-trigger methods
82
+ - [ ] No synchronous callouts
83
+
84
+ ---
85
+
86
+ ## Related
87
+
88
+ - **Skill**: `sf-trigger-frameworks` -- Framework patterns, base class code, migration guide
89
+ - **Reference**: @../_reference/TRIGGER_PATTERNS.md -- Order of execution, context variables, framework comparison
90
+ - **Agent**: `sf-architect` -- Interactive trigger design guidance