acek-skills 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -149,7 +149,9 @@ metadata is touched: runs Discovery (project scan + choice-based clarifying ques
149
149
  text), proposes Decisions with trade-offs for data model / automation layer / integration pattern
150
150
  / security model / Permission Set strategy, then breaks approved work into tasks dispatched to the
151
151
  right skill (`sf-admin`, `sf-dev`, `sf-testing`, `sf-devops`, `sf-security-review`,
152
- `sf-data-migration`). Plans are saved to `instructions/architecture/` and are resumable across
152
+ `sf-data-migration`) always including a `sf-security-review` task when the plan touches a
153
+ PII-bearing object/field, an integration, or a sharing/OWD change. Plans are saved to
154
+ `instructions/architecture/` and are resumable across
153
155
  sessions without re-scanning the project or re-reading chat history. Complements — does not
154
156
  replace — `sf-ba` (PRDs) and `sf-ideation` (open-ended brainstorming), and can consume either as
155
157
  input.
@@ -177,7 +179,10 @@ data management (import/export/data loader) — anything declarative, no code in
177
179
 
178
180
  Writes PRDs, user stories, feature specs, and functional requirements — structured Markdown docs
179
181
  intended to hand off to an admin or developer. Triggers on "write a PRD", "document requirements",
180
- "write user stories", or when a business problem needs to become executable documentation.
182
+ "write user stories", or when a business problem needs to become executable documentation. Its
183
+ Admin Spec / Dev Spec templates are for work that will **not** go through `sf-architect`; for
184
+ open-ended brainstorming on a specific existing component instead of net-new requirements, it
185
+ hands off to `sf-ideation`.
181
186
 
182
187
  </details>
183
188
 
@@ -230,8 +235,9 @@ opportunities.
230
235
 
231
236
  CRUD/FLS checks, sharing model review, `with sharing` enforcement, SOQL injection, XSS in LWC,
232
237
  hardcoded credential detection, and a production sign-off checklist. Also covers Shield,
233
- encryption, audit trail, and a dedicated PII & Data Privacy section — field classification,
234
- sandbox data masking, and UU PDP-aware review practices.
238
+ encryption, audit trail, a dedicated PII & Data Privacy section — field classification, sandbox
239
+ data masking, and UU PDP-aware review practices — plus Guest User/Experience Cloud access review
240
+ and Connected App/External Client App (OAuth) review checklists.
235
241
 
236
242
  </details>
237
243
 
@@ -239,8 +245,9 @@ sandbox data masking, and UU PDP-aware review practices.
239
245
  <summary><strong>sf-testing</strong> — Apex test strategy</summary>
240
246
 
241
247
  Test classes, `TestDataFactory` patterns, mocking HTTP callouts and platform events, test
242
- assertions, coverage reports, and debugging org-only test failures. Enforces the 85% coverage
243
- floor and `AuraHandledException` assertion conventions.
248
+ assertions, coverage reports, debugging org-only test failures, and LWC Jest unit tests
249
+ (`@salesforce/sfdx-lwc-jest`). Enforces the 85% Apex coverage floor, `AuraHandledException`
250
+ assertion conventions, and a 3-scenario minimum (happy/empty/interaction) for every LWC component.
244
251
 
245
252
  </details>
246
253
 
@@ -310,7 +317,7 @@ responds, and doesn't touch any org, sandbox, or production data on its own.
310
317
  Yes — deselect any skill in the interactive wizard, or use `install <name>` for a single one.
311
318
 
312
319
  **Can I re-run install to update after a new version is published?**
313
- Yes, `npx` always resolves the latest version unless you pin one (`npx acek-skills@1.2.0 install`).
320
+ Yes, `npx` always resolves the latest version unless you pin one (`npx acek-skills@1.3.0 install`).
314
321
  Re-running overwrites previously installed files for the skills/targets you select.
315
322
 
