@salesforce/afv-skills 1.65.1 → 1.67.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 (71) hide show
  1. package/package.json +2 -4
  2. package/skills/agentforce-offline-experimentation-run/SKILL.md +29 -43
  3. package/skills/agentforce-offline-experimentation-run/scripts/sfap_replay.py +3 -245
  4. package/skills/commerce-react-storefront-create/SKILL.md +350 -0
  5. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +2 -2
  6. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +28 -16
  7. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +18 -1
  8. package/skills/dx-devops-work-item-manage/SKILL.md +34 -17
  9. package/skills/dx-devops-work-item-manage/examples/common-workflows.md +49 -0
  10. package/skills/dx-devops-work-item-manage/references/commit-api.md +116 -0
  11. package/skills/education-cloud-domain-configure/SKILL.md +21 -23
  12. package/skills/education-cloud-domain-configure/references/dependent-features.md +9 -0
  13. package/skills/education-cloud-domain-configure/references/domain-features.md +15 -0
  14. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +8 -0
  15. package/skills/education-cloud-domain-configure/references/setup-widgets.md +123 -0
  16. package/skills/experience-lwc-widget-assess/SKILL.md +79 -0
  17. package/skills/experience-lwc-widget-migrate/SKILL.md +50 -0
  18. package/skills/experience-search-coordinate/SKILL.md +2 -2
  19. package/skills/experience-search-coordinate/references/media-route.md +30 -4
  20. package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -1
  21. package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -1
  22. package/skills/platform-metadata-deploy/README.md +1 -1
  23. package/skills/platform-metadata-deploy/SKILL.md +114 -15
  24. package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +3 -4
  25. package/skills/platform-metadata-deploy/references/orchestration.md +2 -3
  26. package/skills/platform-role-hierarchy-apply/SKILL.md +177 -0
  27. package/skills/platform-value-set-generate/SKILL.md +19 -19
  28. package/skills/platform-widget-generate/SKILL.md +59 -68
  29. package/skills/platform-widget-generate/examples/formulas.json +19 -2
  30. package/skills/platform-widget-generate/examples/localized-labels.json +57 -0
  31. package/skills/platform-widget-generate/references/widget-actions.md +29 -0
  32. package/skills/platform-widget-generate/references/widget-bundle-validation.md +15 -0
  33. package/skills/platform-widget-generate/references/widget-formulas.md +24 -7
  34. package/skills/platform-widget-generate/references/widget-gotchas.md +10 -0
  35. package/skills/platform-widget-generate/references/widget-labels.md +78 -0
  36. package/skills/platform-widget-generate/references/widget-styling.md +12 -0
  37. package/skills/sales-call-scoring-configure/SKILL.md +17 -10
  38. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +55 -33
  39. package/skills/service-agentforce-contact-center-coordinate/SKILL.md +1 -1
  40. package/skills/service-agentforce-contact-center-coordinate/references/number_management_api.md +11 -1
  41. package/skills/service-agentforce-contact-center-coordinate/scripts/fetch-numbers.sh +12 -6
  42. package/skills/service-agentforce-contact-center-coordinate/scripts/lib.sh +16 -0
  43. package/skills/service-agentforce-contact-center-coordinate/scripts/procure-number.sh +2 -1
  44. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +24 -11
  45. package/skills/service-digital-engagement-channel-configure/SKILL.md +29 -16
  46. package/skills/service-digital-engagement-channel-configure/references/channel_settings.md +13 -2
  47. package/skills/service-digital-engagement-channel-configure/references/onboarding_contract.md +143 -0
  48. package/skills/service-digital-engagement-deployment-configure/SKILL.md +65 -79
  49. package/skills/service-digital-engagement-deployment-configure/assets/esd_web_update_template.xml +83 -8
  50. package/skills/service-digital-engagement-deployment-configure/examples/esd_web_prechat_fields.xml +74 -0
  51. package/skills/service-digital-engagement-deployment-configure/references/connect_api_creation.md +26 -7
  52. package/skills/service-digital-engagement-deployment-configure/references/deployment_settings.md +13 -78
  53. package/skills/service-digital-engagement-deployment-configure/references/onboarding_contract.md +145 -0
  54. package/skills/service-digital-engagement-deployment-configure/references/prechat_forms.md +245 -0
  55. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +166 -112
  56. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +61 -37
  57. package/skills/service-itsm-agentic-setup-incident-management/references/audit-tracking.md +42 -0
  58. package/skills/service-itsm-agentic-setup-incident-management/references/broadcast-communications.md +23 -0
  59. package/skills/service-itsm-agentic-setup-incident-management/references/email-to-incident.md +23 -0
  60. package/skills/service-itsm-agentic-setup-incident-management/references/incident-dashboards.md +25 -0
  61. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +30 -145
  62. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +32 -137
  63. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +32 -155
  64. package/skills/service-itsm-agentic-setup-incident-management/references/manage-incidents-with-ai.md +45 -0
  65. package/skills/service-itsm-agentic-setup-incident-management/references/priority-matrix.md +32 -0
  66. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +30 -140
  67. package/skills/service-itsm-agentic-setup-incident-management/references/sla-milestones.md +110 -0
  68. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +34 -25
  69. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +1 -1
  70. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +90 -86
  71. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +63 -38
@@ -146,7 +146,7 @@ Follow this sequence for each intent Step 2 routed here. The media content type
146
146
  1. **Present available search sources via `ask_followup_question` — no other tool calls yet.** Only the sources whose tools you actually have (via introspection), plus "Other", each as its own option. Wait for the user's selection before any tool call. → Read `references/media-route.md` § "Presenting available search sources" for the full template.
147
147
  2. **Build the query, then confirm with the user before searching — required two-message pattern**, in the same turn:
