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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# EditorConfig — https://editorconfig.org
# Keeps whitespace consistent across editors and IDEs before
# Prettier / rustfmt / ruff get a chance to run.

root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
# Markdown: trailing spaces are significant (hard line-break).
# Let Prettier handle wrapping; don't trim here.
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2

[*.{json,jsonc}]
indent_size = 2

[*.toml]
indent_size = 4

[Makefile]
indent_style = tab

[*.rs]
# rustfmt handles Rust formatting; EditorConfig just sets the baseline.
indent_size = 4

[*.py]
indent_size = 4

[*.{cpp,cc,c,h,hpp}]
indent_size = 4
37 changes: 37 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Lint CI for mukoko-dev/.github.
#
# Calls the shared lint workflow in nyuchi/.github. That repository is
# public, so a reusable workflow in it can be called from any repository
# in any organisation — including private ones. This is the same wiring
# bundu-labs and mzizi-dev already use, and it is deliberate: the org's
# lint policy lives in exactly one place and is not copied per org.
#
# The job MUST be called `lint`. A reusable-workflow call publishes its
# checks as "<caller job>/<called job>", which is what produces the five
# contexts the mukoko-dev org ruleset requires:
#
# lint / actionlint lint / markdownlint
# lint / JSON validity lint / yamllint
# lint / prettier
#
# Every repository in this org needs a file exactly like this one, or its
# PRs can never merge.

name: Lint

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
uses: nyuchi/.github/.github/workflows/reusable-lint.yml@main
23 changes: 23 additions & 0 deletions .github/workflows/pr-title-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Conventional-Commits check on the PR title.
#
# Both org rulesets allow squash merges only, so the PR title becomes the
# commit subject on main. Linting it here is what keeps main's history
# machine-readable for release tooling.

name: PR title

on:
pull_request_target:
types: [opened, edited, reopened, synchronize]

permissions:
pull-requests: read
statuses: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
lint:
uses: nyuchi/.github/.github/workflows/reusable-pr-title-lint.yml@main
16 changes: 16 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Marks abandoned issues and PRs stale on a schedule.

name: Stale

on:
schedule:
- cron: '23 1 * * *'
workflow_dispatch:

permissions:
issues: write
pull-requests: write

jobs:
stale:
uses: nyuchi/.github/.github/workflows/reusable-stale.yml@main
39 changes: 39 additions & 0 deletions .markdownlint.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// markdownlint-cli2 config for mukoko-dev/.github.
//
// Relaxed defaults that accommodate:
// - Long prose lines in docs (we format for readability, not width)
// - Inline HTML in the org profile (<div align="center">)
// - Files that start with an HTML comment or a <div> instead of an H1
// - Fenced code blocks without a language tag
//
// See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
{
"default": true,

// Line length — we wrap for readability, not at a fixed column.
"MD013": false,

// Inline HTML — profile/README.md uses <div align="center"> for the
// landing-page layout and that's intentional.
"MD033": false,

// First line must be a top-level heading — profile/README.md opens
// with an HTML comment, which is correct.
"MD041": false,

// Fenced code blocks without a language — acceptable for output
// blocks and plain-text fences.
"MD040": false,

// Duplicate headings are fine as long as they aren't siblings
// (e.g. "## Summary" appears in multiple docs; that's expected).
"MD024": { "siblings_only": true },

// Unordered list indentation — 2 spaces, matching our house style.
"MD007": { "indent": 2 },

// Table-column-style — re-enabled now that Prettier is in CI and
// auto-formats tables. Prettier's table output should satisfy
// MD060's "aligned" style.
"MD060": true,
}
22 changes: 22 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Prettier ignore for mukoko-dev/.github.

# Standard ignores
node_modules/
.git/

# Files with structural meaning that Prettier would harm
LICENSE
CODEOWNERS
.github/CODEOWNERS

# YAML is handled by yamllint and actionlint, not Prettier — they have
# stronger guarantees about GitHub Actions semantics that Prettier does
# not understand (e.g. the `on:` truthy quirk).
*.yml
*.yaml

# Lockfiles, generated output
*-lock.json
*-lock.yaml
package-lock.json
pnpm-lock.yaml
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"printWidth": 80,
"proseWrap": "preserve",
"tabWidth": 2,
"useTabs": false,
"endOfLine": "lf",
"trailingComma": "all",
"overrides": [
{
"files": ["*.md", "*.mdx"],
"options": {
"embeddedLanguageFormatting": "off"
}
}
]
}
57 changes: 57 additions & 0 deletions .yamllint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# yamllint config for mukoko-dev/.github.
#
# Relaxed defaults that accommodate:
# - GitHub Actions workflows (which use `on:` — a YAML 1.1 boolean)
# - Long lines in shell heredocs and comment blocks inside workflows
# - Files without `---` document-start markers
#
# See https://yamllint.readthedocs.io/en/stable/configuration.html

