Skip to content

Bootstrap org-wide defaults: community health, AI-agent policy, reusable workflows#1

Open
bryanfawcett wants to merge 26 commits intomainfrom
claude/org-standardization-github-hmXmu
Open

Bootstrap org-wide defaults: community health, AI-agent policy, reusable workflows#1
bryanfawcett wants to merge 26 commits intomainfrom
claude/org-standardization-github-hmXmu

Conversation

@bryanfawcett
Copy link
Copy Markdown
Contributor

@bryanfawcett bryanfawcett commented Apr 14, 2026

Summary

Bootstraps nyuchitech/.github as the organization-wide default
community-health, AI-agent policy, and CI automation hub
for every
public repo under Nyuchi Web Services.
Everything shipped here either auto-inherits into other repos
(community-health files, issue/PR templates, workflow templates),
is uses:-referenced at runtime (reusable workflows), or ships as
a starter template repos copy into their own tree (CODEOWNERS,
Dependabot).

What's in this PR

Repo basics

  • LICENSEMIT. Chosen because it matches the org's majority
    license and is one-way-compatible with Apache 2.0 and GPL repos
    that may copy files from here.

Org profile

  • profile/README.md — landing page at
    https://github.com/nyuchitech. Positions Nyuchi as an
    "infrastructure company building frontier technology for Africa's
    unique economies,"
    grounded in the Ubuntu philosophy
    ("I am because we are" / "Ndiri nekuti tiri"). Content verified
    against services.nyuchi.com, mukoko.com, design.nyuchi.com, and a
    scan of the org's 20 public repos. Mukoko's mission line quoted
    verbatim; ntl and siafudb highlighted as the frontier-infra
    tier.

Community-health files (auto-inherited)

  • CODE_OF_CONDUCT.md — Contributor Covenant 2.1.
  • SECURITY.md — private vulnerability reporting via GitHub
    Security Advisories, response commitments, safe-harbor terms.
  • SUPPORT.md — routes users to docs, Discussions, Issues, and
    Security.
  • CONTRIBUTING.mdConventional Commits, signed commits
    (GPG/SSH), DCO sign-off, branch naming, squash-merge default,
    license-neutral.
    The rules every human and agent PR must meet.

AI-agent policy

  • AGENTS.md — canonical cross-agent ruleset for Claude, Cursor,
    Copilot, Aider, Devin, Codex. Reserved branch prefixes per agent,
    explicit rule that agents never invent Signed-off-by trailers,
    failure-mode callouts (scope creep, robustness theatre, silently
    weakened tests, un-pinned new Actions, prompt-injection).
  • .github/copilot-instructions.md — Copilot-specific pointer to
    AGENTS.md plus the handful of rules Copilot most often violates
    even when pointed at a doc. Lives at Copilot's canonical path.

Issue and PR forms (auto-inherited)

  • .github/ISSUE_TEMPLATE/config.yml — disables blank issues;
    routes "New issue" picker to docs, support KB, Discussions,
    SECURITY.md.
  • .github/ISSUE_TEMPLATE/bug_report.yml — structured form,
    preflight checks, repro steps, expected vs actual, generic
    environment block (TS/Rust/Python/C++/Astro), CoC agreement.
    Title prefilled bug: .
  • .github/ISSUE_TEMPLATE/feature_request.yml — separates
    problem from proposal, alternatives, scope dropdown,
    breaking-change checkbox, willing-to-contribute signal, CoC.
    Title prefilled feat: .
  • .github/PULL_REQUEST_TEMPLATE.md — type-of-change checklist
    mapped to Conventional Commits, breaking-change section, test
    plan, hard merge checklist mirroring CONTRIBUTING.md with
    anchored links to every rule.

