agentleFS
Microsoft AzureKnown publisherAzure/Connectors-NET-LSP/.github/copilot-instructions.md

Connectors-NET-LSP

Copilot instructions0 starsChanged 6 months agoMITView on GitHub

This repository contains the Language Server Protocol (LSP) server and VS Code extension for Azure Connectors SDK development. It provides intelligent code assistance including hover, completion, CodeLens, and SDK assembly analysis. Code must follow the team's coding conventions based on BPM repo standards. Variable naming rules: One type per file: ALWAYS use this multi-line format: Single line if all parameters fit: Boolean parameters MUST always use named arguments: Multi-line with named parameters: Inline comments - use NOTE format: XML documentation…

  • Where files are headingNew Copilot instructions files have more rules about branches and pull requests, and fewer about code style.

    Of the Copilot instructions files we read, the 2,336 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

The file

Show its rules and text

We split this copilot-instructions.md into the rules it gives an agent

19 rules. About 0 are common, 8 are distinctive, and 11 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 11

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    No empty lines between using groups

    line 32
  2. Distinctive:

    Use complete, unabbreviated English terms for all identifiers

    line 47
  3. Very specific:

    No single-letter variable names, even in lambdas (use arg, item, method, parameter)

    line 48
  4. Very specific:

    No placeholder names (blah, foo, temp, x) — always use meaningful names

    line 49
  5. Very specific:

    ALWAYS use ConfigureAwait(continueOnCapturedContext: false) with explicit parameter name

    line 73
  6. Distinctive:

    Single line: if all parameters fit:

    line 93
  7. Distinctive:

    NO empty line between comment and code it describes

    line 132
  8. Distinctive:

    Do NOT comment on the 'what' unless the code is obscure.

    line 134
  9. Very specific:

    Document return values with <returns> tags on all public methods that return a value (non-void)

    line 149
  10. Very specific:

    Use <see cref="ClassName"/> for type references

    line 150
  11. Very specific:

    Use exception filter when (!ex.IsFatal()) for general catches to avoid catching fatal exceptions

    line 175
  12. Distinctive:

    Before implementing a new feature or significant change:

    line 336
  13. Very specific:

    Test method naming: MethodName_Scenario_ExpectedResult

    line 367
  14. Very specific:

    Use async/await, never .Result

    line 368
  15. Very specific:

    Use ConfigureAwait(continueOnCapturedContext: false) in tests too

    line 369
  16. Distinctive:

    Never push directly to main

    line 374
  17. Distinctive:

    Always create PR for review

    line 375
  18. Very specific:

    Do not assume [DynamicValues] parameters are always string.

    line 399
  19. Very specific:

    When distinguishing source-defined types from SDK metadata references, compare against semanticModel.Compilation.AssemblyName rather than …

    line 414

≈ “Common” is an estimate: at least 1 in 5 of the 3,779 Copilot instructions files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
copilot-instructions.md

Copilot Instructions for Connectors-NET-LSP

Overview

This repository contains the Language Server Protocol (LSP) server and VS Code extension for Azure Connectors SDK development. It provides intelligent code assistance including hover, completion, CodeLens, and SDK assembly analysis. Code must follow the team's coding conventions based on BPM repo standards.

Quick Reference: Coding Style Rules

File Structure

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

using System;
using System.Collections.Generic;

namespace SdkLspServer
{
    public class YourClass
    {
    }
}

