Skip to content
Merged
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
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1826.severity = suggestion
dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA1861.severity = suggestion
dotnet_diagnostic.CA1873.severity = suggestion
dotnet_diagnostic.CA2000.severity = none
dotnet_diagnostic.CA2227.severity = none
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ apm.lock.yaml linguist-generated=true
.github/instructions/** linguist-generated=true
.github/prompts/** linguist-generated=true
# END DO NOT EDIT

# DO NOT EDIT: csharp convention
# generated from https://github.com/Faithlife/CodingGuidelines/blob/master/sections/csharp/gitattributes.md
*.cs text diff=csharp
# END DO NOT EDIT
1 change: 1 addition & 0 deletions .github/conventions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ conventions:
- path: ../conventions/editorconfig-ps1
- path: ../conventions/editorconfig-yaml
- path: ../conventions/editorconfig-csharp
- path: ../conventions/gitattributes-csharp
- path: ../conventions/faithlife-license-mit
- path: ../conventions/dotnet-sdk-10
- path: ./conventions/update-editorconfig-csharp
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
| Convention | Description |
| --- | --- |
| [agentic-repo](./conventions/agentic-repo/README.md) | Applies conventions useful for repositories that keep agent customization files in source control. |
| [apm-install](./conventions/apm-install/README.md) | Installs configured packages and updates existing packages for the Copilot APM target with `apm`. |
| [apm](./conventions/apm/README.md) | Installs configured packages for the Copilot APM target with `apm` and optionally updates existing packages. |
| [build-script](./conventions/build-script/README.md) | Installs the published `build.ps1` script at the repository root. |
| [config-text-section](./conventions/config-text-section/README.md) | Manages one named text section in a repository file. |
| [copilot-lsp](./conventions/copilot-lsp/README.md) | Manages project-scoped GitHub Copilot CLI LSP server definitions in `.github/lsp.json`. |
Expand All @@ -44,6 +44,7 @@
| [faithlife-dotnet-library-build](./conventions/faithlife-dotnet-library-build/README.md) | Creates or refreshes Faithlife .NET library build project files under `tools/Build`. |
| [faithlife-dotnet-library-workflow](./conventions/faithlife-dotnet-library-workflow/README.md) | Installs the published Faithlife .NET library workflows at `.github/workflows/ci.yml` and `.github/workflows/copilot-setup-steps.yml`. |
| [faithlife-license-mit](./conventions/faithlife-license-mit/README.md) | Applies [license-mit](./conventions/license-mit/README.md) with `copyright-holder` set to `Faithlife`. |
| [gitattributes-csharp](./conventions/gitattributes-csharp/README.md) | Ensures the repository-root `.gitattributes` contains the standard C# section from [files/.gitattributes](./conventions/gitattributes-csharp/files/.gitattributes). |
| [gitattributes-lf](./conventions/gitattributes-lf/README.md) | Ensures the repository-root `.gitattributes` starts with `* text=auto eol=lf`. |
| [gitattributes-section](./conventions/gitattributes-section/README.md) | Manages a named section within the repository-root `.gitattributes` file. |
| [gitignore-section](./conventions/gitignore-section/README.md) | Manages a named section within the repository-root `.gitignore` file. |
Expand Down
4 changes: 3 additions & 1 deletion conventions/agentic-repo/convention.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ conventions:
.agents/
apm.lock.yaml
apm.yml
- path: ../apm-install
- path: ../apm
settings:
update: true

pull-request:
auto-merge: true
33 changes: 0 additions & 33 deletions conventions/apm-install/README.md

This file was deleted.

36 changes: 36 additions & 0 deletions conventions/apm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# apm

Installs configured packages for the Copilot APM target with `apm` and optionally updates existing packages.

## Settings

- `install`: Optional sequence of package identifiers to pass to `apm install --target copilot`. Defaults to no configured packages.
- `update`: Optional boolean that adds `--update` to `apm install` when `true`. Defaults to `false`.

## Behavior

The convention requires the `apm` command to be available when it runs. `copilot` is the only target currently supported.

If no packages are configured and the repository has no root `apm.yml`, the convention leaves the repository unchanged. When `update` is `true`, the convention adds `--update`; otherwise it runs a plain install. After `apm install` completes, if the only changed file is `apm.lock.yaml`, the convention restores that file so update-only no-op runs stay clean.

## Examples

Install specific packages:

```yaml
conventions:
- path: Faithlife/CodingGuidelines/conventions/apm
settings:
install:
- richlander/dotnet-inspect/skills/dotnet-inspect
- microsoft/playwright-cli/skills/playwright-cli
```

Update only:

```yaml
conventions:
- path: Faithlife/CodingGuidelines/conventions/apm
settings:
update: true
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ $utf8 = [System.Text.UTF8Encoding]::new($false)
[Console]::OutputEncoding = $utf8
$OutputEncoding = $utf8

# Define the Pester suite for the apm-install convention.
Describe 'apm-install convention' {
# Define the Pester suite for the apm convention.
Describe 'apm convention' {
BeforeAll {
# Load the convention script and shared test helpers.
$script:conventionScriptPath = Join-Path $PSScriptRoot 'convention.ps1'
Expand Down Expand Up @@ -81,7 +81,7 @@ exit 0
}
}

It 'runs apm install --update --target copilot' {
It 'runs apm install --target copilot by default' {
# Set up a repository with apm.yml and a fake argument capture file.
$testDirectory = New-TemporaryDirectory
$toolDirectory = New-TemporaryDirectory
Expand Down Expand Up @@ -109,6 +109,48 @@ exit 0

# Run the convention and assert it invokes apm with the default arguments.
{ Invoke-ConventionScript -ScriptPath $conventionScriptPath -RepositoryRoot $testDirectory -InputPath $inputPath } | Should -Not -Throw
((Get-Content -LiteralPath $argumentsPath -Raw).TrimEnd("`r", "`n")) | Should -Be 'install --target copilot'
}
finally {
# Restore process state and remove temporary files.
$env:PATH = $originalPath
Remove-Item Env:APM_ARGUMENTS_PATH -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $inputPath -Force
Remove-Item -LiteralPath $toolDirectory -Recurse -Force
Remove-Item -LiteralPath $testDirectory -Recurse -Force
}
}

It 'adds --update only when the update setting is true' {
# Set up a repository with apm.yml and an explicit update request.
$testDirectory = New-TemporaryDirectory
$toolDirectory = New-TemporaryDirectory
$argumentsPath = Join-Path $toolDirectory 'apm-arguments.txt'
$inputPath = New-ConventionInputFile -Settings @{
update = $true
}
$originalPath = $env:PATH

try {
# Arrange a fake apm command that records its argument list.
[System.IO.File]::WriteAllText((Join-Path $testDirectory 'apm.yml'), "packages: []`n", $utf8)
Initialize-TestRepository -Path $testDirectory
NewFakeApmCommand -ToolDirectory $toolDirectory -WindowsScript @'
@echo off
setlocal
> "%APM_ARGUMENTS_PATH%" echo %*
exit /b 0
'@ -BashScript @'
#!/usr/bin/env bash
printf '%s\n' "$*" > "$APM_ARGUMENTS_PATH"
exit 0
'@

$env:APM_ARGUMENTS_PATH = $argumentsPath
$env:PATH = $toolDirectory + [System.IO.Path]::PathSeparator + $originalPath

# Run the convention and assert the update flag is included only when requested.
{ Invoke-ConventionScript -ScriptPath $conventionScriptPath -RepositoryRoot $testDirectory -InputPath $inputPath } | Should -Not -Throw
((Get-Content -LiteralPath $argumentsPath -Raw).TrimEnd("`r", "`n")) | Should -Be 'install --update --target copilot'
}
finally {
Expand All @@ -121,13 +163,13 @@ exit 0
}
}

It 'passes configured packages to apm install --update --target copilot' {
It 'passes configured install packages to apm install --target copilot' {
# Set up convention input that includes configured apm packages.
$testDirectory = New-TemporaryDirectory
$toolDirectory = Join-Path $testDirectory 'tools'
$argumentsPath = Join-Path $testDirectory 'apm-arguments.txt'
$inputPath = New-ConventionInputFile -Settings @{
packages = @(
install = @(
'richlander/dotnet-inspect/skills/dotnet-inspect'
'microsoft/playwright-cli/skills/playwright-cli'
)
Expand All @@ -153,7 +195,7 @@ exit 0

# Run the convention and assert configured packages are appended.
{ & $conventionScriptPath $inputPath } | Should -Not -Throw
((Get-Content -LiteralPath $argumentsPath -Raw).TrimEnd("`r", "`n")) | Should -Be 'install --update --target copilot richlander/dotnet-inspect/skills/dotnet-inspect microsoft/playwright-cli/skills/playwright-cli'
((Get-Content -LiteralPath $argumentsPath -Raw).TrimEnd("`r", "`n")) | Should -Be 'install --target copilot richlander/dotnet-inspect/skills/dotnet-inspect microsoft/playwright-cli/skills/playwright-cli'
}
finally {
# Restore process state and remove temporary files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,37 @@ $utf8 = [System.Text.UTF8Encoding]::new($false)
[Console]::OutputEncoding = $utf8
$OutputEncoding = $utf8

# Collect optional package settings from the convention input.
$packages = @()
# Collect optional install settings from the convention input.
$packagesToInstall = @()
$shouldUpdate = $false
$conventionInput = Get-Content -LiteralPath $args[0] -Raw | ConvertFrom-Json -AsHashtable
$settings = $conventionInput.settings

if ($null -ne $settings -and $settings.ContainsKey('packages') -and $null -ne $settings.packages) {
[string[]] $packages = @($settings.packages)
if ($null -ne $settings -and $settings.ContainsKey('install') -and $null -ne $settings.install) {
[string[]] $packagesToInstall = @($settings.install)
}

if ($null -ne $settings -and $settings.ContainsKey('update') -and $null -ne $settings.update) {
$shouldUpdate = [bool] $settings.update
}

# Skip when neither an apm manifest nor explicit packages are available.
if ($packages.Count -eq 0 -and -not (Test-Path -LiteralPath 'apm.yml')) {
if ($packagesToInstall.Count -eq 0 -and -not (Test-Path -LiteralPath 'apm.yml')) {
Write-Host 'Skipping apm install because apm.yml is absent and no packages were configured.'
return
}

# Build the apm install command for the copilot target.
$apmArguments = @('install', '--update', '--target', 'copilot')
$apmArguments = @('install')

if ($shouldUpdate) {
$apmArguments += '--update'
}

$apmArguments += @('--target', 'copilot')

if ($packages.Count -gt 0) {
$apmArguments += $packages
if ($packagesToInstall.Count -gt 0) {
$apmArguments += $packagesToInstall
}

# Verify apm is available before invoking it.
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions conventions/faithlife-dotnet-library-workflow/files/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
name: Build ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
Expand Down
14 changes: 14 additions & 0 deletions conventions/gitattributes-csharp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# gitattributes-csharp

Ensures the repository-root `.gitattributes` contains the standard C# section from [files/.gitattributes](./files/.gitattributes).

## Behavior

The convention manages the fixed `csharp` section and reads the section text from the packaged [files/.gitattributes](./files/.gitattributes) asset. Existing `.gitattributes` content outside the managed section is preserved.

## Example

```yaml
conventions:
- path: Faithlife/CodingGuidelines/conventions/gitattributes-csharp
```
77 changes: 77 additions & 0 deletions conventions/gitattributes-csharp/convention.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#requires -PSEdition Core
#requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$utf8 = [System.Text.UTF8Encoding]::new($false)
[Console]::InputEncoding = $utf8
[Console]::OutputEncoding = $utf8
$OutputEncoding = $utf8

Describe 'gitattributes-csharp convention' {
BeforeAll {
# Load shared test helpers for temporary repositories and convention execution.
$script:testHelpersPath = Join-Path $PSScriptRoot '..' 'scripts' 'TestHelpers.ps1'
. $script:testHelpersPath
}

It 'creates .gitattributes with the shared C# section' {
$testDirectory = New-TemporaryDirectory

try {
# Arrange an isolated repository with the C# gitattributes convention enabled.
Copy-TestConventionAssets -TestDirectory $testDirectory
[System.IO.Directory]::CreateDirectory((Join-Path $testDirectory '.github')) | Out-Null
[System.IO.File]::WriteAllText((Join-Path $testDirectory '.github/conventions.yml'), @"
conventions:
- path: ../conventions/gitattributes-csharp
"@, $utf8)
Initialize-TestRepository -Path $testDirectory

# Apply the convention under test.
{ Invoke-RepoConventionsApply -TestDirectory $testDirectory } | Should -Not -Throw

# Read the generated gitattributes and packaged section for comparison.
$gitattributesPath = Join-Path $testDirectory '.gitattributes'
$content = Get-Content -LiteralPath $gitattributesPath -Raw
$normalizedContent = ($content -replace "`r`n", "`n")
$expectedSection = ((Get-Content -LiteralPath (Join-Path $testDirectory 'conventions/gitattributes-csharp/files/.gitattributes') -Raw) -replace "`r`n", "`n").TrimEnd("`n")

# Assert the generated file contains the managed C# section.
(Test-Path -LiteralPath $gitattributesPath) | Should -Be $true
$content | Should -Match "(?m)^# DO NOT EDIT: csharp convention\r?$"
$content | Should -Match "(?m)^# generated from https://github\.com/Faithlife/CodingGuidelines/blob/master/sections/csharp/gitattributes\.md\r?$"
$content | Should -Match "(?m)^\*\.cs text diff=csharp\r?$"
$normalizedContent.Contains($expectedSection) | Should -Be $true
}
finally {
Remove-Item -LiteralPath $testDirectory -Recurse -Force
}
}

It 'commits .gitattributes changes with the packaged commit message' {
$testDirectory = New-TemporaryDirectory

try {
# Arrange an isolated repository with the C# gitattributes convention enabled.
Copy-TestConventionAssets -TestDirectory $testDirectory
[System.IO.Directory]::CreateDirectory((Join-Path $testDirectory '.github')) | Out-Null
[System.IO.File]::WriteAllText((Join-Path $testDirectory '.github/conventions.yml'), @"
conventions:
- path: ../conventions/gitattributes-csharp
"@, $utf8)
Initialize-TestRepository -Path $testDirectory
$initialHead = Get-CommitId -TestDirectory $testDirectory

# Apply the convention and allow it to create its packaged commit.
{ Invoke-RepoConventionsApply -TestDirectory $testDirectory } | Should -Not -Throw

# Assert the commit message and clean working tree match expectations.
(Get-CommitId -TestDirectory $testDirectory -Revision 'HEAD~1') | Should -Be $initialHead
(@(Get-CommitSubjects -TestDirectory $testDirectory -Count 1))[0] | Should -Be 'Update C# gitattributes settings'
(@(Get-GitStatusLines -TestDirectory $testDirectory)).Count | Should -Be 0
}
finally {
Remove-Item -LiteralPath $testDirectory -Recurse -Force
}
}
}
11 changes: 11 additions & 0 deletions conventions/gitattributes-csharp/convention.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
commit:
message: "Update C# gitattributes settings"

conventions:
- path: ../gitattributes-section
settings:
name: csharp
text: ${{ readText("files/.gitattributes") }}

pull-request:
auto-merge: true
2 changes: 2 additions & 0 deletions conventions/gitattributes-csharp/files/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# generated from https://github.com/Faithlife/CodingGuidelines/blob/master/sections/csharp/gitattributes.md
*.cs text diff=csharp
1 change: 1 addition & 0 deletions sections/csharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# C# Coding Guidelines

* [.editorconfig for C#](./editorconfig.md)
* [.gitattributes for C#](./gitattributes.md)
* [global.json](./globaljson.md)
* [NoWarn usage](./nowarn.md)

Expand Down
Loading
Loading