Skip to content

Add PyPiLibrary, Per-Language Devcontainers, Signing Docs, NuGet Rename - #66

Merged
ptr727 merged 8 commits into
mainfrom
develop
May 11, 2026
Merged

Add PyPiLibrary, Per-Language Devcontainers, Signing Docs, NuGet Rename#66
ptr727 merged 8 commits into
mainfrom
develop

Conversation

@ptr727

Copy link
Copy Markdown
Owner

Release merge: brings five squashed PRs from develop into main.

Squashed PRs included

  • Pin release action SHA, target_commitish, agent conventions #61 — Pin release action SHA, target_commitish, agent conventions.softprops/action-gh-release pinned to a commit SHA with target_commitish: ${{ github.sha }} so the release tag lands on the artifact's commit, not the default branch. Updated AGENTS.md workflow YAML conventions.
  • Rename Library project to NuGetLibrary #62 — Rename Library project to NuGetLibrary. Project + folder renamed; .slnx, .csproj, build workflow, and references updated. Disambiguates from the new Python sibling.
  • Add devcontainer + per-OS host and SSH signing docs #63 — Add devcontainer + per-OS host and SSH signing docs. New docs/host-setup.md, docs/ssh-signing.md, docs/devcontainer.md. Devcontainer bind-mounts SSH public key, allowed_signers, and gh config so commits sign correctly inside the container.
  • Add PyPiLibrary Python sibling project #64 — Add PyPiLibrary Python sibling project. New PyPiLibrary/ template under src/-layout: pyproject.toml + uv.lock + ruff/pyright/pytest config + sample module + tests + build-pypilibrary-task.yml workflow + publish-pypi job in publish-release.yml.
  • Split Devcontainer and Workspace per Language and Drop Husky #65 — Split Devcontainer and Workspace per Language and Drop Husky..devcontainer/dotnet/ + DotNet.code-workspace and .devcontainer/python/ + Python.code-workspace. Husky.Net removed (CI is the lint backstop). Optional opt-in hooks documented in README. All Husky references removed from workflows, AGENTS, CODESTYLE, and tasks.

Notes

