diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 583e188..9ae3d34 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -3,6 +3,40 @@ Org-wide CI building blocks. Callable from any `resq-software/*` repo via `uses: resq-software/.github/.github/workflows/.yml@main`. +## `repo-standards.yml` + +Template/standards conformance check. Validates that a repo has a detectable +`LICENSE`, a non-stub `README.md` with a title, and no leftover +`{{PLACEHOLDER}}` tokens or `ResQ README Template` scaffold markers. + +It is already wired into `required.yml` (so every consumer repo inherits it +through the `required` status check) and into `required-gate.yml` (so this +repo dogfoods it). You don't call it directly unless you want a standalone +conformance job. + +**Warn-by-default**, matching the org's audit → enforce pattern +(harden-runner audit, rulesets evaluate). Violations surface as +`::warning` annotations without failing the build. Once a repo is clean, +flip enforcement on by passing `repo-standards-strict: true` to +`required.yml`: + +```yaml +jobs: + ci: + uses: resq-software/.github/.github/workflows/required.yml@main + with: + lang: rust + repo-standards-strict: true # turn conformance violations into a hard failure +``` + +### Inputs (when called directly) + +| Input | Type | Default | Notes | +| :-- | :-- | :-- | :-- | +| `strict` | bool | `false` | Fail the job on any violation. Default warns only. | +| `require-license` | bool | `true` | Require a `LICENSE`/`COPYING` file at the repo root. | +| `readme-min-bytes` | string | `"500"` | README smaller than this is treated as a stub. | + ## `security-scan.yml` Defense-in-depth security scan. All third-party `uses:` refs are SHA-pinned diff --git a/.github/workflows/repo-standards.yml b/.github/workflows/repo-standards.yml new file mode 100644 index 0000000..3266d88 --- /dev/null +++ b/.github/workflows/repo-standards.yml @@ -0,0 +1,124 @@ +# Copyright 2026 ResQ Software +# SPDX-License-Identifier: Apache-2.0 +# +# Reusable repo-conformance check. Validates that a consumer repo meets +# the org template/standards baseline: +# * a detectable LICENSE file (Apache-2.0 across the org) +# * a non-stub README.md with a title and no leftover template tokens +# * no unrendered {{PLACEHOLDER}} tokens or template scaffold markers +# +# Wired into required.yml (consumer repos) and required-gate.yml (this +# repo), so the single `required` status check (gated by org ruleset +# `default-branch-baseline`, id 15191038) also covers template +# conformance. Warn-by-default to match the org's audit -> enforce +# rollout pattern (harden-runner audit, rulesets evaluate). Flip +# `strict: true` once a repo is clean to turn violations into a hard +# CI failure. +# +# Security: inputs that reach `run:` are forwarded through `env:` and +# referenced as "$VAR" to prevent template-time expansion of +# caller-controlled strings into the shell. + +name: repo-standards + +on: + workflow_call: + inputs: + strict: + description: Fail the job on any violation. Default warns (annotations only). + type: boolean + required: false + default: false + require-license: + description: Require a detectable LICENSE/COPYING file at the repo root. + type: boolean + required: false + default: true + readme-min-bytes: + description: Minimum README.md size in bytes; smaller is treated as a stub. + type: string + required: false + default: "500" + +permissions: + contents: read + +jobs: + repo-standards: + name: repo-standards + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2 + with: + egress-policy: audit + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Check repo conformance + env: + STRICT: ${{ inputs.strict }} + REQUIRE_LICENSE: ${{ inputs.require-license }} + README_MIN_BYTES: ${{ inputs.readme-min-bytes }} + run: | + set -eu + violations=0 + note() { + printf '::warning title=repo-standards::%s\n' "$1" + violations=$((violations + 1)) + } + + # ── LICENSE ────────────────────────────────────────────────────── + # POSIX glob loop (not ls/find) so shellcheck stays happy and odd + # filenames are handled safely. Unmatched globs stay literal, so the + # `[ -e ]` test is false for them. + if [ "$REQUIRE_LICENSE" = "true" ]; then + license_found=no + for f in LICENSE LICENSE.* COPYING COPYING.*; do + if [ -e "$f" ]; then license_found=yes; break; fi + done + if [ "$license_found" = yes ]; then + echo "ok: LICENSE present" + else + note "No LICENSE/COPYING file at repo root (org standard: Apache-2.0)." + fi + fi + + # ── README ─────────────────────────────────────────────────────── + if [ ! -f README.md ]; then + note "No README.md at repo root." + else + bytes=$(wc -c < README.md | tr -d ' ') + if [ "$bytes" -lt "$README_MIN_BYTES" ]; then + note "README.md is a stub ($bytes bytes < $README_MIN_BYTES required)." + fi + if ! grep -qE '^[[:space:]]*(#[[:space:]]|')." + fi + # Count DISTINCT {{TOKEN}} occurrences, excluding the literal + # meta-example {{PLACEHOLDER}} (which docs legitimately mention). + # An unrendered template carries many distinct tokens + # ({{PROJECT_NAME}}, {{REPO}}, ...); a doc mentioning the syntax + # once should not trip. Threshold: 3 distinct tokens. + ph_count=$(grep -oE '\{\{[A-Z0-9_]+\}\}' README.md \ + | grep -vxF '{{PLACEHOLDER}}' | sort -u | wc -l | tr -d ' ') + if [ "$ph_count" -ge 3 ]; then + note "README.md still has $ph_count distinct {{PLACEHOLDER}} tokens (unrendered template?)." + fi + if grep -q 'ResQ README Template' README.md; then + note "README.md still contains the template scaffold marker comment." + fi + fi + + # ── Summary ────────────────────────────────────────────────────── + if [ "$violations" -eq 0 ]; then + echo "repo-standards: all checks passed." + exit 0 + fi + echo "repo-standards: $violations violation(s) found." + if [ "$STRICT" = "true" ]; then + echo "::error title=repo-standards::strict mode — failing on $violations violation(s)." + exit 1 + fi + echo "repo-standards: warn mode — not failing the build. Set strict:true to enforce." + exit 0 diff --git a/.github/workflows/required-gate.yml b/.github/workflows/required-gate.yml index 5e03adf..44fa12d 100644 --- a/.github/workflows/required-gate.yml +++ b/.github/workflows/required-gate.yml @@ -28,12 +28,27 @@ concurrency: cancel-in-progress: true jobs: + # Dogfood the org template/standards check on this repo itself. + repo-standards: + name: Repo standards + uses: ./.github/workflows/repo-standards.yml + required: name: required + needs: [repo-standards] + if: always() runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2 with: egress-policy: audit - - run: echo "ok — .github repo has no language CI to gate on" + - name: Evaluate upstream results + env: + REPO_STANDARDS_RESULT: ${{ needs.repo-standards.result }} + run: | + set -eu + case "$REPO_STANDARDS_RESULT" in + success|skipped|"") echo "ok — .github repo has no language CI to gate on" ;; + *) echo "::error::repo-standards returned: $REPO_STANDARDS_RESULT"; exit 1 ;; + esac diff --git a/.github/workflows/required.yml b/.github/workflows/required.yml index ae1c8a9..d8b6a96 100644 --- a/.github/workflows/required.yml +++ b/.github/workflows/required.yml @@ -105,6 +105,15 @@ on: type: string required: false default: "" + repo-standards-strict: + description: > + Fail `required` when the repo violates template/standards + conformance (LICENSE present, non-stub README, no leftover + {{PLACEHOLDER}} tokens). Default warns only — flip to true once + the repo is clean. + type: boolean + required: false + default: false permissions: contents: read @@ -198,10 +207,17 @@ jobs: source-dir: ${{ inputs.cpp-source-dir }} cmake-flags: ${{ inputs.cpp-cmake-flags }} + # Template/standards conformance — lang-independent, runs for every repo. + repo-standards: + name: Repo standards + uses: ./.github/workflows/repo-standards.yml + with: + strict: ${{ inputs.repo-standards-strict }} + # `required` is the single status-check context consumed by Ruleset A. required: name: required - needs: [validate-lang, security, rust, python, node, dotnet, cpp] + needs: [validate-lang, security, rust, python, node, dotnet, cpp, repo-standards] if: always() runs-on: ubuntu-latest steps: @@ -218,6 +234,7 @@ jobs: NODE_RESULT: ${{ needs.node.result }} DOTNET_RESULT: ${{ needs.dotnet.result }} CPP_RESULT: ${{ needs.cpp.result }} + REPO_STANDARDS_RESULT: ${{ needs.repo-standards.result }} run: | set -eu # validate-lang must be success — typo defenses handled there @@ -226,7 +243,7 @@ jobs: exit 1 fi fail=0 - for r in "$SECURITY_RESULT" "$RUST_RESULT" "$PYTHON_RESULT" "$NODE_RESULT" "$DOTNET_RESULT" "$CPP_RESULT"; do + for r in "$SECURITY_RESULT" "$RUST_RESULT" "$PYTHON_RESULT" "$NODE_RESULT" "$DOTNET_RESULT" "$CPP_RESULT" "$REPO_STANDARDS_RESULT"; do case "$r" in success|skipped|"") ;; *) echo "::error::Upstream job returned: $r"; fail=1 ;; diff --git a/README.template.md b/README.template.md index 0265fc4..3e69273 100644 --- a/README.template.md +++ b/README.template.md @@ -83,20 +83,38 @@