148
148
  - **Search using keywords** (`search_media_cms_channels`): extract keywords (concrete nouns, max 10 terms) and taxonomies (descriptive/style/mood/category terms — extract every one the query actually contains; don't default to empty just because keywords were found). E.g. "luxury apartment with river view" → keywords `apartment OR villa OR penthouse OR residence`, taxonomies `{"OR": ["Luxury", "Premium", "Waterfront", "Riverside"]}`; "car" → taxonomies `"{}"` (no descriptive terms). Build the payload with `contentTypeFqn` set to the FQN(s) from Step 2, `searchLanguage` set to a locale (`en_US`, `es_MX`, ... — default `en_US`; **required, never null/omitted**), `searchLimit: 5`. → Read `references/media-route.md` for full worked examples.
149
- - **Search using Data 360 hybrid search** (`search_electronic_media`) — pass the user's query as-is to `searchQuery`, no extraction needed.
149
+ - **Search using Data 360 hybrid search** (`search_electronic_media`): verbatim `searchQuery`; bare ISO `searchLanguage` only if the schema lists it; confirm first. On zero results or a language error, ask the user before one retry without it; never retry after a server error → `references/media-route.md`.
150
150
  - **Other** — ask for a direct URL, asset library path, or specific system to check (no confirmation step, since no tool is called).
151
151
 
152
152
  → Read `references/media-route.md` § "Confirming the query before searching" for the confirmation message templates (labeled-parameters chat message, then `ask_followup_question` with `Yes - search now` / `Edit search` / `Cancel`).
@@ -175,7 +175,7 @@ Each route's results are labeled and presented on their own (e.g. "Images:" for
175
175
  | `metadata-grounding` unavailable or no content types match | Apply the fallback chain in `references/content-type-discovery.md` — do not block or ask for an FQN |
176
176
  | No search results (either route) | "No [content/media] found. Try broader keywords, removing descriptive terms, or a different [content type/source]." |
177
177
  | Invalid user selection | Re-display options and ask again |
178
- | Tool returns error (invalid input, missing argument, or otherwise) | Show error message, offer retry with corrected parameters **on that same tool — never fall through to the other route's search tool as a workaround** (see Key Principle 3) |
178
+ | Tool returns error (invalid input, missing argument, or otherwise) | Show error message, offer retry with corrected parameters **on that same tool — never fall through to the other route's search tool as a workaround** (see Key Principle 3); hybrid language errors: offer one no-language retry, with the user's OK (see media route) |
179
179
  | Search tool on the connected server returns a server error (5xx, timeout, connection failure — not "no results") | **Fail the call — do NOT silently retry against a different server.** Tell the user to check that server's connection/session and wait before retrying. |
180
180
 
181
181
  **Never silently fail on a route the user asked for.** If one route in a mixed request comes up empty, still present the other route's results and say so explicitly. This does not mean substituting a different server when the connected one errors — see the server-error row above.
@@ -109,10 +109,25 @@ Keyword/taxonomy extraction rule: see `SKILL.md` Step 4 point 2. Extract taxonom
109
109
 
110
110
  ## Search using Data 360 hybrid search (`search_electronic_media`)
111
111
 
112
- 1. Use the user's query **as-is** — no keyword extraction or transformation needed
113
- 2. Call `search_electronic_media(searchQuery="<user's query>")`
112
+ 1. Pass `searchQuery` **verbatim** — same language, same script as the user wrote it. No translation, no keyword extraction: translating first only makes step 2 harder, since you'd be detecting the language of your own translation instead of the user's actual words.
113
+ 2. **Determine language** — a short ISO code read directly off the user's literal wording, before any translation happens in your reasoning. Bare 2-letter codes (`en`, `fr`, `de`, `es`, `ja`, ...) cover most languages and are the validated, simpler form; a few regional splits need a country suffix instead (`pt_BR`, `zh_CN`, `zh_TW`, ...). Default to `en` only when the message is genuinely ambiguous (e.g. a bare product name) — never because translating felt natural. Use the bare 2-letter form whenever one exists — the backend accepts only the platform's own language codes, so `fr` works but `fr_FR` is rejected as invalid (`en` and `en_US` both work because `en_US` is itself a platform code).
114
+ 3. Call `search_electronic_media(searchQuery="<verbatim query>", searchLanguage="<language code>")`. **Only send `searchLanguage` if the tool's input schema actually lists it** — orgs without language-variant search enabled don't declare the input at all. When it's absent, call with `searchQuery` alone, skip the language line in the confirmation message, and skip steps 4–5 (the backend searches primary-language content only).
115
+ 4. **Offer one retry with `searchLanguage` omitted entirely — ask the user first, never retry silently — but only when the first call either:**
116
+ - returns **zero results**, **or**
117
+ - fails with an **input-validation error about the language value itself** — an unrecognized language code errors the whole call (for example, `INVALID_API_INPUT` / "Specify a valid language code, such as en_US, fr, or de.") rather than returning an empty list.
114
118
 
115
- Example: user query "modern luxury apartment with natural lighting" → `search_electronic_media(searchQuery="modern luxury apartment with natural lighting")`
119
+ **Do NOT offer this retry on a server or transport error (5xx, timeout, connection failure).** Those must fail per `SKILL.md`'s Error Handling table — omitting the language cannot fix them and would mask the real failure. The retry exists only for the two language-specific cases above.
120
+
121
+ Offer it before concluding nothing exists and before suggesting broader keywords: tell the user what happened in plain chat text (e.g. "No results found in French."), then `ask_followup_question` with options `Yes - search without language filter` / `No - stop here`. The retry is a second backend call the user didn't confirm, so they decide whether it runs. Skip the question only if the user already said, in this conversation, to fall back to any language when nothing matches theirs — then retry directly. Omitting the field makes the backend fall back to each item's own primary/default language variant instead of the requested one — so an item that only has, say, a French variant still surfaces on the retry even when the user asked in English. Only fall through to "no results" / broadening suggestions if the user declines, or the retry also comes back empty.
122
+ 5. **Read each result item's `language` field to know which language variant you actually got** (a locale string such as `en_US` or `fr`). If it differs from the language the user asked for — typically after the retry — say so explicitly when presenting that item, e.g. "No French version exists for this item — here's the original in English." Never present a fallback-language result as if it were the requested-language match; the user needs to know a substitution happened.
123
+
124
+ If `language` is **absent** on the results of a call that sent `searchLanguage`, the backend couldn't apply the language filter (for example, the org's search index predates language-variant support) and silently returned primary-language content instead — even on the first call, with no error and no empty list. Don't claim the results are in the requested language; tell the user the search couldn't be restricted to that language.
125
+
126
+ This is a **strict** filter, not soft ranking like the keyword route: the backend only returns items carrying that exact language variant, and it does **not** fall back to English (or any other language) per item on its own — that's why step 4's retry exists as an explicit, user-approved second call, not something the backend does silently.
127
+
128
+ Example — French input "trouve-moi une image de pinceaux depuis le CMS" → `search_electronic_media(searchQuery="trouve-moi une image de pinceaux depuis le CMS", searchLanguage="fr")`.
129
+
130
+ Example of the retry — English input "give me an image of a snowboard from the CMS" → first call `search_electronic_media(searchQuery="give me an image of a snowboard from the CMS", searchLanguage="en")` returns zero results (the only snowboard item in the org has a French variant, not English) → tell the user "No results found in English." and ask whether to search without the language filter → user picks `Yes - search without language filter` → retry `search_electronic_media(searchQuery="give me an image of a snowboard from the CMS")` with `searchLanguage` omitted → the item surfaces with `language: "fr"` → tell the user: "No English version exists for this item — here's the original in French."
116
131
 
