Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"name": "Microsoft.Data.SqlClient",
"dockerComposeFile": "docker-compose.yml",
"dockerComposeFile": [
"docker-compose.yml",
"docker-compose.worktree.yml",
"docker-compose.local.yml"
],
"service": "devcontainer",
"workspaceFolder": "/workspaces/SqlClient",
"features": {
Expand All @@ -20,6 +24,13 @@
}
}
},
// Restore tools to enable compose file edits via powershell
"initializeCommand": "dotnet tool restore && dotnet tool run pwsh -- -NoProfile -ExecutionPolicy Bypass -File .devcontainer/init.ps1",
"postCreateCommand": "bash .devcontainer/setup-sqlserver.sh",
// We need to mark the repository as safe because it's not owned by the docker user.
// Refreshing the git index now gets the container immediately ready for git operations,
// otherwise a git command needs to be manually run to refresh the index.
// Restore tools again within the container to ensure they are available for developer use
"postStartCommand": "git config --global --add safe.directory /workspaces/SqlClient && git update-index --really-refresh && dotnet tool restore",
"remoteUser": "vscode"
}
150 changes: 150 additions & 0 deletions .devcontainer/init.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env pwsh
#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

# =============================================================================
# Cross-platform devcontainer initialization script.
# Runs on the HOST before the container starts. Detects worktree/project
# context and generates the .env file consumed by docker-compose.yml.
#
# Git worktree support:
# The .git file in a worktree contains an absolute host path. On Windows
# hosts this is a drive-letter path (C:/...) that Linux git cannot resolve.
# We maintain two path variants:
# - Host path: raw OS path used on the source side of bind mounts
# - Container path: POSIX path (/c/... on Windows) used inside the container
# A generated docker-compose.worktree.yml bind-mounts the main .git directory
# and overlays a corrected .git file so git works inside the container.
#
# Invoked via: dotnet tool run pwsh -- -NoProfile -ExecutionPolicy Bypass -File .devcontainer/init.ps1
# =============================================================================

$devcontainerDir = $PSScriptRoot # .devcontainer/

# --- Worktree and project detection ---

$worktreeDirName = Split-Path -Leaf (Get-Location)

# Sanitize worktree name for use in DB names, container names, etc.
$worktreeName = ($worktreeDirName -replace '[^a-zA-Z0-9-]', '_').ToLowerInvariant()

# Detect the current branch name.
$branchName = (git branch --show-current 2>$null) ?? ''
$branchName = ($branchName -replace '/', '-' -replace '[^a-zA-Z0-9-]', '_').ToLowerInvariant()

# Resolve the git common directory (the main .git directory).
# In a worktree this points to the main repo's .git; in a standard clone it's just .git.
$gitCommonDir = git rev-parse --git-common-dir 2>$null
if (-not [System.IO.Path]::IsPathRooted($gitCommonDir)) {
$gitCommonDir = Join-Path (Get-Location) $gitCommonDir
}
$gitCommonDir = (Resolve-Path $gitCommonDir).Path

# Host path: use forward slashes (Docker Desktop accepts both separators).
$gitCommonDirHost = $gitCommonDir -replace '\\', '/'

# Container path: on Windows convert drive-letter paths ("C:/foo") to POSIX
# ("/c/foo") so they resolve inside the Linux container.
$gitCommonDirContainer = $gitCommonDirHost
if ($gitCommonDirContainer -match '^([A-Za-z]):(.*)$') {
$gitCommonDirContainer = '/' + $Matches[1].ToLowerInvariant() + $Matches[2]
}

$mainRepoName = Split-Path -Leaf (Split-Path -Parent $gitCommonDirHost)
$projectName = $env:PROJECT_NAME ?? $mainRepoName

$composeProjectName = "$projectName-$branchName"
$localWorkspaceFolder = (Get-Location).Path

# Detect if this is a worktree (informational).
$dotGitPath = Join-Path (Get-Location) '.git'
if (Test-Path $dotGitPath -PathType Leaf) {
Write-Host "[devcontainer] Worktree detected. GIT_COMMON_DIR='$gitCommonDirHost' (container: '$gitCommonDirContainer')"
} else {
Write-Host "[devcontainer] Standard clone. GIT_COMMON_DIR='$gitCommonDirHost'"
}

