Create nwarila/python-template and nwarila/.github as the two-layer
standard for every Python repository in the organization.
.githubowns organization-wide governance, repository policy, workflow templates, and non-language-specific automation.python-templateowns the Python-specific developer experience, quality gates, reusable workflows, and reference configuration.- Each downstream repository owns its domain logic, package metadata, product-specific workflows, and documentation.
This is also a portfolio asset. The consistency, polish, and rigor across the organization should be visible to recruiters before they read any source code.
- Any engineer can move between Python repos and find the same setup flow, the same QA commands, and the same CI contract.
- Local development, pre-commit, and CI all execute the same underlying checks.
- New repos can be brought to a "green" baseline quickly, with minimal custom glue.
- Shared standard changes arrive in downstream repos as reviewable PRs, not as hidden drift.
- Exceptions are explicit, versioned, and rare.
- Recruiters see stable checks, coverage visibility, clean READMEs, and intentional engineering standards everywhere.
pyproject.tomlis the center of gravity for package metadata and tool configuration.- Local must match CI. If the behavior differs, the template is wrong.
- Cross-platform support is a first-class requirement: Windows, macOS, and Linux must all be considered during design, not patched later.
- The standard should be opinionated by default, but have documented and auditable escape hatches.
- Template-owned files should stay small, generic, and stable. Repo-specific concerns stay in the repo.
- One stable required status check should represent "the Python bar was met."
- Visible quality is part of product quality.
| Layer | Owns | Examples |
|---|---|---|
.github |
Org-wide governance and workflow entry points | Community health files, issue/PR templates, ruleset guidance, workflow templates, dependency-review enforcement, markdown/action lint, link checking |
python-template |
Python-specific standard | Reusable Python QA workflow, setup action, QA scripts, reference pyproject.toml, pre-commit config, VSCode defaults, packaging/security/type/test policy |
| Downstream repo | Product-specific behavior | Package metadata, runtime dependencies, README, release workflow, deploy workflow, repo-specific tasks, repo-specific ignore rules |
The repository is still partway through extraction from the resume project.
- The checked-in scripts still assume they live under
.github/scripts, while the repo now stores them underscripts/. - Multiple scripts and reference files still hardcode resume-specific package names, paths, build flows, and artifacts.
- Only
actions/setup-pythonexists today; the rest of the proposed shared CI surface is still aspirational. - The current plan assumes external composite actions will power CI, but that conflicts with the stated goal that local and CI should run the exact same downstream scripts.
- Ownership boundaries for syncable files versus repo-owned files are not yet crisp enough to prevent merge friction.
This revision tightens the architecture so the implementation backlog matches the quality bar we are trying to set.
Use reusable workflows for centrally maintained CI orchestration, and keep composite actions small and tactical.
Why this is the better fit:
- GitHub's current documentation distinguishes reusable workflows from composite actions: reusable workflows can contain multiple jobs, can use secrets, and preserve step-level logging.
- GitHub also documents that when a reusable workflow in another repository
uses
actions/checkout, it checks out the caller repository, not the called repository. That means a centrally maintained workflow can still execute the synced local scripts in the downstream repo. - Composite actions still have value for small step bundles such as environment bootstrap, but they should not be the main abstraction for our full CI contract.
python-templateships the canonicalscripts/implementation.- Downstream repos sync the template-owned script/config files into their own repository.
- Local development runs those synced scripts directly.
- CI in downstream repos calls a reusable workflow from
python-template. - That reusable workflow checks out the downstream repo and runs the same synced scripts that developers use locally.
- Branch protection or rulesets target a single stable
ci-passedcheck.
nwarila/python-template
├── .github/
│ ├── actions/
│ │ └── setup-python/
│ │ └── action.yml # Small shared bootstrap action
│ ├── scripts/ # Released script copies for self-dogfooding
│ │ ├── .version # Tracks which release these scripts came from
│ │ └── (mirrors scripts/)
│ └── workflows/
│ ├── auto-release.yml # Auto-creates a release when scripts/ changes
│ ├── python-qa.yml # Reusable workflow for downstream repos
│ ├── self-update.yml # Nightly: pulls released files from latest release
│ └── template-ci.yml # This repo's own CI (uses .github/scripts/)
├── scripts/
│ ├── check_lint.py
│ ├── check_types.py
│ ├── check_tests.py
│ ├── check_security.py
│ ├── check_spelling.py
│ ├── check_package.py
│ ├── qa.py
│ ├── sync.py
│ ├── setup.sh
│ └── setup.ps1
├── reference/
│ ├── pyproject.toml
│ ├── pre-commit-config.yaml
│ ├── markdownlint-cli2.jsonc
│ ├── tasks.json
│ ├── settings.json
│ ├── extensions.json
│ ├── gitignore
│ ├── gitattributes
│ └── repo-ci.yml
├── sync-manifest.json # Source→dest mappings read by downstream sync workflows
└── README.md
Every Python repo that adopts the standard should have:
pyproject.toml- A declared Python support range
- A clear source layout
- A test location
- An org-standard
.gitignorethat starts with**and explicitly allowlists tracked roots - An org-standard
.gitattributesbaseline aligned with.github - Synced
.github/scripts/frompython-template - A
.pre-commit-config.yaml - A CI workflow that delegates to the shared reusable workflow or mirrors its contract exactly
V1 should optimize for repos that have importable Python code under src/.
This includes libraries, CLIs, internal apps, and most automation projects.
Script-only repos are still in scope, but should initially be supported via an explicit opt-out of the packaging gate rather than through a separate script- first architecture.
No custom config namespace. Scripts infer behavior from standard pyproject.toml
sections that repo admins already control:
| Script behavior | Inferred from | Default when absent |
|---|---|---|
| Source paths for lint/types | [tool.ruff] src |
["src"] |
| Test paths | [tool.pytest.ini_options] testpaths |
["tests"] |
| Coverage threshold | [tool.pytest.ini_options] --cov-fail-under |
90 |
| Strict typing | [tool.mypy] strict |
true |
| Run package check | [build-system] section exists |
Skip if absent |
| Smoke entry points | [project.scripts] section exists |
Skip if absent |
| Codespell ignore list | [tool.codespell] ignore-words-list |
Empty |
CI matrix dimensions (Python versions, OS coverage) are controlled via reusable workflow inputs — not pyproject.toml, since they are CI concerns.
Local overrides use CLI arguments: qa.py --skip package or
check_lint.py --fix. Repo admins control their quality bar through the
standard tool config sections they already maintain.
The template should align its defaults to CPython's upstream support policy, not to habit.
- New repos should not launch on a minimum Python version with less than 12 months of upstream support remaining.
- As of April 7, 2026, CPython 3.10 is already in security-fix-only support
and reaches end-of-life in October 2026, so the default floor for new repos
should remain
>=3.11. - Template defaults should be reviewed after each annual CPython feature release and ratcheted forward intentionally, not ad hoc.
- Scheduled or non-required CI may include prerelease interpreters, but the required branch-protection matrix should target supported stable versions.
| Concern | Standard direction | Notes |
|---|---|---|
| Project metadata | pyproject.toml with [build-system], [project], and [tool.*] |
Keep package metadata and QA config centralized |
| Source layout | Prefer src/ for importable code |
Reduces accidental imports from the repo root |
| Lint + format | Ruff owns both | Avoid parallel Black/isort/Flake8 duplication |
| Type checking | Mypy baseline, strict by default for new repos | Legacy repos may adopt in stages, but must ratchet upward |
| Tests | pytest with explicit testpaths and --import-mode=importlib for new repos |
Keeps import behavior closer to installed reality |
| Coverage | Enforced threshold plus Actions job summary | New repos default to 90% |
| Security | pip-audit in CI plus GitHub dependency review on PRs |
CodeQL belongs in .github policy, not a V1 blocker |
| Packaging | validate-pyproject, build wheel/sdist, twine check, optional entry-point smoke |
Auto-enabled when [build-system] exists |
| Spelling | codespell |
Repo-local [tool.codespell] controls ignore list |
| Hooks | pre-commit with pre-commit and pre-push installation |
CI remains the source of truth |
| Editor DX | Shared VSCode settings/extensions/tasks where practical | Generic only; no repo-specific build logic in template-owned files |
The org-standard ruff rule selection:
[tool.ruff]
target-version = "py311"
line-length = 120
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"W", # pycodestyle warnings
"I", # isort (import ordering)
"UP", # pyupgrade (modernize syntax)
"B", # flake8-bugbear (common bug patterns)
"S", # flake8-bandit (security)
"SIM", # flake8-simplify
"C4", # flake8-comprehensions (cleaner comprehensions)
"PT", # flake8-pytest-style (pytest best practices)
"T20", # flake8-print (no print statements in library code)
"RUF", # ruff-specific rules
]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"] # assert is fine in tests
"scripts/**" = ["T20"] # print is fine in QA scriptsRationale: E/F/W are baseline correctness. I ensures consistent
imports (ruff replaces isort). UP keeps code modern. B catches real bugs.
S aligns with the security-first philosophy. SIM/C4 enforce clean
idiomatic Python. PT standardizes pytest usage. T20 prevents debug prints
from reaching library code (QA scripts are excluded). RUF catches
ruff-specific issues. line-length = 120 balances readability with modern
wide displays.
The standard distinguishes greenfield from legacy adoption:
- New repos: default to
strict = truein[tool.mypy]and--cov-fail-under=90in[tool.pytest.ini_options]. - Existing repos: start from their real baseline if needed, but never lower the threshold after adopting the standard.
- Any temporary waiver must be explicit, visible, and time-bounded.
Official docs strongly support pyproject.toml, src/ layout, Ruff, reusable
workflows, dependency review, and pre-commit. They also suggest that uv is
mature enough to pilot as the default developer toolchain, but not yet so
uniformly supported across the ecosystem that we should declare it final
without first validating it in pilot repos.
- Use
uvas the primary environment and dependency workflow. - Keep
pyproject.tomlas the metadata source of truth. - Use
[dependency-groups]for local development groups ifuvis ratified. - Commit
uv.lockfor deterministic installs. - Use wrapper scripts and VSCode tasks so developers interact with a stable repo interface, not a moving tool CLI.
uvnow has official guidance for GitHub Actions, lockfiles, dependency groups, and dependency-bot integration.- It reduces tool sprawl while still allowing export to
requirements.txt,pylock.toml, and CycloneDX when needed. - It supports Windows, macOS, and Linux, which fits the org standard.
- The
uvdocs explicitly note that dependency groups are standardized but not yet supported by all tools. - Dependabot support exists, but Astral's own docs still call out incomplete scenarios.
- We should prove the workflow in at least two real repos before making it the mandatory baseline.
uv looks strong as a developer workflow, but GitHub's current dependency-
graph documentation still describes Python support primarily around
requirements.txt, pipenv, and Poetry-style manifests. Before uv becomes
mandatory, pilot repos need to prove that the GitHub-native security surface is
still good enough:
- Dependency graph visibility
- Dependabot alerts and updates
- Dependency review on pull requests
- Any needed lockfile or dependency-submission compatibility workarounds
If the pilot exposes a gap, the standard should add a compensating mechanism instead of hand-waving the gap away.
These should be auto-updated by release-triggered PRs:
scripts/**.pre-commit-config.yaml.markdownlint-cli2.jsonc.vscode/settings.json.vscode/extensions.json
These are synced by release-triggered PRs, but use region-delimited sections to preserve repo-specific content:
.vscode/tasks.json— template-owned QA regions are replaced; repo-specific task regions are preserved
These define mandatory org-standard starting points for new repos, but are not auto-overwritten after initial creation in V1 because repo-specific extensions still need explicit review:
reference/pyproject.tomlreference/repo-ci.ymlreference/gitignore- starts with**and uses an explicit allowlist modelreference/gitattributes- comment-rich normalization and diff baseline aligned with.github
These always remain local to the downstream repository:
README.mdLICENSE- Package name, description, runtime dependencies, and entry points
- Deployment and release workflows
- Product-specific VSCode tasks
- Repo-specific tracked-root allowlist additions in
.gitignore - Repo-specific binary or file-type additions in
.gitattributes
- Every sync PR must reference the source template release tag.
- Every sync PR must include migration notes when behavior changes.
- Every synced file should carry a lightweight "managed by template" header comment where the file format allows it.
- High-churn files should remain reference-only unless we build a safe marker-preserving merge strategy.
- Sync automation should read from a machine-readable manifest
(for example
sync-manifest.json) that defines source path, destination path, ownership mode, and merge strategy. The workflow should never rely on a hardcoded path list buried in implementation code.
The .github repository should provide:
- Workflow templates that new repos can select from the GitHub UI
- Workflow-template metadata files (
.properties.json) and$default-branchplaceholders where appropriate - Issue and PR templates
- Community health files
- Ruleset guidance or enforcement for required workflows
- Shared non-language checks such as markdown lint, action lint, shell lint, and link checking
- Dependency review as an org-level required workflow where appropriate
python-template should provide:
- A reusable
python-qa.ymlworkflow that runs against the caller repo - A stable
ci-passedaggregator job - A small bootstrap action for Python environment setup if still useful
- The synced local scripts that implement the actual checks
The template repo eats its own dog food by running CI against released scripts rather than the development source. This ensures that new script changes are validated by the same mechanism downstream repos use.
How it works:
scripts/is the development source for all check scripts and setup scripts.- When changes to
scripts/are merged tomain,auto-release.ymlautomatically creates a new patch release (auto-incrementing from the latest tag). - A nightly scheduled workflow (
self-update.yml) checks whether a new release exists. When it detects one, it pulls the released files via the same manifest-driven sync that downstream repos use, and opens a PR. template-ci.ymlruns all quality gates from.github/scripts/— the released copies — not fromscripts/directly.
Why this matters:
- The template validates itself using the same artifacts and sync mechanism it ships to consumers.
- Script regressions are caught before downstream repos receive them.
- The release-and-pull pipeline is exercised continuously, not only at manually triggered milestones.
Bootstrap: .github/scripts/ is initially seeded from the current
scripts/ directory. After the first release and nightly cycle, it is kept
in sync automatically.
- PR workflow: required, comprehensive, and still expected to stay fast for this organization's current small repositories
- Main-branch or scheduled workflow: broader compatibility matrix when needed
- One required
ci-passedcheck name across repos
Current default:
- PRs: 3 operating systems x min/max supported Python versions
- Main or scheduled: keep the same baseline, and optionally add prerelease or extended compatibility jobs as non-required checks
This is intentionally stricter than a typical "Ubuntu-only on PRs" baseline. For this portfolio, the repos are small enough that the matrix cost is acceptable and the cross-platform signal is part of the value proposition.
- Pin third-party GitHub Actions to full-length commit SHAs.
- Internal
python-templatereusable workflow references may use@v1as the semver contract. - Required job names must be unique across workflows to avoid ambiguous branch protection behavior.
- Use Dependabot version updates for GitHub Actions and reusable workflow references so SHA-pinned dependencies still move forward deliberately.
- Do not rely on GitHub security alerts alone for SHA-pinned actions; GitHub's dependency-graph docs explicitly scope action alerts to semantic-versioned refs rather than SHA pins.
Rulesets should be treated as the governance primitive for the organization.
- Use repository-level rulesets everywhere they are available.
- Use organization-wide rulesets when the GitHub plan supports them; otherwise
document a repo-level baseline in
.githuband apply it consistently. - The default protected-branch baseline should include:
- Require a pull request before merging
- Require status checks to pass before merging
- Require linear history
- Block force pushes
- Require code scanning results where CodeQL is enabled
- Require dependency review where the workflow exists
- Evaluate required signed commits separately after confirming bot, release, and sync-automation compatibility.
For published artifacts, the standard should leave room for stronger supply- chain signals than pass/fail CI alone.
- Release workflows should be designed so SBOM export and provenance attestations can be added cleanly.
- GitHub's attestation guidance makes reusable workflows especially valuable: attestations alone provide SLSA Build Level 2, and shared reusable build workflows help move toward Build Level 3.
- This does not need to block V1 for every repo, but the architecture should not paint us into a corner.
python-template is itself a product and needs a stable upgrade contract.
- The template follows semantic versioning.
- Breaking changes ship only in major releases.
- Tightening a default quality gate, changing a managed file's shape, removing a synced file, or changing script/workflow interfaces counts as a breaking change unless explicitly backward-compatible.
- Deprecations must be announced at least one minor release before removal.
- Every release must include migration notes, managed-file impact, and any required downstream action.
- Downstream repos may use
@v1for normal consumption, but exact release tags should remain easy to reference for investigations and rollback.
- Finalize the architecture shift to reusable workflows for CI
- Codify
uvas the pilot default and document thepip+venvfallback path - Document the inference rules: how scripts derive behavior from standard
pyproject.tomlsections (no custom config namespace) - Define the Python support policy relative to the CPython lifecycle
- Decide which files are sync-managed versus reference-only
- Decide the ruleset baseline and repo-level fallback if org-wide rulesets are not available on the current GitHub plan
- Codify the default PR matrix and 90% greenfield coverage floor in the reference config and docs
Exit criteria:
- The plan is internally consistent
- The repo contract is documented
- No major ownership ambiguity remains
- Establish the inline config-reading pattern (stdlib
tomllib, read[tool.ruff],[tool.mypy],[tool.pytest.ini_options], and[build-system]sections) that each script will duplicate independently — no shared module - Remove every
.github/scriptspath assumption from the scripts - Remove every resume-specific path, package name, and CLI assumption
- Standardize script CLI contracts (
--fix,--paths,--skip, config lookup, clean exit codes) - Make
qa.pyauto-discovercheck_*.pyscripts and honor repo profile opt-outs - Teach
check_package.pyto discover[project.scripts]and only run entry-point smoke tests when appropriate - Add coverage summary output to
$GITHUB_STEP_SUMMARY
Exit criteria:
- A minimal smoke project can run the scripts locally on Windows, macOS, and Linux
- No checked-in script contains resume-specific references
- The script interface is documented and stable
- Add
.github/workflows/python-qa.ymlas the reusable downstream QA workflow - Add
.github/workflows/template-ci.ymlto dogfood the template itself - Decide whether
actions/setup-pythonremains the bootstrap action or is replaced by a more generalsetup-project - Add the machine-readable sync manifest and the reference template-sync workflow for downstream repos
- Add
qa-gatebehavior as a stable aggregator job - Pin all third-party actions to full-length commit SHAs
- Add dependency review to the template repo's own PR workflow
- Add Dependabot version updates for GitHub Actions and reusable workflow references
- Decide whether SBOM export and provenance attestations land in V1 or immediately after V1
Exit criteria:
- A downstream repo can call the reusable workflow and still execute its own synced scripts
- The template repo enforces the same standards it asks others to adopt
- Replace
reference/pyproject.tomlwith a generic baseline - Restructure
reference/tasks.jsonwith region-delimited template-owned sections (setup, QA) and a clearly marked repo-specific region - Replace
reference/settings.jsonandreference/extensions.jsonwith Python-generic defaults - Strip resume-specific content from
.gitignore,.gitattributes, and workflow examples - Remove
reference/build-resume.yml,reference/build-resumes-action.yml, andreference/release.yml - Ensure
reference/pre-commit-config.yamlmatches the chosen toolchain - Ensure
reference/pyproject.tomlincludes--import-mode=importlibin pytest config andtestpaths = ["tests"]as recommended defaults
Exit criteria:
- The reference directory can seed a new Python repo without leaking unrelated project assumptions
- Every file in
reference/is either generic or intentionally marked as future work
- Lint and format all Python scripts
- Type-check all Python scripts
- Validate reusable workflow YAML and action metadata
- Markdown-lint the documentation
- Smoke-test the scripts against a generated minimal project
- Integration-test the reusable workflow against a sample caller workflow
Exit criteria:
python-templateis fully dogfooding itself- The test suite validates both local and CI execution paths
- Write a polished
README.mdwith quick start, architecture, adoption guide, and migration guide - Publish release notes that clearly distinguish breaking versus non- breaking changes
- Add workflow templates in
.githubthat call the shared reusable workflow, including the required.properties.jsonmetadata files - Pilot the standard in
nwarila/resume - Pilot the standard in at least one additional Python repo with a different profile
- Cut
v1.0.0and maintain the floatingv1tag
Exit criteria:
- Two real repos have adopted the standard successfully
- The docs are good enough that a future repo can onboard without tribal knowledge
- The release and upgrade contract is proven
V1 is complete when all of the following are true:
- No template-owned file contains resume-specific logic or naming
- The script contract is stable and documented
- The reusable workflow runs the same downstream scripts that local developers run
- The template repo passes its own full QA suite
- At least two pilot repos have adopted the standard successfully
- The org has a clear rule for required checks, dependency review, and workflow templates
- The Python support policy is documented and avoids near-EOL interpreter defaults
- Sync automation is manifest-driven and reviewable
- The release contract (
v1.0.0plus floatingv1) is documented and used
-
uvadoption risk Mitigation: treatuvas a pilot default until two repos prove the workflow. -
uvplus GitHub dependency-graph visibility gap Mitigation: validate alerts, dependency review, and graph visibility during pilot; add dependency submission or compatibility artifacts if needed. -
Template drift versus repo customization Mitigation: keep high-conflict files reference-only until we have a safe merge strategy.
-
CI cost and slowness Mitigation: separate required PR coverage from broader scheduled or main-branch compatibility testing.
-
Strict typing friction in legacy repos Mitigation: allow staged adoption, but require ratcheting and visible waivers.
-
Ambiguous required checks in GitHub Mitigation: keep job names unique and route branch protection through one stable
ci-passedcheck. -
Ruleset capability differs by GitHub plan Mitigation: standardize the baseline behavior first, then implement it via org-wide rulesets where available and repo-level rulesets where necessary.
-
Standards becoming performative instead of useful Mitigation: keep local setup simple, logs readable, and failure messages actionable.
Each check_*.py and qa.py must be:
- Fully standalone — no shared module, no cross-script imports. Each script is independently runnable. Duplicating small helper logic (config reading, path resolution) across scripts is acceptable.
- Stdlib-only — scripts use only the Python standard library. They shell
out to tools (
ruff,mypy,pytest, etc.) viasubprocess. This avoids polluting downstream dev dependencies with template infrastructure.
Python was chosen because a single .py file runs on any OS. The scripts are
thin wrappers that read config from pyproject.toml (via tomllib, stdlib
since Python 3.11), resolve paths, invoke the tool, and report results. Each
script that reads pyproject.toml does so inline — the pattern is ~10 lines
and repeating it is cleaner than importing it.
qa.py is the local orchestrator (for VSCode tasks and command-line use). It
auto-discovers check_*.py scripts in its directory and runs them
sequentially. It infers which checks to run from pyproject.toml:
- If
[build-system]is absent,check_package.pyis skipped - If
[project.scripts]is absent, entry-point smoke tests are skipped - CLI
--skip=<check>overrides for ad-hoc local runs (e.g.,--skip package)
qa.py is not used in CI. The reusable workflow runs each check as a
separate job for better Actions UI presentation.
check_tests.py needs to write a coverage table to $GITHUB_STEP_SUMMARY.
Since scripts are stdlib-only, the approach is:
- Run pytest with
--cov-report=json:coverage.json --cov-report=term - Parse
coverage.jsonwith stdlibjsonmodule - Write a markdown summary table to
$GITHUB_STEP_SUMMARY(only whenGITHUB_ACTIONS=true) - Clean up
coverage.jsonafter processing
Pre-commit hooks call tools directly (not wrapper scripts) because pre-commit
manages its own venvs per hook. pyproject.toml is the convergence point —
both hooks and scripts read the same [tool.ruff], [tool.mypy], and
[tool.codespell] config sections. The scripts add orchestration, summary
reporting, annotations, and $GITHUB_STEP_SUMMARY output that hooks don't
need.
The ci-passed job lives inside the reusable python-qa.yml workflow, not as
a separate action. The reusable workflow owns the full contract: it runs all
check jobs and includes a final ci-passed job that if: always() evaluates
all upstream results. Downstream branch protection targets this single job name.
The python-qa.yml reusable workflow runs each check as an independent job
so that every gate gets its own status icon, collapsible log section, and
pass/fail indicator in the PR checks UI. This maximizes reviewer clarity.
Jobs in the reusable workflow:
lint— runscheck_lint.pyacross the matrixtypes— runscheck_types.pyacross the matrixtests— runscheck_tests.pyacross the matrix (writes coverage summary)security— runscheck_security.py(single OS is sufficient)spelling— runscheck_spelling.py(single OS is sufficient)package— runscheck_package.py(conditional, single OS)ci-passed— aggregator,if: always(), evaluates all upstream results
Each matrix job runs setup-python independently (jobs don't share state). For small repos this overhead is negligible and the UI benefit is worth it.
Workflow inputs:
| Input | Default | Purpose |
|---|---|---|
python-min |
"3.11" |
Minimum Python version for matrix |
python-max |
"3.14" |
Maximum Python version for matrix |
full-os-matrix |
true |
Whether to run all 3 OS or Ubuntu-only |
run-package-check |
true |
Whether to run the packaging gate |
All quality-gate configuration (coverage threshold, strict typing, codespell
ignores) is read from the caller repo's pyproject.toml by the scripts at
runtime. The workflow interface stays stable even as the check contract evolves.
setup.sh and setup.ps1 need to handle both toolchains. The decision logic:
- If
uv.lockexists in the project root, useuv - Otherwise, fall back to
python -m venv+pip
This is file-presence detection, not configuration — a repo that commits
uv.lock opts into uv automatically. The check scripts don't care which
path was taken; they run against the activated venv regardless.
Sync is pull-based. Each downstream repo owns a template-sync.yml
workflow that pulls released files from nwarila/python-template. The template
publishes releases; downstream repos pull when ready. No cross-repo credentials,
no push permissions, no coupling.
Each downstream repo's sync workflow:
- Checks for the latest release on
nwarila/python-template(or accepts a manual tag input) - Clones the template at the release tag
- Runs
scripts/sync.pyfrom the template clone, which readssync-manifest.jsonfor file mappings, copies fully-managed files, and runs marker-preserving merge for files liketasks.json(// #regionmarkers delimit template-owned vs repo-owned sections) - Opens a PR via
gh pr createusing the repo's ownGITHUB_TOKEN
self-update.yml supports workflow_call, so downstream repos call it as a
reusable workflow via uses: nwarila/python-template/.github/workflows/self-update.yml@v1
from a thin wrapper with their own schedule trigger.
sync-manifest.json schema:
{
"files": [
{ "src": "scripts/check_lint.py", "dest": ".github/scripts/check_lint.py", "mode": "overwrite" },
{ "src": "reference/pre-commit-config.yaml", "dest": ".pre-commit-config.yaml", "mode": "overwrite" },
{ "src": "reference/tasks.json", "dest": ".vscode/tasks.json", "mode": "marker-preserve" }
]
}Synced files carry a header comment identifying their source:
# Managed by nwarila/python-template — do not edit manually.
# Source: https://github.com/nwarila/python-template
# Version: v1.2.3For JSONC files (.vscode/settings.json, etc.), use a JSONC comment at the
top of the file in the same format.
The org-standard reference/settings.json includes only universal settings:
Changes from current reference: rulers move from 96/98 to 120 (matching
line-length). Resume-specific exclusions (output/, data/, etc.) are
removed — those belong in the downstream repo's own settings if needed.
.github enforces dependency review org-wide via rulesets or required
workflows. python-template also includes dependency review in its own
template-ci.yml for self-validation. These are complementary, not
conflicting — the template dogfoods what the org requires.
-
uvbecomes mandatory after pilot. Scripts are venv-agnostic — they run against an activated environment regardless of how it was created. Onlysetup.sh/setup.ps1and the lock mechanism change. After two repos prove theuvworkflow, it becomes the default for new repos. A documented fallback topip+venvremains available. -
Greenfield repos default to 90% coverage. Achievable when tests are written alongside code from day one, especially for the low-complexity, single-function repos in this portfolio. Legacy repos adopt via ratchet-up, never lowering the threshold after adoption.
-
tasks.jsonuses marker-preserving sync. Template-owned regions (delimited by// #regioncomments) are replaced by the sync PR. Repo-specific tasks live in their own regions outside the managed blocks. This preserves the "identical QA experience" promise while allowing repo-specific build tasks. -
Full 3-OS × min/max Python matrix on every PR. Cross-platform-first is a stated principle, the repos are small enough that 6 jobs are fast, and the green matrix grid is a visible quality signal. Controlled by the reusable workflow's
full-os-matrixinput (defaulttrue) so repos can opt into a leaner matrix if needed. -
Dependabot, not Renovate. Native to GitHub, zero extra setup, already established in
.githubrepo for Actions updates. Simpler and more "native" in a GitHub-centric portfolio. If Dependabot'suvsupport has gaps during pilot, that's useful signal for theuvdecision itself. -
CodeQL lands now, owned by
.github. Independent ofpython-templateV1 — it's a workflow template in.github, not a python-template concern. Adds security tab visibility and the "Code scanning" badge immediately. -
qa.pyruns checks sequentially. Simpler output, easier to debug. -
Reference configs live in
reference/directory on main branch. A separate branch would be harder to discover and maintain. -
No
Makefile.qa.pyis cross-platform, VSCode tasks cover the IDE. -
.gitignoreand.gitattributesfollow org-standard baselines aligned with.github. In V1 they remain reference-managed rather than auto-synced, but every repo starts from the shared templates. The baseline.gitignorebegins with**, and repo-specific tracked roots are added explicitly. -
Coverage summary renders in
$GITHUB_STEP_SUMMARY. Visible quality signal on every workflow run — not just pass/fail, but concrete numbers. -
Mypy uses
strict = truewith pinned version. Behavior stability comes from the pinned mypy version in dev dependencies, not from enumerating individual strict flags. -
Pre-commit hooks call tools directly, not wrapper scripts.
pyproject.tomlis the convergence point for consistent flags. Scripts add orchestration, annotations, and summary output that hooks don't need. -
Scripts are standalone and stdlib-only. No
_common.py, no cross-script imports, no third-party dependencies. Each.pyfile runs independently with just the Python standard library. Python was chosen because one file runs on any OS. -
The default minimum Python version for new repos is 3.11. As of April 7, 2026, Python 3.10 reaches end-of-life in October 2026, so it is too close to retirement to be the default floor for newly created repos.
-
Rulesets are the governance primitive. Use organization-wide rulesets when the GitHub plan supports them; otherwise apply the same baseline with repository-level rulesets and document the fallback in
.github. -
Sync automation is manifest-driven. Source-to-destination mappings, ownership mode, and merge strategy live in a machine-readable manifest, not in workflow code.
-
No custom
[tool.nwarila.template]config. Scripts infer behavior from standard pyproject.toml sections ([build-system],[project.scripts],[tool.mypy],[tool.pytest.ini_options],[tool.ruff]). Repo admins control their quality bar through the tool configs they already maintain. -
Reusable workflow runs each check as a separate job.
qa.pyis the local orchestrator only (for VSCode tasks). CI uses independent jobs per check for better PR reviewer experience — each gate gets its own status icon and collapsible log. -
Ruff rule set:
E/F/W/I/UP/B/S/SIM/C4/PT/T20/RUFwithline-length = 120. Curated for correctness, security, modern idioms, and clean pytest style.T20prevents debug prints in library code (scripts are excluded). 120-char lines balance readability with modern displays. -
VSCode rulers at 120. Matching ruff
line-length. The previous 96/98 rulers were resume-specific and are removed from the org standard. -
Sync is pull-based, not push-based. Each downstream repo owns a thin workflow that calls
self-update.ymlas a reusable workflow. The template publishes releases; consumers pull when ready. No cross-repo credentials, no PATs, no push permissions. The originalsync-downstream.ymlrequired a fine-grained PAT (TEMPLATE_SYNC_PAT) to clone and push to private downstream repos, which was never configured and is bad practice for a personal org. -
PROJECT_ROOT is resolved by walking up to
pyproject.toml, not by assumingSCRIPT_DIR.parent. Scripts can live atscripts/(one level deep) or.github/scripts/(two levels deep). The oldSCRIPT_DIR.parentassumption resolved to.github/when running from the synced location, breaking tool discovery andcwdfor every subprocess call. The walk-up pattern traverses parent directories until it findspyproject.toml, working from any depth. Applied toqa.py,setup.sh, andsetup.ps1.
The resume repo is the first adopter and the original motivation for this template. Here is the concrete migration path:
- Repo-specific
.github/scripts/contents — replaced by synced template-managed.github/scripts/ .github/actions/setup-python/— replaced by the template's action or reusable workflow's built-in setup- Local copies of
check_lint.py,check_types.py, etc. under.github/
| Resume file | Replaced by |
|---|---|
.github/scripts/*.py |
Synced .github/scripts/*.py from template |
.pre-commit-config.yaml |
Synced from template (generic, no python-docx mypy dep) |
.vscode/settings.json |
Synced from template (rulers at 120, no resume-specific exclusions) |
.vscode/extensions.json |
Synced from template |
.vscode/tasks.json |
Synced with marker-preserve (QA regions from template, build regions kept) |
.markdownlint-cli2.jsonc |
Synced from template |
-
.github/workflows/repo-ci.yml— rewrite to call the reusablepython-qa.ymlworkflow frompython-template. The resume-specificbuild-resumesjob stays as a repo-owned job. Structure:jobs: python-qa: uses: nwarila/python-template/.github/workflows/python-qa.yml@v1 with: python-min: "3.11" python-max: "3.12" build-resumes: needs: [python-qa] # ... resume-specific build logic stays here ... # release job stays repo-owned
-
pyproject.toml— update tool config sections to match org standard:- Ruff: add
C4,PT,T20toselect; updatesrcpaths to["src", "tests"](remove.github/scripts) - Pytest: add
--import-mode=importlib, update--cov-fail-under=90 - Mypy: already
strict = true, just verifypython_version - Codespell: keep repo-specific
ignore-words-list - Remove resume-specific
python-docxfrom mypy deps (it's a runtime dep, not a mypy plugin)
- Ruff: add
README.md,LICENSEsrc/,tests/,data/,maps/,templates/.gitignore,.gitattributesremain repo-local files after initial adoption, but should preserve the org-standard structure and extend only in repo-specific sections- Release and build workflows (resume-specific)
pyproject.toml[project]section (package metadata, runtime deps)
- Template V1 is released and tagged
- First sync PR is opened against resume
- Resume CI workflow is rewritten to call reusable workflow
- Resume pyproject.toml tool configs are aligned to org standard
- Resume passes the full quality gate via the reusable workflow
- Old
.github/scripts/and.github/actions/are deleted - Resume README is updated if it references old script paths
These sources directly informed the plan revision.
-
PyPA
pyproject.tomlguide https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ Key takeaways: keep[build-system]present, use[project]for new projects, and centralize tool config in[tool.*]. -
PyPA
srclayout discussion https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/ Key takeaway:src/helps avoid accidental imports from the repo root and better matches installed behavior. -
PyPA dependency groups specification https://packaging.python.org/en/latest/specifications/dependency-groups/ Key takeaway: dependency groups are meant for local development needs and are not included in built package metadata.
-
Python Developer's Guide: status of Python versions https://devguide.python.org/versions/ Key takeaways: as of April 7, 2026, Python 3.10 is in security-only support and reaches end-of-life in October 2026; Python 3.11 remains supported until October 2027, making
>=3.11the more durable floor for new repos. -
uv project and integration docs https://docs.astral.sh/uv/ https://docs.astral.sh/uv/concepts/projects/dependencies/ https://docs.astral.sh/uv/concepts/projects/sync/ https://docs.astral.sh/uv/guides/integration/github/ https://docs.astral.sh/uv/guides/integration/dependabot/ https://docs.astral.sh/uv/guides/integration/renovate/ Key takeaways:
uvnow covers project management, lockfiles, GitHub Actions, dependency groups, and dependency-bot integration; however, dependency-group ecosystem support is still uneven enough to justify a pilot before making it mandatory. -
Ruff formatter docs https://docs.astral.sh/ruff/formatter/ Key takeaway: Ruff is intentionally a unified formatter and linter toolchain, which supports the goal of reducing duplicated Python tooling.
-
pytest good practices https://docs.pytest.org/en/stable/explanation/goodpractices.html Key takeaways: use
pyproject.toml, prefersrc/layout, and use--import-mode=importlibfor new projects. -
mypy command line docs https://mypy.readthedocs.io/en/stable/command_line.html Key takeaway:
--strictenables a changing subset of optional checks, so if we want long-term stability we may eventually prefer explicit strict flags over a barestrict = true. -
pre-commit docs https://pre-commit.com/ Key takeaways:
pre-commit run --all-filesis suitable for CI, anddefault_install_hook_typessupports installing bothpre-commitandpre-pushhooks by default. -
GitHub Actions reusable workflow docs https://docs.github.com/en/actions/concepts/workflows-and-actions/reusing-workflow-configurations https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows Key takeaways: reusable workflows are centrally maintainable, preserve step-level logs, support multiple jobs and secrets, and run actions in the caller context.
-
GitHub workflow-template docs https://docs.github.com/en/actions/how-tos/reuse-automations/create-workflow-templates https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations Key takeaways: organization workflow templates belong in the
.githubrepository, require matching.properties.jsonmetadata files, and support$default-branchplaceholders. -
GitHub secure use guidance https://docs.github.com/en/actions/reference/security/secure-use Key takeaway: third-party actions should be pinned to full-length commit SHAs.
-
GitHub rulesets docs https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets Key takeaways: rulesets are available for public repositories on GitHub Free, organization-wide rulesets depend on plan level, and rulesets can require pull requests, required checks, linear history, signed commits, and code scanning results.
-
GitHub protected-branch and dependency-review docs https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks https://docs.github.com/en/code-security/concepts/supply-chain-security/about-dependency-review Key takeaways: required job names must be unique, required checks must be healthy recently enough to remain selectable, and dependency review can be enforced at scale via rulesets.
-
GitHub dependency-graph and dependency-submission docs https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/dependency-graph-supported-package-ecosystems https://docs.github.com/en/code-security/reference/supply-chain-security/automatic-dependency-submission Key takeaways: GitHub's dependency graph has explicit supported-ecosystem rules, currently lists Python support around pip and Poetry manifests rather than
uv.lock, only generates GitHub Actions alerts for semantic-versioned refs, and can be supplemented with automatic or manual dependency submission when static analysis is incomplete. -
GitHub Dependabot for Actions docs https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot Key takeaway: Dependabot version updates can keep GitHub Actions and reusable workflow references current even when the workflow files pin specific refs.
-
GitHub code-scanning and attestation docs https://docs.github.com/en/enterprise-cloud@latest/code-security/concepts/code-scanning/setup-types https://docs.github.com/en/actions/concepts/security/artifact-attestations Key takeaways: GitHub recommends default CodeQL setup for eligible repos, and reusable workflows combine well with artifact attestations for stronger supply-chain posture.
-
pip-audit project docs https://github.com/pypa/pip-audit Key takeaways:
pip-auditcan scan local environments and lock-style inputs, supports machine-readable output, and remains a good baseline dependency vulnerability gate for Python repos.