Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0421792
Remove sample projects and build infra; extract cspell.json
ptr727 Jul 4, 2026
3c6def5
Relocate rulesets to repo-config and app workflows to catalog
ptr727 Jul 4, 2026
2ff129c
Repurpose governance docs; remove template-inheritance framing
ptr727 Jul 4, 2026
e8e05ba
Split README; add fleet README exemplar and GitHub-setup reference
ptr727 Jul 4, 2026
5eaf9bc
Add fleet registry, ground-truth spec, schemas, and validator
ptr727 Jul 4, 2026
dd5d1ff
Add AUDIT.md audit-agent instruction set and report template
ptr727 Jul 4, 2026
73441ac
Add workflow flow diagrams; complete cspell dictionary
ptr727 Jul 4, 2026
80c02ba
Adapt CI to lint-only and a source-only release
ptr727 Jul 4, 2026
71edae4
Verification cleanups: ASCII, links, EOL, residual framing
ptr727 Jul 4, 2026
fa1dca0
Adopt grouped reference-style links in human-facing docs
ptr727 Jul 4, 2026
e8f3681
Resolve Copilot review: harden spec validator and secrets schema
ptr727 Jul 4, 2026
6a6e14f
Resolve Copilot round 2: more defensive validator and ruleset script
ptr727 Jul 4, 2026
f3d651e
Resolve Copilot round 3: fail on unclassified repo; refresh devcontai…
ptr727 Jul 4, 2026
2dd363f
Resolve Copilot round 4: pin CI images by digest; LF-pin Python
ptr727 Jul 4, 2026
f3c6413
Resolve Copilot round 5: paginate the ruleset lookup
ptr727 Jul 4, 2026
a3278fb
Resolve Copilot round 6: fully defensive validator; duplicate-safe sc…
ptr727 Jul 4, 2026
38996f2
Resolve Copilot round 7: fix create path abort in configure.sh
ptr727 Jul 4, 2026
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
20 changes: 0 additions & 20 deletions .config/dotnet-tools.json

This file was deleted.

41 changes: 0 additions & 41 deletions .dockerignore

This file was deleted.

4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ end_of_line = crlf
[*.sh]
end_of_line = lf

# Python - LF so shebang scripts stay executable
[*.py]
end_of_line = lf

# Dockerfiles - CRLF breaks RUN heredocs and line continuations
[{Dockerfile,*.Dockerfile}]
end_of_line = lf
Expand Down
33 changes: 17 additions & 16 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Default: do not normalize line endings (`* -text`); .editorconfig end_of_line rules guide what the editor writes.
# The exception pins below are git's own enforcement - they force LF for execution-sensitive classes regardless of editor.
# git config --global core.autocrlf false
# git add --renormalize .
# git ls-files --eol
* -text

# Exception: scripts must stay LF regardless of the `* -text` default - a CRLF shebang breaks execution. `.editorconfig`
# covers `*.sh`, but extensionless executables match no extension rule, so pin them here so git enforces LF on checkout
# and `--renormalize`. Any repo whose tooling ships extensionless scripts adds the matching path pin, e.g. s6-overlay
# init `Docker/s6-overlay/** text eol=lf` or husky/git hooks `.husky/pre-commit text eol=lf`.
*.sh text eol=lf

# Dockerfiles must be LF - a CRLF breaks RUN heredocs and line continuations.
Dockerfile text eol=lf
*.Dockerfile text eol=lf
# Default: do not normalize line endings (`* -text`); .editorconfig end_of_line rules guide what the editor writes.
# The exception pins below are git's own enforcement - they force LF for execution-sensitive classes regardless of editor.
# git config --global core.autocrlf false
# git add --renormalize .
# git ls-files --eol
* -text

# Exception: scripts must stay LF regardless of the `* -text` default - a CRLF shebang breaks execution. `.editorconfig`
# covers `*.sh`, but extensionless executables match no extension rule, so pin them here so git enforces LF on checkout
# and `--renormalize`. Any repo whose tooling ships extensionless scripts adds the matching path pin, e.g. s6-overlay
# init `Docker/s6-overlay/** text eol=lf` or husky/git hooks `.husky/pre-commit text eol=lf`.
*.sh text eol=lf
*.py text eol=lf

