@salesforce/afv-skills 1.55.0 → 1.56.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 (220) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-generate/SKILL.md +8 -0
  3. package/skills/agentforce-generate/references/actions-reference.md +2 -2
  4. package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
  5. package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
  6. package/skills/agentforce-observe/SKILL.md +13 -9
  7. package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
  8. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
  9. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
  10. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  11. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
  12. package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
  13. package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
  14. package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
  15. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
  16. package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
  17. package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
  18. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
  19. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
  20. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
  21. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
  22. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
  23. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
  24. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
  25. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
  31. package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
  32. package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
  33. package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
  34. package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
  35. package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
  36. package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
  37. package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
  38. package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
  39. package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
  40. package/skills/dx-org-analyze/SKILL.md +0 -1
  41. package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
  42. package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
  43. package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
  44. package/skills/dx-org-manage/SKILL.md +6 -50
  45. package/skills/dx-org-manage/examples/README.md +10 -16
  46. package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
  47. package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
  48. package/skills/dx-org-shape-manage/SKILL.md +4 -3
  49. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
  50. package/skills/experience-accessibility-validate/SKILL.md +8 -5
  51. package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
  52. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
  53. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
  54. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
  55. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
  56. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
  57. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
  58. package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
  59. package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
  60. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
  61. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
  62. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
  63. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
  64. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
  65. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
  66. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
  67. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
  68. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
  69. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
  70. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
  71. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
  72. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
  73. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
  74. package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
  75. package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
  76. package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
  77. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
  78. package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
  79. package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
  80. package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
  81. package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
  82. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
  83. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
  84. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
  85. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
  86. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
  87. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
  88. package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
  89. package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
  90. package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
  91. package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
  92. package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
  93. package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
  94. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
  95. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
  96. package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
  97. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
  98. package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
  99. package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
  100. package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
  101. package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
  102. package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
  103. package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
  104. package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
  105. package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
  106. package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
  107. package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
  108. package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
  109. package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
  110. package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
  111. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
  112. package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
  113. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
  116. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
  117. package/skills/mobile-apps-create/SKILL.md +1 -1
  118. package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
  119. package/skills/platform-sandbox-configure/SKILL.md +108 -133
  120. package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
  121. package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
  122. package/skills/platform-widget-generate/SKILL.md +26 -18
  123. package/skills/platform-widget-generate/examples/formulas.json +191 -0
  124. package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
  125. package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
  126. package/skills/sales-call-scoring-configure/SKILL.md +318 -0
  127. package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
  128. package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
  129. package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
  130. package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
  131. package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
  132. package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
  133. package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
  134. package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
  135. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
  136. package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
  137. package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
  138. package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
  139. package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
  140. package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
  141. package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
  142. package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
  143. package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
  144. package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
  145. package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
  146. package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
  147. package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
  148. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
  149. package/skills/service-de-channel-activate/SKILL.md +6 -5
  150. package/skills/service-de-channel-create/SKILL.md +3 -3
  151. package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
  152. package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
  153. package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
  154. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
  155. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
  156. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
  157. package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
  158. package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
  159. package/skills/service-helpagent-coordinate/SKILL.md +3 -2
  160. package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
  161. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
  162. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
  163. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
  164. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
  165. package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
  166. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
  167. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
  168. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
  169. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
  170. package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
  171. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
  172. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
  173. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
  174. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
  175. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
  176. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
  177. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
  178. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
  179. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
  180. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
  181. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
  182. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
  183. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
  184. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
  185. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
  186. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
  187. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
  188. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
  189. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
  190. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
  191. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
  192. package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
  193. package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
  194. package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
  195. package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
  196. package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
  197. package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
  198. package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
  199. package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
  200. package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
  201. package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
  202. package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
  203. package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
  204. package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
  205. package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
  206. package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
  207. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
  208. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
  209. package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
  210. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  211. package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
  212. package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
  213. package/skills/dx-org-manage/references/cli_flags.md +0 -67
  214. package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
  215. package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
  216. package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
  217. package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
  218. package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
  219. /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
  220. /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: automation-sandbox-post-copy-configure