117
132
  ## Confirming the query before searching
118
133
 
@@ -130,11 +145,22 @@ I'll search for media with these parameters:
130
145
  Channels: Public
131
146
  ```
132
147
 
148
+ **Message 1 — Data 360 hybrid search variant** (surface the detected language so the user can catch a mis-detection before the search runs — language detection is the most error-prone step of this route):
149
+
150
+ ```text
151
+ I'll search Data 360 hybrid search with:
152
+
153
+ Query: trouve-moi une image de pinceaux depuis le CMS
154
+ Language: fr (auto-detected — tell me if this is wrong)
155
+ ```
156
+
157
+ Show `Query` exactly as the user wrote it (verbatim) and `Language` as the bare code you'll send. This route has no `Content Type` / `Keywords` / `Taxonomies` / `Channels` lines — it ignores channel scope and does its own semantic matching.
158
+
133
159
  **Message 2 (immediately after):** `ask_followup_question` with options `Yes - search now` / `Edit search` / `Cancel`.
134
160
 
135
161
  ## Presenting Search Results
136
162
 
137
- 1. **Parse each call's response** — Extract all results (title and content type) and each item's full `managedContentChannelDeliveryDetails` array, merged across calls if the item appears in more than one response (per "Executing the search call(s)" above — nothing is dropped as a duplicate).
163
+ 1. **Parse each call's response** — Extract all results (title and content type) and each item's full `managedContentChannelDeliveryDetails` array, merged across calls if the item appears in more than one response (per "Executing the search call(s)" above — nothing is dropped as a duplicate). Data 360 hybrid search results have a different shape: each item carries `mediaUrl` (or a `mediaUrls` list of `{channelId, channelName, url}`) instead of `managedContentChannelDeliveryDetails`, plus a `language` field when language-variant search applied — use `language` per step 5 of "Search using Data 360 hybrid search" above.
138
164
  2. **Use `ask_followup_question`** to present ALL results as selectable **unique** options (one option per item, by identifier), grouped by content type — same grouping key as the Content Route (see `content-route.md` Step 6), never by channel. Show the title, content type, and channel on each option — do not display the URL. If an item's `managedContentChannelDeliveryDetails` array has more than one entry, show the **first** entry's channel name here as a lightweight hint only; the actual choice between channels is made after selection (see "Disambiguating channel at selection" below), not here. If more than one content type FQN is in scope, order the options by content type (the tool has no native grouping — approximate it by ordering and, where the UI allows a label, prefixing the option text with the content type). Regardless of grouping, always include the channel as a per-item suffix, e.g. `Product Launch Hero Banner (sfdc_cms__image) — Public`.
139
165
  3. **Receive the user's selection** from the tool response
140
166
 
@@ -10,7 +10,7 @@ metadata:
10
10
  semver: ">=7.0.0"
11
11
  - tool: ["npm"]
12
12
  semver: ">=7.0.0"
13
- accessCheck: "(OrgPreferences.Sites) && (License.ExperienceCloudCustomerCommunityCustomerCommunityPlus)"
13
+ accessCheck: "OrgPermissions.Networks"
14
14
  ---
15
15
 
16
16
  # UI Bundle Features
@@ -10,7 +10,7 @@ metadata:
10
10
  semver: ">=1.6"
11
11
  - tool: ["sf"]
12
12
  semver: ">=2.0.0"
13
- accessCheck: "License.ExperienceCloudCustomerCommunityCustomerCommunityLogin"
13
+ accessCheck: "OrgPermissions.Networks"
14
14
  relatedSkills:
15
15
  - "dx-org-permission-set-assign"
16
16
  - "experience-ui-bundle-deploy"
@@ -86,7 +86,7 @@ platform-custom-object-generate/automation-flow-generate → platform-metadata-d
86
86
 
87
87
  | Related Skill | When to Use |
88
88
  |---------------|-------------|
89
- | platform-custom-object-generate / platform-custom-field-generate | Create objects/fields BEFORE deploy |
89
+ | platform-custom-object-generate | Create objects/fields BEFORE deploy |
90
90
  | platform-apex-test-run | Run tests AFTER deployment |
91
91
  | platform-data-manage | Create describe-validated test data AFTER deployment |
92
92
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: platform-metadata-deploy
3
- description: "Salesforce DevOps automation using sf CLI v2. TRIGGER when: user deploys metadata, creates/manages scratch orgs or sandboxes, sets up CI/CD pipelines, or troubleshoots deployment errors with sf project deploy. DO NOT TRIGGER when: writing Apex code (use platform-apex-generate), building LWC components (use experience-lwc-generate), creating metadata definitions (use platform-custom-object-generate or platform-custom-field-generate), or querying org data (use platform-data-manage)."
3
+ description: "Salesforce DevOps automation using sf CLI v2. TRIGGER when: user deploys metadata, validates a deploy or does a dry-run, deploys to a Production org, quick-deploys a validated job ID, ships/promotes to prod, creates/manages scratch orgs or sandboxes, sets up CI/CD pipelines, or troubleshoots deployment errors with sf project deploy. DO NOT TRIGGER when: writing Apex code, building LWC components (use experience-lwc-generate), creating metadata definitions (use platform-custom-object-generate), deleting metadata from an org (use platform-destructive-deploy), or querying org data (use platform-data-manage)."
4
4
  metadata:
5
- version: "1.1"
5
+ version: "1.2"
6
6
  domains: ["Platform", "Developer Experience"]
7
7
  relatedSkills:
8
8
  - "agentforce-generate"
@@ -11,9 +11,7 @@ metadata:
11
11
  - "experience-lwc-generate"
12
12
  - "integration-connectivity-connected-app-configure"
13
13
  - "integration-connectivity-generate"
14
- - "platform-apex-generate"
15
14
  - "platform-apex-test-run"
16
- - "platform-custom-field-generate"
17
15
  - "platform-custom-object-generate"
18
16
  - "platform-data-manage"
19
17
  cliTools:
@@ -40,9 +38,9 @@ Use `platform-metadata-deploy` when the work involves:
40
38
  - troubleshooting deployment failures and dependency ordering
41
39
 
42
40
  Delegate elsewhere when the user is:
43
- - authoring Apex code → [platform-apex-generate](../platform-apex-generate/SKILL.md)
41
+ - authoring Apex code (this skill only deploys it)
44
42
  - authoring LWC components → [experience-lwc-generate](../experience-lwc-generate/SKILL.md)
45
- - creating custom objects or fields → [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md), [platform-custom-field-generate](../platform-custom-field-generate/SKILL.md)
43
+ - creating custom objects or fields → [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md)
46
44
  - building Flows → [automation-flow-generate](../automation-flow-generate/SKILL.md)
47
45
  - doing org data operations → [platform-data-manage](../platform-data-manage/SKILL.md)
48
46
  - authoring or testing Agentforce agents → [agentforce-generate](../agentforce-generate/SKILL.md)
@@ -54,6 +52,7 @@ Delegate elsewhere when the user is:
54
52
  - Use **`sf` CLI v2 only**.
55
53
  - On non-source-tracking orgs, deploy/retrieve commands require an explicit scope such as `--source-dir`, `--metadata`, or `--manifest`.
56
54
  - Prefer **`--dry-run` first** before real deploys.
55
+ - **Never run `sf project deploy start` against a production org.** Production goes through the validate → quick-deploy path below (server-side validation returns a 10-day job ID; quick-deploy promotes it without re-running tests, behind an explicit confirmation).
57
56
  - For Flows, deploy safely and activate only after validation.
58
57
  - Keep test-data creation guidance delegated to **`platform-data-manage`** after metadata is validated or deployed.
59
58
 
@@ -82,7 +81,7 @@ Ask for or infer:
82
81
  Preflight checks:
83
82
  ```bash