Rules:

  • Copyright header: Use //---- (4 dashes) format with double space before "All rights reserved"
  • Usings OUTSIDE namespace (standard C# convention)
  • Usings sorted: System.* first, then alphabetically
  • No empty lines between using groups

Naming and Qualification

ElementRuleExample
Static membersQualify with class nameMyClass.StaticMethod()
Instance membersQualify with this.this.instanceField
Private fieldsUse _camelCaseprivate readonly string _connectionString;
ConstantsQualify with class nameMyClass.DefaultTimeout
Local variablesUse complete English termsparameter not p, method not m
Lambda parametersUse descriptive namesmethods.Where(method => ...) not m => ...

Variable naming rules:

  • Use complete, unabbreviated English terms for all identifiers
  • No single-letter variable names, even in lambdas (use arg, item, method, parameter)
  • No placeholder names (blah, foo, temp, x) — always use meaningful names

File Organization

One type per file:

  • Declare only one class, struct, enum, or interface per file
  • File name must match the type name
  • Exception: Nested types (e.g., private helper classes) are allowed within the containing type

Async/Await Format

ALWAYS use this multi-line format:

var result = await this.httpClient
    .GetAsync(requestUri)
    .ConfigureAwait(continueOnCapturedContext: false);

Rules:

  • Period on NEW line, not end of previous line
  • Arguments indented ONE level (4 spaces)
  • ALWAYS use ConfigureAwait(continueOnCapturedContext: false) with explicit parameter name
  • Exception: Skip await for lone return statement (unless inside using block)

DO NOT:

// Wrong: ConfigureAwait without named parameter
.ConfigureAwait(false);

// Wrong: Method call on same line as object
var result = await this.httpClient.GetAsync(requestUri)
    .ConfigureAwait(continueOnCapturedContext: false);

// Wrong: Dot at end of line
var result = await this.httpClient.
    GetAsync(requestUri);

Method Calls with Multiple Parameters

Single line if all parameters fit:

this.DoSomething(param1, param2);

Boolean parameters MUST always use named arguments:

// Correct
this.CreateHandler(logger, isEnabled: true);

// Wrong - unnamed boolean is ambiguous
this.CreateHandler(logger, true);

Multi-line with named parameters:

return await this
    .ProcessRequestAsync(
        requestUri: uri,
        content: payload,
        cancellationToken: cancellationToken)
    .ConfigureAwait(continueOnCapturedContext: false);

Comments

Inline comments - use NOTE format:

// NOTE(username): Explanation of why this code exists.
var result = DoSomething();

Rules:

  • Empty line ABOVE comment (unless first line in block)
  • NO empty line between comment and code it describes
  • Prefix: // NOTE(username): where username is your GitHub username
  • Do NOT comment on the 'what' unless the code is obscure; instead comment on the 'why' when appropriate

XML documentation - required for all public APIs:

/// <summary>
/// Processes the incoming request and returns the result.
/// </summary>
/// <param name="request">The request to process.</param>
public async Task<Response> ProcessAsync(Request request)

Rules:

  • End descriptions with period
  • Document return values with <returns> tags on all public methods that return a value (non-void)
  • Use <see cref="ClassName"/> for type references

Exception Handling

try
{
    await this
        .DoWorkAsync()
        .ConfigureAwait(continueOnCapturedContext: false);
}
catch (SpecificException ex)
{
    this.logger.LogError(ex, "Failed: '{Message}'.", ex.Message);
    throw;
}
catch (Exception ex) when (!ex.IsFatal())
{
    throw new InvalidOperationException(message: "Operation failed.", innerException: ex);
}

Rules:

  • Exception variable name: ex (not exception)
  • Use exception filter when (!ex.IsFatal()) for general catches to avoid catching fatal exceptions
  • Wrap inserted values in single quotes in error messages
  • End error messages with period
  • All exceptions must have descriptive messages — never throw exceptions without context

String Comparison

ALWAYS use StringComparison:

// Correct
string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)
str.StartsWith(prefix, StringComparison.Ordinal)

DO NOT:

str1 == str2
str1.Equals(str2)

Spacing and Braces

Empty line after closing brace:

if (condition)
{
    DoSomething();
}

DoSomethingElse();  // Empty line above

NO empty line before closing brace.

Switch statements - empty line between cases:

switch (value)
{
    case "A":
        HandleA();
        break;

    case "B":
        HandleB();
        break;

    default:
        throw new InvalidOperationException();
}

Variable Declaration

Use var when type is obvious:

var items = new List<string>();
var response = await this.GetResponseAsync();

Use explicit type for null initialization:

byte[] buffer = null;  // Not: var buffer = (byte[])null;

Ternary Operators

Put ? and : at START of new line:

var result = condition
    ? valueIfTrue
    : valueIfFalse;

Logical Operators

Put || and && at END of line:

if (string.IsNullOrEmpty(value1) ||
    string.IsNullOrEmpty(value2) ||
    string.IsNullOrEmpty(value3))

Access Modifiers

ALWAYS explicit - order: access, static, readonly, other:

public static readonly string DefaultValue = "default";
private readonly ILogger _logger;
internal async Task ProcessAsync()

Class Layout Order

  1. Constants
  2. Static fields
  3. Instance fields
  4. Constructors
  5. Properties
  6. Public methods
  7. Internal methods
  8. Private methods

Within each group: public → internal → private

Patterns to Avoid

Anti-PatternCorrect Pattern
.Result on Taskawait task.ConfigureAwait(continueOnCapturedContext: false)
.Wait() on Taskawait task.ConfigureAwait(continueOnCapturedContext: false)
Task.Run() for I/Oawait the async method directly
new Exception("msg.")new SpecificException(message: "msg.")
Magic numbersNamed constants (e.g., MyClass.DefaultTimeoutSeconds)
Magic strings (e.g., "type", "object")Named constants (e.g., SchemaPropertyNames.Type)
[0] or .First().Single() (or .SingleOrDefault() + explicit validation)

Pre-Commit Review Checklist

