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
105 changes: 105 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

`1set/meta` is the **shared CI/CD and quality-gate hub** for the entire Star\*
ecosystem. It is not a library anyone imports for runtime behavior — it is the
single source of truth every `1set/*` and `starpkg/*` module *consumes* so the
whole ecosystem stays uniform: one reusable CI workflow, one lint config, and a
trio of gate tools that enforce the three coverage standards.

## Repository layout

- **`.github/workflows/go-ci.yml`** — the reusable workflow (`on: workflow_call`).
Every consuming repo's `build.yml` is a thin caller pinned to a SHA here.
- **`doccov/`**, **`covcheck/`**, **`footprint/`** — three `main` command
packages, the gate tools.
- **`revive.toml`** — the shared `revive` lint config (fetched at CI time by the
Analyze step; includes the `exported` GoDoc rule and the Lizard nloc limit).
- **`selftest/`** — a fixture *module* used to exercise `go-ci.yml`.
- **`.github/workflows/selftest.yml`** — runs `go-ci.yml` on the fixture + the
tools' `go test`.

### Module layout (important — why there's one go.mod)

The root holds **one** module `github.com/1set/meta` (`go.mod`, go 1.19). The
three tools are command packages *under* it (`github.com/1set/meta/doccov`,
`/covcheck`, `/footprint`), each `package main`. This is deliberate:

- **All three are standard-library only** — zero third-party deps, so the root
go.mod needs no `go.sum`, and there is nothing to isolate per tool.
- **They still build/run independently**: `go run github.com/1set/meta/<tool>@<ref>`
(and `go build ./<tool>/`) compile only that command and its imports — the
other two are not touched. One module ≠ one binary.
- **Per-tool go.mod would be pure overhead** — three go.mod/go.sum to keep in
sync, and no `go test ./...` across them. The idiomatic Go layout for a
multi-command repo is many command packages under one module.
- **`selftest/` is a separate nested module on purpose** — it is the *thing being
tested* and carries its own deps (e.g. go-difflib for a non-empty go.sum); it
must not pollute the tools' module.

## The three gate tools

All are tiny, stdlib-only, and run from CI as `go run …@master` (like
`govulncheck`), gated behind opt-in workflow inputs.

- **`doccov`** — AST-scans non-test `.go` for every `starlark.NewBuiltin(<arg>)`
(resolving `"mod.fn"` and `ModuleName + ".fn"`), and fails if a builtin's name
isn't a backtick word in the README. Checks **omission, not accuracy**.
- **`covcheck`** — parses a `go test` coverage profile (`coverage.txt`), computes
total statement coverage (same math as `go tool cover -func`), and fails below
`-min`. The ecosystem uses it as a **ratchet**: each repo's `cov-min` sits just
below its current coverage, so coverage can only hold or rise.
- **`footprint`** — builds a baseline starlet host vs the host +
`mod.NewModule().LoadModule()`, so the module + its SDKs link, and reports the
marginal binary size (default + stripped). `-json` emits a shields.io badge;
`-max-mb` gates against bloat.

GoDoc completeness is *not* a tool here — it is `revive`'s `exported` rule
(`revive.toml`), run in the Analyze step. Keep that division: doccov = README
surface, revive = GoDoc.

## go-ci.yml design (the part that matters when editing)

- Matrix: `[<go-floor>.x, 1.25.x] × {ubuntu-22.04, macos-14, windows-2022}`.
- Two env-flag legs: **`IS_CHECKS`** (latest Go + Linux) runs the once-only static
checks (vet/gofmt/tidy/govulncheck/doccov); **`IS_REPORT`** (floor + Linux) runs
`make ci` coverage upload + the covcheck/footprint gates (floor for comparable
footprint numbers).
- New gates are added as **opt-in inputs** (default off) so existing callers and
the core libraries are unaffected until they opt in.

## Conventions (non-obvious, easy to get wrong)

