mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
Merge branch 'main' into add-symfony-instructions
This commit is contained in:
791
instructions/agents.instructions.md
Normal file
791
instructions/agents.instructions.md
Normal file
@@ -0,0 +1,791 @@
|
||||
---
|
||||
description: 'Guidelines for creating custom agent files for GitHub Copilot'
|
||||
applyTo: '**/*.agent.md'
|
||||
---
|
||||
|
||||
# Custom Agent File Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable custom agent files that provide specialized expertise for specific development tasks in GitHub Copilot.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Target audience: Developers creating custom agents for GitHub Copilot
|
||||
- File format: Markdown with YAML frontmatter
|
||||
- File naming convention: lowercase with hyphens (e.g., `test-specialist.agent.md`)
|
||||
- Location: `.github/agents/` directory (repository-level) or `agents/` directory (organization/enterprise-level)
|
||||
- Purpose: Define specialized agents with tailored expertise, tools, and instructions for specific tasks
|
||||
- Official documentation: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents
|
||||
|
||||
## Required Frontmatter
|
||||
|
||||
Every agent file must include YAML frontmatter with the following fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
description: 'Brief description of the agent purpose and capabilities'
|
||||
name: 'Agent Display Name'
|
||||
tools: ['read', 'edit', 'search']
|
||||
model: 'Claude Sonnet 4.5'
|
||||
target: 'vscode'
|
||||
infer: true
|
||||
---
|
||||
```
|
||||
|
||||
### Core Frontmatter Properties
|
||||
|
||||
#### **description** (REQUIRED)
|
||||
- Single-quoted string, clearly stating the agent's purpose and domain expertise
|
||||
- Should be concise (50-150 characters) and actionable
|
||||
- Example: `'Focuses on test coverage, quality, and testing best practices'`
|
||||
|
||||
#### **name** (OPTIONAL)
|
||||
- Display name for the agent in the UI
|
||||
- If omitted, defaults to filename (without `.md` or `.agent.md`)
|
||||
- Use title case and be descriptive
|
||||
- Example: `'Testing Specialist'`
|
||||
|
||||
#### **tools** (OPTIONAL)
|
||||
- List of tool names or aliases the agent can use
|
||||
- Supports comma-separated string or YAML array format
|
||||
- If omitted, agent has access to all available tools
|
||||
- See "Tool Configuration" section below for details
|
||||
|
||||
#### **model** (STRONGLY RECOMMENDED)
|
||||
- Specifies which AI model the agent should use
|
||||
- Supported in VS Code, JetBrains IDEs, Eclipse, and Xcode
|
||||
- Example: `'Claude Sonnet 4.5'`, `'gpt-4'`, `'gpt-4o'`
|
||||
- Choose based on agent complexity and required capabilities
|
||||
|
||||
#### **target** (OPTIONAL)
|
||||
- Specifies target environment: `'vscode'` or `'github-copilot'`
|
||||
- If omitted, agent is available in both environments
|
||||
- Use when agent has environment-specific features
|
||||
|
||||
#### **infer** (OPTIONAL)
|
||||
- Boolean controlling whether Copilot can automatically use this agent based on context
|
||||
- Default: `true` if omitted
|
||||
- Set to `false` to require manual agent selection
|
||||
|
||||
#### **metadata** (OPTIONAL, GitHub.com only)
|
||||
- Object with name-value pairs for agent annotation
|
||||
- Example: `metadata: { category: 'testing', version: '1.0' }`
|
||||
- Not supported in VS Code
|
||||
|
||||
#### **mcp-servers** (OPTIONAL, Organization/Enterprise only)
|
||||
- Configure MCP servers available only to this agent
|
||||
- Only supported for organization/enterprise level agents
|
||||
- See "MCP Server Configuration" section below
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
### Tool Specification Strategies
|
||||
|
||||
**Enable all tools** (default):
|
||||
```yaml
|
||||
# Omit tools property entirely, or use:
|
||||
tools: ['*']
|
||||
```
|
||||
|
||||
**Enable specific tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'execute']
|
||||
```
|
||||
|
||||
**Enable MCP server tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'github/*', 'playwright/navigate']
|
||||
```
|
||||
|
||||
**Disable all tools**:
|
||||
```yaml
|
||||
tools: []
|
||||
```
|
||||
|
||||
### Standard Tool Aliases
|
||||
|
||||
All aliases are case-insensitive:
|
||||
|
||||
| Alias | Alternative Names | Category | Description |
|
||||
|-------|------------------|----------|-------------|
|
||||
| `execute` | shell, Bash, powershell | Shell execution | Execute commands in appropriate shell |
|
||||
| `read` | Read, NotebookRead, view | File reading | Read file contents |
|
||||
| `edit` | Edit, MultiEdit, Write, NotebookEdit | File editing | Edit and modify files |
|
||||
| `search` | Grep, Glob, search | Code search | Search for files or text in files |
|
||||
| `agent` | custom-agent, Task | Agent invocation | Invoke other custom agents |
|
||||
| `web` | WebSearch, WebFetch | Web access | Fetch web content and search |
|
||||
| `todo` | TodoWrite | Task management | Create and manage task lists (VS Code only) |
|
||||
|
||||
### Built-in MCP Server Tools
|
||||
|
||||
**GitHub MCP Server**:
|
||||
```yaml
|
||||
tools: ['github/*'] # All GitHub tools
|
||||
tools: ['github/get_file_contents', 'github/search_repositories'] # Specific tools
|
||||
```
|
||||
- All read-only tools available by default
|
||||
- Token scoped to source repository
|
||||
|
||||
**Playwright MCP Server**:
|
||||
```yaml
|
||||
tools: ['playwright/*'] # All Playwright tools
|
||||
tools: ['playwright/navigate', 'playwright/screenshot'] # Specific tools
|
||||
```
|
||||
- Configured to access localhost only
|
||||
- Useful for browser automation and testing
|
||||
|
||||
### Tool Selection Best Practices
|
||||
|
||||
- **Principle of Least Privilege**: Only enable tools necessary for the agent's purpose
|
||||
- **Security**: Limit `execute` access unless explicitly required
|
||||
- **Focus**: Fewer tools = clearer agent purpose and better performance
|
||||
- **Documentation**: Comment why specific tools are required for complex configurations
|
||||
|
||||
## Sub-Agent Invocation (Agent Orchestration)
|
||||
|
||||
Agents can invoke other agents using `runSubagent` to orchestrate multi-step workflows.
|
||||
|
||||
### How It Works
|
||||
|
||||
Include `agent` in tools list to enable sub-agent invocation:
|
||||
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'agent']
|
||||
```
|
||||
|
||||
Then invoke other agents with `runSubagent`:
|
||||
|
||||
```javascript
|
||||
const result = await runSubagent({
|
||||
description: 'What this step does',
|
||||
prompt: `You are the [Specialist] specialist.
|
||||
|
||||
Context:
|
||||
- Parameter: ${parameterValue}
|
||||
- Input: ${inputPath}
|
||||
- Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Do the specific work
|
||||
2. Write results to output location
|
||||
3. Return summary of completion`
|
||||
});
|
||||
```
|
||||
|
||||
### Basic Pattern
|
||||
|
||||
Structure each sub-agent call with:
|
||||
|
||||
1. **description**: Clear one-line purpose of the sub-agent invocation
|
||||
2. **prompt**: Detailed instructions with substituted variables
|
||||
|
||||
The prompt should include:
|
||||
- Who the sub-agent is (specialist role)
|
||||
- What context it needs (parameters, paths)
|
||||
- What to do (concrete tasks)
|
||||
- Where to write output
|
||||
- What to return (summary)
|
||||
|
||||
### Example: Multi-Step Processing
|
||||
|
||||
```javascript
|
||||
// Step 1: Process data
|
||||
const processing = await runSubagent({
|
||||
description: 'Transform raw input data',
|
||||
prompt: `You are the Data Processor specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/raw/
|
||||
Output: ${basePath}/processed/
|
||||
|
||||
Task:
|
||||
1. Read all files from input directory
|
||||
2. Apply transformations
|
||||
3. Write processed files to output
|
||||
4. Create summary: ${basePath}/processed/summary.md
|
||||
|
||||
Return: Number of files processed and any issues found`
|
||||
});
|
||||
|
||||
// Step 2: Analyze (depends on Step 1)
|
||||
const analysis = await runSubagent({
|
||||
description: 'Analyze processed data',
|
||||
prompt: `You are the Data Analyst specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/processed/
|
||||
Output: ${basePath}/analysis/
|
||||
|
||||
Task:
|
||||
1. Read processed files from input
|
||||
2. Generate analysis report
|
||||
3. Write to: ${basePath}/analysis/report.md
|
||||
|
||||
Return: Key findings and identified patterns`
|
||||
});
|
||||
```
|
||||
|
||||
### Key Points
|
||||
|
||||
- **Pass variables in prompts**: Use `${variableName}` for all dynamic values
|
||||
- **Keep prompts focused**: Clear, specific tasks for each sub-agent
|
||||
- **Return summaries**: Each sub-agent should report what it accomplished
|
||||
- **Sequential execution**: Use `await` to maintain order when steps depend on each other
|
||||
- **Error handling**: Check results before proceeding to dependent steps
|
||||
|
||||
### ⚠️ Tool Availability Requirement
|
||||
|
||||
**Critical**: If a sub-agent requires specific tools (e.g., `edit`, `execute`, `search`), the orchestrator must include those tools in its own `tools` list. Sub-agents cannot access tools that aren't available to their parent orchestrator.
|
||||
|
||||
**Example**:
|
||||
```yaml
|
||||
# If your sub-agents need to edit files, execute commands, or search code
|
||||
tools: ['read', 'edit', 'search', 'execute', 'agent']
|
||||
```
|
||||
|
||||
The orchestrator's tool permissions act as a ceiling for all invoked sub-agents. Plan your tool list carefully to ensure all sub-agents have the tools they need.
|
||||
|
||||
### ⚠️ Important Limitation
|
||||
|
||||
**Sub-agent orchestration is NOT suitable for large-scale data processing.** Avoid using `runSubagent` when:
|
||||
- Processing hundreds or thousands of files
|
||||
- Handling large datasets
|
||||
- Performing bulk transformations on big codebases
|
||||
- Orchestrating more than 5-10 sequential steps
|
||||
|
||||
Each sub-agent call adds latency and context overhead. For high-volume processing, implement logic directly in a single agent instead. Use orchestration only for coordinating specialized tasks on focused, manageable datasets.
|
||||
|
||||
## Agent Prompt Structure
|
||||
|
||||
The markdown content below the frontmatter defines the agent's behavior, expertise, and instructions. Well-structured prompts typically include:
|
||||
|
||||
1. **Agent Identity and Role**: Who the agent is and its primary role
|
||||
2. **Core Responsibilities**: What specific tasks the agent performs
|
||||
3. **Approach and Methodology**: How the agent works to accomplish tasks
|
||||
4. **Guidelines and Constraints**: What to do/avoid and quality standards
|
||||
5. **Output Expectations**: Expected output format and quality
|
||||
|
||||
### Prompt Writing Best Practices
|
||||
|
||||
- **Be Specific and Direct**: Use imperative mood ("Analyze", "Generate"); avoid vague terms
|
||||
- **Define Boundaries**: Clearly state scope limits and constraints
|
||||
- **Include Context**: Explain domain expertise and reference relevant frameworks
|
||||
- **Focus on Behavior**: Describe how the agent should think and work
|
||||
- **Use Structured Format**: Headers, bullets, and lists make prompts scannable
|
||||
|
||||
## Variable Definition and Extraction
|
||||
|
||||
Agents can define dynamic parameters to extract values from user input and use them throughout the agent's behavior and sub-agent communications. This enables flexible, context-aware agents that adapt to user-provided data.
|
||||
|
||||
### When to Use Variables
|
||||
|
||||
**Use variables when**:
|
||||
- Agent behavior depends on user input
|
||||
- Need to pass dynamic values to sub-agents
|
||||
- Want to make agents reusable across different contexts
|
||||
- Require parameterized workflows
|
||||
- Need to track or reference user-provided context
|
||||
|
||||
**Examples**:
|
||||
- Extract project name from user prompt
|
||||
- Capture certification name for pipeline processing
|
||||
- Identify file paths or directories
|
||||
- Extract configuration options
|
||||
- Parse feature names or module identifiers
|
||||
|
||||
### Variable Declaration Pattern
|
||||
|
||||
Define variables section early in the agent prompt to document expected parameters:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
|
||||
- **Parameter Name**: Description and usage
|
||||
- **Another Parameter**: How it's extracted and used
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process [PARAMETER_NAME] to accomplish [task].
|
||||
```
|
||||
|
||||
### Variable Extraction Methods
|
||||
|
||||
#### 1. **Explicit User Input**
|
||||
Ask the user to provide the variable if not detected in the prompt:
|
||||
|
||||
```markdown
|
||||
## Your Mission
|
||||
|
||||
Process the project by analyzing your codebase.
|
||||
|
||||
### Step 1: Identify Project
|
||||
If no project name is provided, **ASK THE USER** for:
|
||||
- Project name or identifier
|
||||
- Base path or directory location
|
||||
- Configuration type (if applicable)
|
||||
|
||||
Use this information to contextualize all subsequent tasks.
|
||||
```
|
||||
|
||||
#### 2. **Implicit Extraction from Prompt**
|
||||
Automatically extract variables from the user's natural language input:
|
||||
|
||||
```javascript
|
||||
// Example: Extract certification name from user input
|
||||
const userInput = "Process My Certification";
|
||||
|
||||
// Extract key information
|
||||
const certificationName = extractCertificationName(userInput);
|
||||
// Result: "My Certification"
|
||||
|
||||
const basePath = `certifications/${certificationName}`;
|
||||
// Result: "certifications/My Certification"
|
||||
```
|
||||
|
||||
#### 3. **Contextual Variable Resolution**
|
||||
Use file context or workspace information to derive variables:
|
||||
|
||||
```markdown
|
||||
## Variable Resolution Strategy
|
||||
|
||||
1. **From User Prompt**: First, look for explicit mentions in user input
|
||||
2. **From File Context**: Check current file name or path
|
||||
3. **From Workspace**: Use workspace folder or active project
|
||||
4. **From Settings**: Reference configuration files
|
||||
5. **Ask User**: If all else fails, request missing information
|
||||
```
|
||||
|
||||
### Using Variables in Agent Prompts
|
||||
|
||||
#### Variable Substitution in Instructions
|
||||
|
||||
Use template variables in agent prompts to make them dynamic:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
- **Project Name**: ${projectName}
|
||||
- **Base Path**: ${basePath}
|
||||
- **Output Directory**: ${outputDir}
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process the **${projectName}** project located at `${basePath}`.
|
||||
|
||||
## Process Steps
|
||||
|
||||
1. Read input from: `${basePath}/input/`
|
||||
2. Process files according to project configuration
|
||||
3. Write results to: `${outputDir}/`
|
||||
4. Generate summary report
|
||||
|
||||
## Quality Standards
|
||||
|
||||
- Maintain project-specific coding standards for **${projectName}**
|
||||
- Follow directory structure: `${basePath}/[structure]`
|
||||
```
|
||||
|
||||
#### Passing Variables to Sub-Agents
|
||||
|
||||
When invoking a sub-agent, pass all context through template variables in the prompt:
|
||||
|
||||
```javascript
|
||||
// Extract and prepare variables
|
||||
const basePath = `projects/${projectName}`;
|
||||
const inputPath = `${basePath}/src/`;
|
||||
const outputPath = `${basePath}/docs/`;
|
||||
|
||||
// Pass to sub-agent with all variables substituted
|
||||
const result = await runSubagent({
|
||||
description: 'Generate project documentation',
|
||||
prompt: `You are the Documentation specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${inputPath}
|
||||
Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Read source files from ${inputPath}
|
||||
2. Generate comprehensive documentation
|
||||
3. Write to ${outputPath}/index.md
|
||||
4. Include code examples and usage guides
|
||||
|
||||
Return: Summary of documentation generated (file count, word count)`
|
||||
});
|
||||
```
|
||||
|
||||
The sub-agent receives all necessary context embedded in the prompt. Variables are resolved before sending the prompt, so the sub-agent works with concrete paths and values, not variable placeholders.
|
||||
|
||||
### Real-World Example: Code Review Orchestrator
|
||||
|
||||
Example of a simple orchestrator that validates code through multiple specialized agents:
|
||||
|
||||
```javascript
|
||||
async function reviewCodePipeline(repositoryName, prNumber) {
|
||||
const basePath = `projects/${repositoryName}/pr-${prNumber}`;
|
||||
|
||||
// Step 1: Security Review
|
||||
const security = await runSubagent({
|
||||
description: 'Scan for security vulnerabilities',
|
||||
prompt: `You are the Security Reviewer specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Code: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Scan code for OWASP Top 10 vulnerabilities
|
||||
2. Check for injection attacks, auth flaws
|
||||
3. Write findings to ${basePath}/security-review.md
|
||||
|
||||
Return: List of critical, high, and medium issues found`
|
||||
});
|
||||
|
||||
// Step 2: Test Coverage Check
|
||||
const coverage = await runSubagent({
|
||||
description: 'Verify test coverage for changes',
|
||||
prompt: `You are the Test Coverage specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Changes: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Analyze code coverage for modified files
|
||||
2. Identify untested critical paths
|
||||
3. Write report to ${basePath}/coverage-report.md
|
||||
|
||||
Return: Current coverage percentage and gaps`
|
||||
});
|
||||
|
||||
// Step 3: Aggregate Results
|
||||
const finalReport = await runSubagent({
|
||||
description: 'Compile all review findings',
|
||||
prompt: `You are the Review Aggregator specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
Reports: ${basePath}/*.md
|
||||
|
||||
Task:
|
||||
1. Read all review reports from ${basePath}/
|
||||
2. Synthesize findings into single report
|
||||
3. Determine overall verdict (APPROVE/NEEDS_FIXES/BLOCK)
|
||||
4. Write to ${basePath}/final-review.md
|
||||
|
||||
Return: Final verdict and executive summary`
|
||||
});
|
||||
|
||||
return finalReport;
|
||||
}
|
||||
```
|
||||
|
||||
This pattern applies to any orchestration scenario: extract variables, call sub-agents with clear context, await results.
|
||||
|
||||
|
||||
### Variable Best Practices
|
||||
|
||||
#### 1. **Clear Documentation**
|
||||
Always document what variables are expected:
|
||||
|
||||
```markdown
|
||||
## Required Variables
|
||||
- **projectName**: The name of the project (string, required)
|
||||
- **basePath**: Root directory for project files (path, required)
|
||||
|
||||
## Optional Variables
|
||||
- **mode**: Processing mode - quick/standard/detailed (enum, default: standard)
|
||||
- **outputFormat**: Output format - markdown/json/html (enum, default: markdown)
|
||||
|
||||
## Derived Variables
|
||||
- **outputDir**: Automatically set to ${basePath}/output
|
||||
- **logFile**: Automatically set to ${basePath}/.log.md
|
||||
```
|
||||
|
||||
#### 2. **Consistent Naming**
|
||||
Use consistent variable naming conventions:
|
||||
|
||||
```javascript
|
||||
// Good: Clear, descriptive naming
|
||||
const variables = {
|
||||
projectName, // What project to work on
|
||||
basePath, // Where project files are located
|
||||
outputDirectory, // Where to save results
|
||||
processingMode, // How to process (detail level)
|
||||
configurationPath // Where config files are
|
||||
};
|
||||
|
||||
// Avoid: Ambiguous or inconsistent
|
||||
const bad_variables = {
|
||||
name, // Too generic
|
||||
path, // Unclear which path
|
||||
mode, // Too short
|
||||
config // Too vague
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. **Validation and Constraints**
|
||||
Document valid values and constraints:
|
||||
|
||||
```markdown
|
||||
## Variable Constraints
|
||||
|
||||
**projectName**:
|
||||
- Type: string (alphanumeric, hyphens, underscores allowed)
|
||||
- Length: 1-100 characters
|
||||
- Required: yes
|
||||
- Pattern: `/^[a-zA-Z0-9_-]+$/`
|
||||
|
||||
**processingMode**:
|
||||
- Type: enum
|
||||
- Valid values: "quick" (< 5min), "standard" (5-15min), "detailed" (15+ min)
|
||||
- Default: "standard"
|
||||
- Required: no
|
||||
```
|
||||
|
||||
## MCP Server Configuration (Organization/Enterprise Only)
|
||||
|
||||
MCP servers extend agent capabilities with additional tools. Only supported for organization and enterprise-level agents.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-custom-agent
|
||||
description: 'Agent with MCP integration'
|
||||
tools: ['read', 'edit', 'custom-mcp/tool-1']
|
||||
mcp-servers:
|
||||
custom-mcp:
|
||||
type: 'local'
|
||||
command: 'some-command'
|
||||
args: ['--arg1', '--arg2']
|
||||
tools: ["*"]
|
||||
env:
|
||||
ENV_VAR_NAME: ${{ secrets.API_KEY }}
|
||||
---
|
||||
```
|
||||
|
||||
### MCP Server Properties
|
||||
|
||||
- **type**: Server type (`'local'` or `'stdio'`)
|
||||
- **command**: Command to start the MCP server
|
||||
- **args**: Array of command arguments
|
||||
- **tools**: Tools to enable from this server (`["*"]` for all)
|
||||
- **env**: Environment variables (supports secrets)
|
||||
|
||||
### Environment Variables and Secrets
|
||||
|
||||
Secrets must be configured in repository settings under "copilot" environment.
|
||||
|
||||
**Supported syntax**:
|
||||
```yaml
|
||||
env:
|
||||
# Environment variable only
|
||||
VAR_NAME: COPILOT_MCP_ENV_VAR_VALUE
|
||||
|
||||
# Variable with header
|
||||
VAR_NAME: $COPILOT_MCP_ENV_VAR_VALUE
|
||||
VAR_NAME: ${COPILOT_MCP_ENV_VAR_VALUE}
|
||||
|
||||
# GitHub Actions-style (YAML only)
|
||||
VAR_NAME: ${{ secrets.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
VAR_NAME: ${{ var.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
```
|
||||
|
||||
## File Organization and Naming
|
||||
|
||||
### Repository-Level Agents
|
||||
- Location: `.github/agents/`
|
||||
- Scope: Available only in the specific repository
|
||||
- Access: Uses repository-configured MCP servers
|
||||
|
||||
### Organization/Enterprise-Level Agents
|
||||
- Location: `.github-private/agents/` (then move to `agents/` root)
|
||||
- Scope: Available across all repositories in org/enterprise
|
||||
- Access: Can configure dedicated MCP servers
|
||||
|
||||
### Naming Conventions
|
||||
- Use lowercase with hyphens: `test-specialist.agent.md`
|
||||
- Name should reflect agent purpose
|
||||
- Filename becomes default agent name (if `name` not specified)
|
||||
- Allowed characters: `.`, `-`, `_`, `a-z`, `A-Z`, `0-9`
|
||||
|
||||
## Agent Processing and Behavior
|
||||
|
||||
### Versioning
|
||||
- Based on Git commit SHAs for the agent file
|
||||
- Create branches/tags for different agent versions
|
||||
- Instantiated using latest version for repository/branch
|
||||
- PR interactions use same agent version for consistency
|
||||
|
||||
### Name Conflicts
|
||||
Priority (highest to lowest):
|
||||
1. Repository-level agent
|
||||
2. Organization-level agent
|
||||
3. Enterprise-level agent
|
||||
|
||||
Lower-level configurations override higher-level ones with the same name.
|
||||
|
||||
### Tool Processing
|
||||
- `tools` list filters available tools (built-in and MCP)
|
||||
- No tools specified = all tools enabled
|
||||
- Empty list (`[]`) = all tools disabled
|
||||
- Specific list = only those tools enabled
|
||||
- Unrecognized tool names are ignored (allows environment-specific tools)
|
||||
|
||||
### MCP Server Processing Order
|
||||
1. Out-of-the-box MCP servers (e.g., GitHub MCP)
|
||||
2. Custom agent MCP configuration (org/enterprise only)
|
||||
3. Repository-level MCP configurations
|
||||
|
||||
Each level can override settings from previous levels.
|
||||
|
||||
## Agent Creation Checklist
|
||||
|
||||
### Frontmatter
|
||||
- [ ] `description` field present and descriptive (50-150 chars)
|
||||
- [ ] `description` wrapped in single quotes
|
||||
- [ ] `name` specified (optional but recommended)
|
||||
- [ ] `tools` configured appropriately (or intentionally omitted)
|
||||
- [ ] `model` specified for optimal performance
|
||||
- [ ] `target` set if environment-specific
|
||||
- [ ] `infer` set to `false` if manual selection required
|
||||
|
||||
### Prompt Content
|
||||
- [ ] Clear agent identity and role defined
|
||||
- [ ] Core responsibilities listed explicitly
|
||||
- [ ] Approach and methodology explained
|
||||
- [ ] Guidelines and constraints specified
|
||||
- [ ] Output expectations documented
|
||||
- [ ] Examples provided where helpful
|
||||
- [ ] Instructions are specific and actionable
|
||||
- [ ] Scope and boundaries clearly defined
|
||||
- [ ] Total content under 30,000 characters
|
||||
|
||||
### File Structure
|
||||
- [ ] Filename follows lowercase-with-hyphens convention
|
||||
- [ ] File placed in correct directory (`.github/agents/` or `agents/`)
|
||||
- [ ] Filename uses only allowed characters
|
||||
- [ ] File extension is `.agent.md`
|
||||
|
||||
### Quality Assurance
|
||||
- [ ] Agent purpose is unique and not duplicative
|
||||
- [ ] Tools are minimal and necessary
|
||||
- [ ] Instructions are clear and unambiguous
|
||||
- [ ] Agent has been tested with representative tasks
|
||||
- [ ] Documentation references are current
|
||||
- [ ] Security considerations addressed (if applicable)
|
||||
|
||||
## Common Agent Patterns
|
||||
|
||||
### Testing Specialist
|
||||
**Purpose**: Focus on test coverage and quality
|
||||
**Tools**: All tools (for comprehensive test creation)
|
||||
**Approach**: Analyze, identify gaps, write tests, avoid production code changes
|
||||
|
||||
### Implementation Planner
|
||||
**Purpose**: Create detailed technical plans and specifications
|
||||
**Tools**: Limited to `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze requirements, create documentation, avoid implementation
|
||||
|
||||
### Code Reviewer
|
||||
**Purpose**: Review code quality and provide feedback
|
||||
**Tools**: `['read', 'search']` only
|
||||
**Approach**: Analyze, suggest improvements, no direct modifications
|
||||
|
||||
### Refactoring Specialist
|
||||
**Purpose**: Improve code structure and maintainability
|
||||
**Tools**: `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze patterns, propose refactorings, implement safely
|
||||
|
||||
### Security Auditor
|
||||
**Purpose**: Identify security issues and vulnerabilities
|
||||
**Tools**: `['read', 'search', 'web']`
|
||||
**Approach**: Scan code, check against OWASP, report findings
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
### Frontmatter Errors
|
||||
- ❌ Missing `description` field
|
||||
- ❌ Description not wrapped in quotes
|
||||
- ❌ Invalid tool names without checking documentation
|
||||
- ❌ Incorrect YAML syntax (indentation, quotes)
|
||||
|
||||
### Tool Configuration Issues
|
||||
- ❌ Granting excessive tool access unnecessarily
|
||||
- ❌ Missing required tools for agent's purpose
|
||||
- ❌ Not using tool aliases consistently
|
||||
- ❌ Forgetting MCP server namespace (`server-name/tool`)
|
||||
|
||||
### Prompt Content Problems
|
||||
- ❌ Vague, ambiguous instructions
|
||||
- ❌ Conflicting or contradictory guidelines
|
||||
- ❌ Lack of clear scope definition
|
||||
- ❌ Missing output expectations
|
||||
- ❌ Overly verbose instructions (exceeding character limits)
|
||||
- ❌ No examples or context for complex tasks
|
||||
|
||||
### Organizational Issues
|
||||
- ❌ Filename doesn't reflect agent purpose
|
||||
- ❌ Wrong directory (confusing repo vs org level)
|
||||
- ❌ Using spaces or special characters in filename
|
||||
- ❌ Duplicate agent names causing conflicts
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### Manual Testing
|
||||
1. Create the agent file with proper frontmatter
|
||||
2. Reload VS Code or refresh GitHub.com
|
||||
3. Select the agent from the dropdown in Copilot Chat
|
||||
4. Test with representative user queries
|
||||
5. Verify tool access works as expected
|
||||
6. Confirm output meets expectations
|
||||
|
||||
### Integration Testing
|
||||
- Test agent with different file types in scope
|
||||
- Verify MCP server connectivity (if configured)
|
||||
- Check agent behavior with missing context
|
||||
- Test error handling and edge cases
|
||||
- Validate agent switching and handoffs
|
||||
|
||||
### Quality Checks
|
||||
- Run through agent creation checklist
|
||||
- Review against common mistakes list
|
||||
- Compare with example agents in repository
|
||||
- Get peer review for complex agents
|
||||
- Document any special configuration needs
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Creating Custom Agents](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents)
|
||||
- [Custom Agents Configuration](https://docs.github.com/en/copilot/reference/custom-agents-configuration)
|
||||
- [Custom Agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents)
|
||||
- [MCP Integration](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp)
|
||||
|
||||
### Community Resources
|
||||
- [Awesome Copilot Agents Collection](https://github.com/github/awesome-copilot/tree/main/agents)
|
||||
- [Customization Library Examples](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents)
|
||||
- [Your First Custom Agent Tutorial](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents/your-first-custom-agent)
|
||||
|
||||
### Related Files
|
||||
- [Prompt Files Guidelines](./prompt.instructions.md) - For creating prompt files
|
||||
- [Instructions Guidelines](./instructions.instructions.md) - For creating instruction files
|
||||
|
||||
## Version Compatibility Notes
|
||||
|
||||
### GitHub.com (Coding Agent)
|
||||
- ✅ Fully supports all standard frontmatter properties
|
||||
- ✅ Repository and org/enterprise level agents
|
||||
- ✅ MCP server configuration (org/enterprise)
|
||||
- ❌ Does not support `model`, `argument-hint`, `handoffs` properties
|
||||
|
||||
### VS Code / JetBrains / Eclipse / Xcode
|
||||
- ✅ Supports `model` property for AI model selection
|
||||
- ✅ Supports `argument-hint` and `handoffs` properties
|
||||
- ✅ User profile and workspace-level agents
|
||||
- ❌ Cannot configure MCP servers at repository level
|
||||
- ⚠️ Some properties may behave differently
|
||||
|
||||
When creating agents for multiple environments, focus on common properties and test in all target environments. Use `target` property to create environment-specific agents when necessary.
|
||||
136
instructions/kubernetes-manifests.instructions.md
Normal file
136
instructions/kubernetes-manifests.instructions.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
applyTo: 'k8s/**/*.yaml,k8s/**/*.yml,manifests/**/*.yaml,manifests/**/*.yml,deploy/**/*.yaml,deploy/**/*.yml,charts/**/templates/**/*.yaml,charts/**/templates/**/*.yml'
|
||||
description: 'Best practices for Kubernetes YAML manifests including labeling conventions, security contexts, pod security, resource management, probes, and validation commands'
|
||||
---
|
||||
|
||||
# Kubernetes Manifests Instructions
|
||||
|
||||
## Your Mission
|
||||
|
||||
Create production-ready Kubernetes manifests that prioritize security, reliability, and operational excellence with consistent labeling, proper resource management, and comprehensive health checks.
|
||||
|
||||
## Labeling Conventions
|
||||
|
||||
**Required Labels** (Kubernetes recommended):
|
||||
- `app.kubernetes.io/name`: Application name
|
||||
- `app.kubernetes.io/instance`: Instance identifier
|
||||
- `app.kubernetes.io/version`: Version
|
||||
- `app.kubernetes.io/component`: Component role
|
||||
- `app.kubernetes.io/part-of`: Application group
|
||||
- `app.kubernetes.io/managed-by`: Management tool
|
||||
|
||||
**Additional Labels**:
|
||||
- `environment`: Environment name
|
||||
- `team`: Owning team
|
||||
- `cost-center`: For billing
|
||||
|
||||
**Useful Annotations**:
|
||||
- Documentation and ownership
|
||||
- Monitoring: `prometheus.io/scrape`, `prometheus.io/port`, `prometheus.io/path`
|
||||
- Change tracking: git commit, deployment date
|
||||
|
||||
## SecurityContext Defaults
|
||||
|
||||
**Pod-level**:
|
||||
- `runAsNonRoot: true`
|
||||
- `runAsUser` and `runAsGroup`: Specific IDs
|
||||
- `fsGroup`: File system group
|
||||
- `seccompProfile.type: RuntimeDefault`
|
||||
|
||||
**Container-level**:
|
||||
- `allowPrivilegeEscalation: false`
|
||||
- `readOnlyRootFilesystem: true` (with tmpfs mounts for writable dirs)
|
||||
- `capabilities.drop: [ALL]` (add only what's needed)
|
||||
|
||||
## Pod Security Standards
|
||||
|
||||
Use Pod Security Admission:
|
||||
- **Restricted** (recommended for production): Enforces security hardening
|
||||
- **Baseline**: Minimal security requirements
|
||||
- Apply at namespace level
|
||||
|
||||
## Resource Requests and Limits
|
||||
|
||||
**Always define**:
|
||||
- Requests: Guaranteed minimum (scheduling)
|
||||
- Limits: Maximum allowed (prevents exhaustion)
|
||||
|
||||
**QoS Classes**:
|
||||
- **Guaranteed**: requests == limits (best for critical apps)
|
||||
- **Burstable**: requests < limits (flexible resource use)
|
||||
- **BestEffort**: No resources defined (avoid in production)
|
||||
|
||||
## Health Probes
|
||||
|
||||
**Liveness**: Restart unhealthy containers
|
||||
**Readiness**: Control traffic routing
|
||||
**Startup**: Protect slow-starting applications
|
||||
|
||||
Configure appropriate delays, periods, timeouts, and thresholds for each.
|
||||
|
||||
## Rollout Strategies
|
||||
|
||||
**Deployment Strategy**:
|
||||
- `RollingUpdate` with `maxSurge` and `maxUnavailable`
|
||||
- Set `maxUnavailable: 0` for zero-downtime
|
||||
|
||||
**High Availability**:
|
||||
- Minimum 2-3 replicas
|
||||
- Pod Disruption Budget (PDB)
|
||||
- Anti-affinity rules (spread across nodes/zones)
|
||||
- Horizontal Pod Autoscaler (HPA) for variable load
|
||||
|
||||
## Validation Commands
|
||||
|
||||
**Pre-deployment**:
|
||||
- `kubectl apply --dry-run=client -f manifest.yaml`
|
||||
- `kubectl apply --dry-run=server -f manifest.yaml`
|
||||
- `kubeconform -strict manifest.yaml` (schema validation)
|
||||
- `helm template ./chart | kubeconform -strict` (for Helm)
|
||||
|
||||
**Policy Validation**:
|
||||
- OPA Conftest, Kyverno, or Datree
|
||||
|
||||
## Rollout & Rollback
|
||||
|
||||
**Deploy**:
|
||||
- `kubectl apply -f manifest.yaml`
|
||||
- `kubectl rollout status deployment/NAME`
|
||||
|
||||
**Rollback**:
|
||||
- `kubectl rollout undo deployment/NAME`
|
||||
- `kubectl rollout undo deployment/NAME --to-revision=N`
|
||||
- `kubectl rollout history deployment/NAME`
|
||||
|
||||
**Restart**:
|
||||
- `kubectl rollout restart deployment/NAME`
|
||||
|
||||
## Manifest Checklist
|
||||
|
||||
- [ ] Labels: Standard labels applied
|
||||
- [ ] Annotations: Documentation and monitoring
|
||||
- [ ] Security: runAsNonRoot, readOnlyRootFilesystem, dropped capabilities
|
||||
- [ ] Resources: Requests and limits defined
|
||||
- [ ] Probes: Liveness, readiness, startup configured
|
||||
- [ ] Images: Specific tags (never :latest)
|
||||
- [ ] Replicas: Minimum 2-3 for production
|
||||
- [ ] Strategy: RollingUpdate with appropriate surge/unavailable
|
||||
- [ ] PDB: Defined for production
|
||||
- [ ] Anti-affinity: Configured for HA
|
||||
- [ ] Graceful shutdown: terminationGracePeriodSeconds set
|
||||
- [ ] Validation: Dry-run and kubeconform passed
|
||||
- [ ] Secrets: In Secrets resource, not ConfigMaps
|
||||
- [ ] NetworkPolicy: Least-privilege access (if applicable)
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
1. Use standard labels and annotations
|
||||
2. Always run as non-root with dropped capabilities
|
||||
3. Define resource requests and limits
|
||||
4. Implement all three probe types
|
||||
5. Pin image tags to specific versions
|
||||
6. Configure anti-affinity for HA
|
||||
7. Set Pod Disruption Budgets
|
||||
8. Use rolling updates with zero unavailability
|
||||
9. Validate manifests before applying
|
||||
10. Enable read-only root filesystem when possible
|
||||
426
instructions/lwc.instructions.md
Normal file
426
instructions/lwc.instructions.md
Normal file
@@ -0,0 +1,426 @@
|
||||
---
|
||||
description: 'Guidelines and best practices for developing Lightning Web Components (LWC) on Salesforce Platform.'
|
||||
applyTo: 'force-app/main/default/lwc/**'
|
||||
---
|
||||
|
||||
# LWC Development
|
||||
|
||||
## General Instructions
|
||||
|
||||
- Each LWC should reside in its own folder under `force-app/main/default/lwc/`.
|
||||
- The folder name should match the component name (e.g., `myComponent` folder for the `myComponent` component).
|
||||
- Each component folder should contain the following files:
|
||||
- `myComponent.html`: The HTML template file.
|
||||
- `myComponent.js`: The JavaScript controller file.
|
||||
- `myComponent.js-meta.xml`: The metadata configuration file.
|
||||
- Optional: `myComponent.css` for component-specific styles.
|
||||
- Optional: `myComponent.test.js` for Jest unit tests.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Use Lightning Components Over HTML Tags
|
||||
Always prefer Lightning Web Component library components over plain HTML elements for consistency, accessibility, and future-proofing.
|
||||
|
||||
#### Recommended Approach
|
||||
```html
|
||||
<!-- Use Lightning components -->
|
||||
<lightning-button label="Save" variant="brand" onclick={handleSave}></lightning-button>
|
||||
<lightning-input type="text" label="Name" value={name} onchange={handleNameChange}></lightning-input>
|
||||
<lightning-combobox label="Type" options={typeOptions} value={selectedType}></lightning-combobox>
|
||||
<lightning-radio-group name="duration" label="Duration" options={durationOptions} value={duration} type="radio"></lightning-radio-group>
|
||||
```
|
||||
|
||||
#### Avoid Plain HTML
|
||||
```html
|
||||
<!-- Avoid these -->
|
||||
<button onclick={handleSave}>Save</button>
|
||||
<input type="text" onchange={handleNameChange} />
|
||||
<select onchange={handleTypeChange}>
|
||||
<option value="option1">Option 1</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
### 2. Lightning Component Mapping Guide
|
||||
|
||||
| HTML Element | Lightning Component | Key Attributes |
|
||||
|--------------|-------------------|----------------|
|
||||
| `<button>` | `<lightning-button>` | `variant`, `label`, `icon-name` |
|
||||
| `<input>` | `<lightning-input>` | `type`, `label`, `variant` |
|
||||
| `<select>` | `<lightning-combobox>` | `options`, `value`, `placeholder` |
|
||||
| `<textarea>` | `<lightning-textarea>` | `label`, `max-length` |
|
||||
| `<input type="checkbox">` | `<lightning-input type="checkbox">` | `checked`, `label` |
|
||||
| `<input type="radio">` | `<lightning-radio-group>` | `options`, `type`, `name` |
|
||||
| `<input type="toggle">` | `<lightning-input type="toggle">` | `checked`, `variant` |
|
||||
| Custom pills | `<lightning-pill>` | `label`, `name`, `onremove` |
|
||||
| Icons | `<lightning-icon>` | `icon-name`, `size`, `variant` |
|
||||
|
||||
### 3. Lightning Design System Compliance
|
||||
|
||||
#### Use SLDS Utility Classes
|
||||
Always use Salesforce Lightning Design System utility classes with the `slds-var-` prefix for modern implementations:
|
||||
|
||||
```html
|
||||
<!-- Spacing -->
|
||||
<div class="slds-var-m-around_medium slds-var-p-top_large">
|
||||
<div class="slds-var-m-bottom_small">Content</div>
|
||||
</div>
|
||||
|
||||
<!-- Layout -->
|
||||
<div class="slds-grid slds-wrap slds-gutters_small">
|
||||
<div class="slds-col slds-size_1-of-2 slds-medium-size_1-of-3">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Typography -->
|
||||
<h2 class="slds-text-heading_medium slds-var-m-bottom_small">Section Title</h2>
|
||||
<p class="slds-text-body_regular">Description text</p>
|
||||
```
|
||||
|
||||
#### SLDS Component Patterns
|
||||
```html
|
||||
<!-- Card Layout -->
|
||||
<article class="slds-card slds-var-m-around_medium">
|
||||
<header class="slds-card__header">
|
||||
<h2 class="slds-text-heading_small">Card Title</h2>
|
||||
</header>
|
||||
<div class="slds-card__body slds-card__body_inner">
|
||||
<!-- Card content -->
|
||||
</div>
|
||||
<footer class="slds-card__footer">
|
||||
<!-- Card actions -->
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- Form Layout -->
|
||||
<div class="slds-form slds-form_stacked">
|
||||
<div class="slds-form-element">
|
||||
<lightning-input label="Field Label" value={fieldValue}></lightning-input>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. Avoid Custom CSS
|
||||
|
||||
#### Use SLDS Classes
|
||||
```html
|
||||
<!-- Color and theming -->
|
||||
<div class="slds-theme_success slds-text-color_inverse slds-var-p-around_small">
|
||||
Success message
|
||||
</div>
|
||||
|
||||
<div class="slds-theme_error slds-text-color_inverse slds-var-p-around_small">
|
||||
Error message
|
||||
</div>
|
||||
|
||||
<div class="slds-theme_warning slds-text-color_inverse slds-var-p-around_small">
|
||||
Warning message
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Avoid Custom CSS (Anti-Pattern)
|
||||
```css
|
||||
/* Don't create custom styles that override SLDS */
|
||||
.custom-button {
|
||||
background-color: red;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.my-special-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
```
|
||||
|
||||
#### When Custom CSS is Necessary
|
||||
If you must use custom CSS, follow these guidelines:
|
||||
- Use CSS custom properties (design tokens) when possible
|
||||
- Prefix custom classes to avoid conflicts
|
||||
- Never override SLDS base classes
|
||||
|
||||
```css
|
||||
/* Custom CSS example */
|
||||
.my-component-special {
|
||||
border-radius: var(--lwc-borderRadiusMedium);
|
||||
box-shadow: var(--lwc-shadowButton);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Component Architecture Best Practices
|
||||
|
||||
#### Reactive Properties
|
||||
```javascript
|
||||
import { LightningElement, track, api } from 'lwc';
|
||||
|
||||
export default class MyComponent extends LightningElement {
|
||||
// Use @api for public properties
|
||||
@api recordId;
|
||||
@api title;
|
||||
|
||||
// Primitive properties (string, number, boolean) are automatically reactive
|
||||
// No decorator needed - reassignment triggers re-render
|
||||
simpleValue = 'initial';
|
||||
count = 0;
|
||||
|
||||
// Computed properties
|
||||
get displayName() {
|
||||
return this.name ? `Hello, ${this.name}` : 'Hello, Guest';
|
||||
}
|
||||
|
||||
// @track is NOT needed for simple property reassignment
|
||||
// This will trigger reactivity automatically:
|
||||
handleUpdate() {
|
||||
this.simpleValue = 'updated'; // Reactive without @track
|
||||
this.count++; // Reactive without @track
|
||||
}
|
||||
|
||||
// @track IS needed when mutating nested properties without reassignment
|
||||
@track complexData = {
|
||||
user: {
|
||||
name: 'John',
|
||||
preferences: {
|
||||
theme: 'dark'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDeepUpdate() {
|
||||
// Requires @track because we're mutating a nested property
|
||||
this.complexData.user.preferences.theme = 'light';
|
||||
}
|
||||
|
||||
// BETTER: Avoid @track by using immutable patterns
|
||||
regularData = {
|
||||
user: {
|
||||
name: 'John',
|
||||
preferences: {
|
||||
theme: 'dark'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleImmutableUpdate() {
|
||||
// No @track needed - we're creating a new object reference
|
||||
this.regularData = {
|
||||
...this.regularData,
|
||||
user: {
|
||||
...this.regularData.user,
|
||||
preferences: {
|
||||
...this.regularData.user.preferences,
|
||||
theme: 'light'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Arrays: @track is needed only for mutating methods
|
||||
@track items = ['a', 'b', 'c'];
|
||||
|
||||
handleArrayMutation() {
|
||||
// Requires @track
|
||||
this.items.push('d');
|
||||
this.items[0] = 'z';
|
||||
}
|
||||
|
||||
// BETTER: Use immutable array operations
|
||||
regularItems = ['a', 'b', 'c'];
|
||||
|
||||
handleImmutableArray() {
|
||||
// No @track needed
|
||||
this.regularItems = [...this.regularItems, 'd'];
|
||||
this.regularItems = this.regularItems.map((item, idx) =>
|
||||
idx === 0 ? 'z' : item
|
||||
);
|
||||
}
|
||||
|
||||
// Use @track only for complex objects/arrays when you mutate nested properties.
|
||||
// For example, updating complexObject.details.status without reassigning complexObject.
|
||||
@track complexObject = {
|
||||
details: {
|
||||
status: 'new'
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Event Handling Patterns
|
||||
```javascript
|
||||
// Custom event dispatch
|
||||
handleSave() {
|
||||
const saveEvent = new CustomEvent('save', {
|
||||
detail: {
|
||||
recordData: this.recordData,
|
||||
timestamp: new Date()
|
||||
}
|
||||
});
|
||||
this.dispatchEvent(saveEvent);
|
||||
}
|
||||
|
||||
// Lightning component event handling
|
||||
handleInputChange(event) {
|
||||
const fieldName = event.target.name;
|
||||
const fieldValue = event.target.value;
|
||||
|
||||
// For lightning-input, lightning-combobox, etc.
|
||||
this[fieldName] = fieldValue;
|
||||
}
|
||||
|
||||
handleRadioChange(event) {
|
||||
// For lightning-radio-group
|
||||
this.selectedValue = event.detail.value;
|
||||
}
|
||||
|
||||
handleToggleChange(event) {
|
||||
// For lightning-input type="toggle"
|
||||
this.isToggled = event.detail.checked;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Data Handling and Wire Services
|
||||
|
||||
#### Use @wire for Data Access
|
||||
```javascript
|
||||
import { getRecord } from 'lightning/uiRecordApi';
|
||||
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
|
||||
|
||||
const FIELDS = ['Account.Name', 'Account.Industry', 'Account.AnnualRevenue'];
|
||||
|
||||
export default class MyComponent extends LightningElement {
|
||||
@api recordId;
|
||||
|
||||
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
|
||||
record;
|
||||
|
||||
@wire(getObjectInfo, { objectApiName: 'Account' })
|
||||
objectInfo;
|
||||
|
||||
get recordData() {
|
||||
return this.record.data ? this.record.data.fields : {};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Error Handling and User Experience
|
||||
|
||||
#### Implement Proper Error Boundaries
|
||||
```javascript
|
||||
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
|
||||
|
||||
export default class MyComponent extends LightningElement {
|
||||
isLoading = false;
|
||||
error = null;
|
||||
|
||||
async handleAsyncOperation() {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const result = await this.performOperation();
|
||||
this.showSuccessToast();
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
this.showErrorToast(error.body?.message || 'An error occurred');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
performOperation() {
|
||||
// Developer-defined async operation
|
||||
}
|
||||
|
||||
showSuccessToast() {
|
||||
const event = new ShowToastEvent({
|
||||
title: 'Success',
|
||||
message: 'Operation completed successfully',
|
||||
variant: 'success'
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
showErrorToast(message) {
|
||||
const event = new ShowToastEvent({
|
||||
title: 'Error',
|
||||
message: message,
|
||||
variant: 'error',
|
||||
mode: 'sticky'
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Performance Optimization
|
||||
|
||||
#### Conditional Rendering
|
||||
Prefer `lwc:if`, `lwc:elseif` and `lwc:else` for conditional rendering (API v58.0+). Legacy `if:true` / `if:false` are still supported but should be avoided in new components.
|
||||
|
||||
```html
|
||||
<!-- Use template directives for conditional rendering -->
|
||||
<template lwc:if={isLoading}>
|
||||
<lightning-spinner alternative-text="Loading..."></lightning-spinner>
|
||||
</template>
|
||||
<template lwc:elseif={error}>
|
||||
<div class="slds-theme_error slds-text-color_inverse slds-var-p-around_small">
|
||||
{error.message}
|
||||
</div>
|
||||
</template>
|
||||
<template lwc:else>
|
||||
<template for:each={items} for:item="item">
|
||||
<div key={item.id} class="slds-var-m-bottom_small">
|
||||
{item.name}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Legacy approach (avoid in new components) -->
|
||||
<template if:true={isLoading}>
|
||||
<lightning-spinner alternative-text="Loading..."></lightning-spinner>
|
||||
</template>
|
||||
<template if:true={error}>
|
||||
<div class="slds-theme_error slds-text-color_inverse slds-var-p-around_small">
|
||||
{error.message}
|
||||
</div>
|
||||
</template>
|
||||
<template if:false={isLoading}>
|
||||
<template if:false={error}>
|
||||
<template for:each={items} for:item="item">
|
||||
<div key={item.id} class="slds-var-m-bottom_small">
|
||||
{item.name}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 9. Accessibility Best Practices
|
||||
|
||||
#### Use Proper ARIA Labels and Semantic HTML
|
||||
```html
|
||||
<!-- Use semantic structure -->
|
||||
<section aria-label="Product Selection">
|
||||
<h2 class="slds-text-heading_medium">Products</h2>
|
||||
|
||||
<lightning-input
|
||||
type="search"
|
||||
label="Search Products"
|
||||
placeholder="Enter product name..."
|
||||
aria-describedby="search-help">
|
||||
</lightning-input>
|
||||
|
||||
<div id="search-help" class="slds-assistive-text">
|
||||
Type to filter the product list
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
- **Direct DOM Manipulation**: Never use `document.querySelector()` or similar
|
||||
- **jQuery or External Libraries**: Avoid non-Lightning compatible libraries
|
||||
- **Inline Styles**: Use SLDS classes instead of `style` attributes
|
||||
- **Global CSS**: All styles should be scoped to the component
|
||||
- **Hardcoded Values**: Use custom labels, custom metadata, or constants
|
||||
- **Imperative API Calls**: Prefer `@wire` over imperative `import` calls when possible
|
||||
- **Memory Leaks**: Always clean up event listeners in `disconnectedCallback()`
|
||||
357
instructions/mcp-m365-copilot.instructions.md
Normal file
357
instructions/mcp-m365-copilot.instructions.md
Normal file
@@ -0,0 +1,357 @@
|
||||
---
|
||||
description: 'Best practices for building MCP-based declarative agents and API plugins for Microsoft 365 Copilot with Model Context Protocol integration'
|
||||
applyTo: '**/{*mcp*,*agent*,*plugin*,declarativeAgent.json,ai-plugin.json,mcp.json,manifest.json}'
|
||||
---
|
||||
|
||||
# MCP-based M365 Copilot Development Guidelines
|
||||
|
||||
## Core Principles
|
||||
|
||||
### Model Context Protocol First
|
||||
- Leverage MCP servers for external system integration
|
||||
- Import tools from server endpoints, not manual definitions
|
||||
- Let MCP handle schema discovery and function generation
|
||||
- Use point-and-click tool selection in Agents Toolkit
|
||||
|
||||
### Declarative Over Imperative
|
||||
- Define agent behavior through configuration, not code
|
||||
- Use declarativeAgent.json for instructions and capabilities
|
||||
- Specify tools and actions in ai-plugin.json
|
||||
- Configure MCP servers in mcp.json
|
||||
|
||||
### Security and Governance
|
||||
- Always use OAuth 2.0 or SSO for authentication
|
||||
- Follow principle of least privilege for tool selection
|
||||
- Validate MCP server endpoints are secure
|
||||
- Review compliance requirements before deployment
|
||||
|
||||
### User-Centric Design
|
||||
- Create adaptive cards for rich visual responses
|
||||
- Provide clear conversation starters
|
||||
- Design for responsive experience across hubs
|
||||
- Test thoroughly before organizational deployment
|
||||
|
||||
## MCP Server Design
|
||||
|
||||
### Server Selection
|
||||
Choose MCP servers that:
|
||||
- Expose relevant tools for user tasks
|
||||
- Support secure authentication (OAuth 2.0, SSO)
|
||||
- Provide reliable uptime and performance
|
||||
- Follow MCP specification standards
|
||||
- Return well-structured response data
|
||||
|
||||
### Tool Import Strategy
|
||||
- Import only necessary tools (avoid over-scoping)
|
||||
- Group related tools from same server
|
||||
- Test each tool individually before combining
|
||||
- Consider token limits when selecting multiple tools
|
||||
|
||||
### Authentication Configuration
|
||||
**OAuth 2.0 Static Registration:**
|
||||
```json
|
||||
{
|
||||
"type": "OAuthPluginVault",
|
||||
"reference_id": "YOUR_AUTH_ID",
|
||||
"client_id": "github_client_id",
|
||||
"client_secret": "github_client_secret",
|
||||
"authorization_url": "https://github.com/login/oauth/authorize",
|
||||
"token_url": "https://github.com/login/oauth/access_token",
|
||||
"scope": "repo read:user"
|
||||
}
|
||||
```
|
||||
|
||||
**SSO (Microsoft Entra ID):**
|
||||
```json
|
||||
{
|
||||
"type": "OAuthPluginVault",
|
||||
"reference_id": "sso_auth",
|
||||
"authorization_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
"token_url": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
"scope": "User.Read"
|
||||
}
|
||||
```
|
||||
|
||||
## File Organization
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
project-root/
|
||||
├── appPackage/
|
||||
│ ├── manifest.json # Teams app manifest
|
||||
│ ├── declarativeAgent.json # Agent config (instructions, capabilities)
|
||||
│ ├── ai-plugin.json # API plugin definition
|
||||
│ ├── color.png # App icon color
|
||||
│ └── outline.png # App icon outline
|
||||
├── .vscode/
|
||||
│ └── mcp.json # MCP server configuration
|
||||
├── .env.local # Credentials (NEVER commit)
|
||||
└── teamsapp.yml # Teams Toolkit config
|
||||
```
|
||||
|
||||
### Critical Files
|
||||
|
||||
**declarativeAgent.json:**
|
||||
- Agent name and description
|
||||
- Instructions for behavior
|
||||
- Conversation starters
|
||||
- Capabilities (actions from plugins)
|
||||
|
||||
**ai-plugin.json:**
|
||||
- MCP server tools import
|
||||
- Response semantics (data_path, properties)
|
||||
- Static adaptive card templates
|
||||
- Function definitions (auto-generated)
|
||||
|
||||
**mcp.json:**
|
||||
- MCP server URL
|
||||
- Server metadata endpoint
|
||||
- Authentication reference
|
||||
|
||||
**.env.local:**
|
||||
- OAuth client credentials
|
||||
- API keys and secrets
|
||||
- Environment-specific config
|
||||
- **CRITICAL**: Add to .gitignore
|
||||
|
||||
## Response Semantics Best Practices
|
||||
|
||||
### Data Path Configuration
|
||||
Use JSONPath to extract relevant data:
|
||||
```json
|
||||
{
|
||||
"data_path": "$.items[*]",
|
||||
"properties": {
|
||||
"title": "$.name",
|
||||
"subtitle": "$.description",
|
||||
"url": "$.html_url"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Template Selection
|
||||
For dynamic templates:
|
||||
```json
|
||||
{
|
||||
"data_path": "$",
|
||||
"template_selector": "$.templateType",
|
||||
"properties": {
|
||||
"title": "$.title",
|
||||
"url": "$.url"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Static Templates
|
||||
Define in ai-plugin.json for consistent formatting:
|
||||
- Use when all responses follow same structure
|
||||
- Better performance than dynamic templates
|
||||
- Easier to maintain and version control
|
||||
|
||||
## Adaptive Card Guidelines
|
||||
|
||||
### Design Principles
|
||||
- **Single-column layout**: Stack elements vertically
|
||||
- **Flexible widths**: Use "stretch" or "auto", not fixed pixels
|
||||
- **Responsive design**: Test in Chat, Teams, Outlook
|
||||
- **Minimal complexity**: Keep cards simple and scannable
|
||||
|
||||
### Template Language Patterns
|
||||
**Conditionals:**
|
||||
```json
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${if(status == 'active', '✅ Active', '❌ Inactive')}"
|
||||
}
|
||||
```
|
||||
|
||||
**Data Binding:**
|
||||
```json
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${title}",
|
||||
"weight": "bolder"
|
||||
}
|
||||
```
|
||||
|
||||
**Number Formatting:**
|
||||
```json
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Score: ${formatNumber(score, 0)}"
|
||||
}
|
||||
```
|
||||
|
||||
**Conditional Rendering:**
|
||||
```json
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${count(items) > 0}",
|
||||
"items": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
### Card Elements Usage
|
||||
- **TextBlock**: Titles, descriptions, metadata
|
||||
- **FactSet**: Key-value pairs (status, dates, IDs)
|
||||
- **Image**: Icons, thumbnails (use size: "small")
|
||||
- **Container**: Grouping related content
|
||||
- **ActionSet**: Buttons for follow-up actions
|
||||
|
||||
## Testing and Deployment
|
||||
|
||||
### Local Testing Workflow
|
||||
1. **Provision**: Teams Toolkit → Provision
|
||||
2. **Deploy**: Teams Toolkit → Deploy
|
||||
3. **Sideload**: App uploaded to Teams
|
||||
4. **Test**: Visit [m365.cloud.microsoft/chat](https://m365.cloud.microsoft/chat)
|
||||
5. **Iterate**: Fix issues and re-deploy
|
||||
|
||||
### Pre-Deployment Checklist
|
||||
- [ ] All MCP server tools tested individually
|
||||
- [ ] Authentication flow works end-to-end
|
||||
- [ ] Adaptive cards render correctly across hubs
|
||||
- [ ] Response semantics extract expected data
|
||||
- [ ] Error handling provides clear messages
|
||||
- [ ] Conversation starters are relevant and clear
|
||||
- [ ] Agent instructions guide proper behavior
|
||||
- [ ] Compliance and security reviewed
|
||||
|
||||
### Deployment Options
|
||||
**Organization Deployment:**
|
||||
- IT admin deploys to all or selected users
|
||||
- Requires approval in Microsoft 365 admin center
|
||||
- Best for internal business agents
|
||||
|
||||
**Agent Store:**
|
||||
- Submit to Partner Center for validation
|
||||
- Public availability to all Copilot users
|
||||
- Requires rigorous security review
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Multi-Tool Agent
|
||||
Import tools from multiple MCP servers:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"url": "https://github-mcp.example.com"
|
||||
},
|
||||
"jira": {
|
||||
"url": "https://jira-mcp.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Search and Display
|
||||
1. Tool retrieves data from MCP server
|
||||
2. Response semantics extract relevant fields
|
||||
3. Adaptive card displays formatted results
|
||||
4. User can take action from card buttons
|
||||
|
||||
### Authenticated Actions
|
||||
1. User triggers tool requiring auth
|
||||
2. OAuth flow redirects for consent
|
||||
3. Access token stored in plugin vault
|
||||
4. Subsequent requests use stored token
|
||||
|
||||
## Error Handling
|
||||
|
||||
### MCP Server Errors
|
||||
- Provide clear error messages in agent responses
|
||||
- Fall back to alternative tools if available
|
||||
- Log errors for debugging
|
||||
- Guide user to retry or alternative approach
|
||||
|
||||
### Authentication Failures
|
||||
- Check OAuth credentials in .env.local
|
||||
- Verify scopes match required permissions
|
||||
- Test auth flow outside Copilot first
|
||||
- Ensure token refresh logic works
|
||||
|
||||
### Response Parsing Failures
|
||||
- Validate JSONPath expressions in response semantics
|
||||
- Handle missing or null data gracefully
|
||||
- Provide default values where appropriate
|
||||
- Test with varied API responses
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Tool Selection
|
||||
- Import only necessary tools (reduces token usage)
|
||||
- Avoid redundant tools from multiple servers
|
||||
- Test impact of each tool on response time
|
||||
|
||||
### Response Size
|
||||
- Use data_path to filter unnecessary data
|
||||
- Limit result sets where possible
|
||||
- Consider pagination for large datasets
|
||||
- Keep adaptive cards lightweight
|
||||
|
||||
### Caching Strategy
|
||||
- MCP servers should cache where appropriate
|
||||
- Agent responses may be cached by M365
|
||||
- Consider cache invalidation for time-sensitive data
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Credential Management
|
||||
- **NEVER** commit .env.local to source control
|
||||
- Use environment variables for all secrets
|
||||
- Rotate OAuth credentials regularly
|
||||
- Use separate credentials for dev/prod
|
||||
|
||||
### Data Privacy
|
||||
- Only request minimum necessary scopes
|
||||
- Avoid logging sensitive user data
|
||||
- Review data residency requirements
|
||||
- Follow compliance policies (GDPR, etc.)
|
||||
|
||||
### Server Validation
|
||||
- Verify MCP server is trusted and secure
|
||||
- Check HTTPS endpoints only
|
||||
- Review server's privacy policy
|
||||
- Test for injection vulnerabilities
|
||||
|
||||
## Governance and Compliance
|
||||
|
||||
### Admin Controls
|
||||
Agents can be:
|
||||
- **Blocked**: Prevented from use
|
||||
- **Deployed**: Assigned to specific users/groups
|
||||
- **Published**: Made available organization-wide
|
||||
|
||||
### Monitoring
|
||||
Track:
|
||||
- Agent usage and adoption
|
||||
- Error rates and performance
|
||||
- User feedback and satisfaction
|
||||
- Security incidents
|
||||
|
||||
### Audit Requirements
|
||||
Maintain:
|
||||
- Change history for agent configurations
|
||||
- Access logs for sensitive operations
|
||||
- Approval records for deployments
|
||||
- Compliance attestations
|
||||
|
||||
## Resources and References
|
||||
|
||||
### Official Documentation
|
||||
- [Build Declarative Agents with MCP (DevBlogs)](https://devblogs.microsoft.com/microsoft365dev/build-declarative-agents-for-microsoft-365-copilot-with-mcp/)
|
||||
- [Build MCP Plugins (Learn)](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/build-mcp-plugins)
|
||||
- [API Plugin Adaptive Cards (Learn)](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/api-plugin-adaptive-cards)
|
||||
- [Manage Copilot Agents (Learn)](https://learn.microsoft.com/en-us/microsoft-365/admin/manage/manage-copilot-agents-integrated-apps)
|
||||
|
||||
### Tools and SDKs
|
||||
- Microsoft 365 Agents Toolkit (VS Code extension v6.3.x+)
|
||||
- Teams Toolkit for agent packaging
|
||||
- Adaptive Cards Designer
|
||||
- MCP specification documentation
|
||||
|
||||
### Partner Examples
|
||||
- monday.com: Task management integration
|
||||
- Canva: Design automation
|
||||
- Sitecore: Content management
|
||||
834
instructions/scala2.instructions.md
Normal file
834
instructions/scala2.instructions.md
Normal file
@@ -0,0 +1,834 @@
|
||||
---
|
||||
description: 'Scala 2.12/2.13 programming language coding conventions and best practices following Databricks style guide for functional programming, type safety, and production code quality.'
|
||||
applyTo: '**.scala, **/build.sbt, **/build.sc'
|
||||
---
|
||||
|
||||
# Scala Best Practices
|
||||
|
||||
Based on the [Databricks Scala Style Guide](https://github.com/databricks/scala-style-guide)
|
||||
|
||||
## Core Principles
|
||||
|
||||
### Write Simple Code
|
||||
Code is written once but read and modified multiple times. Optimize for long-term readability and maintainability by writing simple code.
|
||||
|
||||
### Immutability by Default
|
||||
- Always prefer `val` over `var`
|
||||
- Use immutable collections from `scala.collection.immutable`
|
||||
- Case class constructor parameters should NOT be mutable
|
||||
- Use copy constructor to create modified instances
|
||||
|
||||
```scala
|
||||
// Good - Immutable case class
|
||||
case class Person(name: String, age: Int)
|
||||
|
||||
// Bad - Mutable case class
|
||||
case class Person(name: String, var age: Int)
|
||||
|
||||
// To change values, use copy constructor
|
||||
val p1 = Person("Peter", 15)
|
||||
val p2 = p1.copy(age = 16)
|
||||
|
||||
// Good - Immutable collections
|
||||
val users = List(User("Alice", 30), User("Bob", 25))
|
||||
val updatedUsers = users.map(u => u.copy(age = u.age + 1))
|
||||
```
|
||||
|
||||
### Pure Functions
|
||||
- Functions should be deterministic and side-effect free
|
||||
- Separate pure logic from effects
|
||||
- Use explicit types for methods with effects
|
||||
|
||||
```scala
|
||||
// Good - Pure function
|
||||
def calculateTotal(items: List[Item]): BigDecimal =
|
||||
items.map(_.price).sum
|
||||
|
||||
// Bad - Impure function with side effects
|
||||
def calculateTotal(items: List[Item]): BigDecimal = {
|
||||
println(s"Calculating total for ${items.size} items") // Side effect
|
||||
val total = items.map(_.price).sum
|
||||
saveToDatabase(total) // Side effect
|
||||
total
|
||||
}
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Classes and Objects
|
||||
|
||||
```scala
|
||||
// Classes, traits, objects - PascalCase
|
||||
class ClusterManager
|
||||
trait Expression
|
||||
object Configuration
|
||||
|
||||
// Packages - all lowercase ASCII
|
||||
package com.databricks.resourcemanager
|
||||
|
||||
// Methods/functions - camelCase
|
||||
def getUserById(id: Long): Option[User]
|
||||
def processData(input: String): Result
|
||||
|
||||
// Constants - uppercase in companion object
|
||||
object Configuration {
|
||||
val DEFAULT_PORT = 10000
|
||||
val MAX_RETRIES = 3
|
||||
val TIMEOUT_MS = 5000L
|
||||
}
|
||||
```
|
||||
|
||||
### Variables and Parameters
|
||||
|
||||
```scala
|
||||
// Variables - camelCase, self-evident names
|
||||
val serverPort = 1000
|
||||
val clientPort = 2000
|
||||
val maxRetryAttempts = 3
|
||||
|
||||
// One-character names OK in small, localized scope
|
||||
for (i <- 0 until 10) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Do NOT use "l" (Larry) - looks like "1", "|", "I"
|
||||
```
|
||||
|
||||
### Enumerations
|
||||
|
||||
```scala
|
||||
// Enumeration object - PascalCase
|
||||
// Values - UPPER_CASE with underscores
|
||||
private object ParseState extends Enumeration {
|
||||
type ParseState = Value
|
||||
|
||||
val PREFIX,
|
||||
TRIM_BEFORE_SIGN,
|
||||
SIGN,
|
||||
VALUE,
|
||||
UNIT_BEGIN,
|
||||
UNIT_END = Value
|
||||
}
|
||||
```
|
||||
|
||||
## Syntactic Style
|
||||
|
||||
### Line Length and Spacing
|
||||
|
||||
```scala
|
||||
// Limit lines to 100 characters
|
||||
// One space before and after operators
|
||||
def add(int1: Int, int2: Int): Int = int1 + int2
|
||||
|
||||
// One space after commas
|
||||
val list = List("a", "b", "c")
|
||||
|
||||
// One space after colons
|
||||
def getConf(key: String, defaultValue: String): String = {
|
||||
// code
|
||||
}
|
||||
|
||||
// Use 2-space indentation
|
||||
if (true) {
|
||||
println("Wow!")
|
||||
}
|
||||
|
||||
// 4-space indentation for long parameter lists
|
||||
def newAPIHadoopFile[K, V, F <: NewInputFormat[K, V]](
|
||||
path: String,
|
||||
fClass: Class[F],
|
||||
kClass: Class[K],
|
||||
vClass: Class[V],
|
||||
conf: Configuration = hadoopConfiguration): RDD[(K, V)] = {
|
||||
// method body
|
||||
}
|
||||
|
||||
// Class with long parameters
|
||||
class Foo(
|
||||
val param1: String, // 4 space indent
|
||||
val param2: String,
|
||||
val param3: Array[Byte])
|
||||
extends FooInterface // 2 space indent
|
||||
with Logging {
|
||||
|
||||
def firstMethod(): Unit = { ... } // blank line above
|
||||
}
|
||||
```
|
||||
|
||||
### Rule of 30
|
||||
|
||||
- A method should contain less than 30 lines of code
|
||||
- A class should contain less than 30 methods
|
||||
|
||||
### Curly Braces
|
||||
|
||||
```scala
|
||||
// Always use curly braces for multi-line blocks
|
||||
if (true) {
|
||||
println("Wow!")
|
||||
}
|
||||
|
||||
// Exception: one-line ternary (side-effect free)
|
||||
val result = if (condition) value1 else value2
|
||||
|
||||
// Always use braces for try-catch
|
||||
try {
|
||||
foo()
|
||||
} catch {
|
||||
case e: Exception => handle(e)
|
||||
}
|
||||
```
|
||||
|
||||
### Long Literals
|
||||
|
||||
```scala
|
||||
// Use uppercase L for long literals
|
||||
val longValue = 5432L // Do this
|
||||
val badValue = 5432l // Don't do this - hard to see
|
||||
```
|
||||
|
||||
### Parentheses
|
||||
|
||||
```scala
|
||||
// Methods with side-effects - use parentheses
|
||||
class Job {
|
||||
def killJob(): Unit = { ... } // Correct - changes state
|
||||
def getStatus: JobStatus = { ... } // Correct - no side-effect
|
||||
}
|
||||
|
||||
// Callsite should match declaration
|
||||
new Job().killJob() // Correct
|
||||
new Job().getStatus // Correct
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
||||
```scala
|
||||
// Avoid wildcard imports unless importing 6+ entities
|
||||
import scala.collection.mutable.{Map, HashMap, ArrayBuffer}
|
||||
|
||||
// OK to use wildcard for implicits or 6+ items
|
||||
import scala.collection.JavaConverters._
|
||||
import java.util.{Map, HashMap, List, ArrayList, Set, HashSet}
|
||||
|
||||
// Always use absolute paths
|
||||
import scala.util.Random // Good
|
||||
// import util.Random // Don't use relative
|
||||
|
||||
// Import order (with blank lines):
|
||||
import java.io.File
|
||||
import javax.servlet.http.HttpServlet
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
import scala.util.Random
|
||||
|
||||
import org.apache.spark.SparkContext
|
||||
import org.apache.spark.rdd.RDD
|
||||
|
||||
import com.databricks.MyClass
|
||||
```
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
```scala
|
||||
// Put match on same line if method is entirely pattern match
|
||||
def test(msg: Message): Unit = msg match {
|
||||
case TextMessage(text) => handleText(text)
|
||||
case ImageMessage(url) => handleImage(url)
|
||||
}
|
||||
|
||||
// Single case closures - same line
|
||||
list.zipWithIndex.map { case (elem, i) =>
|
||||
// process
|
||||
}
|
||||
|
||||
// Multiple cases - indent and wrap
|
||||
list.map {
|
||||
case a: Foo => processFoo(a)
|
||||
case b: Bar => processBar(b)
|
||||
case _ => handleDefault()
|
||||
}
|
||||
|
||||
// Match on type only - don't expand all args
|
||||
case class Pokemon(name: String, weight: Int, hp: Int, attack: Int, defense: Int)
|
||||
|
||||
// Bad - brittle when fields change
|
||||
targets.foreach {
|
||||
case Pokemon(_, _, hp, _, defense) =>
|
||||
// error prone
|
||||
}
|
||||
|
||||
// Good - match on type
|
||||
targets.foreach {
|
||||
case p: Pokemon =>
|
||||
val loss = math.min(0, myAttack - p.defense)
|
||||
p.copy(hp = p.hp - loss)
|
||||
}
|
||||
```
|
||||
|
||||
### Anonymous Functions
|
||||
|
||||
```scala
|
||||
// Avoid excessive parentheses
|
||||
// Correct
|
||||
list.map { item =>
|
||||
transform(item)
|
||||
}
|
||||
|
||||
// Correct
|
||||
list.map(item => transform(item))
|
||||
|
||||
// Wrong - unnecessary braces
|
||||
list.map(item => {
|
||||
transform(item)
|
||||
})
|
||||
|
||||
// Wrong - excessive nesting
|
||||
list.map({ item => ... })
|
||||
```
|
||||
|
||||
### Infix Methods
|
||||
|
||||
```scala
|
||||
// Avoid infix for non-symbolic methods
|
||||
list.map(func) // Correct
|
||||
list map func // Wrong
|
||||
|
||||
// OK for operators
|
||||
arrayBuffer += elem
|
||||
```
|
||||
|
||||
## Language Features
|
||||
|
||||
### Avoid apply() on Classes
|
||||
|
||||
```scala
|
||||
// Avoid apply on classes - hard to trace
|
||||
class TreeNode {
|
||||
def apply(name: String): TreeNode = { ... } // Don't do this
|
||||
}
|
||||
|
||||
// OK on companion objects as factory
|
||||
object TreeNode {
|
||||
def apply(name: String): TreeNode = new TreeNode(name) // OK
|
||||
}
|
||||
```
|
||||
|
||||
### override Modifier
|
||||
|
||||
```scala
|
||||
// Always use override - even for abstract methods
|
||||
trait Parent {
|
||||
def hello(data: Map[String, String]): Unit
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
// Without override, this might not actually override!
|
||||
override def hello(data: Map[String, String]): Unit = {
|
||||
println(data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid Destructuring in Constructors
|
||||
|
||||
```scala
|
||||
// Don't use destructuring binds in constructors
|
||||
class MyClass {
|
||||
// Bad - creates non-transient Tuple2
|
||||
@transient private val (a, b) = someFuncThatReturnsTuple2()
|
||||
|
||||
// Good
|
||||
@transient private val tuple = someFuncThatReturnsTuple2()
|
||||
@transient private val a = tuple._1
|
||||
@transient private val b = tuple._2
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid Call-by-Name
|
||||
|
||||
```scala
|
||||
// Avoid call-by-name parameters
|
||||
// Bad - caller can't tell if executed once or many times
|
||||
def print(value: => Int): Unit = {
|
||||
println(value)
|
||||
println(value + 1)
|
||||
}
|
||||
|
||||
// Good - explicit function type
|
||||
def print(value: () => Int): Unit = {
|
||||
println(value())
|
||||
println(value() + 1)
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid Multiple Parameter Lists
|
||||
|
||||
```scala
|
||||
// Avoid multiple parameter lists (except for implicits)
|
||||
// Bad
|
||||
case class Person(name: String, age: Int)(secret: String)
|
||||
|
||||
// Good
|
||||
case class Person(name: String, age: Int, secret: String)
|
||||
|
||||
// Exception: separate list for implicits (but avoid implicits!)
|
||||
def foo(x: Int)(implicit ec: ExecutionContext): Future[Int]
|
||||
```
|
||||
|
||||
### Symbolic Methods
|
||||
|
||||
```scala
|
||||
// Only use for arithmetic operators
|
||||
class Vector {
|
||||
def +(other: Vector): Vector = { ... } // OK
|
||||
def -(other: Vector): Vector = { ... } // OK
|
||||
}
|
||||
|
||||
// Don't use for other methods
|
||||
// Bad
|
||||
channel ! msg
|
||||
stream1 >>= stream2
|
||||
|
||||
// Good
|
||||
channel.send(msg)
|
||||
stream1.join(stream2)
|
||||
```
|
||||
|
||||
### Type Inference
|
||||
|
||||
```scala
|
||||
// Always type public methods
|
||||
def getUserById(id: Long): Option[User] = { ... }
|
||||
|
||||
// Always type implicit methods
|
||||
implicit def stringToInt(s: String): Int = s.toInt
|
||||
|
||||
// Type variables when not obvious (3 second rule)
|
||||
val user: User = complexComputation()
|
||||
|
||||
// OK to omit when obvious
|
||||
val count = 5
|
||||
val name = "Alice"
|
||||
```
|
||||
|
||||
### Return Statements
|
||||
|
||||
```scala
|
||||
// Avoid return in closures - uses exceptions under the hood
|
||||
def receive(rpc: WebSocketRPC): Option[Response] = {
|
||||
tableFut.onComplete { table =>
|
||||
if (table.isFailure) {
|
||||
return None // Don't do this - wrong thread!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use return as guard to simplify control flow
|
||||
def doSomething(obj: Any): Any = {
|
||||
if (obj eq null) {
|
||||
return null
|
||||
}
|
||||
// do something
|
||||
}
|
||||
|
||||
// Use return to break loops early
|
||||
while (true) {
|
||||
if (cond) {
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Recursion and Tail Recursion
|
||||
|
||||
```scala
|
||||
// Avoid recursion unless naturally recursive (trees, graphs)
|
||||
// Use @tailrec for tail-recursive methods
|
||||
@scala.annotation.tailrec
|
||||
def max0(data: Array[Int], pos: Int, max: Int): Int = {
|
||||
if (pos == data.length) {
|
||||
max
|
||||
} else {
|
||||
max0(data, pos + 1, if (data(pos) > max) data(pos) else max)
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer explicit loops for clarity
|
||||
def max(data: Array[Int]): Int = {
|
||||
var max = Int.MinValue
|
||||
for (v <- data) {
|
||||
if (v > max) {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
max
|
||||
}
|
||||
```
|
||||
|
||||
### Implicits
|
||||
|
||||
```scala
|
||||
// Avoid implicits unless:
|
||||
// 1. Building a DSL
|
||||
// 2. Implicit type parameters (ClassTag, TypeTag)
|
||||
// 3. Private type conversions within your class
|
||||
|
||||
// If you must use them, don't overload
|
||||
object ImplicitHolder {
|
||||
// Bad - can't selectively import
|
||||
def toRdd(seq: Seq[Int]): RDD[Int] = { ... }
|
||||
def toRdd(seq: Seq[Long]): RDD[Long] = { ... }
|
||||
}
|
||||
|
||||
// Good - distinct names
|
||||
object ImplicitHolder {
|
||||
def intSeqToRdd(seq: Seq[Int]): RDD[Int] = { ... }
|
||||
def longSeqToRdd(seq: Seq[Long]): RDD[Long] = { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Type Safety
|
||||
|
||||
### Algebraic Data Types
|
||||
|
||||
```scala
|
||||
// Sum types - sealed traits with case classes
|
||||
sealed trait PaymentMethod
|
||||
case class CreditCard(number: String, cvv: String) extends PaymentMethod
|
||||
case class PayPal(email: String) extends PaymentMethod
|
||||
case class BankTransfer(account: String, routing: String) extends PaymentMethod
|
||||
|
||||
def processPayment(payment: PaymentMethod): Either[Error, Receipt] = payment match {
|
||||
case CreditCard(number, cvv) => chargeCreditCard(number, cvv)
|
||||
case PayPal(email) => chargePayPal(email)
|
||||
case BankTransfer(account, routing) => chargeBankAccount(account, routing)
|
||||
}
|
||||
|
||||
// Product types - case classes
|
||||
case class User(id: Long, name: String, email: String, age: Int)
|
||||
case class Order(id: Long, userId: Long, items: List[Item], total: BigDecimal)
|
||||
```
|
||||
|
||||
### Option over null
|
||||
|
||||
```scala
|
||||
// Use Option instead of null
|
||||
def findUserById(id: Long): Option[User] = {
|
||||
database.query(id)
|
||||
}
|
||||
|
||||
// Use Option() to guard against nulls
|
||||
def myMethod1(input: String): Option[String] = Option(transform(input))
|
||||
|
||||
// Don't use Some() - it won't protect against null
|
||||
def myMethod2(input: String): Option[String] = Some(transform(input)) // Bad
|
||||
|
||||
// Pattern matching on Option
|
||||
def processUser(id: Long): String = findUserById(id) match {
|
||||
case Some(user) => s"Found: ${user.name}"
|
||||
case None => "User not found"
|
||||
}
|
||||
|
||||
// Don't call get() unless absolutely sure
|
||||
val user = findUserById(123).get // Dangerous!
|
||||
|
||||
// Use getOrElse, map, flatMap, fold instead
|
||||
val name = findUserById(123).map(_.name).getOrElse("Unknown")
|
||||
```
|
||||
|
||||
### Error Handling with Either
|
||||
|
||||
```scala
|
||||
sealed trait ValidationError
|
||||
case class InvalidEmail(email: String) extends ValidationError
|
||||
case class InvalidAge(age: Int) extends ValidationError
|
||||
case class MissingField(field: String) extends ValidationError
|
||||
|
||||
def validateUser(data: Map[String, String]): Either[ValidationError, User] = {
|
||||
for {
|
||||
name <- data.get("name").toRight(MissingField("name"))
|
||||
email <- data.get("email").toRight(MissingField("email"))
|
||||
validEmail <- validateEmail(email)
|
||||
ageStr <- data.get("age").toRight(MissingField("age"))
|
||||
age <- ageStr.toIntOption.toRight(InvalidAge(-1))
|
||||
} yield User(name, validEmail, age)
|
||||
}
|
||||
```
|
||||
|
||||
### Try vs Exceptions
|
||||
|
||||
```scala
|
||||
// Don't return Try from APIs
|
||||
// Bad
|
||||
def getUser(id: Long): Try[User]
|
||||
|
||||
// Good - explicit throws
|
||||
@throws(classOf[DatabaseConnectionException])
|
||||
def getUser(id: Long): Option[User]
|
||||
|
||||
// Use NonFatal for catching exceptions
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
try {
|
||||
dangerousOperation()
|
||||
} catch {
|
||||
case NonFatal(e) =>
|
||||
logger.error("Operation failed", e)
|
||||
case e: InterruptedException =>
|
||||
// handle interruption
|
||||
}
|
||||
```
|
||||
|
||||
## Collections
|
||||
|
||||
### Prefer Immutable Collections
|
||||
|
||||
```scala
|
||||
import scala.collection.immutable._
|
||||
|
||||
// Good
|
||||
val numbers = List(1, 2, 3, 4, 5)
|
||||
val doubled = numbers.map(_ * 2)
|
||||
val evens = numbers.filter(_ % 2 == 0)
|
||||
|
||||
val userMap = Map(
|
||||
1L -> "Alice",
|
||||
2L -> "Bob"
|
||||
)
|
||||
val updated = userMap + (3L -> "Charlie")
|
||||
|
||||
// Use Stream (Scala 2.12) or LazyList (Scala 2.13) for lazy sequences
|
||||
val fibonacci: LazyList[BigInt] =
|
||||
BigInt(0) #:: BigInt(1) #:: fibonacci.zip(fibonacci.tail).map { case (a, b) => a + b }
|
||||
|
||||
val first10 = fibonacci.take(10).toList
|
||||
```
|
||||
|
||||
### Monadic Chaining
|
||||
|
||||
```scala
|
||||
// Avoid chaining more than 3 operations
|
||||
// Break after flatMap
|
||||
// Don't chain with if-else blocks
|
||||
|
||||
// Bad - too complex
|
||||
database.get(name).flatMap { elem =>
|
||||
elem.data.get("address").flatMap(Option.apply)
|
||||
}
|
||||
|
||||
// Good - more readable
|
||||
def getAddress(name: String): Option[String] = {
|
||||
if (!database.contains(name)) {
|
||||
return None
|
||||
}
|
||||
|
||||
database(name).data.get("address") match {
|
||||
case Some(null) => None
|
||||
case Some(addr) => Option(addr)
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
|
||||
// Don't chain with if-else
|
||||
// Bad
|
||||
if (condition) {
|
||||
Seq(1, 2, 3)
|
||||
} else {
|
||||
Seq(1, 2, 3)
|
||||
}.map(_ + 1)
|
||||
|
||||
// Good
|
||||
val seq = if (condition) Seq(1, 2, 3) else Seq(4, 5, 6)
|
||||
seq.map(_ + 1)
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Use while Loops
|
||||
|
||||
```scala
|
||||
// For performance-critical code, use while instead of for/map
|
||||
val arr = Array.fill(1000)(Random.nextInt())
|
||||
|
||||
// Slow
|
||||
val newArr = arr.zipWithIndex.map { case (elem, i) =>
|
||||
if (i % 2 == 0) 0 else elem
|
||||
}
|
||||
|
||||
// Fast
|
||||
val newArr = new Array[Int](arr.length)
|
||||
var i = 0
|
||||
while (i < arr.length) {
|
||||
newArr(i) = if (i % 2 == 0) 0 else arr(i)
|
||||
i += 1
|
||||
}
|
||||
```
|
||||
|
||||
### Option vs null
|
||||
|
||||
```scala
|
||||
// For performance-critical code, prefer null over Option
|
||||
class Foo {
|
||||
@javax.annotation.Nullable
|
||||
private[this] var nullableField: Bar = _
|
||||
}
|
||||
```
|
||||
|
||||
### Use private[this]
|
||||
|
||||
```scala
|
||||
// private[this] generates fields, not accessor methods
|
||||
class MyClass {
|
||||
private val field1 = ... // Might use accessor
|
||||
private[this] val field2 = ... // Direct field access
|
||||
|
||||
def perfSensitiveMethod(): Unit = {
|
||||
var i = 0
|
||||
while (i < 1000000) {
|
||||
field2 // Guaranteed field access
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Java Collections
|
||||
|
||||
```scala
|
||||
// For performance, prefer Java collections
|
||||
import java.util.{ArrayList, HashMap}
|
||||
|
||||
val list = new ArrayList[String]()
|
||||
val map = new HashMap[String, Int]()
|
||||
```
|
||||
|
||||
## Concurrency
|
||||
|
||||
### Prefer ConcurrentHashMap
|
||||
|
||||
```scala
|
||||
// Use java.util.concurrent.ConcurrentHashMap
|
||||
private[this] val map = new java.util.concurrent.ConcurrentHashMap[String, String]
|
||||
|
||||
// Or synchronized map for low contention
|
||||
private[this] val map = java.util.Collections.synchronizedMap(
|
||||
new java.util.HashMap[String, String]
|
||||
)
|
||||
```
|
||||
|
||||
### Explicit Synchronization
|
||||
|
||||
```scala
|
||||
class Manager {
|
||||
private[this] var count = 0
|
||||
private[this] val map = new java.util.HashMap[String, String]
|
||||
|
||||
def update(key: String, value: String): Unit = synchronized {
|
||||
map.put(key, value)
|
||||
count += 1
|
||||
}
|
||||
|
||||
def getCount: Int = synchronized { count }
|
||||
}
|
||||
```
|
||||
|
||||
### Atomic Variables
|
||||
|
||||
```scala
|
||||
import java.util.concurrent.atomic._
|
||||
|
||||
// Prefer Atomic over @volatile
|
||||
val initialized = new AtomicBoolean(false)
|
||||
|
||||
// Clearly express only-once execution
|
||||
if (!initialized.getAndSet(true)) {
|
||||
initialize()
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Intercept Specific Exceptions
|
||||
|
||||
```scala
|
||||
import org.scalatest._
|
||||
|
||||
// Bad - too broad
|
||||
intercept[Exception] {
|
||||
thingThatThrows()
|
||||
}
|
||||
|
||||
// Good - specific type
|
||||
intercept[IllegalArgumentException] {
|
||||
thingThatThrows()
|
||||
}
|
||||
```
|
||||
|
||||
## SBT Configuration
|
||||
|
||||
```scala
|
||||
// build.sbt
|
||||
ThisBuild / version := "0.1.0-SNAPSHOT"
|
||||
ThisBuild / scalaVersion := "2.13.12"
|
||||
ThisBuild / organization := "com.example"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
name := "my-application",
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "cats-core" % "2.10.0",
|
||||
"org.typelevel" %% "cats-effect" % "3.5.2",
|
||||
|
||||
// Testing
|
||||
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
|
||||
"org.scalatestplus" %% "scalacheck-1-17" % "3.2.17.0" % Test
|
||||
),
|
||||
|
||||
scalacOptions ++= Seq(
|
||||
"-encoding", "UTF-8",
|
||||
"-feature",
|
||||
"-unchecked",
|
||||
"-deprecation",
|
||||
"-Xfatal-warnings"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
### Use nanoTime
|
||||
|
||||
```scala
|
||||
// Use nanoTime for durations, not currentTimeMillis
|
||||
val start = System.nanoTime()
|
||||
doWork()
|
||||
val elapsed = System.nanoTime() - start
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
val elapsedMs = TimeUnit.NANOSECONDS.toMillis(elapsed)
|
||||
```
|
||||
|
||||
### URI over URL
|
||||
|
||||
```scala
|
||||
// Use URI instead of URL (URL.equals does DNS lookup!)
|
||||
val uri = new java.net.URI("http://example.com")
|
||||
// Not: val url = new java.net.URL("http://example.com")
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
1. **Write simple code** - Optimize for readability and maintainability
|
||||
2. **Use immutable data** - val, immutable collections, case classes
|
||||
3. **Avoid language features** - Limit implicits, avoid symbolic methods
|
||||
4. **Type public APIs** - Explicit types for methods and fields
|
||||
5. **Prefer explicit over implicit** - Clear is better than concise
|
||||
6. **Use standard libraries** - Don't reinvent the wheel
|
||||
7. **Follow naming conventions** - PascalCase, camelCase, UPPER_CASE
|
||||
8. **Keep methods small** - Rule of 30
|
||||
9. **Handle errors explicitly** - Option, Either, exceptions with @throws
|
||||
10. **Profile before optimizing** - Measure, don't guess
|
||||
|
||||
For complete details, see the [Databricks Scala Style Guide](https://github.com/databricks/scala-style-guide).
|
||||
440
instructions/typespec-m365-copilot.instructions.md
Normal file
440
instructions/typespec-m365-copilot.instructions.md
Normal file
@@ -0,0 +1,440 @@
|
||||
---
|
||||
description: 'Guidelines and best practices for building TypeSpec-based declarative agents and API plugins for Microsoft 365 Copilot'
|
||||
applyTo: '**/*.tsp'
|
||||
---
|
||||
|
||||
# TypeSpec for Microsoft 365 Copilot Development Guidelines
|
||||
|
||||
## Core Principles
|
||||
|
||||
When working with TypeSpec for Microsoft 365 Copilot:
|
||||
|
||||
1. **Type Safety First**: Leverage TypeSpec's strong typing for all models and operations
|
||||
2. **Declarative Approach**: Use decorators to describe intent, not implementation
|
||||
3. **Scoped Capabilities**: Always scope capabilities to specific resources when possible
|
||||
4. **Clear Instructions**: Write explicit, detailed agent instructions
|
||||
5. **User-Centric**: Design for the end-user experience in Microsoft 365 Copilot
|
||||
|
||||
## File Organization
|
||||
|
||||
### Standard Structure
|
||||
```
|
||||
project/
|
||||
├── appPackage/
|
||||
│ ├── cards/ # Adaptive Card templates
|
||||
│ │ └── *.json
|
||||
│ ├── .generated/ # Generated manifests (auto-generated)
|
||||
│ └── manifest.json # Teams app manifest
|
||||
├── src/
|
||||
│ ├── main.tsp # Agent definition
|
||||
│ └── actions.tsp # API operations (for plugins)
|
||||
├── m365agents.yml # Agents Toolkit configuration
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Import Statements
|
||||
Always include required imports at the top of TypeSpec files:
|
||||
|
||||
```typescript
|
||||
import "@typespec/http";
|
||||
import "@typespec/openapi3";
|
||||
import "@microsoft/typespec-m365-copilot";
|
||||
|
||||
using TypeSpec.Http;
|
||||
using TypeSpec.M365.Copilot.Agents; // For agents
|
||||
using TypeSpec.M365.Copilot.Actions; // For API plugins
|
||||
```
|
||||
|
||||
## Agent Development Best Practices
|
||||
|
||||
### Agent Declaration
|
||||
```typescript
|
||||
@agent({
|
||||
name: "Role-Based Name", // e.g., "Customer Support Assistant"
|
||||
description: "Clear, concise description under 1,000 characters"
|
||||
})
|
||||
```
|
||||
|
||||
- Use role-based names that describe what the agent does
|
||||
- Make descriptions informative but concise
|
||||
- Avoid generic names like "Helper" or "Bot"
|
||||
|
||||
### Instructions
|
||||
```typescript
|
||||
@instructions("""
|
||||
You are a [specific role] specialized in [domain].
|
||||
|
||||
Your responsibilities include:
|
||||
- [Key responsibility 1]
|
||||
- [Key responsibility 2]
|
||||
|
||||
When helping users:
|
||||
- [Behavioral guideline 1]
|
||||
- [Behavioral guideline 2]
|
||||
|
||||
You should NOT:
|
||||
- [Constraint 1]
|
||||
- [Constraint 2]
|
||||
""")
|
||||
```
|
||||
|
||||
- Write in second person ("You are...")
|
||||
- Be specific about the agent's role and expertise
|
||||
- Define both what to do AND what not to do
|
||||
- Keep under 8,000 characters
|
||||
- Use clear, structured formatting
|
||||
|
||||
### Conversation Starters
|
||||
```typescript
|
||||
@conversationStarter(#{
|
||||
title: "Action-Oriented Title", // e.g., "Check Status"
|
||||
text: "Specific example query" // e.g., "What's the status of my ticket?"
|
||||
})
|
||||
```
|
||||
|
||||
- Provide 2-4 diverse starters
|
||||
- Make each showcase a different capability
|
||||
- Use action-oriented titles
|
||||
- Write realistic example queries
|
||||
|
||||
### Capabilities - Knowledge Sources
|
||||
|
||||
**Web Search** - Scope to specific sites when possible:
|
||||
```typescript
|
||||
op webSearch is AgentCapabilities.WebSearch<Sites = [
|
||||
{ url: "https://learn.microsoft.com" },
|
||||
{ url: "https://docs.microsoft.com" }
|
||||
]>;
|
||||
```
|
||||
|
||||
**OneDrive and SharePoint** - Use URLs or IDs:
|
||||
```typescript
|
||||
op oneDriveAndSharePoint is AgentCapabilities.OneDriveAndSharePoint<
|
||||
ItemsByUrl = [
|
||||
{ url: "https://contoso.sharepoint.com/sites/Engineering" }
|
||||
]
|
||||
>;
|
||||
```
|
||||
|
||||
**Teams Messages** - Specify channels/chats:
|
||||
```typescript
|
||||
op teamsMessages is AgentCapabilities.TeamsMessages<Urls = [
|
||||
{ url: "https://teams.microsoft.com/l/channel/..." }
|
||||
]>;
|
||||
```
|
||||
|
||||
**Email** - Scope to specific folders:
|
||||
```typescript
|
||||
op email is AgentCapabilities.Email<
|
||||
Folders = [
|
||||
{ folderId: "Inbox" },
|
||||
{ folderId: "SentItems" }
|
||||
],
|
||||
SharedMailbox = "support@contoso.com" // Optional
|
||||
>;
|
||||
```
|
||||
|
||||
**People** - No scoping needed:
|
||||
```typescript
|
||||
op people is AgentCapabilities.People;
|
||||
```
|
||||
|
||||
**Copilot Connectors** - Specify connection IDs:
|
||||
```typescript
|
||||
op copilotConnectors is AgentCapabilities.GraphConnectors<
|
||||
Connections = [
|
||||
{ connectionId: "your-connector-id" }
|
||||
]
|
||||
>;
|
||||
```
|
||||
|
||||
**Dataverse** - Scope to specific tables:
|
||||
```typescript
|
||||
op dataverse is AgentCapabilities.Dataverse<
|
||||
KnowledgeSources = [
|
||||
{
|
||||
hostName: "contoso.crm.dynamics.com";
|
||||
tables: [
|
||||
{ tableName: "account" },
|
||||
{ tableName: "contact" }
|
||||
];
|
||||
}
|
||||
]
|
||||
>;
|
||||
```
|
||||
|
||||
### Capabilities - Productivity Tools
|
||||
|
||||
```typescript
|
||||
// Python code execution
|
||||
op codeInterpreter is AgentCapabilities.CodeInterpreter;
|
||||
|
||||
// Image generation
|
||||
op graphicArt is AgentCapabilities.GraphicArt;
|
||||
|
||||
// Meeting content access
|
||||
op meetings is AgentCapabilities.Meetings;
|
||||
|
||||
// Specialized AI models
|
||||
op scenarioModels is AgentCapabilities.ScenarioModels<
|
||||
ModelsById = [
|
||||
{ id: "model-id" }
|
||||
]
|
||||
>;
|
||||
```
|
||||
|
||||
## API Plugin Development Best Practices
|
||||
|
||||
### Service Definition
|
||||
```typescript
|
||||
@service
|
||||
@actions(#{
|
||||
nameForHuman: "User-Friendly API Name",
|
||||
descriptionForHuman: "What users will understand",
|
||||
descriptionForModel: "What the model needs to know",
|
||||
contactEmail: "support@company.com",
|
||||
privacyPolicyUrl: "https://company.com/privacy",
|
||||
legalInfoUrl: "https://company.com/terms"
|
||||
})
|
||||
@server("https://api.example.com", "API Name")
|
||||
@useAuth([AuthType]) // If authentication needed
|
||||
namespace APINamespace {
|
||||
// Operations here
|
||||
}
|
||||
```
|
||||
|
||||
### Operation Definition
|
||||
```typescript
|
||||
@route("/resource/{id}")
|
||||
@get
|
||||
@action
|
||||
@card(#{
|
||||
dataPath: "$.items",
|
||||
title: "$.title",
|
||||
file: "cards/card.json"
|
||||
})
|
||||
@capabilities(#{
|
||||
confirmation: #{
|
||||
type: "AdaptiveCard",
|
||||
title: "Confirm Action",
|
||||
body: "Confirm with {{ function.parameters.param }}"
|
||||
}
|
||||
})
|
||||
@reasoning("Consider X when Y")
|
||||
@responding("Present results as Z")
|
||||
op getResource(
|
||||
@path id: string,
|
||||
@query filter?: string
|
||||
): ResourceResponse;
|
||||
```
|
||||
|
||||
### Models
|
||||
```typescript
|
||||
model Resource {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string; // Optional fields
|
||||
status: "active" | "inactive"; // Union types for enums
|
||||
@format("date-time")
|
||||
createdAt: utcDateTime;
|
||||
@format("uri")
|
||||
url?: string;
|
||||
}
|
||||
|
||||
model ResourceList {
|
||||
items: Resource[];
|
||||
totalCount: int32;
|
||||
nextPage?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
**API Key**
|
||||
```typescript
|
||||
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "X-API-Key">)
|
||||
|
||||
// Or with reference ID
|
||||
@useAuth(Auth)
|
||||
@authReferenceId("${{ENV_VAR_REFERENCE_ID}}")
|
||||
model Auth is ApiKeyAuth<ApiKeyLocation.header, "X-API-Key">;
|
||||
```
|
||||
|
||||
**OAuth2**
|
||||
```typescript
|
||||
@useAuth(OAuth2Auth<[{
|
||||
type: OAuth2FlowType.authorizationCode;
|
||||
authorizationUrl: "https://auth.example.com/authorize";
|
||||
tokenUrl: "https://auth.example.com/token";
|
||||
refreshUrl: "https://auth.example.com/refresh";
|
||||
scopes: ["read", "write"];
|
||||
}]>)
|
||||
|
||||
// Or with reference ID
|
||||
@useAuth(Auth)
|
||||
@authReferenceId("${{OAUTH_REFERENCE_ID}}")
|
||||
model Auth is OAuth2Auth<[...]>;
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Files
|
||||
- `main.tsp` - Agent definition
|
||||
- `actions.tsp` - API operations
|
||||
- `[feature].tsp` - Additional feature files
|
||||
- `cards/*.json` - Adaptive Card templates
|
||||
|
||||
### TypeSpec Elements
|
||||
- **Namespaces**: PascalCase (e.g., `CustomerSupportAgent`)
|
||||
- **Operations**: camelCase (e.g., `listProjects`, `createTicket`)
|
||||
- **Models**: PascalCase (e.g., `Project`, `TicketResponse`)
|
||||
- **Model Properties**: camelCase (e.g., `projectId`, `createdDate`)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Multi-Capability Agent
|
||||
```typescript
|
||||
@agent("Knowledge Worker", "Description")
|
||||
@instructions("...")
|
||||
namespace KnowledgeWorker {
|
||||
op webSearch is AgentCapabilities.WebSearch;
|
||||
op files is AgentCapabilities.OneDriveAndSharePoint;
|
||||
op people is AgentCapabilities.People;
|
||||
}
|
||||
```
|
||||
|
||||
### CRUD API Plugin
|
||||
```typescript
|
||||
namespace ProjectAPI {
|
||||
@route("/projects") @get @action
|
||||
op list(): Project[];
|
||||
|
||||
@route("/projects/{id}") @get @action
|
||||
op get(@path id: string): Project;
|
||||
|
||||
@route("/projects") @post @action
|
||||
@capabilities(#{confirmation: ...})
|
||||
op create(@body project: CreateProject): Project;
|
||||
|
||||
@route("/projects/{id}") @patch @action
|
||||
@capabilities(#{confirmation: ...})
|
||||
op update(@path id: string, @body project: UpdateProject): Project;
|
||||
|
||||
@route("/projects/{id}") @delete @action
|
||||
@capabilities(#{confirmation: ...})
|
||||
op delete(@path id: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
### Adaptive Card Data Binding
|
||||
```json
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"$data": "${$root}",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Title: ${if(title, title, 'N/A')}",
|
||||
"wrap": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Validation and Testing
|
||||
|
||||
### Before Provisioning
|
||||
1. Run TypeSpec validation: `npm run build` or use Agents Toolkit
|
||||
2. Check all file paths in `@card` decorators exist
|
||||
3. Verify authentication references match configuration
|
||||
4. Ensure capability scoping is appropriate
|
||||
5. Review instructions for clarity and length
|
||||
|
||||
### Testing Strategy
|
||||
1. **Provision**: Deploy to development environment
|
||||
2. **Test**: Use Microsoft 365 Copilot at https://m365.cloud.microsoft/chat
|
||||
3. **Debug**: Enable Copilot developer mode for orchestrator insights
|
||||
4. **Iterate**: Refine based on actual behavior
|
||||
5. **Validate**: Test all conversation starters and capabilities
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
1. **Scope Capabilities**: Don't grant access to all data if only subset needed
|
||||
2. **Limit Operations**: Only expose API operations the agent actually uses
|
||||
3. **Efficient Models**: Keep response models focused on necessary data
|
||||
4. **Card Optimization**: Use conditional rendering (`$when`) in Adaptive Cards
|
||||
5. **Caching**: Design APIs with appropriate caching headers
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Authentication**: Always use authentication for non-public APIs
|
||||
2. **Scoping**: Limit capability access to minimum required resources
|
||||
3. **Validation**: Validate all inputs in API operations
|
||||
4. **Secrets**: Use environment variables for sensitive data
|
||||
5. **References**: Use `@authReferenceId` for production credentials
|
||||
6. **Permissions**: Request minimum necessary OAuth scopes
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
model ErrorResponse {
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: ErrorDetail[];
|
||||
};
|
||||
}
|
||||
|
||||
model ErrorDetail {
|
||||
field?: string;
|
||||
message: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Include comments in TypeSpec for complex operations:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Retrieves project details with associated tasks and team members.
|
||||
*
|
||||
* @param id - Unique project identifier
|
||||
* @param includeArchived - Whether to include archived tasks
|
||||
* @returns Complete project information
|
||||
*/
|
||||
@route("/projects/{id}")
|
||||
@get
|
||||
@action
|
||||
op getProjectDetails(
|
||||
@path id: string,
|
||||
@query includeArchived?: boolean
|
||||
): ProjectDetails;
|
||||
```
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
1. ❌ Generic agent names ("Helper Bot")
|
||||
2. ❌ Vague instructions ("Help users with things")
|
||||
3. ❌ No capability scoping (accessing all data)
|
||||
4. ❌ Missing confirmations on destructive operations
|
||||
5. ❌ Overly complex Adaptive Cards
|
||||
6. ❌ Hard-coded credentials in TypeSpec files
|
||||
7. ❌ Missing error response models
|
||||
8. ❌ Inconsistent naming conventions
|
||||
9. ❌ Too many capabilities (use only what's needed)
|
||||
10. ❌ Instructions over 8,000 characters
|
||||
|
||||
## Resources
|
||||
|
||||
- [TypeSpec Official Docs](https://typespec.io/)
|
||||
- [Microsoft 365 Copilot Extensibility](https://learn.microsoft.com/microsoft-365-copilot/extensibility/)
|
||||
- [Agents Toolkit](https://aka.ms/M365AgentsToolkit)
|
||||
- [Adaptive Cards Designer](https://adaptivecards.io/designer/)
|
||||
647
instructions/vsixtoolkit.instructions.md
Normal file
647
instructions/vsixtoolkit.instructions.md
Normal file
@@ -0,0 +1,647 @@
|
||||
---
|
||||
description: 'Guidelines for Visual Studio extension (VSIX) development using Community.VisualStudio.Toolkit'
|
||||
applyTo: '**/*.cs, **/*.vsct, **/*.xaml, **/source.extension.vsixmanifest'
|
||||
---
|
||||
|
||||
# Visual Studio Extension Development with Community.VisualStudio.Toolkit
|
||||
|
||||
## Scope
|
||||
|
||||
**These instructions apply ONLY to Visual Studio extensions using `Community.VisualStudio.Toolkit`.**
|
||||
|
||||
Verify the project uses the toolkit by checking for:
|
||||
- `Community.VisualStudio.Toolkit.*` NuGet package reference
|
||||
- `ToolkitPackage` base class (not raw `AsyncPackage`)
|
||||
- `BaseCommand<T>` pattern for commands
|
||||
|
||||
**If the project uses raw VSSDK (`AsyncPackage` directly) or the new `VisualStudio.Extensibility` model, do not apply these instructions.**
|
||||
|
||||
## Goals
|
||||
|
||||
- Generate async-first, thread-safe extension code
|
||||
- Use toolkit abstractions (`VS.*` helpers, `BaseCommand<T>`, `BaseOptionModel<T>`)
|
||||
- Ensure all UI respects Visual Studio themes
|
||||
- Follow VSSDK and VSTHRD analyzer rules
|
||||
- Produce testable, maintainable extension code
|
||||
|
||||
## Example Prompt Behaviors
|
||||
|
||||
### ✅ Good Suggestions
|
||||
- "Create a command that opens the current file's containing folder using `BaseCommand<T>`"
|
||||
- "Add an options page with a boolean setting using `BaseOptionModel<T>`"
|
||||
- "Write a tagger provider for C# files that highlights TODO comments"
|
||||
- "Show a status bar progress indicator while processing files"
|
||||
|
||||
### ❌ Avoid
|
||||
- Suggesting raw `AsyncPackage` instead of `ToolkitPackage`
|
||||
- Using `OleMenuCommandService` directly instead of `BaseCommand<T>`
|
||||
- Creating WPF elements without switching to UI thread first
|
||||
- Using `.Result`, `.Wait()`, or `Task.Run` for UI work
|
||||
- Hardcoding colors instead of using VS theme colors
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── Commands/ # Command handlers (menu items, toolbar buttons)
|
||||
├── Options/ # Settings/options pages
|
||||
├── Services/ # Business logic and services
|
||||
├── Tagging/ # ITagger implementations (syntax highlighting, outlining)
|
||||
├── Adornments/ # Editor adornments (IntraTextAdornment, margins)
|
||||
├── QuickInfo/ # QuickInfo/tooltip providers
|
||||
├── SuggestedActions/ # Light bulb actions
|
||||
├── Handlers/ # Event handlers (format document, paste, etc.)
|
||||
├── Resources/ # Images, icons, license files
|
||||
├── source.extension.vsixmanifest # Extension manifest
|
||||
├── VSCommandTable.vsct # Command definitions (menus, buttons)
|
||||
├── VSCommandTable.cs # Auto-generated command IDs
|
||||
└── *Package.cs # Main package class
|
||||
```
|
||||
|
||||
## Community.VisualStudio.Toolkit Patterns
|
||||
|
||||
### Global Usings
|
||||
|
||||
Extensions using the toolkit should have these global usings in the Package file:
|
||||
|
||||
```csharp
|
||||
global using System;
|
||||
global using Community.VisualStudio.Toolkit;
|
||||
global using Microsoft.VisualStudio.Shell;
|
||||
global using Task = System.Threading.Tasks.Task;
|
||||
```
|
||||
|
||||
### Package Class
|
||||
|
||||
```csharp
|
||||
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
|
||||
[InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)]
|
||||
[ProvideMenuResource("Menus.ctmenu", 1)]
|
||||
[Guid(PackageGuids.YourExtensionString)]
|
||||
[ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), Vsix.Name, "General", 0, 0, true, SupportsProfiles = true)]
|
||||
public sealed class YourPackage : ToolkitPackage
|
||||
{
|
||||
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
|
||||
{
|
||||
await this.RegisterCommandsAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
Commands use the `[Command]` attribute and inherit from `BaseCommand<T>`:
|
||||
|
||||
```csharp
|
||||
[Command(PackageIds.YourCommandId)]
|
||||
internal sealed class YourCommand : BaseCommand<YourCommand>
|
||||
{
|
||||
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
|
||||
{
|
||||
// Command implementation
|
||||
}
|
||||
|
||||
// Optional: Control command state (enabled, checked, visible)
|
||||
protected override void BeforeQueryStatus(EventArgs e)
|
||||
{
|
||||
Command.Checked = someCondition;
|
||||
Command.Enabled = anotherCondition;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Options Pages
|
||||
|
||||
```csharp
|
||||
internal partial class OptionsProvider
|
||||
{
|
||||
[ComVisible(true)]
|
||||
public class GeneralOptions : BaseOptionPage<General> { }
|
||||
}
|
||||
|
||||
public class General : BaseOptionModel<General>
|
||||
{
|
||||
[Category("Category Name")]
|
||||
[DisplayName("Setting Name")]
|
||||
[Description("Description of the setting.")]
|
||||
[DefaultValue(true)]
|
||||
public bool MySetting { get; set; } = true;
|
||||
}
|
||||
```
|
||||
|
||||
## MEF Components
|
||||
|
||||
### Tagger Providers
|
||||
|
||||
Use `[Export]` and appropriate `[ContentType]` attributes:
|
||||
|
||||
```csharp
|
||||
[Export(typeof(IViewTaggerProvider))]
|
||||
[ContentType("CSharp")]
|
||||
[ContentType("Basic")]
|
||||
[TagType(typeof(IntraTextAdornmentTag))]
|
||||
[TextViewRole(PredefinedTextViewRoles.Document)]
|
||||
internal sealed class YourTaggerProvider : IViewTaggerProvider
|
||||
{
|
||||
[Import]
|
||||
internal IOutliningManagerService OutliningManagerService { get; set; }
|
||||
|
||||
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
|
||||
{
|
||||
if (textView == null || !(textView is IWpfTextView wpfTextView))
|
||||
return null;
|
||||
|
||||
if (textView.TextBuffer != buffer)
|
||||
return null;
|
||||
|
||||
return wpfTextView.Properties.GetOrCreateSingletonProperty(
|
||||
() => new YourTagger(wpfTextView)) as ITagger<T>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### QuickInfo Sources
|
||||
|
||||
```csharp
|
||||
[Export(typeof(IAsyncQuickInfoSourceProvider))]
|
||||
[Name("YourQuickInfo")]
|
||||
[ContentType("code")]
|
||||
[Order(Before = "Default Quick Info Presenter")]
|
||||
internal sealed class YourQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider
|
||||
{
|
||||
public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
|
||||
{
|
||||
return textBuffer.Properties.GetOrCreateSingletonProperty(
|
||||
() => new YourQuickInfoSource(textBuffer));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Suggested Actions (Light Bulb)
|
||||
|
||||
```csharp
|
||||
[Export(typeof(ISuggestedActionsSourceProvider))]
|
||||
[Name("Your Suggested Actions")]
|
||||
[ContentType("text")]
|
||||
internal sealed class YourSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
|
||||
{
|
||||
public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer)
|
||||
{
|
||||
return new YourSuggestedActionsSource(textView, textBuffer);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Threading Guidelines
|
||||
|
||||
### Always switch to UI thread for WPF operations
|
||||
|
||||
```csharp
|
||||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
// Now safe to create/modify WPF elements
|
||||
```
|
||||
|
||||
### Background work
|
||||
|
||||
```csharp
|
||||
ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
|
||||
{
|
||||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
||||
await VS.Commands.ExecuteAsync("View.TaskList");
|
||||
});
|
||||
```
|
||||
|
||||
## VSSDK & Threading Analyzer Rules
|
||||
|
||||
Extensions should enforce these analyzer rules. Add to `.editorconfig`:
|
||||
|
||||
```ini
|
||||
dotnet_diagnostic.VSSDK*.severity = error
|
||||
dotnet_diagnostic.VSTHRD*.severity = error
|
||||
```
|
||||
|
||||
### Performance Rules
|
||||
| ID | Rule | Fix |
|
||||
|----|------|-----|
|
||||
| **VSSDK001** | Derive from `AsyncPackage` | Use `ToolkitPackage` (derives from AsyncPackage) |
|
||||
| **VSSDK002** | `AllowsBackgroundLoading = true` | Add to `[PackageRegistration]` |
|
||||
|
||||
### Threading Rules (VSTHRD)
|
||||
| ID | Rule | Fix |
|
||||
|----|------|-----|
|
||||
| **VSTHRD001** | Avoid `.Wait()` | Use `await` |
|
||||
| **VSTHRD002** | Avoid `JoinableTaskFactory.Run` | Use `RunAsync` or `await` |
|
||||
| **VSTHRD010** | COM calls require UI thread | `await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()` |
|
||||
| **VSTHRD100** | No `async void` | Use `async Task` |
|
||||
| **VSTHRD110** | Observe async results | `await task;` or suppress with pragma |
|
||||
|
||||
## Visual Studio Theming
|
||||
|
||||
**All UI must respect VS themes (Light, Dark, Blue, High Contrast)**
|
||||
|
||||
### WPF Theming with Environment Colors
|
||||
|
||||
```xml
|
||||
<!-- MyControl.xaml -->
|
||||
<UserControl x:Class="MyExt.MyControl"
|
||||
xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
|
||||
<Grid Background="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundBrushKey}}">
|
||||
<TextBlock Foreground="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowTextBrushKey}}"
|
||||
Text="Hello, themed world!" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### Toolkit Auto-Theming (Recommended)
|
||||
|
||||
The toolkit provides automatic theming for WPF UserControls:
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="MyExt.MyUserControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
|
||||
toolkit:Themes.UseVsTheme="True">
|
||||
<!-- Controls automatically get VS styling -->
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
For dialog windows, use `DialogWindow`:
|
||||
|
||||
```xml
|
||||
<platform:DialogWindow
|
||||
x:Class="MyExt.MyDialog"
|
||||
xmlns:platform="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
|
||||
xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
|
||||
toolkit:Themes.UseVsTheme="True">
|
||||
</platform:DialogWindow>
|
||||
```
|
||||
|
||||
### Common Theme Color Tokens
|
||||
|
||||
| Category | Token | Usage |
|
||||
|----------|-------|-------|
|
||||
| **Background** | `EnvironmentColors.ToolWindowBackgroundBrushKey` | Window/panel background |
|
||||
| **Foreground** | `EnvironmentColors.ToolWindowTextBrushKey` | Text |
|
||||
| **Command Bar** | `EnvironmentColors.CommandBarTextActiveBrushKey` | Menu items |
|
||||
| **Links** | `EnvironmentColors.ControlLinkTextBrushKey` | Hyperlinks |
|
||||
|
||||
### Theme-Aware Icons
|
||||
|
||||
Use `KnownMonikers` from the VS Image Catalog for theme-aware icons:
|
||||
|
||||
```csharp
|
||||
public ImageMoniker IconMoniker => KnownMonikers.Settings;
|
||||
```
|
||||
|
||||
In VSCT:
|
||||
```xml
|
||||
<Icon guid="ImageCatalogGuid" id="Settings"/>
|
||||
<CommandFlag>IconIsMoniker</CommandFlag>
|
||||
```
|
||||
|
||||
## Common VS SDK APIs
|
||||
|
||||
### VS Helper Methods (Community.VisualStudio.Toolkit)
|
||||
|
||||
```csharp
|
||||
// Status bar
|
||||
await VS.StatusBar.ShowMessageAsync("Message");
|
||||
await VS.StatusBar.ShowProgressAsync("Working...", currentStep, totalSteps);
|
||||
|
||||
// Solution/Projects
|
||||
Solution solution = await VS.Solutions.GetCurrentSolutionAsync();
|
||||
IEnumerable<SolutionItem> items = await VS.Solutions.GetActiveItemsAsync();
|
||||
bool isOpen = await VS.Solutions.IsOpenAsync();
|
||||
|
||||
// Documents
|
||||
DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();
|
||||
string text = docView?.TextBuffer?.CurrentSnapshot.GetText();
|
||||
await VS.Documents.OpenAsync(fileName);
|
||||
await VS.Documents.OpenInPreviewTabAsync(fileName);
|
||||
|
||||
// Commands
|
||||
await VS.Commands.ExecuteAsync("View.TaskList");
|
||||
|
||||
// Settings
|
||||
await VS.Settings.OpenAsync<OptionsProvider.GeneralOptions>();
|
||||
|
||||
// Messages
|
||||
await VS.MessageBox.ShowAsync("Title", "Message");
|
||||
await VS.MessageBox.ShowErrorAsync("Extension Name", ex.ToString());
|
||||
|
||||
// Events
|
||||
VS.Events.SolutionEvents.OnAfterOpenProject += OnAfterOpenProject;
|
||||
VS.Events.DocumentEvents.Saved += OnDocumentSaved;
|
||||
```
|
||||
|
||||
### Working with Settings
|
||||
|
||||
```csharp
|
||||
// Read settings synchronously
|
||||
var value = General.Instance.MyOption;
|
||||
|
||||
// Read settings asynchronously
|
||||
var general = await General.GetLiveInstanceAsync();
|
||||
var value = general.MyOption;
|
||||
|
||||
// Write settings
|
||||
General.Instance.MyOption = newValue;
|
||||
General.Instance.Save();
|
||||
|
||||
// Or async
|
||||
general.MyOption = newValue;
|
||||
await general.SaveAsync();
|
||||
|
||||
// Listen for settings changes
|
||||
General.Saved += OnSettingsSaved;
|
||||
```
|
||||
|
||||
### Text Buffer Operations
|
||||
|
||||
```csharp
|
||||
// Get snapshot
|
||||
ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
|
||||
|
||||
// Get line
|
||||
ITextSnapshotLine line = snapshot.GetLineFromLineNumber(lineNumber);
|
||||
string lineText = line.GetText();
|
||||
|
||||
// Create tracking span
|
||||
ITrackingSpan trackingSpan = snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeInclusive);
|
||||
|
||||
// Edit buffer
|
||||
using (ITextEdit edit = textBuffer.CreateEdit())
|
||||
{
|
||||
edit.Replace(span, newText);
|
||||
edit.Apply();
|
||||
}
|
||||
|
||||
// Insert at caret position
|
||||
DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();
|
||||
if (docView?.TextView != null)
|
||||
{
|
||||
SnapshotPoint position = docView.TextView.Caret.Position.BufferPosition;
|
||||
docView.TextBuffer?.Insert(position, "text to insert");
|
||||
}
|
||||
```
|
||||
|
||||
## VSCT Command Table
|
||||
|
||||
### Menu/Command Structure
|
||||
|
||||
```xml
|
||||
<Commands package="YourPackage">
|
||||
<Menus>
|
||||
<Menu guid="YourPackage" id="SubMenu" type="Menu">
|
||||
<Parent guid="YourPackage" id="MenuGroup"/>
|
||||
<Strings>
|
||||
<ButtonText>Menu Name</ButtonText>
|
||||
<CommandName>Menu Name</CommandName>
|
||||
<CanonicalName>.YourExtension.MenuName</CanonicalName>
|
||||
</Strings>
|
||||
</Menu>
|
||||
</Menus>
|
||||
|
||||
<Groups>
|
||||
<Group guid="YourPackage" id="MenuGroup" priority="0x0600">
|
||||
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
|
||||
</Group>
|
||||
</Groups>
|
||||
|
||||
<Buttons>
|
||||
<Button guid="YourPackage" id="CommandId" type="Button">
|
||||
<Parent guid="YourPackage" id="MenuGroup"/>
|
||||
<Icon guid="ImageCatalogGuid" id="Settings"/>
|
||||
<CommandFlag>IconIsMoniker</CommandFlag>
|
||||
<CommandFlag>DynamicVisibility</CommandFlag>
|
||||
<Strings>
|
||||
<ButtonText>Command Name</ButtonText>
|
||||
<CanonicalName>.YourExtension.CommandName</CanonicalName>
|
||||
</Strings>
|
||||
</Button>
|
||||
</Buttons>
|
||||
</Commands>
|
||||
|
||||
<Symbols>
|
||||
<GuidSymbol name="YourPackage" value="{guid-here}">
|
||||
<IDSymbol name="MenuGroup" value="0x0001"/>
|
||||
<IDSymbol name="CommandId" value="0x0100"/>
|
||||
</GuidSymbol>
|
||||
</Symbols>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Performance
|
||||
|
||||
- Check file/buffer size before processing large documents
|
||||
- Use `NormalizedSnapshotSpanCollection` for efficient span operations
|
||||
- Cache parsed results when possible
|
||||
- Use `ConfigureAwait(false)` in library code
|
||||
|
||||
```csharp
|
||||
// Skip large files
|
||||
if (buffer.CurrentSnapshot.Length > 150000)
|
||||
return null;
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
- Wrap external operations in try-catch
|
||||
- Log errors appropriately
|
||||
- Never let exceptions crash VS
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
// Operation
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ex.LogAsync();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Disposable Resources
|
||||
|
||||
- Implement `IDisposable` on taggers and other long-lived objects
|
||||
- Unsubscribe from events in Dispose
|
||||
|
||||
```csharp
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_buffer.Changed -= OnBufferChanged;
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Content Types
|
||||
|
||||
Common content types for `[ContentType]` attribute:
|
||||
- `"text"` - All text files
|
||||
- `"code"` - All code files
|
||||
- `"CSharp"` - C# files
|
||||
- `"Basic"` - VB.NET files
|
||||
- `"CSS"`, `"LESS"`, `"SCSS"` - Style files
|
||||
- `"TypeScript"`, `"JavaScript"` - Script files
|
||||
- `"HTML"`, `"HTMLX"` - HTML files
|
||||
- `"XML"` - XML files
|
||||
- `"JSON"` - JSON files
|
||||
|
||||
### 5. Images and Icons
|
||||
|
||||
Use `KnownMonikers` from the VS Image Catalog:
|
||||
|
||||
```csharp
|
||||
public ImageMoniker IconMoniker => KnownMonikers.Settings;
|
||||
```
|
||||
|
||||
In VSCT:
|
||||
```xml
|
||||
<Icon guid="ImageCatalogGuid" id="Settings"/>
|
||||
<CommandFlag>IconIsMoniker</CommandFlag>
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- Use `[VsTestMethod]` for tests requiring VS context
|
||||
- Mock VS services when possible
|
||||
- Test business logic separately from VS integration
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
| Pitfall | Solution |
|
||||
|---------|----------|
|
||||
| Blocking UI thread | Always use `async`/`await` |
|
||||
| Creating WPF on background thread | Call `SwitchToMainThreadAsync()` first |
|
||||
| Ignoring cancellation tokens | Pass them through async chains |
|
||||
| VSCommandTable.cs mismatch | Regenerate after VSCT changes |
|
||||
| Hardcoded GUIDs | Use `PackageGuids` and `PackageIds` constants |
|
||||
| Swallowing exceptions | Log with `await ex.LogAsync()` |
|
||||
| Missing DynamicVisibility | Required for `BeforeQueryStatus` to work |
|
||||
| Using `.Result`, `.Wait()` | Causes deadlocks; always `await` |
|
||||
| Hardcoded colors | Use VS theme colors (`EnvironmentColors`) |
|
||||
| `async void` methods | Use `async Task` instead |
|
||||
|
||||
## Validation
|
||||
|
||||
Build and verify the extension:
|
||||
|
||||
```bash
|
||||
msbuild /t:rebuild
|
||||
```
|
||||
|
||||
Ensure analyzers are enabled in `.editorconfig`:
|
||||
|
||||
```ini
|
||||
dotnet_diagnostic.VSSDK*.severity = error
|
||||
dotnet_diagnostic.VSTHRD*.severity = error
|
||||
```
|
||||
|
||||
Test in VS Experimental Instance before release.
|
||||
|
||||
## NuGet Packages
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| `Community.VisualStudio.Toolkit.17` | Simplifies VS extension development |
|
||||
| `Microsoft.VisualStudio.SDK` | Core VS SDK |
|
||||
| `Microsoft.VSSDK.BuildTools` | Build tools for VSIX |
|
||||
| `Microsoft.VisualStudio.Threading.Analyzers` | Threading analyzers |
|
||||
| `Microsoft.VisualStudio.SDK.Analyzers` | VSSDK analyzers |
|
||||
|
||||
## Resources
|
||||
|
||||
- [Community.VisualStudio.Toolkit](https://github.com/VsixCommunity/Community.VisualStudio.Toolkit)
|
||||
- [VS Extensibility Docs](https://learn.microsoft.com/en-us/visualstudio/extensibility/)
|
||||
- [VSIX Community Samples](https://github.com/VsixCommunity/Samples)
|
||||
|
||||
## README and Marketplace Presentation
|
||||
|
||||
A good README works on both GitHub and the VS Marketplace. The Marketplace uses the README.md as the extension's description page.
|
||||
|
||||
### README Structure
|
||||
|
||||
```markdown
|
||||
[marketplace]: https://marketplace.visualstudio.com/items?itemName=Publisher.ExtensionName
|
||||
[repo]: https://github.com/user/repo
|
||||
|
||||
# Extension Name
|
||||
|
||||
[](...)
|
||||
[][marketplace]
|
||||
[][marketplace]
|
||||
|
||||
Download this extension from the [Visual Studio Marketplace][marketplace]
|
||||
or get the [CI build](http://vsixgallery.com/extension/ExtensionId/).
|
||||
|
||||
--------------------------------------
|
||||
|
||||
**Hook line that sells the extension in one sentence.**
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
### Feature 1
|
||||
Description with screenshot...
|
||||
|
||||
## How to Use
|
||||
...
|
||||
|
||||
## License
|
||||
[Apache 2.0](LICENSE)
|
||||
```
|
||||
|
||||
### README Best Practices
|
||||
|
||||
| Element | Guideline |
|
||||
|---------|-----------|
|
||||
| **Title** | Use the same name as `DisplayName` in vsixmanifest |
|
||||
| **Hook line** | Bold, one-sentence value proposition immediately after badges |
|
||||
| **Screenshots** | Place in `/art` folder, use relative paths (`art/image.png`) |
|
||||
| **Image sizes** | Keep under 1MB, 800-1200px wide for clarity |
|
||||
| **Badges** | Version, downloads, rating, build status |
|
||||
| **Feature sections** | Use H3 (`###`) with screenshots for each major feature |
|
||||
| **Keyboard shortcuts** | Format as **Ctrl+M, Ctrl+C** (bold) |
|
||||
| **Tables** | Great for comparing options or listing features |
|
||||
| **Links** | Use reference-style links at top for cleaner markdown |
|
||||
|
||||
### VSIX Manifest (source.extension.vsixmanifest)
|
||||
|
||||
```xml
|
||||
<Metadata>
|
||||
<Identity Id="ExtensionName.guid-here" Version="1.0.0" Language="en-US" Publisher="Your Name" />
|
||||
<DisplayName>Extension Name</DisplayName>
|
||||
<Description xml:space="preserve">Short, compelling description under 200 chars. This appears in search results and the extension tile.</Description>
|
||||
<MoreInfo>https://github.com/user/repo</MoreInfo>
|
||||
<License>Resources\LICENSE.txt</License>
|
||||
<Icon>Resources\Icon.png</Icon>
|
||||
<PreviewImage>Resources\Preview.png</PreviewImage>
|
||||
<Tags>keyword1, keyword2, keyword3</Tags>
|
||||
</Metadata>
|
||||
```
|
||||
|
||||
### Manifest Best Practices
|
||||
|
||||
| Element | Guideline |
|
||||
|---------|-----------|
|
||||
| **DisplayName** | 3-5 words, no "for Visual Studio" (implied) |
|
||||
| **Description** | Under 200 chars, focus on value not features. Appears in search tiles |
|
||||
| **Tags** | 5-10 relevant keywords, comma-separated, helps discoverability |
|
||||
| **Icon** | 128x128 or 256x256 PNG, simple design visible at small sizes |
|
||||
| **PreviewImage** | 200x200 PNG, can be same as Icon or a feature screenshot |
|
||||
| **MoreInfo** | Link to GitHub repo for documentation and issues |
|
||||
|
||||
### Writing Tips
|
||||
|
||||
1. **Lead with benefits, not features** - "Stop wrestling with XML comments" beats "XML comment formatter"
|
||||
2. **Show, don't tell** - Screenshots are more convincing than descriptions
|
||||
3. **Use consistent terminology** - Match terms between README, manifest, and UI
|
||||
4. **Keep the description scannable** - Short paragraphs, bullet points, tables
|
||||
5. **Include keyboard shortcuts** - Users love productivity tips
|
||||
6. **Add a "Why" section** - Explain the problem before the solution
|
||||
Reference in New Issue
Block a user