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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Consolidate code style into one CODESTYLE.md and govern tasks by ptr727 · Pull Request #178 · ptr727/ProjectTemplate · GitHub
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,9 @@ end_of_line = crlf
# C# files
[*.cs]
end_of_line = crlf
# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a
# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule
# repo-wide here only when it applies to every project (never a brownfield batch).
dotnet_diagnostic.IDE0055.severity = none
dotnet_analyzer_diagnostic.severity = suggestion
csharp_indent_block_contents = true
Expand Down
9 changes: 3 additions & 6 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,7 @@ Repository conventions for GitHub Copilot (and any other AI agent reading this f

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root - read it first, including the [PR Review Etiquette](../AGENTS.md#pr-review-etiquette) review-loop contract this file's runbook implements. This file is intentionally narrow: commit/PR-title conventions (summarized inline so VS Code's commit-message and PR-title generators have them) plus the GitHub Copilot Review Runbook.

For language-specific style rules, see:

- .NET - [`CODESTYLE.md`](../CODESTYLE.md) at the repo root.
- Python - [`PyPiLibrary/CODESTYLE.md`](../PyPiLibrary/CODESTYLE.md).
For code-style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root - one guide with a General section plus per-language sections (.NET, Python).

Do not duplicate language-specific rules here. **Project-specific conventions and API/behavioral contracts also belong in [AGENTS.md](../AGENTS.md), not here** - this file is intentionally limited to the inline commit/PR-title summary and the GitHub Copilot Review Runbook. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.

Expand DownExpand Up@@ -145,13 +142,13 @@ Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution
Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or language CODESTYLE) and the existing-tree precedent.
- Declined style comment: cite the rule (AGENTS.md or the CODESTYLE.md language section) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

## When in Doubt

Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For language-specific rules, the per-language CODESTYLE files are authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.
Read [AGENTS.md](../AGENTS.md) for this repo's conventions. For code-style rules, [`CODESTYLE.md`](../CODESTYLE.md) (its General section plus the relevant language section) is authoritative. Don't restate any of these files' rules in commit bodies or PR descriptions - keep those focused on the change itself.

**In a derived repo:** if you find a discrepancy that should be fixed in the template itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), open an issue upstream in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only fixing it locally - see the template's [AGENTS.md "Staying in Sync and Reporting Drift Upstream"](https://github.com/ptr727/ProjectTemplate/blob/main/AGENTS.md#staying-in-sync-and-reporting-drift-upstream).
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@
"name": "Console Root",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -20,7 +20,7 @@
"name": "Console Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/Console/debug/Console.dll",
"args": [
"--loglevel=Debug",
Expand All@@ -37,7 +37,7 @@
"name": "CodeGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"preLaunchTask": ".NET Build",
"program": "${workspaceFolder}/.artifacts/bin/CodeGen/debug/CodeGen.dll",
"args": [
"--codepath",
Expand Down
17 changes: 11 additions & 6 deletions .vscode/tasks.json
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
{
"version": "2.0.0",
"tasks": [
// .NET language group. A non-.NET repo drops this group and adds its own
// language's tasks. The first three tasks are the .NET clean-compile set
// (CODESTYLE.md) carried verbatim; the rest are convenience/project-specific.
{
"label": ".Net Build",
"label": ".NET Build",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -20,7 +23,7 @@
}
},
{
"label": ".Net Format",
"label": ".NET Format",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -37,9 +40,10 @@
"showReuseMessage": false,
"clear": false
},
"dependsOrder": "sequence",
"dependsOn": [
Comment thread
ptr727 marked this conversation as resolved.
"CSharpier Format",
".Net Build"
".NET Build"
]
},
{
Expand All@@ -60,8 +64,9 @@
"clear": false
}
},
// Convenience / project-specific tasks (adapt or drop per repo).
{
"label": ".Net Tool Update",
"label": ".NET Tool Update",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -78,7 +83,7 @@
}
},
{
"label": ".Net Benchmark",
"label": ".NET Benchmark",
"type": "process",
"command": "dotnet",
"args": [
Expand All@@ -100,7 +105,7 @@
}
},
{
"label": ".Net Outdated Upgrade",
"label": ".NET Outdated Upgrade",
"type": "process",
"command": "dotnet",
"args": [
Expand Down
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
# Instructions for AI Coding Agents

**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Language-specific style guides live next to the code:

- .NET - [`CODESTYLE.md`](./CODESTYLE.md)
- Python - [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)
**ProjectTemplate** is a polyglot template repo. The .NET side ships under [`NuGetLibrary/`](./NuGetLibrary/) (plus `Console/`, `Tests/`, `Benchmarks/`, `CodeGen/`); the Python side ships under [`PyPiLibrary/`](./PyPiLibrary/). This file is the single source of truth for cross-cutting rules. Code style lives in [`CODESTYLE.md`](./CODESTYLE.md) at the repo root - one guide with a General section that applies to every language plus droppable per-language sections (.NET, Python).

Treat this file as authoritative for everything else; don't restate its rules elsewhere. A derived repo's **project-specific conventions and public-API/behavioral contracts** (e.g. a "Library API Conventions" section) also live here, **not** in [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - that file targets GitHub Copilot / VS Code specifically, while this file is the agent-agnostic one every coding agent is directed to read, so any rule a reviewer must honor has to live here to be provider-independent.

Expand DownExpand Up@@ -221,24 +218,24 @@ Each devcontainer's `customizations.vscode.extensions` mirrors the `recommendati
- `Tests/` - xUnit + AwesomeAssertions
- `Benchmarks/` - BenchmarkDotNet
- `CodeGen/` - internal codegen tooling
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) ".NET" section**.
- **Python project** (env/build/test with `uv` from inside `PyPiLibrary/`):
- `PyPiLibrary/` - PyPI library template, published as `ptr727-projecttemplate-library`
- **Style guide: [`PyPiLibrary/CODESTYLE.md`](./PyPiLibrary/CODESTYLE.md)**.
- **Style guide: [`CODESTYLE.md`](./CODESTYLE.md) "Python" section**.
- **Cross-cutting**:
- `.github/` - workflows, Dependabot, Copilot instructions
- `.devcontainer/dotnet/` and `.devcontainer/python/` - per-language devcontainer configs + post-create scripts
- `DotNet.code-workspace`, `Python.code-workspace` - per-language VS Code workspace files (each pairs with its devcontainer)
- `.vscode/` - debug configs and tasks (.NET-oriented)
- `.vscode/` - debug configs and tasks, grouped by language (the template ships the .NET group); carry your language's named clean-compile tasks verbatim (see [`CODESTYLE.md`](./CODESTYLE.md))
- `Docker/` - multi-platform Linux container build for the Console app

When you touch code in either language, also respect that language's style guide. Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.
When you touch code in either language, also respect that language's style guide. After editing, that language's **clean-compile** must pass before commit, and new-port/brownfield status never licenses relaxing analyzer/linter severities or silencing newly surfaced diagnostics - both rules live in [`CODESTYLE.md`](./CODESTYLE.md) "General". Conventions in this file (PR titles, branching, US English, devcontainer behavior, workflow YAML) apply uniformly to both languages.

## Quick Start for Derived Projects

1. **Clone this template** as the baseline for your project.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - see the relevant CODESTYLE for the deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) (.NET) and/or [PyPiLibrary/CODESTYLE.md](./PyPiLibrary/CODESTYLE.md) (Python) for the per-language style.
2. **Decide** which language sides you need. If you need only one, delete the other folder and its references - drop that language's section in [CODESTYLE.md](./CODESTYLE.md) and follow its "Adopting Without ..." deletion checklist.
3. **Read** [CODESTYLE.md](./CODESTYLE.md) - the General section plus the section(s) for the language(s) you keep (.NET, Python).
4. **Carry the mandatory shared files and sections verbatim** - do not re-invent them per repo. See [Files and Sections Derived Repos Must Carry Verbatim](#files-and-sections-derived-repos-must-carry-verbatim) for the exact list (review-loop contract + runbook, lint config, line-ending governance) and what to adapt.
5. **Update project-specific values** - `PackageId`/`RootNamespace` in `.csproj`, `name` in `pyproject.toml`, namespace conventions, `README.md`, `HISTORY.md`, `version.json`, `LICENSE`, NuGet/PyPI badge URLs.
6. **Run tools before first commit**:
Expand All@@ -256,12 +253,14 @@ These artifacts are the template's cross-cutting contract. A derived repo must c
- **[`.github/copilot-instructions.md`](./.github/copilot-instructions.md)** - the whole file is a drop-in; its "GitHub Copilot Review Runbook" carries the provider mechanics. Copy verbatim and change only the `<owner>` / `<repo>` / `<N>` placeholders in the API snippets; drop language-specific style pointers that don't apply. Keep this file **narrow** - provider-specific mechanics (the Copilot review runbook) plus the inline commit/PR-title summary. **Project-specific conventions and API/behavioral contracts do not belong here**; put them in [`AGENTS.md`](./AGENTS.md), the agent-agnostic file every coding agent reads. Non-Copilot agents (Claude Code, Codex, Cursor, ...) are not directed to this file and don't read it by default, so any rule a reviewer must honor has to live in `AGENTS.md` to be provider-independent.
- **[`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc)** - the shared lint config read by both the davidanson `markdownlint` IDE extension and CLI/CI `markdownlint-cli2`, so the IDE and command line stay in lock-step. Copy verbatim (it is repo-agnostic). **On first adoption**, a repo's existing docs often carry structural debt this config surfaces (MD022/MD031/MD032 blank lines around headings/fences/lists, MD040 unlabeled fences). Clear it in one pass by running the markdownlint-cli2 Docker command from [Running the Linters Locally](#running-the-linters-locally-known-working-invocations) with `--fix` added (`docker run --rm -v "$PWD":/workdir davidanson/markdownlint-cli2:latest --fix "**/*.md"`), then hand-label any remaining unlabeled fences (MD040 - usually `text` for format/example blocks) and **re-verify the line endings of touched `.md` files** (`--fix` can rewrite a CRLF file as LF).
- **[`.editorconfig`](./.editorconfig) and [`.gitattributes`](./.gitattributes)** - line-ending governance (see [Line Endings](#line-endings)). `.editorconfig` sets `end_of_line` per file type and `.gitattributes` (`* -text`) stops git from normalizing; a repo missing either, or one that only sets `end_of_line` for `[*.md]` instead of carrying the full per-extension rules, drifts between LF and CRLF. The **defaults + per-extension EOL block is always-verbatim**; the `[*.cs]` + ReSharper style block at the end is **.NET-only** and may be dropped in a non-.NET repo (the file marks the boundary). A repo adopting `.gitattributes` for the **first time** must do a one-time explicit line-ending normalization: `* -text` tells git to stop normalizing, so pre-existing files keep whatever (possibly mixed) endings they have - convert each to its `.editorconfig` ending and commit that as a deliberate one-time pass, best isolated in its own commit.
- **[`CODESTYLE.md`](./CODESTYLE.md)** - the single code-style guide. Its **General** section is always carried; each **language section** (.NET, Python) is droppable, exactly like the `.editorconfig` `[*.cs]` boundary - keep the section(s) for the language(s) you ship and drop the rest. **Repo-root placement is load-bearing**: `AGENTS.md` links it as `./CODESTYLE.md` and `.github/copilot-instructions.md` as `../CODESTYLE.md`, so moving it breaks those links. Adapt the in-section repo-specific bits - the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels - to your repo.
- **[`.vscode/tasks.json`](./.vscode/tasks.json)** - carry your language's **named clean-compile definitions verbatim**: as VS Code tasks where the template ships them that way (the .NET group - `.NET Build` / `CSharpier Format` / `.NET Format`), or as the documented commands where it doesn't (Python's `ruff` / `pyright`, in `CODESTYLE.md`). Their names are owned by the matching `CODESTYLE.md` language section and their command sequence + arguments are the canonical clean-compile spec. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`) and project-specific tasks (`.NET Benchmark`) are the adapt zone; a non-.NET repo drops the .NET task group and carries its own language's definitions.

When the template changes one of these, re-sync the derived repo from the new version (see below).

### Staying in Sync and Reporting Drift Upstream

A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), prefer to keep it as the full multi-language aggregate the template ships and re-sync the whole file, even if the repo uses only one language - replacing the entire file is simpler to keep current than maintaining hand-trimmed per-language snippets.
A derived repo is expected to **re-sync against the template periodically**, not just at creation: pull the current version of each verbatim-carry artifact above and re-apply it (adapting only the noted placeholders). For [`CODESTYLE.md`](./CODESTYLE.md), re-sync the whole file from the template and then drop the language section(s) you don't ship (always keeping the General section) - replacing the file wholesale and trimming whole sections is simpler to keep current than hand-editing per-language snippets.

**Drift flows back upstream as an issue, not a private fix.** When porting or re-syncing, if you find a discrepancy that should be fixed in the **template itself** - a gap, an outdated instruction, a missing rule, something that bit this repo and would bite the next derived repo too - **open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate)** describing it, rather than only patching it locally. A local fix realigns *this* repo; an upstream issue (then fix) corrects it *for every future derived repo* and keeps the template the single source of truth. This is exactly how the current review-loop / lint-config / brownfield-migration gaps were surfaced.

Expand Down
Loading
Loading