- **Self-test before consumers re-pin.** Any change to `go-ci.yml` or a tool must
keep `selftest.yml` green. Consumers pin a SHA; they only get a change after
re-pinning.
- **The `@master` chicken-and-egg.** The workflow runs the tools via
`go run …@master`. A *new* tool isn't on `master` until its PR merges, so its
go-ci.yml step must be gated on an input the `selftest/` fixture does **not**
set — otherwise the self-test PR runs `@master` for a tool that doesn't exist
yet and fails. Add the tool + the (default-off) input together; let real
consumers enable it after merge.
- **Footprint is platform-specific.** linux/amd64 (CI) differs from local
mac/arm64 — gum was 16.5 MB locally, 23.8 MB in CI. Ceilings and badges must use
the **CI value** (read from the PR's footprint step log), or the gate misfires.
- **Coverage is measured without the private suite.** CI has no `starpkg/test`
integration scripts, so `make ci` coverage = unit tests only; set ratchet
floors from that (locally, measure with `starpkg/test` moved aside).
- **Third-party GitHub Actions are pinned to a full commit SHA** (Codacy's
0-new-issue gate flags unpinned actions); tools like revive/tokei are pinned to
a release tag.

## Dev commands

```bash
go test -race ./... # the three tools (selftest fixture is a separate module)
go vet ./... && gofmt -l . # must be clean
go run ./doccov <module-dir> # try a tool locally
```

CI/commit style follows the 1set repos (`[ci]`/`[feat]`/`[chore]`/`[doc]`
prefixes). Changes here ripple to every consumer — prefer additive, opt-in
changes and keep the self-test green.
92 changes: 90 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,90 @@
# meta
1set's meta configuration files 🤿
# meta 🤿

The single source of truth for the **Star\*** ecosystem's shared CI/CD and
quality-gate tooling. Every `1set/*` and `starpkg/*` Go module consumes this
repo so the whole ecosystem stays uniform — one place to fix the pipeline, one
place that defines the standards.

## What's here

| Path | What it is |
|------|------------|
| [`.github/workflows/go-ci.yml`](.github/workflows/go-ci.yml) | the **reusable CI workflow** every module calls |
| [`doccov/`](doccov/) | **doc-coverage** gate — every script builtin is documented in the README |
| [`covcheck/`](covcheck/) | **test-coverage** gate — total coverage may not drop below a floor (ratchet) |
| [`footprint/`](footprint/) | **binary-footprint** gate + badge — the marginal binary size a module adds |
| [`revive.toml`](revive.toml) | the shared `revive` lint config (incl. the `exported` GoDoc rule) |
| `selftest/`, `.github/workflows/selftest.yml` | proves `go-ci.yml` + the tools end-to-end inside meta |

`doccov`, `covcheck`, and `footprint` are three `main` command packages under the
**single module `github.com/1set/meta`** (one root `go.mod`, go 1.19,
**standard-library only — no go.sum**). Each builds and runs independently:
`go run github.com/1set/meta/<tool>@<ref>` compiles just that command. (The
`selftest/` fixture is a separate nested module on purpose — it has its own
deps.)

## Reusable CI workflow

A consuming repo's `.github/workflows/build.yml` is a thin caller:

```yaml
name: Build
on:
push: { branches: ["master"] }
pull_request: { branches: ["master"] }
permissions: read-all
jobs:
ci:
uses: 1set/meta/.github/workflows/go-ci.yml@<pinned-sha>
with:
go-floor: "1.19" # this repo's go.mod floor minor version
# doc-coverage: true # opt in to the doccov gate
# cov-min: 78 # test-coverage ratchet floor (0 = no gate)
# footprint: true # measure & badge the binary footprint
# footprint-max-mb: 7 # fail if the stripped footprint delta exceeds this
secrets: inherit
```

It runs the matrix `[<go-floor>.x, 1.25.x] × {ubuntu-22.04, macos-14,
windows-2022}`. Static checks (vet, gofmt, `go mod tidy`, govulncheck, doccov)
run once on the latest-Go Linux leg; coverage upload + the covcheck/footprint
gates run on the floor Linux leg.

### Inputs

| Input | Type | Default | Meaning |
|-------|------|---------|---------|
| `go-floor` | string | (required) | the repo's go.mod floor minor, e.g. `"1.19"` / `"1.22"` |
| `working-directory` | string | `"."` | path to the Go module within the repo |
| `doc-coverage` | bool | `false` | run the **doccov** README↔builtin gate |
| `cov-min` | number | `0` | **covcheck** test-coverage floor (`0` = no gate) |
| `footprint` | bool | `false` | run **footprint** (badge + bloat gate) |
| `footprint-max-mb` | number | `0` | fail if the stripped footprint delta exceeds this many MB |

Pin the `@<sha>` for supply-chain safety; bump it when this workflow changes.

## Gate tools

Each tool also runs standalone for local checks (see its README):

```bash
go run github.com/1set/meta/doccov@master . # docs ↔ builtins
go run github.com/1set/meta/covcheck@master -min 70 coverage.txt # test coverage floor
go run github.com/1set/meta/footprint@master -modpath github.com/starpkg/sqlite -dir . # binary size
```

- **doccov** ([README](doccov/README.md)) — AST-scans every `starlark.NewBuiltin(...)` and fails if it isn't documented (backtick) in the README.
- **covcheck** ([README](covcheck/README.md)) — parses the `make ci` coverage profile and fails below `-min`. Policy is a **ratchet**: each repo's floor sits just below its current coverage, so it can only hold or improve.
- **footprint** ([README](footprint/README.md)) — builds a bare starlet host vs the host + module and reports the marginal binary size (default + stripped); emits a shields.io badge and gates against silent bloat. Numbers are platform-specific — use the CI (linux/amd64) value.

GoDoc completeness (a doc comment on every exported symbol) is enforced by
`revive`'s `exported` rule from `revive.toml`, in the workflow's Analyze step.

## Self-test

`selftest.yml` calls `go-ci.yml` against the `selftest/` fixture (so any change
to the workflow is proven before consumers re-pin) and runs `go test ./...` over
the gate tools. Note the **chicken-and-egg**: a new tool isn't on `master` until
its PR merges, so don't enable its `go-ci.yml` input in the fixture in the same
PR — keep new tool inputs opt-in (default off) and let consumers turn them on
after the merge.
Loading