Merge branch 'main' into add-agent-skills-instructions

This commit is contained in:
Matt Soucoup
2026-01-09 13:24:43 -10:00
committed by GitHub
42 changed files with 8690 additions and 6 deletions

View File

@@ -485,7 +485,7 @@ class SimpleDataAgent:
"""Agent function: Check table health."""
try:
tables = self.client.list_tables()
matching = [t for t in tables if t['table_logical_name'] == table_name]
matching = [t for t in tables if t['LogicalName'] == table_name]
if not matching:
return {"status": "error", "message": f"Table {table_name} not found"}

View 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

View 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()`

View 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

View File

@@ -0,0 +1,122 @@
---
description: "Symfony development standards aligned with official Symfony Best Practices"
applyTo: "**/*.php, **/*.yaml, **/*.yml, **/*.xml, **/*.twig"
---
# Symfony Development Instructions
Instructions for developing Symfony applications following the official Symfony Best Practices and core framework philosophy.
## Project Context
- Symfony (latest stable or LTS)
- Default Symfony directory structure
- Autowiring and autoconfiguration enabled
- Doctrine ORM when persistence is needed
- Twig for templating
- Symfony Forms, Validator, Security, Messenger as needed
- PHPUnit for testing
- Attribute-based configuration where supported
## Project Structure
- Use the default Symfony directory structure
- Do not create bundles for application code
- Organize application code using PHP namespaces
- Keep configuration in `config/`, application code in `src/`, templates in `templates/`
## Configuration
### Environment Configuration
- Use environment variables for infrastructure-related configuration
- Use `.env` files to define environment-specific values
- Do not use environment variables to control application behavior
### Sensitive Configuration
- Store secrets (API keys, credentials) using Symfony Secrets
- Never commit secrets to the repository
### Application Configuration
- Use parameters in `config/services.yaml` for application behavior configuration
- Override parameters per environment only when needed
- Prefix parameters with `app.` to avoid collisions
- Use short, descriptive parameter names
- Use PHP constants for configuration values that rarely change
## Services & Dependency Injection
- Use dependency injection exclusively
- Prefer constructor injection
- Use autowiring and autoconfiguration by default
- Keep services private whenever possible
- Avoid accessing services via `$container->get()`
- Use YAML as the preferred format for service configuration
- Use interfaces where it improves decoupling or clarity
## Controllers
- Extend `AbstractController`
- Keep controllers thin and focused on glue code
- Do not place business logic in controllers
- Use attributes to configure routing, caching, and security
- Use dependency injection for services
- Use Entity Value Resolvers when convenient and appropriate
- Perform complex queries explicitly via repositories when needed
## Doctrine & Persistence
- Use Doctrine entities as plain PHP objects
- Define Doctrine mapping using PHP attributes
- Use repositories for querying data
- Avoid putting business logic in repositories
- Use migrations for all schema changes
## Templates (Twig)
- Use snake_case for template names, directories, and variables
- Prefix template fragments with an underscore
- Keep templates focused on presentation
- Avoid business logic in Twig templates
- Escape output by default
- Avoid using `|raw` unless content is trusted and sanitized
## Forms
- Define forms as PHP classes
- Do not build forms directly in controllers
- Add form buttons in templates, not in form classes
- Define validation constraints on the underlying object
- Use a single controller action to render and process each form
- Define submit buttons in controllers only when multiple submits are required
## Validation
- Use Symfony Validator constraints
- Validate data at application boundaries
- Prefer object-level validation over form-only validation when reuse is needed
## Internationalization
- Use XLIFF for translation files
- Use translation keys instead of literal content strings
- Use descriptive keys that express purpose, not location
## Security
- Prefer a single firewall unless multiple systems are required
- Use the auto password hasher
- Use voters for complex authorization logic
- Avoid complex security expressions in attributes
## Web Assets
- Use AssetMapper to manage web assets
- Avoid unnecessary frontend build complexity unless required
## Asynchronous Processing
- Use Symfony Messenger for async and background tasks
- Keep message handlers small and focused
- Configure failure transports for failed messages
## Testing
- Write functional tests using `WebTestCase`
- Add smoke tests to ensure all public URLs respond successfully
- Hard-code URLs in functional tests instead of generating routes
- Use unit tests where appropriate for isolated logic
- Add more specific tests incrementally as the application evolves
## General Guidelines
- Prefer clarity over abstraction
- Follow Symfony conventions before introducing custom patterns
- Keep configuration explicit and readable
- Avoid premature optimization
- Use Symfony Demo as a reference implementation

View File

@@ -13,10 +13,25 @@ Instructions for creating effective and maintainable prompt files that guide Git
- Primary references: VS Code documentation on prompt files and organization-specific conventions.
## Frontmatter Requirements
- Include `description` (single sentence, actionable outcome), `mode` (explicitly choose `ask`, `edit`, or `agent`), and `tools` (minimal set of tool bundles required to fulfill the prompt).
- Declare `model` when the prompt depends on a specific capability tier; otherwise inherit the active model.
- Preserve any additional metadata (`language`, `tags`, `visibility`, etc.) required by your organization.
- Use consistent quoting (single quotes recommended) and keep one field per line for readability and version control clarity.
Every prompt file should include YAML frontmatter with the following fields:
### Required/Recommended Fields
| Field | Required | Description |
|-------|----------|-------------|
| `description` | Recommended | A short description of the prompt (single sentence, actionable outcome) |
| `name` | Optional | The name shown after typing `/` in chat. Defaults to filename if not specified |
| `agent` | Recommended | The agent to use: `ask`, `edit`, `agent`, or a custom agent name. Defaults to current agent |
| `model` | Optional | The language model to use. Defaults to the currently selected model |
| `tools` | Optional | List of tool/tool set names available for this prompt |
| `argument-hint` | Optional | Hint text shown in chat input to guide user interaction |
### Guidelines
- Use consistent quoting (single quotes recommended) and keep one field per line for readability and version control clarity
- If `tools` are specified and the current agent is `ask` or `edit`, the default agent becomes `agent`
- Preserve any additional metadata (`language`, `tags`, `visibility`, etc.) required by your organization
## File Naming and Placement
- Use kebab-case filenames ending with `.prompt.md` and store them under `.github/prompts/` unless your workspace standard specifies another directory.

View 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).

View 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/)

View 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
[![Build](https://github.com/user/repo/actions/workflows/build.yaml/badge.svg)](...)
[![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/Publisher.ExtensionName)][marketplace]
[![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/Publisher.ExtensionName)][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.**
![Screenshot](art/screenshot.png)
## 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