C1 drift: re-sync unprotected template-tracked files#100
Conversation
Template-drift resolution for ETL-Test-Kit — the non-protected half. Re-syncs 15 template-tracked infrastructure files to the canonical repo-template. Excludes docfx_project/* (repo-specific docs), protected config/workflow files (separate PR), and .gitkeep / expected-noise files. Part of #72. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Resyncs a set of template-tracked infrastructure files to reduce drift from the canonical repo-template, focusing on the “unprotected” half (scripts, docs, templates, and git attributes). This introduces new maintenance-framework tooling and hardening/validation scripts while updating existing setup/docs guidance.
Changes:
- Added GitHub repo bootstrap utilities (labels provisioning, maintenance issue creation, branch ruleset setup/fix).
- Added DocFX gh-pages deployment validation script and maintenance framework templates.
- Updated docs and repository templates (
CONTRIBUTING.md, PR/issue templates, formatting/security docs,.gitattributes) to match the template baseline.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/Validate-DocsDeploy.sh | Adds post-deploy validation for DocFX gh-pages contents. |
| scripts/templates/maintenance-parent-body.md | Adds canonical body template for the parent Maintenance issue. |
| scripts/setup.ps1 | Updates setup guidance to include labels + maintenance issue provisioning. |
| scripts/Setup-Maintenance.ps1 | Adds idempotent script to create the parent Maintenance issue via gh. |
| scripts/Setup-Labels.ps1 | Updates label provisioning to include Maintenance framework labels. |
| scripts/Setup-BranchRuleset.ps1 | Adds interactive ruleset creation for main with required checks and security gates. |
| scripts/format.ps1 | Improves messaging around required SDK version for dotnet format. |
| scripts/Fix-BranchRuleset.ps1 | Adds script to disable/rename existing rulesets and re-run setup. |
| docs/WORKFLOW_SECURITY.md | Updates protected-file patterns to include workflow files. |
| docs/RELEASE-WORKFLOW-SETUP.md | Tweaks troubleshooting guidance wording. |
| docs/README-FORMATTING.md | Updates formatting prerequisites and line-ending guidance. |
| CONTRIBUTING.md | Resynced contributing guide content to template baseline. |
| .github/pull_request_template.md | Adds guidance for linking Maintenance sub-issues. |
| .github/ISSUE_TEMPLATE/maintenance-task.yaml | Adds Maintenance-task issue form. |
| .gitattributes | Clarifies/enforces LF strategy for PowerShell scripts and shebang compatibility. |
| $rulesetOutput = gh api ` | ||
| -H "Accept: application/vnd.github+json" ` | ||
| -H "X-GitHub-Api-Version: 2022-11-28" ` | ||
| "/repos/$Repository/rulesets" ` | ||
| --paginate ` | ||
| --jq '.[] | select(.name == "Protect main branch")' 2>&1 | ||
|
|
||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Warning "⚠️ Could not check for existing rulesets (API returned exit code $LASTEXITCODE). Continuing..." | ||
| } elseif ($rulesetOutput) { | ||
| $matchingRulesets = $rulesetOutput | ConvertFrom-Json | ||
| $existingRuleset = $matchingRulesets | Select-Object -First 1 | ||
|
|
||
| if ($existingRuleset) { | ||
| Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green | ||
| Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan | ||
| $response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)" | ||
| if ($response -ne 'y' -and $response -ne 'Y') { | ||
| Write-Host "Exiting." -ForegroundColor Yellow | ||
| exit 0 | ||
| } | ||
| } | ||
| } else { | ||
| Write-Host "ℹ️ Ruleset 'Protect main branch' does not exist yet." -ForegroundColor Gray | ||
| } |
| # Contributing to {{PROJECT_NAME}} | ||
|
|
||
| Thank you for your interest in contributing to **Wolfgang.Etl.TestKit**! We welcome contributions to help improve this project. | ||
| Thank you for your interest in contributing to **{{PROJECT_NAME}}**! We welcome contributions to help improve this project. |
The C1 drift re-sync brought the canonical CONTRIBUTING.md but left the
template's {{PROJECT_NAME}} placeholder literal. Replace it with this repo's
project name so the synced CONTRIBUTING.md is correct, not a raw template.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Normalize to the package name for ETL-family consistency (the prior commit fell back to the repo name because the local clone lacked a single src/ project dir to derive from). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| -H "Accept: application/vnd.github+json" ` | ||
| -H "X-GitHub-Api-Version: 2022-11-28" ` | ||
| "/repos/$Repository/rulesets" ` | ||
| --paginate ` | ||
| --jq '.[] | select(.name == "Protect main branch")' 2>&1 |
| } elseif ($rulesetOutput) { | ||
| $matchingRulesets = $rulesetOutput | ConvertFrom-Json | ||
| $existingRuleset = $matchingRulesets | Select-Object -First 1 | ||
|
|
||
| if ($existingRuleset) { | ||
| Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green | ||
| Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan | ||
| $response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)" | ||
| if ($response -ne 'y' -and $response -ne 'Y') { | ||
| Write-Host "Exiting." -ForegroundColor Yellow | ||
| exit 0 | ||
| } | ||
| } | ||
| } else { | ||
| Write-Host "ℹ️ Ruleset 'Protect main branch' does not exist yet." -ForegroundColor Gray | ||
| } |
| @{ context = "Stage 3: macOS Tests (.NET 6.0-10.0)" }, | ||
| @{ context = "Security Scan (DevSkim)" }, | ||
| @{ context = "Security Scan (CodeQL) (csharp)" }, | ||
| @{ context = "Secrets Scan (gitleaks)" } |
| Write-Host " - Stage 3: macOS Tests (.NET 6.0-10.0)" -ForegroundColor DarkGray | ||
| Write-Host " - Security Scan (DevSkim)" -ForegroundColor DarkGray | ||
| Write-Host " - Security Scan (CodeQL) (csharp)" -ForegroundColor DarkGray | ||
| Write-Host " - Secrets Scan (gitleaks)" -ForegroundColor DarkGray |
Fold-down of the repo-template fix (PR #389): Replace-Placeholders now writes with -Encoding utf8NoBOM -NoNewline. Rolled to every repo so the campaign stays consistent with the updated canonical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Propagates the three files folded into repo-template during drift Phase 0: tests/Directory.Build.props + benchmarks/Directory.Build.props (TreatWarningsAsErrors=false) and tests/.editorconfig (test-project analyzer relaxations). Excluded from the initial C1 sync; rolled out now so every repo matches the canonical template. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the TreatWarningsAsErrors=false override on test projects. The genuinely test-inappropriate analyzer rules are already silenced per-rule in tests/.editorconfig; test code now inherits the root Directory.Build.props (TreatWarningsAsErrors in Release, like src/examples). benchmarks remain exempt for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The explicit interface member declared 'object' but the non-generic System.Collections.IEnumerator.Current is 'object?' in a nullable context, so returning it tripped CS8603. Declare the member 'object?' to match the interface. Surfaced by holding test projects to TreatWarningsAsErrors (the tests/Directory.Build.props drop) — a genuine latent bug, now fixed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| "/repos/$Repository/rulesets" ` | ||
| --paginate ` | ||
| --jq '.[] | select(.name == "Protect main branch")' 2>&1 | ||
|
|
Append a github-actions package-ecosystem to .github/dependabot.yml so Dependabot opens weekly PRs to bump pinned action versions in the repo's GitHub workflows. Grouped under a single PR per week. Existing nuget ecosystems are left unchanged. Initiative CI2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Scope addition to this PR — CI2 stacked. This canonical-unprotected branch now also adds a Initiative CI2 rides this existing branch per your guidance ("add to existing branch"). |
Keep-a-Changelog skeleton with an [Unreleased] section so release notes can accumulate here between releases instead of being lost. Follows the canonical format used across the fleet. Initiative D3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Scope addition to this PR — D3 stacked. Adds |
- scripts/Setup-BranchRuleset.ps1: jq filter wrapped in '[ .[] | select() ]' so the output is always valid JSON even with multiple matches; gh stderr redirected to a temp file so it can't poison stdout. - scripts/Fix-BranchRuleset.ps1: same stderr-isolation fix on the rulesets fetch (no more '2>&1' merge). - scripts/Validate-DocsDeploy.sh: distinguish 'versions.json never created' from 'versions.json failed validation' in step 4 so the skip message reflects reality. - .github/dependabot.yml: drop the stale 'dotnet' label (no longer in the Setup-Labels.ps1 taxonomy). - REPO-INSTRUCTIONS.md: replace the stale label list (dependabot-*, dotnet) with the current Maintenance-framework labels Setup-Labels.ps1 actually creates. IAsyncEnumerable-Extensions also gets README.md / CONTRIBUTING.md updated to point at docs/README-FORMATTING.md since the root copy was removed by the earlier D7 cleanup. Fan-out of the round-2 Copilot fixes verified against DateTime-Extensions (#178 pilot). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Retargeted: base changed from `main` to `vNext`. Per the per-repo release plan, this PR now merges into the repo's `vNext` integration branch instead of directly into `main`. vNext currently equals `canonical-protected`; merging this PR brings the unprotected canonical work onto vNext, where the Release build + automated tests run as the release gate. After both halves are on vNext and the gate is green, the per-repo release flow is:
|
These scripts were run once when this repo was bootstrapped from repo-template and are not needed afterward: - scripts/setup.ps1 (orchestrator for the others) - scripts/Setup-Maintenance.ps1 (created the Maintenance issues) - scripts/Setup-BranchRuleset.ps1 (created the main branch ruleset) - scripts/Setup-GitHubPages.ps1 (bootstrapped gh-pages branch) - scripts/templates/maintenance-parent-body.md (template used by Setup-Maintenance) repo-template keeps these files — they remain available there so any new repo created from the template can still run them at bootstrap. Recurring utilities are intentionally kept: Setup-Labels.ps1 (idempotent), Fix-BranchRuleset.ps1, format.ps1, Validate-DocsDeploy.sh, build-pr.ps1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The formatting section referenced `pwsh ./format.ps1` and linked to `README-FORMATTING.md` at the repo root, but the actual paths are `scripts/format.ps1` and `docs/README-FORMATTING.md`. Both are broken as written — contributors who follow the instructions get a "file not found" on the script and a 404 on the link. Correcting both references to match the canonical repo layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related problems:
1. EXAMPLE comments showed `.\format.ps1` (current directory), but
the script lives at `scripts/format.ps1`. Contributors who copy-
pasted the examples got "file not found".
2. The script used `Get-ChildItem -Path .` to find the solution,
which only worked when invoked from the repo root. Invoking from
anywhere else (including the scripts/ folder where the script
lives) failed with "No solution file found".
Now the script pins cwd to the repo root via Resolve-Path
$PSScriptRoot/.. inside a Push-Location / try / finally Pop-Location
block, so it works from any working directory inside the repo. The
EXAMPLE comments and the Check-mode error message both reference
the correct `.\scripts\format.ps1` path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…0004) The AsyncFixer bullet listed "Ensures proper ConfigureAwait() usage" as one of its capabilities. That's inaccurate — ConfigureAwait enforcement in this fleet is handled by Meziantou's MA0004 (plus SonarAnalyzer's S3216 and Microsoft's CA2007), not by AsyncFixer. AsyncFixer covers other async anti-patterns: AsyncFixer01–05, cancellation-token propagation, fire-and-forget detection. Updated the bullet to describe what AsyncFixer actually does and added a note pointing readers at the right analyzer for ConfigureAwait enforcement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When setup.ps1 reads the repo from an SSH-style git remote (`git@github.com:owner/repo.git`), the placeholder replacement can leave a leading "@" and/or trailing ".git" in the Repository parameter of this script. Both break `gh api /repos/<repo>/rulesets` calls — the "@" turns into a 404 and the ".git" suffix is rejected as an unknown repo. Added a normalization step right after the parameter declaration that strips both. Auto-detection via `gh repo view` already produces a clean nameWithOwner value, so this only affects the user-supplied or placeholder-substituted path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three related fixes so the local script behaves like the per-PR
workflow instead of silently passing where CI would fail:
1. Linux/macOS gitleaks install used `curl ... | tar -C /usr/local/bin`,
which requires sudo for normal users and fails out of the box on
most local dev shells. Now installs to $HOME/.local/bin and adds
that to PATH (only if not already there). Matches the user-writable
pattern the CI workflows use.
2. When CoverageReport/Summary.txt was missing after a successful test
run, the script printed "skipping threshold check" and reported
success. pr.yaml fails the job in that situation. Align: emit a
hard failure with a clear message about ReportGenerator and mark
Coverage as failed.
3. "No test projects found in ./tests — skipping" let the script
finish "All checks passed" even when the repo had src/ projects.
Same rule as the pr.yaml tests-gate strict mode now: if ./src has
projects, refuse the silent skip and fail. If neither has projects
(template-pack / in-dev repos), graceful skip is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prerequisites listed ".NET 8.0 SDK or later", but the repo targets net10.0 (and multi-targets older TFMs). SDK 8.0 cannot load a net10.0 csproj — contributors who follow the instructions would hit NETSDK1045 "does not recognize TargetFramework net10.0". Bumped to .NET 10.0 SDK with a short note about why older SDKs won't work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`git worktree add` requires the target path NOT to exist, but the script created the path first via `mktemp -d` and then immediately called `git worktree add "$WORK_DIR" origin/gh-pages` — failing with "fatal: '<path>' already exists" on every invocation. Fix: `rmdir` the directory right after mktemp reserves the unique name. mktemp still guarantees uniqueness, the trap still cleans up afterward, and worktree-add now succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The [ValidateScript] guard for -Repository was dropped in an earlier refactor, letting callers pass a full URL (https://github.com/owner/repo), a value with a leading "@" from an SSH remote, or other malformed inputs. gh api would later fail with a confusing 404 and the user wouldn't know the parameter format was the problem. Restored an early format check via [ValidatePattern]: - empty (auto-detect via `gh repo view` kicks in downstream) - the template {{GITHUB_USERNAME}}/{{REPO_NAME}} placeholder (also replaced downstream by setup.ps1) - strict `owner/repo` (no /, no @, no whitespace) Anything else fails at parameter binding with a clear error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Windows gitleaks install path uses Invoke-WebRequest with -UseBasicParsing, which is a Windows-PowerShell-5.1-only flag. The script's shebang is pwsh, so PowerShell 7+ errors on the unsupported flag and gitleaks never installs locally. Same fix we applied to docfx.yaml's Invoke-WebRequest earlier — drop the flag (it's unnecessary in pwsh). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Policy: TreatWarningsAsErrors=true applies to ALL projects in Release (src + tests + examples + benchmarks). The subtree Directory.Build.props files in benchmarks/ were overriding the root property back to false for their respective subtrees, contradicting the stated convention. Per-csproj <NoWarn> is the right tool for stylistic / test-pattern / benchmark-specific suppressions — broad TWEA flips at the subtree level are too coarse and let real regressions slip through CI. (The .editorconfig [tests/**/*.cs] relaxations don't help either: the Windows runner doesn't honor them, so test-project warnings need per-csproj <NoWarn> regardless.) Each removed file contained only the MSBuild Import + the TWEA override, nothing else worth preserving. After this commit, benchmarks/ subtree(s) inherit root TWEA=true in Release. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`REPO_NAME=${REPO_URL##*/}` worked for HTTPS remotes
(`https://github.com/owner/repo[.git]` → "repo") but is brittle for
SSH-style remotes (`git@github.com:owner/repo.git`). For example
if someone has an SSH remote without the `.git` suffix, the parsing
falls back to using parts of the host instead of the repo name.
Changing the parameter expansion to strip after either `/` or `:`
(`${REPO_URL##*[/:]}`) so both URL forms produce just "repo".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After installing dotnet-reportgenerator-globaltool, the script invokes `reportgenerator` immediately. On a shell where the .NET installer hasn't yet appended $HOME/.dotnet/tools (Linux/macOS) or $env:USERPROFILE\.dotnet\tools (Windows) to PATH — common for fresh terminals or pwsh sessions launched from a script — the call fails with "reportgenerator: command not found". Switched to update-or-install (idempotent for already-installed tools) and added an explicit PATH-prepend after the install so the subsequent reportgenerator call always finds the binary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esets Fixes two bugs in the canonical-unprotected scripts that were fanned out from repo-template: - Setup-Labels.ps1: the ValidatePattern attribute string was left unterminated and the [string]$Repository declaration + closing paren were dropped, leaving a duplicated script body that ran with $Repository unbound. Param block restored to a single well-formed declaration; duplicated body removed. - Fix-BranchRuleset.ps1: gh api --paginate concatenates multiple JSON array payloads when results span pages, which breaks ConvertFrom-Json. Switched to ?per_page=100 in a single call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every shippable src csproj across the fleet had `<AssemblyVersion>1.0.0</AssemblyVersion>` hardcoded — last touched at v1.0.0 and never bumped. Released NuGet packages from v1.1.0 onward have shipped with AssemblyVersion=1.0.0 even though the package <Version> bumped normally. Drop the explicit AssemblyVersion line. .NET SDK derives AssemblyVersion, FileVersion, and InformationalVersion from <Version> when they are not specified, so all four track every <Version> bump automatically going forward. They are not byte-identical — AssemblyVersion is normalized to a 4-part System.Version, InformationalVersion can carry a +<git-sha> under SourceLink — but they all reflect the current Version, which is the point. Mirrors DateTime-Extensions PR #184 (already merged to main). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Template-drift resolution for
ETL-Test-Kit(initiative C1) — the unprotected half. Re-syncs 15 template-tracked infrastructure files (scripts, docs process files,.githubtemplates,.gitattributes, etc.) to the canonicalrepo-template.Excludes
docfx_project/*(repo-specific docs — docs initiatives), protected config/workflow files (separate PR), and.gitkeep/ expected-noise files.Part of #72.
🤖 Generated with Claude Code