Before committing, verify:

  1. Coding standards — Grep changed files for:
    • Single-letter lambda parameters (p =>, v =>, c =>) → rename to descriptive names
    • StringComparison missing on .StartsWith(), .EndsWith(), .Contains(), .IndexOf(), .Replace()
    • Unnamed boolean arguments → add named parameter
    • ConfigureAwait(false) → ConfigureAwait(continueOnCapturedContext: false)
  2. Comment staleness — For every behavior change, search for comments/docs describing the old behavior. Update all of them, not just the code. Common locations:
    • XML <summary> docs on the changed method
    • Inline comments near the changed code
    • Startup/initialization comments referencing the feature
    • README sections describing the feature
  3. Edge cases — For every new parameter, function, or code path, ask:
    • What if the input is null/empty?
    • What if the input is the wrong type (e.g., DLL path where nupkg expected)?
    • What if a dependency is missing (e.g., no project.assets.json)?
    • What if a collection has 0 items? 1 item? Many items?
  4. Design completeness — Before pushing a fix, review the full call chain:
    • Does the caller handle all return values correctly?
    • Are property/parameter names accurate for all callers (not just the original use case)?
    • Will the next reviewer find a cascading issue from this change?
  5. Platform edge cases — Consider all target environments:
    • Windows: drive-letter roots (C:\), backslash paths, case-insensitive filesystem
    • macOS/Linux: no grep -P, case-sensitive filesystem, forward slashes, BSD vs GNU tools
    • CLI args: empty values (--flag=), missing values (--flag --next-flag), multiple values
    • NuGet: multi-TFM projects, multiple package folders, case variations in package keys, missing project.assets.json
    • Large workspaces: skip node_modules, .git, bin, obj during filesystem scans

Design-First Approach

Before implementing a new feature or significant change:

  1. Draft the API surface — Write function signatures, parameter types, return types, setting names, and CLI arguments in a scratch comment or directly in the code as stubs
  2. Review naming — Are names accurate for all callers? Will they be misleading when the feature evolves?
  3. Enumerate edge cases — Walk through the platform checklist above for each new parameter
  4. Consider the full call chain — Trace from CLI/extension entry point through to the final consumer. Identify null paths, empty collections, and type mismatches
  5. Then implement — Write the body only after the design is settled

This prevents cascading review cycles where each fix introduces new naming, edge case, or consistency issues.

Testing

[TestMethod]
public async Task MethodName_Scenario_ExpectedResult()
{
    // Arrange
    var input = CreateTestInput();

    // Act
    var result = await this.service
        .ProcessAsync(input)
        .ConfigureAwait(continueOnCapturedContext: false);

    // Assert
    Assert.IsNotNull(result);
}

Rules:

  • Test method naming: MethodName_Scenario_ExpectedResult
  • Use async/await, never .Result
  • Use ConfigureAwait(continueOnCapturedContext: false) in tests too

Git Workflow

  • Branch naming: feature/description, fix/description, docs/description
  • Never push directly to main
  • Always create PR for review

Architecture: Diagnostic Validators

Validators target SDK consumers, not SDK authors

Diagnostic validators (IDiagnosticValidator) analyze customer code that calls SDK connector client methods — not the SDK source code itself. For example, a validator checks whether client.GetAllTablesAsync("foobar", ct) passes a valid value, not whether the SDK's [DynamicValues] attribute is correctly declared.

  • Call-site analysis requires CompilationService for semantic model (resolve IMethodSymbol, inspect IParameterSymbol attributes)
  • Attribute-declaration analysis would only help SDK generator authors — not the LSP's target audience

[DynamicValues] is not string-only

The SDK uses [DynamicValues] on non-string parameters too:

// SharePoint approval types use int
[DynamicValues("GetApprovalTypes")] int approvalType

Do not assume [DynamicValues] parameters are always string.

DynamicValueItem.Value quoting

Hover and completion handlers store cached values with surrounding " for code insertion:

Value = $"\"{item.Name}\""   // e.g., "\"siteUrl\""

Any code comparing cached values against Token.ValueText (which returns unquoted content) must strip surrounding quotes first. See ValuesMatch() in DynamicValuesValidator.

CompilationService assembly identity

When distinguishing source-defined types from SDK metadata references, compare against semanticModel.Compilation.AssemblyName rather than hard-coding the compilation name. Currently CompilationService.GetCompilation() uses "LspAnalysis" and CreateSdkMetadataCompilation() uses "SdkMetadataCompilation", but comparing dynamically ensures the validator stays correct if these names change.

VSTHRD103: Prefer async GetText in validators

In diagnostic validators (which implement IDiagnosticValidator.ValidateAsync), prefer await tree.GetTextAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false) over synchronous SyntaxTree.GetText(). The VSTHRD103 analyzer flags synchronous calls in async methods. Note: CompilationService uses GetText() synchronously in a non-async context, which is fine.

Development: DLL lock workaround

The VS Code extension locks SdkLspServer.dll at Server/bin/Debug/net8.0/. When iterating on the server code while the extension is running:

  1. Build to an alternate path: dotnet build Server/SdkLspServer.csproj -o <writable-folder> (e.g., C:\temp\lsp-build on Windows or /tmp/lsp-build on macOS/Linux)
  2. Point connectorSdk.lspServerPath in workspace settings to the alternate path
  3. Reload the VS Code window to pick up the new DLL

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.