# --- Git worktree container fixup ---
# In a git worktree the .git *file* contains an absolute host path.
# Two problems must be solved for the Linux container:
# 1. On Windows hosts the path uses a drive letter (C:/...) that Linux git
# cannot resolve — rewrite it to the container-side POSIX path.
# 2. The main .git directory lives outside the workspace mount and must be
# bind-mounted into the container at the container-side path.
#
# We generate:
# .git-overlay – corrected .git file referencing container paths
# docker-compose.worktree.yml – bind-mounts the main .git dir + the overlay
#
# Long-form volume syntax is used for the GIT_COMMON_DIR mount so Docker does
# not try to split the path on colons (which breaks Windows drive letters).
#
# For standard (non-worktree) clones both files are no-op stubs.

$worktreeComposePath = Join-Path $devcontainerDir 'docker-compose.worktree.yml'
$gitOverlayPath = Join-Path $devcontainerDir '.git-overlay'

if (Test-Path $dotGitPath -PathType Leaf) {
# Worktree: .git is a file — read it and rewrite with container-side paths.
$gitFileContent = (Get-Content $dotGitPath -Raw).Trim()
if ($gitFileContent -match 'gitdir:\s*(.+)') {
$originalGitdir = $Matches[1].Trim() -replace '\\', '/'
# Convert Windows drive-letter path to POSIX for the container.
$containerGitdir = $originalGitdir
if ($containerGitdir -match '^([A-Za-z]):(.*)$') {
$containerGitdir = '/' + $Matches[1].ToLowerInvariant() + $Matches[2]
}
Set-Content -Path $gitOverlayPath -Value "gitdir: $containerGitdir`n" -NoNewline

# Generate compose override. Long-form volume for the GIT_COMMON_DIR
# mount avoids colon-parsing issues with Windows paths.
$worktreeComposeContent = @"
# Auto-generated by init.ps1 - DO NOT EDIT.
# Bind-mounts the main .git directory and overlays a corrected .git file
# so that git works inside the container for worktree checkouts.
services:
devcontainer:
volumes:
- type: bind
source: $gitCommonDirHost
target: $gitCommonDirContainer
- ./.git-overlay:/workspaces/SqlClient/.git:ro
"@
Set-Content -Path $worktreeComposePath -Value $worktreeComposeContent -NoNewline
Write-Host "[devcontainer] Generated git worktree overlay for container."
} else {
Write-Host "[devcontainer] WARNING: Could not parse .git file at '$dotGitPath'."
Set-Content -Path $worktreeComposePath -Value "# Auto-generated by init.ps1 - could not parse .git file.`nservices: {}" -NoNewline
if (Test-Path $gitOverlayPath) { Remove-Item $gitOverlayPath }
}
} else {
# Standard clone: .git is a directory, already covered by the workspace mount.
Set-Content -Path $worktreeComposePath -Value "# Auto-generated by init.ps1 - standard clone, no worktree fixup needed.`nservices: {}" -NoNewline
if (Test-Path $gitOverlayPath) { Remove-Item $gitOverlayPath }
}

# --- Local compose overrides ---
# docker-compose.local.yml is listed in devcontainer.json so it must exist.
# Create an empty stub if missing (gitignored — developers can add personal overrides).

$localComposePath = Join-Path $devcontainerDir 'docker-compose.local.yml'
if (-not (Test-Path $localComposePath)) {
Set-Content -Path $localComposePath -Value "# Personal Docker Compose overrides (gitignored).`n# See docker-compose.yml for the base configuration.`n"
}

# --- Write .env for docker-compose variable substitution ---

$envContent = @"
COMPOSE_PROJECT_NAME=$composeProjectName
WORKTREE_NAME=$worktreeName
BRANCH_NAME=$branchName
GIT_COMMON_DIR_HOST=$gitCommonDirHost
GIT_COMMON_DIR_CONTAINER=$gitCommonDirContainer
MAIN_REPO_NAME=$mainRepoName
PROJECT_NAME=$projectName
LOCAL_WORKSPACE_FOLDER=$localWorkspaceFolder
"@
Set-Content -Path (Join-Path $devcontainerDir '.env') -Value $envContent -NoNewline

Write-Host "[devcontainer] init.ps1 complete for worktree '$worktreeName' branch '$branchName' (project: $projectName)"
Empty file modified .devcontainer/setup-sqlserver.sh
100755 → 100644
Empty file.
Empty file modified .devcontainer/sqlserver-entrypoint.sh
100755 → 100644
Empty file.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
###############################################################################
* text=auto

# Shell scripts must always use LF line endings, even on Windows checkouts,
# because they are bind-mounted into Linux containers.
*.sh text eol=lf