# Dockerfiles must be LF - a CRLF breaks RUN heredocs and line continuations.
Dockerfile text eol=lf
*.Dockerfile text eol=lf
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,4 @@ After the final push, sweep-resolve stale older threads for removed code paths.

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).
If you find a gap in the governance itself (this file or AGENTS.md is out of date, a rule is missing, something bit this repo and would bite the next), fix it in the governance docs as part of your change rather than only working around it locally.
77 changes: 4 additions & 73 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,59 +1,12 @@
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# Every ecosystem appears **twice**: once with `target-branch: "main"`
# and once with `target-branch: "develop"`. Dependabot will open
# parallel PRs against each branch, so both stay current on
# dependency versions independently of the develop -> main release
# cadence.
#
# Why dual-target and not develop-only:
# - `develop` is the integration branch and ships content forward to
# `main` through merge-commit releases, but the time between releases
# can be long (a feature branch may sit on develop for weeks).
# - Push-distribution channels (e.g. HACS for Home Assistant
# integrations, distros that pull from main) consume `main` directly.
# If `main` only got dependency bumps via the next develop -> main
# release, those channels would ship outdated code in the interim.
# - Codegen workflows take the same dual-target shape for the same
# reason - see .github/workflows/run-codegen-pull-request-task.yml.
#
# The merge-bot's `case` statement in
# .github/workflows/merge-bot-pull-request.yml dispatches the merge
# method per base ref (squash on develop, merge on main) so both bases
# auto-merge cleanly. `develop` remains strictly forward-only: there
# are no main -> develop back-merges; each branch absorbs its own
# Dependabot PRs and codegen PRs independently.
#
# Security update PRs (CVE-driven) are opened by Dependabot against
# the repo default branch (`main`) regardless of any `target-branch`
# config - the `case` statement handles them in the same code path.
# github-actions is the only ecosystem this repo ships. Dual-target (main + develop) so both branches
# stay current independently of the develop -> main release cadence; the merge-bot auto-merges each
# base with its per-base method. See catalog/snippets/configs/dependabot.yml for the multi-ecosystem
# reference (nuget, uv) a code-shipping repo uses.
version: 2
updates:

# ----- nuget -----

- package-ecosystem: "nuget"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
groups:
nuget-deps:
patterns:
- "*"

- package-ecosystem: "nuget"
target-branch: "develop"
directory: "/"
schedule:
interval: "daily"
groups:
nuget-deps:
patterns:
- "*"

# ----- github-actions -----

- package-ecosystem: "github-actions"
target-branch: "main"
directory: "/"
Expand All @@ -73,25 +26,3 @@ updates:
actions-deps:
patterns:
- "*"

# ----- uv (PyPiLibrary) -----

- package-ecosystem: "uv"
target-branch: "main"
directory: "/PyPiLibrary"
schedule:
interval: "daily"
groups:
pypi-deps:
patterns:
- "*"

- package-ecosystem: "uv"
target-branch: "develop"
directory: "/PyPiLibrary"
schedule:
interval: "daily"
groups:
pypi-deps:
patterns:
- "*"
119 changes: 40 additions & 79 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,106 +64,67 @@ jobs:
echo "publish=$publish" >> "$GITHUB_OUTPUT"

# Full build + publish of every target per planned branch. The matrix lets one run publish main (Release/latest)
# and develop (Debug/develop) - each leg checks out and versions its own branch via the threaded ref/branch.
# Publish each planned branch: NBGV computes the tag from the checked-out branch, then a GitHub release is created
# (tag + auto source archive + README + LICENSE). Source-only repo - no package or image targets.
publish:
name: Publish project release job
needs: [setup]
if: ${{ needs.setup.outputs.publish == 'true' }}
strategy:
fail-fast: false
matrix:
branch: ${{ fromJSON(needs.setup.outputs.branches) }}
uses: ./.github/workflows/build-release-task.yml
secrets: inherit
permissions:
contents: write
# actions:write lets the github-release job delete the release-asset-* artifacts it consumes (surgical cleanup).
actions: write
with:
ref: ${{ matrix.branch }}
branch: ${{ matrix.branch }}
smoke: false
# Push to GitHub and NuGet and Docker Hub.
github: true
nuget: true
dockerhub: true

