From eb5662a70779ab9d9f2f8dc2d2fb4e0e4ff07706 Mon Sep 17 00:00:00 2001 From: Justin Horst Date: Thu, 12 Mar 2026 11:46:22 -0400 Subject: [PATCH 1/7] @ W-21164311 Mobile sample app (#39) * Add native-mobile-rental-tenant-app sample Made-with: Cursor * Add digitalExperiences metadata to native-mobile-rental-tenant-app - Vibes app metadata (rental_app_metadata): tabs, theme, toolbar - Vibes screens: Home, Tenants, Properties with MCF/SharedUI components - Update README and samples docs Made-with: Cursor * Update digitalExperiences to CAMA format from cama-mcp-server - Replace Vibes format (rental_app, vibesAppMetadata, vibesScreen) with CAMA format (rentalApp, camaAppMetadata, camaScreen) - Add camaBuildMetadata, camaECDefinition - Add rentalApp.digitalExperience-meta.xml - Source: khawkins/cama-mcp-server branch apply_metadata_updates - Update README and samples docs Made-with: Cursor * update readme --- samples/README.md | 11 + .../.forceignore | 15 + .../.gitignore | 20 + .../native-mobile-rental-tenant-app/README.md | 9 + .../config/project-scratch-def.json | 10 + .../appMetadata/_meta.json | 5 + .../appMetadata/content.json | 41 ++ .../buildMetadata/_meta.json | 5 + .../buildMetadata/content.json | 8 + .../rentalApp/_meta.json | 5 + .../rentalApp/content.json | 5 + .../homeScreen/_meta.json | 5 + .../homeScreen/content.json | 489 +++++++++++++++ .../propertiesScreen/_meta.json | 5 + .../propertiesScreen/content.json | 565 ++++++++++++++++++ .../tenantsScreen/_meta.json | 5 + .../tenantsScreen/content.json | 478 +++++++++++++++ .../rentalApp.digitalExperience-meta.xml | 4 + .../package.json | 13 + .../sfdx-project.json | 12 + 20 files changed, 1710 insertions(+) create mode 100644 samples/native-mobile-rental-tenant-app/.forceignore create mode 100644 samples/native-mobile-rental-tenant-app/.gitignore create mode 100644 samples/native-mobile-rental-tenant-app/README.md create mode 100644 samples/native-mobile-rental-tenant-app/config/project-scratch-def.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/_meta.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/content.json create mode 100644 samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/rentalApp.digitalExperience-meta.xml create mode 100644 samples/native-mobile-rental-tenant-app/package.json create mode 100644 samples/native-mobile-rental-tenant-app/sfdx-project.json diff --git a/samples/README.md b/samples/README.md index aed2c51..9edcd00 100644 --- a/samples/README.md +++ b/samples/README.md @@ -41,3 +41,14 @@ Source is synced from the npm package [@salesforce/webapp-template-app-react-sam ### Version tracking The file `samples/webapp-template-app-react-sample-b2x-experimental/.version` stores the last-synced npm version. The Action compares it to the latest on npm and only creates a PR when they differ. + +## native-mobile-rental-tenant-app + +A sample Custom Agentic Mobile App (CAMA) for rental property tenants. This sample is maintained directly in this repository (not synced from npm). It includes: + +- **digitalExperiences** metadata: CAMA app config (`experience__camaAppMetadata`), build metadata (`experience__camaBuildMetadata`), EC definition (`experience__camaECDefinition`), and screens (`experience__camaScreen`) with tabs (Home, Tenants, Properties), theme, and toolbar settings +- **Source**: Synced from [cama-mcp-server](https://git.soma.salesforce.com/khawkins/cama-mcp-server) (branch `apply_metadata_updates`) + +### How it's used + +The sample appears on the Agentforce Vibes welcome page under the **Mobile** app type. Users can clone it via the welcome page wizard or directly from this repo. diff --git a/samples/native-mobile-rental-tenant-app/.forceignore b/samples/native-mobile-rental-tenant-app/.forceignore new file mode 100644 index 0000000..4ad8486 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/.forceignore @@ -0,0 +1,15 @@ +# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status +# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm +# + +package.xml + +# LWC configuration files +**/jsconfig.json +**/.eslintrc.json + +# LWC Jest +**/__tests__/** + +node_modules/ +.DS_Store diff --git a/samples/native-mobile-rental-tenant-app/.gitignore b/samples/native-mobile-rental-tenant-app/.gitignore new file mode 100644 index 0000000..55dce46 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +node_modules/ + +# Salesforce +.sfdx/ +.localdevserver/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* diff --git a/samples/native-mobile-rental-tenant-app/README.md b/samples/native-mobile-rental-tenant-app/README.md new file mode 100644 index 0000000..f31815b --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/README.md @@ -0,0 +1,9 @@ +# Native Mobile Rental Tenant App + +A sample Custom Agentic Mobile App (CAMA) for property managers and tenants. Use this as a starting point for building native mobile apps with Salesforce Mobile Publisher, MCF (Mobile Component Framework), and SharedUI. + +## Overview + +This sample demonstrates a rental tenant experience—browse properties, submit maintenance requests, pay rent, and communicate with property management—all from a native iOS or Android app. + + diff --git a/samples/native-mobile-rental-tenant-app/config/project-scratch-def.json b/samples/native-mobile-rental-tenant-app/config/project-scratch-def.json new file mode 100644 index 0000000..7635992 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/config/project-scratch-def.json @@ -0,0 +1,10 @@ +{ + "orgName": "Rental Tenant Dev", + "edition": "Developer", + "features": [], + "settings": { + "mobileSettings": { + "enableS1EncryptedStoragePref2": false + } + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/_meta.json new file mode 100644 index 0000000..89e4386 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "appMetadata", + "type": "experience__camaAppMetadata", + "path": "appMetadata" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/content.json new file mode 100644 index 0000000..e60f664 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaAppMetadata/appMetadata/content.json @@ -0,0 +1,41 @@ +{ + "type": "experience__camaAppMetadata", + "title": "App Metadata", + "contentBody": { + "name": "Homestead", + "description": "Mobile application for managing property rentals, tenant information, lease agreements, and maintenance requests.", + "version": "1.0.0", + "url": "http://login.test1.pc-rnd.salesforce.com/", + "templateID": "1", + "tabs": [ + { + "id": "home", + "label": "Home", + "icon": "house.fill", + "screen": "homeScreen" + }, + { + "id": "tenants", + "label": "Tenants", + "icon": "person.2.fill", + "screen": "tenantsScreen" + }, + { + "id": "properties", + "label": "Properties", + "icon": "building.2.fill", + "screen": "propertiesScreen" + } + ], + "toolbarActions": { + "notifications": false, + "search": false, + "agentforce": false + }, + "theme": { + "accentColor": "#9400D3", + "primaryColor": "#000000", + "secondaryColor": "#B3B3B3" + } + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/_meta.json new file mode 100644 index 0000000..f188c58 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "buildMetadata", + "type": "experience__camaBuildMetadata", + "path": "buildMetadata" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/content.json new file mode 100644 index 0000000..86f0450 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaBuildMetadata/buildMetadata/content.json @@ -0,0 +1,8 @@ +{ + "type": "experience__camaBuildMetadata", + "title": "Build Metadata", + "contentBody": { + "BiometricOptIn": false, + "NotificationOptIn": false + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/_meta.json new file mode 100644 index 0000000..b04fbca --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "rentalApp", + "type": "experience__camaECDefinition", + "path": "rentalApp" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/content.json new file mode 100644 index 0000000..3cb236c --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaECDefinition/rentalApp/content.json @@ -0,0 +1,5 @@ +{ + "type": "experience__camaECDefinition", + "title": "CAMA EC Definition", + "contentBody": {} +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/_meta.json new file mode 100644 index 0000000..c2d0d5d --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "homeScreen", + "type": "experience__camaScreen", + "path": "homeScreen" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/content.json new file mode 100644 index 0000000..2f89f6c --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/homeScreen/content.json @@ -0,0 +1,489 @@ +{ + "type": "experience__camaScreen", + "title": "Home Screen", + "contentBody": { + "view": { + "definition": "homestead/HomeScreen", + "properties": { + "pullToRefresh": true + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/container", + "properties": { + "padding": "$spacing.spacing4", + "backgroundColor": "$colors.surface2" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/card", + "properties": {}, + "regions": { + "components": { + "components": [ + { + "definition": "ui/column", + "properties": { + "width": "fill", + "height": "fill", + "alignment": "start", + "gap": "$spacing.spacing4", + "padding": "$spacing.spacing4" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/text", + "properties": { + "text": "Today", + "style": "titlesFontScale4Semibold", + "color": "$colors.onSurface1" + }, + "regions": {} + }, + { + "definition": "ui/text", + "properties": { + "text": "You have no event.", + "color": "$colors.onSuccess1", + "style": "bodyFontScale2Regular" + }, + "regions": {} + } + ] + } + } + } + ] + } + } + }, + { + "definition": "ui/card", + "properties": {}, + "regions": { + "components": { + "components": [ + { + "definition": "ui/column", + "properties": { + "width": "fill", + "height": "fill", + "alignment": "start", + "gap": "$spacing.spacing4", + "padding": "$spacing.spacing4" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/text", + "properties": { + "text": "Upcoming", + "style": "titlesFontScale4Semibold", + "color": "$colors.onSurface1" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "123 Oak Street", + "subtitle": "Inspection on 01/08/2026" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Scheduled", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "456 Maple Avenue", + "subtitle": "Lease Signing on 01/10/2026" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Scheduled", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "789 Pine Road", + "subtitle": "Maintenance on 01/15/2026" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Requested", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "321 Elm Street", + "subtitle": "Rent Collection on 01/20/2026" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Scheduled", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Community Center", + "subtitle": "HOA Meeting on 01/22/2026" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Proposed", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "555 Cedar Lane", + "subtitle": "Background Check" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/nothing" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "In Progress", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + } + ] + } + } + }, + { + "definition": "ui/card", + "properties": {}, + "regions": { + "components": { + "components": [ + { + "definition": "ui/column", + "properties": { + "width": "fill", + "height": "fill", + "alignment": "start", + "gap": "$spacing.spacing4", + "padding": "$spacing.spacing4" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/text", + "properties": { + "text": "Tenants", + "style": "titlesFontScale4Semibold", + "color": "$colors.onSurface1" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Sarah Johnson", + "subtitle": "456 Maple Avenue, Apt 5" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Michael Chen", + "subtitle": "789 Pine Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Overdue", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Emily Davis", + "subtitle": "321 Elm Street, Unit 5B" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Robert Williams", + "subtitle": "555 Cedar Lane" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Jessica Martinez", + "subtitle": "123 Oak Street, Unit 3C" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Moved Out", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "David Lee", + "subtitle": "456 Maple Avenue, Apt 2" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Signed", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + }, + "target": "native__homesteadHome", + "apiName": "uem_homesteadHome" + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/_meta.json new file mode 100644 index 0000000..2446d28 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "propertiesScreen", + "type": "experience__camaScreen", + "path": "propertiesScreen" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/content.json new file mode 100644 index 0000000..4412998 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/propertiesScreen/content.json @@ -0,0 +1,565 @@ +{ + "type": "experience__camaScreen", + "title": "Properties Screen", + "contentBody": { + "view": { + "definition": "homestead/PropertiesScreen", + "properties": { + "pullToRefresh": true + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/container", + "properties": { + "padding": "$spacing.spacing4", + "backgroundColor": "$colors.surface2" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/card", + "properties": {}, + "regions": { + "components": { + "components": [ + { + "definition": "ui/baseRow", + "properties": { + "title": "Oak Street Apartments", + "subtitle": "123 Oak Street" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Maple Avenue Complex", + "subtitle": "456 Maple Avenue" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Partial", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Pine Road House", + "subtitle": "789 Pine Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Vacant", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Cedar Lane Condos", + "subtitle": "555 Cedar Lane" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Elm Street Tower", + "subtitle": "321 Elm Street" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Partial", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Birch Boulevard Villa", + "subtitle": "234 Birch Boulevard" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Willow Way Estates", + "subtitle": "890 Willow Way" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Vacant", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Sunset Plaza", + "subtitle": "567 Sunset Drive" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Riverside Apartments", + "subtitle": "678 River Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Partial", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Lakeside Manor", + "subtitle": "901 Lake Avenue" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Mountain View Heights", + "subtitle": "112 Mountain Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Maintenance", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Harbor Point Lofts", + "subtitle": "445 Harbor Street" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Partial", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Garden Terrace", + "subtitle": "223 Garden Lane" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Park Place Residences", + "subtitle": "334 Park Avenue" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Vacant", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Hillside Court", + "subtitle": "778 Hill Street" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Downtown Tower", + "subtitle": "990 Main Street" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Partial", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Bayside Commons", + "subtitle": "101 Bay Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Full", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Meadow Brook Village", + "subtitle": "212 Meadow Lane" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/home", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Maintenance", + "variant": "error" + }, + "regions": {} + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + }, + "target": "native__homesteadProperties", + "apiName": "uem_homesteadProperties" + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/_meta.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/_meta.json new file mode 100644 index 0000000..fc4592a --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName": "tenantsScreen", + "type": "experience__camaScreen", + "path": "tenantsScreen" +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/content.json b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/content.json new file mode 100644 index 0000000..9100149 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/experience__camaScreen/tenantsScreen/content.json @@ -0,0 +1,478 @@ +{ + "type": "experience__camaScreen", + "title": "Tenants Screen", + "contentBody": { + "view": { + "definition": "homestead/TenantsScreen", + "properties": { + "pullToRefresh": true + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/container", + "properties": { + "padding": "$spacing.spacing4", + "backgroundColor": "$colors.surface2" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/card", + "properties": {}, + "regions": { + "components": { + "components": [ + { + "definition": "ui/baseRow", + "properties": { + "title": "Sarah Johnson", + "subtitle": "456 Maple Avenue, Apt 5" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Michael Chen", + "subtitle": "789 Pine Road" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Overdue", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Emily Davis", + "subtitle": "321 Elm Street, Unit 5B" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Robert Williams", + "subtitle": "555 Cedar Lane" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Jessica Martinez", + "subtitle": "123 Oak Street, Unit 3C" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Moved Out", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "David Lee", + "subtitle": "456 Maple Avenue, Apt 2" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Signed", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Amanda Thompson", + "subtitle": "789 Pine Road, Unit 7A" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Christopher Garcia", + "subtitle": "321 Elm Street, Apt 8" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Overdue", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Nicole Anderson", + "subtitle": "555 Cedar Lane, Unit 12B" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "James Wilson", + "subtitle": "123 Oak Street, Apt 1A" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Pending", + "variant": "warning" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Rachel Brown", + "subtitle": "456 Maple Avenue, Unit 9C" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Kevin Taylor", + "subtitle": "789 Pine Road, Apt 4B" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Signed", + "variant": "lightest" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Stephanie Miller", + "subtitle": "321 Elm Street, Unit 6D" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Brian Martinez", + "subtitle": "555 Cedar Lane, Apt 10A" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Overdue", + "variant": "error" + }, + "regions": {} + } + ] + } + } + }, + { + "definition": "ui/baseRow", + "properties": { + "title": "Laura Rodriguez", + "subtitle": "123 Oak Street, Unit 11B" + }, + "regions": { + "components": { + "components": [ + { + "definition": "ui/utilityIcon", + "properties": { + "iconName": "utility/user", + "size": "small" + }, + "regions": {} + }, + { + "definition": "ui/badge", + "properties": { + "label": "Paid", + "variant": "success" + }, + "regions": {} + } + ] + } + } + } + ] + } + } + } + ] + } + } + } + ] + } + } + }, + "target": "native__homesteadTenants", + "apiName": "uem_homesteadTenants" + } +} diff --git a/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/rentalApp.digitalExperience-meta.xml b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/rentalApp.digitalExperience-meta.xml new file mode 100644 index 0000000..b93075a --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/force-app/main/default/digitalExperiences/experiencecontainer/rentalApp/rentalApp.digitalExperience-meta.xml @@ -0,0 +1,4 @@ + + + + diff --git a/samples/native-mobile-rental-tenant-app/package.json b/samples/native-mobile-rental-tenant-app/package.json new file mode 100644 index 0000000..c384ac3 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/package.json @@ -0,0 +1,13 @@ +{ + "name": "native-mobile-rental-tenant-app", + "version": "1.0.0", + "description": "Sample CAMA app for rental property tenants", + "private": true, + "scripts": { + "build": "echo 'No build required for base project'", + "clean": "echo 'No clean required'", + "lint": "eslint **/{aura,lwc}/**/*.js 2>/dev/null || true", + "test": "echo 'Add tests when ready'" + }, + "devDependencies": {} +} diff --git a/samples/native-mobile-rental-tenant-app/sfdx-project.json b/samples/native-mobile-rental-tenant-app/sfdx-project.json new file mode 100644 index 0000000..cb22612 --- /dev/null +++ b/samples/native-mobile-rental-tenant-app/sfdx-project.json @@ -0,0 +1,12 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "name": "RentalTenantApp", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "66.0" +} From 2320c5026469d974cddc202d37a82938fdaa404d Mon Sep 17 00:00:00 2001 From: Mohan Raj Rajamanickam <1509984+mohanraj-r@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:41:52 -0700 Subject: [PATCH 2/7] ci: publish skills as @salesforce/afv-skills npm package @W-21534193@ (#30) * ci: update package details and add GitHub workflows for skills validation and release * chore: update Node version, change license, and refactor skills validation script to TypeScript * chore: remove unused deps * fix: npm package * fix: flatten skills to pass validation * chore: add validation script to package.json and update GitHub workflow to use it * chore: enhance skills validation and update workflows for npm publishing * refactor: streamline skills validation process in workflow and script * refactor: improve documentation and structure of skills validation script * implement checks based on jeff's best practices doc * chore: add pull request template for skill submissions * chore: update pull request template with additional references and improve automated checks section * chore: update pull request template to clarify naming convention with a warning for gerund form * chore: update pull request template to reference skill authoring guide and enhance checklist structure * chore: enhance GitHub workflows for skills validation and release process * refactor: improve parsing logic for SKILL.md files and enhance error collection in validation * chore: update validation checks to enforce character limits for skill names and descriptions * fix: remove unnecessary whitespace in footer string in validate-skills.ts --- .github/pull_request_template.md | 43 ++ .github/workflows/release-skills.yml | 91 +++ .github/workflows/validate-pr.yml | 10 + .github/workflows/validate-skills.yml | 36 ++ .nvmrc | 2 +- package-lock.json | 552 +++++++++++++++++- package.json | 16 +- scripts/validate-skills.ts | 368 ++++++++++++ .../apex-class/SKILL.md | 0 .../examples/AccountDeduplicationBatch.cls | 0 .../apex-class/examples/AccountSelector.cls | 0 .../apex-class/examples/AccountService.cls | 0 .../apex-class/templates/abstract.cls | 0 .../apex-class/templates/batch.cls | 0 .../apex-class/templates/domain.cls | 0 .../apex-class/templates/dto.cls | 0 .../apex-class/templates/exception.cls | 0 .../apex-class/templates/interface.cls | 0 .../apex-class/templates/queueable.cls | 0 .../apex-class/templates/schedulable.cls | 0 .../apex-class/templates/selector.cls | 0 .../apex-class/templates/service.cls | 0 .../apex-class/templates/utility.cls | 0 .../apex-test-class/SKILL.md | 0 .../references/assertion-patterns.md | 0 .../references/async-testing.md | 0 .../references/mocking-patterns.md | 0 .../references/test-data-factory.md | 0 .../deployment-readiness-check/SKILL.md | 0 .../assets/deployment_checklist.md | 0 .../references/rollback_procedures.md | 0 .../scripts/check_metadata.sh | 0 .../trigger-refactor-pipeline/SKILL.md | 0 .../assets/test_template.apex | 0 .../references/handler_patterns.md | 0 .../scripts/analyze_trigger.py | 0 36 files changed, 1111 insertions(+), 7 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/release-skills.yml create mode 100644 .github/workflows/validate-pr.yml create mode 100644 .github/workflows/validate-skills.yml create mode 100644 scripts/validate-skills.ts rename skills/{apex-development => }/apex-class/SKILL.md (100%) rename skills/{apex-development => }/apex-class/examples/AccountDeduplicationBatch.cls (100%) rename skills/{apex-development => }/apex-class/examples/AccountSelector.cls (100%) rename skills/{apex-development => }/apex-class/examples/AccountService.cls (100%) rename skills/{apex-development => }/apex-class/templates/abstract.cls (100%) rename skills/{apex-development => }/apex-class/templates/batch.cls (100%) rename skills/{apex-development => }/apex-class/templates/domain.cls (100%) rename skills/{apex-development => }/apex-class/templates/dto.cls (100%) rename skills/{apex-development => }/apex-class/templates/exception.cls (100%) rename skills/{apex-development => }/apex-class/templates/interface.cls (100%) rename skills/{apex-development => }/apex-class/templates/queueable.cls (100%) rename skills/{apex-development => }/apex-class/templates/schedulable.cls (100%) rename skills/{apex-development => }/apex-class/templates/selector.cls (100%) rename skills/{apex-development => }/apex-class/templates/service.cls (100%) rename skills/{apex-development => }/apex-class/templates/utility.cls (100%) rename skills/{testing-automation => }/apex-test-class/SKILL.md (100%) rename skills/{testing-automation => }/apex-test-class/references/assertion-patterns.md (100%) rename skills/{testing-automation => }/apex-test-class/references/async-testing.md (100%) rename skills/{testing-automation => }/apex-test-class/references/mocking-patterns.md (100%) rename skills/{testing-automation => }/apex-test-class/references/test-data-factory.md (100%) rename skills/{metadata-deployments => }/deployment-readiness-check/SKILL.md (100%) rename skills/{metadata-deployments => }/deployment-readiness-check/assets/deployment_checklist.md (100%) rename skills/{metadata-deployments => }/deployment-readiness-check/references/rollback_procedures.md (100%) rename skills/{metadata-deployments => }/deployment-readiness-check/scripts/check_metadata.sh (100%) rename skills/{apex-development => }/trigger-refactor-pipeline/SKILL.md (100%) rename skills/{apex-development => }/trigger-refactor-pipeline/assets/test_template.apex (100%) rename skills/{apex-development => }/trigger-refactor-pipeline/references/handler_patterns.md (100%) rename skills/{apex-development => }/trigger-refactor-pipeline/scripts/analyze_trigger.py (100%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..bc19414 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,43 @@ +**References:** [Contributing guide](../CONTRIBUTING.md) · [Skill authoring guide](../README.md) · [Agent Skills spec](https://agentskills.io/specification) + +## What changed + + + +## Why + + + +## Notes + + + +--- + +## Skills + +### Manual checklist + +**Description quality** +- [ ] Describes what the skill does and the expected output +- [ ] Includes relevant Salesforce domain keywords (Apex, LWC, SOQL, metadata types, etc.) +- [ ] Trigger phrases are specific enough for Vibes to select this skill reliably + +**Instructions** +- [ ] Clear goal statement +- [ ] Step-by-step workflow +- [ ] Validation rules for generated output +- [ ] Defined output / artifact + +**Context efficiency** +- [ ] Core instructions are concise — supporting material lives in `templates/`, `examples/`, or `docs/` subdirectories +- [ ] No unnecessary background explanation in the body + +### Automated checks + +Enforced by CI ([`npm run validate:skills`](../scripts/validate-skills.ts)) per the [Agent Skills spec](https://agentskills.io/specification): + +- Directory is one level deep, named in kebab-case (max 64 chars), contains `SKILL.md` +- Frontmatter `name` matches directory name; `description` is present, ≥ 20 words, ≤ 1024 characters, and includes trigger language +- Body is non-empty and under 500 lines +- Name uses gerund form ⚠ (warning — does not block merge) diff --git a/.github/workflows/release-skills.yml b/.github/workflows/release-skills.yml new file mode 100644 index 0000000..3cd55bd --- /dev/null +++ b/.github/workflows/release-skills.yml @@ -0,0 +1,91 @@ +name: release-skills + +on: + push: + branches: [main] + paths: ['skills/**'] + workflow_dispatch: + inputs: + ref: + description: "Branch or tag to release from (must be main)" + required: false + default: main + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: ubuntu-latest + outputs: + skipped: ${{ steps.changelog.outputs.skipped }} + version: ${{ steps.changelog.outputs.version }} + steps: + - name: Require main branch + if: github.event_name == 'workflow_dispatch' + run: | + if [ "${{ github.ref_name }}" != "main" ]; then + echo "Releases must be run from the main branch (got '${{ github.ref_name }}')." + exit 1 + fi + + - uses: actions/checkout@v4 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - run: npm ci + + - name: Validate skills + run: npm run validate:skills + + - name: Conventional Changelog Action + id: changelog + uses: TriPSs/conventional-changelog-action@v5 + with: + git-user-name: svc-idee-bot + git-user-email: svc_idee_bot@salesforce.com + github-token: ${{ secrets.IDEE_GH_TOKEN }} + tag-prefix: "" + release-count: "0" + skip-on-empty: ${{ github.event_name == 'push' }} + version-file: "package.json" + output-file: "CHANGELOG.md" + + - name: Create Github Release + id: release + uses: ncipollo/release-action@v1 + if: ${{ steps.changelog.outputs.skipped == 'false' }} + with: + name: "${{ steps.changelog.outputs.version }}" + tag: "${{ steps.changelog.outputs.version }}" + commit: ${{ github.sha }} + body: | + ## Changes in @salesforce/afv-skills + + ${{ steps.changelog.outputs.clean_changelog }} + token: ${{ secrets.IDEE_GH_TOKEN }} + skipIfReleaseExists: true + + - name: Publish to npm + id: publish + if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' }} + run: | + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc + npm publish --access public + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Notify cline-fork + if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' && steps.publish.outcome == 'success' }} + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + repository: forcedotcom/cline-fork + event-type: skills-released + client-payload: '{"version": "${{ steps.changelog.outputs.version }}"}' diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml new file mode 100644 index 0000000..22b478c --- /dev/null +++ b/.github/workflows/validate-pr.yml @@ -0,0 +1,10 @@ +name: pr-validation + +on: + pull_request: + types: [opened, reopened, edited] + branches: [main] + +jobs: + pr-validation: + uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml new file mode 100644 index 0000000..0e9f492 --- /dev/null +++ b/.github/workflows/validate-skills.yml @@ -0,0 +1,36 @@ +name: Validate Skills + +on: + pull_request: + paths: + - 'skills/**' + - 'scripts/validate-skills.ts' + - 'package.json' + - 'package-lock.json' + - '.nvmrc' + - '.github/workflows/validate-skills.yml' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - run: npm ci + + # Run against only changed skills when skills files are touched; fall back + # to full corpus validation when only tooling files changed, so validator + # regressions against existing skills are caught in the same PR. + - name: Validate skills + run: | + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q '^skills/'; then + npm run validate:skills -- --changed --base=origin/${{ github.base_ref }} + else + npm run validate:skills + fi diff --git a/.nvmrc b/.nvmrc index 12419e9..ffa3aec 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.18.1 \ No newline at end of file +v22.22.1 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 45c65fa..7bb5ce5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,459 @@ { - "name": "afv-library", + "name": "@salesforce/afv-skills", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "afv-library", + "name": "@salesforce/afv-skills", + "version": "1.0.0", + "license": "CC-BY-NC-4.0", "devDependencies": { "@salesforce/webapp-template-app-react-sample-b2e-experimental": "*", - "@salesforce/webapp-template-app-react-sample-b2x-experimental": "*" + "@salesforce/webapp-template-app-react-sample-b2x-experimental": "*", + "tsx": "^4.21.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@salesforce/webapp-template-app-react-sample-b2e-experimental": { @@ -22,6 +468,106 @@ "integrity": "sha512-ry4U36CjJx9h7pv5R5q0tVQCHkEvEYm/4u/5Ysh3UqTz89s3B9o/GPb0JK9fZ2Mgt/DCd0zfPUm1oRlCplnhGQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.txt" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } } } } diff --git a/package.json b/package.json index d0e11ad..9004d60 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,22 @@ { - "name": "afv-library", - "description": "AI prompts and rules library for Agentforce Vibes development", - "private": true, + "name": "@salesforce/afv-skills", + "version": "1.0.0", + "description": "Salesforce skills for Agentforce Vibes", + "license": "CC-BY-NC-4.0", + "files": [ + "skills/" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, "devDependencies": { + "tsx": "^4.21.0", "@salesforce/webapp-template-app-react-sample-b2e-experimental": "*", "@salesforce/webapp-template-app-react-sample-b2x-experimental": "*" }, "scripts": { + "validate:skills": "tsx scripts/validate-skills.ts", "sync-react-b2e-sample": "node scripts/sync-react-b2e-sample.js", "sync-react-b2x-sample": "node scripts/sync-react-b2x-sample.js" } diff --git a/scripts/validate-skills.ts b/scripts/validate-skills.ts new file mode 100644 index 0000000..9087b52 --- /dev/null +++ b/scripts/validate-skills.ts @@ -0,0 +1,368 @@ +#!/usr/bin/env tsx +// Validates the skills/ directory structure and SKILL.md format. +// Exits with code 1 if any violations are found. +// +// Usage: +// npm run validate:skills # validate all skills +// npm run validate:skills -- --changed --base=origin/main # validate only skills changed vs base + +import { execSync } from "child_process" +import fs from "fs" +import path from "path" +import { parseArgs } from "util" + +const SKILLS_DIR = path.join(__dirname, "..", "skills") + +/** Parsed context for a single skill directory, built before content checks run. */ +interface SkillContext { + dirName: string + dirPath: string + content: string + frontmatter: Record | null + body: string +} + +/** Return value of every check function. */ +interface CheckResult { + errors: string[] + /** When true and errors is non-empty, skip remaining checks for this entry. */ + fatal?: boolean + /** Defaults to "error". Warnings are printed but do not cause a non-zero exit code. */ + severity?: "error" | "warning" +} + +/** Collected results for a single skill entry. */ +interface SkillResult { + errors: string[] + warnings: string[] +} + +/** Runs before SKILL.md is read — validates directory layout. */ +interface StructureCheck { + description: string + run(dirName: string, dirPath: string): CheckResult +} + +/** Runs after SKILL.md is read — validates file content. */ +interface ContentCheck { + description: string + run(ctx: SkillContext): CheckResult +} + +/** + * Structure checks run on every entry in `skills/` before SKILL.md is read. + * A fatal error aborts content checks for that entry. + */ +const STRUCTURE_CHECKS: StructureCheck[] = [ + { + description: "Entry must be a directory (no loose files in skills/)", + run(dirName, dirPath) { + if (!fs.statSync(dirPath).isDirectory()) { + return { errors: [`Loose file in skills/: ${dirName} (expected only directories)`], fatal: true } + } + return { errors: [] } + }, + }, + { + description: "Skill directory must contain SKILL.md", + run(dirName, dirPath) { + if (!fs.existsSync(path.join(dirPath, "SKILL.md"))) { + return { errors: [`Missing SKILL.md in skills/${dirName}/`], fatal: true } + } + return { errors: [] } + }, + }, + { + description: "Name must be kebab-case (lowercase letters, digits, and hyphens only)", + run(dirName) { + if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(dirName)) { + return { errors: [`skills/${dirName}: name must be kebab-case (only lowercase letters, digits, and hyphens)`] } + } + return { errors: [] } + }, + }, + { + description: "Name must be at most 64 characters", + run(dirName) { + if (dirName.length > 64) { + return { errors: [`skills/${dirName}: name is ${dirName.length} characters (maximum 64)`] } + } + return { errors: [] } + }, + }, + { + description: "Name should use gerund form — first word should end in -ing (e.g. generating-apex-tests)", + run(dirName) { + if (!dirName.split("-")[0].endsWith("ing")) { + return { + errors: [`skills/${dirName}: name should use gerund form (e.g. generating-apex-tests, refactoring-triggers)`], + severity: "warning", + } + } + return { errors: [] } + }, + }, + { + description: "Skills must be exactly one level deep (no nested category directories)", + run(dirName, dirPath) { + const errors: string[] = [] + for (const sub of fs.readdirSync(dirPath)) { + const subPath = path.join(dirPath, sub) + if (fs.statSync(subPath).isDirectory() && fs.existsSync(path.join(subPath, "SKILL.md"))) { + errors.push( + `Nested skill detected: skills/${dirName}/${sub}/SKILL.md — skill directories must be exactly one level deep under skills/` + ) + } + } + return { errors } + }, + }, +] + +/** + * Content checks run only on entries that have a valid SKILL.md. + * A fatal error aborts remaining content checks for that entry. + */ +const CONTENT_CHECKS: ContentCheck[] = [ + { + description: "SKILL.md must have a valid YAML frontmatter block (--- ... ---)", + run({ dirName, frontmatter }) { + if (!frontmatter) { + return { + errors: [`skills/${dirName}/SKILL.md: missing or malformed YAML frontmatter (expected --- ... --- block at top)`], + fatal: true, + } + } + return { errors: [] } + }, + }, + { + description: 'Frontmatter "name" must be present and match the directory name', + run({ dirName, frontmatter }) { + if (!frontmatter) return { errors: [] } + if (!frontmatter.name) { + return { errors: [`skills/${dirName}/SKILL.md: missing "name" field in frontmatter`] } + } + if (frontmatter.name !== dirName) { + return { + errors: [ + `skills/${dirName}/SKILL.md: "name" value ("${frontmatter.name}") does not match directory name ("${dirName}")`, + ], + } + } + return { errors: [] } + }, + }, + { + description: 'Frontmatter "description" must be present and non-empty', + run({ dirName, frontmatter }) { + if (!frontmatter) return { errors: [] } + if (!frontmatter.description?.trim()) { + return { errors: [`skills/${dirName}/SKILL.md: missing or empty "description" field in frontmatter`] } + } + return { errors: [] } + }, + }, + { + description: "SKILL.md must have a non-empty body (instructions after the frontmatter block)", + run({ dirName, body }) { + if (!body.trim()) { + return { errors: [`skills/${dirName}/SKILL.md: body (instructions after frontmatter) is empty`] } + } + return { errors: [] } + }, + }, + { + description: "Description must be at least 20 words to be information-rich", + run({ dirName, frontmatter }) { + if (!frontmatter) return { errors: [] } + const words = frontmatter.description?.trim().split(/\s+/) ?? [] + if (words.length < 20) { + return { + errors: [`skills/${dirName}/SKILL.md: description too short (${words.length} word(s), minimum 20)`], + } + } + return { errors: [] } + }, + }, + { + description: "Description must be at most 1024 characters", + run({ dirName, frontmatter }) { + if (!frontmatter) return { errors: [] } + const len = frontmatter.description?.length ?? 0 + if (len > 1024) { + return { errors: [`skills/${dirName}/SKILL.md: description is ${len} characters (maximum 1024)`] } + } + return { errors: [] } + }, + }, + { + description: 'Description must include trigger/activation language (contain "use")', + run({ dirName, frontmatter }) { + if (!frontmatter) return { errors: [] } + if (!frontmatter.description?.toLowerCase().includes("use")) { + return { + errors: [ + `skills/${dirName}/SKILL.md: description must include trigger context (e.g. "Use this skill when...")`, + ], + } + } + return { errors: [] } + }, + }, + { + description: "Skill body should be under 500 lines for context efficiency", + run({ dirName, body }) { + const lines = body.split("\n").length + if (lines > 500) { + return { + errors: [`skills/${dirName}/SKILL.md: body is ${lines} lines (recommended maximum is 500)`], + severity: "warning", + } + } + return { errors: [] } + }, + }, +] + +/** + * Returns the deduplicated list of top-level skill directory names that have + * changed relative to `base` (e.g. `origin/main`) and still exist on disk. + * Deleted skill directories are intentionally excluded — removing a skill is + * valid and requires no structural validation. + */ +function getChangedSkillDirs(base: string): string[] { + const output = execSync(`git diff --name-only ${base}...HEAD`, { encoding: "utf8" }) + return [ + ...new Set( + output + .split("\n") + .filter((f) => f.startsWith("skills/")) + .map((f) => f.split("/")[1]) + .filter(Boolean) + ), + ].filter((dir) => fs.existsSync(path.join(SKILLS_DIR, dir))) +} + +/** + * Parses a SKILL.md file into its frontmatter fields and body. + * `frontmatter` is `null` if the `--- ... ---` block is missing or malformed. + * Wrapping quotes on frontmatter values are stripped. + */ +function parseSkillMd(content: string): { frontmatter: Record | null; body: string } { + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/) + if (!match) return { frontmatter: null, body: content } + + const frontmatter: Record = {} + for (const line of match[1].split(/\r?\n/)) { + const colonIdx = line.indexOf(":") + if (colonIdx === -1) continue + const key = line.slice(0, colonIdx).trim() + const raw = line.slice(colonIdx + 1).trim() + // Strip wrapping single or double quotes to match how consumers read values + frontmatter[key] = raw.replace(/^(['"])([\s\S]*)\1$/, "$2") + } + + return { frontmatter, body: content.slice(match[0].length) } +} + +/** + * Runs all structure and content checks for a single skill directory. + * Returns errors (block CI) and warnings (advisory, printed but exit 0) separately. + */ +function validateSkill(dirName: string, dirPath: string): SkillResult { + if (!fs.existsSync(dirPath)) { + return { errors: [`skills/${dirName}: directory not found`], warnings: [] } + } + + const errors: string[] = [] + const warnings: string[] = [] + + const collectIssues = (result: CheckResult): boolean => { + if (result.errors.length === 0) return false + if (result.severity === "warning") { + warnings.push(...result.errors) + } else { + errors.push(...result.errors) + } + return result.fatal === true && result.severity !== "warning" + } + + for (const check of STRUCTURE_CHECKS) { + if (collectIssues(check.run(dirName, dirPath))) return { errors, warnings } + } + + const content = fs.readFileSync(path.join(dirPath, "SKILL.md"), "utf8") + const { frontmatter, body } = parseSkillMd(content) + const ctx: SkillContext = { dirName, dirPath, content, frontmatter, body } + + for (const check of CONTENT_CHECKS) { + if (collectIssues(check.run(ctx))) return { errors, warnings } + } + + return { errors, warnings } +} + +/** CLI entry point. Parses flags, resolves the list of skills to check, and reports results. */ +function main(): void { + const { values } = parseArgs({ + args: process.argv.slice(2), + options: { + /** Validate only skill dirs touched in this branch vs the given base ref. */ + changed: { type: "boolean", default: false }, + /** Base ref for --changed (e.g. origin/main). Defaults to origin/HEAD. */ + base: { type: "string", default: "origin/HEAD" }, + }, + }) + + let entries: string[] + + if (values.changed) { + entries = getChangedSkillDirs(values.base!) + if (entries.length === 0) { + console.log("No skill directories changed — nothing to validate.") + return + } + console.log(`Validating ${entries.length} changed skill(s): ${entries.join(", ")}`) + } else { + entries = fs.readdirSync(SKILLS_DIR) + } + + const allErrors: string[] = [] + const allWarnings: string[] = [] + let passed = 0 + + for (const entry of entries) { + const { errors, warnings } = validateSkill(entry, path.join(SKILLS_DIR, entry)) + allErrors.push(...errors) + allWarnings.push(...warnings) + if (errors.length === 0) passed++ + } + + const hasIssues = allErrors.length > 0 || allWarnings.length > 0 + const footer = "Spec: https://agentskills.io/specification · Authoring guide: https://github.com/forcedotcom/afv-library#readme" + + if (allWarnings.length > 0) { + console.warn(`\n${allWarnings.length} warning(s):\n`) + for (const w of allWarnings) { + console.warn(` ⚠ ${w}`) + } + console.warn("") + } + + if (allErrors.length > 0) { + console.error(`\nSkill validation failed with ${allErrors.length} error(s):\n`) + for (const err of allErrors) { + console.error(` ✗ ${err}`) + } + console.error("") + console.error(footer) + console.error("") + process.exit(1) + } else { + console.log(`Skill validation passed: ${passed} of ${entries.length} skill(s) checked.`) + if (hasIssues) console.warn(footer) + } +} + +main() diff --git a/skills/apex-development/apex-class/SKILL.md b/skills/apex-class/SKILL.md similarity index 100% rename from skills/apex-development/apex-class/SKILL.md rename to skills/apex-class/SKILL.md diff --git a/skills/apex-development/apex-class/examples/AccountDeduplicationBatch.cls b/skills/apex-class/examples/AccountDeduplicationBatch.cls similarity index 100% rename from skills/apex-development/apex-class/examples/AccountDeduplicationBatch.cls rename to skills/apex-class/examples/AccountDeduplicationBatch.cls diff --git a/skills/apex-development/apex-class/examples/AccountSelector.cls b/skills/apex-class/examples/AccountSelector.cls similarity index 100% rename from skills/apex-development/apex-class/examples/AccountSelector.cls rename to skills/apex-class/examples/AccountSelector.cls diff --git a/skills/apex-development/apex-class/examples/AccountService.cls b/skills/apex-class/examples/AccountService.cls similarity index 100% rename from skills/apex-development/apex-class/examples/AccountService.cls rename to skills/apex-class/examples/AccountService.cls diff --git a/skills/apex-development/apex-class/templates/abstract.cls b/skills/apex-class/templates/abstract.cls similarity index 100% rename from skills/apex-development/apex-class/templates/abstract.cls rename to skills/apex-class/templates/abstract.cls diff --git a/skills/apex-development/apex-class/templates/batch.cls b/skills/apex-class/templates/batch.cls similarity index 100% rename from skills/apex-development/apex-class/templates/batch.cls rename to skills/apex-class/templates/batch.cls diff --git a/skills/apex-development/apex-class/templates/domain.cls b/skills/apex-class/templates/domain.cls similarity index 100% rename from skills/apex-development/apex-class/templates/domain.cls rename to skills/apex-class/templates/domain.cls diff --git a/skills/apex-development/apex-class/templates/dto.cls b/skills/apex-class/templates/dto.cls similarity index 100% rename from skills/apex-development/apex-class/templates/dto.cls rename to skills/apex-class/templates/dto.cls diff --git a/skills/apex-development/apex-class/templates/exception.cls b/skills/apex-class/templates/exception.cls similarity index 100% rename from skills/apex-development/apex-class/templates/exception.cls rename to skills/apex-class/templates/exception.cls diff --git a/skills/apex-development/apex-class/templates/interface.cls b/skills/apex-class/templates/interface.cls similarity index 100% rename from skills/apex-development/apex-class/templates/interface.cls rename to skills/apex-class/templates/interface.cls diff --git a/skills/apex-development/apex-class/templates/queueable.cls b/skills/apex-class/templates/queueable.cls similarity index 100% rename from skills/apex-development/apex-class/templates/queueable.cls rename to skills/apex-class/templates/queueable.cls diff --git a/skills/apex-development/apex-class/templates/schedulable.cls b/skills/apex-class/templates/schedulable.cls similarity index 100% rename from skills/apex-development/apex-class/templates/schedulable.cls rename to skills/apex-class/templates/schedulable.cls diff --git a/skills/apex-development/apex-class/templates/selector.cls b/skills/apex-class/templates/selector.cls similarity index 100% rename from skills/apex-development/apex-class/templates/selector.cls rename to skills/apex-class/templates/selector.cls diff --git a/skills/apex-development/apex-class/templates/service.cls b/skills/apex-class/templates/service.cls similarity index 100% rename from skills/apex-development/apex-class/templates/service.cls rename to skills/apex-class/templates/service.cls diff --git a/skills/apex-development/apex-class/templates/utility.cls b/skills/apex-class/templates/utility.cls similarity index 100% rename from skills/apex-development/apex-class/templates/utility.cls rename to skills/apex-class/templates/utility.cls diff --git a/skills/testing-automation/apex-test-class/SKILL.md b/skills/apex-test-class/SKILL.md similarity index 100% rename from skills/testing-automation/apex-test-class/SKILL.md rename to skills/apex-test-class/SKILL.md diff --git a/skills/testing-automation/apex-test-class/references/assertion-patterns.md b/skills/apex-test-class/references/assertion-patterns.md similarity index 100% rename from skills/testing-automation/apex-test-class/references/assertion-patterns.md rename to skills/apex-test-class/references/assertion-patterns.md diff --git a/skills/testing-automation/apex-test-class/references/async-testing.md b/skills/apex-test-class/references/async-testing.md similarity index 100% rename from skills/testing-automation/apex-test-class/references/async-testing.md rename to skills/apex-test-class/references/async-testing.md diff --git a/skills/testing-automation/apex-test-class/references/mocking-patterns.md b/skills/apex-test-class/references/mocking-patterns.md similarity index 100% rename from skills/testing-automation/apex-test-class/references/mocking-patterns.md rename to skills/apex-test-class/references/mocking-patterns.md diff --git a/skills/testing-automation/apex-test-class/references/test-data-factory.md b/skills/apex-test-class/references/test-data-factory.md similarity index 100% rename from skills/testing-automation/apex-test-class/references/test-data-factory.md rename to skills/apex-test-class/references/test-data-factory.md diff --git a/skills/metadata-deployments/deployment-readiness-check/SKILL.md b/skills/deployment-readiness-check/SKILL.md similarity index 100% rename from skills/metadata-deployments/deployment-readiness-check/SKILL.md rename to skills/deployment-readiness-check/SKILL.md diff --git a/skills/metadata-deployments/deployment-readiness-check/assets/deployment_checklist.md b/skills/deployment-readiness-check/assets/deployment_checklist.md similarity index 100% rename from skills/metadata-deployments/deployment-readiness-check/assets/deployment_checklist.md rename to skills/deployment-readiness-check/assets/deployment_checklist.md diff --git a/skills/metadata-deployments/deployment-readiness-check/references/rollback_procedures.md b/skills/deployment-readiness-check/references/rollback_procedures.md similarity index 100% rename from skills/metadata-deployments/deployment-readiness-check/references/rollback_procedures.md rename to skills/deployment-readiness-check/references/rollback_procedures.md diff --git a/skills/metadata-deployments/deployment-readiness-check/scripts/check_metadata.sh b/skills/deployment-readiness-check/scripts/check_metadata.sh similarity index 100% rename from skills/metadata-deployments/deployment-readiness-check/scripts/check_metadata.sh rename to skills/deployment-readiness-check/scripts/check_metadata.sh diff --git a/skills/apex-development/trigger-refactor-pipeline/SKILL.md b/skills/trigger-refactor-pipeline/SKILL.md similarity index 100% rename from skills/apex-development/trigger-refactor-pipeline/SKILL.md rename to skills/trigger-refactor-pipeline/SKILL.md diff --git a/skills/apex-development/trigger-refactor-pipeline/assets/test_template.apex b/skills/trigger-refactor-pipeline/assets/test_template.apex similarity index 100% rename from skills/apex-development/trigger-refactor-pipeline/assets/test_template.apex rename to skills/trigger-refactor-pipeline/assets/test_template.apex diff --git a/skills/apex-development/trigger-refactor-pipeline/references/handler_patterns.md b/skills/trigger-refactor-pipeline/references/handler_patterns.md similarity index 100% rename from skills/apex-development/trigger-refactor-pipeline/references/handler_patterns.md rename to skills/trigger-refactor-pipeline/references/handler_patterns.md diff --git a/skills/apex-development/trigger-refactor-pipeline/scripts/analyze_trigger.py b/skills/trigger-refactor-pipeline/scripts/analyze_trigger.py similarity index 100% rename from skills/apex-development/trigger-refactor-pipeline/scripts/analyze_trigger.py rename to skills/trigger-refactor-pipeline/scripts/analyze_trigger.py From ff08ad58e2846d7f4452e30e28cffddc5db0cd22 Mon Sep 17 00:00:00 2001 From: svc-idee-bot Date: Thu, 12 Mar 2026 20:47:02 +0000 Subject: [PATCH 3/7] chore(release): 1.1.0 [skip ci] --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..315ff1b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# 1.1.0 (2026-03-12) + + +### Features + +* add first pass at b2e sample ([ce615bf](https://github.com/forcedotcom/afv-library/commit/ce615bf0361e4a6a8f5635726ce304e6de921413)) +* add metadata type skills ([3689f14](https://github.com/forcedotcom/afv-library/commit/3689f14af7eeb1dd8ea80ae206437f3483d37938)) +* add skills from 260 ([65947b1](https://github.com/forcedotcom/afv-library/commit/65947b15509fef576ac683226976ee3d60a624f3)) + + + diff --git a/package.json b/package.json index 9004d60..5941e85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/afv-skills", - "version": "1.0.0", + "version": "1.1.0", "description": "Salesforce skills for Agentforce Vibes", "license": "CC-BY-NC-4.0", "files": [ From ae4e46d97ffbddb2c003e9fb4d50cb409f66365a Mon Sep 17 00:00:00 2001 From: irisli007 <53790094+irisli007@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:01:32 -0700 Subject: [PATCH 4/7] W-21367283: Add skills for metadata permission set generation (#23) * initial skill for perm sets * more edits * changes after testing * more changes * address comments * standard tabs should have prefix * remove apache license --------- Co-authored-by: irisli --- skills/generate-permission-set/SKILL.md | 171 ++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 skills/generate-permission-set/SKILL.md diff --git a/skills/generate-permission-set/SKILL.md b/skills/generate-permission-set/SKILL.md new file mode 100644 index 0000000..791e5e1 --- /dev/null +++ b/skills/generate-permission-set/SKILL.md @@ -0,0 +1,171 @@ +--- +name: generate-permission-set +description: Generates correct, deployable Salesforce permission set metadata (PermissionSet XML) with object, field, user, and app permissions. Use when creating or editing permission set metadata, PermissionSet XML, object permissions, field-level security (FLS), tab visibility, or deploying permission sets. +compatibility: Salesforce Metadata API v60.0+ +metadata: + author: afv-library + version: "1.0" +--- + +## When to Use This Skill + +Use when generating or editing permission set metadata, or when granting object, field, user, and app permissions. + +## Step 1: Define Core Properties + +Start by defining the required permission set properties: + +```xml + + YourPermissionSetName + + Clear description of purpose and intended audience + +``` + +**Naming conventions:** +- Use descriptive API names (e.g., `Sales_Manager_Access`) + +## Step 2: Configure Object Permissions + +Add CRUD permissions for standard and custom objects: + +```xml + + true + true + true + false + false + false + false + Account + +``` + +## Step 3: Set Field-Level Security + +Define field permissions for sensitive or custom fields: + +```xml + + true + true + Account.SSN__c + +``` + +**Important:** +- Required fields must NEVER appear in list of field permissions. Granting field-level security on required fields is not allowed by the platform and will cause deployment failure. +- Before adding any field, confirm from the object metadata that the field exists and is not required +- A field is required when its metadata contains `true`: +```xml + + FieldName__c + true + +``` +- Use format `ObjectName.FieldName` for field references +- Set both readable and editable to true when the user needs edit access; editable implies readable +- If all fields should be visible, can alternatively enable the "viewAllFields" object permission + +## Step 4: Grant User Permissions + +Add system-level permissions for features and capabilities: + +```xml + + true + ApiEnabled + + + true + RunReports + +``` + +**Common permissions:** +- `ApiEnabled`: API access +- `ViewSetup`: View Setup menu +- `ManageUsers`: User management +- `RunReports`: Report execution + +**Security review required for:** +- `ViewAllData`: Read all records +- `ModifyAllData`: Edit all records +- `ManageUsers`: User administration + +## Step 5: Configure App and Tab Visibility + +Make applications and tabs visible to users: + +```xml + + Sales_Console + true + + + CustomTab__c + Visible + +``` + +**Application visibility options:** +- can be true or false + +**Tab visibility options:** +- `Visible`: Always shown +- `Available`: Available but not default +- `Hidden`: Not visible + +**CRITICAL - Tab Naming:** +- Custom object tabs: MUST include the __c suffix (e.g., MyCustomObject__c) +- Standard object tabs: Use the object name with "standard-" prefix (e.g., standard-Account, standard-Contact) +- The tab name matches the object's API name exactly + +## Step 6: Add Apex and Visualforce Access (Optional) + +Grant access to custom code: + +```xml + + CustomController + true + + + CustomPage + true + +``` + +## Step 7: Set License and Record Type Settings (Optional) + +Specify license requirements and record type visibility: + +```xml +Salesforce +false + + Account.Business + true + true + +``` + +## Validation Checklist + +Before deploying, verify: +- [ ] fullName, label, description set +- [ ] Permissions follow least privilege +- [ ] No required fields in `` +- [ ] No duplicate permissions +- [ ] no lengthy comments + +## What Causes Deployment Failure + +- **Field permissions on required fields:** Any required field in `` fails deployment. Required fields cannot have FLS; omit them entirely. Always confirm from object/field metadata that a field exists and is not required—never assume. +- **Incorrect API names:** Using the wrong name or missing suffixes (e.g. missing `__c` for custom objects, fields, tabs) cause failure. + +## Deployment + +Deploy using Salesforce CLI \ No newline at end of file From ddc6ec0ce3901262b89d96af280dd3e99d9e5ead Mon Sep 17 00:00:00 2001 From: Mohan Raj Rajamanickam <1509984+mohanraj-r@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:16:05 -0700 Subject: [PATCH 5/7] ci: rename PR validation job and skills validation job for clarity @W-21534193@ (#43) Refactor GitHub workflows: rename PR validation job and skills validation job for clarity --- .github/workflows/validate-pr.yml | 4 ++-- .github/workflows/validate-skills.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 22b478c..2d267f5 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -1,4 +1,4 @@ -name: pr-validation +name: Validate PR Title on: pull_request: @@ -6,5 +6,5 @@ on: branches: [main] jobs: - pr-validation: + pr-title: uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml index 0e9f492..d0c3059 100644 --- a/.github/workflows/validate-skills.yml +++ b/.github/workflows/validate-skills.yml @@ -11,7 +11,7 @@ on: - '.github/workflows/validate-skills.yml' jobs: - validate: + validate-skills: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 3bfd122458dde3424f0f050f2830da4958a07807 Mon Sep 17 00:00:00 2001 From: Mohan Raj Rajamanickam <1509984+mohanraj-r@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:32:37 -0700 Subject: [PATCH 6/7] ci: simplify pull request validation triggers in skills workflow @W-21534193@ (#44) ci: simplify pull request validation triggers in skills workflow --- .github/workflows/validate-skills.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml index d0c3059..cd2b321 100644 --- a/.github/workflows/validate-skills.yml +++ b/.github/workflows/validate-skills.yml @@ -2,13 +2,6 @@ name: Validate Skills on: pull_request: - paths: - - 'skills/**' - - 'scripts/validate-skills.ts' - - 'package.json' - - 'package-lock.json' - - '.nvmrc' - - '.github/workflows/validate-skills.yml' jobs: validate-skills: From 08574cf6589ddb16825dfbf641cbc3af30d62518 Mon Sep 17 00:00:00 2001 From: Ayush Gupta Date: Fri, 13 Mar 2026 09:23:51 +0530 Subject: [PATCH 7/7] W-21541841 Updated Salesforce flow skill as Platform Event-Triggered Flows is not supported yet (#40) @W-21541841 Updated Salesforce flow skill as Platform Event-Triggered Flows is not supported yet --- skills/salesforce-flow/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/salesforce-flow/SKILL.md b/skills/salesforce-flow/SKILL.md index e3a55d5..4513457 100644 --- a/skills/salesforce-flow/SKILL.md +++ b/skills/salesforce-flow/SKILL.md @@ -1,6 +1,6 @@ --- name: salesforce-flow -description: Generate Salesforce Flows using the MCP tool execute_metadata_action. Use when the user asks to create, build, or generate a flow — including Screen, Autolaunched, Record-Triggered (before/after-save), Scheduled, or Platform Event-Triggered Flows. Also trigger for flow-like requests such as "when a record is created", "trigger daily at", "send an email when", "update the field when", "automate", "workflow", or "flow XML/metadata". This is the only skill for Salesforce Flow generation. +description: Generate Salesforce Flows using the MCP tool execute_metadata_action. Use when the user asks to create, build, or generate a flow — including Screen, Autolaunched, Record-Triggered (before/after-save), Scheduled. Also trigger for flow-like requests such as "when a record is created", "trigger daily at", "send an email when", "update the field when", "automate", "workflow", or "flow XML/metadata". This is the only skill for Salesforce Flow generation. --- ## Goal @@ -21,7 +21,7 @@ Use this skill when you need to: # Flow Metadata Specification ## Overview -Salesforce Flows are powerful automation tools that enable complex business process automation without code. Flows can collect and process data through interactive screens, execute logic and calculations, manipulate records, call external services, and trigger based on various events. Flow types include Screen Flows (user-guided), Autolaunched Flows (background processing), Record-Triggered Flows (database events), Scheduled Flows (time-based), and Platform Event-Triggered Flows (event-driven). +Salesforce Flows are powerful automation tools that enable complex business process automation without code. Flows can collect and process data through interactive screens, execute logic and calculations, manipulate records, call external services, and trigger based on various events. Flow types include Screen Flows (user-guided), Autolaunched Flows (background processing), Record-Triggered Flows (database events) and Scheduled Flows (time-based). ## Purpose - Automate complex business processes with declarative logic and branching