###############################################################################
# Set default behavior for command prompt diff.
#
Expand Down
2 changes: 1 addition & 1 deletion .github/prompts/generate-doc-comments.prompt.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: doc-comments
name: generate-doc-comments
description: Generate XML documentation comments for C# code following .NET best practices.
argument-hint: <code>
agent: agent
Expand Down
63 changes: 62 additions & 1 deletion .github/prompts/generate-prompt.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Before generating the prompt, review the available skills in the `.github/skills
* `name`: A concise, kebab-case name for the prompt.
* `description`: A clear, short description of what the prompt does.
* `argument-hint`: (Optional) A hint for what arguments the user can provide when using the prompt.
* `tools`: (Recommended) A list of tool identifiers the prompt is allowed to use. See the **Tool Scoping** section below.
* **Body Structure**:
* **Role**: Define the AI's persona (e.g., "You are an expert C# developer...").
* **Context**: Include specific context instructions or references.
Expand All @@ -50,19 +51,78 @@ Before generating the prompt, review the available skills in the `.github/skills
* Use `${input:variableName}` for user inputs (e.g., `${input:methodName}`).
* Use built-in variables like `${selection}`, `${file}`, or `${workspaceFolder}` where appropriate context is needed.

6. **Best Practices**:
6. **Scope Tools**: Restrict the tools available to each prompt using the `tools` frontmatter field. See the **Tool Scoping** section below for detailed guidance.

7. **Best Practices**:
* Be specific and explicit.
* Encourage chain-of-thought reasoning if the task is complex.
* Reference workspace files using Markdown links `[path/to/file.cs](path/to/file.cs)` only if they are static and necessary for *all* invocations of this prompt.
* Prefer referencing skills over duplicating instructions that already exist in skills.

## Tool Scoping

Every generated prompt **should** include a `tools` list in its YAML frontmatter. Scoping tools keeps the model focused by limiting it to approved, known-effective tools for the task. Without tool scoping, the model may invoke irrelevant tools, waste context, or produce unpredictable results.

