@salesforce/afv-skills 1.45.0 → 1.46.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/afv-skills",
3
- "version": "1.45.0",
3
+ "version": "1.46.0",
4
4
  "description": "Salesforce skills for Agentforce Vibes",
5
5
  "license": "CC-BY-NC-4.0",
6
6
  "files": [
@@ -78,11 +78,10 @@ Generates flow metadata element by element. This step is **mandatory** and must
78
78
  - **result** (STRING): Result of the flow element generation. Contains the final flow metadata **only when `isComplete` is `true`**.
79
79
 
80
80
  **MANDATORY: Loop until complete. NEVER pause or ask the user to confirm continuation.**
81
- - A flow can have **any number of elements** (10, 15, or more). Each call generates one element at a time, so you may need **many** iterations. This is expected and normal.
81
+ - A flow can have **any number of elements**. Each call generates one element at a time, so you may need **many** iterations.
82
82
  - Call `flowElementGeneration` with the `operationId` from Step 2 and `requestSource` (use `"A4V"` for XML output, empty string or other value for JSON).
83
83
  - Check the `isComplete` output and the `result` field after each call.
84
- - If `isComplete` is `false` **and no errors are returned**, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2. **Do NOT ask the user if they want to continue. Do NOT pause. Do NOT summarize progress mid-loop. Just keep calling.**
85
- - **Do NOT stop** until `isComplete` is `true` **or** the invocable action returns errors. There is **no maximum** number of iterations — keep going regardless of how many calls it takes.
84
+ - If `isComplete` is `false` **and no errors are returned**, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2. **Do NOT ask the user if they want to continue. Do NOT pause. Do NOT summarize progress mid-loop. Just keep calling** — there is **no maximum** number of iterations.
86
85
  - When `isComplete` is `true`, extract the flow metadata from the `result` field.
87
86
  - If errors are returned, stop the loop and surface the error to the user.
88
87
 
@@ -91,6 +90,8 @@ Generates flow metadata element by element. This step is **mandatory** and must
91
90
  - DO NOT add new nodes, tags, attributes, or text (do not add missing labels, X/Y coordinates, etc.).
92
91
  - DO NOT remove any existing nodes.
93
92
 
93
+ **Canvas mode.** A net-new canvas-based flow needs a `<processMetadataValues>` `CanvasMode`=`AUTO_LAYOUT_CANVAS` entry (independent of `BuilderType`) or it opens in **Free-Form**. STRICT CONSTRAINTS forbid hand-editing the XML, so this must come from the generation pipeline — if the returned XML lacks it, surface a pipeline gap; do NOT hand-edit. (Non-canvas types like `CustomerLifecycle` carry none — don't flag those.)
94
+
94
95
  ## inflightMetadata Format
95
96
  **DATA TYPE: ARRAY (not string)**
96
97
 
@@ -208,6 +209,10 @@ To select records for a scheduled flow, put the record criteria in the **start e
208
209
 
209
210
  When the prompt says "runs once a week / every Sunday / daily at a time", the `userPrompt` should state: scheduled trigger, the frequency, the start time, and the record filter on the triggering object.
210
211
 
212
+ ### "Autolaunched" is literal
213
+
214
+ An **"autolaunched"**/"no-trigger" request builds a **no-trigger** flow (no `triggerType`, `object`, or `<schedule>` on start) — put "for each…/when X"/timing intent in the **body**, not the trigger; add one only if asked.
215
+
211
216
  ### Counting related records ("count all related X", "number of X")
212
217
 
213
218
  To store a count of records, use a **single** assignment element with `<operator>AssignCount</operator>`, assigning from the collection (the record-lookup result) into a Number variable. Do NOT emit two assignment elements for one count, and do NOT give two assignment elements the same `name` — duplicate assignment names, or two assignments doing one logical count, fail deployment. One lookup → one `AssignCount` assignment → one record update.
@@ -299,7 +304,7 @@ Then call Step 2 and Step 3 for this flow.
299
304
  "requestSource": "A4V"
300
305
  }
301
306
  ```
302
- Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
307
+ Loop as in Step 3 (same `operationId`, `requestSource: "A4V"`) until `isComplete` is `true` or errors return; extract the XML from `result` then.
303
308
 
304
309
  **Example 2: With custom objects from local sfdx project**
305
310
 
@@ -348,7 +353,7 @@ Call repeatedly with the same `operationId` until `isComplete` is `true` or erro
348
353
  "requestSource": "A4V"
349
354
  }
350
355
  ```
351
- Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
356
+ Loop exactly as in Example 1's Step 3.
352
357
 
353
358
  ## CRITICAL Verification Checklist (MUST VERIFY BEFORE AND AFTER EVERY FLOW GENERATION)
354
359
 
@@ -361,3 +366,4 @@ Call repeatedly with the same `operationId` until `isComplete` is `true` or erro
361
366
  - [ ] **groundingMetadata** from Step 1 output is passed directly to Step 2 input (already a string — do NOT serialize it again).
362
367
  - [ ] **Step 3** is called in a loop with the same `operationId` from Step 2, `requestSource` always `"A4V"`, until `isComplete` is `true` or errors are returned — no pausing, no asking the user to continue, no matter how many iterations. Extract the XML from `result` only when `isComplete` is `true`.
363
368
  - [ ] **Multi-flow**: Each flow's full pipeline is completed before starting the next, SEQUENTIALLY (never in parallel, never interleaved), and ALL requested flows are generated — do NOT stop after the first.
369
+ - [ ] **Canvas mode (read-only)**: a canvas-based net-new flow's XML carries a `<processMetadataValues>` `CanvasMode`=`AUTO_LAYOUT_CANVAS` entry. If absent, surface a pipeline gap — do NOT hand-edit (STRICT CONSTRAINTS forbid it). Non-canvas types like `CustomerLifecycle` carry none — don't flag those.
@@ -37,10 +37,11 @@ fi
37
37
  # Basic YAML syntax check (if python3 available)
38
38
  if command -v python3 &> /dev/null; then
39
39
  echo "Checking YAML syntax..."
40
- if python3 -c "
40
+ if python3 - "$CONFIG_FILE" 2>&1 <<'PYEOF'; then
41
41
  import yaml, sys
42
+ config_file = sys.argv[1]
42
43
  try:
43
- with open('${CONFIG_FILE}', 'r') as f:
44
+ with open(config_file, 'r') as f:
44
45
  config = yaml.safe_load(f)
45
46
  if config is None:
46
47
  print('WARNING: Config file is empty or contains only comments')
@@ -50,10 +51,10 @@ try:
50
51
  sys.exit(1)
51
52
  print('YAML syntax: OK')
52
53
  except yaml.YAMLError as e:
53
- print(f'ERROR: Invalid YAML syntax')
54
+ print('ERROR: Invalid YAML syntax')
54
55
  print(f' {e}')
55
56
  sys.exit(1)
56
- " 2>&1; then
57
+ PYEOF
57
58
  echo ""
58
59
  else
59
60
  echo ""
@@ -69,16 +70,19 @@ echo "Checking known fields..."
69
70
  VALID_TOP_LEVEL="config_root log_folder log_level rules engines ignores suppressions"
70
71
 
71
72
  if command -v python3 &> /dev/null; then
72
- python3 -c "
73
+ python3 - "$CONFIG_FILE" "$VALID_TOP_LEVEL" 2>&1 <<'PYEOF'
73
74
  import yaml, sys
74
75
 
75
- with open('${CONFIG_FILE}', 'r') as f:
76
+ config_file = sys.argv[1]
77
+ valid_top_level = sys.argv[2]
78
+
79
+ with open(config_file, 'r') as f:
76
80
  config = yaml.safe_load(f)
77
81
 
78
82
  if config is None:
79
83
  sys.exit(0)
80
84
 
81
- valid_fields = set('${VALID_TOP_LEVEL}'.split())
85
+ valid_fields = set(valid_top_level.split())
82
86
  unknown = set(config.keys()) - valid_fields
83
87
  if unknown:
84
88
  print(f'WARNING: Unknown top-level fields: {sorted(unknown)}')
@@ -100,12 +104,12 @@ if 'engines' in config and config['engines']:
100
104
  # Check ignores section
101
105
  if 'ignores' in config and config['ignores']:
102
106
  if 'files' not in config['ignores']:
103
- print('WARNING: ignores section should contain a \"files\" list')
107
+ print('WARNING: ignores section should contain a "files" list')
104
108
  elif not isinstance(config['ignores']['files'], list):
105
109
  print('ERROR: ignores.files must be a list of glob patterns')
106
110
  sys.exit(1)
107
111
  else:
108
- print(f'Ignore patterns: {len(config[\"ignores\"][\"files\"])} patterns configured')
112
+ print(f'Ignore patterns: {len(config["ignores"]["files"])} patterns configured')
109
113
 
110
114
  # Check rules section
111
115
  if 'rules' in config and config['rules']:
@@ -120,7 +124,7 @@ if 'rules' in config and config['rules']:
120
124
  print(f'WARNING: rules.{engine}.{rule_name}.severity = {sev} (expected 1-5 or Critical/High/Moderate/Low/Info)')
121
125
 
122
126
  print('')
123
- " 2>&1
127
+ PYEOF
124
128
  fi
125
129
 
126
130
  # Run sf code-analyzer config validation (if sf CLI available)
@@ -15,14 +15,55 @@ const filePath = process.argv[2];
15
15
  const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
16
16
  const runDir = data.runDir || "";
17
17
 
18
- // Group fixes by file
18
+ // Fix targets are confined to the current working directory (the project being
19
+ // fixed). loc.file and runDir come from the results JSON and are untrusted;
20
+ // resolving each target against cwd and rejecting anything that escapes it
21
+ // stops a crafted results file from writing arbitrary paths (e.g. /etc/passwd
22
+ // or ../../outside-project).
23
+ const baseDir = fs.realpathSync(process.cwd());
24
+
25
+ function isOutside(base, target) {
26
+ const rel = path.relative(base, target);
27
+ return rel === "" || rel.startsWith("..") || path.isAbsolute(rel);
28
+ }
29
+
30
+ function safeResolve(candidate) {
31
+ if (typeof candidate !== "string" || !candidate) return null;
32
+ let rel = candidate;
33
+ // Engine output often uses absolute paths under the scan runDir; normalize
34
+ // those to project-relative before confinement.
35
+ if (runDir && rel.startsWith(runDir)) rel = rel.substring(runDir.length + 1);
36
+ const resolved = path.resolve(baseDir, rel);
37
+ // Lexical guard: rejects absolute paths and `..` traversal.
38
+ if (isOutside(baseDir, resolved)) return null;
39
+ // Symlink-aware guard: path.resolve/relative do not follow symlinks, so a
40
+ // symlinked path component (e.g. `link/victim` where `link` -> outside the
41
+ // tree) would pass the lexical check and let writeFileSync escape. Fix
42
+ // targets are read before being written, so the file exists — resolve its
43
+ // real path and re-check. Writing to the realpath also avoids following a
44
+ // symlink at write time. A missing target (realpathSync throws) is skipped.
45
+ let real;
46
+ try {
47
+ real = fs.realpathSync(resolved);
48
+ } catch {
49
+ return null;
50
+ }
51
+ if (isOutside(baseDir, real)) return null;
52
+ return real;
53
+ }
54
+
55
+ // Group fixes by confined, absolute file path
19
56
  const fileFixesMap = new Map();
57
+ let fixesSkippedUnsafe = 0;
20
58
  data.violations.forEach(v => {
21
59
  if (v.fixes && v.fixes.length > 0) {
22
60
  v.fixes.forEach(fix => {
23
61
  const loc = fix.location;
24
- let filePath = loc.file;
25
- if (runDir && filePath.startsWith(runDir)) filePath = filePath.substring(runDir.length + 1);
62
+ const filePath = safeResolve(loc.file);
63
+ if (!filePath) {
64
+ fixesSkippedUnsafe++;
65
+ return;
66
+ }
26
67
 
27
68
  if (!fileFixesMap.has(filePath)) fileFixesMap.set(filePath, []);
28
69
  fileFixesMap.get(filePath).push({
@@ -83,4 +124,4 @@ fileFixesMap.forEach((fixes, filePath) => {
83
124
  }
84
125
  });
85
126
 
86
- console.log(JSON.stringify({ success: true, filesModified, fixesApplied, fixesSkipped, totalFixableFiles: fileFixesMap.size }));
127
+ console.log(JSON.stringify({ success: true, filesModified, fixesApplied, fixesSkipped, fixesSkippedUnsafe, totalFixableFiles: fileFixesMap.size }));
@@ -15,7 +15,32 @@
15
15
  // resource: https://...
16
16
  // description: Some description text
17
17
 
18
- const { execSync } = require("child_process");
18
+ const { execFileSync } = require("child_process");
19
+
20
+ /**
21
+ * Run `sf code-analyzer rules ...` safely.
22
+ *
23
+ * Arguments are passed as an argv array (NOT a shell string), so a rule name
24
+ * or --engine value can never be interpreted as shell syntax. Returns the
25
+ * command's stdout as text; never throws (a non-zero exit still yields the
26
+ * output the caller wants to parse).
27
+ */
28
+ function runCodeAnalyzerRules(extraArgs) {
29
+ try {
30
+ return execFileSync("sf", ["code-analyzer", "rules", ...extraArgs], {
31
+ encoding: "utf8",
32
+ timeout: 60000,
33
+ maxBuffer: 2 * 1024 * 1024,
34
+ stdio: ["ignore", "pipe", "pipe"],
35
+ });
36
+ } catch (err) {
37
+ // Preserve the original `2>&1` behavior: a non-zero exit that emitted
38
+ // partial stdout plus a diagnostic on stderr must surface BOTH streams, in
39
+ // order, so a genuine failure isn't misparsed as "rule not found".
40
+ const combined = String(err.stdout || "") + String(err.stderr || "");
41
+ return combined || (err.output && err.output.filter(Boolean).join("")) || "";
42
+ }
43
+ }
19
44
 
20
45
  function printUsage() {
21
46
  console.error(`Usage: node describe-rule.js <rule-name> [--engine <engine>]
@@ -52,25 +77,21 @@ for (let i = 1; i < args.length; i++) {
52
77
  // Build the rule selector for the lookup
53
78
  const selector = engine ? `${engine}:${ruleName}` : ruleName;
54
79
 
55
- // Run `sf code-analyzer rules` with --view detail to get full rule info
56
- let rawOutput;
57
- try {
58
- const cmd = `sf code-analyzer rules --rule-selector "${selector}" --view detail 2>&1`;
59
- rawOutput = execSync(cmd, {
60
- encoding: "utf8",
61
- timeout: 60000,
62
- maxBuffer: 2 * 1024 * 1024,
63
- });
64
- } catch (err) {
65
- // execSync throws on non-zero exit, but we still want the output
66
- rawOutput = err.stdout || err.stderr || (err.output && err.output.join("")) || "";
67
- if (!rawOutput) {
68
- console.log(JSON.stringify({
69
- status: "error",
70
- message: `Failed to run sf code-analyzer rules: ${err.message}`,
71
- }));
72
- process.exit(0);
73
- }
80
+ // Run `sf code-analyzer rules` with --view detail to get full rule info.
81
+ // `selector` is passed as a single argv element, so it cannot inject shell
82
+ // commands even if it contains metacharacters.
83
+ const rawOutput = runCodeAnalyzerRules([
84
+ "--rule-selector",
85
+ selector,
86
+ "--view",
87
+ "detail",
88
+ ]);
89
+ if (!rawOutput) {
90
+ console.log(JSON.stringify({
91
+ status: "error",
92
+ message: `Failed to run sf code-analyzer rules for selector "${selector}"`,
93
+ }));
94
+ process.exit(0);
74
95
  }
75
96
 
76
97
  // Parse the detail view output
@@ -224,12 +245,16 @@ function parseDetailOutput(output) {
224
245
  function tryGrepFallback(ruleName, engine) {
225
246
  try {
226
247
  const sel = engine || "Recommended";
227
- const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1 | grep -i "${ruleName}"`;
228
- const grepOutput = execSync(cmd, {
229
- encoding: "utf8",
230
- timeout: 60000,
231
- maxBuffer: 2 * 1024 * 1024,
232
- });
248
+ const output = runCodeAnalyzerRules(["--rule-selector", sel]);
249
+
250
+ // Case-insensitive substring filter done in JS. Previously this shelled out
251
+ // to `... | grep -i "${ruleName}"`, interpolating the untrusted rule name
252
+ // into a shell command; matching here removes that injection sink.
253
+ const needle = ruleName.toLowerCase();
254
+ const grepOutput = output
255
+ .split("\n")
256
+ .filter((line) => line.toLowerCase().includes(needle))
257
+ .join("\n");
233
258
 
234
259
  if (!grepOutput.trim()) return null;
235
260
 
@@ -273,12 +298,7 @@ function tryGrepFallback(ruleName, engine) {
273
298
  function tryFuzzyFallback(ruleName, engine) {
274
299
  try {
275
300
  const sel = engine || "Recommended";
276
- const cmd = `sf code-analyzer rules --rule-selector "${sel}" 2>&1`;
277
- const output = execSync(cmd, {
278
- encoding: "utf8",
279
- timeout: 60000,
280
- maxBuffer: 2 * 1024 * 1024,
281
- });
301
+ const output = runCodeAnalyzerRules(["--rule-selector", sel]);
282
302
 
283
303
  // Extract rule names from table output
284
304
  // Lines with rule data have: index name engine severity tags
@@ -2,11 +2,8 @@
2
2
  name: platform-apex-logs-debug
3
3
  description: "Salesforce debug log analysis and troubleshooting with 100-point scoring. TRIGGER when: user analyzes debug logs, hits governor limits, reads stack traces, or touches .log files from Salesforce orgs. DO NOT TRIGGER when: running Apex tests (use platform-apex-test-run), generating or fixing Apex code (use platform-apex-generate), or Agentforce session tracing (use agentforce-observe)."
4
4
  metadata:
5
- cliTools:
6
- - tool: ["jq"]
7
- semver: ">=1.6.0"
8
- - tool: ["sf"]
9
- semver: ">=2.0.0"
5
+ version: "1.1"
6
+ domains: ["Platform"]
10
7
  relatedSkills:
11
8
  - "agentforce-observe"
12
9
  - "platform-apex-generate"
@@ -14,8 +11,11 @@ metadata:
14
11
  - "platform-data-manage"
15
12
  - "platform-metadata-deploy"
16
13
  - "platform-soql-query"
17
- version: "1.1"
18
- domains: ["Platform"]
14
+ cliTools:
15
+ - tool: ["sf"]
16
+ semver: ">=2.0.0"
17
+ - tool: ["jq"]
18
+ semver: ">=1.6.0"
19
19
  ---
20
20
 
21
21
  # platform-apex-logs-debug: Salesforce Debug Log Analysis & Troubleshooting
@@ -2,11 +2,11 @@
2
2
  name: platform-custom-application-generate
3
3
  description: "Use this skill when users need to create or configure tab-based Salesforce Custom Applications with navigation, branding, and action overrides. Trigger when users mention custom apps, application metadata, app navigation, or organizing tabs into applications. Use when users want to create app containers for tabs and pages. Do NOT use when the goal is hosting a React UI bundle in the App Launcher — use experience-ui-bundle-custom-app-generate for that case."
4
4
  metadata:
5
- relatedSkills:
6
- - "experience-ui-bundle-custom-app-generate"
7
5
  version: "1.0"
8
6
  domains: ["Platform"]
9
7
  minApiVersion: "60.0"
8
+ relatedSkills:
9
+ - "experience-ui-bundle-custom-app-generate"
10
10
  ---
11
11
 
12
12
  ## When to Use This Skill
@@ -48,8 +48,8 @@ Custom applications (Lightning Apps) that group tabs and functionality to provid
48
48
  - **description**: Brief description of the application's purpose
49
49
  - **tabs**: Array of tab names to include
50
50
  - **utilityBar**: API name of the Utility Bar configuration
51
- - **brand**: ⚠️ HIGHLY RECOMMENDED - Branding configuration object (headerColor, shouldOverrideOrgTheme, footerColor)
52
- - **actionOverrides**: ⚠️ REQUIRED when custom record pages exist - Action override configuration (actionName, content, formFactor, type, pageOrSobjectType)
51
+ - **brand**: HIGHLY RECOMMENDED - Branding configuration object (headerColor, shouldOverrideOrgTheme, footerColor)
52
+ - **actionOverrides**: REQUIRED when custom record pages exist - Action override configuration (actionName, content, formFactor, type, pageOrSobjectType)
53
53
  - **profileActionOverrides**: Profile-specific action overrides (actionName, content, formFactor, pageOrSobjectType, type, profile)
54
54
  - **isNavAutoTempTabsDisabled**: Navigation behavior setting (default: false)
55
55
  - **isNavPersonalizationDisabled**: Personalization setting (default: false)
@@ -25,7 +25,7 @@ This document defines the mandatory constraints for generating CustomObject meta
25
25
 
26
26
  **File extension:** `.object-meta.xml`
27
27
 
28
- > **🔔 Description freshness — applies to EVERY object change, fields AND validation rules:** Whenever you add, update, or delete a field **or a validation rule** on an object, the `<description>` may now be stale. Before finishing, refresh it per **Section 3.B** (propose, confirm with the user, write). A validation-rule change counts exactly like a field change — the change is **not** done until the description has been reconciled. This is easy to forget on validation-rule edits/deletes — don't.
28
+ > **Description freshness — applies to EVERY object change, fields AND validation rules:** Whenever you add, update, or delete a field **or a validation rule** on an object, the `<description>` may now be stale. Before finishing, refresh it per **Section 3.B** (propose, confirm with the user, write). A validation-rule change counts exactly like a field change — the change is **not** done until the description has been reconciled. This is easy to forget on validation-rule edits/deletes — don't.
29
29
 
30
30
  ---
31
31
 
@@ -57,7 +57,7 @@ The following constraints must be true for the XML body to deploy successfully.
57
57
  - IF object has Master-Detail field → use `ControlledByParent`
58
58
  - IF a Master-Detail field is being added to an existing child object → that existing object's `<sharingModel>` must also be updated to `ControlledByParent`
59
59
 
60
- **❌ INCORRECT** — Will cause error: `Cannot set sharingModel to ReadWrite on a CustomObject with a MasterDetail relationship field`
60
+ **INCORRECT** — Will cause error: `Cannot set sharingModel to ReadWrite on a CustomObject with a MasterDetail relationship field`
61
61
  ```xml
62
62
  <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
63
63
  <label>Order Line Item</label>
@@ -67,7 +67,7 @@ The following constraints must be true for the XML body to deploy successfully.
67
67
  </CustomObject>
68
68
  ```
69
69
 
70
- **✅ CORRECT:**
70
+ **CORRECT:**
71
71
  ```xml
72
72
  <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
73
73
  <label>Order Line Item</label>
@@ -191,7 +191,7 @@ Do not create more than **2 Master-Detail relationships** for a single object. I
191
191
 
192
192
  Do NOT include the `<fullName>` tag at the root of the `.object-meta.xml` file. The API name is derived from the filename.
193
193
 
194
- **❌ INCORRECT:**
194
+ **INCORRECT:**
195
195
  ```xml
196
196
  <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
197
197
  <fullName>Vehicle__c</fullName> <!-- WRONG: Remove this -->
@@ -199,7 +199,7 @@ Do NOT include the `<fullName>` tag at the root of the `.object-meta.xml` file.
199
199
  </CustomObject>
200
200
  ```
201
201
 
202
- **✅ CORRECT:**
202
+ **CORRECT:**
203
203
  ```xml
204
204
  <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
205
205
  <label>Vehicle</label>
@@ -218,7 +218,7 @@ Validation rule names follow different conventions than custom fields.
218
218
  - Cannot contain two consecutive underscores
219
219
  - **Must NOT end with `__c`** (unlike custom fields)
220
220
 
221
- **❌ INCORRECT:**
221
+ **INCORRECT:**
222
222
  ```xml
223
223
  <validationRules>
224
224
  <fullName>Require_Start_Date__c</fullName> <!-- WRONG: Has __c suffix -->
@@ -229,7 +229,7 @@ Validation rule names follow different conventions than custom fields.
229
229
  ```
230
230
  **Error:** `The validation name can only contain alphanumeric characters, must begin with a letter, cannot end with an underscore...`
231
231
 
232
- **✅ CORRECT:**
232
+ **CORRECT:**
233
233
  ```xml
234
234
  <validationRules>
235
235
  <fullName>Require_Start_Date</fullName> <!-- CORRECT: No __c suffix -->
@@ -81,7 +81,7 @@ Also forbidden:
81
81
  <motif>Custom98: Truck</motif>
82
82
  </CustomTab>
83
83
  ```
84
- - **❌ WRONG** — do NOT add `<sobjectName>`, `<name>`, `<fullName>`, or `<label>`:
84
+ - **WRONG** — do NOT add `<sobjectName>`, `<name>`, `<fullName>`, or `<label>`:
85
85
  ```xml
86
86
  <CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
87
87
  <sobjectName>Space_Station__c</sobjectName> <!-- DEPLOYMENT ERROR -->
@@ -11,6 +11,10 @@ metadata:
11
11
  semver: ">=3.10.0"
12
12
  - tool: ["sf"]
13
13
  semver: ">=2.126.4"
14
+ mcpTools:
15
+ metadata-experts:
16
+ tools: ["execute_metadata_action"]
17
+ semver: ">=1.0.0"
14
18
  ---
15
19
 
16
20
  ## When to Use This Skill
@@ -4,6 +4,7 @@ description: "Use this skill when users need to create, generate, or validate Sa
4
4
  metadata:
5
5
  version: "1.0"
6
6
  domains: ["Platform"]
7
+ minApiVersion: "51.0"
7
8
  ---
8
9
 
9
10
  ## When to Use This Skill
@@ -2,6 +2,14 @@
2
2
  name: platform-soql-query
3
3
  description: "SOQL query generation, optimization, and analysis with 100-point scoring. Use this skill when the user needs SOQL/SOSL authoring or optimization: natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance or safety improvements for Salesforce queries. TRIGGER when: user writes, optimizes, or debugs SOQL/SOSL queries, touches .soql files, or asks about relationship queries, aggregates, or query performance. DO NOT TRIGGER when: bulk data operations (use platform-data-manage), Apex DML logic (use platform-apex-generate), or report/dashboard queries."
4
4
  metadata:
5
+ version: "1.1"
6
+ domains: ["Platform"]
7
+ relatedSkills:
8
+ - "experience-lwc-generate"
9
+ - "platform-apex-generate"
10
+ - "platform-apex-logs-debug"
11
+ - "platform-apex-test-run"
12
+ - "platform-data-manage"
5
13
  cliTools:
6
14
  - tool: ["jq"]
7
15
  semver: ">=1.6.0"
@@ -9,14 +17,6 @@ metadata:
9
17
  semver: ">=3.10.0"
10
18
  - tool: ["sf"]
11
19
  semver: ">=2.0.0"
12
- relatedSkills:
13
- - "experience-lwc-generate"
14
- - "platform-apex-generate"
15
- - "platform-apex-logs-debug"
16
- - "platform-apex-test-run"
17
- - "platform-data-manage"
18
- version: "1.1"
19
- domains: ["Platform"]
20
20
  ---
21
21
 
22
22
  # platform-soql-query: Salesforce SOQL Query Expert
@@ -2,11 +2,11 @@
2
2
  name: platform-value-set-generate
3
3
  description: "Use this skill when users need to create, generate, or validate a Salesforce global value set or customize a standard value set. Trigger when users mention a global value set, GlobalValueSet, standard value set, StandardValueSet, a reusable picklist, a picklist value set shared across fields, or customizing standard picklists like Industry, Lead Source, or Opportunity Stage. Also use when users hit deployment errors adding values to a standard picklist, referencing a value set from a custom field, or working with .globalValueSet-meta.xml or .standardValueSet-meta.xml files. DO NOT TRIGGER for an inline one-off picklist on a single field with no reuse, or for general custom field metadata work that does not involve a GlobalValueSet or StandardValueSet — use platform-custom-field-generate instead."
4
4
  metadata:
5
- relatedSkills:
6
- - "platform-custom-field-generate"
7
5
  version: "1.0"
8
6
  domains: ["Platform"]
9
7
  minApiVersion: "60.0"
8
+ relatedSkills:
9
+ - "platform-custom-field-generate"
10
10
  cliTools:
11
11
  - tool: ["sf"]
12
12
  semver: ">=2.0.0"