publish-pypi:
name: Publish PyPI library job
needs: [setup, publish]
if: ${{ needs.setup.outputs.publish == 'true' }}
strategy:
fail-fast: false
matrix:
branch: ${{ fromJSON(needs.setup.outputs.branches) }}
runs-on: ubuntu-latest
# build-pypilibrary-task.yml picks the PEP 440 version per branch (M.N.P.B on main, M.N.P.B.dev0 on develop). The
# pypi environment's deployment branch rule restricts uploads to main + develop as defense in depth.
environment:
name: pypi
url: https://pypi.org/project/ptr727-projecttemplate-library/
# id-token:write for Trusted Publishing's OIDC exchange, contents:read for repo metadata, actions:write so
# download-artifact can fetch this run's build artifact and the surgical cleanup step can delete it afterwards.
permissions:
id-token: write
contents: read
actions: write

steps:

- name: Download PyPI library build artifacts step
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Setup .NET SDK step
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
# Branch-suffixed name so both branch legs in this run stay distinct.
name: pypilibrary-build-${{ matrix.branch }}
path: ./dist
dotnet-version: 10.x

- name: Publish to PyPI step
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
# Full history so NBGV can compute the git height for the checked-out branch.
- name: Checkout code step
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
packages-dir: ./dist
# Skip rather than fail when the version already exists; the weekly republish re-uploads unchanged versions.
skip-existing: true
ref: ${{ matrix.branch }}
fetch-depth: 0

# Surgical cleanup at the point of consumption: the pypilibrary-build-<branch> artifact has been published, so
# delete it by exact name to free the storage quota. A failure before this leaves it for the retention-days: 1
# backstop.
- name: Delete consumed PyPI build artifact step
# Best-effort: PyPI is already published, so a listing/delete hiccup must never red the job; the
# retention-days: 1 backstop reaps anything missed. Deletes every matching id (a rerun can upload duplicates).
continue-on-error: true
# nbgv floats on @master (its tag stream lags master). IGNORE_GITHUB_REF versions the checked-out branch, not the
# CI ref, so a dispatch from main still classifies the develop leg as a prerelease.
- name: Compute version step
id: nbgv
uses: dotnet/nbgv@master
env:
IGNORE_GITHUB_REF: "true"

# The weekly publisher re-runs with no new commits, so the tag may already exist. Skip create on an existing tag
# (no-op republish); a manual dispatch refreshes it.
- name: Check for existing release step
id: release-exists
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.nbgv.outputs.SemVer2 }}
run: |
set -euo pipefail
if ! ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.run_id }}/artifacts" --paginate \
--jq ".artifacts[] | select(.name == \"pypilibrary-build-${{ matrix.branch }}\") | .id"); then
echo "::warning::Could not list run artifacts; retention-days backstop will reap them."
ids=""
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
for id in $ids; do
gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" \
|| echo "::warning::Failed to delete artifact $id; retention-days backstop will reap it."
done

# Caller-gated to main: the badge and Docker Hub overview have no per-branch context, so they update only when main
# is among the published branches (a develop-only push skips them). One invocation, not a per-branch matrix leg.
date-badge:
name: Create BYOB date badge job
needs: [setup, publish]
if: ${{ needs.setup.outputs.publish == 'true' && contains(fromJSON(needs.setup.outputs.branches), 'main') }}
uses: ./.github/workflows/build-datebadge-task.yml
secrets: inherit
permissions:
contents: write

docker-readme:
name: Publish Docker Hub readme job
needs: [setup, publish]
if: ${{ needs.setup.outputs.publish == 'true' && contains(fromJSON(needs.setup.outputs.branches), 'main') }}
uses: ./.github/workflows/publish-docker-readme-task.yml
secrets: inherit
permissions:
contents: read
with:
ref: main
# target_commitish pins the tag to the exact built commit (GitCommitId), not the default branch. The release is
# the tag plus GitHub's auto source archive, README, and LICENSE - no build assets (source-only).
- name: Create GitHub release step
if: ${{ steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch' }}
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
generate_release_notes: true
tag_name: ${{ steps.nbgv.outputs.SemVer2 }}
target_commitish: ${{ steps.nbgv.outputs.GitCommitId }}
prerelease: ${{ matrix.branch != 'main' }}
files: |
LICENSE
README.md
Loading