Automation config (per-repo; this repo ships a real config + a starter template)

  • .github/dependabot.yml — Dependabot config for this repo
    (github-actions ecosystem only).
  • .github/dependabot.example.yml — full template other repos copy.
    Five ecosystems (github-actions, npm, cargo, pip, docker +
    gitsubmodule commented out), staggered Mon-Fri 06:00 Africa/Harare,
    build: commit prefix so pr-title-lint accepts Dependabot PRs,
    minor/patch grouping.
  • .github/CODEOWNERS — code-review ownership for this repo
    with security-tighter paths.
  • CODEOWNERS.example — starter template for other repos. Covers
    common source layouts, CI paths, lockfiles (routed to platform
    team — lockfile diffs are supply-chain events), docs, and
    security-sensitive /**/secrets/**, /**/auth/**, /**/crypto/**
    paths.

Operational docs

  • ORG_SETTINGS.md — source of truth for GitHub UI settings:
    member privileges, required 2FA, Actions allow-list, workflow
    default permissions, domain verification, full org-wide security
    features (dependency graph, Dependabot alerts, secret scanning +
    push protection, private vulnerability reporting, CodeQL),
    repo-level defaults, branch protection for main (required
    signed commits, linear history, CODEOWNERS review, required
    status checks named by workflow, applied to admins too), tag
    protection for v*, secrets policy (no long-lived cloud creds —
    OIDC only), Ruleset/Terraform migration path, quarterly audit.

Starter workflow templates (Actions → New workflow)

CI:

  • workflow-templates/ci-nextjs-monorepo.yml — Turborepo + pnpm,
    affected-only via turbo --filter=...[base].
  • workflow-templates/ci-rust-monorepo.yml — Cargo workspace gated
    on Rust changes; fmt, clippy, nextest, build, doc with
    Swatinem/rust-cache.
  • workflow-templates/ci-python-monorepo.yml — uv workspace with
    two-layer change detection: workspace-wide ruff/mypy plus
    per-package pytest matrix driven by diff against the base SHA.
  • workflow-templates/ci-docs-mdx.yml — cspell incremental on PRs,
    lychee link check (cached), pnpm-based site build for MDX docs.

Security and housekeeping:

  • workflow-templates/codeql.yml — security-extended +
    security-and-quality over JS/TS, Python, C/C++. PR, push to main,
    weekly cron. Rust excluded (no stable CodeQL support).
  • workflow-templates/dependency-review.yml — blocks PRs with
    moderate-or-higher vulnerabilities. License enforcement left to
    per-repo overrides.
  • workflow-templates/pr-title-lint.yml — amannn/action-semantic-pull-request@v5
    enforcing CONTRIBUTING's Conventional Commits types.
  • workflow-templates/stale.yml — 60/14 on issues, 30/14 on PRs,
    daily 01:23 UTC.

Each ships with a matching *.properties.json for GitHub's
workflow picker.

Reusable workflows (runtime-referenced via uses:)

New: eight reusable workflows under .github/workflows/reusable-*.yml,
one per starter template. Solves the starter-template drift problem:
fixes propagate to every adopter automatically.

  • reusable-ci-nextjs-monorepo.yml — inputs: tasks,
    node-version-file; secrets: TURBO_TOKEN, TURBO_TEAM.
  • reusable-ci-rust-monorepo.yml — input: toolchain.
  • reusable-ci-python-monorepo.yml — convention-based, no inputs.
  • reusable-ci-docs-mdx.yml — inputs: build-command,
    node-version-file, files-glob.
  • reusable-codeql.yml — required input: languages (JSON array).
  • reusable-dependency-review.yml — inputs: fail-on-severity,
    comment-summary-in-pr.
  • reusable-pr-title-lint.yml — input: require-scope; types list
    tracks CONTRIBUTING.md.
  • reusable-stale.yml — fully parameterised, defaults match
    stale.yml.

Adoption model: Path A = copy starter template, own it forever.
Path B = reference the reusable, get automatic upgrades. Repos
choose per workflow.

Repo-index README

  • README.md — documents the layout, propagation semantics
    (auto-inherited vs runtime-referenced vs per-repo-copy vs
    operational), and tracks status. Every row reads ✅.

How GitHub uses these artefacts

Different artefacts propagate in different ways — "org-wide default"
means different things for different files:

Category Mechanism Files
Auto-propagated GitHub's default community-health mechanism CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md, SUPPORT.md, .github/ISSUE_TEMPLATE/*, .github/PULL_REQUEST_TEMPLATE.md, workflow-templates/*
Referenced at runtime Consuming repo uses: .github/workflows/reusable-*.yml
Read in this repo only No propagation; repos copy if they want it AGENTS.md, .github/copilot-instructions.md
Per-repo starter templates Each repo copies and trims .github/dependabot.example.yml, CODEOWNERS.example
Operational (UI-applied) Source-of-truth doc audited quarterly ORG_SETTINGS.md

README.md § "How GitHub uses this repo" covers this in full.

Test plan

  • All YAML parses with PyYAML (8 workflow templates, 8 reusable
    workflows, 3 issue templates, 2 Dependabot configs).
  • All .properties.json files parse with json.load.
  • profile/README.md renders correctly at the org landing page.
  • No invented URLs, brand claims, or personal information
    (verified against services.nyuchi.com, mukoko.com,
    design.nyuchi.com, and the public repo scan).
  • CONTRIBUTING references CODEOWNERS; CODEOWNERS exists.
    CONTRIBUTING references AGENTS.md; it exists.
    ORG_SETTINGS.md references required status checks by name;
    those workflow names exist.
  • PR title on this PR follows Conventional Commits (once
    pr-title-lint is adopted on this repo, it will lint itself).
  • Post-merge: apply ORG_SETTINGS.md in GitHub's UI
    (branch protection, secret scanning, Actions allow-list,
    domain verification, org secrets).
  • Post-merge: create the teams CODEOWNERS references
    (@nyuchitech/maintainers, /security, /platform, /docs,
    /marketing) or rename the handles to match existing teams.
  • Post-merge: confirm the workflow picker shows all eight
    templates in every public org repo.
  • Post-merge: confirm the org landing page renders the new
    profile/README.md.
  • Post-merge: adopt at least one reusable workflow from at
    least one repo (likely ntl or siafudb) end-to-end.

Caveats to flag for review

  • CODEOWNERS team handles (@nyuchitech/maintainers,
    /security, /platform, /docs, /marketing) are the
    intended names. If the real team handles differ, rename before
    enabling "Require review from Code Owners" in branch protection,
    or GitHub silently can't resolve reviewers.
  • AGENTS.md and copilot-instructions.md do not auto-propagate.
    Each repo that wants these rules applied to its own agent sessions
    must copy the files.
  • ORG_SETTINGS.md describes intended state, not applied state.
    Applying it is manual against the GitHub UI, or a follow-up PR
    to a terraform/github or probot/settings configuration.
  • Reusable workflows use @main in the examples. Consuming
    repos that want supply-chain-safe pinning should reference the
    reusables by commit SHA.

Commit order

The 17 commits on this branch tell an ordered story — read them
in order if you want the full rationale:

  1. 7cd1caa bootstrap: profile + 3 monorepo CI templates
  2. 4c4a5ec CODE_OF_CONDUCT + drop invented URL
  3. 3a39f87 SECURITY
  4. 044d08e SUPPORT + README refresh
  5. 9c74465 reposition profile around infrastructure + Ubuntu
  6. 41b8d78 enrich profile with verified extras from brand sites
  7. bc0db2b CONTRIBUTING
  8. 958e6aa issue forms + PR template
  9. c82f8ae CodeQL + dep-review + stale + PR-title-lint + docs/MDX CI
  10. 266859d AGENTS.md
  11. 44dfac9 Dependabot config + example
  12. e4fe364 CODEOWNERS + example
  13. bcbf40e ORG_SETTINGS.md
  14. de2e0db 8 reusable workflows
  15. bdc458b LICENSE
  16. 6327c8a copilot-instructions.md
  17. 155c0f0 document propagation semantics for every artefact

References

claude added 9 commits April 14, 2026 09:57
Bootstraps org-wide standardization:
- profile/README.md renders the org landing page at
  github.com/nyuchitech and links to the community health docs.
- README.md now documents what lives here and how GitHub applies
  these defaults to every repo in the org.
- workflow-templates/ adds three reusable CI templates shown in
  the "New workflow" UI for every repo in the org:
    * ci-nextjs-monorepo  - Turborepo + pnpm, affected-only via
      turbo --filter=...[base].
    * ci-rust-monorepo    - Cargo workspace gated on Rust changes;
      fmt, clippy, nextest, build, doc with Swatinem/rust-cache.
    * ci-python-monorepo  - uv workspace with two-layer change
      detection: workspace-wide ruff/mypy plus a per-package
      pytest matrix driven by diff against the base SHA.

Each template ships a .properties.json for GitHub's workflow
picker. All three pass actionlint.
- CODE_OF_CONDUCT.md adopts Contributor Covenant v2.1 by reference
  (links to the canonical text rather than inlining it), defines
  scope as all repos under the org, and routes reports through
  GitHub's native primitives: private security advisories for
  repo-level reports and GitHub's abuse form for platform-level
  issues. No email addresses or personal contacts.
- profile/README.md: removed the nyuchi.com link that was not
  sourced from anything in the repo.
SECURITY.md covers:
- Two private reporting channels: [email protected] and
  GitHub's private security advisory flow. Public issues/PRs
  for vulnerabilities are explicitly discouraged.
- What to include in a report so we can reproduce and triage.
- Response commitments: 3 business days to acknowledge, 10 to
  triage, and a 90-day default coordinated-disclosure window.
- Scope rules: in-scope = any non-archived repo in the org plus
  its releases and build infra; out-of-scope = third-party deps,
  GitHub itself, DoS, scanner-only reports.
- Safe-harbor language for good-faith research.
- SUPPORT.md routes users to the right channel: Discussions for
  questions, Issues (with templates) for bugs and features,
  SECURITY.md for vulnerabilities, CODE_OF_CONDUCT.md for conduct
  reports, CONTRIBUTING.md for contributions. Sets expectations
  about response time, scope, and no commercial support.
- README.md is restructured with a status column (shipped vs.
  planned) so the table reflects what is actually on disk today
  and what is still coming. Also adds the workflow-template
  inventory, including the three monorepo CI templates already
  shipped and the CodeQL / dependency-review / PR title lint /
  stale templates still to come.
Combines the grounded description from services.nyuchi.com with the
frontier-infrastructure framing provided by org leadership:

- Lead: "An infrastructure company building frontier technology for
  Africa's unique economies." Tagline: "Ubuntu - I am because we
  are."
- "What we build" pillars: Web2->Web3->quantum; on-device, local-
  first, edge compute and storage for the AGI era; platforms built
  for communities and shaped by the Ubuntu philosophy.
- Ecosystem sections updated to reflect the actual product surface
  named on services.nyuchi.com: Nyuchi Learning, MailSense,
  Workspace Tools, and the @nyuchi/* component packages under the
  Nyuchi brand; Mukoko News under the Mukoko brand.
- Links to services.nyuchi.com for the full product catalog.

No personal information. Invented URL (nyuchi.com) was already
removed in a prior commit; this version only links to
services.nyuchi.com, which is sourced from the user.
Pulled grounded content from mukoko.com, nyuchi.com,
design.nyuchi.com, and a scan of the org's 20 public repos:

- Tagline now includes the Shona form: "Ndiri nekuti tiri."
- Top-of-page links to services.nyuchi.com, mukoko.com, and
  design.nyuchi.com.
- Added a "Where that shows up in code" section with three groups:
  - Frontier infrastructure: `ntl` (Rust signal-based data transfer
    layer) and `siafudb` (C++ embedded property graph database for
    device, edge, and Web3) - literal examples of the on-device/
    local-first/edge pillar.
  - Platforms: Mukoko (with its mission quote verbatim from
    mukoko.com) and its app family (News, Lingo, Weather, Nhimbe);
    `learning`; `shamwari-ai` (described using the repo's own
    wording).
  - Design: pointer to design.nyuchi.com with "Five African
    Minerals" palette and APCA Lc 90+ contrast, and a link to the
    `design-portal` repo.
- License section now reflects reality: the org uses a mix of MIT,
  Apache 2.0, and occasionally GPL; always check the repo's LICENSE.

Content is quote-accurate to the sources; no invented claims, no
personal info, no emojis.
Covers the strict rules that apply to every repo in the org, with
explicit language that individual repos may add stricter rules but
may never relax these:

- Conventional Commits 1.0 for every commit and every PR title,
  with the full allowed-types table, scope guidance, and breaking-
  change conventions. Enforced by the pr-title-lint workflow.
- Signed commits required on every merge to main (GPG or SSH, so
  long as GitHub marks the commit Verified). Branch protection
  blocks unsigned commits.
- DCO 1.1 sign-off required on every commit via `git commit -s`.
  Clarifies the difference between `-S` (sign) and `-s` (sign off)
  since contributors routinely confuse them.
- Branch naming: `<type>/<kebab-desc>`, lowercase, under 50 chars.
  Reserves `release/`, `hotfix/`, and `claude/` prefixes.
- PR rules: rebase on base, small diffs, squash-merge default,
  required green CI, one approving review (two for security-
  sensitive or cross-repo infra changes).
- License-neutral section: the org uses a mix of MIT, Apache 2.0,
  and occasionally GPL; contributions are licensed under the
  repo's declared license, with explicit reminders about Apache
  NOTICE files and per-file headers.
- Dependency rules tie into the planned dependency-review workflow.
- Pointers back to CODE_OF_CONDUCT.md, SECURITY.md, and SUPPORT.md.

Also flips CONTRIBUTING's status in README.md from planned to
shipped and removes the placeholder "once shipped" wording in the
"Contributing to this repo" section.
Ship the org-wide defaults for issue and PR forms. Any public repo
under nyuchitech without its own versions now inherits these.

Issue templates:

- `.github/ISSUE_TEMPLATE/config.yml` disables blank issues and
  surfaces four routing links from the "New issue" picker:
  docs repo, support knowledge base, org Discussions, and
  SECURITY.md (with an explicit "do NOT open a public issue" note).
- `.github/ISSUE_TEMPLATE/bug_report.yml` is a structured form with
  preflight checks (de-dup, read docs, not a vuln), summary,
  reproduction steps, expected vs actual behaviour, environment
  (generic enough to cover TS, Rust, Python, C++, Astro repos),
  logs, additional context, and a Code of Conduct agreement. Title
  prefilled with `bug: ` so issues are Conventional-Commits-shaped.
- `.github/ISSUE_TEMPLATE/feature_request.yml` separates problem
  from proposal (users routinely conflate them), asks for
  alternatives considered, a rough scope dropdown, a breaking-
  change checkbox, an optional "willing to contribute" signal,
  and CoC agreement. Title prefilled with `feat: `.

Pull request template:

- `.github/PULL_REQUEST_TEMPLATE.md` with a type-of-change
  checklist mapped to the Conventional Commits types from
  CONTRIBUTING.md, a breaking-change section, a test-plan
  section, and a hard checklist mirroring CONTRIBUTING.md's
  merge requirements (signed commits, DCO sign-off, branch
  naming, rebased, tests, docs, runtime-dependency justification,
  breaking-change footer). Every checklist item links back to the
  exact CONTRIBUTING.md anchor.

All three issue YAML files parsed with PyYAML to confirm
well-formed syntax before commit.

Also flips the four rows in README.md's status table from planned
to shipped.
… lint, docs/MDX CI

Completes the workflow-templates catalogue. Each template ships
with its matching .properties.json so the display name,
description, categories, and file-pattern hints render correctly
in every org repo under Actions -> New workflow.

codeql.yml
  Security-extended + security-and-quality query suites over
  JS/TS, Python, and C/C++ (the three languages CodeQL supports
  stably that we actually use). Runs on PR, push to main, and
  weekly cron. Rust is intentionally excluded - CodeQL does not
  yet support it stably; the Rust CI template handles clippy +
  cargo-audit instead. Header comment tells users to remove
  matrix entries for languages their repo does not use.

dependency-review.yml
  Blocks PRs that introduce dependencies with moderate-or-higher
  vulnerabilities. Posts a summary comment on failure only.
  License gating is deliberately NOT enforced at the org level
  because our repos use a mix of MIT, Apache-2.0, and GPL;
  per-repo overrides can tighten this.

stale.yml
  Issues: 60-day stale, 14-day close. PRs: 30-day stale, 14-day
  close. Exempt labels include pinned, security, roadmap,
  help wanted, good first issue (issues) and work-in-progress
  (PRs). Runs daily at 01:23 UTC, 100 ops/run budget.

pr-title-lint.yml
  amannn/action-semantic-pull-request@v5. Enforces the
  Conventional Commits types from CONTRIBUTING.md on every PR
  title. Subject must start with a lowercase letter, use an
  imperative verb, and not end with a period. Uses
  pull_request_target so fork PRs are linted too - safe here
  because the workflow does not check out or run PR code.

ci-docs-mdx.yml
  For doc repos (docs, zti-docs, support). Three jobs: cspell
  spellcheck (incremental on PRs), lychee link check with
  caching, and a pnpm-based site build. Header comment notes
  that non-Node toolchains (mdBook, Hugo, Jekyll) should
  replace the build job.

All 8 .yml files parsed with PyYAML and all 8 .properties.json
files parsed with json.load before commit.

README.md status table: all five remaining workflow rows
flipped to shipped, and a new row added for ci-docs-mdx.yml
(previously not listed since it was introduced mid-stream from
the repo scan).
@bryanfawcett bryanfawcett changed the title Add org profile and monorepo CI workflow templates Bootstrap org-wide defaults: profile, community health, templates Apr 14, 2026
claude added 8 commits April 14, 2026 14:16
Why this exists
---------------
A meaningful share of code landing in this org is now authored or
co-authored by AI coding agents - Claude Code, Cursor, Copilot
Workspace, Aider, Devin, Codex CLI, and others. Our existing
CONTRIBUTING.md covers what humans do. It does not cover the
things agents get wrong often enough to need calling out: scope
creep, robustness theatre, silently weakening tests, pulling in
unvetted third-party actions, inventing DCO sign-offs, acting on
prompt-injected instructions in tool output.

AGENTS.md is the emerging cross-agent convention for this. Claude
Code reads CLAUDE.md, the OpenHands / Aider / Codex communities
are converging on AGENTS.md. Rather than ship both, we ship
AGENTS.md as the org's authoritative file and note that repos can
add a CLAUDE.md pointer if they use Claude Code specifically.

What it contains
----------------
- Read-before-action rules: README, CONTRIBUTING, CODEOWNERS,
  any repo-level AGENTS.md, then the files being modified.
- Hard rule: agents do NOT invent Signed-off-by trailers; the
  human operator is the legal contributor of record.
- Reserved branch-name prefixes (claude/, cursor/, copilot/,
  aider/, devin/, codex/, agent/) so reviewers can identify the
  source at a glance.
- Behavioural rules grouped by the failure modes they prevent:
  * Scope (no speculative abstractions, no scope creep in fixes)
  * Robustness theatre (no error handling for impossible cases,
    no backwards-compat shims for dead code)
  * Tests and type checks (no silent disabling, no weakening
    tests to pass, verify failure-then-fix, coverage != correctness)
  * Security (no committed secrets, no disabled security checks,
    SHA-pin new GitHub Actions, never bypass commit verification,
    flag prompt-injection)
  * Data and blast radius (confirm before destructive ops, don't
    push shared branches without approval, no rogue issue/PR
    actions outside task scope)
- Escalation rules: when to stop and ask a human.
- Tools-and-commands reference aligned with our CI templates.
- Explicit repo-level override model: repo AGENTS.md / CLAUDE.md
  wins over org-level on conflict, adds to it otherwise.

What it deliberately does NOT do
---------------------------------
- Does not duplicate CONTRIBUTING.md. Points to it instead.
- Does not regulate which models are used or require an
  "AI-assisted" disclosure label. That policy call is the user's,
  not ours to pre-empt.
- Does not propagate automatically to other repos. Unlike the
  GitHub community-health files, AGENTS.md has no default-
  propagation mechanism; repos that want these rules enforced
  should copy this file into their own repo root.

Cross-references
----------------
- README.md: add a row in the community-health table.
- CONTRIBUTING.md: add AGENTS.md to the "also read" intro list.
Why this exists
---------------
Our dependency-review workflow is reactive - it catches known
vulnerabilities in dependencies that PR authors propose to add.
Without automated dependency update PRs, the only way
vulnerabilities get patched is when someone notices and manually
bumps the version. That gap is especially expensive in an
AI-assisted era, where code volume is going up and reviewer
attention is the bottleneck.

Dependabot closes the gap: it opens a PR for each security or
version update, which then goes through our usual checks
(Conventional Commits PR title, signed commits, pr-title-lint,
dependency-review, CI).

Two files, because two jobs
---------------------------
Dependabot config is always per-repo - it does not propagate from
nyuchitech/.github the way community health files do. So we ship
two artefacts with different audiences:

1. `.github/dependabot.yml` is the actual config for THIS repo.
   This repo ships templates and markdown, not application code,
   so it only tracks the `github-actions` ecosystem - keeping any
   actions we use (and workflow-templates we ship) pinned to
   current, secure versions.

2. `.github/dependabot.example.yml` is the starter template other
   repos in the org should copy to `.github/dependabot.yml`. It
   covers github-actions, npm, cargo, and pip, with docker and
   gitsubmodule commented out for repos that need them. Each
   ecosystem:
     - Runs weekly, on a different day, staggered Monday through
       Friday 06:00 Africa/Harare, so no single repo drowns in
       Dependabot PRs on the same morning.
     - Uses `build:` as the Conventional Commits prefix so our
       pr-title-lint workflow accepts Dependabot's PR titles
       without intervention.
     - Groups minor and patch updates so reviewers see one PR per
       category instead of one per dependency.

Notable decisions
-----------------
- Africa/Harare timezone (UTC+02:00) across the board, matching
  the org's primary operating region.
- npm ignore list pins `next`, `react`, and `react-dom` to
  deliberate major upgrades. Framework majors break too much to
  land via Dependabot.
- uv.lock is not yet natively supported by Dependabot. The Python
  CI template already regenerates the lock on pyproject.toml
  changes, so Dependabot's pip bumps flow through correctly.

What this deliberately does NOT do
----------------------------------
- Does not enable Dependabot security updates separately. Those
  are configured at the repo's Settings -> Security level in
  GitHub's UI, not via this file. ORG_SETTINGS.md (next commit in
  this series) documents that expectation.
- Does not enable Renovate. Dependabot is first-party to GitHub
  and integrates with branch protection without a token; Renovate
  is more configurable but adds operational surface we don't need
  yet.
- Does not pre-approve Dependabot PRs. Auto-merge for Dependabot
  is a per-repo call that some teams want for patch-only bumps;
  that's covered as a configurable option in ORG_SETTINGS.md.

Cross-references
----------------
- README.md: new "Automation config" section with rows for both
  files.
- AGENTS.md already requires agents to SHA-pin NEW actions they
  introduce. Dependabot handles the ongoing version-tracking for
  everything that's already there.
Why this exists
---------------
CONTRIBUTING.md says "The CODEOWNERS file in each repo tells you
who reviews PRs." AGENTS.md says "when agents open PRs, CODEOWNERS
is how you guarantee a *human* gets the notification." Both
references pointed to something that didn't exist. This commit
makes them real.

CODEOWNERS matters more in the AI-assisted era, not less: when a
PR can land without any single human having chosen to look at it,
the only safety net is automatic review-assignment. CODEOWNERS is
that safety net.

Two files, because two jobs
---------------------------
CODEOWNERS, like dependabot.yml, does NOT propagate from
nyuchitech/.github. It's per-repo. So we ship two artefacts:

1. `.github/CODEOWNERS` is this repo's actual ownership mapping.
   Default owner is @nyuchitech/maintainers. Tighter ownership on:
     - security-adjacent files (SECURITY.md, AGENTS.md,
       ORG_SETTINGS.md): maintainers + @nyuchitech/security.
     - CI / automation (workflow-templates/, dependabot*): add
       @nyuchitech/platform.
     - /profile/ (public landing page): add @nyuchitech/marketing
       because it's a content / brand artefact, not just code.

2. `CODEOWNERS.example` at the repo root is the starter template
   other repos should copy to their own `.github/CODEOWNERS`. It
   covers:
     - Source layouts common across our stacks (src, packages,
       apps, crates, lib).
     - CI and infrastructure directories (.github/workflows,
       infra, terraform, k8s, scripts).
     - Lockfiles (pnpm-lock, Cargo.lock, uv.lock, poetry.lock) -
       unexpected lockfile churn is often where supply-chain
       surprises show up, so we route those through platform.
     - Documentation (docs/, *.md, *.mdx).
     - Security-sensitive paths (/**/secrets/**, /**/auth/**,
       /**/crypto/**) always gated on the security team.

Notable decisions
-----------------
- Team handles (@nyuchitech/maintainers, /security, /platform,
  /docs, /marketing) are the INTENDED names, called out as such
  in both files. If the org's actual team handles differ, rename
  before enabling "Require review from Code Owners" in branch
  protection, or GitHub will not be able to resolve the
  reviewers.
- Lockfiles are deliberately in the platform-team bucket, not the
  default-owner bucket. Reviewing a lockfile diff is a supply-
  chain task, not a feature-code task.
- No individual contributor handles are used - ownership is team-
  based so vacations and handovers don't block merges.

What this deliberately does NOT do
----------------------------------
- Does not enable "Require review from Code Owners" in branch
  protection. That's an org/repo setting, documented in
  ORG_SETTINGS.md (next commit in this series).
- Does not try to cover every project layout. Repos with unusual
  directory structures should extend the example rather than
  treating it as complete.
- Does not auto-propagate. Each repo must copy the template. A
  follow-up could script the initial seed across the org's 20
  repos, but that's an adoption task, not a defaults task.

Cross-references
----------------
- README.md: new rows in the "Automation config" section.
- AGENTS.md § "Before you touch the code" already tells agents to
  read CODEOWNERS; this commit gives them something to read.
Why this exists
---------------
A significant part of our security and review posture lives in
GitHub's UI settings, not in any file:

  - Who can create repos.
  - Which actions and reusable workflows are allowed.
  - What branch protection on main requires (signed commits, linear
    history, required status checks, CODEOWNERS review, no bypass
    for admins).
  - What secret scanning / push protection / private vulnerability
    reporting is enabled.
  - What org secrets exist and what they're scoped to.

Those settings are trivial to drift out of, easy to forget on a new
repo, and impossible to review because they live in a web UI that
nobody looks at. Every other policy artefact in this repo
(CONTRIBUTING, AGENTS, CODEOWNERS, dependabot) references branch
protection or secret scanning being enabled - without documenting
WHERE that happens and WHAT state it should be in, those references
are load-bearing prose pointing at vapour.

ORG_SETTINGS.md makes the intended state explicit and auditable.

What it contains
----------------
- Org-level settings: member privileges, required 2FA, Actions
  allow-list, workflow default permissions, domain verification,
  org-wide security features (dependency graph, Dependabot alerts,
  secret scanning + push protection, private vulnerability
  reporting, CodeQL).
- Repo-level defaults: default branch, issues/discussions/wiki
  expectations, merge settings (squash-merge default, auto-delete
  head branches).
- Branch protection for main: the full rule set, including
  require-signed-commits, require-linear-history, required reviews
  (1 default, 2 for .github / security / infra), dismiss-stale-
  approvals, require-CODEOWNERS-review, require-conversation-
  resolution, required status checks listed by workflow name,
  block-force-pushes, apply-to-admins-too.
- Tag protection: v* tags require maintainer approval.
- Secrets: which org secrets exist, what they're used for, and the
  explicit rule that long-lived cloud credentials DO NOT go in
  Actions secrets - OIDC federation instead.
- Enforcement and audit: today manual, migrate to Rulesets, longer-
  term manage via Terraform or probot/settings. Quarterly audit.

Notable decisions
-----------------
- Wiki is disabled org-wide. We keep documentation in repos where
  it's reviewed like code, not in parallel wikis that drift.
- "Do not allow bypass" applies to admins too. Admins being able
  to push directly to main defeats the point of branch protection.
- Actions are allow-listed, not wide-open. The allow-list names
  only actions we actually use (the ones referenced in our
  workflow templates plus a few standards like github/codeql-
  action).
- "Allow GitHub Actions to create and approve pull requests" is
  explicitly DISABLED. A malicious action that could self-approve
  its own PR would bypass all our review gates at once.
- No long-lived cloud credentials in Actions secrets. OIDC
  federation only.

What this deliberately does NOT do
----------------------------------
- Does not apply any settings. It describes the intended state;
  applying it is a manual operation against the GitHub UI (or a
  follow-up terraform/ruleset PR).
- Does not enumerate every per-repo deviation. Repos that
  legitimately need to differ (e.g. allowing rebase-merge for a
  release-train workflow) should note the deviation in their own
  README or repo settings docs.
- Does not try to be a GitHub config reference. It only covers
  the settings WE care about; everything else defaults to
  GitHub's own defaults.

This commit closes the loop on the four follow-ups flagged in the
AI-era review: AGENTS.md, Dependabot, CODEOWNERS, and documented
org settings are now all in place.

Cross-references
----------------
- README.md: new "Operational docs" section.
- CODEOWNERS gates changes to ORG_SETTINGS.md on both
  @nyuchitech/maintainers and @nyuchitech/security, matching its
  security-adjacent role.
Why this exists
---------------
The workflow templates under workflow-templates/ solve "scaffold me
a CI workflow." They work via copy-on-init: GitHub copies the file
into the repo's .github/workflows/ and from then on the repo owns
that copy. This is great for repos that want to pin the behaviour
and pick up changes deliberately. It's bad for the org as a whole:
when we fix a bug in the Rust template, the 5 repos that already
adopted it do not get the fix unless each maintainer re-copies it.

Reusable workflows solve the same problem from the other side: the
logic lives in ONE place (this repo's .github/workflows/), and
consuming repos reference it with `uses:`. Fixes propagate to every
adopter automatically. The cost is that every caller is coupled to
this repo's main branch (or a pinned SHA).

Rather than pick one model and force every repo into it, we ship
BOTH and document the tradeoff. Each repo chooses per workflow:

  - Want to pin the behaviour? Use the starter template. It's a
    copy. Upgrades are explicit.
  - Want to stay in lockstep with the org? Call the reusable
    workflow. Upgrades are automatic. Breakage is also automatic
    when we change something.

What's in this commit
---------------------
Eight reusable workflows under .github/workflows/, one per starter
template. Each preserves the exact behaviour of the matching
template (same tools, same commands, same options) but re-shaped
for workflow_call with explicit `inputs:` and `secrets:` blocks.

CI workflows:
  reusable-ci-nextjs-monorepo.yml
    Inputs: tasks (space-separated, default "lint typecheck test
    build"), node-version-file (default .nvmrc).
    Secrets: TURBO_TOKEN, TURBO_TEAM (both optional).
    Behaviour: matrix job per task, turbo filter auto-computed
    from PR base SHA or HEAD^1.

  reusable-ci-rust-monorepo.yml
    Input: toolchain (default stable).
    Behaviour: unchanged from the starter - paths-filter gate,
    fmt + clippy + nextest + build + doc jobs with rust-cache.

  reusable-ci-python-monorepo.yml
    No inputs - convention-based. Assumes uv workspace at the
    repo root with .python-version, uv.lock, packages/* layout.
    Behaviour: unchanged from the starter - two-layer change
    detection, ruff + mypy + per-package pytest matrix.

  reusable-ci-docs-mdx.yml
    Inputs: build-command (default "pnpm build"),
    node-version-file (default .nvmrc), files-glob (default
    "**/*.{md,mdx}"). Behaviour: cspell incremental on PRs,
    lychee link check with cache, site build.