Test plan

  • CI passes on the merge commit (test-release-task workflow, all build matrix legs).
  • Confirm release tag lands on the merge commit (target_commitish from Pin release action SHA, target_commitish, agent conventions #61).
  • Spot-check the new .devcontainer/dotnet/ and .devcontainer/python/ open and build cleanly.
  • Spot-check cd PyPiLibrary && uv sync && uv run pytest passes.
  • Confirm .git/hooks/pre-commit is absent in a fresh clone (Husky removed).

CopilotAI review requested due to automatic review settings May 4, 2026 02:33

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Release merge that turns the template into a polyglot (.NET + Python) repo, updates release/CI plumbing accordingly, documents devcontainer + SSH signing setup, and completes the .NET “Library” → “NuGetLibrary” rename while removing Husky as the default hook runner.

Changes:

  • Add Python PyPiLibrary/ (uv + hatchling + ruff + pyright + pytest) with CI build workflow and PyPI Trusted Publishing job.
  • Split tooling into per-language devcontainers + VS Code workspaces, and add host/devcontainer/SSH-signing documentation.
  • Rename the .NET library project/folder to NuGetLibrary/ and update workflows + references; remove Husky from tasks/tools/workflows and rely on CI as the lint backstop.

Reviewed changes

Copilot reviewed 45 out of 51 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
docs/ssh-signing.mdAdds SSH commit signing setup/verification/troubleshooting docs.
docs/host-setup.mdDocuments per-OS host prerequisites (SSH key, allowed_signers, gh auth).
docs/devcontainer.mdDocuments per-language devcontainers, mounts, lifecycle, and verification steps.
Tests/Tests.csprojUpdates test project reference to NuGetLibrary.
Tests/LoggingTests.csUpdates namespace import to ptr727.ProjectTemplate.NuGetLibrary.
README.mdAdds PyPI surface area, devcontainer-first setup path, and optional hook guidance; reorders link refs.
Python.code-workspaceAdds Python-focused VS Code workspace settings/extensions.
PyPiLibrary/uv.lockAdds committed uv lockfile for reproducible Python CI.
PyPiLibrary/tests/test_example.pyAdds basic pytest coverage for the sample module and version export.
PyPiLibrary/tests/init.pyInitializes Python tests package (empty marker).
PyPiLibrary/src/ptr727_projecttemplate_library/py.typedMarks the Python package as typed (PEP 561).
PyPiLibrary/src/ptr727_projecttemplate_library/example.pyAdds a trivial example function (greet).
PyPiLibrary/src/ptr727_projecttemplate_library/_version.pyAdds single-source version module for hatchling dynamic version.
PyPiLibrary/src/ptr727_projecttemplate_library/init.pyRe-exports __version__ and greet as the public surface.
PyPiLibrary/pyproject.tomlDefines Python packaging + tool config (hatchling/ruff/pyright/pytest; Python >=3.14).
PyPiLibrary/README.mdDocuments local dev loop and Trusted Publishing setup + template adoption notes.
PyPiLibrary/CODESTYLE.mdAdds Python-specific style guide and local/CI command loop.
ProjectTemplate.slnxUpdates .NET project paths and adds the new PyPI build workflow to solution folders.
NuGetLibrary/Options.csUpdates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/NuGetLibrary.csprojRenames RootNamespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/LogOptions.csUpdates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/Library.csUpdates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/GlobalUsings.csAdds library-level global usings for logging abstractions.
NuGetLibrary/Extensions.csUpdates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/.editorconfigAdds scoped suppression for CS1591 missing XML-doc warnings in library.
DotNet.code-workspaceRenames/splits the main workspace into a .NET-focused workspace file.
Console/Program.csUpdates namespace import to ptr727.ProjectTemplate.NuGetLibrary.
Console/Console.csprojUpdates console project reference to NuGetLibrary.
CodeGen/CodeGen.csUpdates generated quote/date content.
CODESTYLE.mdRe-scopes root style guide to .NET and documents CI lint backstop (no default hooks).
Benchmarks/Benchmarks.csprojUpdates benchmarks project reference to NuGetLibrary.
AGENTS.mdExpands cross-cutting conventions (branch model, workflow conventions, devcontainers, review loop).
.vscode/tasks.jsonRemoves Husky task and keeps .NET build/format tasks.
.vscode/launch.jsonUpdates debug launch configs and adds CodeGen --apikey env wiring.
.husky/task-runner.jsonRemoves Husky.Net task runner config.
.husky/pre-commitRemoves Husky pre-commit hook script.
.gitignoreAdds Python/uv ignores (.venv, dist, caches, etc.).
.github/workflows/test-release-task.ymlReplaces Husky invocation with dotnet csharpier check + dotnet format --verify-no-changes.
.github/workflows/test-pull-request.ymlRenames a step to match naming conventions (“… step”).
.github/workflows/run-codegen-pull-request-task.ymlRemoves Husky install from codegen formatting step.
.github/workflows/run-codegen-app-pull-request-task.ymlRemoves Husky install from codegen formatting step.
.github/workflows/publish-release.ymlAdds a publish-pypi job using OIDC Trusted Publishing.
.github/workflows/merge-bot-pull-request.ymlDocuments token/recursion-guard behavior for merge-bot merges.
.github/workflows/build-release-task.ymlSwitches to NuGetLibrary build task, adds PyPiLibrary build task, and pins release target_commitish.
.github/workflows/build-pypilibrary-task.ymlAdds reusable Python CI build task (uv sync, ruff, pyright, pytest, uv build, artifact upload).
.github/workflows/build-nugetlibrary-task.ymlRenames reusable workflow/job/artifacts from “library” to “nugetlibrary”.
.github/dependabot.ymlAdds Dependabot uv ecosystem updates for /PyPiLibrary.
.github/copilot-instructions.mdRefactors Copilot instructions to defer to AGENTS.md + adds review runbook mechanics.
.devcontainer/python/post-create.shInstalls pinned uv and pre-syncs PyPiLibrary in the Python devcontainer.
.devcontainer/python/devcontainer.jsonAdds Python devcontainer with bind mounts + matching extension list.
.devcontainer/dotnet/post-create.shRestores .NET local tools in the .NET devcontainer.
.devcontainer/dotnet/devcontainer.jsonAdds .NET devcontainer with bind mounts + matching extension list.
.config/dotnet-tools.jsonRemoves Husky local tool from the tool manifest.
Comments suppressed due to low confidence (1)

DotNet.code-workspace:6

  • This workspace file is indented with tabs, but the repo .editorconfig sets indent_style = space globally. Converting the indentation to spaces will keep EditorConfig/formatting tools from constantly flagging this file as non-compliant.

Comment threadPython.code-workspace Outdated
Comment thread.github/workflows/build-pypilibrary-task.yml Outdated
Comment threadREADME.md Outdated
ptr727 added 5 commits May 3, 2026 20:18
## Summary
Three independent hardenings ported from the `homeassistant-purpleair`
sibling repo, plus convention docs for future PRs:
- **Tag pinning on releases** — `softprops/action-gh-release` is pinned
to commit SHA `3bb12739` (v2.6.2) with a trailing version comment, and
`target_commitish: ${{ github.sha }}` is set explicitly. Without it,
GitHub's REST API silently retargets the new tag to the repository
default branch (`main`) — so prerelease tags created on `develop` pushes
were attaching to `main`'s tip instead of the develop commit that built
the artifact, leaving "Browse files" and `git checkout <tag>` pointing
at unrelated code.
- **Merge-bot token comment** — documents the `GITHUB_TOKEN` vs
App-token recursion-guard tradeoff. No behavior change. The codegen-app
job already side-steps the recursion guard via the App identity;
dependabot and PAT-codegen targets feed `main` where releases are
dispatched manually, so the missing trigger is fine there.
- **Conventions documented** — `AGENTS.md` and
`.github/copilot-instructions.md` now codify PR title rules (≤72 chars,
imperative, no `Co-Authored-By` unless asked, no release-bump magnitude
in title), markdown style (reference-style links, alphabetized),
workflow YAML conventions (action SHA pinning + version comment, naming,
concurrency, `set -euo pipefail`, `if: >-` over `if: |`, boolean input
mirroring), and the develop=squash / main=merge branching model.
## Test plan
- [ ] CI green on the PR (test-pull-request workflow)
- [ ] After merge to develop, observe the next prerelease tag on the
Releases page — the tag's "Browse files" link should resolve to the same
commit SHA that ran `publish-release.yml`, not `main`'s tip
- [ ] Markdown renders correctly on the PR page (no MD025/MD031/MD032
warnings in editor preview)
- [ ] Subsequent PRs in this refactor series follow the new
title/body/workflow conventions
## Summary
Disambiguates the .NET project name from the upcoming Python
`PyPiLibrary` sibling. Folder, csproj filename, `RootNamespace`, and
namespace declarations move from `Library` to `NuGetLibrary`. The
companion GitHub Actions reusable workflow `build-library-task.yml` is
renamed to `build-nugetlibrary-task.yml`; the produced artifact name and
7z filename track the rename.
## Preserved on purpose
- `<PackageId>ptr727.ProjectTemplate.Library</PackageId>` is **kept** so
the published nupkg keeps its identity. No orphaned package, no 404 on
existing consumers, no new badge URL needed.
- README NuGet badges (`nuget-link`, `nugetreleaseversion-shield`,
`nugetprereleaseversion-shield`) still point at
`ptr727.ProjectTemplate.Library` and continue to work.
- Class names `TemplateLibrary` and `StaticTemplateLibrary` are
unchanged — they describe types, not the project.
- `InternalsVisibleTo` declarations stay (`Console`, `Tests`,
`Benchmarks` assembly names are unchanged).
## Files touched
- `git mv Library/ NuGetLibrary/` (folder)
- `git mv NuGetLibrary/Library.csproj NuGetLibrary/NuGetLibrary.csproj`
- `git mv .github/workflows/build-library-task.yml
.github/workflows/build-nugetlibrary-task.yml` — paths, job key
`build-nugetlibrary`, artifact name `nugetlibrary-build`, zip
`NuGetLibrary.7z`
- `.github/workflows/build-release-task.yml` — caller updated: job key,
`uses:`, `needs:` array, artifact-id reference
- `Console/Console.csproj`, `Tests/Tests.csproj`,
`Benchmarks/Benchmarks.csproj` — `<ProjectReference>` paths
- `Console/Program.cs`, `Tests/LoggingTests.cs` — `using` statement
- `NuGetLibrary/{Library,Options,LogOptions,Extensions}.cs` — namespace
declaration
- `NuGetLibrary/NuGetLibrary.csproj` — `RootNamespace`
- `ProjectTemplate.slnx` — 4 project path entries + 1 GitHub Actions
folder entry
- `ProjectTemplate.code-workspace` — `cSpell.words` add `nugetlibrary`
- `AGENTS.md`, `.github/copilot-instructions.md` — project list,
namespace examples, structure section
## Test plan
- [x] `dotnet build` — 0 warnings, 0 errors locally
- [x] `dotnet test` — 15 passed, 0 failed
- [x] `dotnet pack ./NuGetLibrary/NuGetLibrary.csproj` — produces
`ptr727.ProjectTemplate.Library.1.0.0-pre.nupkg` (PackageId preserved)
- [x] Repo-wide grep for `Library/Library.csproj`, `build-library-task`
— zero remaining hits
- [ ] CI green on the PR (test-pull-request workflow invokes the renamed
reusable workflow)
- [ ] After merge, next prerelease produces `NuGetLibrary.7z` artifact
attached to the GitHub release
## Summary
Adds a single unified [Dev Container](https://containers.dev/) hosting
both the .NET 10 SDK and the upcoming Python `uv` toolchain, plus three
focused docs files that decompose host setup, devcontainer setup, and
SSH commit signing per-OS. On Windows, the **devcontainer flow**
requires WSL2 (the bind-mounts use POSIX paths). The **host-install
flow** in `README.md` supports native Windows via winget — see
`docs/host-setup.md` for the per-flow scope.
> **Stacked on [#62](#62)
(NuGetLibrary rename)**. The diff against `develop` will show PR #62's
changes until that PR merges; once it does, the diff here cleans up to
just the devcontainer + docs work.
## Changes
**New files**:
- `.devcontainer/devcontainer.json` — base
`mcr.microsoft.com/devcontainers/dotnet:1-10.0`, `gh` and `common-utils`
features. Bind-mounts `~/.ssh/id_ed25519.pub` (read-only),
`~/.config/git/allowed_signers` (read-only), `~/.config/gh`
(read-write). `${localEnv:HOME}${localEnv:USERPROFILE}` form covers
Linux/macOS and WSL2 hosts. Extension list mirrors
`ProjectTemplate.code-workspace` `recommendations`.
- `.devcontainer/post-create.sh` (executable, mode 100755) — installs
`uv` from `astral.sh` (pinned to a specific version via the
version-prefixed install URL; re-installs on version mismatch so the pin
holds even when uv is already on PATH), runs `dotnet tool restore`,
installs Husky.Net hooks, pre-syncs `PyPiLibrary` if it exists (guarded
so this script works before PR 5 lands).
- `docs/host-setup.md` — git identity, Ed25519 SSH key,
`allowed_signers`, `gh auth login`, per-OS ssh-agent setup, verify
checklist.
- `docs/devcontainer.md` — bind-mount table, lifecycle commands, `gh`
credential-store nuance (Keychain vs libsecret vs file), verify
checklist, troubleshooting.
- `docs/ssh-signing.md` — per-OS deltas (systemd ssh-agent on Linux,
Apple Keychain on macOS, WSL2 caveats), `allowed_signers` format,
devcontainer interaction, troubleshooting.
**Modified**:
- `README.md` — adds a "Recommended (devcontainer)" path to the
Development Environment Setup section linking to the new docs; the
existing host-install path stays. Template Project Setup section now
points at the docs files for prerequisites instead of inlining them.
- `ProjectTemplate.code-workspace` — adds `ms-python.python` and
`charliermarsh.ruff` to `recommendations` (mirrors the devcontainer
list); adds `unwantedRecommendations` for `ms-pyright.pyright`
(deprecated; pyright is provided by Pylance which `ms-python.python`
auto-installs), `ms-python.mypy-type-checker`, `ms-python.pylint`,
`ms-python.flake8`, `ms-python.isort`, and `ms-python.black-formatter`
so contributors aren't prompted to install tools that overlap with ruff
+ Pylance and would surface "could not find binary" connection errors
against the venv. Also adds `astral`, `devcontainer`, `hatchling`,
`Keychain`, `libsecret`, `onCreateCommand`, `postCreateCommand`,
`pyproject`, `pypi`, `pypilibrary`, `pyright`, `ruff` to `cSpell.words`.
## Why a single unified container
VS Code Dev Containers does not support per-folder containers in the
same multi-root window — only a picker per session. A single image with
both .NET and `uv` is the simplest mental model and lets downstream
users delete the language they don't need by removing a feature line and
a postCreateCommand step. See [VS Code Dev Containers
docs](https://code.visualstudio.com/remote/advancedcontainers/connect-multiple-containers)
for the limitation.
## Why bind-mount the public key, not the private key
The private key never enters the container. Signing happens via the SSH
agent socket forwarded by VS Code Dev Containers (`SSH_AUTH_SOCK`). The
public key plus `allowed_signers` is enough for git to know which key to
delegate signing to and to verify signatures in `git log
--show-signature`.
## Why an `onCreateCommand` chown
On macOS hosts the bind-mount surfaces `/home/vscode/.ssh` as root-owned
inside the container, which would block `gh` from updating
`known_hosts`. The chown is idempotent on Linux and WSL2 so it stays
unconditional rather than gated on host detection.
## Test plan
- [x] `gh pr create --base develop --head devcontainer-docs` succeeded
- [ ] CI green on the PR (test-pull-request workflow)
- [ ] Build the devcontainer on Linux, run `git -c gpg.format=ssh commit
-S --allow-empty -m verify` inside, verify it signs
- [ ] Build the devcontainer on macOS host, verify the `onCreateCommand`
chown lets `gh auth status` work
- [ ] Build on WSL2 host, verify behavior matches Linux
- [ ] Confirm `recommendations` in `code-workspace` and
`customizations.vscode.extensions` in `devcontainer.json` are identical
- [ ] Markdown lint passes on the new docs files
## Summary
Adds a Python PyPi template project alongside the .NET `NuGetLibrary`,
completing the polyglot template. Modern 2026 stack: hatchling backend,
`uv` for env/deps/publish, ruff for lint+format, pyright for typing,
pytest for tests, PyPI Trusted Publishing via OIDC.
> **Stacked on [#62](#62)
(NuGetLibrary rename, merged) and
[#63](#63) (devcontainer +
docs)**. Once #63 merges this PR's diff cleans up to just the
PyPiLibrary work.
## Naming
- Folder: `PyPiLibrary/` — qualifier on disk to disambiguate from
`NuGetLibrary/`
- Published PyPI name: `ptr727-projecttemplate-library` — **no `pypi`
qualifier**, mirrors the NuGet identity
- Python import name: `ptr727_projecttemplate_library`
## New tree
```text
PyPiLibrary/
pyproject.toml # hatchling backend, ruff/pyright/pytest config, PEP 735 [dependency-groups]
README.md # what this PyPi template is + uv quickstart + Trusted Publisher setup
uv.lock # committed for reproducible CI
src/
ptr727_projecttemplate_library/
__init__.py
_version.py # __version__ = "0.0.0" placeholder; see README.md "Template Adoption" for version-scheme options
example.py # trivial greet() function
tests/
test_example.py # 3 tests
```
## Workflow plumbing
The split-by-purpose layout was chosen so `id-token: write` (required by
Trusted Publishing) only has to be granted on the entry-point job, not
propagated through reusable-workflow chains:
- **New** `.github/workflows/build-pypilibrary-task.yml` — reusable
workflow that **only builds**: setup uv (pinned to `0.11.8` to match the
devcontainer), sync, ruff check, ruff format --check, pyright, pytest,
`uv build`, upload artifact. **No publish job here**, no `id-token:
write`.
- **Modified** `.github/workflows/build-release-task.yml` — adds a
`build-pypilibrary` job calling the new reusable workflow, includes it
in the `github-release` `needs:` list. Build runs unconditionally
(matches the always-validate-on-PR semantic of the rest of the
workflow). **No `pypi: bool` input** — would require id-token
propagation through the test-pull-request chain (and triggered
`startup_failure`, fixed in 4c939f6).
- **Modified** `.github/workflows/publish-release.yml` — adds a
top-level `publish-pypi` job that runs after `create-release`, downloads
the `pypilibrary-build` artifact by name (artifacts uploaded by reusable
workflows are accessible to sibling jobs in the same run), and publishes
via Trusted Publishing. **`id-token: write` lives only here**, alongside
the explicit `contents: read` and `actions: read` needed for
`actions/download-artifact`. Uses `skip-existing: true` so the
placeholder `0.0.0` version doesn't fail the workflow on repeated
pushes.
- **Modified** `.github/workflows/test-release-task.yml` — no
PyPi-specific input needed; the build runs as part of the existing
chain.
## Other plumbing
- **`.github/dependabot.yml`** — adds `package-ecosystem: "uv"`
targeting `/PyPiLibrary` with the `pypi-deps` group label. Existing
`nuget` and `github-actions` blocks normalized to standard two-space
indentation under `updates:`.
- **`.husky/task-runner.json`** — adds `Ruff Format` and `Ruff Check`
tasks scoped to `PyPiLibrary/**/*.py`. Both pass `${staged}` as
positional args via `bash -c "..." -- ${staged}` so paths with spaces
survive; both gate on `command -v uv` so a `.cs`-only commit on a
contributor without uv installed doesn't fail.
- **`ProjectTemplate.code-workspace`** — adds Python format-on-save with
ruff, the `[python]` formatter binding,
`python.terminal.activateEnvironment: false`. No hard-coded venv paths
(those caused "could not find ruff binary" popups before `uv sync` ran).
Adds `unwantedRecommendations` for mypy / pylint / flake8 / isort /
black / standalone pyright so contributors aren't prompted to install
tools that overlap with ruff and Pylance.
- **`ProjectTemplate.slnx`** — adds `build-pypilibrary-task.yml` to the
GitHub Actions folder.
- **`.gitignore`** — adds `.venv/`, `dist/`, `__pycache__/`,
`*.py[cod]`, `*.egg-info/`, `.pytest_cache/`, `.ruff_cache/`,
`.pyright/`.
- **`README.md`** — PyPI badge + link in the build/distribution and
releases sections; template TODO list reminds the deriver to delete the
unused language side. `gh` "pre-authenticated" wording softened to call
out the Keychain/libsecret credential-store limitation.
## Trusted Publisher setup (one-time, on PyPI side)
1. PyPI → **Account settings** → **Publishing** → **Add a new pending
publisher**
- Project name: `ptr727-projecttemplate-library`
- Owner: `ptr727`
- Repo: `ProjectTemplate`
- Workflow: `publish-release.yml`
- Environment: `pypi`
2. GitHub repo → **Settings** → **Environments** → create `pypi`
environment (optionally with required reviewers).
The first successful release converts the pending publisher to a real
publisher.
## Versioning gap
`_version.py` ships with `__version__ = "0.0.0"`. Trusted Publishing
with `skip-existing: true` means the workflow won't fail, but no new
PyPI versions land until you wire `_version.py` to something that
increments — see `PyPiLibrary/README.md` "Template Adoption" for the
three usual options (hatch-vcs / version.json bridge / manual bumps).
## Test plan
- [x] `uv sync` clean (host: uv 0.11.8)
- [x] `uv run ruff check` — All checks passed
- [x] `uv run ruff format --check` — clean
- [x] `uv run pyright` — 0 errors, 0 warnings, 0 informations
- [x] `uv run pytest` — 3 passed
- [x] `uv build` — produces
`ptr727_projecttemplate_library-0.0.0.tar.gz` and wheel
- [x] `dotnet build` — 0 warnings, 0 errors
- [x] `dotnet test` — 15 passed (no .NET regression)
- [ ] CI green on the PR (test-release-task exercises ruff, pyright,
pytest, uv build via the same reusable workflow that publish uses)
- [ ] After Trusted Publisher is configured on PyPI and a real version
scheme is wired in `_version.py`, next merge to `main` smoke-tests the
publish path
## Summary
- Replace the single `.devcontainer/` + `ProjectTemplate.code-workspace`
with per-language pairs: `.devcontainer/dotnet/` +
`DotNet.code-workspace` and `.devcontainer/python/` +
`Python.code-workspace`. Each container ships only one toolchain so
editor surface, recommended extensions, and `postCreateCommand` match
the language being worked on.
- Drop the Husky.Net hooks framework. Hooks are repo-global, so any
framework forces its runtime (`dotnet` for Husky, Python for pre-commit)
into the wrong-language container. CI already runs `dotnet csharpier
check`, `dotnet format --verify-no-changes`, `ruff check`, `ruff format
--check`, and `pyright` on every PR — that is the lint backstop.
- Document opt-in hooks per language in README under "Optional: enable
git hooks locally" so downstream forks can wire up Husky.Net (.NET) or
pre-commit (Python) if they want pre-commit checks locally.
## Notes
- Windows host Python work is intentionally unsupported: the Python
extension caches `.venv/bin/python` (Linux layout) against a venv whose
actual Windows path is `.venv\Scripts\python.exe`, breaking Ruff. The
python devcontainer is the supported path.
- Git rename detection labels `ProjectTemplate.code-workspace ->
Python.code-workspace` because `Python.code-workspace` happened to be
the closer content match. Conceptually `DotNet.code-workspace` is the
descendant; the actual file contents are correct in both.
## Test plan
- [ ] `git diff main...HEAD` review.
- [ ] Open `DotNet.code-workspace` on Windows host -> `dotnet build` +
`dotnet test` succeed without Husky.
- [ ] Open `DotNet.code-workspace` -> Reopen in Container -> "dotnet" ->
`dotnet build` succeeds; `which uv` returns nothing; `ms-python.python`
not installed.
- [ ] Open `Python.code-workspace` -> Reopen in Container -> "python" ->
`cd PyPiLibrary && uv sync && uv run pytest` succeeds; `which dotnet`
returns nothing; Ruff extension log shows interpreter at the container
venv path.
- [ ] Confirm CI green on this PR (csharpier check + dotnet format
--verify-no-changes; ruff check + ruff format --check + pyright).
- [ ] Push a deliberately mis-formatted `.cs` file on a throwaway branch
-> .NET pipeline fails on csharpier; same with a `.py` file -> Python
pipeline fails on ruff. (Verifies the lint backstop without hooks.)
- [ ] Confirm `.git/hooks/pre-commit` does not exist after a fresh clone
+ devcontainer rebuild.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 45 out of 51 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (4)

.github/workflows/test-pull-request.yml:32

  • This multi-line bash run block doesn’t start with set -euo pipefail, which is a documented workflow convention in AGENTS.md. Adding it as the first line will make failures/undefined variables surface reliably and keeps the file consistent with the repo’s workflow standards.
 - name: Check workflow results step
run: |
exit_on_result() {
if [[ "$2" == "failure" || "$2" == "cancelled" ]]; then
echo "Job '$1' failed or was cancelled."

.github/workflows/build-nugetlibrary-task.yml:45

  • This multi-line bash run block doesn’t start with set -euo pipefail (required by AGENTS.md workflow conventions). Add it as the first line to fail fast on errors and undefined variables.
    .github/workflows/build-nugetlibrary-task.yml:58
  • This multi-line bash run block doesn’t start with set -euo pipefail (required by AGENTS.md workflow conventions). Add it as the first line to fail fast on errors and undefined variables.
    DotNet.code-workspace:6
  • DotNet.code-workspace uses tab indentation, but the repo-wide .editorconfig sets indent_style=space (and indent_size=4) for all files. Please reformat this workspace file to use spaces so it stays consistent with the repo’s formatting rules and avoids noisy diffs.

Comment thread.github/workflows/run-codegen-pull-request-task.yml
Comment thread.github/workflows/run-codegen-app-pull-request-task.yml
Comment thread.github/workflows/build-pypilibrary-task.yml
Comment threadPython.code-workspace Outdated
- Python.code-workspace and DotNet.code-workspace: convert tab indentation to 4 spaces to match .editorconfig (DotNet bundled for consistency, same issue)
- build-pypilibrary-task.yml: correct uv pin reference path to .devcontainer/python/post-create.sh
- README.md: fix double-ampersand in nugetprereleaseversion-shield URL
## Summary
Addresses unresolved Copilot review threads on [PR
#66](#66) (the active
develop→main release PR) plus the same-class items flagged in Copilot's
review-summary "low-confidence" comments. Per [AGENTS.md workflow
conventions](https://github.com/ptr727/ProjectTemplate/blob/develop/AGENTS.md),
multi-line bash `run:` blocks must start with `set -euo pipefail` so
failures and undefined variables surface reliably.
### Workflows hardened
-
[`.github/workflows/run-codegen-pull-request-task.yml`](.github/workflows/run-codegen-pull-request-task.yml)
— codegen, format, trigger-PR steps (3 blocks). _Inline Copilot threads
on PR #66._
-
[`.github/workflows/run-codegen-app-pull-request-task.yml`](.github/workflows/run-codegen-app-pull-request-task.yml)
— codegen, format steps (2 blocks). _Inline Copilot threads on PR #66._
-
[`.github/workflows/test-pull-request.yml`](.github/workflows/test-pull-request.yml)
— Check-workflow-results step that defines `exit_on_result` (1 block).
_Review-summary item._
-
[`.github/workflows/build-nugetlibrary-task.yml`](.github/workflows/build-nugetlibrary-task.yml)
— dotnet-build and dotnet-nuget-push steps (2 blocks). _Review-summary
item._
### .gitignore housekeeping
- Adds `.claude` so the local Claude harness state directory does not
appear in `git status`.
- Drops the now-redundant `# Python / uv` section comment.
### Why a new PR (not a fresh commit on PR #66's branch)
Standing project rule: no direct commits to `develop`. Once this PR
merges to `develop`, PR #66's diff will absorb the same fixes
automatically (since #66 is `develop` → `main`), and the threads there
can be marked resolved.
## Test plan
- [ ] CI passes on this PR.
- [ ] After merge, confirm PR #66's diff now includes the four workflow
fixes and the `.gitignore` entry, and Copilot threads on #66 can be
resolved.
CopilotAI review requested due to automatic review settings May 11, 2026 15:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 45 out of 51 changed files in this pull request and generated 1 comment.

Comment threadREADME.md Outdated
## Summary
Two follow-ups for [PR
#66](#66) (the active
`develop` → `main` release PR):
### 1. README.md — missing colons on NuGet/PyPI bullets
Copilot review thread on PR #66 flagged that the NuGet and PyPI bullets
in the **Build and Distribution** list are missing the colon after the
bold label that every other bullet in the list uses.
```diff
- - **NuGet Packages** [NuGet Packages][nuget-link] - .NET libraries published to NuGet.org.
- - **PyPI Packages** [PyPI Packages][pypi-link] - Python library published to PyPI.org.
+ - **NuGet Packages**: [NuGet Packages][nuget-link] - .NET libraries published to NuGet.org.
+ - **PyPI Packages**: [PyPI Packages][pypi-link] - Python library published to PyPI.org.
```
### 2. publish-release.yml — wrong SHA for
`pypa/gh-action-pypi-publish@v1.14.0`
The action was pinned to SHA `6733eb7d741f0b11ec6a39b58540dab7590f9b7d`
with a `# v1.14.0` comment, but the upstream `v1.14.0` tag actually
points at `cef221092ed1bacb1cc03d23a2d87d1d172e277b`. Because
`ghcr.io/pypa/gh-action-pypi-publish` is tagged by release SHAs, no GHCR
image existed at the wrong SHA — Docker bailed out with `manifest
unknown`.
This has caused **`Publish PyPI library job` to fail on every push to
`develop`** since PR #64 added the action. CI evidence:
- Run on `25c338b9` (May 4) — failed at the same step.
- Run on `0da21b2` (today, the PR #68 merge) — failed at the same step.
Fix: use the actual upstream `v1.14.0` SHA, keep the `# v1.14.0`
comment.
## Why a new PR (not committed onto PR #66's branch)
Standing project rule: no direct commits to `develop`. Once this PR
merges to `develop`, PR #66's diff absorbs both fixes automatically
(since #66 is `develop` → `main`), and the README Copilot thread on #66
can be resolved.
## Test plan
- [ ] CI passes on this PR (in particular, the publish job won't run on
a non-release push — but the resolution will only be observable on the
next release push to `develop`).
- [ ] After merge, PR #66's CI re-runs with both fixes and `Publish PyPI
library job` succeeds.
- [ ] PR #66 README Copilot thread can be replied/resolved citing this
merge commit.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 45 out of 51 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit ffcb754 into mainMay 11, 2026
42 of 43 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@ptr727