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

@ptr727 ptr727 commented May 4, 2026

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).

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

Copilot AI 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
File Description
docs/ssh-signing.md Adds SSH commit signing setup/verification/troubleshooting docs.
docs/host-setup.md Documents per-OS host prerequisites (SSH key, allowed_signers, gh auth).
docs/devcontainer.md Documents per-language devcontainers, mounts, lifecycle, and verification steps.
Tests/Tests.csproj Updates test project reference to NuGetLibrary.
Tests/LoggingTests.cs Updates namespace import to ptr727.ProjectTemplate.NuGetLibrary.
README.md Adds PyPI surface area, devcontainer-first setup path, and optional hook guidance; reorders link refs.
Python.code-workspace Adds Python-focused VS Code workspace settings/extensions.
PyPiLibrary/uv.lock Adds committed uv lockfile for reproducible Python CI.
PyPiLibrary/tests/test_example.py Adds basic pytest coverage for the sample module and version export.
PyPiLibrary/tests/init.py Initializes Python tests package (empty marker).
PyPiLibrary/src/ptr727_projecttemplate_library/py.typed Marks the Python package as typed (PEP 561).
PyPiLibrary/src/ptr727_projecttemplate_library/example.py Adds a trivial example function (greet).
PyPiLibrary/src/ptr727_projecttemplate_library/_version.py Adds single-source version module for hatchling dynamic version.
PyPiLibrary/src/ptr727_projecttemplate_library/init.py Re-exports __version__ and greet as the public surface.
PyPiLibrary/pyproject.toml Defines Python packaging + tool config (hatchling/ruff/pyright/pytest; Python >=3.14).
PyPiLibrary/README.md Documents local dev loop and Trusted Publishing setup + template adoption notes.
PyPiLibrary/CODESTYLE.md Adds Python-specific style guide and local/CI command loop.
ProjectTemplate.slnx Updates .NET project paths and adds the new PyPI build workflow to solution folders.
NuGetLibrary/Options.cs Updates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/NuGetLibrary.csproj Renames RootNamespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/LogOptions.cs Updates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/Library.cs Updates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/GlobalUsings.cs Adds library-level global usings for logging abstractions.
NuGetLibrary/Extensions.cs Updates namespace to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/.editorconfig Adds scoped suppression for CS1591 missing XML-doc warnings in library.
DotNet.code-workspace Renames/splits the main workspace into a .NET-focused workspace file.
Console/Program.cs Updates namespace import to ptr727.ProjectTemplate.NuGetLibrary.
Console/Console.csproj Updates console project reference to NuGetLibrary.
CodeGen/CodeGen.cs Updates generated quote/date content.
CODESTYLE.md Re-scopes root style guide to .NET and documents CI lint backstop (no default hooks).
Benchmarks/Benchmarks.csproj Updates benchmarks project reference to NuGetLibrary.
AGENTS.md Expands cross-cutting conventions (branch model, workflow conventions, devcontainers, review loop).
.vscode/tasks.json Removes Husky task and keeps .NET build/format tasks.
.vscode/launch.json Updates debug launch configs and adds CodeGen --apikey env wiring.
.husky/task-runner.json Removes Husky.Net task runner config.
.husky/pre-commit Removes Husky pre-commit hook script.
.gitignore Adds Python/uv ignores (.venv, dist, caches, etc.).
.github/workflows/test-release-task.yml Replaces Husky invocation with dotnet csharpier check + dotnet format --verify-no-changes.
.github/workflows/test-pull-request.yml Renames a step to match naming conventions (“… step”).
.github/workflows/run-codegen-pull-request-task.yml Removes Husky install from codegen formatting step.
.github/workflows/run-codegen-app-pull-request-task.yml Removes Husky install from codegen formatting step.
.github/workflows/publish-release.yml Adds a publish-pypi job using OIDC Trusted Publishing.
.github/workflows/merge-bot-pull-request.yml Documents token/recursion-guard behavior for merge-bot merges.
.github/workflows/build-release-task.yml Switches to NuGetLibrary build task, adds PyPiLibrary build task, and pins release target_commitish.
.github/workflows/build-pypilibrary-task.yml Adds reusable Python CI build task (uv sync, ruff, pyright, pytest, uv build, artifact upload).
.github/workflows/build-nugetlibrary-task.yml Renames reusable workflow/job/artifacts from “library” to “nugetlibrary”.
.github/dependabot.yml Adds Dependabot uv ecosystem updates for /PyPiLibrary.
.github/copilot-instructions.md Refactors Copilot instructions to defer to AGENTS.md + adds review runbook mechanics.
.devcontainer/python/post-create.sh Installs pinned uv and pre-syncs PyPiLibrary in the Python devcontainer.
.devcontainer/python/devcontainer.json Adds Python devcontainer with bind mounts + matching extension list.
.devcontainer/dotnet/post-create.sh Restores .NET local tools in the .NET devcontainer.
.devcontainer/dotnet/devcontainer.json Adds .NET devcontainer with bind mounts + matching extension list.
.config/dotnet-tools.json Removes 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 thread Python.code-workspace Outdated
Comment thread .github/workflows/build-pypilibrary-task.yml Outdated
Comment thread README.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.

Copilot AI 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 thread Python.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.
Copilot AI review requested due to automatic review settings May 11, 2026 15:12

Copilot AI 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 thread README.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>

Copilot AI 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 main May 11, 2026
42 of 43 checks passed
Sign up for free to 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