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,138 @@
1
+ # Scratch Org Setup Example
2
+
3
+ Complete scratch org configuration with definition file, setup scripts, and sample data.
4
+
5
+ ## Structure
6
+
7
+ ```text
8
+ config/
9
+ project-scratch-def.json # Scratch org definition
10
+ scripts/
11
+ setup-scratch-org.sh # Automated setup script
12
+ data/
13
+ sample-accounts.json # Sample data for development
14
+ sfdx-project.json # Project configuration
15
+ ```
16
+
17
+ ## Scratch Org Definition
18
+
19
+ ```json
20
+ {
21
+ "orgName": "SCC Dev Scratch Org",
22
+ "edition": "Developer",
23
+ "features": [
24
+ "EnableSetPasswordInApi",
25
+ "Communities",
26
+ "ServiceCloud",
27
+ "LightningSalesConsole"
28
+ ],
29
+ "settings": {
30
+ "lightningExperienceSettings": {
31
+ "enableS1DesktopEnabled": true
32
+ },
33
+ "securitySettings": {
34
+ "passwordPolicies": {
35
+ "enableSetPasswordInApi": true
36
+ }
37
+ },
38
+ "mobileSettings": {
39
+ "enableS1EncryptedStoragePref2": false
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Project Configuration (sfdx-project.json)
46
+
47
+ ```json
48
+ {
49
+ "packageDirectories": [
50
+ {
51
+ "path": "force-app",
52
+ "default": true
53
+ }
54
+ ],
55
+ "namespace": "",
56
+ "sfdcLoginUrl": "https://login.salesforce.com",
57
+ "sourceApiVersion": "66.0"
58
+ }
59
+ ```
60
+
61
+ ## Setup Script
62
+
63
+ ```bash
64
+ #!/bin/bash
65
+ set -e
66
+
67
+ ORG_ALIAS=${1:-dev-scratch}
68
+ DURATION=${2:-7}
69
+
70
+ echo "Creating scratch org: $ORG_ALIAS (${DURATION} days)"
71
+ sf org create scratch \
72
+ --definition-file config/project-scratch-def.json \
73
+ --alias "$ORG_ALIAS" \
74
+ --duration-days "$DURATION" \
75
+ --set-default \
76
+ --wait 15
77
+
78
+ echo "Deploying source..."
79
+ sf project deploy start --target-org "$ORG_ALIAS"
80
+
81
+ echo "Assigning permission sets..."
82
+ sf org assign permset --name SCC_Admin --target-org "$ORG_ALIAS" || true
83
+
84
+ echo "Importing sample data..."
85
+ sf data import tree --files data/sample-accounts.json --target-org "$ORG_ALIAS" || true
86
+
87
+ echo "Running tests..."
88
+ sf apex run test --test-level RunLocalTests --target-org "$ORG_ALIAS" --result-format human
89
+
90
+ echo "Opening org..."
91
+ sf org open --target-org "$ORG_ALIAS"
92
+
93
+ echo "Scratch org $ORG_ALIAS is ready!"
94
+ ```
95
+
96
+ ## Sample Data
97
+
98
+ ```json
99
+ {
100
+ "records": [
101
+ {
102
+ "attributes": { "type": "Account", "referenceId": "AccRef1" },
103
+ "Name": "Acme Corporation",
104
+ "Industry": "Technology",
105
+ "AnnualRevenue": 5000000,
106
+ "NumberOfEmployees": 250
107
+ },
108
+ {
109
+ "attributes": { "type": "Account", "referenceId": "AccRef2" },
110
+ "Name": "Global Industries",
111
+ "Industry": "Manufacturing",
112
+ "AnnualRevenue": 12000000,
113
+ "NumberOfEmployees": 1500
114
+ }
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ## Lifecycle Commands
120
+
121
+ ```bash
122
+ # Create
123
+ sf org create scratch -f config/project-scratch-def.json -a my-scratch -d 7
124
+
125
+ # Develop
126
+ sf project deploy start # Push source
127
+ sf project retrieve start # Pull changes from org
128
+
129
+ # Test
130
+ sf apex run test --test-level RunLocalTests
131
+
132
+ # Inspect
133
+ sf org display # Show org details
134
+ sf org open # Open in browser
135
+
136
+ # Cleanup
137
+ sf org delete scratch -o my-scratch --no-prompt
138
+ ```
@@ -0,0 +1,244 @@
1
+ # Security Audit Walkthrough
2
+
3
+ Step-by-step security audit for Salesforce Apex code covering CRUD/FLS, SOQL injection, sharing model, and static analysis. Patterns verified for API version 66.0 (Spring '26).
4
+
5
+ ## When to Use This Pattern
6
+
7
+ - Before deploying Apex code to production
8
+ - During security reviews or AppExchange security submissions
9
+ - When refactoring legacy code that bypasses security controls
10
+ - After a Salesforce security health check flags issues
11
+
12
+ ## CRUD/FLS Enforcement
13
+
14
+ ### Before (Insecure)
15
+
16
+ ```apex
17
+ // BAD: No CRUD or FLS checks — any user can read/update regardless of permissions
18
+ public class AccountService {
19
+ public static List<Account> getAccounts() {
20
+ return [SELECT Id, Name, AnnualRevenue, Phone FROM Account];
21
+ }
22
+
23
+ public static void updateRevenue(Id accountId, Decimal newRevenue) {
24
+ Account acc = new Account(Id = accountId, AnnualRevenue = newRevenue);
25
+ update acc;
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### After (Secure — WITH SECURITY_ENFORCED)
31
+
32
+ ```apex
33
+ // GOOD: CRUD/FLS enforced at the query level
34
+ public with sharing class AccountService {
35
+ public static List<Account> getAccounts() {
36
+ return [
37
+ SELECT Id, Name, AnnualRevenue, Phone
38
+ FROM Account
39
+ WITH SECURITY_ENFORCED
40
+ ];
41
+ }
42
+
43
+ public static void updateRevenue(Id accountId, Decimal newRevenue) {
44
+ // Check field-level access before DML
45
+ if (!Schema.sObjectType.Account.fields.AnnualRevenue.isUpdateable()) {
46
+ throw new SecurityException('Insufficient access to update AnnualRevenue');
47
+ }
48
+
49
+ Account acc = new Account(Id = accountId, AnnualRevenue = newRevenue);
50
+ update acc;
51
+ }
52
+
53
+ public class SecurityException extends Exception {}
54
+ }
55
+ ```
56
+
57
+ ### After (Secure — stripInaccessible)
58
+
59
+ ```apex
60
+ // GOOD: stripInaccessible silently removes inaccessible fields instead of throwing
61
+ public with sharing class AccountService {
62
+ public static List<Account> getAccounts() {
63
+ List<Account> accounts = [SELECT Id, Name, AnnualRevenue, Phone FROM Account];
64
+ SObjectAccessDecision decision = Security.stripInaccessible(AccessType.READABLE, accounts);
65
+ return (List<Account>) decision.getRecords();
66
+ }
67
+
68
+ public static void updateRevenue(Id accountId, Decimal newRevenue) {
69
+ List<Account> toUpdate = new List<Account>{
70
+ new Account(Id = accountId, AnnualRevenue = newRevenue)
71
+ };
72
+ SObjectAccessDecision decision = Security.stripInaccessible(AccessType.UPDATABLE, toUpdate);
73
+ update decision.getRecords();
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## SOQL Injection Prevention
79
+
80
+ ### Before (Vulnerable)
81
+
82
+ ```apex
83
+ // BAD: User input concatenated directly into query string
84
+ public class AccountSearch {
85
+ @AuraEnabled
86
+ public static List<Account> search(String searchTerm) {
87
+ String query = 'SELECT Id, Name FROM Account WHERE Name LIKE \'%' + searchTerm + '%\'';
88
+ return Database.query(query);
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### After (Safe — Bind Variables)
94
+
95
+ ```apex
96
+ // GOOD: Bind variable prevents injection
97
+ public with sharing class AccountSearch {
98
+ @AuraEnabled(cacheable=true)
99
+ public static List<Account> search(String searchTerm) {
100
+ String safeTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
101
+ return [
102
+ SELECT Id, Name
103
+ FROM Account
104
+ WHERE Name LIKE :safeTerm
105
+ WITH SECURITY_ENFORCED
106
+ LIMIT 50
107
+ ];
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### After (Safe — Dynamic Query with Escaping)
113
+
114
+ ```apex
115
+ // GOOD: When dynamic SOQL is unavoidable, escape input and enforce CRUD/FLS
116
+ public with sharing class AccountSearch {
117
+ @AuraEnabled(cacheable=true)
118
+ public static List<Account> search(String searchTerm, String sortField) {
119
+ // Whitelist allowed sort fields
120
+ Set<String> allowedSortFields = new Set<String>{ 'Name', 'CreatedDate', 'AnnualRevenue' };
121
+ if (!allowedSortFields.contains(sortField)) {
122
+ sortField = 'Name';
123
+ }
124
+
125
+ String safeTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
126
+ String query = 'SELECT Id, Name FROM Account'
127
+ + ' WHERE Name LIKE :safeTerm'
128
+ + ' WITH SECURITY_ENFORCED'
129
+ + ' ORDER BY ' + sortField
130
+ + ' LIMIT 50';
131
+ return Database.query(query);
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Sharing Model Review
137
+
138
+ ```apex
139
+ // with sharing — enforces the running user's sharing rules (default for most classes)
140
+ public with sharing class OpportunityService {
141
+ public List<Opportunity> getMyOpportunities() {
142
+ return [SELECT Id, Name, Amount FROM Opportunity WITH SECURITY_ENFORCED];
143
+ }
144
+ }
145
+
146
+ // without sharing — intentional escalation (document WHY)
147
+ // Use case: Background process that needs org-wide visibility
148
+ public without sharing class OpportunityRollupService {
149
+ /**
150
+ * Runs in system context because rollup calculations require
151
+ * access to all child records regardless of the triggering user's
152
+ * sharing rules. Called only from a trusted trigger handler.
153
+ */
154
+ public static void recalculateRollups(Set<Id> accountIds) {
155
+ // System-level aggregation
156
+ List<AggregateResult> results = [
157
+ SELECT AccountId, SUM(Amount) totalAmount
158
+ FROM Opportunity
159
+ WHERE AccountId IN :accountIds AND IsClosed = true AND IsWon = true
160
+ GROUP BY AccountId
161
+ ];
162
+ // ... update accounts with rollup values
163
+ }
164
+ }
165
+
166
+ // inherited sharing — inherits context from the caller
167
+ // Use case: Utility classes that should respect whatever context invokes them
168
+ public inherited sharing class QueryHelper {
169
+ public static List<SObject> queryWithLimit(String objectName, Integer recordLimit) {
170
+ String safeObject = String.escapeSingleQuotes(objectName);
171
+ return Database.query(
172
+ 'SELECT Id, Name FROM ' + safeObject + ' WITH SECURITY_ENFORCED LIMIT :recordLimit'
173
+ );
174
+ }
175
+ }
176
+ ```
177
+
178
+ ## Running SFDX Scanner
179
+
180
+ ```bash
181
+ # Install the scanner plugin (one-time setup)
182
+ sf plugins install @salesforce/sfdx-scanner
183
+
184
+ # Scan all Apex classes for security issues
185
+ sf scanner run --target "force-app/main/default/classes/**/*.cls" \
186
+ --category "Security" \
187
+ --format table
188
+
189
+ # Scan with PMD rules and generate a report
190
+ sf scanner run --target "force-app/main/default/classes/**/*.cls" \
191
+ --engine pmd \
192
+ --format csv \
193
+ --outfile scanner-results.csv
194
+
195
+ # Scan for specific security rules
196
+ sf scanner run --target "force-app/main/default/classes/AccountService.cls" \
197
+ --category "Security,Best Practices" \
198
+ --format table \
199
+ --severity-threshold 2
200
+
201
+ # Run the full AppExchange-style security review
202
+ sf scanner run --target "force-app/" \
203
+ --category "Security" \
204
+ --engine "pmd,retire-js" \
205
+ --format html \
206
+ --outfile security-report.html
207
+ ```
208
+
209
+ ## Security Checklist
210
+
211
+ | Check | Status | How to Verify |
212
+ |-------|--------|---------------|
213
+ | All classes use `with sharing` or document why not | | Search for `without sharing` and verify justification |
214
+ | SOQL uses `WITH SECURITY_ENFORCED` or `stripInaccessible` | | Search for `Database.query` and `[SELECT` without enforcement |
215
+ | No raw user input in SOQL/SOSL strings | | Search for string concatenation in queries |
216
+ | DML operations check field-level access | | Check `isCreateable()`, `isUpdateable()`, `stripInaccessible` |
217
+ | `@AuraEnabled` methods validate input parameters | | Review all `@AuraEnabled` methods |
218
+ | No hardcoded credentials or secrets | | Search for passwords, tokens, API keys in source |
219
+ | Named Credentials used for external callouts | | Search for `HttpRequest` and verify endpoint source |
220
+ | CSRF tokens on Visualforce pages | | Ensure forms are not using `GET` actions with state changes |
221
+ | Guest user profiles have minimal permissions | | Review site guest user profile in Setup |
222
+ | Sensitive data not logged or exposed in debug | | Search for `System.debug` with sensitive field names |
223
+
224
+ ## Key Principles
225
+
226
+ - Default to `with sharing` on every class; only use `without sharing` with documented justification
227
+ - Prefer `WITH SECURITY_ENFORCED` for reads and `stripInaccessible` when you need graceful degradation
228
+ - Never concatenate user input into SOQL/SOSL; use bind variables or `String.escapeSingleQuotes`
229
+ - Whitelist dynamic field/object names rather than relying solely on escaping
230
+ - Run SFDX Scanner in CI to catch regressions before deployment
231
+
232
+ ## Common Pitfalls
233
+
234
+ - Assuming `with sharing` enforces FLS (it only enforces record-level sharing rules, not field access)
235
+ - Using `String.escapeSingleQuotes` alone without also whitelisting dynamic identifiers
236
+ - Forgetting to add `WITH SECURITY_ENFORCED` to SOQL inside batch/schedulable classes
237
+ - Marking utility classes as `without sharing` out of convenience
238
+ - Not testing with a non-admin user profile to catch missing permissions
239
+
240
+ ## SCC Skills
241
+
242
+ - `/sf-security` -- run a comprehensive security audit on your codebase
243
+ - `/sf-apex-best-practices` -- review Apex code including security best practices
244
+ - `/sf-governor-limits` -- check for governor limit issues (overlaps with security for SOQL)
@@ -0,0 +1,314 @@
1
+ # Migrating Visualforce to LWC
2
+
3
+ Side-by-side examples showing how to convert a Visualforce page with an Apex controller to a Lightning Web Component using modern patterns.
4
+
5
+ ## When to Use This Pattern
6
+
7
+ - Modernizing legacy Visualforce pages to Lightning Experience
8
+ - Converting controller-based pages to wire-service LWC components
9
+ - Replacing `apex:form` with `lightning-record-edit-form`
10
+ - Migrating page navigation from `PageReference` to `NavigationMixin`
11
+ - Preparing for retirement of Visualforce in Lightning-only orgs
12
+
13
+ ## Original Visualforce Page
14
+
15
+ ### Visualforce Markup
16
+
17
+ ```html
18
+ <!-- AccountEditor.page -->
19
+ <apex:page controller="AccountEditorController" lightningStylesheets="true">
20
+ <apex:form>
21
+ <apex:pageBlock title="Edit Account" mode="edit">
22
+ <apex:pageMessages />
23
+
24
+ <apex:pageBlockButtons>
25
+ <apex:commandButton action="{!save}" value="Save" />
26
+ <apex:commandButton action="{!cancel}" value="Cancel" immediate="true" />
27
+ </apex:pageBlockButtons>
28
+
29
+ <apex:pageBlockSection columns="2">
30
+ <apex:inputField value="{!account.Name}" required="true" />
31
+ <apex:inputField value="{!account.Industry}" />
32
+ <apex:inputField value="{!account.Phone}" />
33
+ <apex:inputField value="{!account.Website}" />
34
+ <apex:inputField value="{!account.AnnualRevenue}" />
35
+ <apex:inputField value="{!account.Description}" />
36
+ </apex:pageBlockSection>
37
+
38
+ <apex:pageBlockSection title="Related Contacts" columns="1">
39
+ <apex:pageBlockTable value="{!contacts}" var="c">
40
+ <apex:column value="{!c.Name}" />
41
+ <apex:column value="{!c.Email}" />
42
+ <apex:column value="{!c.Phone}" />
43
+ </apex:pageBlockTable>
44
+ </apex:pageBlockSection>
45
+ </apex:pageBlock>
46
+ </apex:form>
47
+ </apex:page>
48
+ ```
49
+
50
+ ### Visualforce Controller
51
+
52
+ ```apex
53
+ public with sharing class AccountEditorController {
54
+ public Account account { get; set; }
55
+ public List<Contact> contacts { get; set; }
56
+
57
+ public AccountEditorController() {
58
+ Id accountId = ApexPages.currentPage().getParameters().get('id');
59
+ if (accountId != null) {
60
+ account = [
61
+ SELECT Id, Name, Industry, Phone, Website, AnnualRevenue, Description
62
+ FROM Account WHERE Id = :accountId
63
+ ];
64
+ contacts = [
65
+ SELECT Id, Name, Email, Phone
66
+ FROM Contact WHERE AccountId = :accountId
67
+ ORDER BY Name
68
+ ];
69
+ } else {
70
+ account = new Account();
71
+ contacts = new List<Contact>();
72
+ }
73
+ }
74
+
75
+ public PageReference save() {
76
+ try {
77
+ upsert account;
78
+ return new PageReference('/' + account.Id);
79
+ } catch (DmlException e) {
80
+ ApexPages.addMessages(e);
81
+ return null;
82
+ }
83
+ }
84
+
85
+ public PageReference cancel() {
86
+ if (account.Id != null) {
87
+ return new PageReference('/' + account.Id);
88
+ }
89
+ return new PageReference('/001'); // Account list view
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Equivalent LWC Component
95
+
96
+ ### LWC HTML
97
+
98
+ ```html
99
+ <!-- accountEditor.html -->
100
+ <template>
101
+ <lightning-card title="Edit Account" icon-name="standard:account">
102
+ <!-- Record Edit Form replaces apex:form + apex:inputField -->
103
+ <lightning-record-edit-form
104
+ record-id={recordId}
105
+ object-api-name="Account"
106
+ onsuccess={handleSuccess}
107
+ onerror={handleError}>
108
+
109
+ <lightning-messages></lightning-messages>
110
+
111
+ <div class="slds-grid slds-wrap slds-gutters">
112
+ <div class="slds-col slds-size_1-of-2 slds-p-around_small">
113
+ <lightning-input-field field-name="Name" required></lightning-input-field>
114
+ </div>
115
+ <div class="slds-col slds-size_1-of-2 slds-p-around_small">
116
+ <lightning-input-field field-name="Industry"></lightning-input-field>
117
+ </div>
118
+ <div class="slds-col slds-size_1-of-2 slds-p-around_small">
119
+ <lightning-input-field field-name="Phone"></lightning-input-field>
120
+ </div>
121
+ <div class="slds-col slds-size_1-of-2 slds-p-around_small">
122
+ <lightning-input-field field-name="Website"></lightning-input-field>
123
+ </div>
124
+ <div class="slds-col slds-size_1-of-2 slds-p-around_small">
125
+ <lightning-input-field field-name="AnnualRevenue"></lightning-input-field>
126
+ </div>
127
+ <div class="slds-col slds-size_1-of-1 slds-p-around_small">
128
+ <lightning-input-field field-name="Description"></lightning-input-field>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="slds-m-top_medium slds-p-around_small">
133
+ <lightning-button variant="brand" type="submit" label="Save"></lightning-button>
134
+ <lightning-button label="Cancel" onclick={handleCancel} class="slds-m-left_x-small"></lightning-button>
135
+ </div>
136
+ </lightning-record-edit-form>
137
+
138
+ <!-- Related Contacts section -->
139
+ <template if:true={contacts.data}>
140
+ <div class="slds-p-around_small slds-m-top_medium">
141
+ <h2 class="slds-text-heading_small slds-m-bottom_small">Related Contacts</h2>
142
+ <lightning-datatable
143
+ key-field="Id"
144
+ data={contacts.data}
145
+ columns={contactColumns}
146
+ hide-checkbox-column>
147
+ </lightning-datatable>
148
+ </div>
149
+ </template>
150
+ </lightning-card>
151
+ </template>
152
+ ```
153
+
154
+ ### LWC JavaScript
155
+
156
+ ```javascript
157
+ // accountEditor.js
158
+ import { LightningElement, api, wire } from 'lwc';
159
+ import { NavigationMixin } from 'lightning/navigation';
160
+ import { ShowToastEvent } from 'lightning/platformShowToastEvent';
161
+ import getRelatedContacts from '@salesforce/apex/AccountEditorLwcController.getRelatedContacts';
162
+
163
+ const CONTACT_COLUMNS = [
164
+ { label: 'Name', fieldName: 'Name', type: 'text' },
165
+ { label: 'Email', fieldName: 'Email', type: 'email' },
166
+ { label: 'Phone', fieldName: 'Phone', type: 'phone' }
167
+ ];
168
+
169
+ export default class AccountEditor extends NavigationMixin(LightningElement) {
170
+ @api recordId;
171
+ contactColumns = CONTACT_COLUMNS;
172
+
173
+ @wire(getRelatedContacts, { accountId: '$recordId' })
174
+ contacts;
175
+
176
+ handleSuccess(event) {
177
+ this.dispatchEvent(
178
+ new ShowToastEvent({
179
+ title: 'Success',
180
+ message: 'Account saved successfully',
181
+ variant: 'success'
182
+ })
183
+ );
184
+
185
+ // Navigate to the record page (replaces PageReference)
186
+ this[NavigationMixin.Navigate]({
187
+ type: 'standard__recordPage',
188
+ attributes: {
189
+ recordId: event.detail.id,
190
+ objectApiName: 'Account',
191
+ actionName: 'view'
192
+ }
193
+ });
194
+ }
195
+
196
+ handleError(event) {
197
+ this.dispatchEvent(
198
+ new ShowToastEvent({
199
+ title: 'Error',
200
+ message: event.detail.message,
201
+ variant: 'error'
202
+ })
203
+ );
204
+ }
205
+
206
+ handleCancel() {
207
+ if (this.recordId) {
208
+ // Navigate back to the record (replaces PageReference)
209
+ this[NavigationMixin.Navigate]({
210
+ type: 'standard__recordPage',
211
+ attributes: {
212
+ recordId: this.recordId,
213
+ objectApiName: 'Account',
214
+ actionName: 'view'
215
+ }
216
+ });
217
+ } else {
218
+ // Navigate to the Account list view (replaces PageReference('/001'))
219
+ this[NavigationMixin.Navigate]({
220
+ type: 'standard__objectPage',
221
+ attributes: {
222
+ objectApiName: 'Account',
223
+ actionName: 'list'
224
+ },
225
+ state: {
226
+ filterName: 'Recent'
227
+ }
228
+ });
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### LWC Metadata
235
+
236
+ ```xml
237
+ <!-- accountEditor.js-meta.xml -->
238
+ <?xml version="1.0" encoding="UTF-8"?>
239
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
240
+ <apiVersion>66.0</apiVersion>
241
+ <isExposed>true</isExposed>
242
+ <targets>
243
+ <target>lightning__RecordPage</target>
244
+ <target>lightning__AppPage</target>
245
+ </targets>
246
+ <targetConfigs>
247
+ <targetConfig targets="lightning__RecordPage">
248
+ <objects>
249
+ <object>Account</object>
250
+ </objects>
251
+ </targetConfig>
252
+ </targetConfigs>
253
+ </LightningComponentBundle>
254
+ ```
255
+
256
+ ### Apex Controller (Simplified)
257
+
258
+ ```apex
259
+ public with sharing class AccountEditorLwcController {
260
+ @AuraEnabled(cacheable=true)
261
+ public static List<Contact> getRelatedContacts(Id accountId) {
262
+ return [
263
+ SELECT Id, Name, Email, Phone
264
+ FROM Contact
265
+ WHERE AccountId = :accountId
266
+ WITH SECURITY_ENFORCED
267
+ ORDER BY Name
268
+ LIMIT 100
269
+ ];
270
+ }
271
+ }
272
+ ```
273
+
274
+ ## Migration Mapping Reference
275
+
276
+ | Visualforce | LWC Equivalent |
277
+ |-------------|----------------|
278
+ | `apex:page` | LWC component with `lightning-card` |
279
+ | `apex:form` | `lightning-record-edit-form` |
280
+ | `apex:inputField` | `lightning-input-field` |
281
+ | `apex:commandButton action="{!save}"` | `lightning-button type="submit"` |
282
+ | `apex:pageMessages` | `lightning-messages` |
283
+ | `apex:pageBlockTable` | `lightning-datatable` |
284
+ | `ApexPages.addMessages(e)` | `ShowToastEvent` |
285
+ | `new PageReference('/id')` | `NavigationMixin.Navigate` |
286
+ | Controller constructor query | `@wire` with `@AuraEnabled(cacheable=true)` |
287
+ | `ApexPages.currentPage().getParameters().get('id')` | `@api recordId` |
288
+ | `apex:outputPanel rendered="{!condition}"` | `template if:true={condition}` |
289
+ | `apex:repeat` | `template for:each={items}` |
290
+ | `apex:actionFunction` | Imperative Apex call |
291
+
292
+ ## Key Principles
293
+
294
+ - Use `lightning-record-edit-form` instead of custom save logic when editing standard/custom object fields
295
+ - Replace all `PageReference` navigation with `NavigationMixin` for Lightning Experience compatibility
296
+ - Use `@wire` for read operations and imperative calls for mutations
297
+ - Move field-level rendering logic from controller to reactive properties in JS
298
+ - LWC automatically handles CRUD/FLS when using `lightning-record-edit-form`
299
+
300
+ ## Common Pitfalls
301
+
302
+ - Forgetting to extend `NavigationMixin(LightningElement)` before calling Navigate
303
+ - Using imperative Apex for cacheable reads instead of `@wire` (loses caching and reactivity)
304
+ - Not adding `WITH SECURITY_ENFORCED` in the LWC controller Apex methods
305
+ - Trying to replicate the exact VF layout instead of adopting SLDS grid patterns
306
+ - Hardcoding record type IDs or key prefixes (like `/001`) that were common in VF pages
307
+ - Missing the `isExposed` and `targets` configuration in the meta XML
308
+
309
+ ## SCC Skills
310
+
311
+ - `/sf-visualforce-development` -- audit a Visualforce page for migration readiness
312
+ - `/sf-lwc-development` -- review the migrated LWC component for best practices
313
+ - `/sf-apex-best-practices` -- review the simplified Apex controller
314
+ - `/sf-security` -- verify security enforcement in the new component