@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
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env python3
2
+ """Discover a caller flow's transitive subflow dependency tree from local XML.
3
+
4
+ Reads only the flow-meta.xml files already retrieved to disk (offline,
5
+ deterministic — never queries the org). Builds the transitive <subflows> graph,
6
+ topologically sorts it leaves-first, classifies each node
7
+ migratable / blocked / missing, and reports so a caller can be migrated in
8
+ dependency order and never deployed when a dependency can't be migrated.
9
+
10
+ Usage:
11
+ discover_subflow_tree.py <flows_dir> (<caller.flow-meta.xml> ... | --all) [--json OUT]
12
+
13
+ Exit codes:
14
+ 0 clean tree — every node is migratable
15
+ 1 >=1 node blocked / missing / cyclic (do NOT deploy the caller)
16
+ 2 usage error
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import sys
22
+ import xml.etree.ElementTree as ET
23
+ from pathlib import Path
24
+
25
+ NS = {'sf': 'http://soap.sforce.com/2006/04/metadata'}
26
+
27
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
28
+ from subflow_names import migrated_dc_flow_name # noqa: E402
29
+
30
+
31
+ def _findall(el, tag):
32
+ """Direct children with or without the metadata namespace."""
33
+ r = el.findall(f'sf:{tag}', namespaces=NS)
34
+ return r if r else el.findall(tag)
35
+
36
+
37
+ def _findtext(el, tag):
38
+ r = el.findtext(f'sf:{tag}', namespaces=NS)
39
+ return r if r is not None else el.findtext(tag)
40
+
41
+
42
+ def _index_flows(flows_dir):
43
+ """API name (filename stem) -> Path for every *.flow-meta.xml in flows_dir."""
44
+ index = {}
45
+ for p in sorted(Path(flows_dir).glob('*.flow-meta.xml')):
46
+ api_name = p.name[:-len('.flow-meta.xml')]
47
+ index[api_name] = p
48
+ return index
49
+
50
+
51
+ def _parse_flow(path):
52
+ """Return (processType, has_screens, [referenced_subflow_api_names])."""
53
+ root = ET.parse(path).getroot()
54
+ process_type = _findtext(root, 'processType')
55
+ has_screens = len(_findall(root, 'screens')) > 0
56
+ refs = []
57
+ for sf_el in _findall(root, 'subflows'):
58
+ fn = _findtext(sf_el, 'flowName')
59
+ if fn:
60
+ refs.append(fn)
61
+ return process_type, has_screens, refs
62
+
63
+
64
+ def _classify(api_name, path, process_type, has_screens):
65
+ """Return (status, reason) for a resolvable node."""
66
+ if process_type != 'FieldServiceMobile':
67
+ return 'blocked', (
68
+ f"processType is '{process_type}', not 'FieldServiceMobile' — "
69
+ "cannot become a DataCaptureFlow"
70
+ )
71
+ if not has_screens:
72
+ return 'blocked', (
73
+ "no <screens> element — pure automation flow, cannot become a "
74
+ "DataCaptureFlow"
75
+ )
76
+ return 'migratable', ''
77
+
78
+
79
+ def build_tree(flows_dir, callers):
80
+ """Build the transitive dependency tree for the given caller flow paths.
81
+
82
+ Returns {"order": [...leaves first...], "blocked": [...], "hasBlockers": bool}.
83
+ """
84
+ index = _index_flows(flows_dir)
85
+
86
+ nodes = {} # api_name -> node dict
87
+ callers_of = {} # api_name -> set of api_names that reference it
88
+ order = [] # post-order DFS accumulation (leaves first)
89
+ perm = set() # fully processed
90
+ stack = [] # current recursion path (for cycle detection)
91
+
92
+ def record_caller(child, parent):
93
+ if parent is not None:
94
+ callers_of.setdefault(child, set()).add(parent)
95
+
96
+ def visit(api_name, path, depth, parent):
97
+ record_caller(api_name, parent)
98
+
99
+ if api_name in perm:
100
+ # Already ordered; just note the (possibly deeper) reachability.
101
+ if parent is not None and api_name in nodes:
102
+ nodes[api_name]['callers'] = sorted(callers_of.get(api_name, set()))
103
+ return
104
+
105
+ if api_name in stack:
106
+ # Cycle: mark blocked, do not descend again.
107
+ node = nodes.get(api_name)
108
+ if node is None:
109
+ node = {
110
+ 'apiName': api_name, 'file': str(path) if path else None,
111
+ 'migratedDcName': migrated_dc_flow_name(api_name),
112
+ 'processType': None, 'status': 'blocked',
113
+ 'reason': 'part of a subflow cycle — cannot be topologically ordered',
114
+ 'depth': depth, 'callers': sorted(callers_of.get(api_name, set())),
115
+ }
116
+ nodes[api_name] = node
117
+ else:
118
+ node['status'] = 'blocked'
119
+ node['reason'] = 'part of a subflow cycle — cannot be topologically ordered'
120
+ return
121
+
122
+ stack.append(api_name)
123
+
124
+ if path is None:
125
+ # Referenced but no file on disk.
126
+ nodes[api_name] = {
127
+ 'apiName': api_name, 'file': None,
128
+ 'migratedDcName': migrated_dc_flow_name(api_name),
129
+ 'processType': None, 'status': 'missing',
130
+ 'reason': 'referenced subflow has no .flow-meta.xml on disk',
131
+ 'depth': depth, 'callers': sorted(callers_of.get(api_name, set())),
132
+ }
133
+ stack.pop()
134
+ perm.add(api_name)
135
+ order.append(nodes[api_name])
136
+ return
137
+
138
+ process_type, has_screens, refs = _parse_flow(path)
139
+
140
+ # Descend into children first (post-order => leaves land earlier).
141
+ for ref in refs:
142
+ visit(ref, index.get(ref), depth + 1, api_name)
143
+
144
+ status, reason = _classify(api_name, path, process_type, has_screens)
145
+ node = nodes.get(api_name)
146
+ if node is None:
147
+ node = {'apiName': api_name}
148
+ nodes[api_name] = node
149
+ # Preserve a cycle mark set while descending; otherwise use classification.
150
+ if node.get('status') == 'blocked' and 'cycle' in node.get('reason', ''):
151
+ pass
152
+ else:
153
+ node['status'] = status
154
+ node['reason'] = reason
155
+ node.update({
156
+ 'file': str(path),
157
+ 'migratedDcName': migrated_dc_flow_name(api_name),
158
+ 'processType': process_type,
159
+ 'depth': depth,
160
+ 'callers': sorted(callers_of.get(api_name, set())),
161
+ })
162
+
163
+ stack.pop()
164
+ perm.add(api_name)
165
+ order.append(node)
166
+
167
+ for caller in callers:
168
+ cpath = Path(caller)
169
+ api_name = cpath.name[:-len('.flow-meta.xml')]
170
+ visit(api_name, cpath, 0, None)
171
+
172
+ blocked = [n for n in order if n['status'] != 'migratable']
173
+ return {'order': order, 'blocked': blocked, 'hasBlockers': bool(blocked)}
174
+
175
+
176
+ def _render_summary(tree):
177
+ lines = ['Subflow dependency tree — migrate in this order (leaves first):', '']
178
+ for i, n in enumerate(tree['order'], 1):
179
+ mark = {'migratable': 'OK', 'blocked': 'BLOCKED', 'missing': 'BLOCKED'}[n['status']]
180
+ lines.append(f" {i}. [{mark}] {n['apiName']} -> {n['migratedDcName']}")
181
+ if n['status'] != 'migratable':
182
+ lines.append(f" reason: {n['reason']}")
183
+ if tree['hasBlockers']:
184
+ lines.append('')
185
+ lines.append('BLOCKED: one or more dependencies cannot be migrated — '
186
+ 'do NOT deploy the caller until these are resolved:')
187
+ for n in tree['blocked']:
188
+ lines.append(f" - {n['apiName']} ({n['status']}): {n['reason']}")
189
+ return '\n'.join(lines)
190
+
191
+
192
+ def main(argv=None):
193
+ parser = argparse.ArgumentParser(add_help=True)
194
+ parser.add_argument('flows_dir')
195
+ parser.add_argument('callers', nargs='*')
196
+ parser.add_argument('--all', action='store_true',
197
+ help='treat every FieldServiceMobile flow with screens as a root')
198
+ parser.add_argument('--json', dest='json_out', default=None)
199
+ args = parser.parse_args(argv)
200
+
201
+ if not args.all and not args.callers:
202
+ parser.error('provide at least one caller flow path or --all')
203
+
204
+ if args.all:
205
+ index = _index_flows(args.flows_dir)
206
+ callers = []
207
+ for api_name, path in index.items():
208
+ pt, screens, _ = _parse_flow(path)
209
+ if pt == 'FieldServiceMobile' and screens:
210
+ callers.append(str(path))
211
+ else:
212
+ callers = args.callers
213
+
214
+ tree = build_tree(args.flows_dir, callers)
215
+
216
+ print(_render_summary(tree))
217
+ if args.json_out:
218
+ Path(args.json_out).write_text(json.dumps(tree, indent=2))
219
+
220
+ return 1 if tree['hasBlockers'] else 0
221
+
222
+
223
+ if __name__ == '__main__':
224
+ try:
225
+ sys.exit(main())
226
+ except SystemExit:
227
+ raise
228
+ except Exception as exc: # noqa: BLE001
229
+ print(f'ERROR: {exc}', file=sys.stderr)
230
+ sys.exit(2)
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared input ingestion and validation for the field-service-data-capture-migrate skill.
4
+
5
+ Both entry points (analyze_flow.py, convert_to_dc_spec.py) call
6
+ load_and_validate_flow() to parse and validate a Field Service Mobile Flow
7
+ metadata file before processing it. Validation is fail-fast: the first failing
8
+ check raises FlowValidationError with a machine-readable category. Non-fatal
9
+ issues (e.g. a missing apiVersion) are returned as a list of FlowWarning records
10
+ alongside the parsed root.
11
+
12
+ Offline only (FR-008): operates entirely on the provided file; no org connection.
13
+
14
+ Namespace helpers come from flow_xml_utils (the single shared source) and are
15
+ re-exported here so callers/tests can reference flow_input._findtext etc.
16
+ """
17
+
18
+ import os
19
+ import xml.etree.ElementTree as ET
20
+ from typing import List, Optional, Tuple
21
+
22
+ from flow_xml_utils import NS, _findtext, _findall, _findall_deep # noqa: F401
23
+
24
+ # Fatal validation categories (raised via FlowValidationError).
25
+ MISSING_ARG = 'MISSING_ARG'
26
+ FILE_NOT_FOUND = 'FILE_NOT_FOUND'
27
+ ENCODING = 'ENCODING'
28
+ MALFORMED_XML = 'MALFORMED_XML'
29
+ WRONG_TYPE = 'WRONG_TYPE'
30
+ MISSING_START = 'MISSING_START'
31
+ SCREENLESS = 'SCREENLESS'
32
+
33
+ # Non-fatal warning categories (returned, never raised).
34
+ MISSING_API_VERSION = 'MISSING_API_VERSION'
35
+
36
+ EXPECTED_PROCESS_TYPE = 'FieldServiceMobile'
37
+
38
+
39
+ class FlowValidationError(Exception):
40
+ """Fatal input-validation failure. Carries a machine-readable category."""
41
+
42
+ def __init__(self, category: str, message: str, element: Optional[str] = None):
43
+ self.category = category
44
+ self.message = message
45
+ self.element = element
46
+ super().__init__(message)
47
+
48
+
49
+ class FlowWarning:
50
+ """Non-fatal input finding. Named FlowWarning to avoid shadowing builtin Warning."""
51
+
52
+ def __init__(self, category: str, message: str, element: Optional[str] = None):
53
+ self.category = category
54
+ self.message = message
55
+ self.element = element
56
+
57
+ def __eq__(self, other) -> bool:
58
+ return (isinstance(other, FlowWarning)
59
+ and self.category == other.category
60
+ and self.message == other.message
61
+ and self.element == other.element)
62
+
63
+ def __repr__(self) -> str:
64
+ return f'FlowWarning(category={self.category!r}, element={self.element!r})'
65
+
66
+
67
+ def _read_source(path: str) -> bytes:
68
+ """Read the file as bytes, or raise FILE_NOT_FOUND."""
69
+ if not os.path.isfile(path):
70
+ raise FlowValidationError(
71
+ FILE_NOT_FOUND,
72
+ f"[ERROR] Input file not found: '{path}'. Check the path and re-run.",
73
+ element=path,
74
+ )
75
+ try:
76
+ with open(path, 'rb') as f:
77
+ return f.read()
78
+ except OSError as e:
79
+ raise FlowValidationError(
80
+ FILE_NOT_FOUND,
81
+ f"[ERROR] Could not read '{path}': {e}. Check the path and permissions.",
82
+ element=path,
83
+ )
84
+
85
+
86
+ def _decode(raw: bytes, path: str) -> str:
87
+ """Decode bytes as UTF-8, transparently stripping a BOM. Raise ENCODING."""
88
+ try:
89
+ return raw.decode('utf-8-sig')
90
+ except UnicodeDecodeError as e:
91
+ raise FlowValidationError(
92
+ ENCODING,
93
+ f"[ERROR] Could not decode '{path}' as UTF-8: {e}. "
94
+ f"Re-export the flow as UTF-8.",
95
+ element=path,
96
+ )
97
+
98
+
99
+ def _parse(text: str, path: str) -> ET.Element:
100
+ """Parse decoded XML text into a root element, or raise MALFORMED_XML.
101
+
102
+ Parse from re-encoded bytes: this avoids ElementTree's refusal to accept a
103
+ str that still carries an <?xml ... encoding=?> declaration, and the BOM is
104
+ already gone (stripped in _decode).
105
+ """
106
+ try:
107
+ return ET.fromstring(text.encode('utf-8'))
108
+ except ET.ParseError as e:
109
+ position = getattr(e, 'position', None)
110
+ line = position[0] if position else '?'
111
+ raise FlowValidationError(
112
+ MALFORMED_XML,
113
+ f"[ERROR] Malformed XML in '{path}' at line {line}: {e}. "
114
+ f"Re-export the flow from Setup.",
115
+ element=str(line),
116
+ )
117
+
118
+
119
+ def _check_process_type(root: ET.Element) -> None:
120
+ """Raise WRONG_TYPE unless processType is FieldServiceMobile."""
121
+ found = _findtext(root, 'processType')
122
+ if found != EXPECTED_PROCESS_TYPE:
123
+ shown = found if found else '(none)'
124
+ raise FlowValidationError(
125
+ WRONG_TYPE,
126
+ f"[ERROR] processType is '{shown}', expected "
127
+ f"'{EXPECTED_PROCESS_TYPE}'. Export a Field Service Mobile Flow.",
128
+ element=shown,
129
+ )
130
+
131
+
132
+ def _check_start(root: ET.Element) -> None:
133
+ """Raise MISSING_START if no <start> element is present."""
134
+ if not _findall(root, 'start'):
135
+ raise FlowValidationError(
136
+ MISSING_START,
137
+ "[ERROR] No <start> element found. "
138
+ "File may be truncated; re-export the flow.",
139
+ element='start',
140
+ )
141
+
142
+
143
+ def _check_screens(root: ET.Element) -> None:
144
+ """Raise SCREENLESS if the flow has no <screens> element."""
145
+ if not _findall_deep(root, 'screens'):
146
+ raise FlowValidationError(
147
+ SCREENLESS,
148
+ "[ERROR] Flow has no screen components; nothing to migrate. "
149
+ "Only screen-collecting Mobile Flows are supported.",
150
+ element='screens',
151
+ )
152
+
153
+
154
+ def _check_api_version(root: ET.Element) -> List[FlowWarning]:
155
+ """Return a MISSING_API_VERSION warning when <apiVersion> is absent.
156
+
157
+ A present apiVersion never warns, at any value: there is no defined minimum
158
+ supported version (OQ-6 open). This discharges only the "not silent" half —
159
+ absence of provenance is surfaced, not guessed.
160
+ """
161
+ if _findtext(root, 'apiVersion') is None:
162
+ return [FlowWarning(
163
+ MISSING_API_VERSION,
164
+ "[WARNING] No <apiVersion> element found (version unspecified). "
165
+ "Verify the migrated flow manually.",
166
+ element='apiVersion',
167
+ )]
168
+ return []
169
+
170
+
171
+ def load_and_validate_flow(path: str) -> Tuple[ET.Element, List[FlowWarning]]:
172
+ """Parse and validate a Field Service Mobile Flow file.
173
+
174
+ Fail-fast: the first failing check raises FlowValidationError. On success
175
+ returns (parsed <Flow> root, list of non-fatal warnings). Order matters —
176
+ each step depends on the previous succeeding, and structural checks
177
+ (WRONG_TYPE) precede content checks (SCREENLESS).
178
+ """
179
+ raw = _read_source(path)
180
+ text = _decode(raw, path)
181
+ root = _parse(text, path)
182
+ _check_process_type(root)
183
+ _check_start(root)
184
+ _check_screens(root)
185
+ warnings = _check_api_version(root)
186
+ return root, warnings
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Namespace-aware ElementTree helpers for Salesforce Flow metadata XML.
4
+
5
+ Flow metadata may or may not declare the Salesforce metadata namespace, so every
6
+ lookup tries the namespaced form first and falls back to the plain tag. These
7
+ helpers are the single shared source for flow_input.py, convert_to_dc_spec.py,
8
+ and analyze_flow.py. This module depends on nothing else in the skill, so it is
9
+ safe for any of them to import (no circular import).
10
+ """
11
+
12
+ import xml.etree.ElementTree as ET
13
+ from typing import Optional
14
+
15
+ NS = {'sf': 'http://soap.sforce.com/2006/04/metadata'}
16
+
17
+
18
+ def _findtext(el: ET.Element, tag: str) -> Optional[str]:
19
+ """Find direct-child text with or without namespace."""
20
+ result = el.findtext(f'sf:{tag}', namespaces=NS)
21
+ if result is None:
22
+ result = el.findtext(tag)
23
+ return result
24
+
25
+
26
+ def _findall(el: ET.Element, tag: str) -> list:
27
+ """Find direct children with or without namespace."""
28
+ results = el.findall(f'sf:{tag}', namespaces=NS)
29
+ if not results:
30
+ results = el.findall(tag)
31
+ return results
32
+
33
+
34
+ def _findall_deep(el: ET.Element, tag: str) -> list:
35
+ """Find all descendants with or without namespace."""
36
+ results = el.findall(f'.//sf:{tag}', namespaces=NS)
37
+ if not results:
38
+ results = el.findall(f'.//{tag}')
39
+ return results