### Why scope tools?
- **Focus**: Fewer tools means the model spends less reasoning on tool selection and more on the task.
- **Reliability**: Restricting to tested tools avoids unexpected side effects (e.g., a read-only review prompt shouldn't have edit tools).
- **Safety**: Prevents prompts from accidentally running terminal commands or making file changes when they shouldn't.

### How to choose tools
Apply the **principle of least privilege** — include only the tools the prompt actually needs:

| Prompt type | Recommended tools |
|---|---|
| **Read-only analysis** (review, triage, explain) | `read/readFile`, `search/codebase`, `search/textSearch` |
| **Code editing** (bug fix, feature, refactor) | `edit/editFiles`, `edit/createFile`, `read/readFile`, `search/codebase` |
| **Needs terminal** (build, test, scripts) | All of the above + `execute/runInTerminal`, `execute/getTerminalOutput` |
| **Needs GitHub data** (triage, release notes) | All of the above + `github/search_issues` or other GitHub tools |
| **Needs web content** (docs lookup) | `web/fetch` |

### Available built-in tool identifiers

You can specify individual tools or tool sets (which include all tools in that group).

**Tool sets** (use these to include all tools in a category):
- `edit` — File creation and editing tools
- `read` — File and notebook reading tools
- `search` — Codebase, text, and file search tools
- `execute` — Terminal, task, and notebook execution tools
- `web` — Web content fetching tools

**Commonly used individual tools:**

| Tool identifier | Purpose |
|---|---|
| `edit/editFiles` | Apply edits to existing files |
| `edit/createFile` | Create a new file |
| `read/readFile` | Read file contents |
| `read/problems` | Get workspace problems/diagnostics |
| `search/codebase` | Semantic code search |
| `search/textSearch` | Text/regex search in files |
| `search/fileSearch` | Search for files by glob pattern |
| `search/listDirectory` | List directory contents |
| `search/usages` | Find references and implementations |
| `execute/runInTerminal` | Run a shell command |
| `execute/getTerminalOutput` | Get terminal output |
| `execute/testFailure` | Get test failure details |
| `web/fetch` | Fetch a web page |

**Extension / MCP tools** can also be included using their identifier (e.g., `github/search_issues`). Use `<server-name>/*` to include all tools from an MCP server.

### Frontmatter syntax
```yaml
tools: ['read/readFile', 'search/codebase', 'edit/editFiles']
```

## Example Output Structure (with skill reference)

```markdown
---
name: my-new-prompt
description: specialized task description
argument-hint: input parameter hint
tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal']
---
You are a specialized agent for...

Expand All @@ -89,6 +149,7 @@ Use ${input:param1} to...
name: my-new-prompt
description: specialized task description
argument-hint: input parameter hint
tools: ['read/readFile', 'search/codebase']
---
You are a specialized agent for...

Expand Down
12 changes: 7 additions & 5 deletions .github/prompts/refine-test-overlap.prompt.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
name: test-minimize-overlap
name: refine-test-overlap
description: Run coverage overlap analysis and suggest test suite optimizations
argument-hint: Test filter (e.g. FullyQualifiedName~MyTests) or describe the tests you want to analyze
agent: agent
tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal', 'execute/getTerminalOutput']
---
You are an expert .NET Test Engineer specialized in optimizing test coverage and reducing technical debt.

Expand All @@ -10,19 +12,19 @@ Your task is to analyze the user's test suite using the `AnalyzeTestOverlap.ps1`

## Skills
This prompt leverages the following skills for specific sub-tasks:
- [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions
- [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions

## Tools
You have access to the analysis script at `[AnalyzeTestOverlap.ps1](./scripts/AnalyzeTestOverlap.ps1)`.
You have access to the analysis script at [AnalyzeTestOverlap.ps1](.github/prompts/scripts/AnalyzeTestOverlap.ps1).

## Workflow
1. **Parse or Generate Test Filter**:
* If `${input:filter}` is a valid MSTest filter expression (e.g., `FullyQualifiedName~MyTests`), use it directly.
* If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression.
* If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression.
* If `${input:filter}` is empty, ask the user for a test filter or description to target specific tests.

2. **Run Analysis**:
* Run the script using the filter: `.\scripts\AnalyzeTestOverlap.ps1 -Filter "<filter>"`.
* Run the script from the workspace root: `.\.github\prompts\scripts\AnalyzeTestOverlap.ps1 -Filter "<filter>"`.
* *Note*: The script produces a console summary and a `test-coverage-analysis.json` file.

3. **Review Overlap**:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,9 @@ MigrationBackup/

# MDS "Not Supported" GenAPI code
**/notsupported/*.cs

# Devcontainer generated files (created by init.ps1)
.devcontainer/.env
.devcontainer/.git-overlay
.devcontainer/docker-compose.local.yml
.devcontainer/docker-compose.worktree.yml
7 changes: 7 additions & 0 deletions dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"apicompat"
],
"rollForward": false
},
"powershell": {
"version": "7.6.0",
"commands": [
"pwsh"
],
"rollForward": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ internal ChannelDbConnectionPool(
Identity = identity;
AuthenticationContexts = new();
MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize);
TransactedConnectionPool = new(this);

_connectionSlots = new(MaxPoolSize);

Expand Down Expand Up @@ -148,6 +149,9 @@ public ConcurrentDictionary<
/// <inheritdoc />
public DbConnectionPoolState State { get; private set; }

/// <inheritdoc />
public TransactedConnectionPool TransactedConnectionPool { get; }

/// <inheritdoc />
public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ bool hasTransactionAffinity
_hasTransactionAffinity = hasTransactionAffinity;
}

/// <summary>
/// The time (in milliseconds) to wait for a connection to be created/returned before terminating the attempt.
/// </summary>
public int CreationTimeout
{
get { return _creationTimeout; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ internal interface IDbConnectionPool
/// </summary>
DbConnectionPoolState State { get; }

/// <summary>
/// Holds connections that are currently enlisted in a transaction.
/// </summary>
TransactedConnectionPool TransactedConnectionPool { get; }

/// <summary>
/// Indicates whether the connection pool is using load balancing.
/// </summary>
Expand All @@ -106,7 +111,7 @@ internal interface IDbConnectionPool
/// <param name="userOptions">The user options to use if a new connection must be opened.</param>
/// <param name="connection">The retrieved connection will be passed out via this parameter.</param>
/// <returns>True if a connection was set in the out parameter, otherwise returns false.</returns>
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal>? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);

Comment on lines 88 to 115
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDbConnectionPool.TryGetConnection now allows a nullable TaskCompletionSource and nullable out connection. At least WaitHandleDbConnectionPool still uses non-nullable annotations and assigns null in its implementation, which will trigger nullability-mismatch warnings (and the repo treats warnings as errors) and break the build. Update all IDbConnectionPool implementers to match this contract (parameter and out nullability).

Copilot uses AI. Check for mistakes.
/// <summary>
/// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool.
Expand Down
Loading
Loading