Security and policy workflows:
  reusable-codeql.yml
    REQUIRED input: languages - a JSON array of
    {language, build-mode} objects. Expressed this way so callers
    only declare the languages their repo actually uses, without
    us having to ship separate one-language reusables.

  reusable-dependency-review.yml
    Inputs: fail-on-severity (default moderate),
    comment-summary-in-pr (default on-failure).

  reusable-pr-title-lint.yml
    Input: require-scope (default false). Types list baked in to
    match CONTRIBUTING.md - when that list changes, change it
    here and in the starter template too.

  reusable-stale.yml
    Fully parameterised: days-before-*, exempt-*-labels,
    operations-per-run. Defaults match what stale.yml currently
    uses. Caller provides the schedule (cron) since workflow_call
    can't carry schedule triggers through to the reusable.

Notable decisions
-----------------
- Every reusable declares its own permissions: block at the
  workflow or job level. Callers must grant AT LEAST those
  permissions on the calling job; if they grant less, GitHub
  silently downgrades and steps fail in hard-to-debug ways. Each
  reusable's header comment spells out what the caller needs.
- No SHA-pinning of internal `uses:` inside the reusables. Tag
  pins (@v4, @v5) are acceptable here because Dependabot now
  tracks this repo's own Actions dependencies (see commit 44dfac9).
  Consuming repos that want SHA-pinning should reference this
  repo's reusables by SHA, not tag.
