AgentSkillsCN

cirra-ai-sf-apex

利用Cirra AI MCP Server元数据API,依据2025年最佳实践与150分评分标准,生成并审查Salesforce Apex代码。在编写Apex类、触发器、测试类、批量作业,或对现有Apex代码进行批量处理优化、安全加固及SOLID原则合规性审查时均可使用。

SKILL.md
--- frontmatter
name: cirra-ai-sf-apex
description: >
  Generates and reviews Salesforce Apex code with 2025 best practices and 150-point
  scoring using Cirra AI MCP Server metadata API. Use when writing Apex classes, triggers,
  test classes, batch jobs, or reviewing existing Apex code for bulkification, security,
  and SOLID principles.
license: MIT
metadata:
  version: '2.0.0'
  author: 'Jag Valaiyapathy'
  refactorNote: 'Migrated from Salesforce CLI (sf project deploy/retrieve) to Cirra AI MCP Server (metadata_create/metadata_update/metadata_read)'
  scoring: '150 points across 8 categories'
hooks:
  SessionStart:
    - type: command
      command: 'cirra_ai_init'
      timeout: 5000
  PreToolUse:
    - matcher: 'Bash'
      hooks:
        - type: notification
          message: 'Cirra AI MCP Server initialized'
  PostToolUse:
    - matcher: 'Write|Edit'
      hooks:
        - type: validation
          rule: 'Validate Apex syntax before metadata operations'
          timeout: 15000

cirra-ai-sf-apex: Salesforce Apex Code Generation and Review (Cirra AI Edition)

Expert Apex developer specializing in clean code, SOLID principles, and 2025 best practices. Generate production-ready, secure, performant, and maintainable Apex code with deployment via Cirra AI MCP Server.

Core Responsibilities

  1. Code Generation: Create Apex classes, triggers (TAF), tests, async jobs from requirements
  2. Code Review: Analyze existing Apex for best practices violations with actionable fixes
  3. Validation & Scoring: Score code against 8 categories (0-150 points)
  4. Metadata Deployment: Deploy via Cirra AI MCP Server (metadata_create/metadata_update/metadata_read)

Key Changes from CLI Version

Removed (CLI-dependent)

  • sf project deploy → Replaced with metadata_create / metadata_update (Cirra AI MCP)
  • sf project retrieve → Replaced with metadata_read (Cirra AI MCP)
  • sf apex run → Removed (anonymous Apex not supported in MCP)
  • Local file system operations (force-app/main/default/) → Replaced with string-based code generation
  • sf sobject describe → Replaced with sobject_describe (Cirra AI MCP)

Added (Cirra AI MCP)

  • cirra_ai_init: Initialize MCP server connection (MUST call first)
  • metadata_create: Deploy new Apex classes/triggers
  • metadata_update: Update existing Apex classes/triggers
  • metadata_read: Retrieve existing Apex code for review
  • soql_query: Query SOQL with WITH USER_MODE for testing context
  • tooling_api_query: Query ApexClass, ApexTrigger, ApexTestResult metadata
  • tooling_api_dml: Execute DML on metadata objects

Workflow (5-Phase Pattern)

Phase 1: Requirements Gathering & MCP Initialization

FIRST: Call cirra_ai_init with your Salesforce org context:

code
Use: cirra_ai_init(cirra_ai_team="your-team-id", sf_user="your-org-alias")

Then use AskUserQuestion to gather:

  • Class type (Trigger, Service, Selector, Batch, Queueable, Test, Controller)
  • Primary purpose (one sentence)
  • Target object(s)
  • Test requirements

Then:

  1. Check existing code: tooling_api_query(sObject="ApexClass", whereClause="Name LIKE '%Account%'")
  2. Check for existing Trigger Actions Framework: tooling_api_query(sObject="ApexClass", whereClause="Name LIKE 'TA_%'")
  3. Create TodoWrite tasks

Phase 2: Design & Template Selection

Select template (for reference - code is generated as strings):

Class TypeReference Template
TriggerStandard TAF trigger pattern
Trigger ActionTA_ObjectName_Purpose naming
ServiceService layer pattern
SelectorSelector pattern for queries
BatchBatch Apex pattern
QueueableQueueable/async pattern
TestTest class with PNB patterns
Test Data FactoryFactory pattern for test data
Standard ClassStandard utility/controller class

Template-Free Design: Generate Apex code directly as strings following naming conventions and patterns. No file system templates needed.


Phase 3: Code Generation/Review

For Generation:

  1. Generate Apex code as a STRING (not saved to file system)
  2. Apply naming conventions (see best practices section)
  3. Include ApexDoc comments
  4. Generate corresponding test class as STRING
  5. Validate code against guardrails (see below)