extends: default

ignore: |
node_modules/

rules:
# 140-char limit. Workflow files have legitimately long shell URLs
# (the actionlint download script, action references) and issue-
# template `about:` strings render as a single line in the picker,
# so wrapping them would change user-visible behaviour. 140 is
# tight enough to flag truly excessive lines without forcing
# awkward breaks on URLs. Strict CI fails on any violation.
line-length:
max: 140
level: error
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true

# GitHub Actions uses `on:` as a key, which YAML 1.1 treats as a
# boolean. Disable the truthy check on keys to stop yamllint from
# rejecting every workflow.
truthy:
check-keys: false
level: error

# We don't prefix YAML files with `---`.
document-start: disable

# Be lenient about how many spaces follow `#` in comments.
comments:
min-spaces-from-content: 1

# Comments-indentation flags standalone comments that don't match the
# indent of surrounding content. Real-world configs use comments to
# delimit sections (long `# ----` separator banners) and to mark
# commented-out blocks at the level of the code they replace, neither
# of which fits the rule cleanly. Disabled.
comments-indentation: disable

# GitHub issue forms have deeply nested lists and mappings.
indentation:
spaces: 2
indent-sequences: consistent

# Stops a warning on the trailing-empty-line check for files that
# end cleanly with a single newline.
empty-lines:
max-end: 1
87 changes: 86 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,86 @@
# .github
# mukoko-dev/.github

Org-wide defaults for `mukoko-dev`: the shared CI wiring, the lint
configuration every repository inherits, and the notes explaining why both
are shaped the way they are.

## CI is not written here

The reusable workflow library lives in [`nyuchi/.github`][hub] and is shared
across the whole estate. That repository is **public**, and a reusable
workflow in a public repository can be called from any repository in any
organisation — private callers included. So `mukoko-dev` repositories call
those workflows directly rather than keeping a second copy of them.

This is already how `bundu-labs` and `mzizi-dev` consume the library. Copying
the workflows into this org would double the maintenance surface and let the
two copies drift apart silently, which is the failure this arrangement is
meant to avoid.

Only add a workflow file here when the behaviour genuinely differs from the
shared one. If you ever do copy a reusable workflow into this org, say so in
the PR and record why, so the divergence is deliberate and visible.

[hub]: https://github.com/nyuchi/.github/tree/main/.github/workflows

## Every repository needs `lint.yml`

The org ruleset `org-wide-main-protection` requires five status checks on the
default branch of every repository:

```text
lint / actionlint
lint / JSON validity
lint / prettier
lint / markdownlint
lint / yamllint
```

Those names are not free-form. A job that calls a reusable workflow publishes
its checks as `<caller job> / <called job>`, so the five strings above are
produced by a job named exactly `lint` calling `reusable-lint.yml`, whose own
jobs are named `actionlint`, `JSON validity`, and so on.

Defining the five as ordinary top-level jobs does **not** work. They then
report as bare `actionlint`, `JSON validity`, … , the five required contexts
never report at all, and a required context that never reports is
permanently pending — every pull request in the repository is blocked
forever, with every visible check green. Copy
[`.github/workflows/lint.yml`](.github/workflows/lint.yml) verbatim.

## Per-repository CI

Beyond lint, pick the reusable workflow that matches the project and call it
from a workflow in that repository:

| project shape | reusable workflow |
| -------------------------------------- | ------------------------------------------------------- |
| Next.js app or pnpm/Turborepo monorepo | `reusable-ci-nextjs-monorepo.yml` |
| TypeScript application | `reusable-ci-typescript.yml` |
| Published TypeScript package | `reusable-ci-typescript-lib.yml` |
| any repository | `reusable-codeql.yml`, `reusable-dependency-review.yml` |

## Lint configuration

`.prettierrc`, `.prettierignore`, `.markdownlint.jsonc`, `.yamllint.yaml` and
`.editorconfig` in this repository are the org baseline. The lint tools
auto-discover them from the repository root, so copy them into each
repository rather than pointing at these.

Two adjustments come up often:

- **`.yamllint.yaml` must ignore your lockfile.** `pnpm-lock.yaml` has lines
far past any sane width, and the reusable runs `yamllint -s`, which
promotes warnings to errors.
- **Prettier and `.mdx` do not mix.** Prettier still parses `.mdx` with its
legacy MDX1 parser and rewrites MDX2 expression comments — `{/* … */}`
becomes `{/_ … _/}` — corrupting the file. If a repository has `.mdx`
content, pass a `prettier-glob` that excludes it.

## Merge method

The org ruleset allows **squash merges only**, alongside
`required_linear_history` and `required_signatures`. Note that the
repository-level settings in this org still leave merge-commit and rebase
switched on; the ruleset is what actually decides, and it permits squash
alone.