316
323
  **Why do only Claude Code skills auto-trigger?**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acek-skills",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Installable Claude Code skills for Salesforce development (admin, dev, BA, testing, devops, security review, data migration, ideation)",
5
5
  "bin": {
6
6
  "acek-skills": "bin/cli.js"
@@ -14,7 +14,10 @@ description: >
14
14
 
15
15
  ## Environment Context
16
16
 
17
- - API Version: **67.0** (Summer '26)
17
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
18
+ project's `sfdx-project.json` when scaffolded via SF CLI. When touching an **existing**
19
+ class/component/metadata item, check its `apiVersion`: below **62.0** → bump to **67.0** as part
20
+ of the change; 62.0+ → leave as-is unless the task needs 67.0+ behavior specifically.
18
21
  - Org Type: Enterprise (single org: sandbox + production)
19
22
  - Tooling: **SF CLI** for metadata retrieval; most admin tasks done declaratively in Setup UI
20
23
  - Any metadata changes intended for production must go through the DevOps deployment process
@@ -141,12 +141,19 @@ Nothing is built yet. This phase only produces decisions with rationale.
141
141
  **Touches** list (exact file(s)/component(s) it will create or modify), a dependency list, and
142
142
  status `P` (Pending). `Touches` is what Phase 4 uses to decide which tasks are safe to run in
143
143
  parallel — be specific and accurate here, not vague.
144
- 3. **If any task's Owner Skill is `sf-devops` or `sf-data-migration`, resolve org aliases before
144
+ 3. **Always add a `sf-security-review` task** when the plan creates or modifies access to any
145
+ PII-bearing object/field (see `sf-security-review`'s PII & Data Privacy categories), introduces
146
+ a new integration/callout, or changes the sharing model — never skip it because the feature
147
+ "seems simple," the same standard already applied to Permission Set Strategy above. Depend it
148
+ on the relevant `sf-admin`/`sf-dev` tasks and put it before the `sf-devops` deploy task — its
149
+ sign-off is what fills the CR's "PII Impact" section. Skip only when Project Context confirms
150
+ none of those three surfaces are touched.
151
+ 4. **If any task's Owner Skill is `sf-devops` or `sf-data-migration`, resolve org aliases before
145
152
  finalizing** — see [Org Alias Resolution](#org-alias-resolution) below. Skip this step
146
153
  entirely if no task touches those two skills; don't ask about aliases a plan doesn't need.
147
- 4. Present the plan summary to the user and ask for explicit approval — options: **Approve**,
154
+ 5. Present the plan summary to the user and ask for explicit approval — options: **Approve**,
148
155
  **Revise a section**, **Cancel**. Do not proceed on silence or an ambiguous reply.
149
- 5. Only after explicit approval: write the plan file to
156
+ 6. Only after explicit approval: write the plan file to
150
157
  `instructions/architecture/YYYYMMDD-<feature-slug>-plan.md`.
151
158
 
152
159
  This is the hard gate. Nothing before this point touches the org or the repo's buildable code.
@@ -185,15 +192,22 @@ dispatched to parallel subagents; tasks that depend on each other still run in s
185
192
  1. **Compute the ready set.** A task is ready when every task in its `Depends On` list is `D`.
186
193
  2. **Split the ready set into parallel-safe groups.** Two ready tasks can run in the same batch
187
194
  only if their `Touches` values don't overlap (no shared file/component). If a task's `Touches`
188
- is unclear or ambiguous, treat it as not parallel-safe — run it alone.
195
+ is unclear or ambiguous, treat it as not parallel-safe — run it alone. **This applies
196
+ regardless of Owner Skill** — two tasks both owned by `sf-dev` (e.g. two independent LWC
197
+ components, or two unrelated Apex classes) are just as eligible for the same parallel batch as
198
+ two tasks owned by different skills, as long as their `Touches` don't overlap. Parallel
199
+ eligibility is decided by `Touches`, never by which skill owns the task.
189
200
  3. **Dispatch the batch:**
190
201
  - **Batch of 1** → run directly in the main thread, exactly as before (no subagent overhead
191
202
  for solo work).
192
203
  - **Batch of 2+** → dispatch each task to a separate subagent via the Task tool, in the same
193
- turn. Each subagent prompt must state explicitly: the task description, the exact file(s) in
194
- `Touches`, and "follow `sf-[owner skill]`'s conventions exactly as if that skill were invoked
195
- directly" never duplicate that skill's rules into the prompt, just name it and let it
196
- trigger in the subagent's own context.
204
+ turn. This holds even when two or more tasks in the batch share the same Owner Skill (e.g.
205
+ two `sf-dev` tasks) each still gets its own subagent, invoked independently; never route
206
+ two tasks through one subagent just because they'd trigger the same skill. Each subagent
207
+ prompt must state explicitly: the task description, the exact file(s) in `Touches`, and
208
+ "follow `sf-[owner skill]`'s conventions exactly as if that skill were invoked directly" —
209
+ never duplicate that skill's rules into the prompt, just name it and let it trigger in the
210
+ subagent's own context.
197
211
  - **Assign each subagent in a batch a random name from the pool below, unique within that
198
212
  batch** — used only as a tracking label in the orchestrator's own narration and Execution Log
199
213
  ("Dispatched to Sasha"), never written into code, commits, or file content.
@@ -313,9 +327,11 @@ Permission Set Group `[PSG Name]`
313
327
  **Status codes:** `P` Pending · `IP` In Progress · `B` Blocked · `D` Done
314
328
 
315
329
  **Touches** is what makes parallel dispatch safe: TASK-02, TASK-03, and TASK-04 all depend only on
316
- TASK-01 and touch different things — they're a valid parallel batch. TASK-05 depends on TASK-03
317
- and must wait for it. TASK-06 depends on both TASK-04 and TASK-05, so it waits for the slower of
318
- the two.
330
+ TASK-01 and touch different things — they're a valid parallel batch, dispatched as three separate
331
+ subagents. Note TASK-03 and TASK-04 share the same Owner Skill (`sf-dev`) but still run as two
332
+ independent subagents in that batch — parallel eligibility is decided by `Touches`, not by which
333
+ skill owns the task. TASK-05 depends on TASK-03 and must wait for it. TASK-06 depends on both
334
+ TASK-04 and TASK-05, so it waits for the slower of the two.
319
335
 
320
336
  ---
321
337
 
@@ -398,8 +414,14 @@ best if users need to review before submitting (D) Other — describe your own a
398
414
  - ✕ Skipping the Permission Set Strategy decision because the feature "seems simple" — access
399
415
  control is part of every feature that touches an object, field, Apex class, or LWC
400
416
  - ✕ Assuming a new Permission Set is needed without first scanning for an existing one that fits
417
+ - ✕ Omitting a `sf-security-review` task from Task Breakdown when the plan touches a PII-bearing
418
+ field/object, adds a new integration/callout, or changes the sharing model
401
419
  - ✕ Dispatching two tasks in parallel when their `Touches` overlap, or when either one's
402
420
  `Touches` is too vague to be sure it doesn't
421
+ - ✕ Merging two or more same-Owner-Skill tasks into a single subagent because they'd trigger the
422
+ same skill — each task still gets its own subagent when their `Touches` don't overlap
423
+ - ✕ Treating parallel-safe dispatch as only available across *different* Owner Skills — two tasks
424
+ under the same skill (e.g. two `sf-dev` tasks) are equally eligible when `Touches` don't overlap
403
425
  - ✕ Letting a subagent write directly to the plan file — only the orchestrator writes status/log
404
426
  updates, and only after the batch reports back
405
427
  - ✕ Reusing the same agent name for two subagents within the same batch
@@ -29,6 +29,14 @@ running `sf-architect` first, or right after — it consumes the PRD as input an
29
29
  technical execution plan. `sf-ba` decides WHAT to build; `sf-architect` decides HOW and in what
30
30
  order. Don't try to make the PRD carry both.
31
31
 
32
+ **Admin Spec and Dev Spec are for work that will NOT go through `sf-architect`** — e.g. handing
33
+ instructions to a human admin/dev directly, or documenting a declarative/code change after the
34
+ fact for the record. If the work is going to be executed via an `sf-architect` plan, skip writing
35
+ these specs: `sf-architect`'s Task Breakdown (with each task's Owner Skill and exact `Touches`)
36
+ already carries that execution detail, and a separately-written spec would just be a second copy
37
+ of the same decisions to keep in sync by hand. Write the PRD, hand it to `sf-architect`, and let
38
+ the plan carry the rest.
39
+
32
40
  ---
33
41
 
34
42
  ## Document Types
@@ -14,7 +14,10 @@ description: >
14
14
 
15
15
  ## Environment Context
16
16
 
17
- - API Version: **67.0** (Summer '26)
17
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
18
+ project's `sfdx-project.json` when scaffolded via SF CLI. When touching an **existing**
19
+ class/component/metadata item, check its `apiVersion`: below **62.0** → bump to **67.0** as part
20
+ of the change; 62.0+ → leave as-is unless the task needs 67.0+ behavior specifically.
18
21
  - Tooling: **SF CLI** (`sf data` commands) for dev/test volumes; Data Loader for production volumes
19
22
  - Always migrate to **sandbox first**, verify, then production
20
23
  - Single org: sandbox → production
@@ -13,7 +13,12 @@ description: >
13
13
 
14
14
  ## Environment Context
15
15
 
16
- - API Version: **67.0** (Summer '26)
16
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
17
+ project's `sfdx-project.json` when scaffolded via SF CLI (see [File
18
+ Creation](#-file-creation--always-via-sf-cli-never-manual) below — this is one more reason to
19
+ always generate, never hand-create). When touching an **existing** class/component, check its
20
+ `apiVersion`: below **62.0** → bump to **67.0** as part of the change; 62.0+ → leave as-is unless
21
+ the task needs 67.0+ behavior specifically.
17
22
  - Tooling: **SF CLI** (`sf` commands)
18
23
  - Apex LSP: **offline** — never use compile-time SObject type references that require org metadata
19
24
  - Version control: **GitHub** (branching: `main → staging → develop → feature/*`)
@@ -174,10 +179,11 @@ public static Result myMethod(String param) {
174
179
 
175
180
  | Type | Convention | Example |
176
181
  | ------------ | -------------------------- | ------------------------------------- |
177
- | Apex Class | PascalCase | `AccountHelper`, `OpportunityTrigger` |
178
- | Test Class | PascalCase + `Test` suffix | `AccountHelperTest` |
179
- | Custom Field | PascalCase\_\_c | `ProjectStatus__c` |
180
- | Variable | camelCase | `accountList`, `oppMap` |
182
+ | Apex Class | PascalCase | `AccountHelper`, `OpportunityService` |
183
+ | Test Class | PascalCase + `Test` suffix | `AccountHelperTest` |
184
+ | Apex Trigger | `<Object>Trigger`, one per object | `AccountTrigger`, `OpportunityTrigger` |
185
+ | Custom Field | PascalCase\_\_c | `ProjectStatus__c` |
186
+ | Variable | camelCase | `accountList`, `oppMap` |
181
187
 
182
188
  ### ApexDoc (required on all public methods)
183
189
 
@@ -194,34 +200,17 @@ public static List<Result__c> processRecord(Id recordId) { ... }
194
200
 
195
201
  ### Test Class Pattern
196
202
 
197
- ```apex
198
- @isTest
199
- private class AccountHelperTest {
200
- @TestSetup
201
- static void makeData() {
202
- // create test records once for all test methods
203
- }
204
-
205
- @isTest
206
- static void testPositivePath() {
207
- Test.startTest();
208
- // call method
209
- Test.stopTest();
210
- // assert
211
- }
203
+ Full test class structure, `TestDataFactory` usage, callout/platform-event mocking, and assertion
204
+ conventions are owned by `sf-testing` — follow that skill's pattern exactly rather than
205
+ improvising a competing one here. Two things worth remembering while writing the code being
206
+ tested:
212
207
 
213
- @isTest
214
- static void testNegativePath_insufficientPermission() {
215
- // test exception path
216
- try {
217
- AccountHelper.myMethod(null);
218
- Assert.fail('Expected exception not thrown');
219
- } catch (AuraHandledException e) {
220
- Assert.areEqual('Script-thrown exception', e.getMessage(), 'AuraHandledException message is masked in test context by design');
221
- }
222
- }
223
- }
224
- ```
208
+ - `AuraHandledException.getMessage()` always returns `'Script-thrown exception'` in test context
209
+ (Salesforce by design) — the test asserting your exception path is expected to check this exact
210
+ string, not your real error message
211
+ - Structure the method so `Test.startTest()` / `Test.stopTest()` around the call actually resets
212
+ governor limits and forces async paths (Queueable, `@future`, Platform Events) to run — don't
213
+ write logic that fights that boundary
225
214
 
226
215
  ---
227
216
 
@@ -442,4 +431,6 @@ sf apex run --target-org <alias> --file scripts/apex/myScript.apex
442
431
  - [ ] No `WITH SECURITY_ENFORCED` in SOQL — removed in API 67.0
443
432
  - [ ] All `@AuraEnabled` methods wrapped in try-catch → `AuraHandledException`
444
433
  - [ ] LWC uses SLDS 2 hooks (`var(--slds-g-*, fallback)`) — no hardcoded hex/px, no `.slds-*` overrides
434
+ - [ ] New/modified LWC has a Jest test file under `__tests__/` — see `sf-testing`'s LWC Jest
435
+ Testing section for the pattern; don't improvise a competing one
445
436
  - [ ] Version badge present _if the project has adopted that convention_ (not required by default)
@@ -14,7 +14,10 @@ description: >
14
14
 
15
15
  ## Environment Context
16
16
 
17
- - API Version: **67.0** (Summer '26)
17
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
18
+ project's `sfdx-project.json` when scaffolded via SF CLI. When touching an **existing**
19
+ class/component/metadata item, check its `apiVersion`: below **62.0** → bump to **67.0** as part
20
+ of the change; 62.0+ → leave as-is unless the task needs 67.0+ behavior specifically.
18
21
  - Branching: `main → staging → develop → feature/*`
19
22
  - Production org alias: **`{{PROD_ORG_ALIAS}}`**
20
23
  - Sandbox/dev org alias: **`{{DEV_ORG_ALIAS}}`**
@@ -64,6 +67,8 @@ Never skip steps. Never deploy to `{{PROD_ORG_ALIAS}}` without a CR.
64
67
  an LWC missing its `js-meta.xml` will fail to deploy or stay invisible in the org
65
68
  - [ ] No `@wire` adapter inside a loop
66
69
  - [ ] All public `@api` properties have JSDoc
70
+ - [ ] Jest test file exists under `__tests__/` covering at least happy path, empty state, and one
71
+ interaction/error path (see `sf-testing`'s LWC Jest Testing section) — no LWC ships untested
67
72
 
68
73
  > **Out of scope for this checklist:** CSS/styling conventions and design system compliance.
69
74
  > That belongs to whichever skill built the component — `sf-dev` (standard SLDS 2 styling) or
@@ -161,6 +161,13 @@ using the project's actual stack, referencing the anchor by name.
161
161
  - **Be opinionated** — don't just list options, recommend the best one and explain why.
162
162
  - **Think Salesforce seasons** — flag if an idea aligns with an upcoming Salesforce release
163
163
  feature that could reduce build effort.
164
+ - **Hand off, don't build.** This skill's job stops at generating and prioritizing ideas — it
165
+ never writes code, metadata, or a plan file itself, and its output is intentionally **not
166
+ persisted** to a file. For a **Big Bet** or any idea that needs a real architecture decision
167
+ (data model, Flow vs Apex, integration pattern, security model), hand off to `sf-architect` — its
168
+ plan file is where the idea becomes durable. For an idea that should become a formally scoped
169
+ feature with requirements and acceptance criteria first, hand off to `sf-ba` to write a PRD.
170
+ Don't try to make this skill's output do either job.
164
171
 
165
172
  ---
166
173
 
@@ -175,3 +182,4 @@ using the project's actual stack, referencing the anchor by name.
175
182
  | "apa next step dari PRD [name]?" | Anchor = that PRD (confirm it's closed/approved). Run Extension Questions |
176
183
  | "prioritas enhancement mana yang paling worth it?" | Requires a prior anchored ideation output in this conversation to rank — if none exists, ask which anchor to run first |
177
184
  | "ada yang bisa diimprove dari sisi performance?" (no name given) | No anchor — scan project, ask which component to focus the Performance lens on |
185
+ | "let's build idea #[N]" / "jalankan ide nomor [N]" | Hand off — don't execute here. Route to `sf-architect` if it needs a technical decision, or `sf-ba` if it needs a PRD first |
@@ -14,7 +14,12 @@ description: >
14
14
 
15
15
  ## Environment Context
16
16
 
17
- - API Version: **67.0** (Summer '26)
17
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
18
+ project's `sfdx-project.json` when scaffolded via SF CLI. When reviewing an **existing**
19
+ class/component, check its `apiVersion`: below **62.0** → recommend bumping to **67.0** as part
20
+ of the change; 62.0+ → confirm whether the [API 67.0+ Default Security
21
+ Model](#-api-670-default-security-model-summer-26) applies before deciding whether a missing
22
+ manual check is a real gap.
18
23
  - Apex LSP: **offline** — use `Schema.getGlobalDescribe()` pattern (not compile-time SObject reference)
19
24
  - All security decisions must be documented in the relevant CR before production deploy
20
25
 
@@ -370,6 +375,56 @@ When PII-bearing objects/fields are part of the change being reviewed, add to th
370
375
 
371
376
  ---
372
377
 
378
+ ## Guest User & Experience Cloud Access
379
+
380
+ Guest User (unauthenticated Experience Cloud / site visitor) access is one of the highest-risk
381
+ misconfigurations in Salesforce — a single overly-broad Guest User sharing rule or OWD setting can
382
+ expose record data to anyone on the internet, no login required.
383
+
384
+ ### Review Checklist
385
+
386
+ - [ ] Guest User profile has **no** object permissions beyond what the public-facing page
387
+ genuinely needs to render — verify against the actual Experience Cloud page, not assumption
388
+ - [ ] No Guest User sharing rule or "Public Read/Write" OWD grants access to a PII-bearing object
389
+ (see [PII & Data Privacy](#pii--data-privacy)) unless explicitly required and documented
390
+ - [ ] "Secure Guest User Record Access" org setting is enabled (Setup → Sharing Settings) — this
391
+ caps Guest User access to explicit sharing rules rather than the older default-access model
392
+ - [ ] Guest User cannot create/edit/delete records unless the page's actual function requires it
393
+ (e.g. a public intake form) — CRUD checked per object, not granted broadly
394
+ - [ ] No Apex class exposed to Guest User (`without sharing` + `global`/`@AuraEnabled` reachable
395
+ from a public page) returns fields beyond what the page displays — treat any Guest-reachable
396
+ Apex method as a public API surface, not an internal helper
397
+ - [ ] Community/site URLs referencing record IDs are validated server-side, not trusted as proof
398
+ of access — a guessable/enumerable ID in a public URL is not an access control
399
+
400
+ Treat any change that touches a Guest User profile, an Experience Cloud page, or a
401
+ Guest-reachable Apex method as requiring this checklist, in addition to the standard Apex/LWC
402
+ review above.
403
+
404
+ ---
405
+
406
+ ## Connected App & External Client App Review
407
+
408
+ Relevant whenever a change introduces or modifies a Connected App, External Client App, or any
409
+ OAuth-based integration.
410
+
411
+ ### Review Checklist
412
+
413
+ - [ ] OAuth scopes requested are the **minimum** needed for the integration's actual purpose —
414
+ flag broad scopes (`full`, `api` when something narrower would do) without a documented
415
+ reason
416
+ - [ ] Refresh token policy is explicit (expire on inactivity / never expire) — "never expire" needs
417
+ a documented justification, not a default
418
+ - [ ] IP restriction is "Enforce IP restrictions" + Trusted IP Ranges unless the integration
419
+ genuinely runs from unpredictable IPs
420
+ - [ ] Callback URL(s) are exact, not wildcarded, unless the integration pattern requires it
421
+ - [ ] The integration/running user for the app has the minimum Permission Set needed — never
422
+ "System Administrator" as a convenience default
423
+ - [ ] Client secret / certificate lives in a secrets manager or Named Credential — never committed
424
+ to source control or hardcoded in an integration script
425
+
426
+ ---
427
+
373
428
  ## Security Review Checklist
374
429
 
375
430
  ### Apex
@@ -3,11 +3,12 @@ name: sf-testing
3
3
  description: >
4
4
  Use this skill for ANY Salesforce test-related task: writing Apex test classes, building
5
5
  TestDataFactory, mocking HTTP callouts, mocking platform events, writing test assertions,
6
- generating test coverage reports, debugging test failures, and designing test strategies.
7
- Trigger when the user asks to "write a test class", "fix test coverage", "mock a callout",
8
- "write a TestDataFactory", "test fails in org but passes locally", "assert this method",
9
- or anything about Apex unit testing, test data, or code coverage. Also use when coverage
10
- drops below 85% or when AuraHandledException assertions are involved.
6
+ generating test coverage reports, debugging test failures, designing test strategies, and
7
+ writing LWC Jest unit tests. Trigger when the user asks to "write a test class", "fix test
8
+ coverage", "mock a callout", "write a TestDataFactory", "test fails in org but passes locally",
9
+ "assert this method", "write a jest test", "test this component", or anything about Apex unit
10
+ testing, LWC unit testing, test data, or code coverage. Also use when coverage drops below 85%
11
+ or when AuraHandledException assertions are involved.
11
12
  ---
12
13
 
13
14
  # Salesforce Testing Skill
@@ -15,7 +16,10 @@ description: >
15
16
  ## Environment Context
16
17
 
17
18
  - Minimum coverage: **85%** (aim for 90%+)
18
- - API Version: **67.0** (Summer '26)
19
+ - API Version: **not hardcoded** new files inherit whatever `sourceApiVersion` is set in the
20
+ project's `sfdx-project.json` when scaffolded via SF CLI. When touching an **existing** test
21
+ class, check its `apiVersion`: below **62.0** → bump to **67.0** as part of the change; 62.0+ →
22
+ leave as-is unless the task needs 67.0+ behavior specifically.
19
23
  - Assertions: prefer the **`Assert` class** (`Assert.areEqual`, `Assert.isTrue`, etc. — API 56.0+)
20
24
  over legacy `System.assertEquals`/`System.assert` in new tests
21
25
  - `AuraHandledException.getMessage()` in test context **always** returns `'Script-thrown exception'` — this is Salesforce by design, not a bug
@@ -372,6 +376,112 @@ sf apex run test \
372
376
 
373
377
  ---
374
378
 
379
+ ## LWC Jest Testing
380
+
381
+ Every new or modified LWC component needs a Jest test file — same discipline as Apex coverage,
382
+ just not gated by a governor limit. This is separate from `sf-devops`'s LWC review checklist
383
+ (structural: naming, wire usage, JSDoc) and from `shaiden`'s design critique (visual/SLDS
384
+ compliance) — this section owns whether the component's *behavior* is actually tested.
385
+
386
+ ### File Location
387
+
388
+ ```
389
+ force-app/main/default/lwc/myComponent/
390
+ ├── myComponent.html
391
+ ├── myComponent.js
392
+ ├── myComponent.css
393
+ ├── myComponent.js-meta.xml
394
+ └── __tests__/
395
+ └── myComponent.test.js
396
+ ```
397
+
398
+ Uses `@salesforce/sfdx-lwc-jest` — run via `npm run test:unit` (or the project's configured Jest
399
+ script), not SF CLI.
400
+
401
+ ### Test Structure — Standard Pattern
402
+
403
+ ```javascript
404
+ import { createElement } from 'lwc';
405
+ import MyComponent from 'c/myComponent';
406
+ import getRelatedItems from '@salesforce/apex/MyController.getRelatedItems';
407
+
408
+ // Mock the wired Apex method — never let a Jest test hit a real Apex method
409
+ jest.mock(
410
+ '@salesforce/apex/MyController.getRelatedItems',
411
+ () => ({ default: jest.fn() }),
412
+ { virtual: true }
413
+ );
414
+
415
+ describe('c-my-component', () => {
416
+ afterEach(() => {
417
+ while (document.body.firstChild) {
418
+ document.body.removeChild(document.body.firstChild);
419
+ }
420
+ jest.clearAllMocks();
421
+ });
422
+
423
+ // ─── Happy Path ────────────────────────────────────────────────────────
424
+ it('renders items after the wire resolves', async () => {
425
+ const element = createElement('c-my-component', { is: MyComponent });
426
+ document.body.appendChild(element);
427
+
428
+ getRelatedItems.emit([{ Id: '001', Name: 'Test Item' }]);
429
+ await Promise.resolve();
430
+
431
+ const items = element.shadowRoot.querySelectorAll('.my-component__item');
432
+ expect(items.length).toBe(1);
433
+ });
434
+
435
+ // ─── Empty State ───────────────────────────────────────────────────────
436
+ it('shows the empty state when there is no data', async () => {
437
+ const element = createElement('c-my-component', { is: MyComponent });
438
+ document.body.appendChild(element);
439
+
440
+ getRelatedItems.emit([]);
441
+ await Promise.resolve();
442
+
443
+ expect(element.shadowRoot.querySelector('.my-component__empty')).not.toBeNull();
444
+ });
445
+
446
+ // ─── Interaction / Error Path ──────────────────────────────────────────
447
+ it('fires the itemselected event on click', () => {
448
+ const element = createElement('c-my-component', { is: MyComponent });
449
+ document.body.appendChild(element);
450
+
451
+ const handler = jest.fn();
452
+ element.addEventListener('itemselected', handler);
453
+ element.shadowRoot.querySelector('button')?.click();
454
+
455
+ expect(handler).toHaveBeenCalled();
456
+ });
457
+ });
458
+ ```
459
+
460
+ ### Rules
461
+
462
+ - **Minimum 3 scenarios** per component — same bar as Apex: happy/rendered path, empty state, one
463
+ interaction or error path
464
+ - Mock every `@wire`/imperative Apex import — a Jest test that reaches a real Apex method is broken
465
+ - Query only through `element.shadowRoot` — never reach into the component's internals directly
466
+ - Assert on rendered output (DOM nodes, text, fired events), not on private component state
467
+ - Run `npm run test:unit` before every PR — same gate as Apex coverage, checked in `sf-devops`'s
468
+ review
469
+
470
+ ### Run Commands
471
+
472
+ ```bash
473
+ # Run all LWC Jest tests
474
+ npm run test:unit
475
+
476
+ # Run a single component's tests
477
+ npx sfdx-lwc-jest -- myComponent
478
+
479
+ # Watch mode during development
480
+ npx sfdx-lwc-jest --watch
481
+ ```
482
+
483
+ ---
484
+
375
485
  ## Common Test Failures & Fixes
376
486
 
377
487
  | Error | Cause | Fix |