- Reusables do NOT declare `on:` triggers other than
  workflow_call. The caller owns the trigger surface. This is
  non-negotiable: reusables that try to declare pull_request
  will simply never run.
- I kept Rust's dtolnay/rust-toolchain switched from `@stable` to
  `@master` with a `toolchain:` input, because the former is an
  alias that only points at stable and can't be parameterised.
  Functionally identical when the input is left at its default.

What this deliberately does NOT do
----------------------------------
- Does not delete, rewrite, or otherwise touch workflow-templates/.
  The starter templates remain valid standalone workflows. Repos
  that already adopted them keep working.
- Does not ship .properties.json for the reusables. Reusable
  workflows don't show up in the "New workflow" picker - they're
  called, not scaffolded - so properties metadata is irrelevant.
- Does not enforce which path a repo picks. Platform team can
  recommend; repos choose per workflow.
- Does not set up a release versioning scheme (e.g. v1/v2 major-
  version tags pointing at the tip of each major). Callers use
  @main today. A future commit can add moving major tags once
  we've actually broken something.

Cross-references
----------------
- README.md: the Reusable workflow templates section is now split
  into Path A (starter templates) and Path B (reusable workflows)
  with a short paragraph explaining when to pick each. Both tables
  are populated with every shipped file.
- ORG_SETTINGS.md § Actions permissions: the org Actions allow-list
  must include `nyuchitech/*` (workflows from the same org are
  allowed by default, but if the allow-list is tightened, the
  reusables' paths must remain reachable).
- AGENTS.md § Security: "Pin third-party GitHub Actions by SHA"
  applies to the reusables too - consuming repos that care about
  this should reference the reusables by SHA, not @main.
Why this exists
---------------
Every other file in this repo - workflow templates, CODEOWNERS
example, AGENTS.md, ORG_SETTINGS.md, the issue/PR templates - is
written with the assumption that downstream repos may copy it. That
assumption only works if we're explicit about what copying entails.

Up to now, the repo has shipped without any LICENSE file. Our own
CONTRIBUTING.md says "contributions inherit the repo's LICENSE."
Without a LICENSE, that sentence referenced nothing.

What's in this commit
---------------------
LICENSE at the repo root: the standard MIT text, copyright Nyuchi
Web Services 2026.

Why MIT
-------
The org's public repos use a mix of licenses - MIT is the majority
(14 of 20 repos per the April 2026 scan), Apache 2.0 covers the
infrastructure tier (ntl, siafudb), and GPL covers auto-seo-manager.

MIT was chosen for this defaults repo because:

1. Consumers will copy these files into repos under ALL of those
   licenses. MIT is one-way-compatible with Apache 2.0 (MIT code
   can be included in an Apache 2.0 project under Apache's terms)
   and GPL (MIT code can be included in a GPL project under GPL's
   terms). The reverse is not true for any other reasonable choice.
2. It matches the org's majority license, so a maintainer who
   copies a workflow template into a new MIT repo inherits no new
   obligations they weren't already expecting.
3. It is short, well-understood, and does not introduce any
   copyleft or patent provisions that would surprise a consumer
   who copies a file from here without reading the LICENSE first.

What this deliberately does NOT do
----------------------------------
- Does not relicense any file in the repo. Files that had no
  explicit license before this commit were effectively unlicensed,
  which meant "all rights reserved" by default; they're now MIT.
  No file in the repo ships under an incompatible license.
- Does not switch the repo to CC0 / CC-BY / Apache 2.0. MIT is the
  most portable choice for content that will be copied into repos
  under multiple downstream licenses.
- Does not add per-file headers. Per CONTRIBUTING.md, when a file
  is copied into a repo that uses per-file headers, the consumer
  adds its own header under its own license.

Cross-references
----------------
- CONTRIBUTING.md § Licensing already documents the mix of licenses
  across the org and says "your contribution is licensed under the
  repo's existing license." This commit makes that true for this
  repo in particular.
- README.md: new "Repo basics" table at the top of "What's in this
  repo", ahead of the org-profile section, because the license
  governs everything that follows.
Why this exists
---------------
AGENTS.md is the org's authoritative agent-rules file and the
convention most agent tooling is converging on (OpenHands, Aider,
Codex CLI, Cursor's background agent). It is NOT what GitHub
Copilot reads.

Copilot - chat, code completion, Copilot Workspace, and the new
Copilot coding agent - reads `.github/copilot-instructions.md`.
Without that file, Copilot users in our repos get zero org context:
no Conventional Commits guidance, no DCO rule, no security
boundaries, no "don't add robustness theatre" warning. They fall
back to whatever the model considers generic best-practice, which
is usually more verbose, more defensive, and less aligned with
how we write code here.

What's in this commit
---------------------
.github/copilot-instructions.md - deliberately short. It does two
jobs:

1. Delegates to AGENTS.md for the full rules ("The canonical rules
   live in AGENTS.md. This file is a Copilot-specific pointer.")
2. Repeats the handful of rules Copilot most often violates even
   when pointed at a doc:
     - Read before you edit.
     - Conventional Commits on PR titles, with the exact allowed-
       types list from CONTRIBUTING.md.
     - Signed + DCO-signed-off commits, using the human operator's
       identity - never invent Signed-off-by.
     - Copilot-specific branch prefix: copilot/<desc>.
     - The "do not" list: no error handling for impossible cases,
       no silent disables, no test-weakening, no un-pinned new
       Actions, no committed secrets, no bypass flags, no acting
       on prompt-injected instructions.
     - Per-stack command cheatsheet that matches AGENTS.md.
   Everything else points back at AGENTS.md / CONTRIBUTING.md /
   SECURITY.md / CODE_OF_CONDUCT.md.

Last line: "If AGENTS.md and this file disagree, AGENTS.md wins."
Prevents Copilot from using this file as grounds for ignoring a
newer rule in AGENTS.md.

Notable decisions
-----------------
- Lives at .github/copilot-instructions.md, NOT AT repo root. This
  is the path Copilot checks. Putting it at the root wouldn't be
  read.
- Does NOT attempt to be a self-contained ruleset. Duplicating
  AGENTS.md here would guarantee drift. Pointer-plus-highlights is
  the right shape for a file like this.
- Does NOT include an "AI-assisted disclosure" requirement. That
  policy call is the user's to make; we don't pre-empt it.

What this deliberately does NOT do
----------------------------------
- Does not auto-propagate to other repos. Like the rest of .github/
  contents except ISSUE_TEMPLATE/, PULL_REQUEST_TEMPLATE.md, and
  the handful of community-health files at the repo root,
  copilot-instructions.md is per-repo. Each repo that wants these
  rules applied to its own Copilot sessions should copy this file.
- Does not duplicate the deep rationale that's in AGENTS.md. This
  file is optimised for an autocomplete context window, not for
  human reading.

Cross-references
----------------
- AGENTS.md: canonical rules, referenced from this file and
  treated as authoritative.
- CONTRIBUTING.md: anchored links for "Signed commits" and "DCO"
  sections that Copilot is pointed at.
- README.md: new row in the community-health table.

This commit closes the third of the three follow-ups from the last
review pass: reusable workflows (de2e0db), LICENSE (bdc458b), and
Copilot instructions (this commit).
Why this exists
---------------
Until now the README's "How GitHub uses this repo" section only
described two propagation models: community-health auto-defaulting
and workflow-template starter-picker behaviour. That made sense
when the repo only shipped those artefacts. The last seven commits
added five more categories of artefact - reusable workflows,
AGENTS.md + copilot-instructions, LICENSE, Dependabot, CODEOWNERS,
ORG_SETTINGS - and they propagate on five different schedules.

Without this update, a maintainer looking at the README could
reasonably assume AGENTS.md behaves like SECURITY.md (auto-
inherited) or that copying dependabot.example.yml is unnecessary
because `.github/dependabot.yml` already exists here. Both
assumptions would be wrong and would produce silent failures.

What changed
------------
Rewrote the "How GitHub uses this repo" section from a flat list
into five labelled categories matching the actual behaviours:

1. Auto-propagated by GitHub (no action needed):
   CODE_OF_CONDUCT, CONTRIBUTING, SECURITY, SUPPORT, ISSUE_TEMPLATE,
   PULL_REQUEST_TEMPLATE, workflow-templates.
2. Referenced at runtime via `uses:`:
   .github/workflows/reusable-*.yml.
3. Read by AI agents in this repo only (do NOT auto-propagate):
   AGENTS.md, .github/copilot-instructions.md. Repos that want the
   rules applied must copy the files.
4. Per-repo configuration (starter templates here, each repo
   copies): .github/dependabot.example.yml, CODEOWNERS.example.
   Explicit call-out that this repo's own .github/dependabot.yml
   and .github/CODEOWNERS do NOT get inherited - GitHub's default-
   propagation mechanism doesn't cover these paths.
5. Operational (applied manually in GitHub's UI):
   ORG_SETTINGS.md. Source-of-truth doc; not enforced by any file.

What this deliberately does NOT do
----------------------------------
- Does not add any new artefacts. Pure documentation sync.
- Does not move anything into a different category by changing
  propagation semantics - the five categories describe the actual,
  existing behaviour.
- Does not promise org-level tooling to auto-copy the "per-repo
  configuration" files across the 20 repos. That's adoption work,
  not a defaults-repo concern.

Standing rule
-------------
Going forward, any commit that adds a new artefact to this repo
must also place that artefact in one of the five categories above
(or add a new category if none fits) in the same commit. Docs ship
with the feature.
@bryanfawcett bryanfawcett changed the title Bootstrap org-wide defaults: profile, community health, templates Bootstrap org-wide defaults: community health, AI-agent policy, reusable workflows Apr 15, 2026
claude added 4 commits April 15, 2026 08:38
Why this exists
---------------
Up to now this repo had zero CI on itself. We were validating
YAML and JSON ad-hoc with PyYAML / json.load before each commit,
which catches syntax errors but does NOT catch:

  - Invalid action references (typos in `uses:` lines).
  - Wrong event types or unsupported `with:` keys.
  - Reusable workflow interface mismatches between caller and
    callee (the bug class most likely to bite us, given commit
    de2e0db just shipped 8 reusable workflows).
  - Missing required `permissions:` blocks for declared actions.
  - GitHub Actions expression syntax errors.
  - Markdown reference-link breakage and structural issues.

For an enterprise-grade defaults repo whose contents propagate to
20+ other repos, "we ran PyYAML on it" is not adequate quality
evidence. Lint CI is the lowest-cost intervention that closes the
gap.

What's in this commit
---------------------
.github/workflows/lint.yml ships four parallel jobs:

1. actionlint
   - Pinned to actionlint 1.7.4, downloaded via the official
     install script at the same pinned tag (no curl|bash from a
     moving target).
   - Runs against BOTH .github/workflows/*.yml AND
     workflow-templates/*.yml. The starter templates are designed
     to be valid standalone workflows once copied into a repo, so
     we lint them in place to catch breakage before consumers do.

2. yamllint
   - Pinned to yamllint 1.35.1.
   - Configured via .yamllint.yaml at the repo root. Notable
     deviations from the default config:
       * `truthy.check-keys: false` so `on:` (a YAML 1.1 boolean)
         doesn't fail every Actions workflow.
       * `line-length` set to warn at 120, not fail. Workflow
         files have legitimately long shell heredocs.
       * `document-start` disabled (we don't prefix YAML files
         with `---`).

3. markdownlint
   - Uses DavidAnson/markdownlint-cli2-action@v18.
   - Configured via .markdownlint.jsonc at the repo root. Notable
     deviations from the default config:
       * MD013 (line-length) disabled — we wrap for readability.
       * MD033 (inline HTML) disabled — profile/README.md uses
         <div align="center"> for the org landing page layout.
       * MD041 (first-line H1) disabled — profile/README.md and
         several docs open with HTML comments or <div>.
       * MD040 (fenced-code-language) disabled — acceptable for
         output blocks and plain-text fences.
       * MD024 (no-duplicate-headings) set to siblings_only.

4. JSON validity
   - Walks every *.json file in the repo and validates with
     json.load. Catches the case where a workflow-template's
     .properties.json or any other JSON config has been
     hand-edited into something invalid.

All four jobs run on every PR and every push to main. They are
expected to be required status checks on main once branch
protection is applied (see ORG_SETTINGS.md and the rulesets JSON
provided alongside this commit).

Notable decisions
-----------------
- actionlint, yamllint, and markdownlint are all pinned to
  specific versions or majors. We do NOT pin to commit SHAs at
  this point because the friction outweighs the benefit for an
  internal lint workflow with no secrets and read-only contents.
  Dependabot (commit 44dfac9) will track these dependencies and
  open update PRs.
- The lint workflow declares `permissions: contents: read` at
  the workflow level — minimum required, no token elevation.
- concurrency: cancel-in-progress on PRs but not on main, so
  superseded PR runs are killed but main pushes always complete.
- Lint configs (.yamllint.yaml, .markdownlint.jsonc) are
  documented inline with rationale for every rule deviation, so
  a future maintainer can tell why each rule is relaxed.

What this deliberately does NOT do
----------------------------------
- Does not add cspell. Spell-check on docs is high-noise on
  technical content (Mukoko, Nyuchi, Ubuntu, Shamwari, ntl,
  siafudb, shadcn, Turborepo, ...) and would need a custom
  dictionary maintained alongside. Defer until we have a docs-
  authoring workflow that benefits from it.
- Does not add the lint workflow as a reusable workflow. It is
  specific to this repo's structure (workflow-templates/ +
  .github/workflows/) and isn't intended for adoption elsewhere.
- Does not enforce MD025 (single H1) or MD025 strictness. Our
  files comply with the defaults; explicit configuration would
  be noise.

Cross-references
----------------
- README.md: three new rows in the "Repo basics" table for
  lint.yml, .yamllint.yaml, and .markdownlint.jsonc.
- ORG_SETTINGS.md § Branch protection — required status checks:
  the four job names from this workflow (actionlint, yamllint,
  markdownlint, JSON validity) should be added to the required-
  status-checks list for this repo's main branch in addition to
  "Conventional Commits" from pr-title-lint.

This commit closes the actionlint gap I flagged in the
"merge-readiness" review.
What broke
----------
The lint workflow added in fad7db9 ran for the first time on PR #1
and the markdownlint job reported 34 errors across 4 files. Three
were genuine accessibility / structure issues; one was a brittle
cosmetic rule.

Real content fixes
------------------
SECURITY.md
  - Line 19: `**[email protected]**` was a bare email. Wrapped
    in <...> so it renders as a mailto link
    (`**<[email protected]>**`). Bold preserved.
  - Line 34: "GitHub's documentation for this flow is [here]"
    used non-descriptive link text. Rewritten so the link text
    itself describes the destination ("[private vulnerability
    reporting flow]"), which is what screen readers and link
    indexers actually surface.
  - Lines 65 and 75: `**In scope**` and `**Out of scope**` were
    bold paragraphs styled as headings. Promoted to real `### `
    headings so they appear in the table of contents and get
    proper landmark navigation.

SUPPORT.md
  - Lines 37 and 45: same pattern - `**We can help with**` and
    `**We generally cannot help with**` promoted from bold-as-
    heading to `### ` headings.

These are not "make the linter happy" patches - they fix
real accessibility issues. Bold text used as a heading is a
known a11y antipattern (reported by MD036 for exactly this
reason): screen readers don't announce it as a heading, link
indexers can't navigate to it, and it doesn't appear in tables
of contents.

Config change
-------------
.markdownlint.jsonc: disable MD060 (table-column-style) with
inline rationale. MD060 fires the moment any cell length
changes in a table - re-padding sibling rows by hand on every
edit is high-friction for zero rendered-output difference. The
rule exists because formatters like prettier and mdformat
auto-fix it; without one wired up to CI, manually maintaining
column alignment is busywork. The 28 MD060 failures across
AGENTS.md and ORG_SETTINGS.md were all of this form.

If we add a markdown formatter to the lint pipeline later
(prettier --write, mdformat, dprint), re-enable MD060.

Verification
------------
Re-ran `npx markdownlint-cli2 "**/*.md" "!**/node_modules/**"`
locally with the updated config: 0 errors across 10 files.

Adheres to the standing rule from AGENTS.md
-------------------------------------------
"Don't disable lints, type checks, or tests silently. If you
must silence something, leave a same-line comment with the
reason." MD060 is now disabled with a multi-line rationale
explaining why and what would let us re-enable it. Not
silent.
Why this exists
---------------
The previous commit (80ec6b3) disabled markdownlint's MD060
(table-column-style) rule with a stated condition for re-enabling
it: "If we add a markdown formatter to the lint pipeline later,
re-enable this rule." This commit satisfies that condition.

The 28 MD060 violations across AGENTS.md and ORG_SETTINGS.md were
all the same shape: a single cell length changing on a single row
broke the whole table's alignment. That's exactly the failure mode
auto-formatters exist to eliminate. Prettier's markdown printer
reformats every table to a consistent aligned style, so MD060 can
be enforced as a check against drift rather than as a manual
maintenance burden.

What's in this commit
---------------------
1. `.prettierrc` at the repo root pinning the formatter behaviour
   that matters:
     - printWidth: 80 (fits standard terminal/diff views).
     - proseWrap: preserve - critical. Without this, Prettier would
       reflow every paragraph on every save and produce huge
       cross-cutting diffs that destroy git blame. Our prose is
       wrapped intentionally for readability.
     - tabWidth: 2, useTabs: false, endOfLine: lf, trailingComma:
       all - house style that matches the rest of our ecosystem.
     - embeddedLanguageFormatting: off for *.md / *.mdx so that
       fenced code blocks (especially YAML and bash inside our
       workflow examples) aren't reformatted by Prettier and put
       into conflict with the original source.

2. `.prettierignore` at the repo root that excludes:
     - YAML files. yamllint and actionlint enforce stricter
       semantics on those (the `on:` truthy quirk in particular)
       that Prettier doesn't understand. Letting both touch the
       same files would produce conflicting expectations.
     - LICENSE (verbatim text, must not be reformatted).
     - CODEOWNERS files (have their own structural meaning that
       Prettier doesn't model).
     - Lockfiles and node_modules.

3. New `prettier --check` job in .github/workflows/lint.yml. Pinned
   to prettier 3.3.3, installed globally on Node 20, runs against
   `**/*.{md,mdx,json,jsonc}`. Should be a required status check on
   main alongside the other four lint jobs.

4. Re-enabled `MD060: true` in `.markdownlint.jsonc`. Verified
   locally that Prettier's table output satisfies markdownlint's
   "aligned" style - 0 errors after `prettier --write` followed by
   `markdownlint-cli2`.

5. The 11 files Prettier reformatted on this first run:
     - AGENTS.md, CONTRIBUTING.md, ORG_SETTINGS.md, README.md,
       SECURITY.md, SUPPORT.md, profile/README.md - tables
       re-aligned, italics normalised from `*x*` to `_x_`.
     - .markdownlint.jsonc - trailing comma per JSONC convention.
     - workflow-templates/ci-docs-mdx.properties.json,
       codeql.properties.json, dependency-review.properties.json -
       arrays expanded to multi-line where they exceeded printWidth.
   None of the changes alter rendered output.

Notable decisions
-----------------
- `--check` not `--write` in CI. Auto-fixing in CI would require
  pushing a commit, which is its own surface (token scope, signed
  commits, DCO trailer). `--check` fails fast and tells the
  contributor to run `prettier --write` locally.
- Prettier scope is markdown + JSON only. NOT YAML. yamllint's
  `truthy: { check-keys: false }` config and Prettier's YAML
  printer disagree about how to render the GitHub Actions `on:`
  key, and resolving that means picking one tool. yamllint wins
  because actionlint depends on the same indentation conventions.
- Prettier 3.3.3 pinned. Dependabot (44dfac9) will track it via
  GitHub Actions ecosystem updates triggered by changes to the
  workflow file.
- Italics use `_underscores_` after this commit. That's Prettier's
  default and not worth fighting. GitHub renders both identically.

What this deliberately does NOT do
----------------------------------
- Does NOT add a pre-commit hook to auto-format on commit. That's
  a per-developer-machine concern; an org-wide default repo isn't
  the place to mandate it. Anyone who wants one can add husky or
  lefthook to their own repo.
- Does NOT format YAML. See above.
- Does NOT add Prettier as a reusable workflow. The same pattern
  (a few lines of CI calling `npm install -g [email protected] &&
  prettier --check`) is shorter than the reusable-workflow
  invocation would be.

Cross-references
----------------
- README.md "Repo basics": new rows for `.prettierrc` and
  `.prettierignore`.
- ORG_SETTINGS.md § required status checks should add `prettier
  --check` to the list for this repo's main branch alongside the
  existing four lint job names.

Closes the MD060 / "no markdown formatter" gap from the
merge-readiness review.
Why this exists
---------------
The previous lint workflow gated merges on prettier --check,
markdownlint, and yamllint. That treats style preferences the
same way as real bugs: a one-character italics-quoting difference
fails CI just as hard as a broken `uses:` reference.

The user's call: style is a developer choice, not an enforcement
target. The CI job's job is to *surface a suggested fix*, not to
demand the developer accept it. Real bugs (workflow syntax,
unparseable JSON) still block. Cosmetic issues (formatting,
italics style, table alignment, line-length warnings) become
inline PR review suggestions.

Two tiers
---------

BLOCKING - failure prevents merge. These jobs catch real bugs
that would ship to every consuming repo of our reusable
workflows:
  - actionlint: workflow syntax, expressions, action references,
    reusable-workflow interface mismatches.
  - JSON validity: anything that can't be parsed.

These two are the ones that belong in the required-status-checks
list of the main-branch ruleset.

ADVISORY - the job runs the linter in --fix / --write mode, then
reviewdog/action-suggester picks up the resulting diff and posts
it as inline PR review suggestions. The developer can click
"commit suggestion" or dismiss. Job exits 0 either way.
  - prettier (advisory): markdown + JSON formatting suggestions.
  - markdownlint (advisory): markdown structure suggestions.
  - yamllint (advisory): YAML style and warnings, surfaced as
    GitHub Actions workflow annotations via `yamllint -f github`
    plus continue-on-error: true.

These three should NOT be in the required-status-checks list.
They will report a green check or "neutral" status; the value is
the inline suggestions, not the pass/fail signal.

Italics
-------
With prettier moved to advisory, contributors can use `*x*` or
`_x_` for italics. CI will suggest converting `*x*` to `_x_`
(Prettier's preference) but won't fail. Existing files stay as
they are; new contributions can use either style. This matches
the user's "leave it as be" call - we stop enforcing one style.

Permissions
-----------
The three advisory jobs need `pull-requests: write` so reviewdog
can post the suggestions. The two blocking jobs keep the
workflow-default `contents: read`. Token scope is granted at the
job level, not workflow level - principle of least privilege.

What this deliberately does NOT do
----------------------------------
- Does NOT remove the configs (.prettierrc, .markdownlint.jsonc,
  .yamllint.yaml). They still drive what reviewdog suggests; the
  policy change is purely about whether suggestions are
  enforced.
- Does NOT add a pre-commit hook to auto-format on commit. Hooks
  remain optional per-developer setup, not an org requirement.
- Does NOT revert the italic changes Prettier made in commit
  3344f79. Those files are now formatted; future drift is
  acceptable; CI suggests but doesn't insist.

Cross-references
----------------
- README.md "Repo basics": `.github/workflows/lint.yml` row
  rewritten to call out the blocking vs advisory split and which
  job names go in required-status-checks.
- ORG_SETTINGS.md § required status checks: list should now
  contain only `actionlint` and `JSON validity` for this repo,
  not the three advisory ones.
- The rulesets JSON I provided previously needs updating too -
  remove `markdownlint`, `yamllint`, `prettier --check` from
  required_status_checks. Keep only `actionlint` and `JSON
  validity`.
Comment thread README.md Outdated
Comment on lines +14 to +16
| Path | Purpose | Status |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----: |
| `LICENSE` | MIT. Declares the terms every other file in this repo ships under, so downstream consumers copying a workflow template or CODEOWNERS example know their obligations. | ✅ |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[prettier] reported by reviewdog 🐶

Suggested change
| Path | Purpose | Status |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----: |
| `LICENSE` | MIT. Declares the terms every other file in this repo ships under, so downstream consumers copying a workflow template or CODEOWNERS example know their obligations. ||
| Path | Purpose | Status |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----: |
| `LICENSE` | MIT. Declares the terms every other file in this repo ships under, so downstream consumers copying a workflow template or CODEOWNERS example know their obligations. ||

claude and others added 5 commits April 15, 2026 09:01
Why this exists
---------------
The previous commit (803b732) made prettier, markdownlint, and
yamllint advisory - they posted PR review suggestions via
reviewdog and never blocked the merge. The reviewdog comment that
appeared on PR #1 demonstrated the model working as designed:
prettier offered a suggested table-padding diff, the developer
could click "commit suggestion" or ignore it.

The user's call: that's the wrong shape. CI should not be writing
fixes. CI should fail, and the developer should fix it locally
and push the corrected commit. The reasoning:

  - Posting a fix as a PR comment normalises CI editing the
    developer's PR, which blurs the line between "the developer
    chose to land this change" and "a bot landed something on
    behalf of the developer."
  - Auto-fix-as-suggestion makes lint feel optional. Some PRs get
    them applied, others don't, and the resulting `main` is
    inconsistently formatted.
  - Style consistency only works if it's enforced. A linter that
    can be ignored stops being a linter.

What changed
------------
.github/workflows/lint.yml: rewrote so all five jobs are strict
blocking. Removed every `--write`, `--fix`, `continue-on-error`,
and `reviewdog/action-suggester`. Job names dropped the
"(advisory)" suffix.

  - actionlint: blocking (unchanged).
  - JSON validity: blocking (unchanged).
  - prettier: now `prettier --check`, blocking. Fails the build
    if any file isn't Prettier-formatted.
  - markdownlint: now blocking, no `fix: true`.
  - yamllint: now `yamllint -s -f github .` (strict mode treats
    warnings as errors). Removed continue-on-error.

Required status checks for THIS repo's main branch must list all
five names.

.github/workflows/reusable-lint.yml (new): same five-job policy
exposed as a reusable workflow. Every other repo in the org
should call it from a thin caller workflow:

    on:
      pull_request:
      push:
        branches: [main]
    jobs:
      lint:
        uses: nyuchitech/.github/.github/workflows/reusable-lint.yml@main

Inputs let callers tune file globs (`prettier-glob`,
`markdownlint-globs`, `yamllint-target`, `actionlint-paths`) and
tool versions (`prettier-version`, `yamllint-version`). Defaults
match this repo's local lint workflow. Each consuming repo ships
its own .prettierrc, .markdownlint.jsonc, and .yamllint.yaml
(copy from this repo as starting point) - the tools auto-discover
config from the repo root.

.yamllint.yaml: two policy adjustments to make strict mode
viable:

  - line-length: bumped 120 -> 140, level: warning -> error.
    Workflow files have legitimately long shell URLs (the
    actionlint download script is 95 chars by itself, plus
    `bash <(curl ` and `) 1.7.4`) and issue-template `about:`
    strings render as a single line in the picker, so wrapping
    them changes user-visible behaviour. 140 still flags truly
    excessive lines.
  - comments-indentation: disabled. Real configs use comments to
    delimit sections (long `# ----` banners) and to mark
    commented-out blocks at the level of the code they replace.
    Neither fits the rule cleanly. Disabled with rationale
    inline (not silent).

README.md: lint.yml row rewritten to call out the strict-blocking
five-job model. Added a row for `reusable-lint.yml` in the
reusable-workflows table.

ORG_SETTINGS.md § required status checks: split into a "Lint -
required on every repo, no exceptions" group (the five lint
names) and a "Per-repo CI" group (Conventional Commits, Review
dependencies, CodeQL Analyze, primary CI). The lint group is the
new bottom-line requirement for every repo.

What this commit deliberately does NOT do
-----------------------------------------
- Does NOT delete the existing reviewdog comment on PR #1 (it's
  historical record; will become stale).
- Does NOT revert the italic style changes Prettier made in
  commit 3344f79. The files are formatted; future contributors
  who use `*x*` will get a strict `prettier --check` failure
  pointing at exactly what to run (`prettier --write`); they fix
  locally and push. No hidden state.
- Does NOT reformat any file beyond what was needed to make the
  new strict checks pass. README.md was the only file with
  prettier drift; one auto-format brought it back in line.
- Does NOT add a pre-commit hook. Hooks remain optional per-
  developer setup. Strict CI is the enforcement point.

Cross-references
----------------
- Rulesets JSON I provided previously needs updating: required
  status checks for main are now exactly:
    actionlint, JSON validity, prettier, markdownlint, yamllint
  Same five names for every repo that calls reusable-lint.
Why this exists
---------------
We were shipping every CI / policy workflow twice: once as a copy-
on-init starter template under workflow-templates/, and again as
a `uses:`-callable reusable workflow under .github/workflows/. The
user's call: pick one. They want reusable workflows only.

Reusable wins because:

  - Single source of truth. A bug fix in the org's CI logic
    propagates to every adopter on the next CI run, instead of
    being stuck in N copy-pasted instances.
  - Adopter cost is low. A consuming repo writes a 5-line caller
    workflow that `uses:` the reusable. That's smaller than the
    starter template would have been.
  - Matches the user's stated philosophy: examples should be
    "alive" - actually executing in production - not static
    files repos copy and then forget to upgrade.

What's gone
-----------
Deleted 16 files under workflow-templates/ (8 *.yml + 8 matching
*.properties.json):

  - ci-nextjs-monorepo
  - ci-rust-monorepo
  - ci-python-monorepo
  - ci-docs-mdx
  - codeql
  - dependency-review
  - pr-title-lint
  - stale

The directory is removed entirely. Each of these has a parallel
reusable workflow in .github/workflows/reusable-*.yml that
adopters call instead.

What's new / changed
--------------------
.github/workflows/lint.yml: rewritten as a thin caller of
.github/workflows/reusable-lint.yml from the same repo. This
proves the reusable works by using it on its own repo - if the
reusable breaks, this repo's CI breaks first, before any
consumer is affected.

  jobs:
    lint:
      uses: ./.github/workflows/reusable-lint.yml

The five jobs (actionlint, JSON validity, prettier, markdownlint,
yamllint) now run via the reusable. Status check names take the
form `lint / actionlint`, `lint / prettier`, etc. - the caller
job name prefixes the reusable job name.

.github/CODEOWNERS: removed `/workflow-templates/` rule; added
`/.github/workflows/` to the platform team's ownership instead
(reusable workflows are even more sensitive than starter
templates were because changes propagate live).

README.md:
  - "Reusable workflow templates" section rewritten. The Path A /
    Path B split is gone. There is one path: write a thin caller
    that `uses:` the reusable.
  - Added a 10-line copy-paste adopter pattern showing the
    canonical `uses:` invocation.
  - Note about SHA-pinning for repos with stricter supply-chain
    requirements.
  - "How GitHub uses this repo" propagation section: the
    `Workflow templates` bullet under "Auto-propagated by GitHub"
    is removed (those don't exist anymore).

ORG_SETTINGS.md required-status-checks list: rewritten to use
the `<caller-job> / <reusable-job>` format that reusable
workflows produce. Convention: callers name their jobs `lint:`,
`pr-title:`, `dep-review:`, `codeql:`, etc., so check names
read sensibly.

What this deliberately does NOT do
----------------------------------
- Does NOT change the reusable workflows themselves (other than
  lint.yml's caller). The eight reusables under
  .github/workflows/reusable-*.yml are unchanged in this commit.
- Does NOT version the reusables (no v1 tag, no major-version
  alias). Adopters use @main today; we add tags after a real
  release lifecycle proves itself.
- Does NOT delete the dependabot.example.yml or CODEOWNERS.example.
  Those are configuration files, not workflows; Dependabot config
  and CODEOWNERS are inherently per-repo and can't be made
  reusable in the same way. Each repo copies the example once and
  customises.

Cross-references
----------------
- Rulesets JSON I provided previously needs updating again: the
  required status check contexts now include the `lint /` prefix.
  For this repo's main branch:
    "lint / actionlint", "lint / JSON validity",
    "lint / prettier", "lint / markdownlint", "lint / yamllint"
Why this exists
---------------
Previous Dependabot config split each ecosystem's PRs across
multiple sub-groups (production-minor-and-patch separately from
dev-minor-and-patch, separate PRs for major-version updates, etc.)
and staggered ecosystems across Monday-Friday. The result: a repo
using npm + cargo + pip + github-actions could see 10-15
Dependabot PRs per week, scattered across days.

User's call: that's annoying. Run once a week, produce one PR per
ecosystem with every update bundled in. No per-package PR sprawl,
no per-update-type splits. Major version bumps land in the same PR
as patch bumps; the developer reviews the bundle and decides.

What changed
------------

`.github/dependabot.yml` (this repo, github-actions only):
  - open-pull-requests-limit: 5 -> 1. We only want one PR. The
    previous limit allowed Dependabot to create up to 5 in flight
    if it felt like splitting things; with the single `all` group
    that won't happen, but the cap makes the policy explicit.
  - groups: collapsed `actions:` -> `all:` with explicit
    `update-types: ["major", "minor", "patch"]` so nothing
    escapes the bundle.
  - Header comment rewritten to document the org policy that this
    file embodies.

`.github/dependabot.example.yml` (template for other repos):
  - All ecosystems (github-actions, npm, cargo, pip, plus
    commented docker and gitsubmodule) now use the same
    `groups: { all: { patterns: ["*"], update-types: [...] } }`
    pattern. Single PR per ecosystem.
  - All ecosystems run weekly Monday 06:00 Africa/Harare. Removed
    the Mon-Fri stagger - the staggering only made sense when each
    day produced separate PRs. Now Mondays produce at most one PR
    per ecosystem (so 5 PRs at most for a repo with all ecosystems).
  - open-pull-requests-limit: 10 -> 1 across the board.
  - npm ignore list for next/react/react-dom major-version updates
    is preserved with a clarifying comment that minor and patch
    updates still flow through the `all` group.
  - Removed per-ecosystem grouping rules
    (`runtime-minor-and-patch`, `dev-minor-and-patch`,
    `rust-minor-and-patch`, `python-minor-and-patch`) - all
    replaced with the single `all` group.

Concrete result
---------------
A repo using github-actions + npm + cargo + pip + docker now
receives at most 5 Dependabot PRs per week, all on Monday morning,
each containing every available update for one ecosystem.

Trade-off
---------
Bundling major version bumps with patches in one PR means the
developer can't merge "the easy stuff" while reviewing the breaking
change separately. The user has decided this trade-off is worth it
- volume reduction matters more than per-update merge granularity.
A repo that needs the inverse can split its `all` group locally.

Cross-references
----------------
- README.md "Automation config" rows for both Dependabot files
  updated to call out the weekly-Monday + one-grouped-PR policy.
- CONTRIBUTING.md § Dependencies still applies: new RUNTIME deps
  in human-authored PRs need justification. Dependabot bumps are
  exempt from that since they're version-tracking, not new deps.
Mechanical search-and-replace of `nyuchitech` -> `nyuchi` across all
99 occurrences in 29 tracked files: URLs, repo paths, @team handles
in CODEOWNERS, `uses:` references in reusable workflow header
comments, and the org-search query string in SUPPORT.md.

Prettier re-alignment: the new name is 4 chars shorter, so pipe-
aligned tables in README.md and SUPPORT.md needed re-padding.
`prettier --write` fixed that; no content change beyond whitespace.

Verified locally before commit:
  - prettier --check: clean
  - markdownlint: 0 errors
  - yamllint -s: clean
  - git grep nyuchitech: no remaining occurrences

Pushed via MCP (mcp__github__push_files) because the sandbox's git
remote returned HTTP 503 on all 7 direct-push attempts. MCP still
uses the old `nyuchitech` slug because the session's allowlist is
immutable; GitHub's org-rename redirect translates to `nyuchi/.github`
on the backend.

Out-of-band follow-ups for the user:
  - `git remote set-url origin https://github.com/nyuchi/.github.git`
    on any clone that still tracks the old URL.
  - Verify the teams exist at @nyuchi/maintainers, /security,
    /platform, /docs, /marketing.
  - Rulesets JSON should be POSTed to /repos/nyuchi/.github/rulesets.
Follow-up to 820770e which only landed .prettierignore. This commit
pushes the rest of the 29-file rename: the rest of the .github/
contents (CODEOWNERS, ISSUE_TEMPLATE, PR template, copilot-
instructions, dependabot, workflows) and every root doc.

After this commit, the branch state matches the intended single
logical rename: 99 occurrences of `nyuchitech` across 29 files
replaced with `nyuchi`, plus Prettier re-alignment of two tables
whose column widths no longer lined up with the 4-char-shorter
name.

Same verification as 820770e: prettier --check clean,
markdownlint 0 errors, yamllint -s clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants