acek-skills 1.0.3 → 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 +14 -7
- package/commands/sf-init.md +165 -0
- package/package.json +3 -2
- package/skills/sf-admin/SKILL.md +4 -1
- package/skills/sf-architect/SKILL.md +99 -28
- package/skills/sf-ba/SKILL.md +8 -0
- package/skills/sf-data-migration/SKILL.md +4 -1
- package/skills/sf-dev/SKILL.md +23 -32
- package/skills/sf-devops/SKILL.md +6 -1
- package/skills/sf-ideation/SKILL.md +8 -0
- package/skills/sf-security-review/SKILL.md +56 -1
- package/skills/sf-testing/SKILL.md +116 -6
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`)
|
|
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,
|
|
234
|
-
|
|
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,
|
|
243
|
-
floor
|
|
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.
|
|
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?**
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Bootstrap or refresh the project's architecture.md baseline — org aliases, tech stack, data model, integrations, and Permission Set inventory that sf-architect reads before planning any feature.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /sf-init — Architecture Baseline Bootstrap
|
|
6
|
+
|
|
7
|
+
This command creates (or refreshes) `architecture.md` at the project root. It is a one-time setup
|
|
8
|
+
step per project — `sf-architect` reads this file on every plan afterward instead of re-scanning
|
|
9
|
+
the whole project from scratch each time.
|
|
10
|
+
|
|
11
|
+
**This command follows `sf-architect`'s conventions exactly:** scan before asking, never fabricate
|
|
12
|
+
a name that wasn't found, every clarification is a choice with an "Other" option — never a bare
|
|
13
|
+
open text prompt.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 1 — Check for an existing baseline
|
|
18
|
+
|
|
19
|
+
Look for `architecture.md` at the project root.
|
|
20
|
+
|
|
21
|
+
- **Not found** → run the [Bootstrap Flow](#bootstrap-flow) below.
|
|
22
|
+
- **Found** → run the [Refresh Flow](#refresh-flow) below. Do not blindly overwrite or blindly
|
|
23
|
+
merge — always diff and confirm first.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Bootstrap Flow
|
|
28
|
+
|
|
29
|
+
### 1. Scan the project
|
|
30
|
+
|
|
31
|
+
Before asking anything, gather what can be found directly:
|
|
32
|
+
|
|
33
|
+
- **Data model** — custom objects under `force-app/main/default/objects/`, their purpose (from
|
|
34
|
+
field/label context, not guessed)
|
|
35
|
+
- **Components** — LWC folders and Apex classes/triggers present
|
|
36
|
+
- **LWC styling convention in use** — check existing component CSS: custom `:host` brand-token
|
|
37
|
+
block (`sf-dev` standard) vs `--slds-g-*` global hooks (`shaiden` convention) vs none yet
|
|
38
|
+
- **Automation layer balance** — rough split of Flow-triggered vs Apex-triggered automation found
|
|
39
|
+
- **Integrations** — Named Credentials, external callouts referenced in Apex
|
|
40
|
+
- **Permission Sets / Permission Set Groups** — existing inventory
|
|
41
|
+
- **API version** — from `sfdx-project.json` or existing metadata
|
|
42
|
+
- **Org aliases** — run `sf org list` to see what's actually configured locally
|
|
43
|
+
|
|
44
|
+
### 2. Ask only what can't be scanned
|
|
45
|
+
|
|
46
|
+
Batch these into one round-trip, always as choices with an "Other" option:
|
|
47
|
+
|
|
48
|
+
- Which scanned org is **Production** vs **Sandbox/Dev**? (options = orgs found by `sf org list`)
|
|
49
|
+
- Is there an established **data classification / retention / masking policy** already in place?
|
|
50
|
+
(options: "Yes — describe it", "No, not yet documented", "Partially — describe what exists")
|
|
51
|
+
- Any **org-specific constraints** that always apply (naming conventions, governance rules,
|
|
52
|
+
deployment windows)? (options: "Yes — describe", "None beyond standard practice")
|
|
53
|
+
|
|
54
|
+
If `sf org list` returns nothing or the CLI isn't available, ask for aliases directly via the
|
|
55
|
+
choice format instead — never require typing from memory when a scan could have answered it.
|
|
56
|
+
|
|
57
|
+
### 3. Write architecture.md
|
|
58
|
+
|
|
59
|
+
Compile everything into `architecture.md` at the project root using the
|
|
60
|
+
[Baseline Template](#baseline-template) below.
|
|
61
|
+
|
|
62
|
+
### 4. Confirm
|
|
63
|
+
|
|
64
|
+
Tell the user the file was created and that `sf-architect` will now read it automatically on
|
|
65
|
+
future plans instead of re-scanning the whole project each time.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Refresh Flow
|
|
70
|
+
|
|
71
|
+
### 1. Re-scan the project
|
|
72
|
+
|
|
73
|
+
Same scan as [Bootstrap Step 1](#1-scan-the-project), fresh.
|
|
74
|
+
|
|
75
|
+
### 2. Diff against the existing file
|
|
76
|
+
|
|
77
|
+
Compare the fresh scan to what's currently in `architecture.md`. Categorize:
|
|
78
|
+
|
|
79
|
+
- **Added** — new objects/components/Permission Sets/integrations found
|
|
80
|
+
- **Removed** — previously listed items no longer found
|
|
81
|
+
- **Changed** — e.g. org alias no longer resolves, API version bumped
|
|
82
|
+
|
|
83
|
+
Never touch manually-maintained sections (**Data Privacy Baseline** notes, **Org-Specific
|
|
84
|
+
Constraints**) as part of an automatic refresh — those are only updated if the user explicitly
|
|
85
|
+
asks to revise them.
|
|
86
|
+
|
|
87
|
+
### 3. Present the diff and confirm before writing
|
|
88
|
+
|
|
89
|
+
Show what changed as a choice question:
|
|
90
|
+
|
|
91
|
+
- **Update all** — apply every detected change to the auto-scanned sections
|
|
92
|
+
- **Review each change** — walk through each addition/removal one at a time for confirmation
|
|
93
|
+
- **Cancel** — leave `architecture.md` untouched
|
|
94
|
+
|
|
95
|
+
Do not write anything until the user picks one.
|
|
96
|
+
|
|
97
|
+
### 4. Write and log
|
|
98
|
+
|
|
99
|
+
Apply the confirmed changes, update the `Last Scanned` timestamp, and append one line to the
|
|
100
|
+
Changelog section (e.g. `2026-07-13 — Refreshed: +2 objects, org alias updated`).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Baseline Template
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
# Architecture Baseline — [Project Name]
|
|
108
|
+
|
|
109
|
+
_Generated by /sf-init — YYYY-MM-DD_
|
|
110
|
+
_Last Scanned: YYYY-MM-DD HH:MM_
|
|
111
|
+
|
|
112
|
+
## Org Aliases
|
|
113
|
+
|
|
114
|
+
- Production: `[alias]`
|
|
115
|
+
- Sandbox / Dev: `[alias]`
|
|
116
|
+
|
|
117
|
+
## Tech Stack Baseline
|
|
118
|
+
|
|
119
|
+
- API Version: `[version]`
|
|
120
|
+
- LWC Styling Convention: [sf-dev standard SLDS 2 / shaiden / mixed — note which components use which]
|
|
121
|
+
- Primary Automation Layer: [Flow-first / Apex-heavy / mixed]
|
|
122
|
+
|
|
123
|
+
## Data Model Overview
|
|
124
|
+
|
|
125
|
+
| Object | Type | Purpose |
|
|
126
|
+
| ------ | ----------------- | -------- |
|
|
127
|
+
| [Name] | Standard / Custom | [1 line] |
|
|
128
|
+
|
|
129
|
+
## Existing Integrations
|
|
130
|
+
|
|
131
|
+
| System | Type | Notes |
|
|
132
|
+
| ------ | ------------------------------------------------ | -------- |
|
|
133
|
+
| [Name] | Named Credential / REST callout / Platform Event | [1 line] |
|
|
134
|
+
|
|
135
|
+
## Permission Set / PSG Inventory
|
|
136
|
+
|
|
137
|
+
| Name | Type | Purpose |
|
|
138
|
+
| ------ | ------------------------------------- | -------- |
|
|
139
|
+
| [Name] | Permission Set / Permission Set Group | [1 line] |
|
|
140
|
+
|
|
141
|
+
## Data Privacy Baseline
|
|
142
|
+
|
|
143
|
+
[Established classification/retention/masking policy, or "Not yet documented"]
|
|
144
|
+
|
|
145
|
+
## Org-Specific Constraints
|
|
146
|
+
|
|
147
|
+
[Naming conventions, governance rules, non-negotiables — manually maintained, not auto-refreshed
|
|
148
|
+
by future /sf-init runs unless explicitly requested]
|
|
149
|
+
|
|
150
|
+
## Changelog
|
|
151
|
+
|
|
152
|
+
- YYYY-MM-DD — Initial bootstrap
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Relationship to sf-architect
|
|
158
|
+
|
|
159
|
+
`sf-architect`'s Discovery phase checks for this file first and reads it instead of re-scanning
|
|
160
|
+
the whole project on every plan. Only feature-specific touchpoints not already covered by the
|
|
161
|
+
baseline get scanned fresh. If this file doesn't exist yet when `sf-architect` runs, it falls back
|
|
162
|
+
to its own full scan and may suggest running `/sf-init` first for future efficiency.
|
|
163
|
+
|
|
164
|
+
Re-run `/sf-init` periodically or after major structural changes (new integration, object model
|
|
165
|
+
overhaul, org alias change) — it's a refresh, not a one-time-only command.
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acek-skills",
|
|
3
|
-
"version": "1.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"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
|
-
"skills/"
|
|
10
|
+
"skills/",
|
|
11
|
+
"commands"
|
|
11
12
|
],
|
|
12
13
|
"keywords": [
|
|
13
14
|
"claude",
|
package/skills/sf-admin/SKILL.md
CHANGED
|
@@ -14,7 +14,10 @@ description: >
|
|
|
14
14
|
|
|
15
15
|
## Environment Context
|
|
16
16
|
|
|
17
|
-
- API Version: **
|
|
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
|
|
@@ -72,7 +72,7 @@ unrelated features into one plan.
|
|
|
72
72
|
## Workflow
|
|
73
73
|
|
|
74
74
|
```
|
|
75
|
-
DISCOVERY (grill me) → DECISIONS → PLAN (gate) → EXECUTE
|
|
75
|
+
DISCOVERY (grill me) → DECISIONS → PLAN (gate) → EXECUTE in batches (parallel where safe) → RESUME (new session)
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
### Phase 1 — Discovery ("Grill Me")
|
|
@@ -138,13 +138,22 @@ Nothing is built yet. This phase only produces decisions with rationale.
|
|
|
138
138
|
|
|
139
139
|
1. Compile Discovery + Decisions into the full plan file using the [Plan Template](#plan-template).
|
|
140
140
|
2. Break the work into tasks. Every task gets an ID, a description, an **Owner Skill**, a
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
**Touches** list (exact file(s)/component(s) it will create or modify), a dependency list, and
|
|
142
|
+
status `P` (Pending). `Touches` is what Phase 4 uses to decide which tasks are safe to run in
|
|
143
|
+
parallel — be specific and accurate here, not vague.
|
|
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
|
|
143
152
|
finalizing** — see [Org Alias Resolution](#org-alias-resolution) below. Skip this step
|
|
144
153
|
entirely if no task touches those two skills; don't ask about aliases a plan doesn't need.
|
|
145
|
-
|
|
154
|
+
5. Present the plan summary to the user and ask for explicit approval — options: **Approve**,
|
|
146
155
|
**Revise a section**, **Cancel**. Do not proceed on silence or an ambiguous reply.
|
|
147
|
-
|
|
156
|
+
6. Only after explicit approval: write the plan file to
|
|
148
157
|
`instructions/architecture/YYYYMMDD-<feature-slug>-plan.md`.
|
|
149
158
|
|
|
150
159
|
This is the hard gate. Nothing before this point touches the org or the repo's buildable code.
|
|
@@ -177,18 +186,49 @@ commands.
|
|
|
177
186
|
|
|
178
187
|
### Phase 4 — Execute
|
|
179
188
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
Tasks run in **batches**, not strictly one at a time. Within a batch, independent tasks are
|
|
190
|
+
dispatched to parallel subagents; tasks that depend on each other still run in sequence.
|
|
191
|
+
|
|
192
|
+
1. **Compute the ready set.** A task is ready when every task in its `Depends On` list is `D`.
|
|
193
|
+
2. **Split the ready set into parallel-safe groups.** Two ready tasks can run in the same batch
|
|
194
|
+
only if their `Touches` values don't overlap (no shared file/component). If a task's `Touches`
|
|
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.
|
|
200
|
+
3. **Dispatch the batch:**
|
|
201
|
+
- **Batch of 1** → run directly in the main thread, exactly as before (no subagent overhead
|
|
202
|
+
for solo work).
|
|
203
|
+
- **Batch of 2+** → dispatch each task to a separate subagent via the Task tool, in the same
|
|
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.
|
|
211
|
+
- **Assign each subagent in a batch a random name from the pool below, unique within that
|
|
212
|
+
batch** — used only as a tracking label in the orchestrator's own narration and Execution Log
|
|
213
|
+
("Dispatched to Sasha"), never written into code, commits, or file content.
|
|
214
|
+
4. **Only the orchestrator (main thread) writes to the plan file.** Subagents report their result
|
|
215
|
+
back; they never edit `instructions/architecture/*.md` directly. Once every subagent in the
|
|
216
|
+
batch has reported back, the orchestrator updates each task's status serially — this is what
|
|
217
|
+
prevents concurrent writes to the same plan file.
|
|
218
|
+
5. **Per task, on completion:** set status to `D` (Done) and append **one line** to the Execution
|
|
219
|
+
Log — no narrative, just what changed, where, and (if dispatched in a batch) which agent name
|
|
220
|
+
handled it.
|
|
221
|
+
6. **Per task, if it can't proceed** (missing decision, blocked dependency, discovered conflict):
|
|
222
|
+
set status to `B` (Blocked), write a one-line reason, and surface it to the user — a Blocked
|
|
223
|
+
task in a batch does not stop the other tasks in that same batch from completing.
|
|
224
|
+
7. **Recompute and repeat.** After a batch finishes, recompute the ready set (newly-unblocked
|
|
225
|
+
tasks may now qualify) and dispatch the next batch. Continue until every task is `D`, or every
|
|
226
|
+
remaining task is `B` — in which case stop and ask the user how to proceed via the choice
|
|
227
|
+
format.
|
|
228
|
+
|
|
229
|
+
**Subagent name pool** (pick unique names within a batch; reuse across different batches is fine):
|
|
230
|
+
Maya, Sasha, Nadia, Kirana, Alya, Cinta, Bunga, Dewi, Farah, Gita, Intan, Laras, Mira, Nita,
|
|
231
|
+
Putri, Rani, Sari, Tantri, Vina, Wulan
|
|
192
232
|
|
|
193
233
|
### Phase 5 — Resume
|
|
194
234
|
|
|
@@ -201,8 +241,12 @@ direct reference to a plan file.
|
|
|
201
241
|
feature name and last-updated date as the option label).
|
|
202
242
|
4. Read only that plan file. The **Resume Briefing** section is sufficient to continue — if it
|
|
203
243
|
isn't, that's a defect in how the plan was written, not a reason to re-derive context elsewhere.
|
|
204
|
-
5. Find
|
|
205
|
-
|
|
244
|
+
5. Find every task that is not `D`. **Any task still marked `IP` from a previous session means
|
|
245
|
+
its batch was interrupted mid-flight** — don't assume it actually finished or actually failed;
|
|
246
|
+
treat it as `P` again and re-verify its `Touches` before re-dispatching (check whether the file
|
|
247
|
+
actually reflects the change described in the task, since the interruption could have happened
|
|
248
|
+
before or after the underlying work landed). Recompute the ready/parallel-safe batch from
|
|
249
|
+
there and continue Phase 4 normally.
|
|
206
250
|
|
|
207
251
|
---
|
|
208
252
|
|
|
@@ -271,24 +315,35 @@ Permission Set Group `[PSG Name]`
|
|
|
271
315
|
|
|
272
316
|
## Task Breakdown
|
|
273
317
|
|
|
274
|
-
| ID | Description | Owner Skill | Depends On
|
|
275
|
-
| ------- | --------------------------------------------------------------------------- | ----------- |
|
|
276
|
-
| TASK-01 | [what] | sf-admin | —
|
|
277
|
-
| TASK-02 | Grant access via `[PS/PSG name from Decision 3]` — extend/create as decided | sf-admin | TASK-01
|
|
278
|
-
| TASK-03 | [what] | sf-dev | TASK-01
|
|
279
|
-
| TASK-04 | [what] | sf-
|
|
280
|
-
| TASK-05 | [what] | sf-
|
|
318
|
+
| ID | Description | Owner Skill | Touches | Depends On | Status |
|
|
319
|
+
| ------- | --------------------------------------------------------------------------- | ----------- | --------------------------- | ---------------- | ------ |
|
|
320
|
+
| TASK-01 | [what] | sf-admin | `Project__c` (object) | — | D |
|
|
321
|
+
| TASK-02 | Grant access via `[PS/PSG name from Decision 3]` — extend/create as decided | sf-admin | `[PS/PSG name]` | TASK-01 | P |
|
|
322
|
+
| TASK-03 | [what] | sf-dev | `ProjectController.cls` | TASK-01 | P |
|
|
323
|
+
| TASK-04 | [what] | sf-dev | `projectCard` (LWC) | TASK-01 | P |
|
|
324
|
+
| TASK-05 | [what] | sf-testing | `ProjectControllerTest.cls` | TASK-03 | P |
|
|
325
|
+
| TASK-06 | [what] | sf-devops | manifest + deploy | TASK-04, TASK-05 | P |
|
|
281
326
|
|
|
282
327
|
**Status codes:** `P` Pending · `IP` In Progress · `B` Blocked · `D` Done
|
|
283
328
|
|
|
329
|
+
**Touches** is what makes parallel dispatch safe: TASK-02, TASK-03, and TASK-04 all depend only on
|
|
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.
|
|
335
|
+
|
|
284
336
|
---
|
|
285
337
|
|
|
286
338
|
## Execution Log
|
|
287
339
|
|
|
288
|
-
_One line per completed or blocked task. No narrative.
|
|
340
|
+
_One line per completed or blocked task. No narrative. Include the agent name if the task was
|
|
341
|
+
dispatched as part of a parallel batch — omit it for solo/main-thread tasks._
|
|
289
342
|
|
|
290
343
|
- YYYY-MM-DD HH:MM — TASK-01 done — Created `Project__c` fields per Decision 1
|
|
291
|
-
- YYYY-MM-DD HH:MM — TASK-02
|
|
344
|
+
- YYYY-MM-DD HH:MM — TASK-02 done (agent: Nadia) — Extended `Project Manager Access` PS
|
|
345
|
+
- YYYY-MM-DD HH:MM — TASK-03 done (agent: Sasha) — Added `getProjects()` to `ProjectController.cls`
|
|
346
|
+
- YYYY-MM-DD HH:MM — TASK-04 blocked (agent: Kirana) — needs Decision on empty-state copy, asked user
|
|
292
347
|
```
|
|
293
348
|
|
|
294
349
|
---
|
|
@@ -339,6 +394,9 @@ best if users need to review before submitting (D) Other — describe your own a
|
|
|
339
394
|
9. **Read `architecture.md` instead of re-scanning the whole project.** If the project baseline
|
|
340
395
|
exists (from `/sf-init`), it already has the tech stack, data model, Permission Set inventory,
|
|
341
396
|
and org aliases — reuse it. Only scan for what's specific to the current feature.
|
|
397
|
+
10. **Don't parallelize trivial batches.** A batch of 2+ is only worth subagent overhead when the
|
|
398
|
+
tasks are substantial (real file edits, not one-liners). If a "parallel-safe" batch is tiny,
|
|
399
|
+
running it directly in the main thread is often cheaper than the dispatch overhead.
|
|
342
400
|
|
|
343
401
|
---
|
|
344
402
|
|
|
@@ -356,5 +414,18 @@ best if users need to review before submitting (D) Other — describe your own a
|
|
|
356
414
|
- ✕ Skipping the Permission Set Strategy decision because the feature "seems simple" — access
|
|
357
415
|
control is part of every feature that touches an object, field, Apex class, or LWC
|
|
358
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
|
|
419
|
+
- ✕ Dispatching two tasks in parallel when their `Touches` overlap, or when either one's
|
|
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
|
|
425
|
+
- ✕ Letting a subagent write directly to the plan file — only the orchestrator writes status/log
|
|
426
|
+
updates, and only after the batch reports back
|
|
427
|
+
- ✕ Reusing the same agent name for two subagents within the same batch
|
|
428
|
+
- ✕ Treating an `IP` task found on resume as already finished — it means the batch was
|
|
429
|
+
interrupted, not that the work landed; re-verify before re-dispatching
|
|
359
430
|
- ✕ Asking the user to type an org alias from memory when `sf org list` can just be run
|
|
360
431
|
- ✕ Resolving org aliases for a plan that has no `sf-devops`/`sf-data-migration` task at all
|
package/skills/sf-ba/SKILL.md
CHANGED
|
@@ -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: **
|
|
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
|
package/skills/sf-dev/SKILL.md
CHANGED
|
@@ -13,7 +13,12 @@ description: >
|
|
|
13
13
|
|
|
14
14
|
## Environment Context
|
|
15
15
|
|
|
16
|
-
- API Version: **
|
|
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
|
|
178
|
-
| Test Class | PascalCase + `Test` suffix
|
|
179
|
-
|
|
|
180
|
-
|
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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: **
|
|
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: **
|
|
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,
|
|
7
|
-
Trigger when the user asks to "write a test class", "fix test
|
|
8
|
-
"write a TestDataFactory", "test fails in org but passes locally",
|
|
9
|
-
|
|
10
|
-
|
|
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: **
|
|
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 |
|