84
83
  sf --version
85
- sf org list
84
+ sf org list --skip-connection-status
86
85
  sf org display --target-org <alias> --json
87
86
  test -f sfdx-project.json
88
87
  ```
@@ -94,7 +93,25 @@ test -f sfdx-project.json
94
93
  ### 1. Preflight
95
94
  Confirm auth, repo shape, package directories, and target scope.
96
95
 
97
- ### 2. Validate first
96
+ ### 1a. Classify the target org (routes prod vs non-prod)
97
+
98
+ Before any deploy, classify the target from its `sf org list` record — the path forks on whether the org is **production**. Do NOT classify from `sf org display`: it reports only a plain My Domain and omits the sandbox/scratch/trial fields, so a Sandbox-or-Demo Org (SDO) is misclassified as production (forcedotcom/sf-skills#356). The `sf org list` record carries the real classification booleans. Pass `--skip-connection-status` so this stays a local read that does not refresh an OAuth token per authenticated org (forcedotcom/sf-skills#349):
99
+
100
+ ```bash
101
+ sf org list --skip-connection-status --json
102
+ ```
103
+
104
+ Find the record whose `alias`, `username`, or `orgId` matches the target, then classify from that record (first match wins):
105
+ - **Scratch** — `isScratch` is `true`.
106
+ - **Sandbox** — `isSandbox` is `true`, or the `instanceUrl` contains `--` (the My Domain sandbox marker) or `test.salesforce.com`.
107
+ - **Trial / Developer Edition** — `trailExpirationDate` (the CLI's spelling; also accept `trialExpirationDate`) is a date in the **future**, or the `instanceUrl` host contains `orgfarm-`, `.develop.my.salesforce.com`, `.pc-rnd.`, or `.scratch.my.salesforce.com`. A past date does not count — the date is cached at login and never refreshed, so a converted trial keeps a stale past date and is not a trial.
108
+
109
+ Anything else is **production** — including a **Dev Hub** (`isDevHub` is `true`) that shows none of the above signals, since Dev Hub is normally enabled in the customer's own production org. If no record matches or the record has no usable info (no `instanceUrl` and none of the flags), treat it as **unknown** and ask the user before proceeding, rather than assuming production or non-production.
110
+
111
+ - **Non-production** → the dry-run + deploy path (steps 2–6).
112
+ - **Production** → the validate → quick-deploy path ([Production deploys](#production-deploys-validate--quick-deploy)). Never run `sf project deploy start` against production.
113
+
114
+ ### 2. Validate first (non-production)
98
115
  ```bash
99
116
  sf project deploy start --dry-run --source-dir force-app --target-org <alias> --wait 30 --json