+ +

+ Documentation +  ·  + Website +  ·  + Quick Start +  ·  + Report Bug +  ·  + Request Feature +

+ --- ## Table of Contents - [Overview](#overview) +- [Demo](#demo) - [Features](#features) +- [Prerequisites](#prerequisites) - [Install](#install) - [Quick Start](#quick-start) - [Usage](#usage) - [Configuration](#configuration) - [Contributing](#contributing) +- [Security](#security) +- [Support](#support) - [Changelog](#changelog) - [License](#license) +- [Acknowledgements](#acknowledgements) --- @@ -123,9 +141,28 @@ It {{CORE_VALUE_PROPOSITION}}. --- +## Demo + + + +

+ {{PROJECT_NAME}} demo +

+ +> **Try it live:** [{{LIVE_DEMO_URL}}]({{LIVE_DEMO_URL}}) · [Playground](https://resq.software) + +--- + ## Features - + - **{{FEATURE_1}}** — {{FEATURE_1_DESCRIPTION}} - **{{FEATURE_2}}** — {{FEATURE_2_DESCRIPTION}} @@ -133,11 +170,46 @@ It {{CORE_VALUE_PROPOSITION}}. --- +## Prerequisites + + + +| Requirement | Minimum | Notes | +|-------------|---------|-------| +| `{{RUNTIME}}` | `{{MIN_VERSION}}` | {{RUNTIME_NOTE}} | +| Platform | Linux / macOS / Windows | {{PLATFORM_NOTE}} | + +--- + ## Install + + +### ResQ CLI + +Install the [`resq`](https://github.com/resq-software/crates) toolchain (and, +when run inside a repo, the canonical git hooks) in one line: + +```sh +curl -fsSL https://get.resq.software | sh +``` + +Prefer to read before piping to a shell: + +```sh +curl -fsSL https://get.resq.software -o install-resq.sh +less install-resq.sh +sh install-resq.sh +``` + ### Node / Bun ```sh @@ -259,6 +331,30 @@ All PRs must follow the `type(scope): subject` format — see the table below. | `BREAKING CHANGE` footer or `!` suffix | Major bump (`x.0.0`) | | `docs:` `style:` `refactor:` `test:` `chore:` | No version bump | +**CI gate:** every PR must keep the `required` status check green (org +ruleset `default-branch-baseline`). That single check aggregates language +CI, the security scan, and repo-standards conformance — see +[resq-software/.github](https://github.com/resq-software/.github). + +--- + +## Security + +Found a vulnerability? **Do not open a public issue.** Follow the +coordinated-disclosure process in [`SECURITY.md`](./SECURITY.md) (inherited +org-wide from [resq-software/.github](https://github.com/resq-software/.github/blob/main/SECURITY.md)). + +This project ships SHA-pinned CI actions and is scanned for vulnerable +dependencies and leaked secrets on every push. + +--- + +## Support + +- 📖 **Docs:** [docs.resq.software](https://docs.resq.software) +- 💬 **Questions / help:** see [`SUPPORT.md`](./SUPPORT.md) +- 🐛 **Bugs & features:** [open an issue](https://github.com/resq-software/{{REPO}}/issues/new/choose) + --- ## Changelog @@ -278,6 +374,17 @@ Licensed under the [Apache License, Version 2.0](./LICENSE). --- +## Acknowledgements + + + +- Built on the [ResQ platform](https://resq.software). +- {{ACKNOWLEDGEMENT_1}} + +--- +