From 84d2cb6e497834ac64b979cb3543cf776391fffb Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 14:29:38 -0300 Subject: [PATCH 01/21] Added salesforce skill --- skills/discover-salesforce/SKILL.md | 241 ++++++++++ skills/salesforce/INDEX.md | 187 ++++++++ skills/salesforce/sf-cli-operations.md | 588 +++++++++++++++++++++++++ 3 files changed, 1016 insertions(+) create mode 100644 skills/discover-salesforce/SKILL.md create mode 100644 skills/salesforce/INDEX.md create mode 100644 skills/salesforce/sf-cli-operations.md diff --git a/skills/discover-salesforce/SKILL.md b/skills/discover-salesforce/SKILL.md new file mode 100644 index 0000000..0fbd1dc --- /dev/null +++ b/skills/discover-salesforce/SKILL.md @@ -0,0 +1,241 @@ +--- +name: discover-salesforce +description: Automatically discover Salesforce CLI (sf) skills when working with Salesforce orgs, Agile Accelerator (GUS), SOQL queries, work items (WI), user stories, sprints, or Chatter. Activates for Salesforce integration and automation tasks. +--- + +# Salesforce Skills Discovery + +Provides automatic access to comprehensive Salesforce CLI (sf) operations and Agile Accelerator (GUS) workflows. + +## When This Skill Activates + +This skill auto-activates when you're working with: +- Salesforce CLI (sf) commands +- Agile Accelerator (GUS) work items (WI), user stories, bugs +- SOQL queries and data operations +- Salesforce orgs and authentication +- Creating or updating Salesforce records +- Sprints, epics, and builds +- Chatter posts and comments +- Bulk data operations +- Salesforce REST API integration +- Any mention of WI (Work Items) + +## Available Skills + +### Quick Reference + +The Salesforce category contains 1 comprehensive skill: + +1. **sf-cli-operations** - Complete guide to Salesforce CLI operations, SOQL, record management, bulk operations, and Agile Accelerator workflows + +**Note**: All examples use `gus` as the default org alias. This refers to your Salesforce org where Agile Accelerator (GUS) is configured. If your org has a different alias, simply replace `--target-org gus` with your org alias throughout. + +### Load Full Category Details + +For complete descriptions and workflows: + +```bash +cat ~/.claude/skills/salesforce/INDEX.md +``` + +This loads the full Salesforce category index with: +- Detailed skill descriptions +- Usage triggers for each skill +- Common workflow combinations +- Cross-references to related skills + +### Load Specific Skills + +Load the Salesforce CLI operations skill: + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +## Common Workflows + +### Create User Story +**Sequence**: Query dependencies → Create work item → Add Chatter update + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +Use Pattern 1 (Creating User Stories with Dependencies) and Pattern 6 (Finding Record IDs). + +### Sprint Planning +**Sequence**: Create sprint → Query backlog → Bulk assign items + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +Use Pattern 4 (Managing Sprints and Builds) and Pattern 2 (Bulk Status Updates). + +### Bulk Status Update +**Sequence**: Query work items → Export CSV → Bulk update → Verify + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +Use Pattern 2 (Bulk Status Updates) and Pattern 7 (Bulk Data Export). + +### Work Item Reporting +**Sequence**: Query with related data → Export to CSV → Analyze + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +Use Pattern 7 (Bulk Data Export) and SOQL query patterns from Concept 2. + +## Skill Selection Guide + +**Use sf-cli-operations when:** +- Creating or updating work items in Agile Accelerator (GUS) +- Performing bulk operations on Salesforce data +- Querying Salesforce using SOQL +- Managing sprints, epics, and builds +- Posting updates to Chatter feeds +- Automating Salesforce workflows +- Integrating Salesforce with other tools +- Exporting data for reporting and analysis + +**Common Operations:** +- **Authentication**: `sf org login web`, `sf org list`, `sf org display` +- **Queries**: `sf data query` with SOQL syntax +- **Record Creation**: `sf data create record` with `--sobject` and `--values` +- **Record Updates**: `sf data update record` by ID or field match +- **Bulk Operations**: `sf data update bulk`, `sf data export bulk` +- **REST API**: `sf api request rest` for custom operations + +**Common Objects:** +- `ADM_Work__c` - Work Items (Stories, Bugs, Tasks) +- `ADM_Epic__c` - Epics +- `ADM_Sprint__c` - Sprints +- `ADM_Build__c` - Scheduled Builds +- `ADM_Product_Tag__c` - Product Tags +- `FeedItem` - Chatter Posts +- `FeedComment` - Chatter Comments +- `User` - Salesforce Users + +## Integration with Other Skills + +Salesforce skills commonly combine with: + +**API skills** (`discover-api`): +- REST API integration with Salesforce +- OAuth authentication for Salesforce +- Custom API endpoints using Salesforce data +- Webhook handling for Salesforce events + +**Workflow skills** (`discover-workflow`): +- Automated work item creation from CI/CD +- Status update automation based on deployments +- Scheduled bulk operations +- Integration with external systems + +**Data skills** (`discover-data`): +- ETL operations with Salesforce data +- Data transformation and analysis +- Export/import workflows +- Data quality and validation checks + +**Collaboration skills** (`discover-collaboration`): +- Syncing GitHub issues with GUS work items +- Automated Chatter updates from CI/CD +- Cross-platform project management +- Integration dashboards and reporting + +**Testing skills** (`discover-testing`): +- Integration testing for Salesforce integrations +- Automated testing of Salesforce workflows +- Validation of Salesforce data operations +- CI/CD integration with Salesforce deployments + +## Usage Instructions + +1. **Auto-activation**: This skill loads automatically when Claude Code detects Salesforce-related work +2. **Browse skills**: Run `cat ~/.claude/skills/salesforce/INDEX.md` for full category overview +3. **Load specific skills**: Use bash command above to load the comprehensive sf-cli-operations skill +4. **Follow patterns**: Use the 8+ patterns for common Salesforce operations +5. **Combine skills**: Load related skills for comprehensive Salesforce integration + +## Progressive Loading + +This gateway skill (~250 lines, ~2.5K tokens) enables progressive loading: +- **Level 1**: Gateway loads automatically (you're here now) +- **Level 2**: Load category INDEX.md (~3K tokens) for full overview +- **Level 3**: Load sf-cli-operations.md (~4K tokens) for complete guide + +Total context: 2.5K + 3K + 4K = ~10K tokens for complete Salesforce expertise. + +## Quick Start Examples + +**"Create a user story in GUS"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Pattern 1: Creating User Stories with Dependencies + +**"Query work items for a sprint"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Concept 2: SOQL Queries and Pattern 6: Finding Record IDs + +**"Update status for multiple work items"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Pattern 2: Bulk Status Updates + +**"Create a sprint and assign work items"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Pattern 4: Managing Sprints and Builds + +**"Add a comment to Chatter"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Pattern 3: Creating Chatter Posts + +**"Export work items for reporting"**: +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` +See Pattern 7: Bulk Data Export + +## Best Practices + +**Always:** +- **Use `sf` CLI tool directly** - Do NOT use `minigus` or wrapper tools +- Infer WI number from git branch name when not explicitly provided (see Pattern 9) +- Specify `--target-org gus` (or your org alias) to avoid ambiguity +- Query for IDs before creating related records +- Use bulk operations for multiple record updates +- Verify target org before destructive operations +- Include all required fields when creating records + +**Never:** +- Use `minigus` or other wrapper tools - always use `sf` directly +- Hardcode record IDs (they vary across orgs) +- Update records without verifying they exist first +- Use single updates in loops (use bulk operations) +- Forget API name suffixes (`__c` for custom fields) +- Commit authentication tokens or credentials + +**Security:** +- ⚠️ Always verify target org before updates +- ⚠️ Query before update to verify record existence +- ⚠️ Be cautious with bulk delete operations +- ⚠️ Use `--target-org` flag for all operations +- ⚠️ Never commit credentials to version control + +--- + +**Next Steps**: Run `cat ~/.claude/skills/salesforce/INDEX.md` to see full category details, or load `sf-cli-operations.md` for the complete Salesforce CLI guide. diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md new file mode 100644 index 0000000..0c2a24b --- /dev/null +++ b/skills/salesforce/INDEX.md @@ -0,0 +1,187 @@ +# Salesforce Skills + +Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator (GUS). + +## Category Overview + +**Total Skills**: 1 +**Focus**: Salesforce CLI, Agile Accelerator (GUS), SOQL, Record Management, Chatter +**Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration +**Default Org**: Examples use `gus` as the org alias - replace with your org alias if different + +## Skills in This Category + +### sf-cli-operations.md +**Description**: Using Salesforce CLI (sf) for managing orgs, data, and records +**Lines**: ~400 +**Use When**: +- Creating or updating Salesforce records (Work Items/WI, User Stories, Bugs, Epics, Sprints) +- Querying Salesforce data using SOQL +- Managing Salesforce org authentication and connections +- Performing bulk data operations on Salesforce objects +- Interacting with Agile Accelerator (GUS) objects +- Creating Chatter posts or comments +- Updating record statuses or fields +- Executing REST API calls against Salesforce +- Working with WI (Work Items) in any context + +**Key Concepts**: sf CLI, SOQL queries, record creation/updates, bulk operations, Agile Accelerator objects, Chatter integration, REST API + +--- + +## Common Workflows + +### Create User Story +**Goal**: Create a new user story in Agile Accelerator + +**Sequence**: +1. `sf-cli-operations.md` - Query for Epic, Sprint, and User IDs +2. `sf-cli-operations.md` - Create work item with proper fields +3. `sf-cli-operations.md` - Add Chatter post for updates + +**Example**: Creating a feature story linked to current sprint + +--- + +### Sprint Planning +**Goal**: Set up and populate a new sprint + +**Sequence**: +1. `sf-cli-operations.md` - Create sprint record +2. `sf-cli-operations.md` - Query backlog items +3. `sf-cli-operations.md` - Bulk assign items to sprint +4. `sf-cli-operations.md` - Update story points and priorities + +**Example**: Planning two-week sprint with 20 stories + +--- + +### Bulk Status Update +**Goal**: Update status for multiple work items + +**Sequence**: +1. `sf-cli-operations.md` - Query work items by criteria +2. `sf-cli-operations.md` - Export to CSV +3. `sf-cli-operations.md` - Bulk update using CSV +4. `sf-cli-operations.md` - Verify updates with query + +**Example**: Moving all sprint items from "Ready" to "In Progress" + +--- + +### Work Item Reporting +**Goal**: Export work items for analysis + +**Sequence**: +1. `sf-cli-operations.md` - Query work items with related data +2. `sf-cli-operations.md` - Export to CSV using bulk API +3. Process data for reporting (Excel, BI tools) + +**Example**: Sprint burndown report or velocity tracking + +--- + +## Skill Combinations + +### With API Skills (`discover-api`) +- REST API calls to Salesforce +- Custom API integrations +- Webhook handling for Salesforce events +- Authentication using OAuth + +**Common combos**: +- `sf-cli-operations.md` + `api/rest-api-design.md` +- `sf-cli-operations.md` + `api/api-authentication.md` + +--- + +### With Workflow Skills (`discover-workflow`) +- Automated work item creation +- Status update automation +- Integration with CI/CD pipelines +- Scheduled bulk operations + +**Common combos**: +- `sf-cli-operations.md` + `workflow/automation-scripting.md` +- `sf-cli-operations.md` + `cicd/ci-optimization.md` + +--- + +### With Data Skills (`discover-data`) +- ETL operations with Salesforce data +- Data transformation and analysis +- Export/import workflows +- Data quality checks + +**Common combos**: +- `sf-cli-operations.md` + `data/data-transformation.md` +- `sf-cli-operations.md` + `data/data-validation.md` + +--- + +### With Collaboration Skills (`discover-collaboration`) +- Syncing GitHub issues with GUS +- Automated Chatter updates +- Cross-platform project management +- Integration dashboards + +**Common combos**: +- `sf-cli-operations.md` + `collaboration/github/github-issues-projects.md` +- `sf-cli-operations.md` + `collaboration/github/github-actions-workflows.md` + +--- + +## Quick Selection Guide + +**Use sf CLI when**: +- Managing Agile Accelerator (GUS) work items +- Performing bulk operations on Salesforce data +- Automating Salesforce workflows +- Integrating Salesforce with other tools +- Querying data for reports and dashboards + +**Common Objects**: +- `ADM_Work__c` - Work Items (Stories, Bugs, Tasks) +- `ADM_Epic__c` - Epics +- `ADM_Sprint__c` - Sprints +- `ADM_Build__c` - Scheduled Builds +- `ADM_Product_Tag__c` - Product Tags +- `FeedItem` - Chatter Posts +- `FeedComment` - Chatter Comments + +**Authentication**: +- Use `sf org login web --alias gus` for interactive login (or your preferred alias) +- Use `--target-org gus` flag to specify org (replace `gus` with your org alias if different) +- List orgs with `sf org list` +- Open org in browser with `sf org open --target-org gus` + +**Best Practices**: +- Always query for IDs before creating related records +- Use bulk operations for multiple record updates +- Include all required fields when creating records +- Verify target org before destructive operations +- Use HTML formatting for rich text fields +- Export data before bulk deletes +- **Always use `sf` CLI directly** - Do NOT use `minigus` or wrapper tools +- Infer WI number from git branch name when not explicitly provided + +--- + +## Loading Skills + +All skills are available in the `skills/salesforce/` directory: + +```bash +cat ~/.claude/skills/salesforce/sf-cli-operations.md +``` + +**Pro tip**: Start by authenticating to your org, then query for necessary record IDs before creating or updating records. + +--- + +**Related Categories**: +- `discover-api` - REST API patterns and authentication +- `discover-workflow` - Automation and scripting +- `discover-data` - Data transformation and ETL +- `discover-collaboration` - GitHub and project management +- `discover-testing` - Integration testing strategies diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md new file mode 100644 index 0000000..ed94da3 --- /dev/null +++ b/skills/salesforce/sf-cli-operations.md @@ -0,0 +1,588 @@ +--- +name: salesforce-sf-cli-operations +description: Using Salesforce CLI (sf) for managing orgs, data, and records +--- + +# Salesforce CLI Operations + +**Scope**: Comprehensive guide to using the Salesforce CLI (sf) for common operations +**Lines**: ~400 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Creating or updating Salesforce records (Work Items/WI, User Stories, Bugs, Epics, Sprints) +- Querying Salesforce data using SOQL +- Managing Salesforce org authentication and connections +- Performing bulk data operations on Salesforce objects +- Interacting with Agile Accelerator (GUS) objects +- Creating Chatter posts or comments +- Updating record statuses or fields +- Executing REST API calls against Salesforce +- Working with WI (Work Items) in any context + +**Important Notes**: +- **Always use `sf` CLI tool directly** - Do NOT use `minigus` or other wrapper tools +- **WI Number Inference**: If no WI number is explicitly mentioned, check the current git branch name for WI patterns (e.g., `W-12345678`, `wi-12345678`, `12345678-feature-name`) +- **Default Org**: This skill uses `gus` as the default org alias for examples. If your org has a different alias, replace `--target-org gus` with your org alias (e.g., `--target-org my-gus`, `--target-org production-gus`, etc.) + +## Core Concepts + +### Concept 1: Org Authentication + +**Authentication Methods**: +- Web login flow: `sf org login web` +- JWT bearer flow: `sf org login jwt` +- Access token: `sf org login access-token` +- SFDX auth URL: `sf org login sfdx-url` + +**Note**: This skill uses `gus` as the default org alias throughout. Replace with your org alias if different. + +```bash +# Login to org via web browser (use 'gus' as alias for consistency) +sf org login web --alias gus + +# Or use your own alias +sf org login web --alias my-gus + +# List all authenticated orgs +sf org list + +# Display details about the gus org +sf org display --target-org gus + +# Open gus org in browser +sf org open --target-org gus +``` + +### Concept 2: SOQL Queries + +**Query Execution**: +- Query using `sf data query` +- Support for CSV, JSON, and human-readable output +- Can query standard and custom objects +- Tooling API access for metadata queries + +```bash +# Query Work Items (Agile Accelerator) +sf data query \ + --query "SELECT Id, Name, Status__c, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 10" \ + --target-org my-org \ + --result-format json + +# Query from file +sf data query \ + --file query.soql \ + --output-file results.csv \ + --result-format csv \ + --target-org my-org + +# Query using Tooling API +sf data query \ + --query "SELECT Name, ApiVersion FROM ApexClass" \ + --use-tooling-api \ + --target-org my-org +``` + +### Concept 3: Record Creation and Updates + +**Creating Records**: +- Use `sf data create record` for single records +- Specify object type with `--sobject` +- Provide field values with `--values` +- Use bulk operations for multiple records + +```bash +# Create a Work Item (User Story) +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement new feature' Status__c='New' Priority__c='P2' Assignee__c='005xx000001X8Uz'" \ + --target-org my-org + +# Create an Epic +sf data create record \ + --sobject ADM_Epic__c \ + --values "Name='Q1 2024 Features' Status__c='New' Description__c='Major features for Q1'" \ + --target-org my-org + +# Update a record by ID +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDe \ + --values "Status__c='In Progress' Assignee__c='005xx000001X8Uz'" \ + --target-org my-org + +# Update a record by field match +sf data update record \ + --sobject ADM_Work__c \ + --where "Subject__c='Old Feature Name'" \ + --values "Subject__c='New Feature Name' Priority__c='P1'" \ + --target-org my-org +``` + +--- + +## Patterns + +### Pattern 1: Creating User Stories with Dependencies + +**When to use**: +- Creating work items in Agile Accelerator +- Setting up sprints and assignments +- Linking related work items + +```bash +# ❌ Bad: Creating story without proper fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Feature'" \ + --target-org my-org + +# ✅ Good: Complete story with all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement user authentication' \ + Status__c='New' \ + Priority__c='P1' \ + Story_Points__c=5 \ + Epic__c='a0Axx000001ABCD' \ + Sprint__c='a1Fxx000002EFGH' \ + Assignee__c='005xx000001X8Uz' \ + Type__c='User Story' \ + Description__c='

As a user, I want to log in securely

'" \ + --target-org my-org +``` + +**Benefits**: +- Complete work item with proper tracking +- Links to Epic and Sprint for planning +- Includes story points for velocity tracking + +### Pattern 2: Bulk Status Updates + +**Use case**: Update multiple work items to a new status + +```bash +# Query work items to update +sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH' AND Status__c = 'Ready for Development'" \ + --result-format json \ + --target-org my-org > work_items.json + +# Create CSV for bulk update +# work_items.csv: +# Id,Status__c +# a07xx00000ABCD1,In Progress +# a07xx00000ABCD2,In Progress +# a07xx00000ABCD3,In Progress + +# Bulk update using CSV +sf data update bulk \ + --sobject ADM_Work__c \ + --file work_items.csv \ + --wait 10 \ + --target-org my-org +``` + +### Pattern 3: Creating Chatter Posts + +**Use case**: Post updates to Chatter feeds for work items + +```bash +# Create a Chatter post on a Work Item (real example) +sf data create record \ + --sobject FeedItem \ + --values "ParentId=a07EE00002Gt6lxYAB Body='Phase 1.2 complete: S3 Storage Service implemented with 32 tests, file size validation, and CI configuration.'" \ + --target-org gus + +# Output: +# Successfully created record: 0D5EE00002Q7BDg0AN. +# Creating record for FeedItem... done + +# Create a Chatter post with simple update +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' Body='Work has been completed and is ready for review'" \ + --target-org gus + +# Create a Chatter comment on an existing post +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='LGTM - approved for merge'" \ + --target-org gus + +# Query Chatter feed for a record +sf data query \ + --query "SELECT Id, Body, CreatedBy.Name, CreatedDate FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE' ORDER BY CreatedDate DESC LIMIT 20" \ + --target-org gus \ + --json + +# Post to Chatter using WI from git branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Id') + +sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} Body='Feature implementation completed and ready for review'" \ + --target-org gus +``` + +**Key Points**: +- `ParentId` is the Salesforce record Id (e.g., `a07EE00002Gt6lxYAB`) +- `Body` contains the Chatter post text (plain text or basic formatting) +- You don't need `Type='TextPost'` - it's the default for FeedItem +- Returns the FeedItem Id (e.g., `0D5EE00002Q7BDg0AN`) on success +- For comments, use `FeedComment` object with `FeedItemId` and `CommentBody` + +### Pattern 4: Managing Sprints and Builds + +**Use case**: Create and manage sprint/build records + +```bash +# Create a new Sprint +sf data create record \ + --sobject ADM_Sprint__c \ + --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29 Status__c='Planned'" \ + --target-org my-org + +# Create a Build (Scheduled Build) +sf data create record \ + --sobject ADM_Build__c \ + --values "Name='Release 1.0' Scheduled_Date__c=2024-02-01 Status__c='Scheduled'" \ + --target-org my-org + +# Assign work items to sprint +sf data update record \ + --sobject ADM_Work__c \ + --where "Status__c='Ready for Development' AND Sprint__c = null" \ + --values "Sprint__c='a1Fxx000002EFGH'" \ + --target-org my-org +``` + +### Pattern 5: REST API Direct Calls + +**Use case**: Make custom REST API calls for operations not covered by standard commands + +```bash +# Get Work Item details via REST API +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --target-org my-org + +# Update multiple fields via PATCH +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --method PATCH \ + --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ + --target-org my-org + +# Query using REST API +sf api request rest \ + "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ + --target-org my-org +``` + +### Pattern 6: Finding Record IDs + +**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) + +```bash +# Find user ID by name +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ + --target-org my-org + +# Find Epic by name +sf data query \ + --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --target-org my-org + +# Find Sprint by name +sf data query \ + --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --target-org my-org + +# Find Product Tag +sf data query \ + --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ + --target-org my-org +``` + +### Pattern 7: Bulk Data Export + +**Use case**: Export large datasets for analysis or backup + +```bash +# Export all work items for a sprint +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Subject__c, Status__c, Priority__c, Assignee__r.Name, Story_Points__c FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH'" \ + --output-file sprint_42_items.csv \ + --wait 10 \ + --target-org my-org + +# Export all open bugs +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Subject__c, Status__c, Priority__c, Found_in_Build__r.Name FROM ADM_Work__c WHERE Type__c = 'Bug' AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ + --output-file open_bugs.csv \ + --target-org my-org +``` + +### Pattern 8: Complex Field Updates with HTML + +**Use case**: Update rich text fields with HTML content + +```bash +# Update work item description with HTML +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Description__c='

Overview

Additional details here.

'" \ + --target-org my-org + +# Create work item with formatted description +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Database migration' \ + Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ + Status__c='New' \ + Type__c='User Story'" \ + --target-org my-org +``` + +### Pattern 9: Inferring WI Number from Git Branch + +**Use case**: Automatically determine WI number from current git branch name + +**IMPORTANT**: The WI number (e.g., W-12345678) is stored in the `Name` field, NOT the `Id` field. You must query by `Name` to get the actual Salesforce record `Id`. + +```bash +# ❌ Bad: Asking user for WI number when it's in the branch +echo "What's the WI number?" + +# ✅ Good: Extract WI number from git branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +# Common branch patterns that contain WI numbers: +# - W-12345678 +# - wi-12345678 +# - 12345678-feature-name +# - feature/W-12345678 +# - bugfix/12345678-fix-issue + +# Query work item by Name field (NOT Id) to get the record details +# The Name field contains 'W-12345678', the Id field contains Salesforce record ID +sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json + +# Get the Salesforce record Id from the WI Name +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Id') + +# ❌ WRONG: Using WI number as if it were the Salesforce Id +sf data update record \ + --sobject ADM_Work__c \ + --record-id "W-${WI_NUMBER}" \ + --values "Status__c='In Progress'" # This will fail! + +# ✅ CORRECT: Use the actual Salesforce record Id from the query +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org gus + +# Add Chatter post to the WI using the Salesforce record Id +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' Body='Starting work on this feature' Type='TextPost'" \ + --target-org gus +``` + +**Benefits**: +- No need to manually specify WI numbers when working in feature branches +- Reduces errors from copying/pasting wrong WI numbers +- Enables automation based on git workflow +- Makes scripts more context-aware + +**Key Points**: +- WI number (W-12345678) is in the `Name` field, NOT the `Id` field +- Always query `WHERE Name = 'W-...'` to find work items +- Extract the `Id` field from query results for record operations +- Use `--json` flag for easier parsing with `jq` + +--- + +## Quick Reference + +### Common Objects + +``` +Object API Name | Description | Key Fields +-----------------------|--------------------------|---------------------------------- +ADM_Work__c | Work Items/Stories/Bugs | Subject__c, Status__c, Priority__c, Assignee__c +ADM_Epic__c | Epics | Name, Status__c, Description__c +ADM_Sprint__c | Sprints | Name, Start_Date__c, End_Date__c, Status__c +ADM_Build__c | Scheduled Builds | Name, Scheduled_Date__c, Status__c +ADM_Product_Tag__c | Product Tags | Name +FeedItem | Chatter Posts | ParentId, Body, Type +FeedComment | Chatter Comments | FeedItemId, CommentBody +User | Users | Name, Email, Username +``` + +### Common Status Values + +``` +Object | Status Field | Common Values +------------|----------------|---------------------------------------------- +Work Items | Status__c | New, In Progress, Code Review, Fixed, Closed +Epics | Status__c | New, In Progress, Completed +Sprints | Status__c | Planned, Active, Completed +Builds | Status__c | Scheduled, In Progress, Released +``` + +### Key Guidelines + +``` +✅ DO: Always specify --target-org to avoid ambiguity +✅ DO: Use --result-format json for programmatic processing +✅ DO: Query for IDs before creating related records +✅ DO: Use bulk operations for updating multiple records +✅ DO: Include required fields when creating records +✅ DO: Use HTML formatting for rich text fields +✅ DO: Always use sf CLI tool directly (NOT minigus or wrapper tools) +✅ DO: Infer WI number from git branch name when not explicitly provided + +❌ DON'T: Hardcode record IDs (query for them instead) +❌ DON'T: Create records without required fields +❌ DON'T: Use single updates for large datasets (use bulk) +❌ DON'T: Forget to specify API names (use __c suffix for custom fields) +❌ DON'T: Update records without verifying they exist first +❌ DON'T: Use minigus or other wrapper tools - always use sf directly +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Update records without verifying they exist +sf data update record \ + --sobject ADM_Work__c \ + --record-id UNKNOWN_ID \ + --values "Status__c='Fixed'" + +# ✅ CORRECT: Query first, then update +RECORD_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Subject__c='Known Subject'" \ + --result-format json --target-org my-org | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$RECORD_ID" \ + --values "Status__c='Fixed'" \ + --target-org my-org +``` + +❌ **Hardcoding IDs**: IDs vary across orgs (sandbox vs production) +✅ **Correct approach**: Query by unique identifiers (names, external IDs) + +### Common Mistakes + +```bash +# ❌ Don't: Missing required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story'" \ + --target-org my-org + +# ✅ Correct: Include all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ + --target-org my-org +``` + +❌ **Incomplete records**: Missing required fields causes failures +✅ **Better**: Query existing records to understand required fields + +```bash +# ❌ Don't: Single updates in loop +for id in $(cat work_item_ids.txt); do + sf data update record --sobject ADM_Work__c --record-id "$id" --values "Status__c='Closed'" +done + +# ✅ Correct: Use bulk update +sf data update bulk \ + --sobject ADM_Work__c \ + --file updates.csv \ + --wait 10 \ + --target-org my-org +``` + +❌ **Inefficient operations**: Single updates are slow and hit API limits +✅ **Better**: Bulk API for batch operations + +--- + +## Security Considerations + +**Review before creating this skill**: Check `.claude/audits/safety-checklist.md` + +**Does this skill involve** (check all that apply): +- [x] Authentication or authorization +- [ ] Cryptographic operations +- [x] Data deletion or modification +- [ ] Production deployments +- [ ] Database migrations +- [ ] File system operations +- [x] Network requests +- [ ] Executable scripts + +**If yes to any above, ensure**: +- [x] Sensitive operations have clear ⚠️ warnings +- [x] Examples use placeholder credentials (never real) +- [x] Destructive operations include rollback procedures +- [x] Production examples follow security best practices +- [x] Scripts validate all inputs +- [x] No hardcoded secrets or API keys +- [x] Dangerous commands clearly marked + +**Security Notes**: +- ⚠️ Always verify target org before destructive operations +- ⚠️ Use `--target-org` flag to prevent accidental production updates +- ⚠️ Query before update to verify record existence +- ⚠️ Be cautious with bulk delete operations +- ⚠️ Never commit authentication tokens or credentials + +--- + +## Related Skills + +- `salesforce/agile-accelerator-workflows.md` - GUS-specific workflows and automation +- `collaboration/github/github-issues-projects.md` - Alternative project management +- `data/data-transformation.md` - Processing exported Salesforce data +- `testing/integration-testing.md` - Testing Salesforce integrations +- `api/rest-api-design.md` - Understanding Salesforce REST API patterns +- `workflow/automation-scripting.md` - Automating Salesforce operations + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) From 8da7060fdf6be081ca52e1f041c8cfd11cd853f5 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 14:32:16 -0300 Subject: [PATCH 02/21] Added salesforce skill --- skills/salesforce/sf-cli-operations.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md index ed94da3..c733f12 100644 --- a/skills/salesforce/sf-cli-operations.md +++ b/skills/salesforce/sf-cli-operations.md @@ -193,14 +193,14 @@ sf data update bulk \ **Use case**: Post updates to Chatter feeds for work items ```bash -# Create a Chatter post on a Work Item (real example) +# Create a Chatter post on a Work Item (example with real output format) sf data create record \ --sobject FeedItem \ - --values "ParentId=a07EE00002Gt6lxYAB Body='Phase 1.2 complete: S3 Storage Service implemented with 32 tests, file size validation, and CI configuration.'" \ + --values "ParentId=a07xx00000ABCDE Body='Phase 1.2 complete: S3 Storage Service implemented with 32 tests, file size validation, and CI configuration.'" \ --target-org gus # Output: -# Successfully created record: 0D5EE00002Q7BDg0AN. +# Successfully created record: 0D5xx00000FGHIJ. # Creating record for FeedItem... done # Create a Chatter post with simple update @@ -236,10 +236,10 @@ sf data create record \ ``` **Key Points**: -- `ParentId` is the Salesforce record Id (e.g., `a07EE00002Gt6lxYAB`) +- `ParentId` is the Salesforce record Id (e.g., `a07xx00000ABCDE` for Work Items) - `Body` contains the Chatter post text (plain text or basic formatting) - You don't need `Type='TextPost'` - it's the default for FeedItem -- Returns the FeedItem Id (e.g., `0D5EE00002Q7BDg0AN`) on success +- Returns the FeedItem Id (e.g., `0D5xx00000FGHIJ`) on success - For comments, use `FeedComment` object with `FeedItemId` and `CommentBody` ### Pattern 4: Managing Sprints and Builds From 5ba160ba85ac30de24c2fb9d82d2232ad7cd6f69 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 15:44:59 -0300 Subject: [PATCH 03/21] Cleaned minugus references --- skills/discover-salesforce/SKILL.md | 3 +-- skills/salesforce/INDEX.md | 2 +- skills/salesforce/sf-cli-operations.md | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/skills/discover-salesforce/SKILL.md b/skills/discover-salesforce/SKILL.md index 0fbd1dc..c113415 100644 --- a/skills/discover-salesforce/SKILL.md +++ b/skills/discover-salesforce/SKILL.md @@ -213,7 +213,7 @@ See Pattern 7: Bulk Data Export ## Best Practices **Always:** -- **Use `sf` CLI tool directly** - Do NOT use `minigus` or wrapper tools +- **Use `sf` CLI tool directly** for all Salesforce operations - Infer WI number from git branch name when not explicitly provided (see Pattern 9) - Specify `--target-org gus` (or your org alias) to avoid ambiguity - Query for IDs before creating related records @@ -222,7 +222,6 @@ See Pattern 7: Bulk Data Export - Include all required fields when creating records **Never:** -- Use `minigus` or other wrapper tools - always use `sf` directly - Hardcode record IDs (they vary across orgs) - Update records without verifying they exist first - Use single updates in loops (use bulk operations) diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index 0c2a24b..f1b1f96 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -162,7 +162,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Verify target org before destructive operations - Use HTML formatting for rich text fields - Export data before bulk deletes -- **Always use `sf` CLI directly** - Do NOT use `minigus` or wrapper tools +- **Always use `sf` CLI directly** for all Salesforce operations - Infer WI number from git branch name when not explicitly provided --- diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md index c733f12..08fe63f 100644 --- a/skills/salesforce/sf-cli-operations.md +++ b/skills/salesforce/sf-cli-operations.md @@ -26,7 +26,7 @@ Activate this skill when: - Working with WI (Work Items) in any context **Important Notes**: -- **Always use `sf` CLI tool directly** - Do NOT use `minigus` or other wrapper tools +- **Always use `sf` CLI tool directly** for all Salesforce operations - **WI Number Inference**: If no WI number is explicitly mentioned, check the current git branch name for WI patterns (e.g., `W-12345678`, `wi-12345678`, `12345678-feature-name`) - **Default Org**: This skill uses `gus` as the default org alias for examples. If your org has a different alias, replace `--target-org gus` with your org alias (e.g., `--target-org my-gus`, `--target-org production-gus`, etc.) @@ -464,7 +464,7 @@ Builds | Status__c | Scheduled, In Progress, Released ✅ DO: Use bulk operations for updating multiple records ✅ DO: Include required fields when creating records ✅ DO: Use HTML formatting for rich text fields -✅ DO: Always use sf CLI tool directly (NOT minigus or wrapper tools) +✅ DO: Always use sf CLI tool directly for all operations ✅ DO: Infer WI number from git branch name when not explicitly provided ❌ DON'T: Hardcode record IDs (query for them instead) @@ -472,7 +472,6 @@ Builds | Status__c | Scheduled, In Progress, Released ❌ DON'T: Use single updates for large datasets (use bulk) ❌ DON'T: Forget to specify API names (use __c suffix for custom fields) ❌ DON'T: Update records without verifying they exist first -❌ DON'T: Use minigus or other wrapper tools - always use sf directly ``` --- From 9bbc967e0c548ec99930077d22f2b0cdb004601e Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 15:57:48 -0300 Subject: [PATCH 04/21] Verified fields for mentionen objects, like epic, work item or sprint --- skills/salesforce/sf-cli-operations.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md index 08fe63f..5d578d1 100644 --- a/skills/salesforce/sf-cli-operations.md +++ b/skills/salesforce/sf-cli-operations.md @@ -250,14 +250,14 @@ sf data create record \ # Create a new Sprint sf data create record \ --sobject ADM_Sprint__c \ - --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29 Status__c='Planned'" \ - --target-org my-org + --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29" \ + --target-org gus # Create a Build (Scheduled Build) sf data create record \ --sobject ADM_Build__c \ - --values "Name='Release 1.0' Scheduled_Date__c=2024-02-01 Status__c='Scheduled'" \ - --target-org my-org + --values "Name='Release 1.0' External_ID__c='R010'" \ + --target-org gus # Assign work items to sprint sf data update record \ @@ -435,9 +435,9 @@ sf data create record \ Object API Name | Description | Key Fields -----------------------|--------------------------|---------------------------------- ADM_Work__c | Work Items/Stories/Bugs | Subject__c, Status__c, Priority__c, Assignee__c -ADM_Epic__c | Epics | Name, Status__c, Description__c -ADM_Sprint__c | Sprints | Name, Start_Date__c, End_Date__c, Status__c -ADM_Build__c | Scheduled Builds | Name, Scheduled_Date__c, Status__c +ADM_Epic__c | Epics | Name, Description__c, Health__c, Priority__c +ADM_Sprint__c | Sprints | Name, Start_Date__c, End_Date__c +ADM_Build__c | Scheduled Builds | Name, External_ID__c ADM_Product_Tag__c | Product Tags | Name FeedItem | Chatter Posts | ParentId, Body, Type FeedComment | Chatter Comments | FeedItemId, CommentBody @@ -450,11 +450,13 @@ User | Users | Name, Email, Username Object | Status Field | Common Values ------------|----------------|---------------------------------------------- Work Items | Status__c | New, In Progress, Code Review, Fixed, Closed -Epics | Status__c | New, In Progress, Completed -Sprints | Status__c | Planned, Active, Completed -Builds | Status__c | Scheduled, In Progress, Released ``` +**Important Notes**: +- **Epic** objects (`ADM_Epic__c`) do not have a `Status__c` field. They use `Health__c` instead (values: "On Track", "At Risk", etc.) +- **Sprint** objects (`ADM_Sprint__c`) do not have a `Status__c` field. Use `Start_Date__c` and `End_Date__c` to determine sprint status. +- **Build** objects (`ADM_Build__c`) do not have a `Status__c` or `Scheduled_Date__c` field. They use `Name` and `External_ID__c` for identification. + ### Key Guidelines ``` From 53ab1a130d8a672afe80e4dead3b558839b5b829 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:03:57 -0300 Subject: [PATCH 05/21] Changes examples and references for consistency, remove redundancy --- skills/salesforce/sf-cli-operations.md | 167 ++++++++++++++++--------- 1 file changed, 108 insertions(+), 59 deletions(-) diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md index 5d578d1..75c3465 100644 --- a/skills/salesforce/sf-cli-operations.md +++ b/skills/salesforce/sf-cli-operations.md @@ -71,7 +71,7 @@ sf org open --target-org gus # Query Work Items (Agile Accelerator) sf data query \ --query "SELECT Id, Name, Status__c, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 10" \ - --target-org my-org \ + --target-org gus \ --result-format json # Query from file @@ -79,13 +79,13 @@ sf data query \ --file query.soql \ --output-file results.csv \ --result-format csv \ - --target-org my-org + --target-org gus # Query using Tooling API sf data query \ --query "SELECT Name, ApiVersion FROM ApexClass" \ --use-tooling-api \ - --target-org my-org + --target-org gus ``` ### Concept 3: Record Creation and Updates @@ -101,27 +101,27 @@ sf data query \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='Implement new feature' Status__c='New' Priority__c='P2' Assignee__c='005xx000001X8Uz'" \ - --target-org my-org + --target-org gus # Create an Epic sf data create record \ --sobject ADM_Epic__c \ --values "Name='Q1 2024 Features' Status__c='New' Description__c='Major features for Q1'" \ - --target-org my-org + --target-org gus # Update a record by ID sf data update record \ --sobject ADM_Work__c \ --record-id a07xx00000ABCDe \ --values "Status__c='In Progress' Assignee__c='005xx000001X8Uz'" \ - --target-org my-org + --target-org gus # Update a record by field match sf data update record \ --sobject ADM_Work__c \ --where "Subject__c='Old Feature Name'" \ --values "Subject__c='New Feature Name' Priority__c='P1'" \ - --target-org my-org + --target-org gus ``` --- @@ -140,7 +140,7 @@ sf data update record \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='Feature'" \ - --target-org my-org + --target-org gus # ✅ Good: Complete story with all required fields sf data create record \ @@ -154,7 +154,7 @@ sf data create record \ Assignee__c='005xx000001X8Uz' \ Type__c='User Story' \ Description__c='

As a user, I want to log in securely

'" \ - --target-org my-org + --target-org gus ``` **Benefits**: @@ -171,7 +171,7 @@ sf data create record \ sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH' AND Status__c = 'Ready for Development'" \ --result-format json \ - --target-org my-org > work_items.json + --target-org gus > work_items.json # Create CSV for bulk update # work_items.csv: @@ -185,7 +185,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file work_items.csv \ --wait 10 \ - --target-org my-org + --target-org gus ``` ### Pattern 3: Creating Chatter Posts @@ -193,7 +193,7 @@ sf data update bulk \ **Use case**: Post updates to Chatter feeds for work items ```bash -# Create a Chatter post on a Work Item (example with real output format) +# Create a Chatter post on a Work Item sf data create record \ --sobject FeedItem \ --values "ParentId=a07xx00000ABCDE Body='Phase 1.2 complete: S3 Storage Service implemented with 32 tests, file size validation, and CI configuration.'" \ @@ -203,12 +203,6 @@ sf data create record \ # Successfully created record: 0D5xx00000FGHIJ. # Creating record for FeedItem... done -# Create a Chatter post with simple update -sf data create record \ - --sobject FeedItem \ - --values "ParentId='a07xx00000ABCDE' Body='Work has been completed and is ready for review'" \ - --target-org gus - # Create a Chatter comment on an existing post sf data create record \ --sobject FeedComment \ @@ -221,18 +215,25 @@ sf data query \ --target-org gus \ --json -# Post to Chatter using WI from git branch +# Post to Chatter using WI from git branch (with error handling) BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) -WORK_ITEM_ID=$(sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ - --json | jq -r '.result.records[0].Id') -sf data create record \ - --sobject FeedItem \ - --values "ParentId=${WORK_ITEM_ID} Body='Feature implementation completed and ready for review'" \ - --target-org gus +if [ -n "$WI_NUMBER" ]; then + QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json) + + WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + + if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} Body='Feature implementation completed and ready for review'" \ + --target-org gus + fi +fi ``` **Key Points**: @@ -264,7 +265,7 @@ sf data update record \ --sobject ADM_Work__c \ --where "Status__c='Ready for Development' AND Sprint__c = null" \ --values "Sprint__c='a1Fxx000002EFGH'" \ - --target-org my-org + --target-org gus ``` ### Pattern 5: REST API Direct Calls @@ -275,19 +276,19 @@ sf data update record \ # Get Work Item details via REST API sf api request rest \ "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ - --target-org my-org + --target-org gus # Update multiple fields via PATCH sf api request rest \ "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ --method PATCH \ --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ - --target-org my-org + --target-org gus # Query using REST API sf api request rest \ "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ - --target-org my-org + --target-org gus ``` ### Pattern 6: Finding Record IDs @@ -298,22 +299,22 @@ sf api request rest \ # Find user ID by name sf data query \ --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ - --target-org my-org + --target-org gus # Find Epic by name sf data query \ --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ - --target-org my-org + --target-org gus # Find Sprint by name sf data query \ --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ - --target-org my-org + --target-org gus # Find Product Tag sf data query \ --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ - --target-org my-org + --target-org gus ``` ### Pattern 7: Bulk Data Export @@ -327,14 +328,14 @@ sf data export bulk \ --query "SELECT Id, Subject__c, Status__c, Priority__c, Assignee__r.Name, Story_Points__c FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH'" \ --output-file sprint_42_items.csv \ --wait 10 \ - --target-org my-org + --target-org gus # Export all open bugs sf data export bulk \ --sobject ADM_Work__c \ --query "SELECT Id, Subject__c, Status__c, Priority__c, Found_in_Build__r.Name FROM ADM_Work__c WHERE Type__c = 'Bug' AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ --output-file open_bugs.csv \ - --target-org my-org + --target-org gus ``` ### Pattern 8: Complex Field Updates with HTML @@ -347,7 +348,7 @@ sf data update record \ --sobject ADM_Work__c \ --record-id a07xx00000ABCDE \ --values "Description__c='

Overview

  • Item 1
  • Item 2

Additional details here.

'" \ - --target-org my-org + --target-org gus # Create work item with formatted description sf data create record \ @@ -356,7 +357,7 @@ sf data create record \ Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ Status__c='New' \ Type__c='User Story'" \ - --target-org my-org + --target-org gus ``` ### Pattern 9: Inferring WI Number from Git Branch @@ -373,6 +374,12 @@ echo "What's the WI number?" BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) +# Validate WI number was found +if [ -z "$WI_NUMBER" ]; then + echo "Error: No WI number found in branch name: $BRANCH" + exit 1 +fi + # Common branch patterns that contain WI numbers: # - W-12345678 # - wi-12345678 @@ -382,16 +389,28 @@ WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) # Query work item by Name field (NOT Id) to get the record details # The Name field contains 'W-12345678', the Id field contains Salesforce record ID -sf data query \ +QUERY_RESULT=$(sf data query \ --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ --target-org gus \ - --json + --json) + +# Check if query returned results +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item W-${WI_NUMBER} not found in GUS" + exit 1 +fi + +echo "$QUERY_RESULT" # Get the Salesforce record Id from the WI Name -WORK_ITEM_ID=$(sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ - --json | jq -r '.result.records[0].Id') +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +# Validate we got a valid ID +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract Salesforce ID for W-${WI_NUMBER}" + exit 1 +fi # ❌ WRONG: Using WI number as if it were the Salesforce Id sf data update record \ @@ -409,7 +428,7 @@ sf data update record \ # Add Chatter post to the WI using the Salesforce record Id sf data create record \ --sobject FeedItem \ - --values "ParentId='${WORK_ITEM_ID}' Body='Starting work on this feature' Type='TextPost'" \ + --values "ParentId='${WORK_ITEM_ID}' Body='Starting work on this feature'" \ --target-org gus ``` @@ -444,23 +463,27 @@ FeedComment | Chatter Comments | FeedItemId, CommentBody User | Users | Name, Email, Username ``` -### Common Status Values +### Common Status and Health Fields ``` -Object | Status Field | Common Values -------------|----------------|---------------------------------------------- -Work Items | Status__c | New, In Progress, Code Review, Fixed, Closed +Object | Status/Health Field | Common Values +----------------------|---------------------|---------------------------------------------- +Work Items (ADM_Work__c) | Status__c | New, In Progress, Code Review, Fixed, Closed +Epics (ADM_Epic__c) | Health__c | On Track, At Risk, Off Track +Sprints (ADM_Sprint__c) | (none) | Use Start_Date__c and End_Date__c +Builds (ADM_Build__c) | (none) | Use Name and External_ID__c ``` **Important Notes**: -- **Epic** objects (`ADM_Epic__c`) do not have a `Status__c` field. They use `Health__c` instead (values: "On Track", "At Risk", etc.) -- **Sprint** objects (`ADM_Sprint__c`) do not have a `Status__c` field. Use `Start_Date__c` and `End_Date__c` to determine sprint status. -- **Build** objects (`ADM_Build__c`) do not have a `Status__c` or `Scheduled_Date__c` field. They use `Name` and `External_ID__c` for identification. +- Work Items use `Status__c` for workflow tracking +- Epics use `Health__c` instead of Status__c for health tracking +- Sprints and Builds do not have status fields - use dates and identifiers ### Key Guidelines +**Essential Practices:** ``` -✅ DO: Always specify --target-org to avoid ambiguity +✅ DO: Always specify --target-org gus to avoid ambiguity ✅ DO: Use --result-format json for programmatic processing ✅ DO: Query for IDs before creating related records ✅ DO: Use bulk operations for updating multiple records @@ -468,12 +491,38 @@ Work Items | Status__c | New, In Progress, Code Review, Fixed, Closed ✅ DO: Use HTML formatting for rich text fields ✅ DO: Always use sf CLI tool directly for all operations ✅ DO: Infer WI number from git branch name when not explicitly provided +✅ DO: Add error handling for queries that may return no results +✅ DO: Validate extracted values before using them in subsequent commands +``` +**Common Mistakes to Avoid:** +``` ❌ DON'T: Hardcode record IDs (query for them instead) ❌ DON'T: Create records without required fields ❌ DON'T: Use single updates for large datasets (use bulk) ❌ DON'T: Forget to specify API names (use __c suffix for custom fields) ❌ DON'T: Update records without verifying they exist first +❌ DON'T: Assume queries will always return results +❌ DON'T: Skip validation of jq output (check for null/empty) +``` + +**Error Handling Pattern:** +```bash +# Query with result validation +QUERY_RESULT=$(sf data query --query "..." --target-org gus --json) +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: No records found" + exit 1 +fi + +RECORD_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" = "null" ]; then + echo "Error: Failed to extract record ID" + exit 1 +fi ``` --- @@ -492,13 +541,13 @@ sf data update record \ # ✅ CORRECT: Query first, then update RECORD_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Subject__c='Known Subject'" \ - --result-format json --target-org my-org | jq -r '.result.records[0].Id') + --result-format json --target-org gus | jq -r '.result.records[0].Id') sf data update record \ --sobject ADM_Work__c \ --record-id "$RECORD_ID" \ --values "Status__c='Fixed'" \ - --target-org my-org + --target-org gus ``` ❌ **Hardcoding IDs**: IDs vary across orgs (sandbox vs production) @@ -511,13 +560,13 @@ sf data update record \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='New Story'" \ - --target-org my-org + --target-org gus # ✅ Correct: Include all required fields sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ - --target-org my-org + --target-org gus ``` ❌ **Incomplete records**: Missing required fields causes failures @@ -534,7 +583,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file updates.csv \ --wait 10 \ - --target-org my-org + --target-org gus ``` ❌ **Inefficient operations**: Single updates are slow and hit API limits From 4be192490cef0b2a4fbeba17793db2273c3728bd Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:20:59 -0300 Subject: [PATCH 06/21] Modified the skill to extract user email instead of guessing --- skills/salesforce/INDEX.md | 3 +- skills/salesforce/sf-cli-operations.md | 84 +++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index f1b1f96..12f7c3c 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -13,7 +13,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-cli-operations.md **Description**: Using Salesforce CLI (sf) for managing orgs, data, and records -**Lines**: ~400 +**Lines**: ~720 **Use When**: - Creating or updating Salesforce records (Work Items/WI, User Stories, Bugs, Epics, Sprints) - Querying Salesforce data using SOQL @@ -164,6 +164,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Export data before bulk deletes - **Always use `sf` CLI directly** for all Salesforce operations - Infer WI number from git branch name when not explicitly provided +- Dynamically fetch user email/ID instead of hardcoding (see Pattern 10) --- diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md index 75c3465..4ab8467 100644 --- a/skills/salesforce/sf-cli-operations.md +++ b/skills/salesforce/sf-cli-operations.md @@ -6,7 +6,7 @@ description: Using Salesforce CLI (sf) for managing orgs, data, and records # Salesforce CLI Operations **Scope**: Comprehensive guide to using the Salesforce CLI (sf) for common operations -**Lines**: ~400 +**Lines**: ~720 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) @@ -28,6 +28,7 @@ Activate this skill when: **Important Notes**: - **Always use `sf` CLI tool directly** for all Salesforce operations - **WI Number Inference**: If no WI number is explicitly mentioned, check the current git branch name for WI patterns (e.g., `W-12345678`, `wi-12345678`, `12345678-feature-name`) +- **User Email Detection**: Never hardcode user emails. Always fetch the current user's email dynamically using `sf org list --json` or `sf org display user --json` (see Pattern 10) - **Default Org**: This skill uses `gus` as the default org alias for examples. If your org has a different alias, replace `--target-org gus` with your org alias (e.g., `--target-org my-gus`, `--target-org production-gus`, etc.) ## Core Concepts @@ -444,6 +445,85 @@ sf data create record \ - Extract the `Id` field from query results for record operations - Use `--json` flag for easier parsing with `jq` +### Pattern 10: Getting Current User Information + +**Use case**: Dynamically retrieve the logged-in user's email and other details for queries + +**IMPORTANT**: Never hardcode user emails (like `user@gus.com`). Always fetch the current user's email dynamically from the authenticated org. + +```bash +# ❌ Bad: Hardcoding user email +sf data query \ + --query "SELECT Name, Subject__c, Status__c FROM ADM_Work__c WHERE Assignee__r.Email = 'user@gus.com'" \ + --target-org gus + +# ✅ Good: Get current user email from authenticated org (by alias) +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +# Query work items for the logged-in user +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c != 'Closed'" \ + --target-org gus \ + --result-format json + +# Alternative: Get from most recently used org +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs | sort_by(.lastUsed) | reverse | .[0].username') + +# Alternative: Get user details from org display user +USER_INFO=$(sf org display user --target-org gus --json) +USER_EMAIL=$(echo "$USER_INFO" | jq -r '.result.email') +USER_ID=$(echo "$USER_INFO" | jq -r '.result.id') +USER_NAME=$(echo "$USER_INFO" | jq -r '.result.username') + +# Use the user ID directly in queries (more efficient than joining on email) +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c + FROM ADM_Work__c + WHERE Assignee__c = '${USER_ID}' + AND Status__c = 'In Progress'" \ + --target-org gus + +# Get user's org details +ORG_INFO=$(sf org display --target-org gus --json) +ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') +INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') + +echo "Logged in as: $USER_EMAIL" +echo "User ID: $USER_ID" +echo "Org ID: $ORG_ID" +echo "Instance: $INSTANCE_URL" +``` + +**Benefits**: +- Works across different users and orgs without code changes +- No need to hardcode usernames or emails +- Scripts are portable and can be shared with team members +- Safer - prevents accidentally using wrong user credentials + +**Key Points**: +- Use `sf org list --json` to get username from authenticated orgs +- Use `sf org display user --json` for complete user details including ID +- Filter by org alias when multiple orgs are authenticated +- Use User ID (`Assignee__c`) in queries instead of email when possible (more efficient) +- Always validate that the user info was retrieved successfully before using + +**Error Handling Pattern**: +```bash +# Get user email with validation +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +if [ -z "$USER_EMAIL" ] || [ "$USER_EMAIL" = "null" ]; then + echo "Error: Could not retrieve user email. Is the org authenticated?" + echo "Run: sf org login web --alias gus" + exit 1 +fi + +echo "Querying work items for: $USER_EMAIL" +``` + --- ## Quick Reference @@ -491,6 +571,7 @@ Builds (ADM_Build__c) | (none) | Use Name and External_ID__c ✅ DO: Use HTML formatting for rich text fields ✅ DO: Always use sf CLI tool directly for all operations ✅ DO: Infer WI number from git branch name when not explicitly provided +✅ DO: Dynamically fetch user email/ID instead of hardcoding (Pattern 10) ✅ DO: Add error handling for queries that may return no results ✅ DO: Validate extracted values before using them in subsequent commands ``` @@ -498,6 +579,7 @@ Builds (ADM_Build__c) | (none) | Use Name and External_ID__c **Common Mistakes to Avoid:** ``` ❌ DON'T: Hardcode record IDs (query for them instead) +❌ DON'T: Hardcode user emails (fetch dynamically with Pattern 10) ❌ DON'T: Create records without required fields ❌ DON'T: Use single updates for large datasets (use bulk) ❌ DON'T: Forget to specify API names (use __c suffix for custom fields) From d542118dec1b75f0ff90661a236a08d1cfe8d592 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:34:09 -0300 Subject: [PATCH 07/21] Splitted the skills into more a more atomic approach --- skills/discover-salesforce/SKILL.md | 192 +++--- skills/salesforce/INDEX.md | 301 +++++++-- skills/salesforce/sf-automation.md | 382 ++++++++++++ skills/salesforce/sf-bulk-operations.md | 320 ++++++++++ skills/salesforce/sf-chatter.md | 368 +++++++++++ skills/salesforce/sf-cli-operations.md | 720 ---------------------- skills/salesforce/sf-org-auth.md | 290 +++++++++ skills/salesforce/sf-record-operations.md | 406 ++++++++++++ skills/salesforce/sf-soql-queries.md | 394 ++++++++++++ skills/salesforce/sf-work-items.md | 385 ++++++++++++ 10 files changed, 2902 insertions(+), 856 deletions(-) create mode 100644 skills/salesforce/sf-automation.md create mode 100644 skills/salesforce/sf-bulk-operations.md create mode 100644 skills/salesforce/sf-chatter.md delete mode 100644 skills/salesforce/sf-cli-operations.md create mode 100644 skills/salesforce/sf-org-auth.md create mode 100644 skills/salesforce/sf-record-operations.md create mode 100644 skills/salesforce/sf-soql-queries.md create mode 100644 skills/salesforce/sf-work-items.md diff --git a/skills/discover-salesforce/SKILL.md b/skills/discover-salesforce/SKILL.md index c113415..904d3f9 100644 --- a/skills/discover-salesforce/SKILL.md +++ b/skills/discover-salesforce/SKILL.md @@ -1,6 +1,6 @@ --- name: discover-salesforce -description: Automatically discover Salesforce CLI (sf) skills when working with Salesforce orgs, Agile Accelerator (GUS), SOQL queries, work items (WI), user stories, sprints, or Chatter. Activates for Salesforce integration and automation tasks. +description: Automatically discover Salesforce CLI (sf) skills when working with Salesforce orgs, Agile Accelerator (GUS), SOQL queries, work items (WI, wis, wi, WIs), user stories, sprints, epics, or Chatter. Activates for Salesforce integration and automation tasks. --- # Salesforce Skills Discovery @@ -25,9 +25,15 @@ This skill auto-activates when you're working with: ### Quick Reference -The Salesforce category contains 1 comprehensive skill: +The Salesforce category contains **7 focused skills**: -1. **sf-cli-operations** - Complete guide to Salesforce CLI operations, SOQL, record management, bulk operations, and Agile Accelerator workflows +1. **sf-org-auth** - Authentication, org management, and user information +2. **sf-soql-queries** - SOQL queries and data retrieval +3. **sf-record-operations** - Creating and updating individual records +4. **sf-work-items** - Managing work items, sprints, epics, and builds (GUS) +5. **sf-chatter** - Chatter posts, comments, and feed interactions +6. **sf-bulk-operations** - Bulk updates, exports, and large-scale operations +7. **sf-automation** - Git integration, WI inference, and automated workflows **Note**: All examples use `gus` as the default org alias. This refers to your Salesforce org where Agile Accelerator (GUS) is configured. If your org has a different alias, simply replace `--target-org gus` with your org alias throughout. @@ -47,79 +53,120 @@ This loads the full Salesforce category index with: ### Load Specific Skills -Load the Salesforce CLI operations skill: +Load individual skills as needed: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +# Authentication and org management +cat ~/.claude/skills/salesforce/sf-org-auth.md + +# SOQL queries and data retrieval +cat ~/.claude/skills/salesforce/sf-soql-queries.md + +# Create/update individual records +cat ~/.claude/skills/salesforce/sf-record-operations.md + +# Work items, sprints, and epics +cat ~/.claude/skills/salesforce/sf-work-items.md + +# Chatter posts and comments +cat ~/.claude/skills/salesforce/sf-chatter.md + +# Bulk operations and exports +cat ~/.claude/skills/salesforce/sf-bulk-operations.md + +# Git integration and automation +cat ~/.claude/skills/salesforce/sf-automation.md ``` ## Common Workflows ### Create User Story -**Sequence**: Query dependencies → Create work item → Add Chatter update +**Sequence**: Get user → Query IDs → Create work item → Add Chatter update ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-org-auth.md # Get current user +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query for IDs +cat ~/.claude/skills/salesforce/sf-work-items.md # Create work item +cat ~/.claude/skills/salesforce/sf-chatter.md # Add Chatter post ``` -Use Pattern 1 (Creating User Stories with Dependencies) and Pattern 6 (Finding Record IDs). - ### Sprint Planning **Sequence**: Create sprint → Query backlog → Bulk assign items ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-work-items.md # Create sprint +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query backlog +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Bulk assign ``` -Use Pattern 4 (Managing Sprints and Builds) and Pattern 2 (Bulk Status Updates). - ### Bulk Status Update **Sequence**: Query work items → Export CSV → Bulk update → Verify ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query items +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Bulk update ``` -Use Pattern 2 (Bulk Status Updates) and Pattern 7 (Bulk Data Export). +### Git Integration +**Sequence**: Extract WI from branch → Query details → Update status → Post to Chatter + +```bash +cat ~/.claude/skills/salesforce/sf-automation.md # WI inference +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query WI +cat ~/.claude/skills/salesforce/sf-work-items.md # Update status +cat ~/.claude/skills/salesforce/sf-chatter.md # Post update +``` ### Work Item Reporting **Sequence**: Query with related data → Export to CSV → Analyze ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Complex queries +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Export data ``` -Use Pattern 7 (Bulk Data Export) and SOQL query patterns from Concept 2. - ## Skill Selection Guide -**Use sf-cli-operations when:** -- Creating or updating work items in Agile Accelerator (GUS) -- Performing bulk operations on Salesforce data -- Querying Salesforce using SOQL -- Managing sprints, epics, and builds -- Posting updates to Chatter feeds -- Automating Salesforce workflows -- Integrating Salesforce with other tools -- Exporting data for reporting and analysis - -**Common Operations:** -- **Authentication**: `sf org login web`, `sf org list`, `sf org display` -- **Queries**: `sf data query` with SOQL syntax -- **Record Creation**: `sf data create record` with `--sobject` and `--values` -- **Record Updates**: `sf data update record` by ID or field match -- **Bulk Operations**: `sf data update bulk`, `sf data export bulk` -- **REST API**: `sf api request rest` for custom operations - -**Common Objects:** -- `ADM_Work__c` - Work Items (Stories, Bugs, Tasks) -- `ADM_Epic__c` - Epics -- `ADM_Sprint__c` - Sprints -- `ADM_Build__c` - Scheduled Builds -- `ADM_Product_Tag__c` - Product Tags -- `FeedItem` - Chatter Posts -- `FeedComment` - Chatter Comments -- `User` - Salesforce Users +**Use sf-org-auth when:** +- Logging into Salesforce orgs +- Managing multiple org connections +- Getting current user email/ID dynamically +- Switching between environments + +**Use sf-soql-queries when:** +- Querying Salesforce data +- Finding record IDs (Users, Epics, Sprints, etc.) +- Building reports or dashboards +- Working with related objects + +**Use sf-record-operations when:** +- Creating 1-5 records individually +- Updating specific records by ID +- Working with custom objects +- Using REST API for advanced operations + +**Use sf-work-items when:** +- Creating user stories, bugs, or tasks +- Managing sprints and sprint planning +- Working with epics and product tags +- Sprint reporting + +**Use sf-chatter when:** +- Posting updates to work items +- Adding comments to records +- Automating notifications from CI/CD + +**Use sf-bulk-operations when:** +- Updating 10+ records at once +- Exporting large datasets (>2000 records) +- Bulk status changes +- Avoiding API rate limits + +**Use sf-automation when:** +- Extracting WI numbers from git branches +- Automating Chatter posts from commits +- Creating git hooks for Salesforce +- CI/CD integration with GUS ## Integration with Other Skills @@ -159,74 +206,77 @@ Salesforce skills commonly combine with: 1. **Auto-activation**: This skill loads automatically when Claude Code detects Salesforce-related work 2. **Browse skills**: Run `cat ~/.claude/skills/salesforce/INDEX.md` for full category overview -3. **Load specific skills**: Use bash command above to load the comprehensive sf-cli-operations skill -4. **Follow patterns**: Use the 8+ patterns for common Salesforce operations +3. **Load specific skills**: Use bash commands above to load individual skills +4. **Follow patterns**: Each skill contains 5-10 patterns for common operations 5. **Combine skills**: Load related skills for comprehensive Salesforce integration ## Progressive Loading -This gateway skill (~250 lines, ~2.5K tokens) enables progressive loading: +This gateway skill (~300 lines, ~3K tokens) enables progressive loading: - **Level 1**: Gateway loads automatically (you're here now) -- **Level 2**: Load category INDEX.md (~3K tokens) for full overview -- **Level 3**: Load sf-cli-operations.md (~4K tokens) for complete guide +- **Level 2**: Load category INDEX.md (~350 lines, ~3.5K tokens) for full overview +- **Level 3**: Load specific skill (~150-220 lines, ~1.5-2K tokens each) for detailed guidance -Total context: 2.5K + 3K + 4K = ~10K tokens for complete Salesforce expertise. +Total context: 3K + 3.5K + (1.5-2K per skill) = efficient skill discovery and usage. ## Quick Start Examples -**"Create a user story in GUS"**: +**"Authenticate to Salesforce"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-org-auth.md ``` -See Pattern 1: Creating User Stories with Dependencies -**"Query work items for a sprint"**: +**"Query my work items"**: +```bash +cat ~/.claude/skills/salesforce/sf-soql-queries.md +``` + +**"Create a user story in GUS"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-work-items.md ``` -See Concept 2: SOQL Queries and Pattern 6: Finding Record IDs **"Update status for multiple work items"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-bulk-operations.md ``` -See Pattern 2: Bulk Status Updates -**"Create a sprint and assign work items"**: +**"Post update to Chatter"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-chatter.md ``` -See Pattern 4: Managing Sprints and Builds -**"Add a comment to Chatter"**: +**"Extract WI from git branch"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-automation.md ``` -See Pattern 3: Creating Chatter Posts **"Export work items for reporting"**: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +cat ~/.claude/skills/salesforce/sf-bulk-operations.md ``` -See Pattern 7: Bulk Data Export ## Best Practices **Always:** - **Use `sf` CLI tool directly** for all Salesforce operations -- Infer WI number from git branch name when not explicitly provided (see Pattern 9) +- Infer WI number from git branch name when not explicitly provided +- Dynamically fetch user email/ID instead of hardcoding - Specify `--target-org gus` (or your org alias) to avoid ambiguity - Query for IDs before creating related records -- Use bulk operations for multiple record updates +- Use bulk operations for 10+ record updates - Verify target org before destructive operations - Include all required fields when creating records +- Validate query results before using extracted values **Never:** - Hardcode record IDs (they vary across orgs) +- Hardcode user emails (fetch dynamically) - Update records without verifying they exist first - Use single updates in loops (use bulk operations) -- Forget API name suffixes (`__c` for custom fields) +- Forget API name suffixes (`__c` for custom fields, `__r` for relationships) - Commit authentication tokens or credentials +- Skip validation of jq output (check for null/empty) **Security:** - ⚠️ Always verify target org before updates @@ -234,7 +284,9 @@ See Pattern 7: Bulk Data Export - ⚠️ Be cautious with bulk delete operations - ⚠️ Use `--target-org` flag for all operations - ⚠️ Never commit credentials to version control +- ⚠️ Don't post sensitive data to Chatter +- ⚠️ Test automation in sandbox before production --- -**Next Steps**: Run `cat ~/.claude/skills/salesforce/INDEX.md` to see full category details, or load `sf-cli-operations.md` for the complete Salesforce CLI guide. +**Next Steps**: Run `cat ~/.claude/skills/salesforce/INDEX.md` to see full category details, or load specific skills using the commands above. diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index 12f7c3c..997749a 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -4,28 +4,108 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ## Category Overview -**Total Skills**: 1 +**Total Skills**: 7 **Focus**: Salesforce CLI, Agile Accelerator (GUS), SOQL, Record Management, Chatter -**Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration +**Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration, automation **Default Org**: Examples use `gus` as the org alias - replace with your org alias if different ## Skills in This Category -### sf-cli-operations.md -**Description**: Using Salesforce CLI (sf) for managing orgs, data, and records -**Lines**: ~720 +### sf-org-auth.md +**Description**: Authenticate and manage Salesforce orgs using sf CLI +**Lines**: ~180 +**Use When**: +- Logging into Salesforce orgs +- Managing multiple org connections +- Retrieving current user information +- Getting org details and configuration +- Troubleshooting authentication issues + +**Key Concepts**: Web/JWT login, org management, user information, multi-org workflows + +--- + +### sf-soql-queries.md +**Description**: Query Salesforce data using SOQL and sf CLI +**Lines**: ~200 **Use When**: -- Creating or updating Salesforce records (Work Items/WI, User Stories, Bugs, Epics, Sprints) - Querying Salesforce data using SOQL -- Managing Salesforce org authentication and connections -- Performing bulk data operations on Salesforce objects -- Interacting with Agile Accelerator (GUS) objects -- Creating Chatter posts or comments -- Updating record statuses or fields -- Executing REST API calls against Salesforce -- Working with WI (Work Items) in any context +- Retrieving work items, users, or other records +- Finding record IDs for operations +- Exporting data for analysis +- Building reports or dashboards + +**Key Concepts**: SOQL syntax, output formats, finding IDs, date filters, aggregation + +--- + +### sf-record-operations.md +**Description**: Create and update Salesforce records using sf CLI +**Lines**: ~200 +**Use When**: +- Creating new Salesforce records +- Updating existing records +- Working with individual records (not bulk) +- Setting field values on records +- Using REST API for custom operations + +**Key Concepts**: Creating records, updating by ID/field match, REST API, validation + +--- + +### sf-work-items.md +**Description**: Manage Agile Accelerator (GUS) work items, sprints, and epics +**Lines**: ~220 +**Use When**: +- Creating user stories, bugs, or tasks in GUS +- Managing sprints and sprint planning +- Working with epics and product tags +- Managing scheduled builds +- Sprint planning and backlog management + +**Key Concepts**: Work item types, sprints, epics, builds, status workflows, sprint reporting + +--- + +### sf-chatter.md +**Description**: Create and manage Chatter posts and comments in Salesforce +**Lines**: ~180 +**Use When**: +- Posting updates to Chatter feeds +- Adding comments to work items +- Automating Chatter notifications +- Creating progress updates +- Documenting decisions on records + +**Key Concepts**: FeedItem, FeedComment, post types, automated updates, querying feeds + +--- + +### sf-bulk-operations.md +**Description**: Perform bulk data operations on Salesforce objects +**Lines**: ~150 +**Use When**: +- Updating multiple records at once +- Exporting large datasets +- Bulk status changes +- Mass data migrations +- Avoiding API limit issues + +**Key Concepts**: Bulk API, CSV operations, bulk updates/exports, bulk creation + +--- -**Key Concepts**: sf CLI, SOQL queries, record creation/updates, bulk operations, Agile Accelerator objects, Chatter integration, REST API +### sf-automation.md +**Description**: Automate Salesforce operations with git integration and CI/CD +**Lines**: ~170 +**Use When**: +- Integrating Salesforce with git workflows +- Inferring WI numbers from branch names +- Automating Chatter updates from CI/CD +- Building automated workflows +- Creating git hooks for Salesforce operations + +**Key Concepts**: WI inference, git integration, CI/CD workflows, automated status transitions --- @@ -35,9 +115,10 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator **Goal**: Create a new user story in Agile Accelerator **Sequence**: -1. `sf-cli-operations.md` - Query for Epic, Sprint, and User IDs -2. `sf-cli-operations.md` - Create work item with proper fields -3. `sf-cli-operations.md` - Add Chatter post for updates +1. `sf-org-auth.md` - Get current user info +2. `sf-soql-queries.md` - Query for Epic, Sprint, and User IDs +3. `sf-work-items.md` - Create work item with proper fields +4. `sf-chatter.md` - Add Chatter post for updates **Example**: Creating a feature story linked to current sprint @@ -47,10 +128,10 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator **Goal**: Set up and populate a new sprint **Sequence**: -1. `sf-cli-operations.md` - Create sprint record -2. `sf-cli-operations.md` - Query backlog items -3. `sf-cli-operations.md` - Bulk assign items to sprint -4. `sf-cli-operations.md` - Update story points and priorities +1. `sf-work-items.md` - Create sprint record +2. `sf-soql-queries.md` - Query backlog items +3. `sf-bulk-operations.md` - Bulk assign items to sprint +4. `sf-work-items.md` - Update story points and priorities **Example**: Planning two-week sprint with 20 stories @@ -60,21 +141,34 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator **Goal**: Update status for multiple work items **Sequence**: -1. `sf-cli-operations.md` - Query work items by criteria -2. `sf-cli-operations.md` - Export to CSV -3. `sf-cli-operations.md` - Bulk update using CSV -4. `sf-cli-operations.md` - Verify updates with query +1. `sf-soql-queries.md` - Query work items by criteria +2. `sf-bulk-operations.md` - Export to CSV +3. `sf-bulk-operations.md` - Bulk update using CSV +4. `sf-soql-queries.md` - Verify updates with query **Example**: Moving all sprint items from "Ready" to "In Progress" --- +### Automated Git Workflow +**Goal**: Automate WI updates based on git events + +**Sequence**: +1. `sf-automation.md` - Extract WI from branch name +2. `sf-soql-queries.md` - Query work item details +3. `sf-work-items.md` - Update work item status +4. `sf-chatter.md` - Post commit info to Chatter + +**Example**: Auto-update WI to "In Progress" on first commit + +--- + ### Work Item Reporting **Goal**: Export work items for analysis **Sequence**: -1. `sf-cli-operations.md` - Query work items with related data -2. `sf-cli-operations.md` - Export to CSV using bulk API +1. `sf-soql-queries.md` - Query work items with related data +2. `sf-bulk-operations.md` - Export to CSV using bulk API 3. Process data for reporting (Excel, BI tools) **Example**: Sprint burndown report or velocity tracking @@ -90,8 +184,8 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Authentication using OAuth **Common combos**: -- `sf-cli-operations.md` + `api/rest-api-design.md` -- `sf-cli-operations.md` + `api/api-authentication.md` +- `sf-record-operations.md` + `api/rest-api-design.md` +- `sf-org-auth.md` + `api/api-authentication.md` --- @@ -102,8 +196,8 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Scheduled bulk operations **Common combos**: -- `sf-cli-operations.md` + `workflow/automation-scripting.md` -- `sf-cli-operations.md` + `cicd/ci-optimization.md` +- `sf-automation.md` + `workflow/automation-scripting.md` +- `sf-bulk-operations.md` + `cicd/ci-optimization.md` --- @@ -114,8 +208,8 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Data quality checks **Common combos**: -- `sf-cli-operations.md` + `data/data-transformation.md` -- `sf-cli-operations.md` + `data/data-validation.md` +- `sf-bulk-operations.md` + `data/data-transformation.md` +- `sf-soql-queries.md` + `data/data-validation.md` --- @@ -126,45 +220,112 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Integration dashboards **Common combos**: -- `sf-cli-operations.md` + `collaboration/github/github-issues-projects.md` -- `sf-cli-operations.md` + `collaboration/github/github-actions-workflows.md` +- `sf-automation.md` + `collaboration/github/github-issues-projects.md` +- `sf-chatter.md` + `collaboration/github/github-actions-workflows.md` --- ## Quick Selection Guide -**Use sf CLI when**: -- Managing Agile Accelerator (GUS) work items -- Performing bulk operations on Salesforce data -- Automating Salesforce workflows -- Integrating Salesforce with other tools -- Querying data for reports and dashboards - -**Common Objects**: -- `ADM_Work__c` - Work Items (Stories, Bugs, Tasks) -- `ADM_Epic__c` - Epics -- `ADM_Sprint__c` - Sprints -- `ADM_Build__c` - Scheduled Builds -- `ADM_Product_Tag__c` - Product Tags -- `FeedItem` - Chatter Posts -- `FeedComment` - Chatter Comments +**Authentication & Setup**: +- Starting fresh → `sf-org-auth.md` +- Need user info → `sf-org-auth.md` + +**Querying Data**: +- Finding records → `sf-soql-queries.md` +- Complex queries → `sf-soql-queries.md` +- Getting IDs → `sf-soql-queries.md` + +**Single Records**: +- Create/update 1-5 records → `sf-record-operations.md` +- Work with custom objects → `sf-record-operations.md` + +**GUS Work Items**: +- Create user stories → `sf-work-items.md` +- Sprint planning → `sf-work-items.md` +- Epic management → `sf-work-items.md` + +**Communication**: +- Post updates → `sf-chatter.md` +- Automated notifications → `sf-chatter.md` + +**Bulk Operations**: +- Update 10+ records → `sf-bulk-operations.md` +- Export large datasets → `sf-bulk-operations.md` + +**Automation**: +- Git integration → `sf-automation.md` +- CI/CD workflows → `sf-automation.md` +- WI from branch → `sf-automation.md` + +--- + +## Common Objects + +**Work Items (ADM_Work__c)**: +- User Stories, Bugs, Tasks, Investigations +- Key fields: Subject__c, Status__c, Priority__c, Assignee__c + +**Epics (ADM_Epic__c)**: +- Feature groupings +- Key fields: Name, Health__c, Status__c + +**Sprints (ADM_Sprint__c)**: +- Iteration tracking +- Key fields: Name, Start_Date__c, End_Date__c + +**Builds (ADM_Build__c)**: +- Release tracking +- Key fields: Name, External_ID__c + +**Chatter (FeedItem, FeedComment)**: +- Posts and comments +- Key fields: ParentId, Body, CommentBody + +**Users (User)**: +- Salesforce users +- Key fields: Name, Email, Id + +--- + +## Best Practices **Authentication**: -- Use `sf org login web --alias gus` for interactive login (or your preferred alias) -- Use `--target-org gus` flag to specify org (replace `gus` with your org alias if different) -- List orgs with `sf org list` -- Open org in browser with `sf org open --target-org gus` - -**Best Practices**: -- Always query for IDs before creating related records -- Use bulk operations for multiple record updates +- Use meaningful aliases for orgs (e.g., gus, gus-prod, gus-dev) +- Dynamically fetch user email/ID instead of hardcoding +- Validate org connection before operations + +**Querying**: +- Always use LIMIT to avoid timeouts +- Query for IDs before creating related records +- Use `--result-format json` for scripting +- Validate query results before using extracted values + +**Record Operations**: - Include all required fields when creating records -- Verify target org before destructive operations -- Use HTML formatting for rich text fields -- Export data before bulk deletes -- **Always use `sf` CLI directly** for all Salesforce operations -- Infer WI number from git branch name when not explicitly provided -- Dynamically fetch user email/ID instead of hardcoding (see Pattern 10) +- Validate record existence before updates +- Use bulk operations for 10+ records + +**Work Items**: +- Link work items to sprints and epics +- Use story points for estimation +- Update status as work progresses + +**Chatter**: +- Keep posts clear and concise +- Don't post sensitive information +- Validate ParentId before posting + +**Bulk Operations**: +- Test with small dataset first +- Backup data before bulk deletes +- Use appropriate LIMIT in export queries + +**Automation**: +- Include WI number in branch names +- Validate WI exists before operations +- Test automation in sandbox first +- Add error handling and fallbacks --- @@ -173,10 +334,17 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator All skills are available in the `skills/salesforce/` directory: ```bash -cat ~/.claude/skills/salesforce/sf-cli-operations.md +# Load specific skill +cat ~/.claude/skills/salesforce/sf-org-auth.md +cat ~/.claude/skills/salesforce/sf-soql-queries.md +cat ~/.claude/skills/salesforce/sf-record-operations.md +cat ~/.claude/skills/salesforce/sf-work-items.md +cat ~/.claude/skills/salesforce/sf-chatter.md +cat ~/.claude/skills/salesforce/sf-bulk-operations.md +cat ~/.claude/skills/salesforce/sf-automation.md ``` -**Pro tip**: Start by authenticating to your org, then query for necessary record IDs before creating or updating records. +**Pro tip**: Start with `sf-org-auth.md` to authenticate, then use `sf-soql-queries.md` to find IDs, then create/update records with the appropriate skill. --- @@ -186,3 +354,4 @@ cat ~/.claude/skills/salesforce/sf-cli-operations.md - `discover-data` - Data transformation and ETL - `discover-collaboration` - GitHub and project management - `discover-testing` - Integration testing strategies +- `discover-cicd` - CI/CD pipeline integration diff --git a/skills/salesforce/sf-automation.md b/skills/salesforce/sf-automation.md new file mode 100644 index 0000000..3e7aa97 --- /dev/null +++ b/skills/salesforce/sf-automation.md @@ -0,0 +1,382 @@ +--- +name: salesforce-automation +description: Automate Salesforce operations with git integration and CI/CD +--- + +# Salesforce Automation + +**Scope**: Git integration, WI inference, and automated workflows +**Lines**: ~170 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Integrating Salesforce with git workflows +- Inferring WI numbers from branch names +- Automating Chatter updates from CI/CD +- Building automated workflows +- Creating git hooks for Salesforce operations +- Syncing git and GUS automatically + +--- + +## Core Concepts + +### Concept 1: WI Number Inference + +Extract WI numbers from git branch names automatically. + +**Common branch patterns**: +- `W-12345678` +- `wi-12345678` +- `12345678-feature-name` +- `feature/W-12345678` +- `bugfix/12345678-fix-issue` + +### Concept 2: Git Integration Points + +**Pre-commit**: Validate WI exists before commit +**Post-commit**: Post to Chatter after commit +**Pre-push**: Update WI status before push +**Post-merge**: Close WI after merge to main + +--- + +## Patterns + +### Pattern 1: Inferring WI Number from Git Branch + +**Use case**: Automatically determine WI number from current git branch name + +**IMPORTANT**: The WI number (e.g., W-12345678) is stored in the `Name` field, NOT the `Id` field. You must query by `Name` to get the actual Salesforce record `Id`. + +```bash +# ❌ Bad: Asking user for WI number when it's in the branch +echo "What's the WI number?" + +# ✅ Good: Extract WI number from git branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +# Validate WI number was found +if [ -z "$WI_NUMBER" ]; then + echo "Error: No WI number found in branch name: $BRANCH" + exit 1 +fi + +# Query work item by Name field (NOT Id) to get the record details +QUERY_RESULT=$(sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json) + +# Check if query returned results +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item W-${WI_NUMBER} not found in GUS" + exit 1 +fi + +# Get the Salesforce record Id from the WI Name +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +# Validate we got a valid ID +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract Salesforce ID for W-${WI_NUMBER}" + exit 1 +fi + +echo "Found work item: W-${WI_NUMBER} (ID: ${WORK_ITEM_ID})" +``` + +**Benefits**: +- No need to manually specify WI numbers +- Reduces errors from copying/pasting wrong WI numbers +- Enables automation based on git workflow +- Makes scripts more context-aware + +**Key Points**: +- WI number (W-12345678) is in the `Name` field, NOT the `Id` field +- Always query `WHERE Name = 'W-...'` to find work items +- Extract the `Id` field from query results for record operations +- Use `--json` flag for easier parsing with `jq` + +### Pattern 2: Automated Chatter Updates from Git + +**Use case**: Post commit information to Chatter automatically + +```bash +#!/bin/bash +# post-commit hook + +# Get WI from branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Get work item ID + WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Id') + + if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + # Get latest commit + COMMIT_MSG=$(git log -1 --pretty=%B) + COMMIT_HASH=$(git log -1 --pretty=%h) + + # Post to Chatter + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='Commit ${COMMIT_HASH}: ${COMMIT_MSG}'" \ + --target-org gus + + echo "Posted commit to W-${WI_NUMBER}" + fi +fi +``` + +### Pattern 3: CI/CD Integration + +**Use case**: Update work items from CI/CD pipeline + +```bash +#!/bin/bash +# ci-build.sh + +# Extract WI from branch or PR +WI_NUMBER=$(echo "$CI_BRANCH_NAME" | grep -oE '[0-9]{8}' | head -1) + +if [ -z "$WI_NUMBER" ]; then + echo "No WI number found, skipping GUS update" + exit 0 +fi + +# Get work item +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Id') + +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Work item W-${WI_NUMBER} not found" + exit 0 +fi + +# Post build status +if [ "$BUILD_STATUS" = "success" ]; then + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='✅ Build #${BUILD_NUMBER} passed +All tests: ${TEST_TOTAL}/${TEST_TOTAL} ✓ +Coverage: ${COVERAGE}% +Ready for deployment + +Build: ${BUILD_URL}'" \ + --target-org gus + + # Update status if still in progress + CURRENT_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Status__c') + + if [ "$CURRENT_STATUS" = "In Progress" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org gus + + echo "Moved W-${WI_NUMBER} to Code Review" + fi +else + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='❌ Build #${BUILD_NUMBER} failed +Failed tests: ${TEST_FAILED}/${TEST_TOTAL} +See: ${BUILD_URL} +Please investigate.'" \ + --target-org gus +fi +``` + +### Pattern 4: Pre-push Validation + +**Use case**: Validate WI status before allowing push + +```bash +#!/bin/bash +# pre-push hook + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Check if WI exists and is in valid state + WI_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Status__c') + + if [ "$WI_STATUS" = "null" ] || [ -z "$WI_STATUS" ]; then + echo "Error: Work item W-${WI_NUMBER} not found in GUS" + echo "Please create the work item before pushing" + exit 1 + fi + + if [ "$WI_STATUS" = "Closed" ]; then + echo "Error: Work item W-${WI_NUMBER} is already closed" + echo "Please reopen or create a new work item" + exit 1 + fi + + echo "✓ Work item W-${WI_NUMBER} validated (Status: ${WI_STATUS})" +fi +``` + +### Pattern 5: Automated Status Transitions + +**Use case**: Auto-update WI status based on git events + +```bash +#!/bin/bash +# auto-status-update.sh + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -z "$WI_NUMBER" ]; then + echo "No WI number in branch" + exit 0 +fi + +# Get work item +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Id') + +CURRENT_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ + --target-org gus \ + --json | jq -r '.result.records[0].Status__c') + +# Transition logic based on event +case "$1" in + start) + if [ "$CURRENT_STATUS" = "New" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org gus + echo "Started work on W-${WI_NUMBER}" + fi + ;; + + review) + if [ "$CURRENT_STATUS" = "In Progress" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org gus + echo "Moved W-${WI_NUMBER} to Code Review" + fi + ;; + + merged) + if [ "$CURRENT_STATUS" != "Closed" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Fixed'" \ + --target-org gus + echo "Marked W-${WI_NUMBER} as Fixed" + fi + ;; +esac +``` + +--- + +## Quick Reference + +### WI Number Extraction + +```bash +# Extract 8-digit WI number +WI_NUMBER=$(git rev-parse --abbrev-ref HEAD | grep -oE '[0-9]{8}' | head -1) + +# Validate extracted +[ -z "$WI_NUMBER" ] && echo "No WI found" && exit 1 + +# Query by Name field +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus --json | jq -r '.result.records[0].Id') +``` + +### Common Git Hooks + +``` +pre-commit - Validate WI exists +post-commit - Post to Chatter +pre-push - Check WI status +post-merge - Update status to Fixed +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Include WI number in branch names +✅ DO: Validate WI exists before operations +✅ DO: Use error handling in automation +✅ DO: Post meaningful updates to Chatter +✅ DO: Test automation in sandbox first +✅ DO: Add fallbacks for missing WI numbers +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Assume branch always has WI number +❌ DON'T: Skip validation of query results +❌ DON'T: Hardcode org or user information +❌ DON'T: Spam Chatter with every commit +❌ DON'T: Auto-close WIs without verification +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Validate all git inputs (branch names, commit messages) +- ⚠️ Don't expose tokens in git hooks +- ⚠️ Test automation thoroughly before production use +- ⚠️ Use appropriate error handling to avoid data corruption + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for automation +- `sf-soql-queries.md` - Query WI details +- `sf-work-items.md` - Work with GUS objects +- `sf-chatter.md` - Post automated updates + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-bulk-operations.md b/skills/salesforce/sf-bulk-operations.md new file mode 100644 index 0000000..aa66fee --- /dev/null +++ b/skills/salesforce/sf-bulk-operations.md @@ -0,0 +1,320 @@ +--- +name: salesforce-bulk-operations +description: Perform bulk data operations on Salesforce objects +--- + +# Salesforce Bulk Operations + +**Scope**: Bulk data updates, exports, and large-scale operations +**Lines**: ~150 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Updating multiple records at once +- Exporting large datasets +- Bulk status changes +- Mass data migrations +- Batch processing operations +- Avoiding API limit issues + +--- + +## Core Concepts + +### Concept 1: Bulk API vs Single Operations + +**Use Bulk API when**: +- Updating 10+ records +- Exporting large datasets (>2000 records) +- Performing repetitive operations +- Avoiding API limits + +**Use Single Operations when**: +- Updating 1-5 records +- Need immediate feedback +- Complex validation required + +### Concept 2: CSV-Based Operations + +Bulk operations use CSV files: +- Header row with field names +- One record per line +- Include `Id` for updates +- All fields as text + +--- + +## Patterns + +### Pattern 1: Bulk Status Updates + +**Use case**: Update multiple work items to a new status + +```bash +# Step 1: Query work items to update +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c + WHERE Sprint__c = 'a1Fxx000002EFGH' + AND Status__c = 'Ready for Development'" \ + --result-format csv \ + --target-org gus > work_items.csv + +# Step 2: Create CSV for bulk update +# Manually edit or use script to create: +# work_items_update.csv: +# Id,Status__c +# a07xx00000ABCD1,In Progress +# a07xx00000ABCD2,In Progress +# a07xx00000ABCD3,In Progress + +# Step 3: Bulk update using CSV +sf data update bulk \ + --sobject ADM_Work__c \ + --file work_items_update.csv \ + --wait 10 \ + --target-org gus + +echo "Bulk update complete" +``` + +### Pattern 2: Bulk Data Export + +**Use case**: Export large datasets for analysis or backup + +```bash +# Export all work items for a sprint +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Name, Subject__c, Status__c, Priority__c, + Assignee__r.Name, Assignee__r.Email, Story_Points__c, + Sprint__r.Name, Epic__r.Name, CreatedDate, LastModifiedDate + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42'" \ + --output-file sprint_42_items.csv \ + --wait 10 \ + --target-org gus + +echo "Exported to sprint_42_items.csv" + +# Export all open bugs +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Name, Subject__c, Status__c, Priority__c, + Assignee__r.Name, Found_in_Build__r.Name, CreatedDate + FROM ADM_Work__c + WHERE Type__c = 'Bug' + AND Status__c NOT IN ('Fixed', 'Not a Bug', 'Closed')" \ + --output-file open_bugs.csv \ + --wait 10 \ + --target-org gus + +# Export user list +sf data export bulk \ + --sobject User \ + --query "SELECT Id, Name, Email, Profile.Name, IsActive + FROM User + WHERE IsActive = true" \ + --output-file active_users.csv \ + --wait 10 \ + --target-org gus +``` + +### Pattern 3: Script-Driven Bulk Updates + +**Use case**: Generate CSV updates programmatically + +```bash +# Query and transform data +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + AND Status__c = 'Ready for Development'" \ + --result-format json \ + --target-org gus > work_items.json + +# Generate CSV with jq +echo "Id,Status__c,Assignee__c" > bulk_update.csv +jq -r '.result.records[] | [.Id, "In Progress", "005xx000001X8Uz"] | @csv' work_items.json >> bulk_update.csv + +# Bulk update +sf data update bulk \ + --sobject ADM_Work__c \ + --file bulk_update.csv \ + --wait 10 \ + --target-org gus + +echo "Updated $(wc -l < bulk_update.csv) records" +``` + +### Pattern 4: Bulk Assignment to Sprint + +**Use case**: Assign multiple backlog items to a sprint + +```bash +# Get sprint ID +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 43'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +# Query backlog items +sf data query \ + --query "SELECT Id, Name, Subject__c, Story_Points__c + FROM ADM_Work__c + WHERE Status__c = 'Ready for Development' + AND Sprint__c = null + ORDER BY Priority__c + LIMIT 20" \ + --result-format csv \ + --target-org gus > backlog.csv + +# Create assignment CSV +echo "Id,Sprint__c" > sprint_assignment.csv +tail -n +2 backlog.csv | cut -d',' -f1 | while read id; do + echo "${id},${SPRINT_ID}" >> sprint_assignment.csv +done + +# Bulk assign +sf data update bulk \ + --sobject ADM_Work__c \ + --file sprint_assignment.csv \ + --wait 10 \ + --target-org gus + +echo "Assigned $(tail -n +2 sprint_assignment.csv | wc -l) items to Sprint 43" +``` + +### Pattern 5: Bulk Creation + +**Use case**: Create multiple records from CSV + +```bash +# Create CSV file +# new_work_items.csv: +# Subject__c,Status__c,Type__c,Priority__c,Story_Points__c +# "Implement feature A",New,User Story,P2,3 +# "Implement feature B",New,User Story,P2,5 +# "Fix bug in login",New,Bug,P1,2 + +# Bulk create +sf data create bulk \ + --sobject ADM_Work__c \ + --file new_work_items.csv \ + --wait 10 \ + --target-org gus + +echo "Created records from CSV" +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Bulk update +sf data update bulk --sobject --file --wait --target-org + +# Bulk export +sf data export bulk --sobject --query "" --output-file --wait --target-org + +# Bulk create +sf data create bulk --sobject --file --wait --target-org + +# Bulk delete +sf data delete bulk --sobject --file --wait --target-org +``` + +### CSV Format + +**For Updates** (requires Id): +```csv +Id,Field1,Field2 +a07xx00000ABCD1,Value1,Value2 +a07xx00000ABCD2,Value3,Value4 +``` + +**For Creates** (no Id): +```csv +Field1,Field2,Field3 +Value1,Value2,Value3 +Value4,Value5,Value6 +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Use bulk operations for 10+ records +✅ DO: Test with small dataset first +✅ DO: Include --wait flag to monitor completion +✅ DO: Backup data before bulk deletes +✅ DO: Validate CSV format before upload +✅ DO: Use appropriate LIMIT in export queries +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Use single updates in loops +❌ DON'T: Skip validation of CSV data +❌ DON'T: Bulk delete without backup +❌ DON'T: Export without LIMIT (can timeout) +❌ DON'T: Update production without testing +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Loop with single updates +for id in $(cat work_item_ids.txt); do + sf data update record --sobject ADM_Work__c --record-id "$id" --values "Status__c='Closed'" +done + +# ✅ CORRECT: Use bulk update +echo "Id,Status__c" > bulk_update.csv +cat work_item_ids.txt | while read id; do + echo "${id},Closed" >> bulk_update.csv +done + +sf data update bulk \ + --sobject ADM_Work__c \ + --file bulk_update.csv \ + --wait 10 \ + --target-org gus +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Always backup before bulk deletes +- ⚠️ Test bulk operations in sandbox first +- ⚠️ Validate CSV data before upload +- ⚠️ Use appropriate WHERE clauses in exports +- ⚠️ Be cautious with bulk updates in production + +--- + +## Related Skills + +- `sf-soql-queries.md` - Query data for bulk operations +- `sf-record-operations.md` - Single record operations +- `sf-work-items.md` - Bulk sprint/epic management + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md new file mode 100644 index 0000000..de53267 --- /dev/null +++ b/skills/salesforce/sf-chatter.md @@ -0,0 +1,368 @@ +--- +name: salesforce-chatter +description: Create and manage Chatter posts and comments in Salesforce +--- + +# Salesforce Chatter Operations + +**Scope**: Creating Chatter posts, comments, and feed interactions +**Lines**: ~180 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Posting updates to Chatter feeds +- Adding comments to work items +- Automating Chatter notifications +- Creating progress updates +- Documenting decisions on records +- Sharing information with team members + +--- + +## Core Concepts + +### Concept 1: Chatter Objects + +**FeedItem** - Main Chatter posts +**FeedComment** - Comments on posts +**ParentId** - The record to post on (Work Item, Epic, User, etc.) + +**Key Fields**: +``` +FeedItem: + ParentId - Record ID to post on (required) + Body - Post text content (required) + Type - TextPost (default), LinkPost, ContentPost + +FeedComment: + FeedItemId - The post ID to comment on (required) + CommentBody - Comment text (required) +``` + +### Concept 2: Post Types + +**TextPost** (default): +- Plain text or basic formatting +- Most common type +- Simple status updates + +**LinkPost**: +- Shares URLs with preview +- Requires LinkUrl field + +**ContentPost**: +- Shares Salesforce files +- Requires ContentDocumentId + +--- + +## Patterns + +### Pattern 1: Creating Basic Chatter Posts + +**Use case**: Post updates to work item feeds + +```bash +# Simple text post +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' Body='Started work on this feature'" \ + --target-org gus + +# Multi-line post with details +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' Body='Phase 1.2 complete: +- S3 Storage Service implemented +- 32 tests passing +- File size validation added +- CI configuration updated'" \ + --target-org gus + +# Post with structured update +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' \ + Body='Implementation Status: + +✅ Backend API complete +✅ Database schema migrated +⏳ Frontend integration in progress +⏳ Testing pending + +Ready for review by EOD.'" \ + --target-org gus +``` + +### Pattern 2: Adding Comments to Posts + +**Use case**: Comment on existing Chatter posts + +```bash +# Create a comment +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='LGTM - approved for merge'" \ + --target-org gus + +# Comment with @mention (use User ID for @mention) +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='Thanks for the update! Please coordinate with {005xx000001X8Uz} for testing.'" \ + --target-org gus + +# Comment with feedback +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' \ + CommentBody='Great work! A few questions: +1. Did you test edge cases? +2. Is documentation updated? +3. Any performance concerns?'" \ + --target-org gus +``` + +### Pattern 3: Automated Status Updates + +**Use case**: Post Chatter updates from CI/CD or automation + +```bash +# Post build success notification +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Body='✅ Build #${BUILD_NUMBER} passed +All tests: 156/156 ✓ +Coverage: 94% +Ready for deployment'" \ + --target-org gus + +# Post failure notification +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Body='❌ Build #${BUILD_NUMBER} failed +Failed tests: 3/156 +See: ${BUILD_URL} +Please investigate.'" \ + --target-org gus +``` + +### Pattern 4: Post from Git Branch Context + +**Use case**: Automatically post to WI based on current git branch + +```bash +# Extract WI number from branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Query for work item + QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org gus \ + --json) + + WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + + if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + # Post update to work item + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='Feature implementation completed and ready for review + +Branch: ${BRANCH} +Commits: $(git log --oneline -n 5 | head -5)'" \ + --target-org gus + + echo "Posted update to W-${WI_NUMBER}" + else + echo "Work item W-${WI_NUMBER} not found" + fi +else + echo "No WI number found in branch: $BRANCH" +fi +``` + +### Pattern 5: Querying Chatter Feed + +**Use case**: Read Chatter posts and comments + +```bash +# Query Chatter feed for a record +sf data query \ + --query "SELECT Id, Body, CreatedBy.Name, CreatedDate + FROM FeedItem + WHERE ParentId = 'a07xx00000ABCDE' + ORDER BY CreatedDate DESC + LIMIT 20" \ + --target-org gus + +# Query comments on a post +sf data query \ + --query "SELECT CommentBody, CreatedBy.Name, CreatedDate + FROM FeedComment + WHERE FeedItemId = '0D5xx00000FGHIJ' + ORDER BY CreatedDate ASC" \ + --target-org gus + +# Find recent posts mentioning keywords +sf data query \ + --query "SELECT Id, Body, Parent.Name, CreatedBy.Name, CreatedDate + FROM FeedItem + WHERE Body LIKE '%deployment%' + AND CreatedDate = LAST_N_DAYS:7 + ORDER BY CreatedDate DESC" \ + --target-org gus +``` + +### Pattern 6: Link Posts + +**Use case**: Share URLs with previews + +```bash +# Post with link +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' \ + Type='LinkPost' \ + Body='Check out the documentation' \ + LinkUrl='https://docs.example.com/feature-guide'" \ + --target-org gus + +# Post PR link +PR_URL="https://github.com/org/repo/pull/123" +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Type='LinkPost' \ + Body='Pull request ready for review' \ + LinkUrl='${PR_URL}'" \ + --target-org gus +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Create post +sf data create record --sobject FeedItem --values "ParentId= Body=''" --target-org + +# Create comment +sf data create record --sobject FeedComment --values "FeedItemId= CommentBody=''" --target-org + +# Query feed +sf data query --query "SELECT Body, CreatedBy.Name FROM FeedItem WHERE ParentId = ''" --target-org + +# Query comments +sf data query --query "SELECT CommentBody FROM FeedComment WHERE FeedItemId = ''" --target-org +``` + +### Key Fields + +**FeedItem (Posts)**: +``` +ParentId - Record to post on (required) +Body - Post text (required, max 10000 chars) +Type - TextPost, LinkPost, ContentPost +LinkUrl - URL for LinkPost +Visibility - AllUsers, InternalUsers (default: AllUsers) +``` + +**FeedComment (Comments)**: +``` +FeedItemId - Post to comment on (required) +CommentBody - Comment text (required, max 10000 chars) +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Keep posts clear and concise +✅ DO: Use structured formatting for readability +✅ DO: Include relevant context and links +✅ DO: Validate ParentId exists before posting +✅ DO: Handle errors gracefully in automation +✅ DO: Use meaningful update messages +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Post sensitive information (passwords, tokens) +❌ DON'T: Spam feeds with automated posts +❌ DON'T: Use Chatter for private/confidential data +❌ DON'T: Forget to validate work item IDs +❌ DON'T: Post without checking if record exists +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Post without validating ParentId +sf data create record \ + --sobject FeedItem \ + --values "ParentId='UNKNOWN_ID' Body='Update'" \ + --target-org gus + +# ✅ CORRECT: Validate record exists +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' Body='Update'" \ + --target-org gus +else + echo "Error: Work item not found" +fi +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Never post sensitive data (credentials, tokens, PII) +- ⚠️ Chatter posts are visible to users with record access +- ⚠️ Don't use Chatter for confidential information +- ⚠️ Validate ParentId to avoid posting to wrong records +- ⚠️ Be mindful of @mentions and notifications + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for Chatter operations +- `sf-soql-queries.md` - Query Chatter feeds +- `sf-work-items.md` - Post updates to work items +- `sf-automation.md` - Automate Chatter from git/CI + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-cli-operations.md b/skills/salesforce/sf-cli-operations.md deleted file mode 100644 index 4ab8467..0000000 --- a/skills/salesforce/sf-cli-operations.md +++ /dev/null @@ -1,720 +0,0 @@ ---- -name: salesforce-sf-cli-operations -description: Using Salesforce CLI (sf) for managing orgs, data, and records ---- - -# Salesforce CLI Operations - -**Scope**: Comprehensive guide to using the Salesforce CLI (sf) for common operations -**Lines**: ~720 -**Last Updated**: 2025-12-03 -**Format Version**: 1.0 (Atomic) - ---- - -## When to Use This Skill - -Activate this skill when: -- Creating or updating Salesforce records (Work Items/WI, User Stories, Bugs, Epics, Sprints) -- Querying Salesforce data using SOQL -- Managing Salesforce org authentication and connections -- Performing bulk data operations on Salesforce objects -- Interacting with Agile Accelerator (GUS) objects -- Creating Chatter posts or comments -- Updating record statuses or fields -- Executing REST API calls against Salesforce -- Working with WI (Work Items) in any context - -**Important Notes**: -- **Always use `sf` CLI tool directly** for all Salesforce operations -- **WI Number Inference**: If no WI number is explicitly mentioned, check the current git branch name for WI patterns (e.g., `W-12345678`, `wi-12345678`, `12345678-feature-name`) -- **User Email Detection**: Never hardcode user emails. Always fetch the current user's email dynamically using `sf org list --json` or `sf org display user --json` (see Pattern 10) -- **Default Org**: This skill uses `gus` as the default org alias for examples. If your org has a different alias, replace `--target-org gus` with your org alias (e.g., `--target-org my-gus`, `--target-org production-gus`, etc.) - -## Core Concepts - -### Concept 1: Org Authentication - -**Authentication Methods**: -- Web login flow: `sf org login web` -- JWT bearer flow: `sf org login jwt` -- Access token: `sf org login access-token` -- SFDX auth URL: `sf org login sfdx-url` - -**Note**: This skill uses `gus` as the default org alias throughout. Replace with your org alias if different. - -```bash -# Login to org via web browser (use 'gus' as alias for consistency) -sf org login web --alias gus - -# Or use your own alias -sf org login web --alias my-gus - -# List all authenticated orgs -sf org list - -# Display details about the gus org -sf org display --target-org gus - -# Open gus org in browser -sf org open --target-org gus -``` - -### Concept 2: SOQL Queries - -**Query Execution**: -- Query using `sf data query` -- Support for CSV, JSON, and human-readable output -- Can query standard and custom objects -- Tooling API access for metadata queries - -```bash -# Query Work Items (Agile Accelerator) -sf data query \ - --query "SELECT Id, Name, Status__c, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 10" \ - --target-org gus \ - --result-format json - -# Query from file -sf data query \ - --file query.soql \ - --output-file results.csv \ - --result-format csv \ - --target-org gus - -# Query using Tooling API -sf data query \ - --query "SELECT Name, ApiVersion FROM ApexClass" \ - --use-tooling-api \ - --target-org gus -``` - -### Concept 3: Record Creation and Updates - -**Creating Records**: -- Use `sf data create record` for single records -- Specify object type with `--sobject` -- Provide field values with `--values` -- Use bulk operations for multiple records - -```bash -# Create a Work Item (User Story) -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='Implement new feature' Status__c='New' Priority__c='P2' Assignee__c='005xx000001X8Uz'" \ - --target-org gus - -# Create an Epic -sf data create record \ - --sobject ADM_Epic__c \ - --values "Name='Q1 2024 Features' Status__c='New' Description__c='Major features for Q1'" \ - --target-org gus - -# Update a record by ID -sf data update record \ - --sobject ADM_Work__c \ - --record-id a07xx00000ABCDe \ - --values "Status__c='In Progress' Assignee__c='005xx000001X8Uz'" \ - --target-org gus - -# Update a record by field match -sf data update record \ - --sobject ADM_Work__c \ - --where "Subject__c='Old Feature Name'" \ - --values "Subject__c='New Feature Name' Priority__c='P1'" \ - --target-org gus -``` - ---- - -## Patterns - -### Pattern 1: Creating User Stories with Dependencies - -**When to use**: -- Creating work items in Agile Accelerator -- Setting up sprints and assignments -- Linking related work items - -```bash -# ❌ Bad: Creating story without proper fields -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='Feature'" \ - --target-org gus - -# ✅ Good: Complete story with all required fields -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='Implement user authentication' \ - Status__c='New' \ - Priority__c='P1' \ - Story_Points__c=5 \ - Epic__c='a0Axx000001ABCD' \ - Sprint__c='a1Fxx000002EFGH' \ - Assignee__c='005xx000001X8Uz' \ - Type__c='User Story' \ - Description__c='

As a user, I want to log in securely

'" \ - --target-org gus -``` - -**Benefits**: -- Complete work item with proper tracking -- Links to Epic and Sprint for planning -- Includes story points for velocity tracking - -### Pattern 2: Bulk Status Updates - -**Use case**: Update multiple work items to a new status - -```bash -# Query work items to update -sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH' AND Status__c = 'Ready for Development'" \ - --result-format json \ - --target-org gus > work_items.json - -# Create CSV for bulk update -# work_items.csv: -# Id,Status__c -# a07xx00000ABCD1,In Progress -# a07xx00000ABCD2,In Progress -# a07xx00000ABCD3,In Progress - -# Bulk update using CSV -sf data update bulk \ - --sobject ADM_Work__c \ - --file work_items.csv \ - --wait 10 \ - --target-org gus -``` - -### Pattern 3: Creating Chatter Posts - -**Use case**: Post updates to Chatter feeds for work items - -```bash -# Create a Chatter post on a Work Item -sf data create record \ - --sobject FeedItem \ - --values "ParentId=a07xx00000ABCDE Body='Phase 1.2 complete: S3 Storage Service implemented with 32 tests, file size validation, and CI configuration.'" \ - --target-org gus - -# Output: -# Successfully created record: 0D5xx00000FGHIJ. -# Creating record for FeedItem... done - -# Create a Chatter comment on an existing post -sf data create record \ - --sobject FeedComment \ - --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='LGTM - approved for merge'" \ - --target-org gus - -# Query Chatter feed for a record -sf data query \ - --query "SELECT Id, Body, CreatedBy.Name, CreatedDate FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE' ORDER BY CreatedDate DESC LIMIT 20" \ - --target-org gus \ - --json - -# Post to Chatter using WI from git branch (with error handling) -BRANCH=$(git rev-parse --abbrev-ref HEAD) -WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) - -if [ -n "$WI_NUMBER" ]; then - QUERY_RESULT=$(sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ - --json) - - WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') - - if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then - sf data create record \ - --sobject FeedItem \ - --values "ParentId=${WORK_ITEM_ID} Body='Feature implementation completed and ready for review'" \ - --target-org gus - fi -fi -``` - -**Key Points**: -- `ParentId` is the Salesforce record Id (e.g., `a07xx00000ABCDE` for Work Items) -- `Body` contains the Chatter post text (plain text or basic formatting) -- You don't need `Type='TextPost'` - it's the default for FeedItem -- Returns the FeedItem Id (e.g., `0D5xx00000FGHIJ`) on success -- For comments, use `FeedComment` object with `FeedItemId` and `CommentBody` - -### Pattern 4: Managing Sprints and Builds - -**Use case**: Create and manage sprint/build records - -```bash -# Create a new Sprint -sf data create record \ - --sobject ADM_Sprint__c \ - --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29" \ - --target-org gus - -# Create a Build (Scheduled Build) -sf data create record \ - --sobject ADM_Build__c \ - --values "Name='Release 1.0' External_ID__c='R010'" \ - --target-org gus - -# Assign work items to sprint -sf data update record \ - --sobject ADM_Work__c \ - --where "Status__c='Ready for Development' AND Sprint__c = null" \ - --values "Sprint__c='a1Fxx000002EFGH'" \ - --target-org gus -``` - -### Pattern 5: REST API Direct Calls - -**Use case**: Make custom REST API calls for operations not covered by standard commands - -```bash -# Get Work Item details via REST API -sf api request rest \ - "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ - --target-org gus - -# Update multiple fields via PATCH -sf api request rest \ - "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ - --method PATCH \ - --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ - --target-org gus - -# Query using REST API -sf api request rest \ - "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ - --target-org gus -``` - -### Pattern 6: Finding Record IDs - -**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) - -```bash -# Find user ID by name -sf data query \ - --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ - --target-org gus - -# Find Epic by name -sf data query \ - --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ - --target-org gus - -# Find Sprint by name -sf data query \ - --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ - --target-org gus - -# Find Product Tag -sf data query \ - --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ - --target-org gus -``` - -### Pattern 7: Bulk Data Export - -**Use case**: Export large datasets for analysis or backup - -```bash -# Export all work items for a sprint -sf data export bulk \ - --sobject ADM_Work__c \ - --query "SELECT Id, Subject__c, Status__c, Priority__c, Assignee__r.Name, Story_Points__c FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH'" \ - --output-file sprint_42_items.csv \ - --wait 10 \ - --target-org gus - -# Export all open bugs -sf data export bulk \ - --sobject ADM_Work__c \ - --query "SELECT Id, Subject__c, Status__c, Priority__c, Found_in_Build__r.Name FROM ADM_Work__c WHERE Type__c = 'Bug' AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ - --output-file open_bugs.csv \ - --target-org gus -``` - -### Pattern 8: Complex Field Updates with HTML - -**Use case**: Update rich text fields with HTML content - -```bash -# Update work item description with HTML -sf data update record \ - --sobject ADM_Work__c \ - --record-id a07xx00000ABCDE \ - --values "Description__c='

Overview

  • Item 1
  • Item 2

Additional details here.

'" \ - --target-org gus - -# Create work item with formatted description -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='Database migration' \ - Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ - Status__c='New' \ - Type__c='User Story'" \ - --target-org gus -``` - -### Pattern 9: Inferring WI Number from Git Branch - -**Use case**: Automatically determine WI number from current git branch name - -**IMPORTANT**: The WI number (e.g., W-12345678) is stored in the `Name` field, NOT the `Id` field. You must query by `Name` to get the actual Salesforce record `Id`. - -```bash -# ❌ Bad: Asking user for WI number when it's in the branch -echo "What's the WI number?" - -# ✅ Good: Extract WI number from git branch -BRANCH=$(git rev-parse --abbrev-ref HEAD) -WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) - -# Validate WI number was found -if [ -z "$WI_NUMBER" ]; then - echo "Error: No WI number found in branch name: $BRANCH" - exit 1 -fi - -# Common branch patterns that contain WI numbers: -# - W-12345678 -# - wi-12345678 -# - 12345678-feature-name -# - feature/W-12345678 -# - bugfix/12345678-fix-issue - -# Query work item by Name field (NOT Id) to get the record details -# The Name field contains 'W-12345678', the Id field contains Salesforce record ID -QUERY_RESULT=$(sf data query \ - --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ - --json) - -# Check if query returned results -RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') -if [ "$RECORD_COUNT" -eq 0 ]; then - echo "Error: Work item W-${WI_NUMBER} not found in GUS" - exit 1 -fi - -echo "$QUERY_RESULT" - -# Get the Salesforce record Id from the WI Name -WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') - -# Validate we got a valid ID -if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then - echo "Error: Failed to extract Salesforce ID for W-${WI_NUMBER}" - exit 1 -fi - -# ❌ WRONG: Using WI number as if it were the Salesforce Id -sf data update record \ - --sobject ADM_Work__c \ - --record-id "W-${WI_NUMBER}" \ - --values "Status__c='In Progress'" # This will fail! - -# ✅ CORRECT: Use the actual Salesforce record Id from the query -sf data update record \ - --sobject ADM_Work__c \ - --record-id "$WORK_ITEM_ID" \ - --values "Status__c='In Progress'" \ - --target-org gus - -# Add Chatter post to the WI using the Salesforce record Id -sf data create record \ - --sobject FeedItem \ - --values "ParentId='${WORK_ITEM_ID}' Body='Starting work on this feature'" \ - --target-org gus -``` - -**Benefits**: -- No need to manually specify WI numbers when working in feature branches -- Reduces errors from copying/pasting wrong WI numbers -- Enables automation based on git workflow -- Makes scripts more context-aware - -**Key Points**: -- WI number (W-12345678) is in the `Name` field, NOT the `Id` field -- Always query `WHERE Name = 'W-...'` to find work items -- Extract the `Id` field from query results for record operations -- Use `--json` flag for easier parsing with `jq` - -### Pattern 10: Getting Current User Information - -**Use case**: Dynamically retrieve the logged-in user's email and other details for queries - -**IMPORTANT**: Never hardcode user emails (like `user@gus.com`). Always fetch the current user's email dynamically from the authenticated org. - -```bash -# ❌ Bad: Hardcoding user email -sf data query \ - --query "SELECT Name, Subject__c, Status__c FROM ADM_Work__c WHERE Assignee__r.Email = 'user@gus.com'" \ - --target-org gus - -# ✅ Good: Get current user email from authenticated org (by alias) -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') - -# Query work items for the logged-in user -sf data query \ - --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c - FROM ADM_Work__c - WHERE Assignee__r.Email = '${USER_EMAIL}' - AND Status__c != 'Closed'" \ - --target-org gus \ - --result-format json - -# Alternative: Get from most recently used org -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs | sort_by(.lastUsed) | reverse | .[0].username') - -# Alternative: Get user details from org display user -USER_INFO=$(sf org display user --target-org gus --json) -USER_EMAIL=$(echo "$USER_INFO" | jq -r '.result.email') -USER_ID=$(echo "$USER_INFO" | jq -r '.result.id') -USER_NAME=$(echo "$USER_INFO" | jq -r '.result.username') - -# Use the user ID directly in queries (more efficient than joining on email) -sf data query \ - --query "SELECT Name, Subject__c, Status__c, Priority__c - FROM ADM_Work__c - WHERE Assignee__c = '${USER_ID}' - AND Status__c = 'In Progress'" \ - --target-org gus - -# Get user's org details -ORG_INFO=$(sf org display --target-org gus --json) -ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') -INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') - -echo "Logged in as: $USER_EMAIL" -echo "User ID: $USER_ID" -echo "Org ID: $ORG_ID" -echo "Instance: $INSTANCE_URL" -``` - -**Benefits**: -- Works across different users and orgs without code changes -- No need to hardcode usernames or emails -- Scripts are portable and can be shared with team members -- Safer - prevents accidentally using wrong user credentials - -**Key Points**: -- Use `sf org list --json` to get username from authenticated orgs -- Use `sf org display user --json` for complete user details including ID -- Filter by org alias when multiple orgs are authenticated -- Use User ID (`Assignee__c`) in queries instead of email when possible (more efficient) -- Always validate that the user info was retrieved successfully before using - -**Error Handling Pattern**: -```bash -# Get user email with validation -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') - -if [ -z "$USER_EMAIL" ] || [ "$USER_EMAIL" = "null" ]; then - echo "Error: Could not retrieve user email. Is the org authenticated?" - echo "Run: sf org login web --alias gus" - exit 1 -fi - -echo "Querying work items for: $USER_EMAIL" -``` - ---- - -## Quick Reference - -### Common Objects - -``` -Object API Name | Description | Key Fields ------------------------|--------------------------|---------------------------------- -ADM_Work__c | Work Items/Stories/Bugs | Subject__c, Status__c, Priority__c, Assignee__c -ADM_Epic__c | Epics | Name, Description__c, Health__c, Priority__c -ADM_Sprint__c | Sprints | Name, Start_Date__c, End_Date__c -ADM_Build__c | Scheduled Builds | Name, External_ID__c -ADM_Product_Tag__c | Product Tags | Name -FeedItem | Chatter Posts | ParentId, Body, Type -FeedComment | Chatter Comments | FeedItemId, CommentBody -User | Users | Name, Email, Username -``` - -### Common Status and Health Fields - -``` -Object | Status/Health Field | Common Values -----------------------|---------------------|---------------------------------------------- -Work Items (ADM_Work__c) | Status__c | New, In Progress, Code Review, Fixed, Closed -Epics (ADM_Epic__c) | Health__c | On Track, At Risk, Off Track -Sprints (ADM_Sprint__c) | (none) | Use Start_Date__c and End_Date__c -Builds (ADM_Build__c) | (none) | Use Name and External_ID__c -``` - -**Important Notes**: -- Work Items use `Status__c` for workflow tracking -- Epics use `Health__c` instead of Status__c for health tracking -- Sprints and Builds do not have status fields - use dates and identifiers - -### Key Guidelines - -**Essential Practices:** -``` -✅ DO: Always specify --target-org gus to avoid ambiguity -✅ DO: Use --result-format json for programmatic processing -✅ DO: Query for IDs before creating related records -✅ DO: Use bulk operations for updating multiple records -✅ DO: Include required fields when creating records -✅ DO: Use HTML formatting for rich text fields -✅ DO: Always use sf CLI tool directly for all operations -✅ DO: Infer WI number from git branch name when not explicitly provided -✅ DO: Dynamically fetch user email/ID instead of hardcoding (Pattern 10) -✅ DO: Add error handling for queries that may return no results -✅ DO: Validate extracted values before using them in subsequent commands -``` - -**Common Mistakes to Avoid:** -``` -❌ DON'T: Hardcode record IDs (query for them instead) -❌ DON'T: Hardcode user emails (fetch dynamically with Pattern 10) -❌ DON'T: Create records without required fields -❌ DON'T: Use single updates for large datasets (use bulk) -❌ DON'T: Forget to specify API names (use __c suffix for custom fields) -❌ DON'T: Update records without verifying they exist first -❌ DON'T: Assume queries will always return results -❌ DON'T: Skip validation of jq output (check for null/empty) -``` - -**Error Handling Pattern:** -```bash -# Query with result validation -QUERY_RESULT=$(sf data query --query "..." --target-org gus --json) -RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') - -if [ "$RECORD_COUNT" -eq 0 ]; then - echo "Error: No records found" - exit 1 -fi - -RECORD_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') - -if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" = "null" ]; then - echo "Error: Failed to extract record ID" - exit 1 -fi -``` - ---- - -## Anti-Patterns - -### Critical Violations - -```bash -# ❌ NEVER: Update records without verifying they exist -sf data update record \ - --sobject ADM_Work__c \ - --record-id UNKNOWN_ID \ - --values "Status__c='Fixed'" - -# ✅ CORRECT: Query first, then update -RECORD_ID=$(sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Subject__c='Known Subject'" \ - --result-format json --target-org gus | jq -r '.result.records[0].Id') - -sf data update record \ - --sobject ADM_Work__c \ - --record-id "$RECORD_ID" \ - --values "Status__c='Fixed'" \ - --target-org gus -``` - -❌ **Hardcoding IDs**: IDs vary across orgs (sandbox vs production) -✅ **Correct approach**: Query by unique identifiers (names, external IDs) - -### Common Mistakes - -```bash -# ❌ Don't: Missing required fields -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='New Story'" \ - --target-org gus - -# ✅ Correct: Include all required fields -sf data create record \ - --sobject ADM_Work__c \ - --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ - --target-org gus -``` - -❌ **Incomplete records**: Missing required fields causes failures -✅ **Better**: Query existing records to understand required fields - -```bash -# ❌ Don't: Single updates in loop -for id in $(cat work_item_ids.txt); do - sf data update record --sobject ADM_Work__c --record-id "$id" --values "Status__c='Closed'" -done - -# ✅ Correct: Use bulk update -sf data update bulk \ - --sobject ADM_Work__c \ - --file updates.csv \ - --wait 10 \ - --target-org gus -``` - -❌ **Inefficient operations**: Single updates are slow and hit API limits -✅ **Better**: Bulk API for batch operations - ---- - -## Security Considerations - -**Review before creating this skill**: Check `.claude/audits/safety-checklist.md` - -**Does this skill involve** (check all that apply): -- [x] Authentication or authorization -- [ ] Cryptographic operations -- [x] Data deletion or modification -- [ ] Production deployments -- [ ] Database migrations -- [ ] File system operations -- [x] Network requests -- [ ] Executable scripts - -**If yes to any above, ensure**: -- [x] Sensitive operations have clear ⚠️ warnings -- [x] Examples use placeholder credentials (never real) -- [x] Destructive operations include rollback procedures -- [x] Production examples follow security best practices -- [x] Scripts validate all inputs -- [x] No hardcoded secrets or API keys -- [x] Dangerous commands clearly marked - -**Security Notes**: -- ⚠️ Always verify target org before destructive operations -- ⚠️ Use `--target-org` flag to prevent accidental production updates -- ⚠️ Query before update to verify record existence -- ⚠️ Be cautious with bulk delete operations -- ⚠️ Never commit authentication tokens or credentials - ---- - -## Related Skills - -- `salesforce/agile-accelerator-workflows.md` - GUS-specific workflows and automation -- `collaboration/github/github-issues-projects.md` - Alternative project management -- `data/data-transformation.md` - Processing exported Salesforce data -- `testing/integration-testing.md` - Testing Salesforce integrations -- `api/rest-api-design.md` - Understanding Salesforce REST API patterns -- `workflow/automation-scripting.md` - Automating Salesforce operations - ---- - -**Last Updated**: 2025-12-03 -**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-org-auth.md b/skills/salesforce/sf-org-auth.md new file mode 100644 index 0000000..bcb1e90 --- /dev/null +++ b/skills/salesforce/sf-org-auth.md @@ -0,0 +1,290 @@ +--- +name: salesforce-org-auth +description: Authenticate and manage Salesforce orgs using sf CLI +--- + +# Salesforce Org Authentication + +**Scope**: Org authentication, connection management, and user information retrieval +**Lines**: ~180 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Logging into Salesforce orgs +- Managing multiple org connections +- Switching between orgs +- Retrieving current user information +- Getting org details and configuration +- Troubleshooting authentication issues + +--- + +## Core Concepts + +### Concept 1: Authentication Methods + +**Available Methods**: +- **Web login flow**: Interactive browser-based OAuth +- **JWT bearer flow**: Non-interactive for CI/CD +- **Access token**: Direct token authentication +- **SFDX auth URL**: Import existing authentication + +```bash +# Web login (most common - interactive) +sf org login web --alias gus + +# Web login with custom instance +sf org login web --alias production --instance-url https://login.salesforce.com + +# JWT bearer flow (for CI/CD) +sf org login jwt --client-id YOUR_CONSUMER_KEY \ + --jwt-key-file server.key \ + --username user@example.com \ + --alias ci-org + +# Access token login +sf org login access-token --instance-url https://gus.my.salesforce.com \ + --alias gus +``` + +### Concept 2: Managing Multiple Orgs + +**Org Management Commands**: + +```bash +# List all authenticated orgs +sf org list + +# List with JSON output for scripting +sf org list --json + +# Display current org details +sf org display --target-org gus + +# Display with verbose output (includes auth URL) +sf org display --target-org gus --verbose + +# Set default org +sf config set target-org=gus + +# Open org in browser +sf org open --target-org gus + +# Logout from org +sf org logout --target-org gus + +# Logout from all orgs +sf org logout --all +``` + +--- + +## Patterns + +### Pattern 1: Getting Current User Information + +**Use case**: Dynamically retrieve the logged-in user's email and other details for queries + +**IMPORTANT**: Never hardcode user emails (like `user@gus.com`). Always fetch the current user's email dynamically from the authenticated org. + +```bash +# ❌ Bad: Hardcoding user email +USER_EMAIL="user@gus.com" + +# ✅ Good: Get current user email from authenticated org (by alias) +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +# Alternative: Get from most recently used org +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs | sort_by(.lastUsed) | reverse | .[0].username') + +# Alternative: Get user details from org display user +USER_INFO=$(sf org display user --target-org gus --json) +USER_EMAIL=$(echo "$USER_INFO" | jq -r '.result.email') +USER_ID=$(echo "$USER_INFO" | jq -r '.result.id') +USER_NAME=$(echo "$USER_INFO" | jq -r '.result.username') + +# Get user's org details +ORG_INFO=$(sf org display --target-org gus --json) +ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') +INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') + +echo "Logged in as: $USER_EMAIL" +echo "User ID: $USER_ID" +echo "Org ID: $ORG_ID" +echo "Instance: $INSTANCE_URL" +``` + +**Benefits**: +- Works across different users and orgs without code changes +- No need to hardcode usernames or emails +- Scripts are portable and can be shared with team members +- Safer - prevents accidentally using wrong user credentials + +**Key Points**: +- Use `sf org list --json` to get username from authenticated orgs +- Use `sf org display user --json` for complete user details including ID +- Filter by org alias when multiple orgs are authenticated +- Use User ID (`Assignee__c`) in queries instead of email when possible (more efficient) +- Always validate that the user info was retrieved successfully before using + +### Pattern 2: Error Handling for Authentication + +**Use case**: Validate authentication and provide helpful error messages + +```bash +# Get user email with validation +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +if [ -z "$USER_EMAIL" ] || [ "$USER_EMAIL" = "null" ]; then + echo "Error: Could not retrieve user email. Is the org authenticated?" + echo "Run: sf org login web --alias gus" + exit 1 +fi + +echo "Querying work items for: $USER_EMAIL" + +# Check if org is still connected +ORG_STATUS=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .connectedStatus') + +if [ "$ORG_STATUS" != "Connected" ]; then + echo "Error: Org 'gus' is not connected. Status: $ORG_STATUS" + echo "Please re-authenticate: sf org login web --alias gus" + exit 1 +fi +``` + +### Pattern 3: Multi-Org Workflows + +**Use case**: Work with multiple orgs (dev, staging, production) + +```bash +# Setup multiple orgs with meaningful aliases +sf org login web --alias gus-dev +sf org login web --alias gus-staging +sf org login web --alias gus-prod + +# Function to query across multiple orgs +query_all_orgs() { + local query="$1" + for org in gus-dev gus-staging gus-prod; do + echo "=== Results from $org ===" + sf data query --query "$query" --target-org "$org" || echo "Failed for $org" + done +} + +# Use the function +query_all_orgs "SELECT Id, Name FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 5" + +# Get org info for all authenticated orgs +sf org list --json | jq -r '.result.nonScratchOrgs[] | "\(.alias): \(.username) - \(.instanceUrl)"' +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Authentication +sf org login web --alias # Login via browser +sf org list # List all orgs +sf org display --target-org # Show org details +sf org open --target-org # Open org in browser +sf org logout --target-org # Logout from org + +# User Information +sf org display user --target-org # Show current user details +sf org display user --json # JSON output for scripting + +# Configuration +sf config set target-org= # Set default org +sf config get target-org # Get default org +``` + +### Org Information Available via JSON + +From `sf org list --json`: +- `username` - User's email/username +- `alias` - Org alias +- `orgId` - Organization ID +- `instanceUrl` - Salesforce instance URL +- `connectedStatus` - Connection status +- `lastUsed` - Last access timestamp + +From `sf org display user --json`: +- `id` - User's Salesforce ID (use for queries) +- `username` - Username +- `email` - User's email +- `profileName` - User's profile +- `alias` - Org alias + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Use meaningful aliases for orgs (e.g., gus, gus-prod, gus-dev) +✅ DO: Dynamically fetch user email/ID instead of hardcoding +✅ DO: Check org connection status before operations +✅ DO: Use --json flag for programmatic processing +✅ DO: Validate extracted values before using them +✅ DO: Store multiple org connections for different environments +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Hardcode user emails or org URLs +❌ DON'T: Assume an org is still authenticated +❌ DON'T: Skip validation of jq output (check for null/empty) +❌ DON'T: Use generic aliases like "org1" (use descriptive names) +❌ DON'T: Commit auth URLs or tokens to version control +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Hardcode user credentials +USER_EMAIL="user@gus.com" +USER_ID="005xx000001X8Uz" + +# ✅ CORRECT: Fetch dynamically +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +USER_ID=$(sf org display user --target-org gus --json | jq -r '.result.id') +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Never commit authentication tokens or auth URLs +- ⚠️ Use `--verbose` flag carefully (exposes auth URL) +- ⚠️ Validate org alias before operations +- ⚠️ Logout from shared/public machines +- ⚠️ Use JWT for CI/CD instead of storing passwords +- ⚠️ Rotate access tokens regularly + +--- + +## Related Skills + +- `sf-soql-queries.md` - Query data using authenticated orgs +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects +- `sf-bulk-operations.md` - Bulk data operations + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md new file mode 100644 index 0000000..8dfc01e --- /dev/null +++ b/skills/salesforce/sf-record-operations.md @@ -0,0 +1,406 @@ +--- +name: salesforce-record-operations +description: Create and update Salesforce records using sf CLI +--- + +# Salesforce Record Operations + +**Scope**: Creating, updating, and managing individual Salesforce records +**Lines**: ~200 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Creating new Salesforce records +- Updating existing records +- Working with individual records (not bulk operations) +- Setting field values on records +- Using REST API for custom operations + +--- + +## Core Concepts + +### Concept 1: Creating Records + +**Basic Syntax**: +```bash +sf data create record \ + --sobject \ + --values "Field1=Value1 Field2=Value2" \ + --target-org +``` + +**Examples**: + +```bash +# Create a simple record +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement new feature' Status__c='New' Type__c='User Story'" \ + --target-org gus + +# Create with multiple fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Fix critical bug' \ + Status__c='New' \ + Priority__c='P1' \ + Type__c='Bug' \ + Description__c='

Bug details here

'" \ + --target-org gus + +# Create an Epic +sf data create record \ + --sobject ADM_Epic__c \ + --values "Name='Q1 2024 Features' Status__c='New' Description__c='Major features for Q1'" \ + --target-org gus +``` + +### Concept 2: Updating Records + +**Update by Record ID**: +```bash +sf data update record \ + --sobject \ + --record-id \ + --values "Field1=NewValue" \ + --target-org +``` + +**Update by Field Match**: +```bash +sf data update record \ + --sobject \ + --where "Field=Value" \ + --values "Field1=NewValue" \ + --target-org +``` + +**Examples**: + +```bash +# Update by ID +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Status__c='In Progress'" \ + --target-org gus + +# Update multiple fields +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Status__c='Code Review' Assignee__c='005xx000001X8Uz' Story_Points__c=5" \ + --target-org gus + +# Update by field match +sf data update record \ + --sobject ADM_Work__c \ + --where "Name='W-12345678'" \ + --values "Status__c='In Progress'" \ + --target-org gus +``` + +### Concept 3: REST API Direct Calls + +**Use case**: Make custom REST API calls for operations not covered by standard commands + +```bash +# Get record details via REST API +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --target-org gus + +# Update multiple fields via PATCH +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --method PATCH \ + --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ + --target-org gus + +# Query using REST API +sf api request rest \ + "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ + --target-org gus + +# Create record via REST API +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c" \ + --method POST \ + --body '{"Subject__c": "New Story", "Status__c": "New", "Type__c": "User Story"}' \ + --target-org gus +``` + +--- + +## Patterns + +### Pattern 1: Create Record with Query for IDs + +**Use case**: Create records with relationships to other objects + +```bash +# ❌ Bad: Hardcoding IDs +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Feature' Assignee__c='005xx000001X8Uz' Sprint__c='a1Fxx000002EFGH'" \ + --target-org gus + +# ✅ Good: Query for IDs first +# Get user ID +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +# Get sprint ID +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +# Get epic ID +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +# Create work item with relationships +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement OAuth' \ + Status__c='New' \ + Priority__c='P1' \ + Type__c='User Story' \ + Story_Points__c=8 \ + Assignee__c='${USER_ID}' \ + Sprint__c='${SPRINT_ID}' \ + Epic__c='${EPIC_ID}'" \ + --target-org gus +``` + +### Pattern 2: Update with Validation + +**Use case**: Update records only if they exist + +```bash +# Query to verify record exists +QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus) + +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item W-12345678 not found" + exit 1 +fi + +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +# Update the record +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org gus + +echo "Updated work item W-12345678 to In Progress" +``` + +### Pattern 3: Complex Field Updates with HTML + +**Use case**: Update rich text fields with HTML content + +```bash +# Update work item description with HTML +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Description__c='

Overview

  • Item 1
  • Item 2

Additional details here.

'" \ + --target-org gus + +# Create work item with formatted description +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Database migration' \ + Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ + Status__c='New' \ + Type__c='User Story'" \ + --target-org gus +``` + +### Pattern 4: Atomic Updates with Error Handling + +**Use case**: Update multiple fields safely with rollback capability + +```bash +# Capture current state before update +CURRENT_STATE=$(sf data query \ + --query "SELECT Id, Status__c, Assignee__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus) + +WORK_ITEM_ID=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Id') +OLD_STATUS=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Status__c') +OLD_ASSIGNEE=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Assignee__c') + +# Attempt update +if sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review' Assignee__c='${NEW_ASSIGNEE_ID}'" \ + --target-org gus; then + echo "Update successful" +else + echo "Update failed! Rolling back..." + # Rollback to previous state + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='${OLD_STATUS}' Assignee__c='${OLD_ASSIGNEE}'" \ + --target-org gus + exit 1 +fi +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Create record +sf data create record --sobject --values "Field=Value" --target-org + +# Update by ID +sf data update record --sobject --record-id --values "Field=Value" --target-org + +# Update by field match +sf data update record --sobject --where "Field=Value" --values "Field=NewValue" --target-org + +# REST API call +sf api request rest "" --method --target-org +``` + +### Required Fields by Object + +**ADM_Work__c (Work Items)**: +``` +Subject__c (required) +Status__c (required) +Type__c (required) +Priority__c (recommended) +``` + +**ADM_Epic__c (Epics)**: +``` +Name (required) +Status__c (required) +``` + +**ADM_Sprint__c (Sprints)**: +``` +Name (required) +Start_Date__c (recommended) +End_Date__c (recommended) +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Query for IDs before creating related records +✅ DO: Include all required fields when creating records +✅ DO: Validate record existence before updates +✅ DO: Use HTML formatting for rich text fields +✅ DO: Handle errors and provide meaningful messages +✅ DO: Use --result-format json for scripting +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Hardcode record IDs (query for them) +❌ DON'T: Create records without required fields +❌ DON'T: Update records without verifying they exist +❌ DON'T: Forget __c suffix on custom fields +❌ DON'T: Skip error handling in scripts +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Update without checking existence +sf data update record \ + --sobject ADM_Work__c \ + --record-id UNKNOWN_ID \ + --values "Status__c='Fixed'" + +# ✅ CORRECT: Query first, then update +RECORD_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name='W-12345678'" \ + --result-format json --target-org gus | jq -r '.result.records[0].Id') + +if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" = "null" ]; then + echo "Error: Record not found" + exit 1 +fi + +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$RECORD_ID" \ + --values "Status__c='Fixed'" \ + --target-org gus +``` + +```bash +# ❌ Don't: Missing required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story'" \ + --target-org gus + +# ✅ Correct: Include all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ + --target-org gus +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Always verify target org before updates +- ⚠️ Query before update to verify record existence +- ⚠️ Validate all input values +- ⚠️ Be cautious with delete operations (use bulk with care) +- ⚠️ Don't expose sensitive data in field values + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for operations +- `sf-soql-queries.md` - Query for record IDs +- `sf-work-items.md` - Work with GUS-specific objects +- `sf-bulk-operations.md` - Bulk updates for multiple records + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md new file mode 100644 index 0000000..d329785 --- /dev/null +++ b/skills/salesforce/sf-soql-queries.md @@ -0,0 +1,394 @@ +--- +name: salesforce-soql-queries +description: Query Salesforce data using SOQL and sf CLI +--- + +# Salesforce SOQL Queries + +**Scope**: SOQL query syntax, data retrieval, and result formatting +**Lines**: ~200 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Querying Salesforce data using SOQL +- Retrieving work items, users, or other records +- Finding record IDs for operations +- Exporting data for analysis +- Building reports or dashboards +- Joining related objects + +--- + +## Core Concepts + +### Concept 1: Basic SOQL Syntax + +**Query Structure**: +```sql +SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n +``` + +**Field Selection**: +- Standard fields: `Id, Name, CreatedDate` +- Custom fields: Use `__c` suffix (e.g., `Status__c, Subject__c`) +- Related fields: Use `__r` for relationships (e.g., `Assignee__r.Email`) + +```bash +# Basic query +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 10" \ + --target-org gus + +# Query with conditions +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Status__c = 'New'" \ + --target-org gus + +# Query with related fields +sf data query \ + --query "SELECT Id, Subject__c, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Status__c = 'In Progress'" \ + --target-org gus + +# Query with ordering and limit +sf data query \ + --query "SELECT Id, Name, CreatedDate FROM ADM_Work__c ORDER BY CreatedDate DESC LIMIT 20" \ + --target-org gus +``` + +### Concept 2: Output Formats + +**Available Formats**: +- `human` - Table format (default) +- `json` - JSON output for scripting +- `csv` - CSV format for spreadsheets + +```bash +# Human-readable table (default) +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 5" \ + --target-org gus + +# JSON output for scripting +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 5" \ + --target-org gus \ + --result-format json + +# CSV output for Excel +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 100" \ + --target-org gus \ + --result-format csv > work_items.csv + +# Query from file +echo "SELECT Id, Name FROM ADM_Work__c LIMIT 10" > query.soql +sf data query \ + --file query.soql \ + --target-org gus +``` + +--- + +## Patterns + +### Pattern 1: Finding Record IDs + +**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) + +```bash +# Find user ID by name +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ + --target-org gus + +# Find user ID by email +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Email = 'user@example.com'" \ + --target-org gus + +# Find Epic by name +sf data query \ + --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --target-org gus + +# Find Sprint by name +sf data query \ + --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --target-org gus + +# Find Product Tag +sf data query \ + --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ + --target-org gus + +# Get ID with jq for scripting +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +echo "User ID: $USER_ID" +``` + +### Pattern 2: Querying Work Items + +**Use case**: Find work items by various criteria + +```bash +# Get current user's work items dynamically +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c != 'Closed' + ORDER BY Priority__c, CreatedDate DESC" \ + --target-org gus + +# Query by work item name (WI number) +sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --target-org gus + +# Query by sprint +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Assignee__r.Name, Story_Points__c + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + ORDER BY Status__c, Priority__c" \ + --target-org gus + +# Query by epic +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Sprint__r.Name + FROM ADM_Work__c + WHERE Epic__r.Name LIKE '%Q1 Features%' + AND Status__c NOT IN ('Fixed', 'Closed')" \ + --target-org gus + +# Query by type and priority +sf data query \ + --query "SELECT Name, Subject__c, Assignee__r.Name, Sprint__r.Name + FROM ADM_Work__c + WHERE Type__c = 'Bug' + AND Priority__c = 'P1' + AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ + --target-org gus +``` + +### Pattern 3: Complex Queries with Aggregation + +**Use case**: Get counts, sums, and grouped data + +```bash +# Count work items by status +sf data query \ + --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ + --target-org gus + +# Sum story points by sprint +sf data query \ + --query "SELECT Sprint__r.Name, SUM(Story_Points__c) total_points + FROM ADM_Work__c + WHERE Sprint__r.Name != null + GROUP BY Sprint__r.Name" \ + --target-org gus + +# Count by assignee +sf data query \ + --query "SELECT Assignee__r.Name, COUNT(Id) work_count + FROM ADM_Work__c + WHERE Status__c IN ('New', 'In Progress') + GROUP BY Assignee__r.Name + ORDER BY COUNT(Id) DESC" \ + --target-org gus +``` + +### Pattern 4: Querying with Date Filters + +**Use case**: Find records by date ranges + +```bash +# Work items created this week +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate FROM ADM_Work__c WHERE CreatedDate = THIS_WEEK" \ + --target-org gus + +# Work items updated in last 7 days +sf data query \ + --query "SELECT Name, Subject__c, LastModifiedDate FROM ADM_Work__c WHERE LastModifiedDate = LAST_N_DAYS:7" \ + --target-org gus + +# Work items created in date range +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate + FROM ADM_Work__c + WHERE CreatedDate >= 2024-01-01T00:00:00Z + AND CreatedDate <= 2024-01-31T23:59:59Z" \ + --target-org gus + +# Sprints active in date range +sf data query \ + --query "SELECT Name, Start_Date__c, End_Date__c + FROM ADM_Sprint__c + WHERE Start_Date__c <= 2024-12-03 + AND End_Date__c >= 2024-12-03" \ + --target-org gus +``` + +### Pattern 5: Using Tooling API + +**Use case**: Query metadata objects + +```bash +# Query Apex classes +sf data query \ + --query "SELECT Name, ApiVersion, LengthWithoutComments FROM ApexClass" \ + --use-tooling-api \ + --target-org gus + +# Query Apex triggers +sf data query \ + --query "SELECT Name, TableEnumOrId, Status FROM ApexTrigger" \ + --use-tooling-api \ + --target-org gus + +# Query custom fields +sf data query \ + --query "SELECT DeveloperName, DataType, TableEnumOrId FROM CustomField WHERE TableEnumOrId = 'ADM_Work__c'" \ + --use-tooling-api \ + --target-org gus +``` + +--- + +## Quick Reference + +### Common SOQL Operators + +``` += Equal to +!= Not equal to +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +LIKE Pattern match (use % for wildcard) +IN Match any value in list +NOT IN Don't match any value in list +``` + +### Date Literals + +``` +TODAY Current day +THIS_WEEK Current week +THIS_MONTH Current month +LAST_N_DAYS:n Last n days +NEXT_N_DAYS:n Next n days +LAST_WEEK Previous week +THIS_QUARTER Current quarter +``` + +### Common Fields + +**Work Items (ADM_Work__c)**: +``` +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c, Sprint__c, Epic__c +CreatedDate, LastModifiedDate, Description__c +``` + +**Related Field Notation**: +``` +Assignee__r.Name # User name +Assignee__r.Email # User email +Sprint__r.Name # Sprint name +Epic__r.Name # Epic name +Found_in_Build__r.Name # Build name +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Use --result-format json for scripting +✅ DO: Use LIMIT to avoid timeouts on large datasets +✅ DO: Query for IDs before creating related records +✅ DO: Use relationship queries (__r) instead of multiple queries +✅ DO: Validate query results before using extracted values +✅ DO: Use WHERE clauses to filter data server-side +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Query without LIMIT (can timeout) +❌ DON'T: Use SELECT * (not supported in SOQL) +❌ DON'T: Assume queries will always return results +❌ DON'T: Forget __c suffix on custom fields +❌ DON'T: Skip validation of jq output (check for null) +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Query without checking results +WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id') +# If no results, this returns "null" and breaks downstream operations + +# ✅ CORRECT: Validate query results +QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus) + +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item not found" + exit 1 +fi + +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract ID" + exit 1 +fi +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Be careful querying sensitive fields (passwords, tokens, SSN) +- ⚠️ Use appropriate WHERE clauses to avoid exposing all data +- ⚠️ Don't log or export sensitive data to insecure locations +- ⚠️ Validate user input in WHERE clauses to prevent injection + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects +- `sf-bulk-operations.md` - Export large datasets + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md new file mode 100644 index 0000000..0dc811f --- /dev/null +++ b/skills/salesforce/sf-work-items.md @@ -0,0 +1,385 @@ +--- +name: salesforce-work-items +description: Manage Agile Accelerator (GUS) work items, sprints, and epics +--- + +# Salesforce Agile Accelerator Work Items + +**Scope**: Creating and managing work items, sprints, epics, and builds in GUS +**Lines**: ~220 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Creating user stories, bugs, or tasks in GUS +- Managing sprints and sprint planning +- Working with epics and product tags +- Managing scheduled builds +- Organizing work item hierarchies +- Sprint planning and backlog management + +--- + +## Core Concepts + +### Concept 1: Work Item Types + +**ADM_Work__c Object** - Main work tracking object + +**Common Types**: +- `User Story` - Feature development +- `Bug` - Defects and issues +- `Task` - General work items +- `Investigation` - Research tasks + +**Key Fields**: +``` +Name - WI number (e.g., W-12345678) +Subject__c - Title/summary (required) +Status__c - Workflow state (required) +Type__c - Work item type (required) +Priority__c - P0, P1, P2, P3, P4 +Story_Points__c - Estimation +Assignee__c - User ID +Sprint__c - Sprint ID +Epic__c - Epic ID +Description__c - HTML rich text +``` + +### Concept 2: Sprints and Epics + +**ADM_Sprint__c** - Sprint/iteration tracking +**ADM_Epic__c** - Epic grouping +**ADM_Build__c** - Scheduled builds/releases +**ADM_Product_Tag__c** - Product categorization + +--- + +## Patterns + +### Pattern 1: Creating User Stories with Dependencies + +**Use case**: Create complete user story with proper relationships + +```bash +# Step 1: Query for required IDs +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +# Step 2: Create user story with all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement user authentication' \ + Status__c='New' \ + Priority__c='P1' \ + Story_Points__c=5 \ + Epic__c='${EPIC_ID}' \ + Sprint__c='${SPRINT_ID}' \ + Assignee__c='${USER_ID}' \ + Type__c='User Story' \ + Description__c='

As a user, I want to log in securely

'" \ + --target-org gus +``` + +**Benefits**: +- Complete work item with proper tracking +- Links to Epic and Sprint for planning +- Includes story points for velocity tracking + +### Pattern 2: Sprint Planning + +**Use case**: Create sprint and assign work items + +```bash +# Create a new Sprint +SPRINT_RESULT=$(sf data create record \ + --sobject ADM_Sprint__c \ + --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29" \ + --target-org gus \ + --json) + +SPRINT_ID=$(echo "$SPRINT_RESULT" | jq -r '.result.id') + +echo "Created Sprint 42 with ID: $SPRINT_ID" + +# Query backlog items +sf data query \ + --query "SELECT Id, Name, Subject__c, Story_Points__c + FROM ADM_Work__c + WHERE Status__c = 'Ready for Development' + AND Sprint__c = null + ORDER BY Priority__c, Story_Points__c" \ + --target-org gus \ + --result-format json > backlog.json + +# Review items and manually create CSV for assignment +# backlog_assignment.csv: +# Id,Sprint__c +# a07xx00000ABCD1, +# a07xx00000ABCD2, + +# Bulk assign items to sprint (see sf-bulk-operations.md) +``` + +### Pattern 3: Managing Epics + +**Use case**: Create and organize epics + +```bash +# Create an Epic +sf data create record \ + --sobject ADM_Epic__c \ + --values "Name='Q1 2024 Authentication Features' \ + Status__c='New' \ + Priority__c='P1' \ + Health__c='On Track' \ + Description__c='

Complete authentication overhaul

'" \ + --target-org gus + +# Query work items in epic +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Story_Points__c, Assignee__r.Name + FROM ADM_Work__c + WHERE Epic__r.Name LIKE '%Authentication%' + ORDER BY Status__c, Priority__c" \ + --target-org gus + +# Update epic health +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Epic__c \ + --record-id "$EPIC_ID" \ + --values "Health__c='At Risk' Description__c='

Blocked on security review

'" \ + --target-org gus +``` + +### Pattern 4: Managing Builds + +**Use case**: Create and track scheduled builds + +```bash +# Create a Build (Scheduled Build) +sf data create record \ + --sobject ADM_Build__c \ + --values "Name='Release 1.0' External_ID__c='R010' Target_Date__c=2024-02-01" \ + --target-org gus + +# Link work items to build +BUILD_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Build__c WHERE Name = 'Release 1.0'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Work__c \ + --where "Status__c='Fixed' AND Scheduled_Build__c = null" \ + --values "Scheduled_Build__c='${BUILD_ID}'" \ + --target-org gus + +# Query work items in build +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c + FROM ADM_Work__c + WHERE Scheduled_Build__r.Name = 'Release 1.0' + ORDER BY Priority__c" \ + --target-org gus +``` + +### Pattern 5: Work Item Status Workflows + +**Use case**: Move work items through standard workflow states + +```bash +# Common status transitions for User Stories +# New → In Progress → Code Review → QA Review → Fixed → Closed + +# Start work +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org gus | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org gus + +# Move to code review +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org gus + +# Mark as fixed +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Fixed' Resolved_On__c=$(date +%Y-%m-%d)" \ + --target-org gus +``` + +### Pattern 6: Sprint Reporting + +**Use case**: Get sprint metrics and burndown data + +```bash +# Get sprint summary +sf data query \ + --query "SELECT + Status__c, + COUNT(Id) total_items, + SUM(Story_Points__c) total_points + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + GROUP BY Status__c" \ + --target-org gus + +# Get team velocity +sf data query \ + --query "SELECT + Sprint__r.Name, + SUM(Story_Points__c) completed_points + FROM ADM_Work__c + WHERE Sprint__r.Start_Date__c >= 2024-01-01 + AND Status__c IN ('Fixed', 'Closed') + GROUP BY Sprint__r.Name + ORDER BY Sprint__r.Name" \ + --target-org gus + +# Get individual workload +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Story_Points__c, Sprint__r.Name + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c NOT IN ('Fixed', 'Closed') + ORDER BY Priority__c, Sprint__r.Name" \ + --target-org gus +``` + +--- + +## Quick Reference + +### Common Objects and Fields + +**Work Items (ADM_Work__c)**: +``` +Status Values: New, In Progress, Code Review, QA Review, Fixed, Closed +Priority: P0, P1, P2, P3, P4 +Type: User Story, Bug, Task, Investigation +``` + +**Epics (ADM_Epic__c)**: +``` +Health: On Track, At Risk, Off Track +Status: New, In Progress, Complete +``` + +**Sprints (ADM_Sprint__c)**: +``` +Key Fields: Name, Start_Date__c, End_Date__c +No status field - use dates +``` + +**Builds (ADM_Build__c)**: +``` +Key Fields: Name, External_ID__c, Target_Date__c +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Include all required fields when creating work items +✅ DO: Link work items to sprints and epics +✅ DO: Use story points for estimation +✅ DO: Query for IDs before creating relationships +✅ DO: Use descriptive subjects for work items +✅ DO: Update status as work progresses +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Create work items without type or status +❌ DON'T: Hardcode sprint or epic IDs +❌ DON'T: Forget to assign work items +❌ DON'T: Skip story points on user stories +❌ DON'T: Leave work items in stale states +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Create incomplete work items +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Feature'" \ + --target-org gus + +# ✅ CORRECT: Include all required and recommended fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement feature X' \ + Status__c='New' \ + Type__c='User Story' \ + Priority__c='P2' \ + Story_Points__c=3" \ + --target-org gus +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Verify user has permission to create/update work items +- ⚠️ Don't expose sensitive information in subject or description +- ⚠️ Validate sprint and epic IDs before assignment +- ⚠️ Be careful when bulk updating work item statuses + +--- + +## Related Skills + +- `sf-org-auth.md` - Get current user for assignments +- `sf-soql-queries.md` - Query work items and related objects +- `sf-record-operations.md` - Create and update individual records +- `sf-bulk-operations.md` - Bulk status updates +- `sf-chatter.md` - Add updates to work items +- `sf-automation.md` - Automate with git integration + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) From 0feb2c7eed6fa82eed32dbed650f27272d20b823 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:39:51 -0300 Subject: [PATCH 08/21] Improved epic fields --- skills/salesforce/INDEX.md | 3 ++- skills/salesforce/sf-record-operations.md | 7 +++++-- skills/salesforce/sf-soql-queries.md | 10 ++++++++++ skills/salesforce/sf-work-items.md | 6 +++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index 997749a..4af25ec 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -268,7 +268,8 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator **Epics (ADM_Epic__c)**: - Feature groupings -- Key fields: Name, Health__c, Status__c +- Key fields: Name, Health__c, Priority__c, Description__c +- Note: Uses Health__c (not Status__c) **Sprints (ADM_Sprint__c)**: - Iteration tracking diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md index 8dfc01e..80874d3 100644 --- a/skills/salesforce/sf-record-operations.md +++ b/skills/salesforce/sf-record-operations.md @@ -57,7 +57,7 @@ sf data create record \ # Create an Epic sf data create record \ --sobject ADM_Epic__c \ - --values "Name='Q1 2024 Features' Status__c='New' Description__c='Major features for Q1'" \ + --values "Name='Q1 2024 Features' Health__c='On Track' Description__c='Major features for Q1'" \ --target-org gus ``` @@ -303,7 +303,10 @@ Priority__c (recommended) **ADM_Epic__c (Epics)**: ``` Name (required) -Status__c (required) +Health__c (recommended: On Track, At Risk, Off Track, Completed, Canceled) +Priority__c (optional: P0, P1, P2, P3) +Description__c (optional, HTML rich text) +Note: Epics do NOT have Status__c - use Health__c ``` **ADM_Sprint__c (Sprints)**: diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index d329785..c51650e 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -180,6 +180,16 @@ sf data query \ AND Priority__c = 'P1' AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ --target-org gus + +# Query user's epics +USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') + +sf data query \ + --query "SELECT Id, Name, Description__c, Health__c, Priority__c, Owner.Name, LastModifiedDate + FROM ADM_Epic__c + WHERE Owner.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC" \ + --target-org gus ``` ### Pattern 3: Complex Queries with Aggregation diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md index 0dc811f..952fdf9 100644 --- a/skills/salesforce/sf-work-items.md +++ b/skills/salesforce/sf-work-items.md @@ -147,7 +147,6 @@ sf data query \ sf data create record \ --sobject ADM_Epic__c \ --values "Name='Q1 2024 Authentication Features' \ - Status__c='New' \ Priority__c='P1' \ Health__c='On Track' \ Description__c='

Complete authentication overhaul

'" \ @@ -296,8 +295,9 @@ Type: User Story, Bug, Task, Investigation **Epics (ADM_Epic__c)**: ``` -Health: On Track, At Risk, Off Track -Status: New, In Progress, Complete +Health: On Track, At Risk, Off Track, Completed, Canceled +Priority: P0, P1, P2, P3 (optional) +Note: Epics do NOT have a Status__c field - use Health__c instead ``` **Sprints (ADM_Sprint__c)**: From 06174414221b6dcc05f321a170ad33b8fe72d3a6 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:51:28 -0300 Subject: [PATCH 09/21] Removed gus references, instructed to extract default or first avilable org --- skills/discover-salesforce/SKILL.md | 6 +- skills/salesforce/INDEX.md | 7 +- skills/salesforce/sf-automation.md | 84 ++++++++++--- skills/salesforce/sf-bulk-operations.md | 59 +++++++-- skills/salesforce/sf-chatter.md | 64 +++++++--- skills/salesforce/sf-org-auth.md | 139 +++++++++++++++------- skills/salesforce/sf-record-operations.md | 77 ++++++++---- skills/salesforce/sf-soql-queries.md | 101 ++++++++++------ skills/salesforce/sf-work-items.md | 68 +++++++---- 9 files changed, 427 insertions(+), 178 deletions(-) diff --git a/skills/discover-salesforce/SKILL.md b/skills/discover-salesforce/SKILL.md index 904d3f9..885692b 100644 --- a/skills/discover-salesforce/SKILL.md +++ b/skills/discover-salesforce/SKILL.md @@ -35,7 +35,7 @@ The Salesforce category contains **7 focused skills**: 6. **sf-bulk-operations** - Bulk updates, exports, and large-scale operations 7. **sf-automation** - Git integration, WI inference, and automated workflows -**Note**: All examples use `gus` as the default org alias. This refers to your Salesforce org where Agile Accelerator (GUS) is configured. If your org has a different alias, simply replace `--target-org gus` with your org alias throughout. +**Note**: All examples dynamically detect your default org using `sf config get target-org`. You should set your default org with `sf config set target-org=`. Examples in the skills show how to fetch the default org dynamically to avoid hardcoding org aliases. ### Load Full Category Details @@ -261,8 +261,8 @@ cat ~/.claude/skills/salesforce/sf-bulk-operations.md **Always:** - **Use `sf` CLI tool directly** for all Salesforce operations - Infer WI number from git branch name when not explicitly provided -- Dynamically fetch user email/ID instead of hardcoding -- Specify `--target-org gus` (or your org alias) to avoid ambiguity +- Dynamically fetch user email/ID and default org instead of hardcoding +- Use `--target-org "$DEFAULT_ORG"` after fetching it dynamically to avoid ambiguity - Query for IDs before creating related records - Use bulk operations for 10+ record updates - Verify target org before destructive operations diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index 4af25ec..b6faf0e 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -7,7 +7,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator **Total Skills**: 7 **Focus**: Salesforce CLI, Agile Accelerator (GUS), SOQL, Record Management, Chatter **Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration, automation -**Default Org**: Examples use `gus` as the org alias - replace with your org alias if different +**Default Org**: Examples dynamically detect your default org using `sf config get target-org` - set it with `sf config set target-org=` ## Skills in This Category @@ -292,8 +292,9 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ## Best Practices **Authentication**: -- Use meaningful aliases for orgs (e.g., gus, gus-prod, gus-dev) -- Dynamically fetch user email/ID instead of hardcoding +- Use meaningful aliases for orgs (e.g., prod, staging, dev) +- Set a default org with `sf config set target-org=` +- Dynamically fetch default org and user email/ID instead of hardcoding - Validate org connection before operations **Querying**: diff --git a/skills/salesforce/sf-automation.md b/skills/salesforce/sf-automation.md index 3e7aa97..7a183c3 100644 --- a/skills/salesforce/sf-automation.md +++ b/skills/salesforce/sf-automation.md @@ -68,10 +68,19 @@ if [ -z "$WI_NUMBER" ]; then exit 1 fi +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Query work item by Name field (NOT Id) to get the record details QUERY_RESULT=$(sf data query \ --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json) # Check if query returned results @@ -113,6 +122,15 @@ echo "Found work item: W-${WI_NUMBER} (ID: ${WORK_ITEM_ID})" #!/bin/bash # post-commit hook +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Get WI from branch BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) @@ -121,7 +139,7 @@ if [ -n "$WI_NUMBER" ]; then # Get work item ID WORK_ITEM_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Id') if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then @@ -134,7 +152,7 @@ if [ -n "$WI_NUMBER" ]; then --sobject FeedItem \ --values "ParentId=${WORK_ITEM_ID} \ Body='Commit ${COMMIT_HASH}: ${COMMIT_MSG}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Posted commit to W-${WI_NUMBER}" fi @@ -149,6 +167,15 @@ fi #!/bin/bash # ci-build.sh +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Extract WI from branch or PR WI_NUMBER=$(echo "$CI_BRANCH_NAME" | grep -oE '[0-9]{8}' | head -1) @@ -160,7 +187,7 @@ fi # Get work item WORK_ITEM_ID=$(sf data query \ --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Id') if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then @@ -179,12 +206,12 @@ Coverage: ${COVERAGE}% Ready for deployment Build: ${BUILD_URL}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Update status if still in progress CURRENT_STATUS=$(sf data query \ --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Status__c') if [ "$CURRENT_STATUS" = "In Progress" ]; then @@ -192,7 +219,7 @@ Build: ${BUILD_URL}'" \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Code Review'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Moved W-${WI_NUMBER} to Code Review" fi @@ -204,7 +231,7 @@ else Failed tests: ${TEST_FAILED}/${TEST_TOTAL} See: ${BUILD_URL} Please investigate.'" \ - --target-org gus + --target-org "$DEFAULT_ORG" fi ``` @@ -216,6 +243,15 @@ fi #!/bin/bash # pre-push hook +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) @@ -223,7 +259,7 @@ if [ -n "$WI_NUMBER" ]; then # Check if WI exists and is in valid state WI_STATUS=$(sf data query \ --query "SELECT Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Status__c') if [ "$WI_STATUS" = "null" ] || [ -z "$WI_STATUS" ]; then @@ -250,6 +286,15 @@ fi #!/bin/bash # auto-status-update.sh +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) @@ -261,12 +306,12 @@ fi # Get work item WORK_ITEM_ID=$(sf data query \ --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Id') CURRENT_STATUS=$(sf data query \ --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json | jq -r '.result.records[0].Status__c') # Transition logic based on event @@ -277,7 +322,7 @@ case "$1" in --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Started work on W-${WI_NUMBER}" fi ;; @@ -288,7 +333,7 @@ case "$1" in --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Code Review'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Moved W-${WI_NUMBER} to Code Review" fi ;; @@ -299,7 +344,7 @@ case "$1" in --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Fixed'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Marked W-${WI_NUMBER} as Fixed" fi ;; @@ -319,10 +364,19 @@ WI_NUMBER=$(git rev-parse --abbrev-ref HEAD | grep -oE '[0-9]{8}' | head -1) # Validate extracted [ -z "$WI_NUMBER" ] && echo "No WI found" && exit 1 +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Query by Name field WORK_ITEM_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus --json | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" --json | jq -r '.result.records[0].Id') ``` ### Common Git Hooks diff --git a/skills/salesforce/sf-bulk-operations.md b/skills/salesforce/sf-bulk-operations.md index aa66fee..9493f34 100644 --- a/skills/salesforce/sf-bulk-operations.md +++ b/skills/salesforce/sf-bulk-operations.md @@ -56,13 +56,22 @@ Bulk operations use CSV files: **Use case**: Update multiple work items to a new status ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Step 1: Query work items to update sf data query \ --query "SELECT Id, Name FROM ADM_Work__c WHERE Sprint__c = 'a1Fxx000002EFGH' AND Status__c = 'Ready for Development'" \ --result-format csv \ - --target-org gus > work_items.csv + --target-org "$DEFAULT_ORG" > work_items.csv # Step 2: Create CSV for bulk update # Manually edit or use script to create: @@ -77,7 +86,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file work_items_update.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Bulk update complete" ``` @@ -97,7 +106,7 @@ sf data export bulk \ WHERE Sprint__r.Name = 'Sprint 42'" \ --output-file sprint_42_items.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Exported to sprint_42_items.csv" @@ -111,7 +120,7 @@ sf data export bulk \ AND Status__c NOT IN ('Fixed', 'Not a Bug', 'Closed')" \ --output-file open_bugs.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" # Export user list sf data export bulk \ @@ -121,7 +130,7 @@ sf data export bulk \ WHERE IsActive = true" \ --output-file active_users.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 3: Script-Driven Bulk Updates @@ -129,13 +138,22 @@ sf data export bulk \ **Use case**: Generate CSV updates programmatically ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Query and transform data sf data query \ --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Sprint__r.Name = 'Sprint 42' AND Status__c = 'Ready for Development'" \ --result-format json \ - --target-org gus > work_items.json + --target-org "$DEFAULT_ORG" > work_items.json # Generate CSV with jq echo "Id,Status__c,Assignee__c" > bulk_update.csv @@ -146,7 +164,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file bulk_update.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Updated $(wc -l < bulk_update.csv) records" ``` @@ -156,11 +174,20 @@ echo "Updated $(wc -l < bulk_update.csv) records" **Use case**: Assign multiple backlog items to a sprint ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Get sprint ID SPRINT_ID=$(sf data query \ --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 43'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') # Query backlog items sf data query \ @@ -171,7 +198,7 @@ sf data query \ ORDER BY Priority__c LIMIT 20" \ --result-format csv \ - --target-org gus > backlog.csv + --target-org "$DEFAULT_ORG" > backlog.csv # Create assignment CSV echo "Id,Sprint__c" > sprint_assignment.csv @@ -184,7 +211,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file sprint_assignment.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Assigned $(tail -n +2 sprint_assignment.csv | wc -l) items to Sprint 43" ``` @@ -206,7 +233,7 @@ sf data create bulk \ --sobject ADM_Work__c \ --file new_work_items.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Created records from CSV" ``` @@ -283,6 +310,14 @@ for id in $(cat work_item_ids.txt); do done # ✅ CORRECT: Use bulk update +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + echo "Id,Status__c" > bulk_update.csv cat work_item_ids.txt | while read id; do echo "${id},Closed" >> bulk_update.csv @@ -292,7 +327,7 @@ sf data update bulk \ --sobject ADM_Work__c \ --file bulk_update.csv \ --wait 10 \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md index de53267..68d203a 100644 --- a/skills/salesforce/sf-chatter.md +++ b/skills/salesforce/sf-chatter.md @@ -72,7 +72,7 @@ FeedComment: sf data create record \ --sobject FeedItem \ --values "ParentId='a07xx00000ABCDE' Body='Started work on this feature'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Multi-line post with details sf data create record \ @@ -82,7 +82,7 @@ sf data create record \ - 32 tests passing - File size validation added - CI configuration updated'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Post with structured update sf data create record \ @@ -96,7 +96,7 @@ sf data create record \ ⏳ Testing pending Ready for review by EOD.'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 2: Adding Comments to Posts @@ -108,13 +108,13 @@ Ready for review by EOD.'" \ sf data create record \ --sobject FeedComment \ --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='LGTM - approved for merge'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Comment with @mention (use User ID for @mention) sf data create record \ --sobject FeedComment \ --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='Thanks for the update! Please coordinate with {005xx000001X8Uz} for testing.'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Comment with feedback sf data create record \ @@ -124,7 +124,7 @@ sf data create record \ 1. Did you test edge cases? 2. Is documentation updated? 3. Any performance concerns?'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 3: Automated Status Updates @@ -132,11 +132,20 @@ sf data create record \ **Use case**: Post Chatter updates from CI/CD or automation ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Post build success notification WORK_ITEM_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') sf data create record \ --sobject FeedItem \ @@ -145,7 +154,7 @@ sf data create record \ All tests: 156/156 ✓ Coverage: 94% Ready for deployment'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Post failure notification sf data create record \ @@ -155,7 +164,7 @@ sf data create record \ Failed tests: 3/156 See: ${BUILD_URL} Please investigate.'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 4: Post from Git Branch Context @@ -163,6 +172,15 @@ Please investigate.'" \ **Use case**: Automatically post to WI based on current git branch ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Extract WI number from branch BRANCH=$(git rev-parse --abbrev-ref HEAD) WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) @@ -171,7 +189,7 @@ if [ -n "$WI_NUMBER" ]; then # Query for work item QUERY_RESULT=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json) WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') @@ -185,7 +203,7 @@ if [ -n "$WI_NUMBER" ]; then Branch: ${BRANCH} Commits: $(git log --oneline -n 5 | head -5)'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Posted update to W-${WI_NUMBER}" else @@ -208,7 +226,7 @@ sf data query \ WHERE ParentId = 'a07xx00000ABCDE' ORDER BY CreatedDate DESC LIMIT 20" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query comments on a post sf data query \ @@ -216,7 +234,7 @@ sf data query \ FROM FeedComment WHERE FeedItemId = '0D5xx00000FGHIJ' ORDER BY CreatedDate ASC" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Find recent posts mentioning keywords sf data query \ @@ -225,7 +243,7 @@ sf data query \ WHERE Body LIKE '%deployment%' AND CreatedDate = LAST_N_DAYS:7 ORDER BY CreatedDate DESC" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 6: Link Posts @@ -240,7 +258,7 @@ sf data create record \ Type='LinkPost' \ Body='Check out the documentation' \ LinkUrl='https://docs.example.com/feature-guide'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Post PR link PR_URL="https://github.com/org/repo/pull/123" @@ -250,7 +268,7 @@ sf data create record \ Type='LinkPost' \ Body='Pull request ready for review' \ LinkUrl='${PR_URL}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- @@ -324,19 +342,27 @@ CommentBody - Comment text (required, max 10000 chars) sf data create record \ --sobject FeedItem \ --values "ParentId='UNKNOWN_ID' Body='Update'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # ✅ CORRECT: Validate record exists +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + WORK_ITEM_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then sf data create record \ --sobject FeedItem \ --values "ParentId='${WORK_ITEM_ID}' Body='Update'" \ - --target-org gus + --target-org "$DEFAULT_ORG" else echo "Error: Work item not found" fi diff --git a/skills/salesforce/sf-org-auth.md b/skills/salesforce/sf-org-auth.md index bcb1e90..9560f83 100644 --- a/skills/salesforce/sf-org-auth.md +++ b/skills/salesforce/sf-org-auth.md @@ -36,7 +36,7 @@ Activate this skill when: ```bash # Web login (most common - interactive) -sf org login web --alias gus +sf org login web --alias my-org # Web login with custom instance sf org login web --alias production --instance-url https://login.salesforce.com @@ -48,8 +48,8 @@ sf org login jwt --client-id YOUR_CONSUMER_KEY \ --alias ci-org # Access token login -sf org login access-token --instance-url https://gus.my.salesforce.com \ - --alias gus +sf org login access-token --instance-url https://my-org.my.salesforce.com \ + --alias my-org ``` ### Concept 2: Managing Multiple Orgs @@ -63,20 +63,35 @@ sf org list # List with JSON output for scripting sf org list --json -# Display current org details -sf org display --target-org gus +# Get default org dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + # If no config default, check for org marked as default + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + # Fall back to first available org + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Display current org details (only if DEFAULT_ORG is set) +if [ -n "$DEFAULT_ORG" ]; then + sf org display --target-org "$DEFAULT_ORG" +else + echo "No default org found. Set one with: sf config set target-org=" +fi # Display with verbose output (includes auth URL) -sf org display --target-org gus --verbose +sf org display --target-org "$DEFAULT_ORG" --verbose # Set default org -sf config set target-org=gus +sf config set target-org=my-org # Open org in browser -sf org open --target-org gus +sf org open --target-org "$DEFAULT_ORG" # Logout from org -sf org logout --target-org gus +sf org logout --target-org "$DEFAULT_ORG" # Logout from all orgs sf org logout --all @@ -90,29 +105,39 @@ sf org logout --all **Use case**: Dynamically retrieve the logged-in user's email and other details for queries -**IMPORTANT**: Never hardcode user emails (like `user@gus.com`). Always fetch the current user's email dynamically from the authenticated org. +**IMPORTANT**: Never hardcode user emails or org aliases. Always fetch the current user's email and default org dynamically from the authenticated org. ```bash -# ❌ Bad: Hardcoding user email -USER_EMAIL="user@gus.com" - -# ✅ Good: Get current user email from authenticated org (by alias) -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +# ❌ Bad: Hardcoding user email or org alias +USER_EMAIL="user@example.com" +DEFAULT_ORG="gus" + +# ✅ Good: Get default org dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') + +# If no default org is set, check for marked default or use first available +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi -# Alternative: Get from most recently used org -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs | sort_by(.lastUsed) | reverse | .[0].username') +# Get current user email from default org +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') # Alternative: Get user details from org display user -USER_INFO=$(sf org display user --target-org gus --json) +USER_INFO=$(sf org display user --target-org "$DEFAULT_ORG" --json) USER_EMAIL=$(echo "$USER_INFO" | jq -r '.result.email') USER_ID=$(echo "$USER_INFO" | jq -r '.result.id') USER_NAME=$(echo "$USER_INFO" | jq -r '.result.username') # Get user's org details -ORG_INFO=$(sf org display --target-org gus --json) +ORG_INFO=$(sf org display --target-org "$DEFAULT_ORG" --json) ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') +echo "Default Org: $DEFAULT_ORG" echo "Logged in as: $USER_EMAIL" echo "User ID: $USER_ID" echo "Org ID: $ORG_ID" @@ -121,39 +146,58 @@ echo "Instance: $INSTANCE_URL" **Benefits**: - Works across different users and orgs without code changes -- No need to hardcode usernames or emails +- No need to hardcode usernames, emails, or org aliases - Scripts are portable and can be shared with team members -- Safer - prevents accidentally using wrong user credentials +- Safer - prevents accidentally using wrong user credentials or org +- Automatically adapts to the user's default org configuration **Key Points**: +- Use `sf config get target-org --json` to get the default org dynamically - Use `sf org list --json` to get username from authenticated orgs - Use `sf org display user --json` for complete user details including ID -- Filter by org alias when multiple orgs are authenticated +- Fall back to most recently used org if no default org is set - Use User ID (`Assignee__c`) in queries instead of email when possible (more efficient) -- Always validate that the user info was retrieved successfully before using +- Always validate that the user info and org were retrieved successfully before using ### Pattern 2: Error Handling for Authentication **Use case**: Validate authentication and provide helpful error messages ```bash +# Get default org with validation +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') + +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +if [ -z "$DEFAULT_ORG" ] || [ "$DEFAULT_ORG" = "null" ]; then + echo "Error: No default org configured and no authenticated orgs found." + echo "Run: sf org login web --alias my-org" + echo "Then: sf config set target-org=my-org" + exit 1 +fi + # Get user email with validation -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') if [ -z "$USER_EMAIL" ] || [ "$USER_EMAIL" = "null" ]; then - echo "Error: Could not retrieve user email. Is the org authenticated?" - echo "Run: sf org login web --alias gus" + echo "Error: Could not retrieve user email for org: $DEFAULT_ORG" + echo "Run: sf org login web --alias $DEFAULT_ORG" exit 1 fi -echo "Querying work items for: $USER_EMAIL" +echo "Querying work items for: $USER_EMAIL (org: $DEFAULT_ORG)" # Check if org is still connected -ORG_STATUS=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .connectedStatus') +ORG_STATUS=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .connectedStatus') if [ "$ORG_STATUS" != "Connected" ]; then - echo "Error: Org 'gus' is not connected. Status: $ORG_STATUS" - echo "Please re-authenticate: sf org login web --alias gus" + echo "Error: Org '$DEFAULT_ORG' is not connected. Status: $ORG_STATUS" + echo "Please re-authenticate: sf org login web" exit 1 fi ``` @@ -164,14 +208,17 @@ fi ```bash # Setup multiple orgs with meaningful aliases -sf org login web --alias gus-dev -sf org login web --alias gus-staging -sf org login web --alias gus-prod +sf org login web --alias dev +sf org login web --alias staging +sf org login web --alias prod + +# Set default org +sf config set target-org=dev # Function to query across multiple orgs query_all_orgs() { local query="$1" - for org in gus-dev gus-staging gus-prod; do + for org in dev staging prod; do echo "=== Results from $org ===" sf data query --query "$query" --target-org "$org" || echo "Failed for $org" done @@ -230,8 +277,9 @@ From `sf org display user --json`: **Essential Practices:** ``` -✅ DO: Use meaningful aliases for orgs (e.g., gus, gus-prod, gus-dev) -✅ DO: Dynamically fetch user email/ID instead of hardcoding +✅ DO: Use meaningful aliases for orgs (e.g., prod, staging, dev) +✅ DO: Set a default org with sf config set target-org +✅ DO: Dynamically fetch default org and user email/ID instead of hardcoding ✅ DO: Check org connection status before operations ✅ DO: Use --json flag for programmatic processing ✅ DO: Validate extracted values before using them @@ -240,7 +288,7 @@ From `sf org display user --json`: **Common Mistakes to Avoid:** ``` -❌ DON'T: Hardcode user emails or org URLs +❌ DON'T: Hardcode user emails, org aliases, or org URLs ❌ DON'T: Assume an org is still authenticated ❌ DON'T: Skip validation of jq output (check for null/empty) ❌ DON'T: Use generic aliases like "org1" (use descriptive names) @@ -254,13 +302,22 @@ From `sf org display user --json`: ### Critical Violations ```bash -# ❌ NEVER: Hardcode user credentials -USER_EMAIL="user@gus.com" +# ❌ NEVER: Hardcode user credentials or org aliases +USER_EMAIL="user@example.com" USER_ID="005xx000001X8Uz" +DEFAULT_ORG="gus" # ✅ CORRECT: Fetch dynamically -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') -USER_ID=$(sf org display user --target-org gus --json | jq -r '.result.id') +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +USER_ID=$(sf org display user --target-org "$DEFAULT_ORG" --json | jq -r '.result.id') ``` --- diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md index 80874d3..98336b2 100644 --- a/skills/salesforce/sf-record-operations.md +++ b/skills/salesforce/sf-record-operations.md @@ -42,7 +42,7 @@ sf data create record \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='Implement new feature' Status__c='New' Type__c='User Story'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Create with multiple fields sf data create record \ @@ -52,13 +52,13 @@ sf data create record \ Priority__c='P1' \ Type__c='Bug' \ Description__c='

Bug details here

'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Create an Epic sf data create record \ --sobject ADM_Epic__c \ --values "Name='Q1 2024 Features' Health__c='On Track' Description__c='Major features for Q1'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Concept 2: Updating Records @@ -89,21 +89,21 @@ sf data update record \ --sobject ADM_Work__c \ --record-id a07xx00000ABCDE \ --values "Status__c='In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Update multiple fields sf data update record \ --sobject ADM_Work__c \ --record-id a07xx00000ABCDE \ --values "Status__c='Code Review' Assignee__c='005xx000001X8Uz' Story_Points__c=5" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Update by field match sf data update record \ --sobject ADM_Work__c \ --where "Name='W-12345678'" \ --values "Status__c='In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Concept 3: REST API Direct Calls @@ -114,26 +114,26 @@ sf data update record \ # Get record details via REST API sf api request rest \ "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Update multiple fields via PATCH sf api request rest \ "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ --method PATCH \ --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query using REST API sf api request rest \ "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Create record via REST API sf api request rest \ "/services/data/v56.0/sobjects/ADM_Work__c" \ --method POST \ --body '{"Subject__c": "New Story", "Status__c": "New", "Type__c": "User Story"}' \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- @@ -149,26 +149,35 @@ sf api request rest \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='Feature' Assignee__c='005xx000001X8Uz' Sprint__c='a1Fxx000002EFGH'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # ✅ Good: Query for IDs first +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Get user ID USER_ID=$(sf data query \ --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') # Get sprint ID SPRINT_ID=$(sf data query \ --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') # Get epic ID EPIC_ID=$(sf data query \ --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') # Create work item with relationships sf data create record \ @@ -181,7 +190,7 @@ sf data create record \ Assignee__c='${USER_ID}' \ Sprint__c='${SPRINT_ID}' \ Epic__c='${EPIC_ID}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 2: Update with Validation @@ -189,11 +198,20 @@ sf data create record \ **Use case**: Update records only if they exist ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Query to verify record exists QUERY_RESULT=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus) + --target-org "$DEFAULT_ORG") RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') @@ -209,7 +227,7 @@ sf data update record \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" echo "Updated work item W-12345678 to In Progress" ``` @@ -224,7 +242,7 @@ sf data update record \ --sobject ADM_Work__c \ --record-id a07xx00000ABCDE \ --values "Description__c='

Overview

  • Item 1
  • Item 2

Additional details here.

'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Create work item with formatted description sf data create record \ @@ -233,7 +251,7 @@ sf data create record \ Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ Status__c='New' \ Type__c='User Story'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 4: Atomic Updates with Error Handling @@ -241,11 +259,20 @@ sf data create record \ **Use case**: Update multiple fields safely with rollback capability ```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Capture current state before update CURRENT_STATE=$(sf data query \ --query "SELECT Id, Status__c, Assignee__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus) + --target-org "$DEFAULT_ORG") WORK_ITEM_ID=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Id') OLD_STATUS=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Status__c') @@ -256,7 +283,7 @@ if sf data update record \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Code Review' Assignee__c='${NEW_ASSIGNEE_ID}'" \ - --target-org gus; then + --target-org "$DEFAULT_ORG"; then echo "Update successful" else echo "Update failed! Rolling back..." @@ -265,7 +292,7 @@ else --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='${OLD_STATUS}' Assignee__c='${OLD_ASSIGNEE}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" exit 1 fi ``` @@ -366,7 +393,7 @@ sf data update record \ --sobject ADM_Work__c \ --record-id "$RECORD_ID" \ --values "Status__c='Fixed'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ```bash @@ -374,13 +401,13 @@ sf data update record \ sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='New Story'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # ✅ Correct: Include all required fields sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index c51650e..cb58035 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -39,25 +39,34 @@ SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n - Related fields: Use `__r` for relationships (e.g., `Assignee__r.Email`) ```bash +# Get default org (add this at the start of scripts) +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + # Basic query sf data query \ --query "SELECT Id, Name FROM ADM_Work__c LIMIT 10" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query with conditions sf data query \ --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Status__c = 'New'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query with related fields sf data query \ --query "SELECT Id, Subject__c, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Status__c = 'In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query with ordering and limit sf data query \ --query "SELECT Id, Name, CreatedDate FROM ADM_Work__c ORDER BY CreatedDate DESC LIMIT 20" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Concept 2: Output Formats @@ -71,25 +80,25 @@ sf data query \ # Human-readable table (default) sf data query \ --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 5" \ - --target-org gus + --target-org "$DEFAULT_ORG" # JSON output for scripting sf data query \ --query "SELECT Id, Name FROM ADM_Work__c LIMIT 5" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --result-format json # CSV output for Excel sf data query \ --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 100" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --result-format csv > work_items.csv # Query from file echo "SELECT Id, Name FROM ADM_Work__c LIMIT 10" > query.soql sf data query \ --file query.soql \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- @@ -104,33 +113,33 @@ sf data query \ # Find user ID by name sf data query \ --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Find user ID by email sf data query \ --query "SELECT Id, Name, Email FROM User WHERE Email = 'user@example.com'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Find Epic by name sf data query \ --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Find Sprint by name sf data query \ --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Find Product Tag sf data query \ --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Get ID with jq for scripting USER_ID=$(sf data query \ --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') echo "User ID: $USER_ID" ``` @@ -140,8 +149,16 @@ echo "User ID: $USER_ID" **Use case**: Find work items by various criteria ```bash -# Get current user's work items dynamically -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +# Get default org and current user's work items dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') sf data query \ --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c @@ -149,12 +166,12 @@ sf data query \ WHERE Assignee__r.Email = '${USER_EMAIL}' AND Status__c != 'Closed' ORDER BY Priority__c, CreatedDate DESC" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query by work item name (WI number) sf data query \ --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query by sprint sf data query \ @@ -162,7 +179,7 @@ sf data query \ FROM ADM_Work__c WHERE Sprint__r.Name = 'Sprint 42' ORDER BY Status__c, Priority__c" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query by epic sf data query \ @@ -170,7 +187,7 @@ sf data query \ FROM ADM_Work__c WHERE Epic__r.Name LIKE '%Q1 Features%' AND Status__c NOT IN ('Fixed', 'Closed')" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query by type and priority sf data query \ @@ -179,17 +196,25 @@ sf data query \ WHERE Type__c = 'Bug' AND Priority__c = 'P1' AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query user's epics -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') sf data query \ --query "SELECT Id, Name, Description__c, Health__c, Priority__c, Owner.Name, LastModifiedDate FROM ADM_Epic__c WHERE Owner.Email = '${USER_EMAIL}' ORDER BY LastModifiedDate DESC" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 3: Complex Queries with Aggregation @@ -200,7 +225,7 @@ sf data query \ # Count work items by status sf data query \ --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Sum story points by sprint sf data query \ @@ -208,7 +233,7 @@ sf data query \ FROM ADM_Work__c WHERE Sprint__r.Name != null GROUP BY Sprint__r.Name" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Count by assignee sf data query \ @@ -217,7 +242,7 @@ sf data query \ WHERE Status__c IN ('New', 'In Progress') GROUP BY Assignee__r.Name ORDER BY COUNT(Id) DESC" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 4: Querying with Date Filters @@ -228,12 +253,12 @@ sf data query \ # Work items created this week sf data query \ --query "SELECT Name, Subject__c, CreatedDate FROM ADM_Work__c WHERE CreatedDate = THIS_WEEK" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Work items updated in last 7 days sf data query \ --query "SELECT Name, Subject__c, LastModifiedDate FROM ADM_Work__c WHERE LastModifiedDate = LAST_N_DAYS:7" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Work items created in date range sf data query \ @@ -241,7 +266,7 @@ sf data query \ FROM ADM_Work__c WHERE CreatedDate >= 2024-01-01T00:00:00Z AND CreatedDate <= 2024-01-31T23:59:59Z" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Sprints active in date range sf data query \ @@ -249,7 +274,7 @@ sf data query \ FROM ADM_Sprint__c WHERE Start_Date__c <= 2024-12-03 AND End_Date__c >= 2024-12-03" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 5: Using Tooling API @@ -261,19 +286,19 @@ sf data query \ sf data query \ --query "SELECT Name, ApiVersion, LengthWithoutComments FROM ApexClass" \ --use-tooling-api \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query Apex triggers sf data query \ --query "SELECT Name, TableEnumOrId, Status FROM ApexTrigger" \ --use-tooling-api \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query custom fields sf data query \ --query "SELECT DeveloperName, DataType, TableEnumOrId FROM CustomField WHERE TableEnumOrId = 'ADM_Work__c'" \ --use-tooling-api \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- @@ -359,10 +384,18 @@ WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id # If no results, this returns "null" and breaks downstream operations # ✅ CORRECT: Validate query results +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + QUERY_RESULT=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus) + --target-org "$DEFAULT_ORG") RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md index 952fdf9..7d0363e 100644 --- a/skills/salesforce/sf-work-items.md +++ b/skills/salesforce/sf-work-items.md @@ -66,22 +66,30 @@ Description__c - HTML rich text **Use case**: Create complete user story with proper relationships ```bash -# Step 1: Query for required IDs -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +# Step 1: Get default org and query for required IDs +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') USER_ID=$(sf data query \ --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') SPRINT_ID=$(sf data query \ --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') EPIC_ID=$(sf data query \ --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') # Step 2: Create user story with all required fields sf data create record \ @@ -95,7 +103,7 @@ sf data create record \ Assignee__c='${USER_ID}' \ Type__c='User Story' \ Description__c='

As a user, I want to log in securely

'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` **Benefits**: @@ -112,7 +120,7 @@ sf data create record \ SPRINT_RESULT=$(sf data create record \ --sobject ADM_Sprint__c \ --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --json) SPRINT_ID=$(echo "$SPRINT_RESULT" | jq -r '.result.id') @@ -126,7 +134,7 @@ sf data query \ WHERE Status__c = 'Ready for Development' AND Sprint__c = null ORDER BY Priority__c, Story_Points__c" \ - --target-org gus \ + --target-org "$DEFAULT_ORG" \ --result-format json > backlog.json # Review items and manually create CSV for assignment @@ -150,7 +158,7 @@ sf data create record \ Priority__c='P1' \ Health__c='On Track' \ Description__c='

Complete authentication overhaul

'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query work items in epic sf data query \ @@ -158,19 +166,19 @@ sf data query \ FROM ADM_Work__c WHERE Epic__r.Name LIKE '%Authentication%' ORDER BY Status__c, Priority__c" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Update epic health EPIC_ID=$(sf data query \ --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') sf data update record \ --sobject ADM_Epic__c \ --record-id "$EPIC_ID" \ --values "Health__c='At Risk' Description__c='

Blocked on security review

'" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 4: Managing Builds @@ -182,19 +190,19 @@ sf data update record \ sf data create record \ --sobject ADM_Build__c \ --values "Name='Release 1.0' External_ID__c='R010' Target_Date__c=2024-02-01" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Link work items to build BUILD_ID=$(sf data query \ --query "SELECT Id FROM ADM_Build__c WHERE Name = 'Release 1.0'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') sf data update record \ --sobject ADM_Work__c \ --where "Status__c='Fixed' AND Scheduled_Build__c = null" \ --values "Scheduled_Build__c='${BUILD_ID}'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Query work items in build sf data query \ @@ -202,7 +210,7 @@ sf data query \ FROM ADM_Work__c WHERE Scheduled_Build__r.Name = 'Release 1.0' ORDER BY Priority__c" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 5: Work Item Status Workflows @@ -217,27 +225,27 @@ sf data query \ WORK_ITEM_ID=$(sf data query \ --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ --result-format json \ - --target-org gus | jq -r '.result.records[0].Id') + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') sf data update record \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='In Progress'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Move to code review sf data update record \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Code Review'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Mark as fixed sf data update record \ --sobject ADM_Work__c \ --record-id "$WORK_ITEM_ID" \ --values "Status__c='Fixed' Resolved_On__c=$(date +%Y-%m-%d)" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` ### Pattern 6: Sprint Reporting @@ -254,7 +262,7 @@ sf data query \ FROM ADM_Work__c WHERE Sprint__r.Name = 'Sprint 42' GROUP BY Status__c" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Get team velocity sf data query \ @@ -266,10 +274,18 @@ sf data query \ AND Status__c IN ('Fixed', 'Closed') GROUP BY Sprint__r.Name ORDER BY Sprint__r.Name" \ - --target-org gus + --target-org "$DEFAULT_ORG" # Get individual workload -USER_EMAIL=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.alias == "gus") | .username') +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') sf data query \ --query "SELECT Name, Subject__c, Status__c, Story_Points__c, Sprint__r.Name @@ -277,7 +293,7 @@ sf data query \ WHERE Assignee__r.Email = '${USER_EMAIL}' AND Status__c NOT IN ('Fixed', 'Closed') ORDER BY Priority__c, Sprint__r.Name" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- @@ -345,7 +361,7 @@ Key Fields: Name, External_ID__c, Target_Date__c sf data create record \ --sobject ADM_Work__c \ --values "Subject__c='Feature'" \ - --target-org gus + --target-org "$DEFAULT_ORG" # ✅ CORRECT: Include all required and recommended fields sf data create record \ @@ -355,7 +371,7 @@ sf data create record \ Type__c='User Story' \ Priority__c='P2' \ Story_Points__c=3" \ - --target-org gus + --target-org "$DEFAULT_ORG" ``` --- From 57b14a820800342c094e7b73b7c5785932358b05 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 18:58:59 -0300 Subject: [PATCH 10/21] Improved user email handling --- skills/salesforce/sf-record-operations.md | 5 +++-- skills/salesforce/sf-soql-queries.md | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md index 98336b2..0c7b02b 100644 --- a/skills/salesforce/sf-record-operations.md +++ b/skills/salesforce/sf-record-operations.md @@ -161,9 +161,10 @@ if [ -z "$DEFAULT_ORG" ]; then fi fi -# Get user ID +# Get user ID (using dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') USER_ID=$(sf data query \ - --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ --result-format json \ --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index cb58035..9bdd82a 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -115,9 +115,10 @@ sf data query \ --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ --target-org "$DEFAULT_ORG" -# Find user ID by email +# Find user ID by email (use dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') sf data query \ - --query "SELECT Id, Name, Email FROM User WHERE Email = 'user@example.com'" \ + --query "SELECT Id, Name, Email FROM User WHERE Email = '${USER_EMAIL}'" \ --target-org "$DEFAULT_ORG" # Find Epic by name @@ -135,9 +136,10 @@ sf data query \ --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ --target-org "$DEFAULT_ORG" -# Get ID with jq for scripting +# Get ID with jq for scripting (using dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') USER_ID=$(sf data query \ - --query "SELECT Id FROM User WHERE Email = 'user@example.com'" \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ --result-format json \ --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') From d016b1c4f807903f30f13a45538054b25db48396 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 19:17:30 -0300 Subject: [PATCH 11/21] Reflect changes in the readme --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 541b959..bc659e6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Atomic Skills for Claude Code [![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-blue)](https://docs.claude.com/en/docs/claude-code/plugins) -[![Skills](https://img.shields.io/badge/Skills-447-green)](skills/) +[![Skills](https://img.shields.io/badge/Skills-454-green)](skills/) [![License](https://img.shields.io/badge/License-MIT-yellow)](LICENSE) **Context-efficient development knowledge via progressive skill discovery** @@ -42,7 +42,7 @@ This creates an challenging tradeoff between comprehensive coverage and context ## The Solution: Atomic Skills + Progressive Discovery -### Atomic Skills (447 total) +### Atomic Skills (454 total) Each skill is **focused, self-contained, and composable**: - Average 320 lines - small enough to load quickly - Single responsibility - covers one clear topic @@ -53,7 +53,7 @@ Each skill is **focused, self-contained, and composable**: ### Three-Tier Architecture -**Tier 1: Gateway Skills (31 auto-discovered Agent Skills)** +**Tier 1: Gateway Skills (32 auto-discovered Agent Skills)** Lightweight entry points that activate automatically based on keywords: - `discover-api` → triggers on "REST", "GraphQL", "authentication" - `discover-database` → triggers on "PostgreSQL", "MongoDB", "Redis" @@ -61,7 +61,7 @@ Lightweight entry points that activate automatically based on keywords: Each gateway is ~200 lines with quick reference and loading commands. -**Tier 2: Category Indexes (30 detailed references)** +**Tier 2: Category Indexes (31 detailed references)** Full skill listings with descriptions, use cases, and workflows: - `api/INDEX.md` → All 7 API skills with integration patterns - `database/INDEX.md` → All 11 database skills with decision trees @@ -91,7 +91,7 @@ Complete implementation guides loaded only when needed: ## What's Covered -### Core Development (125 skills) +### Core Development (132 skills) **Languages & Frameworks**: - **Backend**: Python (uv), Zig, Rust, Go (31 skills) @@ -110,6 +110,7 @@ Complete implementation guides loaded only when needed: - **Databases**: Postgres, MongoDB, Redis, Redpanda, Iceberg, DuckDB (11 skills) - **Caching**: Multi-layer (browser, HTTP, CDN, Redis), invalidation strategies (7 skills) - **API Design**: REST, GraphQL, auth, rate limiting, versioning (7 skills) +- **Salesforce**: sf CLI, GUS/Agile Accelerator, SOQL, work items, Chatter, automation (7 skills) - **Data Engineering**: ETL, streaming, batch processing (5 skills) - **Testing**: Unit, integration, e2e, TDD, coverage (6 skills) @@ -269,13 +270,14 @@ cat skills/database/redpanda-streaming.md │ ├── README.md # This file │ -└── skills/ # 283 atomic skills, 31 gateways, 30 categories +└── skills/ # 290 atomic skills, 32 gateways, 31 categories │ ├── README.md # Skills catalog │ - ├── Gateway Skills (31 auto-discovered Agent Skills) + ├── Gateway Skills (32 auto-discovered Agent Skills) │ ├── discover-api/ # REST, GraphQL, authentication │ ├── discover-database/ # PostgreSQL, MongoDB, Redis + │ ├── discover-salesforce/ # Salesforce CLI, GUS, SOQL │ ├── discover-frontend/ # React, Next.js, UI components │ ├── discover-ml/ # Machine learning, models │ ├── discover-math/ # Mathematics, algorithms @@ -287,15 +289,17 @@ cat skills/database/redpanda-streaming.md │ ├── discover-workflow/ # Beads, context strategies │ └── ... 20 more gateways │ - ├── Category Indexes (30 detailed references) + ├── Category Indexes (31 detailed references) │ ├── api/INDEX.md # All API skills overview │ ├── database/INDEX.md # All database skills overview + │ ├── salesforce/INDEX.md # All Salesforce skills overview │ ├── ml/INDEX.md # All ML skills overview │ └── ... 27 more indexes │ └── Skills by Category ├── api/ # REST, GraphQL, auth (7) ├── database/ # Postgres, MongoDB, Redis, streaming (11) + ├── salesforce/ # sf CLI, GUS, SOQL, work items, Chatter (7) ├── frontend/ # React, Next.js, performance (8) ├── mobile/ # iOS/Swift, SwiftUI, SwiftData (10) ├── testing/ # Unit, integration, e2e (6) @@ -349,6 +353,7 @@ cat skills/database/redpanda-streaming.md | **Debug** | GDB, LLDB, pdb, DevTools, Valgrind | 14 | discover-debugging | | **Cloud** | AWS, GCP, Modal, Vercel, Cloudflare | 27 | discover-cloud | | **Database** | Postgres, Mongo, Redis, Redpanda, Iceberg | 11 | discover-database | +| **Salesforce** | sf CLI, GUS, SOQL, Chatter, Automation | 7 | discover-salesforce | | **Caching** | Redis, HTTP, CDN, Service Workers | 7 | discover-caching | | **ML/AI** | DSPy, HuggingFace, Arize, GraphRAG | 33 | discover-ml | | **Rust/PyO3** | DSPy integration, RAG, agents, async, production | 19 | N/A | @@ -545,4 +550,4 @@ Feel free to fork and adapt for your own use. Pull requests welcome. --- -**447 atomic skills** • **41 gateway Agent Skills** • **35+ categories** • **100% CI-validated** +**454 atomic skills** • **32 gateway Agent Skills** • **31 categories** • **100% CI-validated** From 89fa215943275deb0eb2c713532018a39d5317e1 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 19:43:13 -0300 Subject: [PATCH 12/21] Added field verification, object description, fix in chatter, command escaping --- skills/salesforce/sf-chatter.md | 65 ++++--- skills/salesforce/sf-soql-queries.md | 251 +++++++++++++++++++++++++-- 2 files changed, 282 insertions(+), 34 deletions(-) diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md index 68d203a..b5abf73 100644 --- a/skills/salesforce/sf-chatter.md +++ b/skills/salesforce/sf-chatter.md @@ -214,36 +214,43 @@ else fi ``` -### Pattern 5: Querying Chatter Feed +### Pattern 5: Querying Chatter Feed (REST API) **Use case**: Read Chatter posts and comments +**IMPORTANT**: FeedItem has strict query restrictions in SOQL. You cannot query FeedItem with `WHERE ParentId` or `WHERE CreatedById` filters. Use the Chatter REST API instead. + ```bash -# Query Chatter feed for a record -sf data query \ - --query "SELECT Id, Body, CreatedBy.Name, CreatedDate - FROM FeedItem - WHERE ParentId = 'a07xx00000ABCDE' - ORDER BY CreatedDate DESC - LIMIT 20" \ +# ❌ This SOQL query FAILS with "Implementation restriction: FeedItem requires a filter by Id" +# sf data query --query "SELECT Id, Body FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE'" + +# ✅ CORRECT: Use Chatter REST API to query feed for a record +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements" \ + --target-org "$DEFAULT_ORG" | jq -r '.elements[] | "[\(.createdDate)] \(.actor.name): \(.body.text)"' + +# Get full feed details with formatting +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements?pageSize=10" \ + --target-org "$DEFAULT_ORG" | jq '.' + +# Query user's feed (user profile posts) +sf api request rest \ + "/services/data/v61.0/chatter/feeds/user-profile/005xx000001X8Uz/feed-elements" \ --target-org "$DEFAULT_ORG" -# Query comments on a post +# Query comments on a specific feed item +sf api request rest \ + "/services/data/v61.0/chatter/feed-elements/0D5xx00000FGHIJ/capabilities/comments/items" \ + --target-org "$DEFAULT_ORG" | jq -r '.items[] | "[\(.createdDate)] \(.user.name): \(.body.text)"' + +# FeedComment CAN be queried with SOQL (unlike FeedItem) sf data query \ --query "SELECT CommentBody, CreatedBy.Name, CreatedDate FROM FeedComment WHERE FeedItemId = '0D5xx00000FGHIJ' ORDER BY CreatedDate ASC" \ --target-org "$DEFAULT_ORG" - -# Find recent posts mentioning keywords -sf data query \ - --query "SELECT Id, Body, Parent.Name, CreatedBy.Name, CreatedDate - FROM FeedItem - WHERE Body LIKE '%deployment%' - AND CreatedDate = LAST_N_DAYS:7 - ORDER BY CreatedDate DESC" \ - --target-org "$DEFAULT_ORG" ``` ### Pattern 6: Link Posts @@ -284,13 +291,15 @@ sf data create record --sobject FeedItem --values "ParentId= Body=''" # Create comment sf data create record --sobject FeedComment --values "FeedItemId= CommentBody=''" --target-org -# Query feed -sf data query --query "SELECT Body, CreatedBy.Name FROM FeedItem WHERE ParentId = ''" --target-org +# Query feed (USE REST API - SOQL has strict limitations) +sf api request rest "/services/data/v61.0/chatter/feeds/record//feed-elements" --target-org -# Query comments +# Query comments (SOQL works for FeedComment) sf data query --query "SELECT CommentBody FROM FeedComment WHERE FeedItemId = ''" --target-org ``` +**IMPORTANT**: FeedItem cannot be queried with `WHERE ParentId` or `WHERE CreatedById` in SOQL. Always use Chatter REST API for reading feeds. + ### Key Fields **FeedItem (Posts)**: @@ -324,6 +333,7 @@ CommentBody - Comment text (required, max 10000 chars) **Common Mistakes to Avoid:** ``` +❌ DON'T: Query FeedItem with SOQL WHERE clauses (use REST API) ❌ DON'T: Post sensitive information (passwords, tokens) ❌ DON'T: Spam feeds with automated posts ❌ DON'T: Use Chatter for private/confidential data @@ -337,6 +347,19 @@ CommentBody - Comment text (required, max 10000 chars) ### Critical Violations +```bash +# ❌ NEVER: Query FeedItem with WHERE ParentId (will fail) +sf data query \ + --query "SELECT Id, Body FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE'" \ + --target-org "$DEFAULT_ORG" +# Error: Implementation restriction: FeedItem requires a filter by Id + +# ✅ CORRECT: Use Chatter REST API instead +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements" \ + --target-org "$DEFAULT_ORG" +``` + ```bash # ❌ NEVER: Post without validating ParentId sf data create record \ diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index 9bdd82a..4d8587f 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -26,7 +26,30 @@ Activate this skill when: ## Core Concepts -### Concept 1: Basic SOQL Syntax +### Concept 1: Field Discovery Before Querying + +**CRITICAL**: Always use `sf sobject describe` when uncertain about field names. This prevents INVALID_FIELD errors. + +```bash +# Discover all fields on an object +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" + +# Search for specific fields (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# Find relationship fields +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' +``` + +**Why this matters**: Guessing field names leads to errors like: +- `No such column 'Team__c' on entity 'User'` (field doesn't exist) +- Wrong field type or reference target +- Missing junction objects for many-to-many relationships + +See **Pattern 1: Discovering Object Fields** below for detailed examples. + +### Concept 2: Basic SOQL Syntax **Query Structure**: ```sql @@ -69,7 +92,77 @@ sf data query \ --target-org "$DEFAULT_ORG" ``` -### Concept 2: Output Formats +### Concept 2: Verified Field Names + +**Common GUS Objects** (verified via `sf sobject describe`): + +**ADM_Work__c (Work Items)**: +``` +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) +Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) +Product_Tag__c (→ADM_Product_Tag__c), Description__c +CreatedDate, LastModifiedDate +``` + +**ADM_Epic__c (Epics)**: +``` +Id, Name, Description__c, Health__c (not Status__c!) +Priority__c, OwnerId (→User) +CreatedDate, LastModifiedDate +``` + +**ADM_Sprint__c (Sprints)**: +``` +Id, Name, Start_Date__c, End_Date__c +Scrum_Team__c (→ADM_Scrum_Team__c) +CreatedDate, LastModifiedDate +``` + +**ADM_Build__c (Builds)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**ADM_Product_Tag__c (Product Tags)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**ADM_Scrum_Team_Member__c (Team Membership - Junction Object)**: +``` +Id, Name, Member_Name__c (→User), Scrum_Team__c (→ADM_Scrum_Team__c) +CreatedDate, LastModifiedDate +``` + +**User (Standard Object)**: +``` +Id, Name, Email, Username, IsActive, ProfileId +Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object +``` + +**FeedItem (Chatter Posts)**: +``` +Id, ParentId, Body, Type, LinkUrl, Visibility +CreatedDate, CreatedById +``` + +**FeedComment (Chatter Comments)**: +``` +Id, FeedItemId, CommentBody, CreatedDate, CreatedById +``` + +**Related Field Notation**: +``` +Assignee__r.Name # User name +Assignee__r.Email # User email +Sprint__r.Name # Sprint name +Epic__r.Name # Epic name +Scrum_Team__r.Name # Team name +Found_in_Build__r.Name # Build name +``` + +### Concept 3: Output Formats **Available Formats**: - `human` - Table format (default) @@ -105,7 +198,62 @@ sf data query \ ## Patterns -### Pattern 1: Finding Record IDs +### Pattern 1: Discovering Object Fields + +**Use case**: Find available fields on an object before querying (avoids INVALID_FIELD errors) + +**IMPORTANT**: Always use `sf sobject describe` when unsure about field names. This prevents errors like `No such column 'Team__c' on entity 'User'`. + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# List all fields on an object +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" + +# Search for specific field names (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# Find fields that reference another object (e.g., User) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, label: .label, relationshipName: .relationshipName}' + +# Get all custom fields (fields ending in __c) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.name | endswith("__c")) | {name: .name, label: .label, type: .type}' + +# Find relationship fields (__r notation) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' +``` + +**Example: Finding team membership fields** +```bash +# Discovered that User doesn't have Team__c, but ADM_Scrum_Team_Member__c has Member_Name__c +USER_ID="005EE000001JW5FYAW" + +# Query teams through the junction object +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +**Benefits**: +- Avoids INVALID_FIELD errors from guessing field names +- Discovers correct relationship field names (__r notation) +- Finds junction objects for many-to-many relationships +- Identifies custom vs standard fields + +### Pattern 2: Finding Record IDs **Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) @@ -146,7 +294,7 @@ USER_ID=$(sf data query \ echo "User ID: $USER_ID" ``` -### Pattern 2: Querying Work Items +### Pattern 3: Querying Work Items **Use case**: Find work items by various criteria @@ -217,9 +365,22 @@ sf data query \ WHERE Owner.Email = '${USER_EMAIL}' ORDER BY LastModifiedDate DESC" \ --target-org "$DEFAULT_ORG" + +# Query work items with epics (filter non-null with jq) +# IMPORTANT: Avoid != in queries due to shell escaping - filter with jq instead +sf data query \ + --query "SELECT Id, Name, Subject__c, Epic__c, Epic__r.Name, Epic__r.Id + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC + LIMIT 50" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name) - \(.Name): \(.Subject__c)"' | sort -u ``` -### Pattern 3: Complex Queries with Aggregation +**Note on filtering null values**: Due to shell escaping issues with `!=` (the `!` triggers history expansion), it's best to query all records and filter with jq using `select(.Epic__r)` to check for non-null relationships. + +### Pattern 4: Complex Queries with Aggregation **Use case**: Get counts, sums, and grouped data @@ -229,13 +390,13 @@ sf data query \ --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ --target-org "$DEFAULT_ORG" -# Sum story points by sprint +# Sum story points by sprint (filter null sprints with jq) sf data query \ --query "SELECT Sprint__r.Name, SUM(Story_Points__c) total_points FROM ADM_Work__c - WHERE Sprint__r.Name != null GROUP BY Sprint__r.Name" \ - --target-org "$DEFAULT_ORG" + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Sprint__r) | "\(.Sprint__r.Name): \(.total_points) points"' # Count by assignee sf data query \ @@ -333,13 +494,16 @@ LAST_WEEK Previous week THIS_QUARTER Current quarter ``` -### Common Fields +### Common Fields (All Verified) -**Work Items (ADM_Work__c)**: +See **Concept 2: Verified Field Names** above for complete field listings. + +**Quick Reference - Most Used Fields**: ``` -Id, Name, Subject__c, Status__c, Priority__c, Type__c -Story_Points__c, Assignee__c, Sprint__c, Epic__c -CreatedDate, LastModifiedDate, Description__c +ADM_Work__c: Subject__c, Status__c, Priority__c, Type__c, Assignee__c, Sprint__c, Epic__c +ADM_Epic__c: Description__c, Health__c (NOT Status__c!), Priority__c, OwnerId +ADM_Sprint__c: Start_Date__c, End_Date__c, Scrum_Team__c +User: Name, Email (no Team__c - use ADM_Scrum_Team_Member__c) ``` **Related Field Notation**: @@ -348,30 +512,38 @@ Assignee__r.Name # User name Assignee__r.Email # User email Sprint__r.Name # Sprint name Epic__r.Name # Epic name +Scrum_Team__r.Name # Team name Found_in_Build__r.Name # Build name ``` +**IMPORTANT**: When uncertain about fields, always use `sf sobject describe --sobject ` to verify field existence and names before querying. + --- ## Best Practices **Essential Practices:** ``` +✅ DO: Use `sf sobject describe` when uncertain about field names (prevents INVALID_FIELD errors) ✅ DO: Use --result-format json for scripting ✅ DO: Use LIMIT to avoid timeouts on large datasets ✅ DO: Query for IDs before creating related records ✅ DO: Use relationship queries (__r) instead of multiple queries ✅ DO: Validate query results before using extracted values ✅ DO: Use WHERE clauses to filter data server-side +✅ DO: Refer to Concept 2 for verified field names ``` **Common Mistakes to Avoid:** ``` +❌ DON'T: Guess field names without verifying (use sf sobject describe) +❌ DON'T: Use != in double-quoted queries (shell escapes !) ❌ DON'T: Query without LIMIT (can timeout) ❌ DON'T: Use SELECT * (not supported in SOQL) ❌ DON'T: Assume queries will always return results ❌ DON'T: Forget __c suffix on custom fields ❌ DON'T: Skip validation of jq output (check for null) +❌ DON'T: Assume User has Team__c field (use junction object) ``` --- @@ -380,6 +552,23 @@ Found_in_Build__r.Name # Build name ### Critical Violations +```bash +# ❌ NEVER: Use != in double-quoted strings (shell escapes the !) +sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --target-org gus +# Error: unexpected token: '\' + +# ✅ CORRECT: Filter null values using jq instead +sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ + --result-format json --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name)"' +``` + +**Why this happens**: In bash/zsh, `!` triggers history expansion even in double quotes, causing the shell to escape it as `\!`. SOQL doesn't recognize this escaped form. + +**Solutions**: +1. **Query all records and filter with jq** (recommended for complex conditions) +2. Use `IS NOT NULL` syntax if your SOQL version supports it +3. Use relationship fields like `Epic__r.Id` and check with `select(.Epic__r)` in jq + ```bash # ❌ NEVER: Query without checking results WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id') @@ -414,6 +603,42 @@ if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then fi ``` +```bash +# ❌ NEVER: Guess field names without verification +sf data query --query "SELECT Id, Name, Team__c FROM User WHERE Id = '005EE000001JW5FYAW'" --target-org gus +# Error: No such column 'Team__c' on entity 'User' + +# ✅ CORRECT: Use sf sobject describe to discover correct fields +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Discover available fields +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team +# Result: No Team__c field exists + +# Find junction object for team membership +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, relationshipName: .relationshipName}' +# Result: Member_Name__c field references User + +# Query using correct field +sf data query \ + --query "SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005EE000001JW5FYAW'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +**Why this matters**: +- Field names vary across Salesforce implementations +- Many-to-many relationships use junction objects +- `sf sobject describe` is the authoritative source +- See **Pattern 1: Discovering Object Fields** for detailed examples + --- ## Security Considerations From a6aa2f6665f580371725e7fc930299218e13f157 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 19:57:26 -0300 Subject: [PATCH 13/21] Change from do to mandatory for describing objects --- skills/salesforce/sf-soql-queries.md | 130 +++++++++++++++++++++------ 1 file changed, 101 insertions(+), 29 deletions(-) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index 4d8587f..c274c80 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -6,9 +6,9 @@ description: Query Salesforce data using SOQL and sf CLI # Salesforce SOQL Queries **Scope**: SOQL query syntax, data retrieval, and result formatting -**Lines**: ~200 +**Lines**: ~250 **Last Updated**: 2025-12-03 -**Format Version**: 1.0 (Atomic) +**Format Version**: 1.1 (Atomic - Enhanced field verification requirements) --- @@ -22,31 +22,48 @@ Activate this skill when: - Building reports or dashboards - Joining related objects +**⚠️ CRITICAL REQUIREMENT**: Before writing ANY SOQL query, you MUST first run `sf sobject describe --sobject ` to verify field names. DO NOT skip this step. DO NOT assume field names exist, even if they seem obvious (like Department, Title, Team__c on User object). See Concept 1 below. + --- ## Core Concepts -### Concept 1: Field Discovery Before Querying +### Concept 1: MANDATORY Field Discovery Before Querying + +**CRITICAL RULE**: You MUST use `sf sobject describe` BEFORE writing any SOQL query with fields beyond Id and Name. This is NOT optional. + +**MANDATORY WORKFLOW**: +1. **FIRST**: Always run `sf sobject describe --sobject ` +2. **SECOND**: Verify the exact field names exist in the output +3. **THIRD**: Only then write your SOQL query using verified field names -**CRITICAL**: Always use `sf sobject describe` when uncertain about field names. This prevents INVALID_FIELD errors. +**NEVER query fields without verifying them first**, even if they seem obvious (Department, Title, Team__c, etc.). ```bash -# Discover all fields on an object -sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" +# STEP 1: ALWAYS describe the object FIRST +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" -# Search for specific fields (e.g., team-related) +# STEP 2: Search for specific fields (e.g., team-related) sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team -# Find relationship fields +# STEP 3: Find relationship fields sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# STEP 4: Only after verification, write your query using confirmed field names ``` **Why this matters**: Guessing field names leads to errors like: - `No such column 'Team__c' on entity 'User'` (field doesn't exist) +- `No such column 'Department' on entity 'User'` (field may not exist) - Wrong field type or reference target - Missing junction objects for many-to-many relationships +**Common fields that DON'T exist**: +- User.Team__c (use ADM_Scrum_Team_Member__c junction object instead) +- User.Department (may not exist in all orgs) +- User.Division (may not exist in all orgs) + See **Pattern 1: Discovering Object Fields** below for detailed examples. ### Concept 2: Basic SOQL Syntax @@ -198,12 +215,15 @@ sf data query \ ## Patterns -### Pattern 1: Discovering Object Fields +### Pattern 1: Discovering Object Fields (MANDATORY FIRST STEP) **Use case**: Find available fields on an object before querying (avoids INVALID_FIELD errors) -**IMPORTANT**: Always use `sf sobject describe` when unsure about field names. This prevents errors like `No such column 'Team__c' on entity 'User'`. +**MANDATORY**: You MUST ALWAYS use `sf sobject describe` as the FIRST STEP before writing ANY query. This is NOT optional, even for fields that seem obvious. + +**WORKFLOW - FOLLOW THESE STEPS IN ORDER**: +**STEP 1: Describe the object to see available fields** ```bash # Get default org DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') @@ -214,16 +234,22 @@ if [ -z "$DEFAULT_ORG" ]; then fi fi -# List all fields on an object +# MANDATORY: Describe the object FIRST sf sobject describe --sobject User --target-org "$DEFAULT_ORG" +``` +**STEP 2: Verify the specific fields you want to query exist** +```bash # Search for specific field names (e.g., team-related) sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team -# Find fields that reference another object (e.g., User) +# If grep returns nothing, the field doesn't exist - find the junction object instead sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, label: .label, relationshipName: .relationshipName}' +``` +**STEP 3: Extract verified field names programmatically (RECOMMENDED)** +```bash # Get all custom fields (fields ending in __c) sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.name | endswith("__c")) | {name: .name, label: .label, type: .type}' @@ -233,12 +259,13 @@ sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' ``` -**Example: Finding team membership fields** +**STEP 4: Only after verification, write your query** ```bash -# Discovered that User doesn't have Team__c, but ADM_Scrum_Team_Member__c has Member_Name__c +# Example: Finding team membership fields +# After discovering that User doesn't have Team__c, we found ADM_Scrum_Team_Member__c has Member_Name__c USER_ID="005EE000001JW5FYAW" -# Query teams through the junction object +# Query teams through the verified junction object sf data query \ --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c @@ -522,21 +549,25 @@ Found_in_Build__r.Name # Build name ## Best Practices -**Essential Practices:** +**Essential Practices (IN ORDER OF IMPORTANCE):** ``` -✅ DO: Use `sf sobject describe` when uncertain about field names (prevents INVALID_FIELD errors) +✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY, NOT OPTIONAL +✅ DO: Verify field names exist in describe output before writing SELECT statements ✅ DO: Use --result-format json for scripting ✅ DO: Use LIMIT to avoid timeouts on large datasets ✅ DO: Query for IDs before creating related records ✅ DO: Use relationship queries (__r) instead of multiple queries ✅ DO: Validate query results before using extracted values ✅ DO: Use WHERE clauses to filter data server-side -✅ DO: Refer to Concept 2 for verified field names +✅ DO: Refer to Concept 2 for verified field names (but verify them first!) ``` -**Common Mistakes to Avoid:** +**Common Mistakes to Avoid (CRITICAL):** ``` -❌ DON'T: Guess field names without verifying (use sf sobject describe) +❌ DON'T: EVER query fields without running sf sobject describe first - THIS CAUSES MOST ERRORS +❌ DON'T: Assume ANY field exists without verification (not even Department, Title, or Team__c) +❌ DON'T: Guess field names without verifying (ALWAYS use sf sobject describe) +❌ DON'T: Write queries based on field names from other orgs or documentation ❌ DON'T: Use != in double-quoted queries (shell escapes !) ❌ DON'T: Query without LIMIT (can timeout) ❌ DON'T: Use SELECT * (not supported in SOQL) @@ -550,7 +581,44 @@ Found_in_Build__r.Name # Build name ## Anti-Patterns -### Critical Violations +### Critical Violation #1: Querying Fields Without Verification (MOST COMMON ERROR) + +```bash +# ❌ NEVER: Query fields without first running sf sobject describe +# This is the #1 cause of INVALID_FIELD errors +sf data query --query "SELECT Id, Name, Username, Email, Department, Division, Title, Team__c FROM User WHERE Username = 'user@example.com'" --target-org gus +# Error: No such column 'Team__c' on entity 'User' +# Error: No such column 'Department' on entity 'User' (may not exist in all orgs) + +# ✅ CORRECT: ALWAYS describe the object FIRST +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# STEP 1: Describe to see what fields actually exist +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i "team\|department\|division\|title" + +# STEP 2: If fields don't exist, find alternative approach (e.g., junction objects) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User")' + +# STEP 3: Query only with verified fields +sf data query \ + --query "SELECT Id, Name, Username, Email FROM User WHERE Username = 'user@example.com'" \ + --target-org "$DEFAULT_ORG" +``` + +**Why this is the #1 error**: +- Field names vary across Salesforce orgs and implementations +- Custom fields that exist in one org may not exist in another +- Assuming field names without verification WILL cause failures +- User.Team__c, User.Department, User.Division are NOT guaranteed to exist + +### Critical Violation #2: Using Shell Special Characters ```bash # ❌ NEVER: Use != in double-quoted strings (shell escapes the !) @@ -603,12 +671,16 @@ if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then fi ``` +### Critical Violation #3: Assuming Fields Exist Without Verification + +**NOTE**: This is another example of Violation #1. See above for the full explanation. + ```bash -# ❌ NEVER: Guess field names without verification +# ❌ NEVER: Guess field names without verification (see Violation #1) sf data query --query "SELECT Id, Name, Team__c FROM User WHERE Id = '005EE000001JW5FYAW'" --target-org gus # Error: No such column 'Team__c' on entity 'User' -# ✅ CORRECT: Use sf sobject describe to discover correct fields +# ✅ CORRECT: Use sf sobject describe to discover correct fields (MANDATORY STEP) DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') if [ -z "$DEFAULT_ORG" ]; then DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) @@ -617,16 +689,16 @@ if [ -z "$DEFAULT_ORG" ]; then fi fi -# Discover available fields +# STEP 1: Discover available fields sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team # Result: No Team__c field exists -# Find junction object for team membership +# STEP 2: Find junction object for team membership sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, relationshipName: .relationshipName}' # Result: Member_Name__c field references User -# Query using correct field +# STEP 3: Query using verified fields and correct object sf data query \ --query "SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005EE000001JW5FYAW'" \ --result-format json \ @@ -636,8 +708,8 @@ sf data query \ **Why this matters**: - Field names vary across Salesforce implementations - Many-to-many relationships use junction objects -- `sf sobject describe` is the authoritative source -- See **Pattern 1: Discovering Object Fields** for detailed examples +- `sf sobject describe` is the authoritative source - USE IT FIRST, ALWAYS +- See **Pattern 1: Discovering Object Fields** and **Violation #1** for detailed examples --- @@ -661,4 +733,4 @@ sf data query \ --- **Last Updated**: 2025-12-03 -**Format Version**: 1.0 (Atomic) +**Format Version**: 1.1 (Atomic - Enhanced field verification requirements) From 5c88f74182261c3c7781436eeb74d55a56717587 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:00:21 -0300 Subject: [PATCH 14/21] Improved keywords --- skills/salesforce/sf-automation.md | 3 ++- skills/salesforce/sf-bulk-operations.md | 3 ++- skills/salesforce/sf-chatter.md | 3 ++- skills/salesforce/sf-org-auth.md | 3 ++- skills/salesforce/sf-record-operations.md | 3 ++- skills/salesforce/sf-soql-queries.md | 3 ++- skills/salesforce/sf-work-items.md | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/skills/salesforce/sf-automation.md b/skills/salesforce/sf-automation.md index 7a183c3..825496f 100644 --- a/skills/salesforce/sf-automation.md +++ b/skills/salesforce/sf-automation.md @@ -1,6 +1,7 @@ --- name: salesforce-automation -description: Automate Salesforce operations with git integration and CI/CD +description: Automate Salesforce operations with git integration and CI/CD. Use for automating GUS work item updates, inferring WI numbers from branches, and git workflows. +keywords: salesforce, gus, automation, git, CI/CD, work item, branch, workflow, hook, automated updates, WI inference --- # Salesforce Automation diff --git a/skills/salesforce/sf-bulk-operations.md b/skills/salesforce/sf-bulk-operations.md index 9493f34..b275ae6 100644 --- a/skills/salesforce/sf-bulk-operations.md +++ b/skills/salesforce/sf-bulk-operations.md @@ -1,6 +1,7 @@ --- name: salesforce-bulk-operations -description: Perform bulk data operations on Salesforce objects +description: Perform bulk data operations on Salesforce objects. Use for mass updates to GUS work items, bulk exports, and large data operations. +keywords: salesforce, gus, bulk, mass update, export, import, CSV, bulk API, large datasets, multiple records --- # Salesforce Bulk Operations diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md index b5abf73..044f9ab 100644 --- a/skills/salesforce/sf-chatter.md +++ b/skills/salesforce/sf-chatter.md @@ -1,6 +1,7 @@ --- name: salesforce-chatter -description: Create and manage Chatter posts and comments in Salesforce +description: Create and manage Chatter posts and comments in Salesforce. Use for posting updates to GUS work items, epics, and records. +keywords: chatter, salesforce, gus, post, comment, feed, FeedItem, FeedComment, update, notification, work item comments --- # Salesforce Chatter Operations diff --git a/skills/salesforce/sf-org-auth.md b/skills/salesforce/sf-org-auth.md index 9560f83..c853928 100644 --- a/skills/salesforce/sf-org-auth.md +++ b/skills/salesforce/sf-org-auth.md @@ -1,6 +1,7 @@ --- name: salesforce-org-auth -description: Authenticate and manage Salesforce orgs using sf CLI +description: Authenticate and manage Salesforce orgs using sf CLI. Use for login, org management, user information, and GUS authentication. +keywords: salesforce, gus, authentication, login, org, user info, sf cli, connect, session --- # Salesforce Org Authentication diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md index 0c7b02b..b432172 100644 --- a/skills/salesforce/sf-record-operations.md +++ b/skills/salesforce/sf-record-operations.md @@ -1,6 +1,7 @@ --- name: salesforce-record-operations -description: Create and update Salesforce records using sf CLI +description: Create and update Salesforce records using sf CLI. Use for creating/updating GUS work items, epics, sprints, and any Salesforce object. +keywords: salesforce, gus, create, update, record, work item, epic, sprint, sf data, REST API, CRUD operations --- # Salesforce Record Operations diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index c274c80..b5d1a31 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -1,6 +1,7 @@ --- name: salesforce-soql-queries -description: Query Salesforce data using SOQL and sf CLI +description: Query Salesforce data using SOQL and sf CLI. Use for GUS work items, epics, sprints, teams, Agile Accelerator queries, and any Salesforce object queries. +keywords: salesforce, gus, soql, query, work items, epic, sprint, team, agile accelerator, ADM_Work__c, ADM_Epic__c, ADM_Sprint__c, ADM_Scrum_Team_Member__c, user, field verification --- # Salesforce SOQL Queries diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md index 7d0363e..3524da4 100644 --- a/skills/salesforce/sf-work-items.md +++ b/skills/salesforce/sf-work-items.md @@ -1,6 +1,7 @@ --- name: salesforce-work-items -description: Manage Agile Accelerator (GUS) work items, sprints, and epics +description: Manage Agile Accelerator (GUS) work items, sprints, and epics. Use for creating stories, bugs, tasks, managing sprints, epics, teams, and builds. +keywords: gus, agile accelerator, work items, story, bug, task, sprint, epic, team, scrum, build, ADM_Work__c, ADM_Epic__c, ADM_Sprint__c, ADM_Scrum_Team__c, salesforce --- # Salesforce Agile Accelerator Work Items From bf04973553d7d1ebbd5dbd4f7897e4f260eae10c Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:04:58 -0300 Subject: [PATCH 15/21] Enhanced Salesforce skills with mandatory field verification, relationship name rules, and GUS/team/epic keywords for better discoverability --- skills/salesforce/sf-soql-queries.md | 89 ++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index b5d1a31..09100db 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -110,7 +110,31 @@ sf data query \ --target-org "$DEFAULT_ORG" ``` -### Concept 2: Verified Field Names +### Concept 2: Relationship Names - CRITICAL RULES + +**CRITICAL**: Relationship names (the `__r` suffix) are DIFFERENT from object names. NEVER assume the relationship name matches the object name. + +**Common Mistake**: +```sql +-- ❌ WRONG: Using object name as relationship +SELECT ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c + +-- ✅ CORRECT: Using actual relationship name from describe +SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c +``` + +**How to get the correct relationship name**: +```bash +# ALWAYS use sf sobject describe to get the relationship name +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r","referenceTo":["ADM_Scrum_Team__c"]} +``` + +**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. + +### Concept 3: Verified Field Names **Common GUS Objects** (verified via `sf sobject describe`): @@ -170,17 +194,24 @@ CreatedDate, CreatedById Id, FeedItemId, CommentBody, CreatedDate, CreatedById ``` -**Related Field Notation**: +**Related Field Notation (VERIFIED via sf sobject describe)**: ``` -Assignee__r.Name # User name -Assignee__r.Email # User email -Sprint__r.Name # Sprint name -Epic__r.Name # Epic name -Scrum_Team__r.Name # Team name -Found_in_Build__r.Name # Build name +# From ADM_Work__c +Assignee__r.Name # User name (field: Assignee__c → User) +Assignee__r.Email # User email (field: Assignee__c → User) +Sprint__r.Name # Sprint name (field: Sprint__c → ADM_Sprint__c) +Epic__r.Name # Epic name (field: Epic__c → ADM_Epic__c) +Found_in_Build__r.Name # Build name (field: Found_in_Build__c → ADM_Build__c) + +# From ADM_Scrum_Team_Member__c +Scrum_Team__r.Name # Team name (field: Scrum_Team__c → ADM_Scrum_Team__c) + # NOTE: Relationship is Scrum_Team__r, NOT ADM_Scrum_Team__r! + +# CRITICAL: Relationship names come from the relationshipName field in sf sobject describe, +# NOT from the object name! Always verify with describe before using. ``` -### Concept 3: Output Formats +### Concept 4: Output Formats **Available Formats**: - `human` - Table format (default) @@ -554,18 +585,21 @@ Found_in_Build__r.Name # Build name ``` ✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY, NOT OPTIONAL ✅ DO: Verify field names exist in describe output before writing SELECT statements +✅ DO: Get relationship names from the relationshipName field in describe (NOT from object names) ✅ DO: Use --result-format json for scripting ✅ DO: Use LIMIT to avoid timeouts on large datasets ✅ DO: Query for IDs before creating related records ✅ DO: Use relationship queries (__r) instead of multiple queries ✅ DO: Validate query results before using extracted values ✅ DO: Use WHERE clauses to filter data server-side -✅ DO: Refer to Concept 2 for verified field names (but verify them first!) +✅ DO: Refer to Concepts 2-3 for verified field/relationship names (but verify them first!) ``` **Common Mistakes to Avoid (CRITICAL):** ``` ❌ DON'T: EVER query fields without running sf sobject describe first - THIS CAUSES MOST ERRORS +❌ DON'T: Assume relationship names match object names (use relationshipName from describe) +❌ DON'T: Use ADM_Scrum_Team__r when the actual relationship is Scrum_Team__r ❌ DON'T: Assume ANY field exists without verification (not even Department, Title, or Team__c) ❌ DON'T: Guess field names without verifying (ALWAYS use sf sobject describe) ❌ DON'T: Write queries based on field names from other orgs or documentation @@ -619,7 +653,40 @@ sf data query \ - Assuming field names without verification WILL cause failures - User.Team__c, User.Department, User.Division are NOT guaranteed to exist -### Critical Violation #2: Using Shell Special Characters +### Critical Violation #2: Assuming Relationship Names Match Object Names (VERY COMMON) + +```bash +# ❌ NEVER: Assume relationship name matches object name +sf data query --query "SELECT Id, Name, ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" --target-org gus +# Error: Didn't understand relationship 'ADM_Scrum_Team__r' in field path + +# ✅ CORRECT: Use sf sobject describe to get the actual relationship name +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# STEP 1: Get the actual relationship name from describe +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName}' +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r"} + +# STEP 2: Use the verified relationship name (Scrum_Team__r, NOT ADM_Scrum_Team__r) +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" \ + --target-org "$DEFAULT_ORG" +``` + +**Why this is a common error**: +- Relationship names are defined in the `relationshipName` field, NOT derived from object names +- Field `Scrum_Team__c` references object `ADM_Scrum_Team__c` but has relationship name `Scrum_Team__r` +- The `ADM_` prefix is NOT part of the relationship name +- ALWAYS use `sf sobject describe` to get the exact `relationshipName` value + +### Critical Violation #3: Using Shell Special Characters ```bash # ❌ NEVER: Use != in double-quoted strings (shell escapes the !) From b4f0a40bc63a030bf10e232619c1d9dfaacf6666 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:09:48 -0300 Subject: [PATCH 16/21] Add ADM_Scrum_Team_Member__c field documentation and LIKE operator anti-pattern for reference fields --- skills/salesforce/sf-soql-queries.md | 133 +++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 10 deletions(-) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index 09100db..5ed0f0b 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -7,9 +7,9 @@ keywords: salesforce, gus, soql, query, work items, epic, sprint, team, agile ac # Salesforce SOQL Queries **Scope**: SOQL query syntax, data retrieval, and result formatting -**Lines**: ~250 +**Lines**: ~920 **Last Updated**: 2025-12-03 -**Format Version**: 1.1 (Atomic - Enhanced field verification requirements) +**Format Version**: 1.2 (Atomic - Added ADM_Scrum_Team_Member__c field details and LIKE operator on reference fields anti-pattern) --- @@ -173,8 +173,21 @@ Id, Name, CreatedDate, LastModifiedDate **ADM_Scrum_Team_Member__c (Team Membership - Junction Object)**: ``` -Id, Name, Member_Name__c (→User), Scrum_Team__c (→ADM_Scrum_Team__c) +Id, Name (auto-number: STM-######) +Member_Name__c (→User - ID field, use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field with team name) +Internal_Email__c (member's email address) +Role__c (member's role on team) +Allocation__c, Department__c, Functional_Area__c +Active__c (boolean indicating active membership) CreatedDate, LastModifiedDate + +CRITICAL NOTES: +- Member_Name__c is a User ID lookup field - use = for exact match, NOT LIKE +- Name is auto-numbered (STM-######), not searchable by member name +- Use Internal_Email__c for email, NOT Email__c (doesn't exist) +- No User__c field exists - use Member_Name__c instead ``` **User (Standard Object)**: @@ -312,7 +325,71 @@ sf data query \ - Finds junction objects for many-to-many relationships - Identifies custom vs standard fields -### Pattern 2: Finding Record IDs +### Pattern 2: Querying Team Memberships (Common Pitfall Example) + +**Use case**: Find which Scrum teams a user belongs to + +**COMMON ERRORS** (see error explanations at bottom): +```bash +# ❌ WRONG: Using non-existent fields +sf data query --query "SELECT Id, Name, User__c FROM ADM_Scrum_Team_Member__c WHERE User__c = '005xx'" --target-org gus +# Error: No such column 'User__c' on entity 'ADM_Scrum_Team_Member__c' + +# ❌ WRONG: Using LIKE on an ID field +sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus +# Error: invalid operator on id field + +# ❌ WRONG: Looking for email field that doesn't exist +sf data query --query "SELECT Id, Name, Email__c FROM ADM_Scrum_Team_Member__c LIMIT 3" --target-org gus +# Error: No such column 'Email__c' on entity 'ADM_Scrum_Team_Member__c' +``` + +**CORRECT APPROACH** (following mandatory field verification): +```bash +# STEP 1: ALWAYS describe the object first +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.custom == true) | {name: .name, type: .type, referenceTo: .referenceTo}' + +# STEP 2: Verify discovered field names (from describe above): +# - Member_Name__c (User ID lookup - use = not LIKE) +# - Scrum_Team__c (Team ID lookup) +# - Scrum_Team_Name__c (formula field with team name) +# - Internal_Email__c (email field, NOT Email__c) +# - Role__c (member's role) + +# STEP 3: Query using verified field names +USER_ID="005EE000001JW5FYAW" + +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" + +# Get just team names as a list +sf data query \ + --query "SELECT Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +**Error Explanations**: +- **No `User__c` field**: The correct field is `Member_Name__c` (found via describe) +- **No `Email__c` field**: The correct field is `Internal_Email__c` (found via describe) +- **Cannot use LIKE on `Member_Name__c`**: It's a User ID lookup field (reference type), not a text field +- **`Name` field is auto-numbered**: The Name field is STM-###### (auto-number), not the member's name + +### Pattern 3: Finding Record IDs **Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) @@ -353,7 +430,7 @@ USER_ID=$(sf data query \ echo "User ID: $USER_ID" ``` -### Pattern 3: Querying Work Items +### Pattern 4: Querying Work Items **Use case**: Find work items by various criteria @@ -439,7 +516,7 @@ sf data query \ **Note on filtering null values**: Due to shell escaping issues with `!=` (the `!` triggers history expansion), it's best to query all records and filter with jq using `select(.Epic__r)` to check for non-null relationships. -### Pattern 4: Complex Queries with Aggregation +### Pattern 5: Complex Queries with Aggregation **Use case**: Get counts, sums, and grouped data @@ -467,7 +544,7 @@ sf data query \ --target-org "$DEFAULT_ORG" ``` -### Pattern 4: Querying with Date Filters +### Pattern 6: Querying with Date Filters **Use case**: Find records by date ranges @@ -499,7 +576,7 @@ sf data query \ --target-org "$DEFAULT_ORG" ``` -### Pattern 5: Using Tooling API +### Pattern 7: Using Tooling API **Use case**: Query metadata objects @@ -739,7 +816,43 @@ if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then fi ``` -### Critical Violation #3: Assuming Fields Exist Without Verification +### Critical Violation #3: Using LIKE on ID/Reference Fields + +```bash +# ❌ NEVER: Use LIKE operator on reference/ID fields +sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus +# Error: invalid operator on id field + +# ✅ CORRECT: Reference fields require exact match with = operator +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# STEP 1: Find the User ID first +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Name LIKE '%Polillo%' LIMIT 1" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# STEP 2: Use the ID with = operator (NOT LIKE) +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Role__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +**Why this happens**: +- Reference fields (fields ending in __c that link to other objects) store IDs, not text +- LIKE is only valid for text fields (string, textarea) +- You must query the referenced object first to get the ID, then use = for exact match +- Check field type with `sf sobject describe` - if `type: "reference"`, use = not LIKE + +### Critical Violation #4: Assuming Fields Exist Without Verification **NOTE**: This is another example of Violation #1. See above for the full explanation. @@ -801,4 +914,4 @@ sf data query \ --- **Last Updated**: 2025-12-03 -**Format Version**: 1.1 (Atomic - Enhanced field verification requirements) +**Format Version**: 1.2 (Atomic - Added ADM_Scrum_Team_Member__c field details and LIKE operator on reference fields anti-pattern) From 1bf5128fc5a049cfa8acf4d5053283f7ddb87dc0 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:21:17 -0300 Subject: [PATCH 17/21] Split sf-soql-queries.md into focused skill files (basics, advanced, troubleshooting) for better organization and faster loading --- skills/salesforce/INDEX.md | 81 +- skills/salesforce/sf-soql-advanced.md | 316 ++++++ skills/salesforce/sf-soql-basics.md | 426 ++++++++ skills/salesforce/sf-soql-queries.md | 984 +++---------------- skills/salesforce/sf-soql-troubleshooting.md | 493 ++++++++++ 5 files changed, 1450 insertions(+), 850 deletions(-) create mode 100644 skills/salesforce/sf-soql-advanced.md create mode 100644 skills/salesforce/sf-soql-basics.md create mode 100644 skills/salesforce/sf-soql-troubleshooting.md diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index b6faf0e..7d2105f 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -4,7 +4,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ## Category Overview -**Total Skills**: 7 +**Total Skills**: 10 **Focus**: Salesforce CLI, Agile Accelerator (GUS), SOQL, Record Management, Chatter **Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration, automation **Default Org**: Examples dynamically detect your default org using `sf config get target-org` - set it with `sf config set target-org=` @@ -25,17 +25,62 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator --- -### sf-soql-queries.md -**Description**: Query Salesforce data using SOQL and sf CLI -**Lines**: ~200 +### sf-soql-queries.md (NAVIGATION FILE) +**Description**: Navigation guide to SOQL query skills (basics, advanced, troubleshooting) +**Lines**: ~230 +**Use When**: +- Deciding which SOQL skill file to use +- Understanding the SOQL skill organization +- Quick reference for field names and critical rules + +**Key Concepts**: Skill navigation, quick start guides, common workflows + +**⚠️ NOTE**: This is a navigation file. Use the specialized skill files below for actual queries: +- `sf-soql-basics.md` - Core concepts and basic queries +- `sf-soql-advanced.md` - Complex queries and aggregation +- `sf-soql-troubleshooting.md` - Error handling and anti-patterns + +--- + +### sf-soql-basics.md +**Description**: Basic SOQL queries, field discovery, and fundamental patterns for Salesforce +**Lines**: ~350 +**Use When**: +- Starting with SOQL queries (learn basics first) +- Querying simple data (users, work items by ID, basic lookups) +- Learning field verification (mandatory describe workflow) +- Looking up team memberships +- Finding record IDs + +**Key Concepts**: MANDATORY field discovery, basic SOQL syntax, relationship names, verified field names, output formats + +--- + +### sf-soql-advanced.md +**Description**: Advanced SOQL queries for GUS - work items, aggregation, date filters, and tooling API +**Lines**: ~350 +**Use When**: +- Querying work items by various criteria (assignee, sprint, epic, status) +- Aggregating data (counts, sums, grouped results) +- Filtering by dates (date ranges, relative dates) +- Querying metadata (Apex classes, triggers using Tooling API) +- Complex multi-condition queries + +**Key Concepts**: Work item queries, aggregation (COUNT, SUM), date filters, tooling API + +--- + +### sf-soql-troubleshooting.md +**Description**: Common SOQL errors, anti-patterns, and troubleshooting for Salesforce queries +**Lines**: ~450 **Use When**: -- Querying Salesforce data using SOQL -- Retrieving work items, users, or other records -- Finding record IDs for operations -- Exporting data for analysis -- Building reports or dashboards +- Debugging SOQL query errors (INVALID_FIELD, relationship errors, operator errors) +- Understanding common mistakes (why queries fail and how to fix them) +- Learning anti-patterns (what NOT to do in SOQL queries) +- Troubleshooting field verification (field doesn't exist errors) +- Fixing relationship queries (wrong relationship name errors) -**Key Concepts**: SOQL syntax, output formats, finding IDs, date filters, aggregation +**Key Concepts**: Critical violations, common field errors, query performance, troubleshooting workflow --- @@ -232,9 +277,12 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator - Need user info → `sf-org-auth.md` **Querying Data**: -- Finding records → `sf-soql-queries.md` -- Complex queries → `sf-soql-queries.md` -- Getting IDs → `sf-soql-queries.md` +- New to SOQL → `sf-soql-basics.md` +- Finding records → `sf-soql-basics.md` +- Getting IDs → `sf-soql-basics.md` +- Complex queries → `sf-soql-advanced.md` +- Query errors → `sf-soql-troubleshooting.md` +- Not sure which → `sf-soql-queries.md` (navigation) **Single Records**: - Create/update 1-5 records → `sf-record-operations.md` @@ -338,7 +386,10 @@ All skills are available in the `skills/salesforce/` directory: ```bash # Load specific skill cat ~/.claude/skills/salesforce/sf-org-auth.md -cat ~/.claude/skills/salesforce/sf-soql-queries.md +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Navigation file +cat ~/.claude/skills/salesforce/sf-soql-basics.md # Basic SOQL queries +cat ~/.claude/skills/salesforce/sf-soql-advanced.md # Advanced SOQL queries +cat ~/.claude/skills/salesforce/sf-soql-troubleshooting.md # SOQL error handling cat ~/.claude/skills/salesforce/sf-record-operations.md cat ~/.claude/skills/salesforce/sf-work-items.md cat ~/.claude/skills/salesforce/sf-chatter.md @@ -346,7 +397,7 @@ cat ~/.claude/skills/salesforce/sf-bulk-operations.md cat ~/.claude/skills/salesforce/sf-automation.md ``` -**Pro tip**: Start with `sf-org-auth.md` to authenticate, then use `sf-soql-queries.md` to find IDs, then create/update records with the appropriate skill. +**Pro tip**: Start with `sf-org-auth.md` to authenticate, then use `sf-soql-basics.md` to learn queries and find IDs, then create/update records with the appropriate skill. --- diff --git a/skills/salesforce/sf-soql-advanced.md b/skills/salesforce/sf-soql-advanced.md new file mode 100644 index 0000000..485171c --- /dev/null +++ b/skills/salesforce/sf-soql-advanced.md @@ -0,0 +1,316 @@ +--- +name: salesforce-soql-advanced +description: Advanced SOQL queries for GUS - work items, aggregation, date filters, and tooling API. For complex queries beyond basic lookups. +keywords: salesforce, gus, soql, advanced, work items, aggregation, date filters, tooling api, epic queries, sprint queries, complex queries +--- + +# Salesforce SOQL Advanced Queries + +**Scope**: Advanced SOQL patterns for work items, aggregation, date filtering, and tooling API +**Lines**: ~350 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Querying work items by various criteria** - Assignee, sprint, epic, status +- **Aggregating data** - Counts, sums, grouped results +- **Filtering by dates** - Date ranges, relative dates (THIS_WEEK, LAST_N_DAYS) +- **Querying metadata** - Apex classes, triggers using Tooling API +- **Complex multi-condition queries** - Multiple filters and relationships + +**Prerequisites**: You should understand basic SOQL from `sf-soql-basics.md` first. + +**⚠️ CRITICAL**: Always run `sf sobject describe` before querying - see `sf-soql-basics.md` for field verification workflow. + +--- + +## Advanced Patterns + +### Pattern 1: Querying Work Items + +**Use case**: Find work items by various criteria (assignee, sprint, epic, status) + +```bash +# Get default org and current user's work items dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c != 'Closed' + ORDER BY Priority__c, CreatedDate DESC" \ + --target-org "$DEFAULT_ORG" + +# Query by work item name (WI number) +sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --target-org "$DEFAULT_ORG" + +# Query by sprint +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Assignee__r.Name, Story_Points__c + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + ORDER BY Status__c, Priority__c" \ + --target-org "$DEFAULT_ORG" + +# Query by epic +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Sprint__r.Name + FROM ADM_Work__c + WHERE Epic__r.Name LIKE '%Q1 Features%' + AND Status__c NOT IN ('Fixed', 'Closed')" \ + --target-org "$DEFAULT_ORG" + +# Query by type and priority +sf data query \ + --query "SELECT Name, Subject__c, Assignee__r.Name, Sprint__r.Name + FROM ADM_Work__c + WHERE Type__c = 'Bug' + AND Priority__c = 'P1' + AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ + --target-org "$DEFAULT_ORG" + +# Query user's epics +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +sf data query \ + --query "SELECT Id, Name, Description__c, Health__c, Priority__c, Owner.Name, LastModifiedDate + FROM ADM_Epic__c + WHERE Owner.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC" \ + --target-org "$DEFAULT_ORG" + +# Query work items with epics (filter non-null with jq) +# IMPORTANT: Avoid != in queries due to shell escaping - filter with jq instead +sf data query \ + --query "SELECT Id, Name, Subject__c, Epic__c, Epic__r.Name, Epic__r.Id + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC + LIMIT 50" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name) - \(.Name): \(.Subject__c)"' | sort -u +``` + +**Note on filtering null values**: Due to shell escaping issues with `!=` (the `!` triggers history expansion), it's best to query all records and filter with jq using `select(.Epic__r)` to check for non-null relationships. + +### Pattern 2: Complex Queries with Aggregation + +**Use case**: Get counts, sums, and grouped data + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Count work items by status +sf data query \ + --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ + --target-org "$DEFAULT_ORG" + +# Sum story points by sprint (filter null sprints with jq) +sf data query \ + --query "SELECT Sprint__r.Name, SUM(Story_Points__c) total_points + FROM ADM_Work__c + GROUP BY Sprint__r.Name" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Sprint__r) | "\(.Sprint__r.Name): \(.total_points) points"' + +# Count by assignee +sf data query \ + --query "SELECT Assignee__r.Name, COUNT(Id) work_count + FROM ADM_Work__c + WHERE Status__c IN ('New', 'In Progress') + GROUP BY Assignee__r.Name + ORDER BY COUNT(Id) DESC" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 3: Querying with Date Filters + +**Use case**: Find records by date ranges (relative and absolute) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Work items created this week +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate FROM ADM_Work__c WHERE CreatedDate = THIS_WEEK" \ + --target-org "$DEFAULT_ORG" + +# Work items updated in last 7 days +sf data query \ + --query "SELECT Name, Subject__c, LastModifiedDate FROM ADM_Work__c WHERE LastModifiedDate = LAST_N_DAYS:7" \ + --target-org "$DEFAULT_ORG" + +# Work items created in date range (absolute dates) +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate + FROM ADM_Work__c + WHERE CreatedDate >= 2024-01-01T00:00:00Z + AND CreatedDate <= 2024-01-31T23:59:59Z" \ + --target-org "$DEFAULT_ORG" + +# Sprints active in date range +sf data query \ + --query "SELECT Name, Start_Date__c, End_Date__c + FROM ADM_Sprint__c + WHERE Start_Date__c <= 2024-12-03 + AND End_Date__c >= 2024-12-03" \ + --target-org "$DEFAULT_ORG" +``` + +**Date Literals Reference**: +``` +TODAY Current day +THIS_WEEK Current week +THIS_MONTH Current month +LAST_N_DAYS:n Last n days +NEXT_N_DAYS:n Next n days +LAST_WEEK Previous week +THIS_QUARTER Current quarter +``` + +### Pattern 4: Using Tooling API + +**Use case**: Query metadata objects (Apex classes, triggers, custom fields) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Query Apex classes +sf data query \ + --query "SELECT Name, ApiVersion, LengthWithoutComments FROM ApexClass" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" + +# Query Apex triggers +sf data query \ + --query "SELECT Name, TableEnumOrId, Status FROM ApexTrigger" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" + +# Query custom fields on a specific object +sf data query \ + --query "SELECT DeveloperName, DataType, TableEnumOrId FROM CustomField WHERE TableEnumOrId = 'ADM_Work__c'" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Best Practices for Advanced Queries + +**Advanced Query Guidelines**: +``` +✅ DO: Use aggregation (COUNT, SUM) instead of querying all records and counting in code +✅ DO: Use date literals (THIS_WEEK, LAST_N_DAYS:7) for relative date queries +✅ DO: Filter null values with jq when ! causes shell escaping issues +✅ DO: Use IN and NOT IN for multiple value matching +✅ DO: Use ORDER BY to sort results server-side +✅ DO: Add LIMIT to aggregation queries to avoid timeouts +✅ DO: Use relationship fields (Epic__r.Name) in WHERE clauses +``` + +**Common Mistakes**: +``` +❌ DON'T: Use != in double-quoted strings (shell escapes the !) +❌ DON'T: Query without LIMIT on large objects (can timeout) +❌ DON'T: Forget to filter null relationships - use jq select() +❌ DON'T: Hardcode date values - use date literals when possible +❌ DON'T: Use LIKE with % on both sides (performance issue) +``` + +--- + +## Advanced GUS Object Fields (Verified) + +**ADM_Epic__c (Epics)**: +``` +Id, Name, Description__c, Health__c (NOT Status__c!) +Priority__c, OwnerId (→User) +CreatedDate, LastModifiedDate +``` + +**ADM_Sprint__c (Sprints)**: +``` +Id, Name, Start_Date__c, End_Date__c +Scrum_Team__c (→ADM_Scrum_Team__c) +CreatedDate, LastModifiedDate +``` + +**ADM_Build__c (Builds)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**ADM_Product_Tag__c (Product Tags)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**IMPORTANT**: These are verified but may vary by org. Always run `sf sobject describe` to verify fields in YOUR org before querying. + +--- + +## Common SOQL Operators for Advanced Queries + +``` += Equal to +!= Not equal to (avoid in double quotes - shell escapes !) +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +LIKE Pattern match (use % for wildcard) +IN Match any value in list +NOT IN Don't match any value in list +``` + +--- + +## Related Skills + +- `sf-soql-basics.md` - Start here for field discovery and basic queries +- `sf-soql-troubleshooting.md` - Error handling and anti-patterns +- `sf-work-items.md` - Creating and managing work items +- `sf-bulk-operations.md` - Bulk data operations and exports +- `sf-org-auth.md` - Authentication and user info + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-basics.md b/skills/salesforce/sf-soql-basics.md new file mode 100644 index 0000000..46b779c --- /dev/null +++ b/skills/salesforce/sf-soql-basics.md @@ -0,0 +1,426 @@ +--- +name: salesforce-soql-basics +description: Basic SOQL queries, field discovery, and fundamental patterns for Salesforce. Start here for querying GUS work items, users, and basic data retrieval. +keywords: salesforce, gus, soql, query, basics, field verification, sf sobject describe, beginner, getting started, team lookup, user query +--- + +# Salesforce SOQL Basics + +**Scope**: Basic SOQL syntax, mandatory field discovery, and fundamental query patterns +**Lines**: ~350 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Starting with SOQL queries** - Learn the basics first +- **Querying simple data** - Users, work items by ID, basic lookups +- **Learning field verification** - Understand the mandatory describe workflow +- **Looking up team memberships** - Find which teams a user belongs to +- **Finding record IDs** - Locate User IDs, Epic IDs, etc. + +**⚠️ CRITICAL REQUIREMENT**: Before writing ANY SOQL query, you MUST first run `sf sobject describe --sobject ` to verify field names. DO NOT skip this step. DO NOT assume field names exist, even if they seem obvious (like Department, Title, Team__c on User object). + +**For advanced queries**: See `sf-soql-advanced.md` +**For troubleshooting**: See `sf-soql-troubleshooting.md` + +--- + +## Core Concepts + +### Concept 1: MANDATORY Field Discovery Before Querying + +**CRITICAL RULE**: You MUST use `sf sobject describe` BEFORE writing any SOQL query with fields beyond Id and Name. This is NOT optional. + +**MANDATORY WORKFLOW**: +1. **FIRST**: Always run `sf sobject describe --sobject ` +2. **SECOND**: Verify the exact field names exist in the output +3. **THIRD**: Only then write your SOQL query using verified field names + +**NEVER query fields without verifying them first**, even if they seem obvious (Department, Title, Team__c, etc.). + +```bash +# STEP 1: ALWAYS describe the object FIRST +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" + +# STEP 2: Search for specific fields (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# STEP 3: Find relationship fields +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# STEP 4: Only after verification, write your query using confirmed field names +``` + +**Why this matters**: Guessing field names leads to errors like: +- `No such column 'Team__c' on entity 'User'` (field doesn't exist) +- `No such column 'Department' on entity 'User'` (field may not exist) +- Wrong field type or reference target +- Missing junction objects for many-to-many relationships + +**Common fields that DON'T exist**: +- User.Team__c (use ADM_Scrum_Team_Member__c junction object instead) +- User.Department (may not exist in all orgs) +- User.Division (may not exist in all orgs) + +### Concept 2: Basic SOQL Syntax + +**Query Structure**: +```sql +SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n +``` + +**Field Selection**: +- Standard fields: `Id, Name, CreatedDate` +- Custom fields: Use `__c` suffix (e.g., `Status__c, Subject__c`) +- Related fields: Use `__r` for relationships (e.g., `Assignee__r.Email`) + +```bash +# Get default org (add this at the start of scripts) +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Basic query +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 10" \ + --target-org "$DEFAULT_ORG" + +# Query with conditions +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Status__c = 'New'" \ + --target-org "$DEFAULT_ORG" + +# Query with related fields +sf data query \ + --query "SELECT Id, Subject__c, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Status__c = 'In Progress'" \ + --target-org "$DEFAULT_ORG" + +# Query with ordering and limit +sf data query \ + --query "SELECT Id, Name, CreatedDate FROM ADM_Work__c ORDER BY CreatedDate DESC LIMIT 20" \ + --target-org "$DEFAULT_ORG" +``` + +### Concept 3: Relationship Names - CRITICAL RULES + +**CRITICAL**: Relationship names (the `__r` suffix) are DIFFERENT from object names. NEVER assume the relationship name matches the object name. + +**Common Mistake**: +```sql +-- ❌ WRONG: Using object name as relationship +SELECT ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c + +-- ✅ CORRECT: Using actual relationship name from describe +SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c +``` + +**How to get the correct relationship name**: +```bash +# ALWAYS use sf sobject describe to get the relationship name +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r","referenceTo":["ADM_Scrum_Team__c"]} +``` + +**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. + +### Concept 4: Verified Field Names for Common GUS Objects + +**IMPORTANT**: These are VERIFIED via `sf sobject describe` but you should still verify them in YOUR org before querying. + +**ADM_Work__c (Work Items)**: +``` +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) +Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) +Product_Tag__c (→ADM_Product_Tag__c), Description__c +CreatedDate, LastModifiedDate +``` + +**ADM_Scrum_Team_Member__c (Team Membership - Junction Object)**: +``` +Id, Name (auto-number: STM-######) +Member_Name__c (→User - ID field, use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field with team name) +Internal_Email__c (member's email address) +Role__c (member's role on team) +Allocation__c, Department__c, Functional_Area__c +Active__c (boolean indicating active membership) +CreatedDate, LastModifiedDate + +CRITICAL NOTES: +- Member_Name__c is a User ID lookup field - use = for exact match, NOT LIKE +- Name is auto-numbered (STM-######), not searchable by member name +- Use Internal_Email__c for email, NOT Email__c (doesn't exist) +- No User__c field exists - use Member_Name__c instead +``` + +**User (Standard Object)**: +``` +Id, Name, Email, Username, IsActive, ProfileId +Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object +``` + +**Related Field Notation (VERIFIED)**: +``` +# From ADM_Work__c +Assignee__r.Name # User name (field: Assignee__c → User) +Assignee__r.Email # User email (field: Assignee__c → User) +Sprint__r.Name # Sprint name (field: Sprint__c → ADM_Sprint__c) +Epic__r.Name # Epic name (field: Epic__c → ADM_Epic__c) + +# From ADM_Scrum_Team_Member__c +Scrum_Team__r.Name # Team name (field: Scrum_Team__c → ADM_Scrum_Team__c) + # NOTE: Relationship is Scrum_Team__r, NOT ADM_Scrum_Team__r! + +# CRITICAL: Relationship names come from the relationshipName field in sf sobject describe, +# NOT from the object name! Always verify with describe before using. +``` + +### Concept 5: Output Formats + +**Available Formats**: +- `human` - Table format (default) +- `json` - JSON output for scripting +- `csv` - CSV format for spreadsheets + +```bash +# Human-readable table (default) +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 5" \ + --target-org "$DEFAULT_ORG" + +# JSON output for scripting +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 5" \ + --target-org "$DEFAULT_ORG" \ + --result-format json + +# CSV output for Excel +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 100" \ + --target-org "$DEFAULT_ORG" \ + --result-format csv > work_items.csv +``` + +--- + +## Basic Patterns + +### Pattern 1: Discovering Object Fields (MANDATORY FIRST STEP) + +**Use case**: Find available fields on an object before querying (avoids INVALID_FIELD errors) + +**MANDATORY**: You MUST ALWAYS use `sf sobject describe` as the FIRST STEP before writing ANY query. This is NOT optional, even for fields that seem obvious. + +**WORKFLOW - FOLLOW THESE STEPS IN ORDER**: + +**STEP 1: Describe the object to see available fields** +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# MANDATORY: Describe the object FIRST +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" +``` + +**STEP 2: Verify the specific fields you want to query exist** +```bash +# Search for specific field names (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# If grep returns nothing, the field doesn't exist - find the junction object instead +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, label: .label, relationshipName: .relationshipName}' +``` + +**STEP 3: Extract verified field names programmatically (RECOMMENDED)** +```bash +# Get all custom fields (fields ending in __c) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.name | endswith("__c")) | {name: .name, label: .label, type: .type}' + +# Find relationship fields (__r notation) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' +``` + +**STEP 4: Only after verification, write your query** +```bash +# Example: Finding team membership fields +# After discovering that User doesn't have Team__c, we found ADM_Scrum_Team_Member__c has Member_Name__c +USER_ID="005EE000001JW5FYAW" + +# Query teams through the verified junction object +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +**Benefits**: +- Avoids INVALID_FIELD errors from guessing field names +- Discovers correct relationship field names (__r notation) +- Finds junction objects for many-to-many relationships +- Identifies custom vs standard fields + +### Pattern 2: Querying Team Memberships (Common Example) + +**Use case**: Find which Scrum teams a user belongs to + +**COMMON ERRORS** (see sf-soql-troubleshooting.md for details): +- Using non-existent `User__c` field (correct field is `Member_Name__c`) +- Using `LIKE` on ID fields (must use `=` for exact match) +- Looking for `Email__c` field (correct field is `Internal_Email__c`) + +**CORRECT APPROACH** (following mandatory field verification): +```bash +# STEP 1: ALWAYS describe the object first +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.custom == true) | {name: .name, type: .type, referenceTo: .referenceTo}' + +# STEP 2: Query using verified field names +USER_ID="005EE000001JW5FYAW" + +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" + +# Get just team names as a list +sf data query \ + --query "SELECT Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +### Pattern 3: Finding Record IDs + +**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Find user ID by name +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ + --target-org "$DEFAULT_ORG" + +# Find user ID by email (use dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Email = '${USER_EMAIL}'" \ + --target-org "$DEFAULT_ORG" + +# Get ID with jq for scripting +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +echo "User ID: $USER_ID" +``` + +--- + +## Best Practices + +**Essential Practices (IN ORDER OF IMPORTANCE)**: +``` +✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY, NOT OPTIONAL +✅ DO: Verify field names exist in describe output before writing SELECT statements +✅ DO: Get relationship names from the relationshipName field in describe (NOT from object names) +✅ DO: Use --result-format json for scripting +✅ DO: Use LIMIT to avoid timeouts on large datasets +✅ DO: Validate query results before using extracted values +``` + +**Common Mistakes to Avoid (CRITICAL)**: +``` +❌ DON'T: EVER query fields without running sf sobject describe first - THIS CAUSES MOST ERRORS +❌ DON'T: Assume relationship names match object names (use relationshipName from describe) +❌ DON'T: Use ADM_Scrum_Team__r when the actual relationship is Scrum_Team__r +❌ DON'T: Assume ANY field exists without verification (not even Department, Title, or Team__c) +❌ DON'T: Guess field names without verifying (ALWAYS use sf sobject describe) +❌ DON'T: Use LIKE on ID/reference fields (use = for exact match) +``` + +--- + +## Quick Reference + +### Common SOQL Operators +``` += Equal to +!= Not equal to (avoid in double quotes - shell escapes !) +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +LIKE Pattern match (use % for wildcard, NOT for ID fields) +IN Match any value in list +NOT IN Don't match any value in list +``` + +### Common Fields (Verified) + +**Quick Reference - Most Used Fields**: +``` +ADM_Work__c: Subject__c, Status__c, Priority__c, Type__c, Assignee__c, Sprint__c, Epic__c +ADM_Scrum_Team_Member__c: Member_Name__c, Scrum_Team__c, Internal_Email__c, Role__c +User: Name, Email, Username (no Team__c - use ADM_Scrum_Team_Member__c) +``` + +**IMPORTANT**: When uncertain about fields, always use `sf sobject describe --sobject ` to verify field existence and names before querying. + +--- + +## Related Skills + +- `sf-soql-advanced.md` - Complex queries, aggregation, date filters, tooling API +- `sf-soql-troubleshooting.md` - Anti-patterns, common errors, and solutions +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index 5ed0f0b..f0f83b1 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -4,914 +4,228 @@ description: Query Salesforce data using SOQL and sf CLI. Use for GUS work items keywords: salesforce, gus, soql, query, work items, epic, sprint, team, agile accelerator, ADM_Work__c, ADM_Epic__c, ADM_Sprint__c, ADM_Scrum_Team_Member__c, user, field verification --- -# Salesforce SOQL Queries +# Salesforce SOQL Queries (Navigation) -**Scope**: SOQL query syntax, data retrieval, and result formatting -**Lines**: ~920 +**Scope**: Navigation guide to SOQL skill files **Last Updated**: 2025-12-03 -**Format Version**: 1.2 (Atomic - Added ADM_Scrum_Team_Member__c field details and LIKE operator on reference fields anti-pattern) +**Format Version**: 2.0 (Split into focused skill files) --- -## When to Use This Skill +## Overview -Activate this skill when: -- Querying Salesforce data using SOQL -- Retrieving work items, users, or other records -- Finding record IDs for operations -- Exporting data for analysis -- Building reports or dashboards -- Joining related objects +This skill has been split into three focused files for better organization and faster loading: -**⚠️ CRITICAL REQUIREMENT**: Before writing ANY SOQL query, you MUST first run `sf sobject describe --sobject ` to verify field names. DO NOT skip this step. DO NOT assume field names exist, even if they seem obvious (like Department, Title, Team__c on User object). See Concept 1 below. +1. **sf-soql-basics.md** - Start here for core concepts and basic queries +2. **sf-soql-advanced.md** - Complex queries, aggregation, and advanced patterns +3. **sf-soql-troubleshooting.md** - Error handling, anti-patterns, and debugging --- -## Core Concepts +## Which Skill File Should I Use? -### Concept 1: MANDATORY Field Discovery Before Querying +### Use sf-soql-basics.md When: +- **Starting with SOQL queries** - Learn the basics first +- **Querying simple data** - Users, work items by ID, basic lookups +- **Learning field verification** - Understand the mandatory describe workflow +- **Looking up team memberships** - Find which teams a user belongs to +- **Finding record IDs** - Locate User IDs, Epic IDs, etc. -**CRITICAL RULE**: You MUST use `sf sobject describe` BEFORE writing any SOQL query with fields beyond Id and Name. This is NOT optional. +**Key Concepts**: MANDATORY field discovery, basic SOQL syntax, relationship names, verified field names, output formats -**MANDATORY WORKFLOW**: -1. **FIRST**: Always run `sf sobject describe --sobject ` -2. **SECOND**: Verify the exact field names exist in the output -3. **THIRD**: Only then write your SOQL query using verified field names - -**NEVER query fields without verifying them first**, even if they seem obvious (Department, Title, Team__c, etc.). - -```bash -# STEP 1: ALWAYS describe the object FIRST -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" - -# STEP 2: Search for specific fields (e.g., team-related) -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team - -# STEP 3: Find relationship fields -sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' - -# STEP 4: Only after verification, write your query using confirmed field names -``` - -**Why this matters**: Guessing field names leads to errors like: -- `No such column 'Team__c' on entity 'User'` (field doesn't exist) -- `No such column 'Department' on entity 'User'` (field may not exist) -- Wrong field type or reference target -- Missing junction objects for many-to-many relationships - -**Common fields that DON'T exist**: -- User.Team__c (use ADM_Scrum_Team_Member__c junction object instead) -- User.Department (may not exist in all orgs) -- User.Division (may not exist in all orgs) - -See **Pattern 1: Discovering Object Fields** below for detailed examples. - -### Concept 2: Basic SOQL Syntax - -**Query Structure**: -```sql -SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n -``` - -**Field Selection**: -- Standard fields: `Id, Name, CreatedDate` -- Custom fields: Use `__c` suffix (e.g., `Status__c, Subject__c`) -- Related fields: Use `__r` for relationships (e.g., `Assignee__r.Email`) - -```bash -# Get default org (add this at the start of scripts) -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -# Basic query -sf data query \ - --query "SELECT Id, Name FROM ADM_Work__c LIMIT 10" \ - --target-org "$DEFAULT_ORG" - -# Query with conditions -sf data query \ - --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Status__c = 'New'" \ - --target-org "$DEFAULT_ORG" - -# Query with related fields -sf data query \ - --query "SELECT Id, Subject__c, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Status__c = 'In Progress'" \ - --target-org "$DEFAULT_ORG" - -# Query with ordering and limit -sf data query \ - --query "SELECT Id, Name, CreatedDate FROM ADM_Work__c ORDER BY CreatedDate DESC LIMIT 20" \ - --target-org "$DEFAULT_ORG" -``` - -### Concept 2: Relationship Names - CRITICAL RULES - -**CRITICAL**: Relationship names (the `__r` suffix) are DIFFERENT from object names. NEVER assume the relationship name matches the object name. - -**Common Mistake**: -```sql --- ❌ WRONG: Using object name as relationship -SELECT ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c - --- ✅ CORRECT: Using actual relationship name from describe -SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c -``` - -**How to get the correct relationship name**: -```bash -# ALWAYS use sf sobject describe to get the relationship name -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ - jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' - -# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r","referenceTo":["ADM_Scrum_Team__c"]} -``` - -**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. - -### Concept 3: Verified Field Names - -**Common GUS Objects** (verified via `sf sobject describe`): - -**ADM_Work__c (Work Items)**: -``` -Id, Name, Subject__c, Status__c, Priority__c, Type__c -Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) -Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) -Product_Tag__c (→ADM_Product_Tag__c), Description__c -CreatedDate, LastModifiedDate -``` - -**ADM_Epic__c (Epics)**: -``` -Id, Name, Description__c, Health__c (not Status__c!) -Priority__c, OwnerId (→User) -CreatedDate, LastModifiedDate -``` - -**ADM_Sprint__c (Sprints)**: -``` -Id, Name, Start_Date__c, End_Date__c -Scrum_Team__c (→ADM_Scrum_Team__c) -CreatedDate, LastModifiedDate -``` - -**ADM_Build__c (Builds)**: -``` -Id, Name, CreatedDate, LastModifiedDate -``` - -**ADM_Product_Tag__c (Product Tags)**: -``` -Id, Name, CreatedDate, LastModifiedDate -``` - -**ADM_Scrum_Team_Member__c (Team Membership - Junction Object)**: -``` -Id, Name (auto-number: STM-######) -Member_Name__c (→User - ID field, use = not LIKE) -Scrum_Team__c (→ADM_Scrum_Team__c) -Scrum_Team_Name__c (formula field with team name) -Internal_Email__c (member's email address) -Role__c (member's role on team) -Allocation__c, Department__c, Functional_Area__c -Active__c (boolean indicating active membership) -CreatedDate, LastModifiedDate - -CRITICAL NOTES: -- Member_Name__c is a User ID lookup field - use = for exact match, NOT LIKE -- Name is auto-numbered (STM-######), not searchable by member name -- Use Internal_Email__c for email, NOT Email__c (doesn't exist) -- No User__c field exists - use Member_Name__c instead -``` - -**User (Standard Object)**: -``` -Id, Name, Email, Username, IsActive, ProfileId -Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object -``` - -**FeedItem (Chatter Posts)**: -``` -Id, ParentId, Body, Type, LinkUrl, Visibility -CreatedDate, CreatedById -``` - -**FeedComment (Chatter Comments)**: -``` -Id, FeedItemId, CommentBody, CreatedDate, CreatedById -``` - -**Related Field Notation (VERIFIED via sf sobject describe)**: -``` -# From ADM_Work__c -Assignee__r.Name # User name (field: Assignee__c → User) -Assignee__r.Email # User email (field: Assignee__c → User) -Sprint__r.Name # Sprint name (field: Sprint__c → ADM_Sprint__c) -Epic__r.Name # Epic name (field: Epic__c → ADM_Epic__c) -Found_in_Build__r.Name # Build name (field: Found_in_Build__c → ADM_Build__c) - -# From ADM_Scrum_Team_Member__c -Scrum_Team__r.Name # Team name (field: Scrum_Team__c → ADM_Scrum_Team__c) - # NOTE: Relationship is Scrum_Team__r, NOT ADM_Scrum_Team__r! - -# CRITICAL: Relationship names come from the relationshipName field in sf sobject describe, -# NOT from the object name! Always verify with describe before using. -``` - -### Concept 4: Output Formats - -**Available Formats**: -- `human` - Table format (default) -- `json` - JSON output for scripting -- `csv` - CSV format for spreadsheets - -```bash -# Human-readable table (default) -sf data query \ - --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 5" \ - --target-org "$DEFAULT_ORG" - -# JSON output for scripting -sf data query \ - --query "SELECT Id, Name FROM ADM_Work__c LIMIT 5" \ - --target-org "$DEFAULT_ORG" \ - --result-format json - -# CSV output for Excel -sf data query \ - --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 100" \ - --target-org "$DEFAULT_ORG" \ - --result-format csv > work_items.csv - -# Query from file -echo "SELECT Id, Name FROM ADM_Work__c LIMIT 10" > query.soql -sf data query \ - --file query.soql \ - --target-org "$DEFAULT_ORG" -``` +**Example Patterns**: +- Discovering object fields (MANDATORY FIRST STEP) +- Querying team memberships +- Finding record IDs +- Basic work item queries --- -## Patterns +### Use sf-soql-advanced.md When: +- **Querying work items by various criteria** - Assignee, sprint, epic, status +- **Aggregating data** - Counts, sums, grouped results +- **Filtering by dates** - Date ranges, relative dates (THIS_WEEK, LAST_N_DAYS) +- **Querying metadata** - Apex classes, triggers using Tooling API +- **Complex multi-condition queries** - Multiple filters and relationships -### Pattern 1: Discovering Object Fields (MANDATORY FIRST STEP) +**Key Concepts**: Work item queries, aggregation (COUNT, SUM), date filters, tooling API -**Use case**: Find available fields on an object before querying (avoids INVALID_FIELD errors) +**Example Patterns**: +- Querying work items by assignee, sprint, epic, type +- Complex queries with aggregation (counts, sums, grouped data) +- Date filtering (relative and absolute) +- Using Tooling API for metadata -**MANDATORY**: You MUST ALWAYS use `sf sobject describe` as the FIRST STEP before writing ANY query. This is NOT optional, even for fields that seem obvious. - -**WORKFLOW - FOLLOW THESE STEPS IN ORDER**: +--- -**STEP 1: Describe the object to see available fields** -```bash -# Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +### Use sf-soql-troubleshooting.md When: +- **Debugging SOQL query errors** - INVALID_FIELD, relationship errors, operator errors +- **Understanding common mistakes** - Why queries fail and how to fix them +- **Learning anti-patterns** - What NOT to do in SOQL queries +- **Troubleshooting field verification** - Field doesn't exist errors +- **Fixing relationship queries** - Wrong relationship name errors -# MANDATORY: Describe the object FIRST -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" -``` +**Key Concepts**: Critical violations, common field errors, query performance, troubleshooting workflow -**STEP 2: Verify the specific fields you want to query exist** -```bash -# Search for specific field names (e.g., team-related) -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team +**Critical Violations Covered**: +1. Querying fields without verification (MOST COMMON) +2. Assuming relationship names match object names (VERY COMMON) +3. Using shell special characters (! in queries) +4. Using LIKE on ID/reference fields +5. Not validating query results -# If grep returns nothing, the field doesn't exist - find the junction object instead -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, label: .label, relationshipName: .relationshipName}' -``` +--- -**STEP 3: Extract verified field names programmatically (RECOMMENDED)** -```bash -# Get all custom fields (fields ending in __c) -sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.name | endswith("__c")) | {name: .name, label: .label, type: .type}' +## Quick Start Guide + +### I'm New to SOQL +**Start with**: [sf-soql-basics.md](sf-soql-basics.md) +- Read Concept 1 (MANDATORY field discovery) +- Read Concept 2 (Basic SOQL syntax) +- Read Concept 3 (Relationship names) +- Try Pattern 1 (Discovering object fields) + +### I Need to Query Work Items +**Start with**: [sf-soql-basics.md](sf-soql-basics.md) for simple queries, then [sf-soql-advanced.md](sf-soql-advanced.md) for complex filtering +- sf-soql-basics.md: Basic work item queries by ID or simple criteria +- sf-soql-advanced.md: Pattern 1 (Querying work items by assignee, sprint, epic) + +### I'm Getting Errors +**Start with**: [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) +- Critical Violation #1: Field doesn't exist errors +- Critical Violation #2: Relationship name errors +- Critical Violation #3: Shell escaping errors +- Critical Violation #4: LIKE on ID fields errors +- Common Field Errors section + +### I Need Aggregation or Complex Queries +**Start with**: [sf-soql-advanced.md](sf-soql-advanced.md) +- Pattern 2 (Complex queries with aggregation) +- Pattern 3 (Querying with date filters) +- Pattern 4 (Using Tooling API) -# Find relationship fields (__r notation) -sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' -``` +--- -**STEP 4: Only after verification, write your query** -```bash -# Example: Finding team membership fields -# After discovering that User doesn't have Team__c, we found ADM_Scrum_Team_Member__c has Member_Name__c -USER_ID="005EE000001JW5FYAW" - -# Query teams through the verified junction object -sf data query \ - --query "SELECT Id, Name, Scrum_Team__r.Name - FROM ADM_Scrum_Team_Member__c - WHERE Member_Name__c = '${USER_ID}'" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' -``` +## Common Workflows -**Benefits**: -- Avoids INVALID_FIELD errors from guessing field names -- Discovers correct relationship field names (__r notation) -- Finds junction objects for many-to-many relationships -- Identifies custom vs standard fields +### Workflow 1: Query User's Teams +1. **sf-soql-basics.md** - Concept 1: Run sf sobject describe for ADM_Scrum_Team_Member__c +2. **sf-soql-basics.md** - Pattern 2: Query team memberships +3. **sf-soql-troubleshooting.md** - If errors occur, check Critical Violations -### Pattern 2: Querying Team Memberships (Common Pitfall Example) +### Workflow 2: Find Work Items by Epic +1. **sf-soql-basics.md** - Pattern 3: Find Epic ID by name +2. **sf-soql-advanced.md** - Pattern 1: Query work items by epic +3. **sf-soql-troubleshooting.md** - If errors occur, check field verification -**Use case**: Find which Scrum teams a user belongs to +### Workflow 3: Sprint Reporting +1. **sf-soql-basics.md** - Pattern 3: Find Sprint ID +2. **sf-soql-advanced.md** - Pattern 2: Aggregate story points by status +3. **sf-soql-advanced.md** - Pattern 3: Filter by date range -**COMMON ERRORS** (see error explanations at bottom): -```bash -# ❌ WRONG: Using non-existent fields -sf data query --query "SELECT Id, Name, User__c FROM ADM_Scrum_Team_Member__c WHERE User__c = '005xx'" --target-org gus -# Error: No such column 'User__c' on entity 'ADM_Scrum_Team_Member__c' +--- -# ❌ WRONG: Using LIKE on an ID field -sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus -# Error: invalid operator on id field +## Critical Rules (Apply to All SOQL Queries) -# ❌ WRONG: Looking for email field that doesn't exist -sf data query --query "SELECT Id, Name, Email__c FROM ADM_Scrum_Team_Member__c LIMIT 3" --target-org gus -# Error: No such column 'Email__c' on entity 'ADM_Scrum_Team_Member__c' +**⚠️ MANDATORY FIELD VERIFICATION**: ``` +BEFORE writing ANY SOQL query, you MUST: +1. Run `sf sobject describe --sobject ` +2. Verify the exact field names exist in the output +3. Only then write your SOQL query using verified field names -**CORRECT APPROACH** (following mandatory field verification): -```bash -# STEP 1: ALWAYS describe the object first -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.custom == true) | {name: .name, type: .type, referenceTo: .referenceTo}' - -# STEP 2: Verify discovered field names (from describe above): -# - Member_Name__c (User ID lookup - use = not LIKE) -# - Scrum_Team__c (Team ID lookup) -# - Scrum_Team_Name__c (formula field with team name) -# - Internal_Email__c (email field, NOT Email__c) -# - Role__c (member's role) - -# STEP 3: Query using verified field names -USER_ID="005EE000001JW5FYAW" - -sf data query \ - --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c - FROM ADM_Scrum_Team_Member__c - WHERE Member_Name__c = '${USER_ID}'" \ - --target-org "$DEFAULT_ORG" - -# Get just team names as a list -sf data query \ - --query "SELECT Scrum_Team__r.Name - FROM ADM_Scrum_Team_Member__c - WHERE Member_Name__c = '${USER_ID}'" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +This is NOT optional. This is MANDATORY. ``` -**Error Explanations**: -- **No `User__c` field**: The correct field is `Member_Name__c` (found via describe) -- **No `Email__c` field**: The correct field is `Internal_Email__c` (found via describe) -- **Cannot use LIKE on `Member_Name__c`**: It's a User ID lookup field (reference type), not a text field -- **`Name` field is auto-numbered**: The Name field is STM-###### (auto-number), not the member's name - -### Pattern 3: Finding Record IDs - -**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) - -```bash -# Find user ID by name -sf data query \ - --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ - --target-org "$DEFAULT_ORG" - -# Find user ID by email (use dynamic user email) -USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') -sf data query \ - --query "SELECT Id, Name, Email FROM User WHERE Email = '${USER_EMAIL}'" \ - --target-org "$DEFAULT_ORG" - -# Find Epic by name -sf data query \ - --query "SELECT Id, Name FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ - --target-org "$DEFAULT_ORG" - -# Find Sprint by name -sf data query \ - --query "SELECT Id, Name, Start_Date__c, End_Date__c FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ - --target-org "$DEFAULT_ORG" - -# Find Product Tag -sf data query \ - --query "SELECT Id, Name FROM ADM_Product_Tag__c WHERE Name LIKE '%Platform%'" \ - --target-org "$DEFAULT_ORG" - -# Get ID with jq for scripting (using dynamic user email) -USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') -USER_ID=$(sf data query \ - --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') - -echo "User ID: $USER_ID" +**⚠️ RELATIONSHIP NAMES**: ``` +Relationship names (the __r suffix) are DIFFERENT from object names. +NEVER assume the relationship name matches the object name. -### Pattern 4: Querying Work Items - -**Use case**: Find work items by various criteria - -```bash -# Get default org and current user's work items dynamically -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') - -sf data query \ - --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c - FROM ADM_Work__c - WHERE Assignee__r.Email = '${USER_EMAIL}' - AND Status__c != 'Closed' - ORDER BY Priority__c, CreatedDate DESC" \ - --target-org "$DEFAULT_ORG" - -# Query by work item name (WI number) -sf data query \ - --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ - --target-org "$DEFAULT_ORG" - -# Query by sprint -sf data query \ - --query "SELECT Name, Subject__c, Status__c, Assignee__r.Name, Story_Points__c - FROM ADM_Work__c - WHERE Sprint__r.Name = 'Sprint 42' - ORDER BY Status__c, Priority__c" \ - --target-org "$DEFAULT_ORG" - -# Query by epic -sf data query \ - --query "SELECT Name, Subject__c, Status__c, Sprint__r.Name - FROM ADM_Work__c - WHERE Epic__r.Name LIKE '%Q1 Features%' - AND Status__c NOT IN ('Fixed', 'Closed')" \ - --target-org "$DEFAULT_ORG" - -# Query by type and priority -sf data query \ - --query "SELECT Name, Subject__c, Assignee__r.Name, Sprint__r.Name - FROM ADM_Work__c - WHERE Type__c = 'Bug' - AND Priority__c = 'P1' - AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ - --target-org "$DEFAULT_ORG" - -# Query user's epics -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') - -sf data query \ - --query "SELECT Id, Name, Description__c, Health__c, Priority__c, Owner.Name, LastModifiedDate - FROM ADM_Epic__c - WHERE Owner.Email = '${USER_EMAIL}' - ORDER BY LastModifiedDate DESC" \ - --target-org "$DEFAULT_ORG" - -# Query work items with epics (filter non-null with jq) -# IMPORTANT: Avoid != in queries due to shell escaping - filter with jq instead -sf data query \ - --query "SELECT Id, Name, Subject__c, Epic__c, Epic__r.Name, Epic__r.Id - FROM ADM_Work__c - WHERE Assignee__r.Email = '${USER_EMAIL}' - ORDER BY LastModifiedDate DESC - LIMIT 50" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name) - \(.Name): \(.Subject__c)"' | sort -u -``` +Example: +- Field: Scrum_Team__c +- Object: ADM_Scrum_Team__c +- Relationship: Scrum_Team__r (NOT ADM_Scrum_Team__r!) -**Note on filtering null values**: Due to shell escaping issues with `!=` (the `!` triggers history expansion), it's best to query all records and filter with jq using `select(.Epic__r)` to check for non-null relationships. - -### Pattern 5: Complex Queries with Aggregation - -**Use case**: Get counts, sums, and grouped data - -```bash -# Count work items by status -sf data query \ - --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ - --target-org "$DEFAULT_ORG" - -# Sum story points by sprint (filter null sprints with jq) -sf data query \ - --query "SELECT Sprint__r.Name, SUM(Story_Points__c) total_points - FROM ADM_Work__c - GROUP BY Sprint__r.Name" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Sprint__r) | "\(.Sprint__r.Name): \(.total_points) points"' - -# Count by assignee -sf data query \ - --query "SELECT Assignee__r.Name, COUNT(Id) work_count - FROM ADM_Work__c - WHERE Status__c IN ('New', 'In Progress') - GROUP BY Assignee__r.Name - ORDER BY COUNT(Id) DESC" \ - --target-org "$DEFAULT_ORG" +ALWAYS use sf sobject describe to get the relationshipName field. ``` -### Pattern 6: Querying with Date Filters - -**Use case**: Find records by date ranges - -```bash -# Work items created this week -sf data query \ - --query "SELECT Name, Subject__c, CreatedDate FROM ADM_Work__c WHERE CreatedDate = THIS_WEEK" \ - --target-org "$DEFAULT_ORG" - -# Work items updated in last 7 days -sf data query \ - --query "SELECT Name, Subject__c, LastModifiedDate FROM ADM_Work__c WHERE LastModifiedDate = LAST_N_DAYS:7" \ - --target-org "$DEFAULT_ORG" - -# Work items created in date range -sf data query \ - --query "SELECT Name, Subject__c, CreatedDate - FROM ADM_Work__c - WHERE CreatedDate >= 2024-01-01T00:00:00Z - AND CreatedDate <= 2024-01-31T23:59:59Z" \ - --target-org "$DEFAULT_ORG" - -# Sprints active in date range -sf data query \ - --query "SELECT Name, Start_Date__c, End_Date__c - FROM ADM_Sprint__c - WHERE Start_Date__c <= 2024-12-03 - AND End_Date__c >= 2024-12-03" \ - --target-org "$DEFAULT_ORG" +**⚠️ COMMON MISTAKES**: ``` - -### Pattern 7: Using Tooling API - -**Use case**: Query metadata objects - -```bash -# Query Apex classes -sf data query \ - --query "SELECT Name, ApiVersion, LengthWithoutComments FROM ApexClass" \ - --use-tooling-api \ - --target-org "$DEFAULT_ORG" - -# Query Apex triggers -sf data query \ - --query "SELECT Name, TableEnumOrId, Status FROM ApexTrigger" \ - --use-tooling-api \ - --target-org "$DEFAULT_ORG" - -# Query custom fields -sf data query \ - --query "SELECT DeveloperName, DataType, TableEnumOrId FROM CustomField WHERE TableEnumOrId = 'ADM_Work__c'" \ - --use-tooling-api \ - --target-org "$DEFAULT_ORG" +❌ DON'T: Query fields without sf sobject describe first +❌ DON'T: Assume relationship names match object names +❌ DON'T: Use LIKE on ID/reference fields (use = instead) +❌ DON'T: Use != in double-quoted queries (shell escapes !) +❌ DON'T: Assume ANY field exists without verification ``` --- -## Quick Reference +## Verified Field Names (Quick Reference) -### Common SOQL Operators +**IMPORTANT**: These are verified but may vary by org. ALWAYS run `sf sobject describe` to verify fields in YOUR org before querying. +**ADM_Work__c (Work Items)**: ``` -= Equal to -!= Not equal to -< Less than -> Greater than -<= Less than or equal ->= Greater than or equal -LIKE Pattern match (use % for wildcard) -IN Match any value in list -NOT IN Don't match any value in list +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) +Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) +Product_Tag__c (→ADM_Product_Tag__c), Description__c ``` -### Date Literals - +**ADM_Scrum_Team_Member__c (Team Membership)**: ``` -TODAY Current day -THIS_WEEK Current week -THIS_MONTH Current month -LAST_N_DAYS:n Last n days -NEXT_N_DAYS:n Next n days -LAST_WEEK Previous week -THIS_QUARTER Current quarter +Id, Name (auto-number: STM-######) +Member_Name__c (→User - use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field) +Internal_Email__c (NOT Email__c!) +Role__c, Active__c ``` -### Common Fields (All Verified) - -See **Concept 2: Verified Field Names** above for complete field listings. - -**Quick Reference - Most Used Fields**: +**User (Standard Object)**: ``` -ADM_Work__c: Subject__c, Status__c, Priority__c, Type__c, Assignee__c, Sprint__c, Epic__c -ADM_Epic__c: Description__c, Health__c (NOT Status__c!), Priority__c, OwnerId -ADM_Sprint__c: Start_Date__c, End_Date__c, Scrum_Team__c -User: Name, Email (no Team__c - use ADM_Scrum_Team_Member__c) +Id, Name, Email, Username, IsActive +Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object ``` -**Related Field Notation**: +**Related Field Notation (VERIFIED)**: ``` Assignee__r.Name # User name Assignee__r.Email # User email Sprint__r.Name # Sprint name Epic__r.Name # Epic name -Scrum_Team__r.Name # Team name -Found_in_Build__r.Name # Build name -``` - -**IMPORTANT**: When uncertain about fields, always use `sf sobject describe --sobject ` to verify field existence and names before querying. - ---- - -## Best Practices - -**Essential Practices (IN ORDER OF IMPORTANCE):** -``` -✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY, NOT OPTIONAL -✅ DO: Verify field names exist in describe output before writing SELECT statements -✅ DO: Get relationship names from the relationshipName field in describe (NOT from object names) -✅ DO: Use --result-format json for scripting -✅ DO: Use LIMIT to avoid timeouts on large datasets -✅ DO: Query for IDs before creating related records -✅ DO: Use relationship queries (__r) instead of multiple queries -✅ DO: Validate query results before using extracted values -✅ DO: Use WHERE clauses to filter data server-side -✅ DO: Refer to Concepts 2-3 for verified field/relationship names (but verify them first!) -``` - -**Common Mistakes to Avoid (CRITICAL):** -``` -❌ DON'T: EVER query fields without running sf sobject describe first - THIS CAUSES MOST ERRORS -❌ DON'T: Assume relationship names match object names (use relationshipName from describe) -❌ DON'T: Use ADM_Scrum_Team__r when the actual relationship is Scrum_Team__r -❌ DON'T: Assume ANY field exists without verification (not even Department, Title, or Team__c) -❌ DON'T: Guess field names without verifying (ALWAYS use sf sobject describe) -❌ DON'T: Write queries based on field names from other orgs or documentation -❌ DON'T: Use != in double-quoted queries (shell escapes !) -❌ DON'T: Query without LIMIT (can timeout) -❌ DON'T: Use SELECT * (not supported in SOQL) -❌ DON'T: Assume queries will always return results -❌ DON'T: Forget __c suffix on custom fields -❌ DON'T: Skip validation of jq output (check for null) -❌ DON'T: Assume User has Team__c field (use junction object) +Scrum_Team__r.Name # Team name (NOT ADM_Scrum_Team__r!) ``` --- -## Anti-Patterns - -### Critical Violation #1: Querying Fields Without Verification (MOST COMMON ERROR) - -```bash -# ❌ NEVER: Query fields without first running sf sobject describe -# This is the #1 cause of INVALID_FIELD errors -sf data query --query "SELECT Id, Name, Username, Email, Department, Division, Title, Team__c FROM User WHERE Username = 'user@example.com'" --target-org gus -# Error: No such column 'Team__c' on entity 'User' -# Error: No such column 'Department' on entity 'User' (may not exist in all orgs) - -# ✅ CORRECT: ALWAYS describe the object FIRST -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -# STEP 1: Describe to see what fields actually exist -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i "team\|department\|division\|title" - -# STEP 2: If fields don't exist, find alternative approach (e.g., junction objects) -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.referenceTo[]? == "User")' - -# STEP 3: Query only with verified fields -sf data query \ - --query "SELECT Id, Name, Username, Email FROM User WHERE Username = 'user@example.com'" \ - --target-org "$DEFAULT_ORG" -``` - -**Why this is the #1 error**: -- Field names vary across Salesforce orgs and implementations -- Custom fields that exist in one org may not exist in another -- Assuming field names without verification WILL cause failures -- User.Team__c, User.Department, User.Division are NOT guaranteed to exist - -### Critical Violation #2: Assuming Relationship Names Match Object Names (VERY COMMON) - -```bash -# ❌ NEVER: Assume relationship name matches object name -sf data query --query "SELECT Id, Name, ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" --target-org gus -# Error: Didn't understand relationship 'ADM_Scrum_Team__r' in field path - -# ✅ CORRECT: Use sf sobject describe to get the actual relationship name -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -# STEP 1: Get the actual relationship name from describe -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ - jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName}' -# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r"} - -# STEP 2: Use the verified relationship name (Scrum_Team__r, NOT ADM_Scrum_Team__r) -sf data query \ - --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" \ - --target-org "$DEFAULT_ORG" -``` - -**Why this is a common error**: -- Relationship names are defined in the `relationshipName` field, NOT derived from object names -- Field `Scrum_Team__c` references object `ADM_Scrum_Team__c` but has relationship name `Scrum_Team__r` -- The `ADM_` prefix is NOT part of the relationship name -- ALWAYS use `sf sobject describe` to get the exact `relationshipName` value - -### Critical Violation #3: Using Shell Special Characters - -```bash -# ❌ NEVER: Use != in double-quoted strings (shell escapes the !) -sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --target-org gus -# Error: unexpected token: '\' - -# ✅ CORRECT: Filter null values using jq instead -sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ - --result-format json --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name)"' -``` - -**Why this happens**: In bash/zsh, `!` triggers history expansion even in double quotes, causing the shell to escape it as `\!`. SOQL doesn't recognize this escaped form. - -**Solutions**: -1. **Query all records and filter with jq** (recommended for complex conditions) -2. Use `IS NOT NULL` syntax if your SOQL version supports it -3. Use relationship fields like `Epic__r.Id` and check with `select(.Epic__r)` in jq - -```bash -# ❌ NEVER: Query without checking results -WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id') -# If no results, this returns "null" and breaks downstream operations - -# ✅ CORRECT: Validate query results -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -QUERY_RESULT=$(sf data query \ - --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ - --result-format json \ - --target-org "$DEFAULT_ORG") - -RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') - -if [ "$RECORD_COUNT" -eq 0 ]; then - echo "Error: Work item not found" - exit 1 -fi - -WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') - -if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then - echo "Error: Failed to extract ID" - exit 1 -fi -``` - -### Critical Violation #3: Using LIKE on ID/Reference Fields - -```bash -# ❌ NEVER: Use LIKE operator on reference/ID fields -sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus -# Error: invalid operator on id field - -# ✅ CORRECT: Reference fields require exact match with = operator -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -# STEP 1: Find the User ID first -USER_ID=$(sf data query \ - --query "SELECT Id FROM User WHERE Name LIKE '%Polillo%' LIMIT 1" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') - -# STEP 2: Use the ID with = operator (NOT LIKE) -sf data query \ - --query "SELECT Id, Name, Scrum_Team_Name__c, Role__c - FROM ADM_Scrum_Team_Member__c - WHERE Member_Name__c = '${USER_ID}'" \ - --target-org "$DEFAULT_ORG" -``` - -**Why this happens**: -- Reference fields (fields ending in __c that link to other objects) store IDs, not text -- LIKE is only valid for text fields (string, textarea) -- You must query the referenced object first to get the ID, then use = for exact match -- Check field type with `sf sobject describe` - if `type: "reference"`, use = not LIKE - -### Critical Violation #4: Assuming Fields Exist Without Verification - -**NOTE**: This is another example of Violation #1. See above for the full explanation. - -```bash -# ❌ NEVER: Guess field names without verification (see Violation #1) -sf data query --query "SELECT Id, Name, Team__c FROM User WHERE Id = '005EE000001JW5FYAW'" --target-org gus -# Error: No such column 'Team__c' on entity 'User' - -# ✅ CORRECT: Use sf sobject describe to discover correct fields (MANDATORY STEP) -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi - -# STEP 1: Discover available fields -sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team -# Result: No Team__c field exists - -# STEP 2: Find junction object for team membership -sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ - jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, relationshipName: .relationshipName}' -# Result: Member_Name__c field references User - -# STEP 3: Query using verified fields and correct object -sf data query \ - --query "SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005EE000001JW5FYAW'" \ - --result-format json \ - --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' -``` - -**Why this matters**: -- Field names vary across Salesforce implementations -- Many-to-many relationships use junction objects -- `sf sobject describe` is the authoritative source - USE IT FIRST, ALWAYS -- See **Pattern 1: Discovering Object Fields** and **Violation #1** for detailed examples - ---- - -## Security Considerations - -**Security Notes**: -- ⚠️ Be careful querying sensitive fields (passwords, tokens, SSN) -- ⚠️ Use appropriate WHERE clauses to avoid exposing all data -- ⚠️ Don't log or export sensitive data to insecure locations -- ⚠️ Validate user input in WHERE clauses to prevent injection - ---- - ## Related Skills - `sf-org-auth.md` - Authentication and user info - `sf-record-operations.md` - Create/update records - `sf-work-items.md` - Work with GUS objects - `sf-bulk-operations.md` - Export large datasets +- `sf-chatter.md` - Post Chatter updates +- `sf-automation.md` - Git integration and automation + +--- + +## File Organization + +**Skill Files**: +- [sf-soql-basics.md](sf-soql-basics.md) - ~350 lines, v1.0 +- [sf-soql-advanced.md](sf-soql-advanced.md) - ~350 lines, v1.0 +- [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) - ~450 lines, v1.0 +- [sf-soql-queries.md](sf-soql-queries.md) - This navigation file, v2.0 + +**Version History**: +- v2.0 (2025-12-03): Split into focused skill files (basics, advanced, troubleshooting) +- v1.2 (2025-12-03): Added mandatory field verification and relationship name rules +- v1.1 (2025-12-03): Added keywords for GUS/team/epic discoverability +- v1.0 (2025-12-02): Initial unified skill file --- **Last Updated**: 2025-12-03 -**Format Version**: 1.2 (Atomic - Added ADM_Scrum_Team_Member__c field details and LIKE operator on reference fields anti-pattern) +**Format Version**: 2.0 (Navigation file for split skills) diff --git a/skills/salesforce/sf-soql-troubleshooting.md b/skills/salesforce/sf-soql-troubleshooting.md new file mode 100644 index 0000000..c41c45c --- /dev/null +++ b/skills/salesforce/sf-soql-troubleshooting.md @@ -0,0 +1,493 @@ +--- +name: salesforce-soql-troubleshooting +description: Common SOQL errors, anti-patterns, and troubleshooting for Salesforce queries. Debug field verification, relationship errors, and query failures. +keywords: salesforce, soql, troubleshooting, errors, anti-patterns, debug, invalid field, relationship errors, query failures, field verification errors +--- + +# Salesforce SOQL Troubleshooting + +**Scope**: Common SOQL errors, anti-patterns, and solutions +**Lines**: ~450 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Debugging SOQL query errors** - INVALID_FIELD, relationship errors, operator errors +- **Understanding common mistakes** - Why queries fail and how to fix them +- **Learning anti-patterns** - What NOT to do in SOQL queries +- **Troubleshooting field verification** - Field doesn't exist errors +- **Fixing relationship queries** - Wrong relationship name errors + +**Prerequisites**: You should understand basic SOQL from `sf-soql-basics.md` first. + +--- + +## Critical Violations (Most Common Errors) + +### Critical Violation #1: Querying Fields Without Verification (MOST COMMON ERROR) + +**Error Message**: `No such column 'Team__c' on entity 'User'` or `No such column 'Department' on entity 'User'` + +**Why This Happens**: +- Field names vary across Salesforce orgs and implementations +- Custom fields that exist in one org may not exist in another +- Assuming field names without verification WILL cause failures +- User.Team__c, User.Department, User.Division are NOT guaranteed to exist + +**Example of the Error**: +```bash +# ❌ NEVER: Query fields without first running sf sobject describe +# This is the #1 cause of INVALID_FIELD errors +sf data query --query "SELECT Id, Name, Username, Email, Department, Division, Title, Team__c FROM User WHERE Username = 'user@example.com'" --target-org gus +# Error: No such column 'Team__c' on entity 'User' +# Error: No such column 'Department' on entity 'User' (may not exist in all orgs) +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# ✅ STEP 1: Describe to see what fields actually exist +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i "team\|department\|division\|title" + +# ✅ STEP 2: If fields don't exist, find alternative approach (e.g., junction objects) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User")' + +# ✅ STEP 3: Query only with verified fields +sf data query \ + --query "SELECT Id, Name, Username, Email FROM User WHERE Username = 'user@example.com'" \ + --target-org "$DEFAULT_ORG" +``` + +**Prevention**: ALWAYS run `sf sobject describe` FIRST before ANY query. This is MANDATORY, not optional. + +--- + +### Critical Violation #2: Assuming Relationship Names Match Object Names (VERY COMMON) + +**Error Message**: `Didn't understand relationship 'ADM_Scrum_Team__r' in field path` + +**Why This Happens**: +- Relationship names are defined in the `relationshipName` field, NOT derived from object names +- Field `Scrum_Team__c` references object `ADM_Scrum_Team__c` but has relationship name `Scrum_Team__r` +- The `ADM_` prefix is NOT part of the relationship name +- ALWAYS use `sf sobject describe` to get the exact `relationshipName` value + +**Example of the Error**: +```bash +# ❌ NEVER: Assume relationship name matches object name +sf data query --query "SELECT Id, Name, ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" --target-org gus +# Error: Didn't understand relationship 'ADM_Scrum_Team__r' in field path +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# ✅ STEP 1: Get the actual relationship name from describe +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName}' +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r"} + +# ✅ STEP 2: Use the verified relationship name (Scrum_Team__r, NOT ADM_Scrum_Team__r) +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" \ + --target-org "$DEFAULT_ORG" +``` + +**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. + +--- + +### Critical Violation #3: Using Shell Special Characters + +**Error Message**: `unexpected token: '\'` + +**Why This Happens**: +In bash/zsh, `!` triggers history expansion even in double quotes, causing the shell to escape it as `\!`. SOQL doesn't recognize this escaped form. + +**Example of the Error**: +```bash +# ❌ NEVER: Use != in double-quoted strings (shell escapes the !) +sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --target-org gus +# Error: unexpected token: '\' +``` + +**Correct Solutions**: + +**Solution 1: Query all records and filter with jq (RECOMMENDED)** +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# ✅ CORRECT: Filter null values using jq instead +sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ + --result-format json --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name)"' +``` + +**Solution 2: Use relationship fields and check with jq** +```bash +# Check for non-null relationships using jq select() +sf data query \ + --query "SELECT Id, Name, Epic__r.Id FROM ADM_Work__c LIMIT 50" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | .Name' +``` + +--- + +### Critical Violation #4: Using LIKE on ID/Reference Fields + +**Error Message**: `invalid operator on id field` + +**Why This Happens**: +- Reference fields (fields ending in __c that link to other objects) store IDs, not text +- LIKE is only valid for text fields (string, textarea) +- You must query the referenced object first to get the ID, then use = for exact match +- Check field type with `sf sobject describe` - if `type: "reference"`, use = not LIKE + +**Example of the Error**: +```bash +# ❌ NEVER: Use LIKE operator on reference/ID fields +sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus +# Error: invalid operator on id field +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# ✅ STEP 1: Find the User ID first +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Name LIKE '%Polillo%' LIMIT 1" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# ✅ STEP 2: Use the ID with = operator (NOT LIKE) +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Role__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +**How to Check Field Type**: +```bash +# Verify if field is a reference type (requires = not LIKE) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Member_Name__c") | {name: .name, type: .type, referenceTo: .referenceTo}' +# Output: {"name":"Member_Name__c","type":"reference","referenceTo":["User"]} +``` + +--- + +### Critical Violation #5: Not Validating Query Results + +**Error Message**: Downstream errors with `null` values or empty results + +**Why This Happens**: +- Queries may return zero results +- Extracting values from empty results returns "null" as a string +- Using "null" in subsequent operations causes failures +- Always validate before using extracted values + +**Example of the Error**: +```bash +# ❌ NEVER: Query without checking results +WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id') +# If no results, this returns "null" and breaks downstream operations +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# ✅ CORRECT: Validate query results +QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG") + +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item not found" + exit 1 +fi + +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract ID" + exit 1 +fi + +echo "Work Item ID: $WORK_ITEM_ID" +``` + +--- + +## Common Field Errors + +### Error: Field Doesn't Exist on Junction Object + +**Scenario**: Querying ADM_Scrum_Team_Member__c with wrong field names + +**Common Mistakes**: +```bash +# ❌ Field User__c doesn't exist (correct: Member_Name__c) +SELECT Id, User__c FROM ADM_Scrum_Team_Member__c + +# ❌ Field Email__c doesn't exist (correct: Internal_Email__c) +SELECT Id, Email__c FROM ADM_Scrum_Team_Member__c + +# ❌ Name field is auto-numbered STM-######, not searchable by member name +SELECT Id FROM ADM_Scrum_Team_Member__c WHERE Name LIKE '%Polillo%' +``` + +**Verified Fields** (from sf sobject describe): +``` +Member_Name__c (→User - use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field) +Internal_Email__c (NOT Email__c) +Role__c, Allocation__c, Department__c, Active__c +``` + +**Correct Approach**: +```bash +# Always describe first +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.custom == true) | {name: .name, type: .type}' + +# Then query with verified fields +USER_ID="005EE000001JW5FYAW" +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +### Error: Epic Field Name Wrong + +**Scenario**: ADM_Epic__c uses `Health__c` not `Status__c` + +**Common Mistake**: +```bash +# ❌ WRONG: Epic doesn't have Status__c field +SELECT Id, Name, Status__c FROM ADM_Epic__c +# Error: No such column 'Status__c' on entity 'ADM_Epic__c' +``` + +**Correct Solution**: +```bash +# ✅ CORRECT: Epic uses Health__c field +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Verify first +sf sobject describe --sobject ADM_Epic__c --target-org "$DEFAULT_ORG" | grep -i "status\|health" + +# Query with correct field +sf data query \ + --query "SELECT Id, Name, Health__c, Priority__c FROM ADM_Epic__c" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Query Performance Issues + +### Issue: Query Timeout + +**Symptom**: Query takes too long or times out + +**Causes**: +- No LIMIT clause on large objects +- Complex WHERE clauses without indexes +- Querying too many fields +- Using LIKE with % on both sides + +**Solutions**: +```bash +# ❌ BAD: No LIMIT on large object +SELECT Id, Name, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' + +# ✅ GOOD: Add LIMIT +SELECT Id, Name, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 200 + +# ❌ BAD: LIKE with % on both sides (slow) +SELECT Id FROM ADM_Work__c WHERE Subject__c LIKE '%feature%' + +# ✅ BETTER: LIKE with % on right only (uses index) +SELECT Id FROM ADM_Work__c WHERE Subject__c LIKE 'feature%' + +# ✅ BEST: Use indexed fields in WHERE clause +SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678' +``` + +--- + +### Issue: Too Many SOQL Queries + +**Symptom**: Hitting SOQL query limits + +**Causes**: +- Querying in loops +- Not using relationship queries +- Multiple queries when one would suffice + +**Solutions**: +```bash +# ❌ BAD: Two separate queries +sf data query --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-123'" --target-org "$DEFAULT_ORG" +sf data query --query "SELECT Name, Email FROM User WHERE Id = '005xx'" --target-org "$DEFAULT_ORG" + +# ✅ GOOD: Single query with relationship +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf data query \ + --query "SELECT Id, Name, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Name = 'W-123'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Best Practices Summary + +**Essential Practices (IN ORDER OF IMPORTANCE)**: +``` +✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY +✅ DO: Verify field names exist in describe output before writing SELECT statements +✅ DO: Get relationship names from relationshipName field (NOT from object names) +✅ DO: Use = operator for ID/reference fields (NOT LIKE) +✅ DO: Filter null values with jq instead of != in queries +✅ DO: Validate query results before using extracted values +✅ DO: Use LIMIT to avoid timeouts on large datasets +✅ DO: Use --result-format json for scripting +``` + +**Common Mistakes to Avoid (CRITICAL)**: +``` +❌ DON'T: EVER query fields without running sf sobject describe first +❌ DON'T: Assume relationship names match object names +❌ DON'T: Use ADM_Scrum_Team__r when actual relationship is Scrum_Team__r +❌ DON'T: Assume ANY field exists without verification +❌ DON'T: Use != in double-quoted queries (shell escapes !) +❌ DON'T: Use LIKE on ID/reference fields (use = instead) +❌ DON'T: Query without LIMIT (can timeout) +❌ DON'T: Use SELECT * (not supported in SOQL) +❌ DON'T: Skip validation of jq output (check for null) +``` + +--- + +## Troubleshooting Workflow + +**When a query fails, follow these steps**: + +1. **Check the error message** - What field or relationship failed? + +2. **Run sf sobject describe** - Verify field exists and get exact name: + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" + ``` + +3. **Check field type** - Is it reference, string, or formula? + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "FieldName__c") | {name: .name, type: .type}' + ``` + +4. **Get relationship name** - If querying related fields: + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "FieldName__c") | {field: .name, relationshipName: .relationshipName}' + ``` + +5. **Test with minimal query** - Remove complexity and add back: + ```bash + sf data query --query "SELECT Id, Name FROM Object__c LIMIT 5" --target-org "$DEFAULT_ORG" + ``` + +6. **Add fields one by one** - Identify which field causes the error + +7. **Check for shell escaping** - Use single quotes or escape special characters + +8. **Validate results** - Check totalSize before extracting values + +--- + +## Related Skills + +- `sf-soql-basics.md` - Start here for field discovery and basic queries +- `sf-soql-advanced.md` - Complex queries, aggregation, date filters, tooling API +- `sf-work-items.md` - Creating and managing work items +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create and update records + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) From 135cff6833ee4874ced11bc35b06ee2c7e477003 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:26:45 -0300 Subject: [PATCH 18/21] Simplify DEFAULT_ORG detection to single-line command for better Claude Code execution --- skills/salesforce/sf-soql-advanced.md | 32 ++-------------- skills/salesforce/sf-soql-basics.md | 32 ++-------------- skills/salesforce/sf-soql-troubleshooting.md | 40 +++----------------- 3 files changed, 13 insertions(+), 91 deletions(-) diff --git a/skills/salesforce/sf-soql-advanced.md b/skills/salesforce/sf-soql-advanced.md index 485171c..5b43ad5 100644 --- a/skills/salesforce/sf-soql-advanced.md +++ b/skills/salesforce/sf-soql-advanced.md @@ -36,13 +36,7 @@ Activate this skill when: ```bash # Get default org and current user's work items dynamically -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') @@ -114,13 +108,7 @@ sf data query \ ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # Count work items by status sf data query \ @@ -151,13 +139,7 @@ sf data query \ ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # Work items created this week sf data query \ @@ -203,13 +185,7 @@ THIS_QUARTER Current quarter ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # Query Apex classes sf data query \ diff --git a/skills/salesforce/sf-soql-basics.md b/skills/salesforce/sf-soql-basics.md index 46b779c..963e0e8 100644 --- a/skills/salesforce/sf-soql-basics.md +++ b/skills/salesforce/sf-soql-basics.md @@ -81,13 +81,7 @@ SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n ```bash # Get default org (add this at the start of scripts) -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # Basic query sf data query \ @@ -229,13 +223,7 @@ sf data query \ **STEP 1: Describe the object to see available fields** ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # MANDATORY: Describe the object FIRST sf sobject describe --sobject User --target-org "$DEFAULT_ORG" @@ -295,13 +283,7 @@ sf data query \ **CORRECT APPROACH** (following mandatory field verification): ```bash # STEP 1: ALWAYS describe the object first -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ jq '.fields[] | select(.custom == true) | {name: .name, type: .type, referenceTo: .referenceTo}' @@ -330,13 +312,7 @@ sf data query \ ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # Find user ID by name sf data query \ diff --git a/skills/salesforce/sf-soql-troubleshooting.md b/skills/salesforce/sf-soql-troubleshooting.md index c41c45c..32ff0bd 100644 --- a/skills/salesforce/sf-soql-troubleshooting.md +++ b/skills/salesforce/sf-soql-troubleshooting.md @@ -50,13 +50,7 @@ sf data query --query "SELECT Id, Name, Username, Email, Department, Division, T **Correct Solution**: ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # ✅ STEP 1: Describe to see what fields actually exist sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i "team\|department\|division\|title" @@ -95,13 +89,7 @@ sf data query --query "SELECT Id, Name, ADM_Scrum_Team__r.Name FROM ADM_Scrum_Te **Correct Solution**: ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # ✅ STEP 1: Get the actual relationship name from describe sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ @@ -137,13 +125,7 @@ sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --targe **Solution 1: Query all records and filter with jq (RECOMMENDED)** ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # ✅ CORRECT: Filter null values using jq instead sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ @@ -181,13 +163,7 @@ sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Membe **Correct Solution**: ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # ✅ STEP 1: Find the User ID first USER_ID=$(sf data query \ @@ -233,13 +209,7 @@ WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id **Correct Solution**: ```bash # Get default org -DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') -if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) - if [ -z "$DEFAULT_ORG" ]; then - DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') - fi -fi +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') # ✅ CORRECT: Validate query results QUERY_RESULT=$(sf data query \ From e31d3201eeeb73ee5993c8dfe391f6424ae7a4fb Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:36:48 -0300 Subject: [PATCH 19/21] Fix SOQL shell escaping: use <> instead of != to avoid MALFORMED_QUERY errors in CLI --- skills/salesforce/sf-soql-queries.md | 3 +- skills/salesforce/sf-soql-troubleshooting.md | 54 ++++++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index f0f83b1..ec21bc1 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -155,7 +155,8 @@ ALWAYS use sf sobject describe to get the relationshipName field. ❌ DON'T: Query fields without sf sobject describe first ❌ DON'T: Assume relationship names match object names ❌ DON'T: Use LIKE on ID/reference fields (use = instead) -❌ DON'T: Use != in double-quoted queries (shell escapes !) +❌ DON'T: Use != in CLI queries (use <> instead - shell escapes !) +❌ DON'T: Check relationship name fields for null (check ID field instead) ❌ DON'T: Assume ANY field exists without verification ``` diff --git a/skills/salesforce/sf-soql-troubleshooting.md b/skills/salesforce/sf-soql-troubleshooting.md index 32ff0bd..d08deee 100644 --- a/skills/salesforce/sf-soql-troubleshooting.md +++ b/skills/salesforce/sf-soql-troubleshooting.md @@ -108,7 +108,7 @@ sf data query \ ### Critical Violation #3: Using Shell Special Characters -**Error Message**: `unexpected token: '\'` +**Error Message**: `unexpected token: '\'` or `MALFORMED_QUERY` **Why This Happens**: In bash/zsh, `!` triggers history expansion even in double quotes, causing the shell to escape it as `\!`. SOQL doesn't recognize this escaped form. @@ -118,21 +118,45 @@ In bash/zsh, `!` triggers history expansion even in double quotes, causing the s # ❌ NEVER: Use != in double-quoted strings (shell escapes the !) sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --target-org gus # Error: unexpected token: '\' +# Error: MALFORMED_QUERY ... unexpected token: '\\' ``` **Correct Solutions**: -**Solution 1: Query all records and filter with jq (RECOMMENDED)** +**Solution 1: Use `<>` instead of `!=` (RECOMMENDED - Most Reliable)** ```bash # Get default org DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') -# ✅ CORRECT: Filter null values using jq instead +# ✅ BEST: Use <> operator instead of != to avoid shell escaping +sf data query --query "SELECT Id, Name, Epic__r.Name FROM ADM_Work__c WHERE Epic__c <> null AND Status__c NOT IN ('Closed', 'Duplicate') LIMIT 50" \ + --result-format json --target-org "$DEFAULT_ORG" + +# ✅ BEST: Use <> for checking if a reference field has a value +sf data query --query "SELECT Id, Name FROM ADM_Work__c WHERE Scrum_Team__c <> null" \ + --target-org "$DEFAULT_ORG" +``` + +**Solution 2: Check ID fields instead of relationship name fields (More Efficient)** +```bash +# ✅ GOOD: Check the ID field (Scrum_Team__c) instead of relationship name field (Scrum_Team__r.Name) +# This is more efficient and avoids querying the related object +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Work__c WHERE Scrum_Team__c <> null" \ + --target-org "$DEFAULT_ORG" + +# ❌ LESS EFFICIENT: Checking relationship name field +# Don't check Scrum_Team__r.Name <> null when you can check Scrum_Team__c <> null +``` + +**Solution 3: Query all records and filter with jq (Alternative)** +```bash +# ✅ ALTERNATIVE: Filter null values using jq instead sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ --result-format json --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name)"' ``` -**Solution 2: Use relationship fields and check with jq** +**Solution 4: Use relationship fields and check with jq (Alternative)** ```bash # Check for non-null relationships using jq select() sf data query \ @@ -141,6 +165,25 @@ sf data query \ --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | .Name' ``` +**Solution 5: Use a query file (For Complex Queries)** +```bash +# ✅ Create a file with your query to avoid shell escaping entirely +cat > query.soql <<'EOF' +SELECT Id, Name, Subject__c +FROM ADM_Work__c +WHERE Scrum_Team__c != null +AND Status__c NOT IN ('Closed', 'Duplicate') +LIMIT 50 +EOF + +sf data query --file query.soql --target-org "$DEFAULT_ORG" --json +``` + +**Key Takeaways**: +- **Always use `<>` instead of `!=`** in CLI queries to avoid shell escaping +- **Check ID fields (`Field__c`)** not relationship name fields (`Field__r.Name`) for null checks - it's more efficient +- **Use query files** for complex queries to avoid all shell escaping issues + --- ### Critical Violation #4: Using LIKE on ID/Reference Fields @@ -404,7 +447,8 @@ sf data query \ ❌ DON'T: Assume relationship names match object names ❌ DON'T: Use ADM_Scrum_Team__r when actual relationship is Scrum_Team__r ❌ DON'T: Assume ANY field exists without verification -❌ DON'T: Use != in double-quoted queries (shell escapes !) +❌ DON'T: Use != in CLI queries (use <> instead - shell escapes !) +❌ DON'T: Check Scrum_Team__r.Name <> null when you can check Scrum_Team__c <> null ❌ DON'T: Use LIKE on ID/reference fields (use = instead) ❌ DON'T: Query without LIMIT (can timeout) ❌ DON'T: Use SELECT * (not supported in SOQL) From 33ab6894d697a5092639d509e27f1f96c7ff6527 Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:49:53 -0300 Subject: [PATCH 20/21] Update line counts in SOQL skill files to reflect current sizes --- skills/salesforce/sf-soql-advanced.md | 2 +- skills/salesforce/sf-soql-basics.md | 2 +- skills/salesforce/sf-soql-queries.md | 8 ++++---- skills/salesforce/sf-soql-troubleshooting.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/skills/salesforce/sf-soql-advanced.md b/skills/salesforce/sf-soql-advanced.md index 5b43ad5..9b4e6fe 100644 --- a/skills/salesforce/sf-soql-advanced.md +++ b/skills/salesforce/sf-soql-advanced.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, soql, advanced, work items, aggregation, date filters # Salesforce SOQL Advanced Queries **Scope**: Advanced SOQL patterns for work items, aggregation, date filtering, and tooling API -**Lines**: ~350 +**Lines**: ~292 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-basics.md b/skills/salesforce/sf-soql-basics.md index 963e0e8..48b2402 100644 --- a/skills/salesforce/sf-soql-basics.md +++ b/skills/salesforce/sf-soql-basics.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, soql, query, basics, field verification, sf sobject d # Salesforce SOQL Basics **Scope**: Basic SOQL syntax, mandatory field discovery, and fundamental query patterns -**Lines**: ~350 +**Lines**: ~402 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md index ec21bc1..d41b17e 100644 --- a/skills/salesforce/sf-soql-queries.md +++ b/skills/salesforce/sf-soql-queries.md @@ -215,10 +215,10 @@ Scrum_Team__r.Name # Team name (NOT ADM_Scrum_Team__r!) ## File Organization **Skill Files**: -- [sf-soql-basics.md](sf-soql-basics.md) - ~350 lines, v1.0 -- [sf-soql-advanced.md](sf-soql-advanced.md) - ~350 lines, v1.0 -- [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) - ~450 lines, v1.0 -- [sf-soql-queries.md](sf-soql-queries.md) - This navigation file, v2.0 +- [sf-soql-basics.md](sf-soql-basics.md) - ~402 lines, v1.0 +- [sf-soql-advanced.md](sf-soql-advanced.md) - ~292 lines, v1.0 +- [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) - ~507 lines, v1.0 +- [sf-soql-queries.md](sf-soql-queries.md) - ~232 lines (navigation file), v2.0 **Version History**: - v2.0 (2025-12-03): Split into focused skill files (basics, advanced, troubleshooting) diff --git a/skills/salesforce/sf-soql-troubleshooting.md b/skills/salesforce/sf-soql-troubleshooting.md index d08deee..dbcfac7 100644 --- a/skills/salesforce/sf-soql-troubleshooting.md +++ b/skills/salesforce/sf-soql-troubleshooting.md @@ -7,7 +7,7 @@ keywords: salesforce, soql, troubleshooting, errors, anti-patterns, debug, inval # Salesforce SOQL Troubleshooting **Scope**: Common SOQL errors, anti-patterns, and solutions -**Lines**: ~450 +**Lines**: ~507 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Split from sf-soql-queries v1.2) From 76582c99be447545cd304e96be0f3bcd1480383a Mon Sep 17 00:00:00 2001 From: Fernando Polillo Date: Wed, 3 Dec 2025 20:55:32 -0300 Subject: [PATCH 21/21] Update line counts in all Salesforce skill files to match actual sizes --- skills/salesforce/INDEX.md | 18 +++++++++--------- skills/salesforce/sf-automation.md | 2 +- skills/salesforce/sf-bulk-operations.md | 2 +- skills/salesforce/sf-chatter.md | 2 +- skills/salesforce/sf-org-auth.md | 2 +- skills/salesforce/sf-record-operations.md | 2 +- skills/salesforce/sf-work-items.md | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md index 7d2105f..037bdbd 100644 --- a/skills/salesforce/INDEX.md +++ b/skills/salesforce/INDEX.md @@ -13,7 +13,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-org-auth.md **Description**: Authenticate and manage Salesforce orgs using sf CLI -**Lines**: ~180 +**Lines**: ~348 **Use When**: - Logging into Salesforce orgs - Managing multiple org connections @@ -44,7 +44,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-soql-basics.md **Description**: Basic SOQL queries, field discovery, and fundamental patterns for Salesforce -**Lines**: ~350 +**Lines**: ~402 **Use When**: - Starting with SOQL queries (learn basics first) - Querying simple data (users, work items by ID, basic lookups) @@ -58,7 +58,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-soql-advanced.md **Description**: Advanced SOQL queries for GUS - work items, aggregation, date filters, and tooling API -**Lines**: ~350 +**Lines**: ~292 **Use When**: - Querying work items by various criteria (assignee, sprint, epic, status) - Aggregating data (counts, sums, grouped results) @@ -72,7 +72,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-soql-troubleshooting.md **Description**: Common SOQL errors, anti-patterns, and troubleshooting for Salesforce queries -**Lines**: ~450 +**Lines**: ~507 **Use When**: - Debugging SOQL query errors (INVALID_FIELD, relationship errors, operator errors) - Understanding common mistakes (why queries fail and how to fix them) @@ -86,7 +86,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-record-operations.md **Description**: Create and update Salesforce records using sf CLI -**Lines**: ~200 +**Lines**: ~438 **Use When**: - Creating new Salesforce records - Updating existing records @@ -100,7 +100,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-work-items.md **Description**: Manage Agile Accelerator (GUS) work items, sprints, and epics -**Lines**: ~220 +**Lines**: ~402 **Use When**: - Creating user stories, bugs, or tasks in GUS - Managing sprints and sprint planning @@ -114,7 +114,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-chatter.md **Description**: Create and manage Chatter posts and comments in Salesforce -**Lines**: ~180 +**Lines**: ~418 **Use When**: - Posting updates to Chatter feeds - Adding comments to work items @@ -128,7 +128,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-bulk-operations.md **Description**: Perform bulk data operations on Salesforce objects -**Lines**: ~150 +**Lines**: ~356 **Use When**: - Updating multiple records at once - Exporting large datasets @@ -142,7 +142,7 @@ Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator ### sf-automation.md **Description**: Automate Salesforce operations with git integration and CI/CD -**Lines**: ~170 +**Lines**: ~437 **Use When**: - Integrating Salesforce with git workflows - Inferring WI numbers from branch names diff --git a/skills/salesforce/sf-automation.md b/skills/salesforce/sf-automation.md index 825496f..64a95f3 100644 --- a/skills/salesforce/sf-automation.md +++ b/skills/salesforce/sf-automation.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, automation, git, CI/CD, work item, branch, workflow, # Salesforce Automation **Scope**: Git integration, WI inference, and automated workflows -**Lines**: ~170 +**Lines**: ~437 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-bulk-operations.md b/skills/salesforce/sf-bulk-operations.md index b275ae6..0caa93d 100644 --- a/skills/salesforce/sf-bulk-operations.md +++ b/skills/salesforce/sf-bulk-operations.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, bulk, mass update, export, import, CSV, bulk API, lar # Salesforce Bulk Operations **Scope**: Bulk data updates, exports, and large-scale operations -**Lines**: ~150 +**Lines**: ~356 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md index 044f9ab..4bc434b 100644 --- a/skills/salesforce/sf-chatter.md +++ b/skills/salesforce/sf-chatter.md @@ -7,7 +7,7 @@ keywords: chatter, salesforce, gus, post, comment, feed, FeedItem, FeedComment, # Salesforce Chatter Operations **Scope**: Creating Chatter posts, comments, and feed interactions -**Lines**: ~180 +**Lines**: ~418 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-org-auth.md b/skills/salesforce/sf-org-auth.md index c853928..7238a64 100644 --- a/skills/salesforce/sf-org-auth.md +++ b/skills/salesforce/sf-org-auth.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, authentication, login, org, user info, sf cli, connec # Salesforce Org Authentication **Scope**: Org authentication, connection management, and user information retrieval -**Lines**: ~180 +**Lines**: ~348 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md index b432172..c0d0d44 100644 --- a/skills/salesforce/sf-record-operations.md +++ b/skills/salesforce/sf-record-operations.md @@ -7,7 +7,7 @@ keywords: salesforce, gus, create, update, record, work item, epic, sprint, sf d # Salesforce Record Operations **Scope**: Creating, updating, and managing individual Salesforce records -**Lines**: ~200 +**Lines**: ~438 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md index 3524da4..a38a676 100644 --- a/skills/salesforce/sf-work-items.md +++ b/skills/salesforce/sf-work-items.md @@ -7,7 +7,7 @@ keywords: gus, agile accelerator, work items, story, bug, task, sprint, epic, te # Salesforce Agile Accelerator Work Items **Scope**: Creating and managing work items, sprints, epics, and builds in GUS -**Lines**: ~220 +**Lines**: ~402 **Last Updated**: 2025-12-03 **Format Version**: 1.0 (Atomic)