3
- description: "Apply a Salesforce sandbox post-copy automation JSON config against a target org. For each entry, the skill derives the correct Tooling API sobject from the entry's `ConfigurationName`, verifies the derivation via a describe probe, resolves the record Id via SOQL-over-REST, then PATCHes the record via the compound `Metadata` field using `sf api request rest`. Use when the user asks to apply, run, execute, dry-run, or preview a post-copy or post-refresh config file (e.g. `post-copy-config.json`) against a sandbox. Trigger phrases: \"apply post-copy config\", \"run post-copy automation\", \"execute sandbox post-refresh JSON\", \"apply sandbox refresh config\", \"configure sandbox after refresh\". DO NOT TRIGGER for generating the config JSON from an SOP (delegate to `automation-sandbox-post-copy-config-generate`), or for deploying metadata XML."
3
+ description: "Apply a Salesforce sandbox post-copy automation JSON config against a target org. For each entry, the skill derives the correct Tooling API sobject from the entry's `ConfigurationName`, verifies the derivation via a describe probe, resolves the record Id via SOQL-over-REST, then PATCHes the record via the compound `Metadata` field using `sf api request rest`. `ScheduledApex` entries run anonymous Apex `System.schedule(...)` via `sf apex run` (because `CronTrigger` is read-only) and verify via a `CronTrigger` SOQL read-back. Use when the user asks to apply, run, execute, dry-run, or preview a post-copy or post-refresh config file (e.g. `post-copy-config.json`) against a sandbox. Trigger phrases: \"apply post-copy config\", \"run post-copy automation\", \"execute sandbox post-refresh JSON\", \"apply sandbox refresh config\", \"configure sandbox after refresh\". DO NOT TRIGGER for generating the config JSON from an SOP (delegate to `automation-sandbox-post-copy-config-generate`), or for deploying metadata XML."
4
4
  allowed-tools: Bash Read Write
5
5
  metadata:
6
6
  relatedSkills:
7
7
  - "automation-sandbox-post-copy-config-generate"
8
8
  - "dx-org-permission-set-assign"
9
9
  - "platform-metadata-deploy"
10
- version: "1.0"
10
+ version: "1.1"
11
11
  domains: ["Automation"]
12
12
  cliTools:
13
13
  - tool: ["curl"]
@@ -23,15 +23,15 @@ metadata:
23
23
  # Automation: Sandbox Post-Copy Configure
24
24
 
25
25
  Apply a Salesforce sandbox post-copy automation JSON config to a target
26
- org. The skill pins the Tooling API sobject **and** the record-lookup
27
- SOQL filter for the two canonical `ConfigurationName` values it has
28
- been calibrated against (`OutboundMessages`, `RemoteSiteSettings`); for
29
- any other `ConfigurationName` it derives a candidate from the entry
30
- value and verifies it against the live org's describe endpoint. Every
31
- entry — pinned or derived — must still pass Step B (describe returns
32
- 200 with a `Metadata` compound field) before any PATCH is planned.
33
- Entries whose API cannot be identified or verified are surfaced in the
34
- summary and skipped — they are never guessed at.
26
+ org. Three canonical `ConfigurationName` values are pinned:
27
+ `OutboundMessages` and `RemoteSiteSettings` take the compound-`Metadata`
28
+ PATCH route (Steps A–E); `ScheduledApex` takes the anonymous-Apex route
29
+ (Step F), because `CronTrigger` is read-only in the Tooling API. Any
30
+ other `ConfigurationName` is derived and describe-verified. Every A–E
31
+ entry — pinned or derived — must pass Step B (describe returns 200 with
32
+ a `Metadata` compound field) before any PATCH is planned; Step B does
33
+ not apply to `ScheduledApex`. Entries whose API cannot be verified are
34
+ surfaced in the summary and skipped — never guessed at.
35
35
 
36
36
  ## Tool Restrictions
37
37
 
@@ -92,6 +92,7 @@ substitute a different sobject name):**
92
92
  |-------------------|---------------------|--------------------|
93
93
  | `OutboundMessages` | `WorkflowOutboundMessage` | `SELECT Id, FullName FROM WorkflowOutboundMessage WHERE EntityDefinition.QualifiedApiName = '<Fields.Object>'` — then client-side pick the row whose `FullName == '<Fields.Object>.<Label>'`. SOQL cannot filter on `FullName` directly for this sobject. |
94
94
  | `RemoteSiteSettings` | `RemoteProxy` | `SELECT Id, SiteName FROM RemoteProxy WHERE SiteName = '<Label>'`. |
95
+ | `ScheduledApex` | `CronTrigger` (read-only — Anonymous Apex route, see Step F) | See Step F — pre-flight `SELECT Id FROM CronTrigger WHERE CronJobDetail.Name = '<escaped>'` gate. |
95
96
 
96
97
  For canonical entries use the pinned sobject and SOQL as-is; skip
97
98
  "derive". Do not invent `OutboundMessage`, `RemoteSiteSetting`, or
@@ -208,6 +209,13 @@ If the read-back value doesn't match the requested value, record
208
209
  `FAILED_VERIFY` — the PATCH returned 204 but the effect is not