100
117
  ```
@@ -118,9 +135,6 @@ sf project deploy start --manifest manifest/package.xml --target-org <alias> --t
118
135
 
119
136
  # manifest deploy with Spring '26 relevant-test selection
120
137
  sf project deploy start --manifest manifest/package.xml --target-org <alias> --test-level RunRelevantTests --wait 30 --json
121
-
122
- # quick deploy after successful validation
123
- sf project deploy quick --job-id <validation-job-id> --target-org <alias> --json
124
138
  ```
125
139
 
126
140
  ### 5. Verify
@@ -136,6 +150,93 @@ Output template: [references/deployment-report-template.md](references/deploymen
136
150
 
137
151
  ---
138
152
 
153
+ ## Production deploys (validate → quick-deploy)
154
+
155
+ Production is never deployed with `sf project deploy start`. It is a two-step promotion: a **server-side validation** produces a job ID, then a **quick deploy** promotes that validated job without re-running tests, behind an explicit confirmation.
156
+
157
+ ### P1. Validate against production
158
+
159
+ Production validations REQUIRE a test level. Use `RunLocalTests` by default; switch to `RunSpecifiedTests --tests <ClassName>...` only if the user has explicitly listed tests.
160
+
161
+ ```bash
162
+ sf project deploy validate --target-org <alias> --json [scope flags] --test-level RunLocalTests
163
+ ```
164
+
165
+ Scope flags — use exactly one: `--source-dir <path>`, `--metadata <Type:Name>`, or `--manifest manifest/package.xml`.
166
+
167
+ The response returns a **job ID** (`result.id`) valid for **10 days**. Persist it **with the target's immutable identity** — the org ID and instance URL from `sf org display`, not just the alias — so the quick deploy can prove the validation belongs to the org it is about to promote to:
168
+
169
+ ```bash
170
+ mkdir -p .sfdx
171
+ ORG_JSON=$(sf org display --target-org <alias> --json)
172
+ ORG_ID=$(printf '%s' "$ORG_JSON" | jq -r '.result.id')
173
+ INSTANCE_URL=$(printf '%s' "$ORG_JSON" | jq -r '.result.instanceUrl')
174
+ printf '{"jobId":"%s","createdAt":"%s","targetOrg":"%s","orgId":"%s","instanceUrl":"%s","testLevel":"RunLocalTests"}\n' \
175
+ "<id>" "<iso8601>" "<alias>" "$ORG_ID" "$INSTANCE_URL" > .sfdx/last-validation.json
176
+ ```
177
+
178
+ On validation failure, triage from `result.details.componentFailures` and `result.details.runTestResult.failures` — surface the top component errors and test failures with a scoped fix direction, then STOP. Never use `--ignore-errors`/`--ignore-warnings` during validation.
179
+
180
+ ### P2. Gate the quick deploy strictly
181
+
182
+ Before promoting, verify all five:
183
+ 1. **Target is production** — re-confirm via step 1a's classification.
184
+ 2. **A validation exists** — read `.sfdx/last-validation.json`, or ask for the job ID, or fall back to `--use-most-recent`. Resolve `--use-most-recent` to a concrete job ID **now** (do not carry it forward as an unresolved flag), so the checks below run against a known job.
185
+ 3. **Validation belongs to THIS target** — re-run `sf org display --target-org <alias> --json` and require the current `result.id` (org ID) to equal the `orgId` persisted in `.sfdx/last-validation.json`. This closes the stale-cache hazard: a cached job ID resolves its org from the deploy cache, so without this check the banner can show org B while the job promotes to org A. On any mismatch — or if the persisted record lacks an `orgId`/`instanceUrl` to compare — treat it as a **hard stop** and re-validate (P1) against the intended target. Never promote a validation whose target you cannot prove.
186
+ 4. **Validation is fresh** — explicit `--job-id` ≤ 10 days; a `--use-most-recent` resolution ≤ 3 days. If stale, re-validate (P1) first.
187
+ 5. **Explicit user confirmation** — print the banner below and require a literal "yes". Populate every banner field from the verified validation record and the just-fetched org data — never from unverified input. Never auto-confirm.
188
+
189
+ ```text
190
+ ┌─ PRODUCTION DEPLOY ─────────────────────────────┐
191
+ │ Org alias: <alias> │
192
+ │ Instance: <instanceUrl> │
193
+ │ Edition: <edition> │
194
+ │ Validation ID: <jobId> │
195
+ │ Validated: <createdAt> (X days ago) │
196
+ │ Components: <componentCount> queued │
197
+ │ Tests: <run>/<passed>/<failed> │
198
+ └─────────────────────────────────────────────────┘
199
+ Confirm deploy to PRODUCTION? (yes/no)
200
+ ```
201
+
202
+ `sf org display --json` does not return the edition. Read `<edition>` from the Organization object's `OrganizationType` (e.g. `Enterprise Edition`):
203
+
204
+ ```bash
205
+ sf data query --query "SELECT OrganizationType FROM Organization" --target-org <alias> --json | jq -r '.result.records[0].OrganizationType'
206
+ ```
207
+
208
+ ### P3. Promote and audit
209
+
210
+ The literal "yes" from P2's banner is the authorization. In the **Salesforce development plugin** context, a PreToolUse hook (`sf-deploy-gate`) blocks a bare production `deploy quick` and only permits it when the command carries the machine-readable confirmation marker `CONFIRM_PROD=1`. Emit that marker to transmit the confirmation the user just gave — it is **not** an independent bypass, so add it **only after** the explicit "yes", never before:
211
+
212
+ ```bash
213
+ # plugin context: CONFIRM_PROD=1 relays P2's confirmation to the safety hook
214
+ CONFIRM_PROD=1 sf project deploy quick --job-id <validation-id> --target-org <alias> --wait 30 --json
215
+ ```
216
+
217
+ Outside the plugin (bare CLI, headless/MCP) there is no such hook — run the same command without the marker.
218
+
219
+ **Quick deploy starts a new deployment with its own job ID** — it does not reuse the validation ID. Capture the response, extract the new ID, and use *that* for polling, reporting, and the audit filename; keep the validation ID separately for traceability. Stop if the response carries no deployment ID:
220
+
221
+ ```bash
222
+ QUICK_RESULT=$(CONFIRM_PROD=1 sf project deploy quick --job-id <validation-id> --target-org <alias> --wait 30 --json)
223
+ QUICK_DEPLOY_ID=$(printf '%s' "$QUICK_RESULT" | jq -r '.result.id')
224
+ [ -n "$QUICK_DEPLOY_ID" ] && [ "$QUICK_DEPLOY_ID" != "null" ] || { echo "No quick-deploy ID returned — do not report the validation ID as the deploy; investigate."; exit 1; }
225
+ ```
226
+
227
+ Quick deploy promotes the validated components and does NOT re-run tests. Persist the report **for the quick-deploy operation** (not the validation) for the audit trail, then guide post-deploy verification (smoke-test critical paths, watch Setup → Deployment Status, prepare a rollback plan if anything regressed):
228
+
229
+ ```bash
230
+ mkdir -p .sfdx/deploy-history
231
+ sf project deploy report --job-id "$QUICK_DEPLOY_ID" --target-org <alias> --json > ".sfdx/deploy-history/${QUICK_DEPLOY_ID}.json"
232
+ ```
233
+
234
+ If quick deploy times out or stays in progress, poll with the **quick-deploy ID**, not the validation ID.
235
+
236
+ Adding `CONFIRM_PROD=1` after an explicit confirmation is the sanctioned path — it is not "working around" the gate. But never override a denial the user has **not** confirmed, and never override one raised for a different reason — a target mismatch (P2 check 3), a destructive deploy, or an unknown/unresolved org. Surface those denials and stop.
237
+
238
+ ---
239
+
139
240
  ## High-Signal Failure Patterns
140
241
 
141
242
  | Error / symptom | Likely cause | Default fix direction |
@@ -163,7 +264,7 @@ Default pipeline shape:
163
264
  7. verify + notify
164
265
 
165
266
  - When org policy and release risk allow it, consider `--test-level RunRelevantTests` for Apex-heavy deployments.
166
- - Pair this with modern Apex test annotations such as `@IsTest(testFor=...)` and `@IsTest(isCritical=true)` — see [platform-apex-generate](../platform-apex-generate/SKILL.md) for authoring guidance.
267
+ - Pair this with modern Apex test annotations such as `@IsTest(testFor=...)` and `@IsTest(isCritical=true)`.
167
268
 
168
269
  Static analysis now uses **Code Analyzer v5** (`sf code-analyzer`), not retired `sf scanner`.
169
270
 
@@ -185,9 +286,7 @@ For full agent DevOps details, including `Agent:` pseudo metadata, publish/activ
185
286
 
186
287
  | Need | Delegate to | Reason |
187
288
  |---|---|---|
188
- | custom object creation | [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md) | define objects before deploy |
189
- | custom field creation | [platform-custom-field-generate](../platform-custom-field-generate/SKILL.md) | define fields before deploy |
190
- | Apex authoring / fixes | [platform-apex-generate](../platform-apex-generate/SKILL.md) | code authoring and repair |
289
+ | custom object / field creation | [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md) | define objects and fields before deploy |
191
290
  | run tests after deployment | [platform-apex-test-run](../platform-apex-test-run/SKILL.md) | post-deploy test execution and coverage |
192
291
  | Flow creation / repair | [automation-flow-generate](../automation-flow-generate/SKILL.md) | Flow authoring and activation guidance |
193
292
  | test data or seed records | [platform-data-manage](../platform-data-manage/SKILL.md) | describe-first data setup and cleanup |
@@ -366,9 +366,9 @@ Pass the BotVersion number you intend to activate as the third argument.
366
366
  **Critical:** Dependencies must be deployed BEFORE the agent.
367
367
 
368
368
  ```text
369
- 1. Custom Objects/Fields (platform-custom-object-generate, platform-custom-field-generate)
369
+ 1. Custom Objects/Fields (platform-custom-object-generate)
370
370
  ↓
371
- 2. Apex Classes (platform-apex-generate)
371
+ 2. Apex Classes
372
372
  ↓
373
373
  3. Flows (automation-flow-generate)
374
374
  ↓
@@ -571,14 +571,13 @@ sf agent activate --api-name My_Agent --target-org myorg
571
571
  | From Skill | To Skill | Purpose |
572
572
  |------------|----------|---------|
573
573
  | agentforce-generate | platform-metadata-deploy | Publish and activate agents |
574
- | platform-apex-generate | platform-metadata-deploy | Deploy Apex before agent |
575
574
  | automation-flow-generate | platform-metadata-deploy | Deploy Flows before agent |
576
575
  | integration-connectivity-generate | platform-metadata-deploy | Deploy Named Credentials for external APIs |
577
576
 
578
577
  ### Integration Pattern
579
578
 
580
579
  ```bash
581
- # 1. platform-apex-generate creates InvocableMethod class
580
+ # 1. An InvocableMethod class is authored
582
581
  # 2. automation-flow-generate creates wrapper Flow
583
582
  # 3. agentforce-generate creates agent with flow:// action
584
583
  # 4. platform-metadata-deploy orchestrates deployment in correct order
@@ -11,7 +11,7 @@ This document details how platform-metadata-deploy fits into the multi-skill wor
11
11
  ┌─────────────────────────────────────────────────────────────────────────────┐
12
12
  │ STANDARD MULTI-SKILL ORCHESTRATION ORDER │
13
13
  ├─────────────────────────────────────────────────────────────────────────────┤
14
- │ 1. platform-custom-object-generate / platform-custom-field-generate │
14
+ │ 1. platform-custom-object-generate │
15
15
  │ └── Create object/field definitions (LOCAL files) │
16
16
  │ │
17
17
  │ 2. automation-flow-generate │
@@ -84,7 +84,7 @@ When deploying agents with external API integrations:
84
84
  ├─────────────────────────────────────────────────────────────────────────────┤
85
85
  │ 1. integration-connectivity-connected-app-configure → Create OAuth Connected App │
86
86
  │ 2. integration-connectivity-generate → Create Named Credential + External Service │
87
- │ 3. platform-apex-generate → Create @InvocableMethod (if needed) │
87
+ │ 3. Apex authoring → Create @InvocableMethod (if needed) │
88
88
  │ 4. automation-flow-generate → Create Flow wrapper │
89
89
  │ │
90
90
  │ 5. platform-metadata-deploy ◀── FIRST DEPLOYMENT │
@@ -169,7 +169,6 @@ sf agent activate --api-name AgentName --version N --target-org alias --json
169
169
  |------------|--------------|------|
170
170
  | platform-custom-object-generate | → platform-metadata-deploy | "Deploy objects to [org]" |
171
171
  | automation-flow-generate | → platform-metadata-deploy | "Deploy flow with --dry-run" |
172
- | platform-apex-generate | → platform-metadata-deploy | "Deploy classes with RunLocalTests" |
173
172
  | agentforce-generate | → platform-metadata-deploy | "Deploy and publish agent" |
174
173
 
175
174
  ---
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: platform-role-hierarchy-apply
3
+ description: "Bulk-realign an existing Salesforce UserRole hierarchy from an external org-chart artifact (PDF, xlsx, or plain-text list of role-and-parent pairs) in one admin-approved manifest. Clarify scope and whether users move with roles, resolve identities that do not match verbatim, diff the artifact against the current tree, and submit create, rename, reparent, empty-role delete, and user-assignment mutations together. Triggers on \"reorganize the role hierarchy\", \"apply this org chart to roles\", \"bulk update role parents\", \"realign roles from PDF\", \"restructure the role hierarchy\", \"reparent roles from this spreadsheet\". Do not trigger for a single role create, rename, or reparent with no other changes, or for asking which roles exist or how they are nested — that is `managing-roles`. Do not trigger for territory, account, or forecast hierarchies, or for authoring sharing rules."
4
+ metadata:
5
+ version: "1.0"
6
+ ---
7
+
8
+ # Role Hierarchy Restructure
9
+
10
+ Apply many UserRole changes from an artifact in one admin-approved manifest.
11
+
12
+ If the admin only asks which roles exist or how they are nested, hand off to `managing-roles`. Do not query, do not ask the step 0 questions, and do not submit.
13
+
14
+ ## Inputs
15
+
16
+ - PDF org chart (text-selectable), `.xlsx` / `.csv` with `role_name` and `parent_role_name` (empty parent = top-level), or plain text with one `Role: Parent` pair per line.
17
+ - Or a pre-parsed list of `{roleName, parentRoleName}` objects.
18
+ - Optional mapping hints when a chart name is not the org's `Name` or `DeveloperName`.
19
+
20
+ ## Flow
21
+
22
+ ### 0. Clarify before querying
23
+
24
+ Ask anything still unresolved. Do not guess.
25
+
26
+ - **Scope.** Default is selected branches: roles absent from the artifact stay. "Full hierarchy" still does not delete those roles unless the admin asked for each deletion.
27
+ - **User reassignment.** Default is no. Do not emit `AssignUser` or `MoveUser` without an explicit yes.
28
+ - **Ambiguous identities.** Resolve every role and user in one batch. See step 2.
29
+ - **Conflicts.** Two parents for one role, or a role parented to itself: stop until the admin picks.
30
+
31
+ If the artifact matches fewer than 20% of its roles to the current tree, stop and ask whether it is a greenfield replacement, the wrong file, or a mapping problem. Do not build a manifest until the admin confirms a greenfield replacement. Between 20% and 50%, show the fraction in the preview and continue.
32
+
33
+ ### 1. Read the current hierarchy
34
+
35
+ ```text
36
+ SELECT Id, DeveloperName, Name, ParentRoleId
37
+ FROM UserRole
38
+ LIMIT 10000
39
+ ```
40
+
41
+ Match artifact roles by `DeveloperName`, then case-insensitive `Name`.
42
+
43
+ ### 2. Parse the artifact and resolve identities
44
+
45
+ Normalize to `{roleName, parentRoleName, users?}` and show that list to the admin before the diff. A PDF that is not text-selectable cannot be parsed. Ask for a plain-text `Role: Parent` list.
46
+
47
+ Roles: exact `DeveloperName`, then case-insensitive `Name`. No fuzzy match. When more than one role matches, list each candidate's `DeveloperName` and `Id` and wait. Do not pick one. Unresolved names go back to step 0.
48
+
49
+ Users, only after reassignment was confirmed:
50
+
51
+ 1. 18-character id starting with `005` → `User.Id`
52
+ 2. Email → `User.Email`
53
+ 3. Username → `User.Username`
54
+ 4. Display name → `User.Name`, and only when exactly one active user matches
55
+
56
+ `IsActive = false` is a non-match. A user listed under two roles is a conflict. Ask which role that user should land under, or whether to drop the reassignment. Do not submit until the admin answers.
57
+
58
+ Stop on a cycle in the target graph. Report the path, for example `A → B → C → A`, and ask for a corrected artifact. Do not submit.
59
+
60
+ ### 3. Build the manifest
61
+
62
+ Each mutation has `operationId`, `operationType`, `dependsOnOperationIds`, and one nested payload. `dependsOnOperationIds` is the order that matters.
63
+
64
+ | operationType | Nested object | Use when |
65
+ |---|---|---|
66
+ | `CreateRole` | `createRole` | Role is in the artifact and not in the org. Set `roleReference`, `developerName`, `name`, and `parentRoleId` or `parentRoleReference`. Default the three account-owner access fields to the org defaults when the artifact omits them. |
67
+ | `ReparentRole` | `reparentRole` | Role exists and the parent differs. Set `targetRoleId` or `targetRoleReference`, `expectedParentRoleId`, and `desiredParentRoleId` or `desiredParentRoleReference`. |
68
+ | `RenameRole` | `renameRole` | Same role, different label or developer name. Set the expected and desired name fields. |
69
+ | `DeleteRole` | `deleteRole` | Admin asked to remove an empty role. Set `targetRoleId`, `expectedParentRoleId`, `expectedAssignedUserIds`, and `expectedDirectChildRoleIds`. |
70
+ | `AssignUser` or `MoveUser` | `userRoleAssignment` | Reassignment was confirmed. Set `userId`, `expectedCurrentRoleId`, and `desiredRoleId` or `desiredRoleReference`. |
71
+
72
+ `DeleteRole` applies only when the role has no direct children and no assigned users at the moment it runs. Reparent those children and move those users in earlier mutations, and put those `operationId`s in `dependsOnOperationIds`. The delete's expected user and child lists are then empty. If the admin declined reassignment, do not delete a role that still has users.
73
+
74
+ A mutation that names a role neither in the org nor created in this manifest is `missing-parent`. Name the roles that point at it. Ask whether to add a `CreateRole` for it in this manifest, skip those rows, or abort. Do not submit with that parent missing.
75
+
76
+ A cycle in `dependsOnOperationIds` blocks the submit. Give every new role a `roleReference` string and point parents and assignments at that string.
77
+
78
+ ### 4. Validate, then preview
79
+
80
+ Block the submit when any of these remain: a cycle, a missing parent, a duplicate `developerName`, a dependency cycle, an `AssignUser` or `MoveUser` without the step 0 yes, a `DeleteRole` whose expected user or child lists are not empty, or overlap under 20% without a confirmed greenfield replacement.
81
+
82
+ On a duplicate `developerName`, show both rows and wait until the admin picks one or renames one.
83
+
84
+ Warn in the preview, and continue only after an explicit yes:
85
+
86
+ - A changed role is referenced by `Territory2.UserRoleId` or a forecast hierarchy override.
87
+ - A changed role has `PortalType != 'None'`.
88
+ - A changed role has more than 500 users (`SELECT COUNT() FROM User WHERE UserRoleId = :id`).
89
+
90
+ ```text
91
+ Restructure preview
92
+ -------------------
93
+ ReparentRole : 42
94
+ CreateRole : 3
95
+ RenameRole : 1
96
+ DeleteRole : 2
97
+ MoveUser : 2
98
+
99
+ Territory / forecast references : 1
100
+ Portal roles : 0
101
+ Roles with more than 500 users : 2
102
+
103
+ Approve this restructure? [yes / no]
104
+ ```
105
+
106
+ `yes` submits. Anything else does not. Roles left in the org and absent from the artifact stay in place.
107
+
108
+ ### 5. Submit
109
+
110
+ ```text
111
+ POST /services/data/v{version}/sharing/role-hierarchy-restructure
112
+ {
113
+ "operation": "Save",
114
+ "mutations": [
115
+ {
116
+ "operationId": "reparent-sales-vp",
117
+ "operationType": "ReparentRole",
118
+ "dependsOnOperationIds": [],
119
+ "reparentRole": {
120
+ "targetRoleId": "00E000000000001",
121
+ "expectedParentRoleId": "00E000000000002",
122
+ "desiredParentRoleReference": "cro"
123
+ }
124
+ },
125
+ {
126
+ "operationId": "delete-legacy-region",
127
+ "operationType": "DeleteRole",
128
+ "dependsOnOperationIds": ["reparent-legacy-children"],
129
+ "deleteRole": {
130
+ "targetRoleId": "00E000000000003",
131
+ "expectedParentRoleId": "00E000000000002",
132
+ "expectedAssignedUserIds": [],
133
+ "expectedDirectChildRoleIds": []
134
+ }
135
+ }
136
+ ]
137
+ }
138
+ ```
139
+
140
+ `CreateRole`, `RenameRole`, `AssignUser`, and `MoveUser` use the same envelope with their nested object from step 3.
141
+
142
+ Send the approved manifest once. Do not edit `UserRole` records one by one.
143
+
144
+ ### 6. Read the response
145
+
146
+ - `Applied` — structural and sharing changes committed.
147
+ - `AppliedSharingPending` — hierarchy is live; sharing catches up on its own. Do not poll.
148
+ - `AsyncExecutionRequired` — the server queued the manifest. Do not poll.
149
+ - `RoleOperationInProgress` — another restructure is running. Stop. Do not queue the manifest. Ask the admin to retry once the other operation finishes.
150
+ - `NotApplied` — nothing committed. Show `reasonCodes` and stop.
151
+ - `404` on submit — API not enabled for this client tier. Halt, tell the admin, do not retry, do not fall back to per-record `/sobjects/UserRole` edits.
152
+
153
+ For each `operationResults[]` entry, `outcome` is `Applied`, `NotApplied`, or `DependencyNotApplied`. Show `reasonCode` for any entry that is not `Applied`. Do not retry and do not roll back.
154
+
155
+ ## Out of scope
156
+
157
+ - A single role create, rename, or reparent with no other changes, and any request that only asks which roles exist or how they are nested (`managing-roles`).
158
+ - Territory, account, and forecast hierarchies.
159
+ - Authoring sharing rules.
160
+ - Image-only PDFs. Ask for text.
161
+ - Rollback. Report what the response says was committed.
162
+ ---
163
+
164
+ <!-- skill-validate: ignore-start -->
165
+ <!-- codey:operations start -->
166
+ ## Operations Reference
167
+
168
+ Operations this skill uses — routing and dependency reference.
169
+
170
+ | Operation | Purpose | Status | Call | Depends on |
171
+ |-----------|---------|--------|------|------------|
172
+ | `read-role-hierarchy` | read | — | `GET /services/data/v68.0/query` | — |
173
+ | `count-users-per-role` | read | — | `GET /services/data/v68.0/query` | `read-role-hierarchy` |
174
+ | `submit-role-hierarchy-restructure` | write | — | `POST /services/data/v68.0/sharing/role-hierarchy-restructure` | `read-role-hierarchy` |
175
+ | `verify-role-hierarchy-restructure` | verify | — | `GET /services/data/v68.0/query` | `submit-role-hierarchy-restructure` |
176
+ <!-- codey:operations end -->
177
+ <!-- skill-validate: ignore-end -->