Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,19 @@ The steps below are followed in order rather than sampled.

### The Prose Content Backlog

One pull request clearing prose findings, leading with [`catalog/snippets/`][snippets] because a non-conformant snippet seeds its violations into every repo that adopts it and the downstream repo is then flagged for content it was handed.
One pull request clearing the prose findings the hub's own docs and spec still carry, now that [`catalog/snippets/`][snippets] is clear. The snippets led because a non-conformant snippet seeds its violations into every repo that adopts it, and the downstream repo is then flagged for content it was handed.

**State** `ready`. **Touches** [`catalog/snippets/`][snippets] first, then the hub's docs and spec. **Cost** one hub edit per batch, hub-only, and a snippet fix reaches the fleet only as repos re-adopt.
**State** `ready`. **Touches** the hub's docs and spec. **Cost** one hub edit per batch, hub-only.

- **Clear the [#519][issue-519] prose backlog, snippets first.** The whole-tree figure moves as readily with a fix to the gate as with a fix to the prose, so it is re-measured rather than quoted.
- **Clear the remaining [#519][issue-519] prose backlog, outside the snippets.** The whole-tree figure moves as readily with a fix to the gate as with a fix to the prose, so it is re-measured rather than quoted.
- **Blocked by** - Nothing.
- **Issue** - [#519][issue-519], whose headline numbers are stale and whose four planned changes are two-thirds landed.
- **Checked** - `develop` at `3d1a0b1` on 2026-08-06, where `python3 scripts/prose_lint.py --summary` reports 557 violations across 45 files, and `catalog/snippets` alone reports 184 across 19.
- **Checked** - `develop` at `a6d7a4b` on 2026-08-07, where `python3 scripts/prose_lint.py --summary` reports 373 violations across 26 files, and `catalog/snippets` reports 0. The snippets sweep took the tree from 557 across 45, of which 184 across 19 were snippets.
- **Open** - Whether the gate becomes a carried file rather than a hub-only one, which "Reducing the Carried Surface Further" asks from the other direction.
- **Settled** - `comment-wrap` and `comment-case` are in `DEFAULT_RULES` and `reports/` is exempt as a generated tree, which is why the figures differ from the 668 and 119 the issue records.
- **Settled** - `sentence-split` is defined but excluded from `DEFAULT_RULES`, so a sweep never reports it and a wrapped sentence in Markdown prose is not a finding.
- **Settled** - The three largest files are not snippets and are Python comments rather than prose, being [`spec/audit.py`][audit] at 99, [`gh-write-guard.py`][write-guard] at 52, and [`spec/validate.py`][validate] at 41 when measured before the sweep.
- **Settled** - A comment opening on a lowercase identifier is the bulk of what `comment-case` still reports, and the rule intends those restructured rather than exempted. The exemptions the snippets sweep added cover a commented-out key and a definition label, and nothing wider.

### Two Checks That Read What a Claim Points At

Expand Down Expand Up @@ -510,5 +512,6 @@ Each was checked against the tree and has nothing left to do anywhere. Closing i
[snippets]: ./catalog/snippets/
[standup]: ./STANDUP.md
[type-model]: ./spec/type-model.md
[validate]: ./spec/validate.py
[workflows]: ./catalog/snippets/workflows/
[write-guard]: ./host-setup/agent-safety/gh-write-guard.py
6 changes: 4 additions & 2 deletions catalog/snippets/configs/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Codecov configuration. Codecov's coverage statuses are advisory - they report and trend coverage, never gating a PR.
# Codecov configuration.
# Codecov's coverage statuses are advisory: they report and trend coverage, never gating a PR.
# - informational: true makes project and patch statuses advisory (always pass), so a coverage delta never blocks a PR.
# - Distinct from the upload step's fail_ci_if_error: false, which only guards upload errors.
# - A repo may override this to enforce a coverage threshold.
Expand All @@ -11,6 +12,7 @@ coverage:
default:
informational: true

# Exclude code intentionally not unit-tested from the coverage denominator. Add the repo's own paths.
# Exclude code intentionally not unit-tested from the coverage denominator.
# Add the repo's own paths.
# ignore:
# - "Sandbox/**"
17 changes: 10 additions & 7 deletions catalog/snippets/configs/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Reference: multi-ecosystem dual-target Dependabot config for a code-shipping repo.
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# Every ecosystem appears twice, once per target branch (main and develop), so both branches stay
# current on dependency versions independently of the develop -> main release cadence. Push-distribution
# channels (HACS, distros that pull main) consume main directly, so main must get bumps without waiting
# for the next release. The merge-bot dispatches the merge method per base ref (squash on develop, merge
# on main). develop is strictly forward-only; each branch absorbs its own PRs. Security (CVE) PRs always
# target the default branch regardless of target-branch. Keep only the ecosystems the repo actually ships.
# Every ecosystem appears twice, once per target branch (main and develop).
# Both branches therefore stay current on dependency versions independently of the release cadence.
# Push-distribution channels (HACS, distros that pull main) consume main directly.
# The main branch must therefore get bumps without waiting for the next release.
# The merge-bot dispatches the merge method per base ref (squash on develop, merge on main).
# Because develop is strictly forward-only, each branch absorbs its own PRs.
# Security (CVE) PRs always target the default branch regardless of target-branch.
# Keep only the ecosystems the repo actually ships.
version: 2
updates:

Expand Down Expand Up @@ -50,7 +52,8 @@ updates:
patterns:
- "*"

# uv directory points at the Python package root (e.g. /PackageName); adjust per repo layout.
# The uv directory points at the Python package root (e.g. /PackageName).
# Adjust it per repo layout.
- package-ecosystem: "uv"
target-branch: "main"
directory: "/PythonPackage"
Expand Down
4 changes: 2 additions & 2 deletions catalog/snippets/configs/docker-hub-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Container image for the ProjectTemplate Console app.

## Image Tags

- `latest` - the current stable release, built from `main`.
- `develop` - the current prerelease, built from `develop`.
- **`latest`** - the current stable release, built from `main`.
- **`develop`** - the current prerelease, built from `develop`.
- Immutable version tags (NBGV SemVer2): `X.Y.Z` for stable releases, `X.Y.Z-g<sha>` for develop prereleases.

## Usage
Expand Down
37 changes: 18 additions & 19 deletions catalog/snippets/devcontainer/python/post-create.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# Install uv (Astral) for the Python project. Idempotent - re-running
# overwrites in place. The installer drops the binary in $HOME/.local/bin and
# updates user shell init to add it to PATH for new shells; we add it to the
# current PATH explicitly so the rest of this script can invoke `uv` without a
# hard-coded path.
# Install uv (Astral) for the Python project.
# It is idempotent, since re-running overwrites in place.
# The installer drops the binary in $HOME/.local/bin.
# It also updates user shell init to add it to PATH for new shells.
# We add it to the current PATH explicitly so the rest of this script can invoke `uv`.
#
# uv is pinned to a specific version (via the version-prefixed install URL,
# https://astral.sh/uv/<version>/install.sh) so a compromised or broken
# upstream `latest` script cannot silently change what runs on contributors'
# machines and CI runners. Bump UV_VERSION when you've reviewed release notes.
# The uv version is pinned via the version-prefixed install URL.
# That URL is https://astral.sh/uv/<version>/install.sh.
# A broken or compromised upstream `latest` script therefore cannot silently change what runs.
# Bump UV_VERSION when you've reviewed release notes.
#
# We re-install when uv is missing OR when the installed version doesn't
# match the pin. The latter handles the case where a contributor (or a
# previous run with a different pin) left a different uv version on PATH -
# the pin is what's reproducible and what the lockfile is generated against.
# We re-install when uv is missing OR when the installed version doesn't match the pin.
# The latter handles a contributor or an earlier pin leaving another uv version on PATH.
# The pin is what's reproducible and what the lockfile is generated against.
UV_VERSION="0.11.8"
installed_uv_version=""
if command -v uv >/dev/null 2>&1; then
installed_uv_version="$(uv --version | awk '{print $2}')"
fi
if [[ "$installed_uv_version" != "$UV_VERSION" ]]; then
# Download the pinned installer to a temp file first instead of piping
# `curl ... | sh`. This produces a logged sha256 of exactly the bytes we
# ran, so a compromised installer leaves a forensic trail; it also lets
# a future change pin a known-good checksum (set EXPECTED_SHA below).
# Download the pinned installer to a temp file first instead of piping `curl ... | sh`.
# This produces a logged sha256 of exactly the bytes we ran.
# A compromised installer therefore leaves a forensic trail.
# It also lets a future change pin a known-good checksum (set EXPECTED_SHA below).
installer=$(mktemp -t uv-install.XXXXXX.sh)
trap 'rm -f "$installer"' EXIT
curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" -o "$installer"
Expand All @@ -40,8 +39,8 @@ if [[ "$installed_uv_version" != "$UV_VERSION" ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi

# Pre-warm uv environment for PyPiLibrary if it exists. Guarded so this script
# is safe before PyPiLibrary lands in the repo.
# Pre-warm uv environment for PyPiLibrary if it exists.
# It is guarded so this script is safe before PyPiLibrary lands in the repo.
if [[ -f PyPiLibrary/pyproject.toml ]]; then
(cd PyPiLibrary && uv sync)
fi
4 changes: 2 additions & 2 deletions catalog/snippets/husky/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Husky snippet

`pre-commit` is the reference git pre-commit hook (installed under `.husky/` by Husky). It runs **language formatting and style only** - CSharpier and `dotnet format` style via `dotnet husky run` for .NET, or ruff for a Python repo - kept fast with native tooling and no Docker.
`pre-commit` is the reference git pre-commit hook (installed under `.husky/` by Husky). It runs **language formatting and style only**: CSharpier and `dotnet format` style via `dotnet husky run` for .NET, or ruff for a Python repo. Native tooling and no Docker is what keeps it fast.

Full linting (line endings, workflow YAML, Markdown, spelling) is **not** run in the hook. It runs in CI as pinned action wrappers, and on demand via the VS Code **Lint** tasks in `catalog/snippets/configs/vscode-tasks.json` (Docker at `:latest`). Keeping the doc linters out of the hook is what keeps it simple.

A copied `.husky/pre-commit` is an extensionless shebang script, so pin it to **LF** in both `.gitattributes` (`.husky/pre-commit text eol=lf`) and `.editorconfig` (`[.husky/pre-commit] end_of_line = lf`) - a CRLF shebang breaks execution. Drop the `dotnet husky run` line in a non-.NET repo.
A copied `.husky/pre-commit` is an extensionless shebang script, so pin it to **LF** in both `.gitattributes` (`.husky/pre-commit text eol=lf`) and `.editorconfig` (`[.husky/pre-commit] end_of_line = lf`). A CRLF shebang breaks execution. Drop the `dotnet husky run` line in a non-.NET repo.
6 changes: 4 additions & 2 deletions catalog/snippets/husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Local pre-commit: language formatting and style only (no Docker). Full lint runs in CI and the VS Code Lint tasks.
# Local pre-commit: language formatting and style only (no Docker).
# Full lint runs in CI and the VS Code Lint tasks.

# .NET: CSharpier + dotnet format style via Husky.Net. A Python repo runs ruff here instead.
# .NET: CSharpier + dotnet format style via Husky.Net.
# A Python repo runs ruff here instead.
if command -v dotnet >/dev/null 2>&1; then
dotnet husky run
fi
26 changes: 13 additions & 13 deletions catalog/snippets/vscode/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# VS Code Workspace Catalog

The shared `.code-workspace` set for the fleet: the standard extensions every repo recommends, the language-specific additions, and the settings that go with them. Each piece is a copyable JSON fragment in this directory - `base.jsonc` for the standard set, and `dotnet.jsonc`, `python.jsonc`, `docker.jsonc` for the per-type additions. A repo's `<Repo>.code-workspace` composes `base.jsonc` plus the fragments for the languages and targets it ships. Discovered from the fleet's workspace files.
The shared `.code-workspace` set for the fleet: the standard extensions every repo recommends, the language-specific additions, and the settings that go with them. Each piece is a copyable JSON fragment in this directory. `base.jsonc` carries the standard set, and `dotnet.jsonc`, `python.jsonc`, and `docker.jsonc` carry the per-type additions. A repo's `<Repo>.code-workspace` composes `base.jsonc` plus the fragments for the languages and targets it ships. Discovered from the fleet's workspace files.

## Standard Extensions (every repo)

- `davidanson.vscode-markdownlint` - Markdown lint, sharing `.markdownlint-cli2.jsonc`.
- `streetsidesoftware.code-spell-checker` - cSpell, sharing `cspell.json`.
- `editorconfig.editorconfig` - applies `.editorconfig`.
- `yzhang.markdown-all-in-one` - Markdown editing and the auto-generated Table of Contents.
- `fanaticpythoner.better-todo-tree` - surfaces TODO/FIXME markers.
- `github.vscode-github-actions` - GitHub Actions authoring.
- `arahata.linter-actionlint` - actionlint for workflow YAML.
- `timonwong.shellcheck` - shellcheck for shell scripts.
- `anthropic.claude-code` - the coding agent.
- **`davidanson.vscode-markdownlint`** - Markdown lint, sharing `.markdownlint-cli2.jsonc`.
- **`streetsidesoftware.code-spell-checker`** - cSpell, sharing `cspell.json`.
- **`editorconfig.editorconfig`** - applies `.editorconfig`.
- **`yzhang.markdown-all-in-one`** - Markdown editing and the auto-generated Table of Contents.
- **`fanaticpythoner.better-todo-tree`** - surfaces TODO/FIXME markers.
- **`github.vscode-github-actions`** - GitHub Actions authoring.
- **`arahata.linter-actionlint`** - actionlint for workflow YAML.
- **`timonwong.shellcheck`** - shellcheck for shell scripts.
- **`anthropic.claude-code`** - the coding agent.

## Language and Target Additions

Expand All @@ -22,10 +22,10 @@ The shared `.code-workspace` set for the fleet: the standard extensions every re

## Settings

- **Table of Contents**: `"markdown.extension.toc.levels": "2..3"` - the Markdown All in One extension includes H2 and H3 headings and updates the TOC on save.
- **Format on save** per language: C# via `csharpier.csharpier-vscode`; Python via `charliermarsh.ruff` with import organization.
- **Table of Contents**: `"markdown.extension.toc.levels": "2..3"`. The Markdown All in One extension includes H2 and H3 headings and updates the TOC on save.
- **Format on save** per language: C# via `csharpier.csharpier-vscode`, Python via `charliermarsh.ruff` with import organization.
- **cSpell and markdownlint** read the repo's `cspell.json` and `.markdownlint-cli2.jsonc` (linter parity).
- Trim trailing whitespace except in Markdown and plaintext; sign off commits (`git.alwaysSignOff`).
- Trim trailing whitespace except in Markdown and plaintext, and sign off commits (`git.alwaysSignOff`).

## Composing a Workspace

Expand Down
14 changes: 7 additions & 7 deletions catalog/snippets/workflows/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Workflow snippets

The reusable build/publish workflow tasks a code-shipping repo runs. They are **inert reference here** - this repo is source-only and keeps just the orchestrator set (`test-pull-request`, `publish-release`, `validate-task`, `merge-bot-pull-request`) in `.github/workflows/`. Each file below is the canonical implementation of one or more `WORKFLOW.md` guarantees; the audit asserts a downstream repo's own Actions satisfy those guarantees, not that they match these bytes.
The reusable build/publish workflow tasks a code-shipping repo runs. They are **inert reference here**: this repo is source-only and keeps just the orchestrator set (`test-pull-request`, `publish-release`, `validate-task`, `merge-bot-pull-request`) in `.github/workflows/`. Each file below is the canonical implementation of one or more `WORKFLOW.md` guarantees. The audit asserts a downstream repo's own Actions satisfy those guarantees, not that they match these bytes.

| File | Role | WORKFLOW.md guarantees |
| --- | --- | --- |
| `build-release-task.yml` | Multi-target release orchestrator: get-version, validate-release, github-release plus per-target build jobs | D3, D4, D5, D6 |
| `get-version-task.yml` | NBGV version/tag computation (reusable) | D3 |
| `publish-plan-task.yml` | Single-source release-gate decision (publish? stable?) reused by every publish-release job | D4 |
| `build-executable-task.yml` | Console/executable per-runtime publish, aggregate to one release asset | D5, D6; section 6 Console walkthrough |
| `build-nugetlibrary-task.yml` | Build + `dotnet nuget push` (OIDC), upload release asset | D3.4, D4.4, D6; section 6 NuGet walkthrough |
| `build-pypilibrary-task.yml` | Build PyPI package; publish split to an OIDC job | D3.4, D4, D7.2; section 6 PyPI walkthrough |
| `build-docker-task.yml` | Multi-arch image build + push, registry layer cache | D4.4, D6, D9.4; section 6 Docker walkthrough |
| `build-datebadge-task.yml` | BYOB date/last-build badge on the default branch | D4; section 3 Release Model |
| `publish-docker-readme-task.yml` | Push the size-limited Docker Hub overview | D2.4; section 6 Docker walkthrough |
| `build-executable-task.yml` | Console/executable per-runtime publish, aggregate to one release asset | D5, D6, and section 6 Console walkthrough |
| `build-nugetlibrary-task.yml` | Build + `dotnet nuget push` (OIDC), upload release asset | D3.4, D4.4, D6, and section 6 NuGet walkthrough |
| `build-pypilibrary-task.yml` | Build PyPI package, with publishing split to an OIDC job | D3.4, D4, D7.2, and section 6 PyPI walkthrough |
| `build-docker-task.yml` | Multi-arch image build + push, registry layer cache | D4.4, D6, D9.4, and section 6 Docker walkthrough |
| `build-datebadge-task.yml` | BYOB date/last-build badge on the default branch | D4, and section 3 Release Model |
| `publish-docker-readme-task.yml` | Push the size-limited Docker Hub overview | D2.4, and section 6 Docker walkthrough |
| `deploy-site.yml` | Dispatch entry point for a site deploy: environment choice, per-environment concurrency, ref gate, shared validation | D2.1, D2.3, D7.1 |
| `deploy-site-task.yml` | Build a site and ship it to a filesystem on a host the project owns, then verify against the running host | D4.6, D5.6, D7.2 (section 6 static-site walkthrough) |
| `check-upstream-version-task.yml` | Upstream-version tracker for wrapper repos | D3.5, D8.3 |
Expand Down
4 changes: 2 additions & 2 deletions catalog/snippets/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Build BYOB date badge task

# Caller-gated: the publisher invokes this only when main is published - the badge has no per-branch context, it tracks
# the last main build.
# Caller-gated: the publisher invokes this only when main is published.
# The badge has no per-branch context, so it tracks the last main build.

on:
workflow_call:
Expand Down
Loading
Loading