209
210
  visible (usually a naming or permission issue).
210
211
 
212
+ ### Step F — Scheduled Apex (anonymous Apex route)
213
+
214
+ `ScheduledApex` entries target the read-only `CronTrigger` sobject —
215
+ Steps A–E do not apply. **Read `references/scheduled_apex_path.md`
216
+ before executing** — it owns the full F-1 → F-4 recipe (pre-flight
217
+ AMBIGUOUS gate, snippet build, `sf apex run`, verify, dry-run).
218
+
211
219
  ---
212
220
 
213
221
  ## IsActive semantics
@@ -233,22 +241,21 @@ side-files (`plan/phases.json`, `requests/*.request.json`, etc.) —
233
241
  inline every planned/actual request in the Markdown.
234
242
 
235
243
  **Phase enumeration is script-owned.** Run `node scripts/plan-phases.mjs
236
- <config.json>` and consume its `phases[]` output verbatim. Each entry
237
- carries `ordinal` (1-indexed phase number) and `executionOrder` (raw
238
- value, for the `(ExecutionOrder = <raw>)` heading annotation). Sparse
239
- values collapse (`1, 2, 5` → ordinals `1, 2, 3`). `IsActive:false`
244
+ <config.json>` and consume its `phases[]` verbatim — each entry carries
245
+ `ordinal` (1-indexed for headings) and `executionOrder` (raw, for
246
+ `(ExecutionOrder = <raw>)`); sparse values collapse; `IsActive:false`
240
247
  entries are pre-marked `SKIP_INACTIVE`. See
241
248
  `references/execution_phasing.md` for the worked example.
242
249
 
243
- For dry-run entries the `HTTP` column is `—` (em-dash). End the
244
- summary with: `No PATCH requests were issued. To apply, re-run
245
- without the dry-run flag.`
246
-
247
250
  **Target-org resolution is script-owned.** Run `node
248
- scripts/resolve-target-org.mjs`; substitute the returned `.alias`
249
- into the header. Never emit `<env:SF_TARGET_ORG>` or `$SF_TARGET_ORG`
251
+ scripts/resolve-target-org.mjs`; substitute the returned `.alias` into
252
+ the header. Never emit `<env:SF_TARGET_ORG>` or `$SF_TARGET_ORG`
250
253
  verbatim.
251
254
 
255
+ For dry-run entries the `HTTP` column is `—`. End the summary with:
256
+ `No PATCH requests were issued. To apply, re-run without the dry-run
257
+ flag.`
258
+
252
259
  ```markdown
253
260
  # Post-Copy Configure Run — <N> entries <planned|applied> against `<alias>`
254
261
 
@@ -287,12 +294,9 @@ glance. Outcome vocabulary: `SUCCESS`, `NOT_FOUND`, `AMBIGUOUS`,
287
294
  `SKIPPED_INACTIVE`, `SKIPPED`, `DRY_RUN`, `DELETE_NOT_SUPPORTED`,
288
295
  `NOT_ATTEMPTED`.
289
296
 
290
- **Scripts are internal.** The `plan-phases.mjs`, `map-metadata-key.mjs`,
291
- `classify-patch-result.mjs`, `resolve-target-org.mjs` invocations are
292
- implementation detail — do **not** inline their raw stdout, JSON
293
- output, or "I ran node …" narration into the summary Markdown or
294
- the printed response. Consume the JSON, use the returned values, and
295
- render the summary in the exact shape above.
297
+ **Scripts are internal.** Do **not** inline raw `node scripts/…` stdout
298
+ or "I ran node …" narration into the summary or the printed response —
299
+ consume the JSON, use the returned values, render the exact shape above.
296
300
 
297
301
  ## Scope
298
302
 
@@ -326,24 +330,17 @@ Gather or infer before applying:
326
330
  the target sandbox. Never assume the default org — always confirm.
327
331
  If the user has not supplied one, list available orgs with `sf org