For Review:

  1. Query existing code: metadata_read(type="ApexClass", fullNames=["AccountService"])
  2. Analyze against best practices
  3. Generate improvement report with specific fixes

Run Validation:

code
Score: XX/150 ⭐⭐⭐⭐ Rating
├─ Bulkification: XX/25
├─ Security: XX/25
├─ Testing: XX/25
├─ Architecture: XX/20
├─ Clean Code: XX/20
├─ Error Handling: XX/15
├─ Performance: XX/10
└─ Documentation: XX/10

⛔ GENERATION GUARDRAILS (MANDATORY)

BEFORE generating ANY Apex code, Claude MUST verify no anti-patterns are introduced.

If ANY of these patterns would be generated, STOP and ask the user:

"I noticed [pattern]. This will cause [problem]. Should I: A) Refactor to use [correct pattern] B) Proceed anyway (not recommended)"

Anti-PatternDetectionImpact
SOQL inside loopfor(...) { [SELECT...] }Governor limit failure (100 SOQL)
DML inside loopfor(...) { insert/update }Governor limit failure (150 DML)
Missing sharingclass X { without keywordSecurity violation
Hardcoded ID15/18-char ID literalDeployment failure
Empty catchcatch(e) { }Silent failures
String concatenation in SOQL'SELECT...WHERE Name = \'' + varSOQL injection
Test without assertions@IsTest method with no Assert.*False positive tests

DO NOT generate anti-patterns even if explicitly requested. Ask user to confirm the exception with documented justification.


Phase 4: Metadata Deployment

Step 1: Generate Code String Generate Apex code as a STRING with full ApexDoc comments and validation.

Step 2: Deploy via Cirra AI MCP

code
metadata_create(
  type="ApexClass",
  metadata=[{
    "fullName": "AccountService",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "[YOUR APEX CODE STRING HERE]"
  }]
)

Step 3: Verify Deployment

code
metadata_read(
  type="ApexClass",
  fullNames=["AccountService"]
)

Step 4: Test Execution (via SOQL on ApexTestResult)

code
tooling_api_query(
  sObject="ApexTestResult",
  whereClause="TestClassName = 'AccountServiceTest' ORDER BY CreatedDate DESC LIMIT 10"
)

Phase 5: Documentation & Testing Guidance

Completion Summary:

code
✓ Apex Code Complete: [ClassName]
  Type: [type] | API: 65.0
  Deployment: VIA CIRRA AI MCP (metadata_create)
  Test Class: [TestClassName]
  Validation: PASSED (Score: XX/150)

Next Steps: Run tests via Cirra AI, verify via metadata_read, monitor logs

Best Practices (150-Point Scoring)

CategoryPointsKey Rules
Bulkification25NO SOQL/DML in loops; collect first, operate after; test 251+ records
Security25WITH USER_MODE; bind variables; with sharing; Security.stripInaccessible()
Testing2590%+ coverage; Assert class; positive/negative/bulk tests; Test Data Factory
Architecture20TAF triggers; Service/Domain/Selector layers; SOLID; dependency injection
Clean Code20Meaningful names; self-documenting; no != false; single responsibility
Error Handling15Specific before generic catch; no empty catch; custom business exceptions
Performance10Monitor with Limits; cache expensive ops; scope variables; async for heavy
Documentation10ApexDoc on classes/methods; meaningful params

Thresholds: ✅ 90+ (Deploy) | ⚠️ 67-89 (Review) | ❌ <67 (Block - fix required)


Trigger Actions Framework (TAF)

Quick Reference

When to Use: If TAF package is installed in target org

Check Installation:

code
tooling_api_query(
  sObject="InstalledSubscriberPackage",
  whereClause="Name = 'Trigger Actions Framework'"
)

Trigger Pattern (one per object):

apex
trigger AccountTrigger on Account (before insert, after insert, before update, after update, before delete, after delete, after undelete) {
    new MetadataTriggerHandler().run();
}

Action Class (one per behavior):

apex
public class TA_Account_SetDefaults implements TriggerAction.BeforeInsert {
    public void beforeInsert(List<Account> newList) {
        for (Account acc : newList) {
            if (acc.Industry == null) {
                acc.Industry = 'Other';
            }
        }
    }
}

Deploy Action Class:

code
metadata_create(
  type="ApexClass",
  metadata=[{
    "fullName": "TA_Account_SetDefaults",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "[GENERATED APEX CODE]"
  }]
)

⚠️ CRITICAL: TAF triggers do NOTHING without Trigger_Action__mdt records! Each action class needs a corresponding Custom Metadata record (deploy manually or via separate metadata deployment).

Fallback: If TAF is NOT installed, use standard trigger pattern (non-TAF).


Async Decision Matrix

ScenarioUse
Simple callout, fire-and-forget@future(callout=true)
Complex logic, needs chainingQueueable
Process millions of recordsBatch Apex
Scheduled/recurring jobSchedulable
Post-queueable cleanupQueueable Finalizer

Modern Apex Features (API 65.0)

  • Null coalescing: value ?? defaultValue
  • Safe navigation: record?.Field__c
  • User mode: WITH USER_MODE in SOQL
  • Assert class: Assert.areEqual(), Assert.isTrue()

Breaking Change (API 62.0): Cannot modify Set while iterating - throws System.FinalException


Flow Integration (@InvocableMethod)

Apex classes can be called from Flow using @InvocableMethod. This pattern enables complex business logic, DML, callouts, and integrations from declarative automation.

Quick Pattern

apex
public with sharing class RecordProcessor {

    @InvocableMethod(label='Process Record' category='Custom')
    public static List<Response> execute(List<Request> requests) {
        List<Response> responses = new List<Response>();
        for (Request req : requests) {
            Response res = new Response();
            res.isSuccess = true;
            res.processedId = req.recordId;
            responses.add(res);
        }
        return responses;
    }

    public class Request {
        @InvocableVariable(label='Record ID' required=true)
        public Id recordId;
    }

    public class Response {
        @InvocableVariable(label='Is Success')
        public Boolean isSuccess;
        @InvocableVariable(label='Processed ID')
        public Id processedId;
    }
}

Deploy via Cirra AI:

code
metadata_create(
  type="ApexClass",
  metadata=[{
    "fullName": "RecordProcessor",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "[YOUR INVOCABLE METHOD CODE]"
  }]
)

Testing Best Practices

The 3 Test Types (PNB Pattern)

Every feature needs:

  1. Positive: Happy path test
  2. Negative: Error handling test
  3. Bulk: 251+ records test

Example:

apex
@IsTest
static void testPositive() {
    Account acc = new Account(Name = 'Test', Industry = 'Tech');
    insert acc;
    Assert.areEqual('Tech', [SELECT Industry FROM Account WHERE Id = :acc.Id].Industry);
}

@IsTest
static void testNegative() {
    try {
        insert new Account(); // Missing Name
        Assert.fail('Expected DmlException');
    } catch (DmlException e) {
        Assert.isTrue(e.getMessage().contains('REQUIRED_FIELD_MISSING'));
    }
}

@IsTest
static void testBulk() {
    List<Account> accounts = new List<Account>();
    for (Integer i = 0; i < 251; i++) {
        accounts.add(new Account(Name = 'Bulk ' + i));
    }
    insert accounts;
    Assert.areEqual(251, [SELECT COUNT() FROM Account]);
}

Deploy Test Class:

code
metadata_create(
  type="ApexClass",
  metadata=[{
    "fullName": "AccountServiceTest",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "[YOUR TEST CLASS CODE]"
  }]
)

Common Exception Types

When writing test classes, use these specific exception types:

Exception TypeWhen to Use
DmlExceptionInsert/update/delete failures
QueryExceptionSOQL query failures
NullPointerExceptionNull reference access
ListExceptionList operation failures
LimitExceptionGovernor limit exceeded
CalloutExceptionHTTP callout failures

Cirra AI MCP Server Integration

Required Initialization

ALWAYS start with:

code
cirra_ai_init(cirra_ai_team="[YOUR_TEAM_ID]", sf_user="[YOUR_ORG_ALIAS]")

This initializes the connection to Cirra AI MCP Server and provides access to all Salesforce metadata operations.

MCP Tools Mapping

OperationCLI CommandMCP ToolExample
Query Apex codesf data querysoql_querysoql_query(sObject="ApexClass", whereClause="Name = 'AccountService'")
Query metadatasf data query --use-tooling-apitooling_api_querytooling_api_query(sObject="ApexClass")
Deploy classsf project deploymetadata_createmetadata_create(type="ApexClass", metadata=[...])
Update classsf project deploy (existing)metadata_updatemetadata_update(type="ApexClass", metadata=[...])
Retrieve classsf project retrievemetadata_readmetadata_read(type="ApexClass", fullNames=["AccountService"])
Describe objectsf sobject describesobject_describesobject_describe(sObject="Account")
Delete classsf project deletemetadata_deletemetadata_delete(type="ApexClass", fullNames=["AccountService"])
Test resultssf apex test run (query)tooling_api_querytooling_api_query(sObject="ApexTestResult")

Metadata API Format

When deploying via metadata_create or metadata_update, use this format:

code
metadata_create(
  type="ApexClass",
  metadata=[{
    "fullName": "ClassName",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "public class ClassName { ... }"
  }]
)

For triggers:

code
metadata_create(
  type="ApexTrigger",
  metadata=[{
    "fullName": "AccountTrigger",
    "apiVersion": "65.0",
    "status": "Active",
    "body": "trigger AccountTrigger on Account (...) { ... }",
    "triggerEvents": ["before insert", "after insert", ...]
  }]
)

Query Examples

Find all Apex classes:

code
tooling_api_query(
  sObject="ApexClass",
  limit=100
)

Find test results for a class:

code
tooling_api_query(
  sObject="ApexTestResult",
  whereClause="TestClassName = 'AccountServiceTest' ORDER BY CreatedDate DESC LIMIT 10"
)

Find triggers on Account:

code
tooling_api_query(
  sObject="ApexTrigger",
  whereClause="EntityDefinitionId IN (SELECT Id FROM EntityDefinition WHERE QualifiedApiName = 'Account')"
)

Query with SOQL (not metadata):

code
soql_query(
  sObject="Account",
  whereClause="Industry = 'Technology'",
  limit=10
)

Cross-MCP Tool Integration

MCP ToolUse CaseExample
sobject_describeDiscover object/fields before codingsobject_describe(sObject="Invoice__c") → get field names, types, CRUD
soql_queryTest code behavior after deploysoql_query(sObject="Account", whereClause="Id IN :accountIds")
tooling_api_queryCheck existing Apex classestooling_api_query(sObject="ApexClass", whereClause="Name LIKE 'Account%'")
metadata_readRetrieve existing code for reviewmetadata_read(type="ApexClass", fullNames=["AccountService"])
metadata_createDeploy new Apex classes/triggersmetadata_create(type="ApexClass", metadata=[...])
metadata_updateUpdate existing Apex codemetadata_update(type="ApexClass", metadata=[...])
tooling_api_dmlPerform DML on metadata objectstooling_api_dml(operation="update", sObject="ApexClass", record={...})

Field-Level Security & CRUD Checks

Before generating code that accesses fields, use sobject_describe:

code
sobject_describe(sObject="Account")
→ Returns: fields[], CRUD permissions, sharing rules

In generated code, use:

apex
// For CRUD/FLS checking
List<String> fieldsToRead = new List<String>{ 'Name', 'Industry' };
Map<String, Schema.SObjectField> fieldMap = Schema.Account.getSObjectType().getDescribe().fields.getMap();

for (String field : fieldsToRead) {
    if (!fieldMap.get(field).getDescribe().isAccessible()) {
        throw new SecurityException('Field ' + field + ' is not accessible');
    }
}

Or use Security.stripInaccessible():

apex
List<Account> accounts = [SELECT Id, Name, Industry FROM Account LIMIT 100];
accounts = (List<Account>) Security.stripInaccessible(AccessType.READABLE, accounts);

Limitations & Workarounds

FeatureCLI SupportMCP SupportWorkaround
Anonymous Apex executionsf apex run❌ Not availableGenerate test class and deploy via metadata_create
Automatic file syncforce-app/main/default/❌ Not availableGenerate strings, deploy via metadata API
Local templatesTemplate file system✅ Reference only (no file access)Generate code from patterns in this doc
Metadata deploymentsf project deploymetadata_create / metadata_updateFull support via MCP
Code retrievalsf project retrievemetadata_readFull support via MCP
Test executionsf apex test runPartial - query via tooling_api_queryQuery ApexTestResult after deployment

Glossary of MCP Terms

  • MCP: Model Context Protocol - allows Claude to call Salesforce Org operations
  • Cirra AI: Salesforce's AI assistant that provides MCP server
  • Metadata API: Programmatic interface to deploy/retrieve Apex, triggers, config
  • Tooling API: Query metadata objects like ApexClass, ApexTrigger, ApexTestResult
  • ApexClass: Apex class metadata object (stored in Salesforce)
  • ApexTrigger: Apex trigger metadata object (stored in Salesforce)
  • ApexTestResult: Test execution result metadata object

Dependencies

Optional: Additional Cirra AI MCP tools for enhanced workflow:

  • sobject_describe - Discover fields before coding
  • soql_query - Test code context
  • tooling_api_dml - Advanced metadata manipulation

Notes

  • API Version: 65.0 required
  • TAF Optional: Prefer TAF when package is installed, use standard trigger pattern as fallback
  • Scoring: Block deployment if score < 67
  • MCP Initialization: ALWAYS call cirra_ai_init first
  • Code as String: Generate all Apex as strings, deploy via metadata_create/update
  • No Local Files: Apex code is NOT saved to local file system - lives only in Salesforce org via Metadata API

License

MIT License. See LICENSE file. Copyright (c) 2024-2025 Jag Valaiyapathy

Refactored for Cirra AI MCP Server by Claude Agent (2025)