328
332
  list --json` and ask which to use.
329
- - **Dry-run flag** (optional, default `false`): Semantics —
330
- **reads are ALLOWED, writes are FORBIDDEN**. Dry-run means the run
331
- produces no mutation on the target org; it does NOT mean "no
332
- network calls". You MUST still execute every read against the org:
333
- the describe probe (Step B, `GET`), the SOQL Id lookup (Step C,
334
- `sf data query`), and the record fetch (Step D-1, `GET`) — because
335
- the plan's "would-be PATCH body" can only be accurate if the
336
- agent has read the current `Metadata` block from the live record.
337
- The ONLY skipped calls are the writes: Step D-3 (`PATCH`) and
338
- Step E (post-PATCH verification). Never fabricate the current
339
- Metadata; never emit a body containing invented keys. If a read
340
- fails (401, 404, `NamedOrgNotFoundError`), surface the error and
341
- stop — do not fall back to a from-memory plan. Use dry-run on the
342
- first pass unless the user has explicitly asked to apply.
343
- - **Continue-on-error** (optional, default `true`): When true, a
344
- failing entry does not abort the phase — the remaining entries in
345
- the phase still execute, and the failure is reported in the
346
- summary. When false, a failure aborts execution mid-phase.
333
+ - **Dry-run flag** (optional, default `false`): **reads ALLOWED, writes
334
+ FORBIDDEN** — not "no network calls". Still run every read (Step B
335
+ describe `GET`, Step C SOQL, Step D-1 record `GET`); the plan's
336
+ would-be PATCH body is only accurate against the real Metadata block.
337
+ The ONLY skipped calls are Step D-3 (`PATCH`) and Step E (verify).
338
+ Never fabricate current Metadata or invent keys. On a read failure
339
+ (401, 404, `NamedOrgNotFoundError`), surface and stop — never fall
340
+ back to a from-memory plan. Prefer dry-run on the first pass.
341
+ - **Continue-on-error** (optional, default `true`): true = failing
342
+ entries don't abort the phase (failure reported in the summary);
343
+ false = abort mid-phase.
347
344
 
348
345
  If the user supplies a clear config path and target alias, proceed
349
346
  without further questions.
@@ -352,9 +349,8 @@ without further questions.
352
349
 
353
350
  ## Workflow
354
351
 
355
- Every step executes real `sf` CLI commands via the Bash tool. Do NOT
356
- narrate the plan without actually running the commands — the point of
357
- this skill is to mutate the target org, not to describe how.
352
+ Every step executes real `sf` CLI commands via Bash. Do NOT narrate the
353
+ plan without running the commands — this skill mutates the target org.
358
354
 
359
355
  1. **Read and validate the config JSON** — load the file with the
360
356
  Read tool. Every entry must be a JSON object with the five
@@ -371,18 +367,21 @@ this skill is to mutate the target org, not to describe how.
371
367
  `references/execution_phasing.md` for the concurrency cap.
372
368
 
373
369
  4. **Per-entry describe-verify pass** — for each distinct
374
- `ConfigurationName`, run Step A + Step B once and cache the
375
- verified sobject. Any `ConfigurationName` failing Step B marks
376
- every entry with that name as `API_NOT_IDENTIFIED`.
370
+ `ConfigurationName` NOT equal to `ScheduledApex`, run Step A +
371
+ Step B once and cache the verified sobject. Any `ConfigurationName`
372
+ failing Step B marks every entry with that name as
373
+ `API_NOT_IDENTIFIED`. `ScheduledApex` skips describe-verify — it
374
+ uses the Anonymous Apex route (Step F), not compound-`Metadata`
375
+ PATCH.
377
376
 
378
377
  5. **Per-entry apply pass** — for each entry inside each phase:
379
- - Entries pre-marked `SKIP_INACTIVE` by plan-phases → record
378
+ - `SKIP_INACTIVE` pre-marked by plan-phases → record
380
379
  `SKIPPED_INACTIVE`, add a Follow-ups bullet, continue.
381
- - Else run Step C (resolve Id), then Step D (GET+mutate+PATCH),
382
- then Step E (verify). If `--dry-run` is true, still run Step C
383
- and D-1/D-2 so the printed plan reflects the real merged
384
- payload — skip only D-3 (the PATCH) and Step E, then record
385
- `DRY_RUN`.
380
+ - `ScheduledApex` → Step F (F-1 pre-flight, F-2 build, F-3 run,
381
+ F-4 verify). Dry-run: run F-1 + F-2 only; record `DRY_RUN`.
382
+ - All others → Step C (Id), Step D (GET+mutate+PATCH), Step E
383
+ (verify). Dry-run: run C + D-1/D-2 (so the printed plan reflects
384
+ the real merged payload); skip D-3 and E; record `DRY_RUN`.
386
385
 
387
386
  6. **Between phases** — wait for every entry in the current phase to
388
387
  complete before starting the next.
@@ -415,9 +414,9 @@ that file before deviating from the Step A–E procedure.
415
414
  | Need | Delegate to |
416
415
  |------|-------------|
417
416
  | Turn a customer SOP into the JSON config this skill consumes | `automation-sandbox-post-copy-config-generate` |
418
- | Deploy Salesforce metadata XML (Custom Labels, Named Credentials, etc.) that lives outside the compound-Metadata Tooling API pattern | The matching `generating-*` skill + a metadata deploy flow |
419
- | Create new records that do not yet exist on the target org | `platform-metadata-deploy` after generating the metadata XML |
420
- | Assign permission sets required to run the API calls | `dx-org-permission-set-assign` |
417
+ | Deploy metadata XML outside the compound-Metadata Tooling pattern | Matching `generating-*` skill + a metadata deploy flow |
418
+ | Create new records that do not yet exist on the target org | `platform-metadata-deploy` |
419
+ | Assign permission sets required for the API calls | `dx-org-permission-set-assign` |
421
420
 
422
421
  ---
423
422
 
@@ -425,14 +424,19 @@ that file before deviating from the Step A–E procedure.
425
424
 
426
425
  | File | When to read |
427
426
  |------|-------------|
428
- | `references/api_endpoints.md` | Steps A–E — the full generic recipe with worked examples (OBM, RSS) and the camelCase-field convention |
429
- | `references/execution_phasing.md` | Step 3 (workflow) — the grouping rules and the intra-phase concurrency cap. `scripts/plan-phases.mjs` is the executable source-of-truth for phase enumeration; this file explains the model behind it |
430
- | `scripts/plan-phases.mjs` | Step 3 (workflow) — deterministic phase planner (invoke, then read its output) |
431
- | `scripts/map-metadata-key.mjs` | Step D-2 — deterministic Metadata-key resolver (override table + case rule + existence check) |
432
- | `scripts/classify-patch-result.mjs` | Step D-4 — deterministic HTTP-outcome classifier (SUCCESS vs FAILED) |
433
- | `scripts/resolve-target-org.mjs` | Canonical output shape — deterministic target-org alias resolver |
434
- | `references/authentication.md` | Step 2 (workflow) — for the session-check recipe and how to handle 401 mid-run |
435
- | `references/rules_gotchas.md` | Before deviating from Step A–E — load-bearing invariants and canonical runtime-failure responses |
436
- | `assets/api_request_templates.json` | Steps A–E — the generic template for describe / lookup / GET+PATCH with placeholder keys |
437
- | `examples/sample_config_input.json` | Step 1 (workflow) — shape of the config JSON this skill consumes |
438
- | `examples/sample_execution_summary.md` | Step 7 (workflow) — the shape of the summary report shown to the user |
427
+ | `references/api_endpoints.md` | Steps A–E — full generic recipe with OBM / RSS worked examples and the camelCase-field convention |
428
+ | `references/scheduled_apex_path.md` | Step F — anonymous Apex route for `ScheduledApex` entries |
429
+ | `references/execution_phasing.md` | Step 3 (workflow) — phase-grouping model behind `scripts/plan-phases.mjs` |
430
+ | `references/authentication.md` | Step 2 — session check + how to handle 401 mid-run |
431
+ | `references/rules_gotchas.md` | Before deviating from Step A–E — invariants and runtime-failure responses |
432
+ | `scripts/plan-phases.mjs` | Step 3 — deterministic phase planner |
433
+ | `scripts/map-metadata-key.mjs` | Step D-2 — deterministic Metadata-key resolver |
434
+ | `scripts/classify-patch-result.mjs` | Step D-4 — deterministic HTTP-outcome classifier |
435
+ | `scripts/resolve-target-org.mjs` | Canonical output shape — target-org alias resolver |
436
+ | `scripts/build-scheduled-apex.mjs` | Step F-2 — anonymous-Apex snippet builder |
437
+ | `scripts/soql-escape-job-name.mjs` | Step F-1/F-4 — SOQL-escape `JobName` |
438
+ | `assets/api_request_templates.json` | Steps A–E — generic describe / lookup / GET+PATCH template |
439
+ | `assets/scheduled_apex_template.apex` | Step F-2 — anonymous Apex template with `{{JOB_NAME}}` / `{{CRON_EXPRESSION}}` / `{{APEX_CLASS_NAME}}` placeholders |
440
+ | `examples/sample_config_input.json` | Step 1 — shape of the config JSON this skill consumes |
441
+ | `examples/sample_scheduled_apex_config.json` | Step 1 — shape of a `ScheduledApex` config entry |
442
+ | `examples/sample_execution_summary.md` | Step 7 — shape of the summary report shown to the user |
@@ -0,0 +1,13 @@
1
+ // Anonymous Apex template — schedules a Salesforce Schedulable class.
2
+ //
3
+ // Placeholders (single-quote-escaped by scripts/build-scheduled-apex.mjs):
4
+ // {{JOB_NAME}} — value of CronTrigger.CronJobDetail.Name after scheduling.
5
+ // {{CRON_EXPRESSION}} — 6- or 7-field Salesforce cron expression.
6
+ // {{APEX_CLASS_NAME}} — bare identifier of a class that implements Schedulable.
7
+ //
8
+ // Prerequisites (enforced upstream — see references/scheduled_apex_path.md):
9
+ // 1. Step F-1 has already asserted no CronTrigger with CronJobDetail.Name = '{{JOB_NAME}}'
10
+ // exists on the target org — otherwise System.schedule throws.
11
+ // 2. The {{APEX_CLASS_NAME}} class exists on the target org — sandbox refresh
12
+ // copies class metadata, so this is normally satisfied post-refresh.
13
+ System.schedule('{{JOB_NAME}}', '{{CRON_EXPRESSION}}', new {{APEX_CLASS_NAME}}());
@@ -0,0 +1,13 @@
1
+ [
2
+ {
3
+ "ConfigurationName": "ScheduledApex",
4
+ "Label": "Nightly Data Sync",
5
+ "Fields": {
6
+ "ApexClassName": "NightlyDataSyncScheduler",
7
+ "CronExpression": "0 0 2 * * ?",
8
+ "JobName": "Nightly Data Sync"
9
+ },
10
+ "IsActive": true,
11
+ "ExecutionOrder": 3
12
+ }
13
+ ]
@@ -0,0 +1,155 @@
1
+ # Step F — Scheduled Apex (anonymous Apex route)
2
+
3
+ `ScheduledApex` entries in the post-copy config target the `CronTrigger`
4
+ sobject, which is **read-only** in the Tooling API — there is no
5
+ compound `Metadata` field, so Steps A–E (describe → SOQL → GET+PATCH)
6
+ cannot apply. Instead this route runs anonymous Apex
7
+ `System.schedule(...)` via `sf apex run` and verifies via a
8
+ `CronTrigger` SOQL read-back.
9
+
10
+ ## Fields shape
11
+
12
+ Every `ScheduledApex` entry must carry these three keys under `Fields`:
13
+
14
+ | Key | Meaning |
15
+ |-----|---------|
16
+ | `ApexClassName` | Bare Apex identifier of the class that implements `Schedulable`. |
17
+ | `CronExpression` | Salesforce cron expression (6- or 7-field). |
18
+ | `JobName` | Human-readable job name. Appears in Setup > Scheduled Jobs; equals `CronTrigger.CronJobDetail.Name` post-schedule. |
19
+
20
+ If any of the three is missing, mark the entry `API_NOT_IDENTIFIED` and
21
+ skip. Never fabricate one from the others.
22
+
23
+ ## Prerequisites
24
+
25
+ The Apex `Schedulable` class named by `ApexClassName` must already exist
26
+ on the target sandbox. Sandbox refresh copies class metadata from source
27
+ to target, so this is normally satisfied on a fresh refresh — the reason
28
+ scheduled *jobs* need re-creation is that `CronTrigger` records (runtime
29
+ state) don't copy, not that the classes don't.
30
+
31
+ If the class is genuinely missing, F-3 (`sf apex run`) fails at compile
32
+ with `Variable does not exist: <ApexClassName>` — the outcome becomes
33
+ `FAILED` and the customer follows the Cross-Skill Integration row on
34
+ `platform-metadata-deploy` to deploy the class before re-running this
35
+ skill.
36
+
37
+ ## F-1 — Pre-flight AMBIGUOUS check
38
+
39
+ `System.schedule` throws `AlreadyScheduledException` if a job with the
40
+ same name already exists. Query first to avoid that path — and per skill
41
+ policy, do NOT auto-abort an existing job; surface it as `AMBIGUOUS` so
42
+ the customer decides.
43
+
44
+ `JobName` is human-readable and may legitimately contain an apostrophe
45
+ (e.g. `Owner's nightly job`). Never inline it into the SOQL literal
46
+ directly — escape it via the deterministic helper first, which also
47
+ rejects the same characters `scripts/build-scheduled-apex.mjs` rejects
48
+ (newlines/backslashes), so an input that passes F-1 will also pass F-2:
49
+
50
+ ```bash
51
+ JOB_NAME_LITERAL=$(node scripts/soql-escape-job-name.mjs "<Fields.JobName>") || {
52
+ # Non-zero exit → outcome FAILED with stderr in Follow-ups; skip F-2/F-3/F-4.
53
+ exit 1
54
+ }
55
+ SOQL="SELECT Id FROM CronTrigger WHERE CronJobDetail.Name = '$JOB_NAME_LITERAL'"
56
+ sf data query --json --query "$SOQL" > /tmp/entry-<Slug>-cron.json
57
+ COUNT=$(jq -r '.result.totalSize' /tmp/entry-<Slug>-cron.json)
58
+ ```
59
+
60
+ Outcomes:
61
+
62
+ - `COUNT == 0` → proceed to F-2.
63
+ - `COUNT >= 1` → outcome `AMBIGUOUS`. Add a Follow-ups bullet listing
64
+ the entry's `Fields.JobName` and every returned `CronTrigger.Id`.
65
+ Do not attempt to schedule.
66
+
67
+ In dry-run, still run F-1 — it is a read.
68
+
69
+ ## F-2 — Build the anonymous Apex snippet
70
+
71
+ Never author the snippet from memory — Apex string escaping is a common
72
+ foot-gun. Use the deterministic builder:
73
+
74
+ ```bash
75
+ node scripts/build-scheduled-apex.mjs \
76
+ "<Fields.JobName>" \
77
+ "<Fields.CronExpression>" \
78
+ "<Fields.ApexClassName>" \
79
+ assets/scheduled_apex_template.apex \
80
+ > /tmp/entry-<Slug>.apex
81
+ ```
82
+
83
+ The script:
84
+
85
+ - Verifies `CronExpression` is 6- or 7-field whitespace-separated.
86
+ - Verifies `ApexClassName` matches `^[A-Za-z_][A-Za-z0-9_]*$`.
87
+ - Rejects newlines and backslashes in any input (would either break the
88
+ string literal or enable Apex injection).
89
+ - Single-quote-escapes `JobName` and `CronExpression` for embedding in
90
+ the Apex single-quoted string.
91
+ - Writes the substituted `.apex` snippet to stdout.
92
+
93
+ Non-zero exit → outcome `FAILED` with stderr in Follow-ups.
94
+
95
+ ## F-3 — Execute the snippet
96
+
97
+ ```bash
98
+ sf apex run --file /tmp/entry-<Slug>.apex --json \
99
+ > /tmp/entry-<Slug>-apex.json
100
+ ```
101
+
102
+ Classify from the JSON response:
103
+
104
+ | `.result.compiled` | `.result.success` | Outcome | Follow-ups body |
105
+ |--------------------|-------------------|---------|-----------------|
106
+ | `false` | (any) | `FAILED` | `.result.compileProblem`, `.result.line`, `.result.column` |
107
+ | `true` | `false` | `FAILED` | `.result.exceptionMessage`, `.result.exceptionStackTrace` |
108
+ | `true` | `true` | proceed to F-4 | — |
109
+
110
+ Skip F-3 entirely in dry-run and record `DRY_RUN`.
111
+
112
+ ## F-4 — Verify the job scheduled
113
+
114
+ Reuse `$JOB_NAME_LITERAL` from F-1 — it is the same JobName, already
115
+ SOQL-escaped and validated:
116
+
117
+ ```bash
118
+ SOQL="SELECT Id, State, NextFireTime, CronExpression FROM CronTrigger WHERE CronJobDetail.Name = '$JOB_NAME_LITERAL'"
119
+ sf data query --json --query "$SOQL" > /tmp/entry-<Slug>-verify.json
120
+ ```
121
+
122
+ Outcomes:
123
+
124
+ - 0 rows → `FAILED_VERIFY` (apex reported success but no CronTrigger
125
+ materialized — usually a permission or platform limits issue).
126
+ - ≥1 row where `.CronExpression == '<Fields.CronExpression>'` → `SUCCESS`.
127
+ - ≥1 row with a different `CronExpression` → `FAILED_VERIFY` (something
128
+ else claimed the name after F-1 saw it clear).
129
+
130
+ Skip F-4 entirely in dry-run (nothing to verify).
131
+
132
+ ## Summary shape for a Step F entry
133
+
134
+ Reuse the phase table from the canonical output shape:
135
+
136
+ - `Sobject` column reads `CronTrigger`.
137
+ - `Describe` column reads `—` (no describe probe on this route).
138
+ - "Planned request" / "Request executed" is the anonymous Apex snippet
139
+ path (`/tmp/entry-<Slug>.apex`) and its contents inline, not a PATCH
140
+ path/body.
141
+ - `HTTP` column reads `—` (no direct HTTP status; `sf apex run` bundles
142
+ compile+execute into one response).
143
+
144
+ ## Dry-run recap
145
+
146
+ | Sub-step | Dry-run behavior |
147
+ |----------|------------------|
148
+ | F-1 SOQL pre-flight | run (read) |
149
+ | F-2 snippet build (writes to `/tmp` only) | run |
150
+ | F-3 `sf apex run` | SKIP → record `DRY_RUN` |
151
+ | F-4 verify SOQL | SKIP (nothing to verify) |
152
+
153
+ The "Planned request" printed in the dry-run summary for a Step F entry
154
+ is the full contents of the built `.apex` snippet, so the customer can
155
+ audit exactly what would have executed.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic Step F-2 helper: render assets/scheduled_apex_template.apex
3
+ // with a config entry's JobName / CronExpression / ApexClassName. Emits
4
+ // the substituted snippet to stdout; non-zero exit on any validation
5
+ // failure with a single-line human-readable reason on stderr.
6
+ //
7
+ // Kept out of SKILL.md prose per authoring standard A9 — string escaping
8
+ // and cron/identifier validation are deterministic and belong in scripts/.
9
+
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const [, , jobName, cronExpression, apexClassName, templatePath] = process.argv;
13
+
14
+ if (!jobName || !cronExpression || !apexClassName || !templatePath) {
15
+ process.stderr.write(
16
+ 'usage: node build-scheduled-apex.mjs <JobName> <CronExpression> <ApexClassName> <template-path>\n'
17
+ );
18
+ process.exit(2);
19
+ }
20
+
21
+ // Reject characters that break out of the Apex single-quoted string
22
+ // literal we substitute into. Newlines are a compile error; backslash
23
+ // is not a general escape in Apex string literals and lets a crafted
24
+ // value inject arbitrary Apex.
25
+ const forbidden = /[\r\n\\]/;
26
+ for (const [name, value] of [
27
+ ['JobName', jobName],
28
+ ['CronExpression', cronExpression],
29
+ ['ApexClassName', apexClassName],
30
+ ]) {
31
+ if (forbidden.test(value)) {
32
+ process.stderr.write(
33
+ `ERROR: ${name} contains a newline or backslash: ${JSON.stringify(value)}\n`
34
+ );
35
+ process.exit(2);
36
+ }
37
+ }
38
+
39
+ const cronFields = cronExpression.trim().split(/\s+/).length;
40
+ if (cronFields !== 6 && cronFields !== 7) {
41
+ process.stderr.write(
42
+ `ERROR: CronExpression must be 6 or 7 whitespace-separated fields; got ${cronFields}: ${JSON.stringify(cronExpression)}\n`
43
+ );
44
+ process.exit(2);
45
+ }
46
+
47
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(apexClassName)) {
48
+ process.stderr.write(
49
+ `ERROR: ApexClassName must be a bare Apex identifier (letters, digits, underscore, not starting with a digit): ${JSON.stringify(apexClassName)}\n`
50
+ );
51
+ process.exit(2);
52
+ }
53
+
54
+ const escapeApexSingleQuoted = (s) => s.replaceAll("'", "\\'");
55
+
56
+ const template = readFileSync(templatePath, 'utf8');
57
+ const rendered = template
58
+ .replaceAll('{{JOB_NAME}}', escapeApexSingleQuoted(jobName))
59
+ .replaceAll('{{CRON_EXPRESSION}}', escapeApexSingleQuoted(cronExpression))
60
+ .replaceAll('{{APEX_CLASS_NAME}}', apexClassName);
61
+
62
+ process.stdout.write(rendered);
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic JobName → SOQL-safe literal helper for Step F-1 / F-4
3
+ // pre-flight and verify SOQL. A human-readable JobName may legitimately
4
+ // contain characters (e.g. an apostrophe in "Owner's nightly job") that
5
+ // break out of a raw single-quoted SOQL literal. This helper rejects
6
+ // characters that would also break the F-2 Apex snippet (kept in
7
+ // lockstep with build-scheduled-apex.mjs) and escapes single quotes for
8
+ // SOQL, so both the pre-flight query and the anonymous Apex snippet
9
+ // operate on the same validated JobName.
10
+ //
11
+ // Exits 0 with the escaped literal on stdout, or non-zero with a
12
+ // single-line reason on stderr.
13
+
14
+ const [, , jobName] = process.argv;
15
+
16
+ if (jobName === undefined) {
17
+ process.stderr.write('usage: node soql-escape-job-name.mjs <JobName>\n');
18
+ process.exit(2);
19
+ }
20
+
21
+ if (/[\r\n\\]/.test(jobName)) {
22
+ process.stderr.write(
23
+ `ERROR: JobName contains a newline or backslash: ${JSON.stringify(jobName)}\n`
24
+ );
25
+ process.exit(2);
26
+ }
27
+
28
+ process.stdout.write(jobName.replaceAll("'", "\\'"));