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
20 changes: 10 additions & 10 deletions .aqua/aqua-checksums.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,28 @@
"algorithm": "sha256"
},
{
"id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_darwin_arm64.tar.gz",
"checksum": "E73ED78C7076867ACE43D7EBC1A1254CCAE50D5992095B405CAD7F2F10787B89",
"id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_darwin_arm64.tar.gz",
"checksum": "BB92510A0019EE03BC520C58528EE828D108BD9D15279FA26F5BE99E23D53FCE",
"algorithm": "sha256"
},
{
"id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_linux_amd64.tar.gz",
"checksum": "63A637D15AB78CE8C1ED4CC64CBEE419E467BAADB4FF290C04228D106B0BFF22",
"id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_amd64.tar.gz",
"checksum": "7828CCC8019C6E7E582A16E9D29BB9A00F8F00F73A7B95005597129121AFCB1B",
"algorithm": "sha256"
},
{
"id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_linux_arm64.tar.gz",
"checksum": "E2D2BB0DE0D81092D2D590DAB80E71979A0EA883E5F81A3DF59F034703F4E0ED",
"id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_arm64.tar.gz",
"checksum": "845F202D468F23ED2FF4D8BA7E2B3328F98B40AD555B5C651F223F568065C9B7",
"algorithm": "sha256"
},
{
"id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_windows_amd64.tar.gz",
"checksum": "9FF90A269A63C6739FD1AB73FD5262BB98D36145916E0D28D3E873453BDD6D57",
"id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_amd64.tar.gz",
"checksum": "250B810AC310B3A4083BB589BDB694512FB4CBD2E172B8DE000AAD698DDEB99C",
"algorithm": "sha256"
},
{
"id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_windows_arm64.tar.gz",
"checksum": "EF61BC2AD757370C00AD313F2BBD247D7847E8C1A1F8E14FB72EBBCB19FEB12A",
"id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_arm64.tar.gz",
"checksum": "2DA4558D67D975137800E35005374A3523F79F2D70D19A9FFAC47B75E73A9874",
"algorithm": "sha256"
},
{
Expand Down
2 changes: 1 addition & 1 deletion .aqua/aqua.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ packages:
# are go.mod tools"). aqua's go_install would build it once per tool
# version, with whichever go ran first, and share that binary.
# --- farcloser tools (local registry; standard once registered upstream) ---
- name: farcloser/limen@v0.8.0 # renovate: depName=farcloser/limen
- name: farcloser/limen@v0.9.0 # renovate: depName=farcloser/limen
registry: local
- import: ../.limen/aqua.yaml
198 changes: 198 additions & 0 deletions .github/workflows/limen-verify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# DO NOT EDIT MANUALLY: content-pinned by limen, reset by `limen fix`.
# The shared CI lanes every repository runs, called from its own ci.yaml
# (`uses: ./.github/workflows/limen-verify.yaml`). A change here reaches every
# repository with its limen bump; the project's own jobs live in its ci.yaml.
#
# Minimal glue: GitHub's own actions pinned by SHA, aqua at .aqua/aqua.yaml's
# pins, `just` running the recipes a laptop runs. No third-party actions,
# hence no egress-filtering action either.
name: limen-verify

on:
workflow_call:
inputs:
os:
# Pinned images, never -latest. macOS is not redundant: its /bin/bash
# is 3.2, the floor the recipes target. Both windows legs run under
# git-bash. These names are NOT the ruleset's required contexts (the
# caller's `gate` is), so a project may trim or extend the list.
description: The verify matrix's runners, as a JSON list.
type: string
default: '["ubuntu-24.04", "ubuntu-24.04-arm", "macos-15", "windows-2025", "windows-11-arm"]'

# No default token permissions: each job states what it needs, within what
# the caller grants (contents: read).
permissions: {}

# Explicit bash everywhere: windows defaults to PowerShell and never sees
# git-bash's environment; explicit bash also brings -eo pipefail.
defaults:
run:
shell: bash

jobs:
verify:
strategy:
fail-fast: false
matrix:
os: ${{ fromJSON(inputs.os) }}
runs-on: ${{ matrix.os }}
# Generous for the windows legs, which are markedly slower.
timeout-minutes: 45
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The token is not left behind in .git/config: nothing in this
# workflow talks to GitHub after checkout.
persist-credentials: false
# Full history and refs: `just do lint commits` validates the commit
# range against the PR's base branch, which a shallow clone lacks.
fetch-depth: 0

# Windows keeps every cache below in one disk image, cached as a single
# file: restored as files, they cost minutes of small-file writes there.
# Before setup-aqua, which takes the AQUA_ROOT_DIR it sets; the hash is
# taken here because hashFiles cannot walk into the attached image.
- id: image
if: runner.os == 'Windows'
uses: ./.github/actions/windows-cache-image
with:
mode: attach
prefix: win-image-${{ runner.os }}-${{ runner.arch }}-
hash: ${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua.yaml', '.limen/aqua-registry.yaml', '**/go.sum') }}

- name: Install aqua (pinned, checksum-verified)
uses: ./.github/actions/setup-aqua

# aqua's package store, keyed on the exact pins and per architecture
# (the store holds native binaries). A warm store turns every lazy first
# use below into a link; the fallback key keeps the tools a pin bump did
# not touch, and the changed one downloads and verifies as before.
- if: runner.os != 'Windows'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.AQUA_ROOT_DIR }}/pkgs
key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }}
restore-keys: |
aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-

- name: Install pinned tools
# Link-only: tools download lazily on first use, checksum-verified; a
# job pays only for what its recipes run.
run: aqua install --only-link

# The Go build and module caches, plus the linter's (build/cache/, see
# main.just). The per-platform analysis legs compile the whole module
# graph five times, cold, on every run without this. Paths come from the
# pinned go itself — they differ per OS — and the key carries its version
# and every go.sum; a stale restore is harmless (the build cache is
# content-addressed, the module cache is checksum-verified), so the
# fallback key is always worth taking.
- id: gocache
if: runner.os != 'Windows'
run: |
echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
echo "mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
echo "version=$(go env GOVERSION)" >> "$GITHUB_OUTPUT"
- if: runner.os != 'Windows'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
${{ steps.gocache.outputs.build }}
${{ steps.gocache.outputs.mod }}
build/cache
key: go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}-
go-${{ runner.os }}-${{ runner.arch }}-

- name: Lint
run: just lint

- name: Test
run: just test

# On a miss, the image this run filled becomes the key's; a hit needs no
# save, since a cache key is never overwritten.
- if: runner.os == 'Windows' && steps.image.outputs.cache-hit != 'true'
uses: ./.github/actions/windows-cache-image
with:
mode: save
key: ${{ steps.image.outputs.key }}

# One linux leg, not the matrix: fuzzing explores the same corpus wherever
# it runs, so more legs cost CPU and add nothing. The recipe passes on a
# tree with no targets or no Go module, so this job is safe in every
# repository.
fuzz:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install aqua (pinned, checksum-verified)
uses: ./.github/actions/setup-aqua

- name: Install pinned tools
run: aqua install --only-link

# The cached corpus is what makes fuzzing cumulative across runs. Keyed
# on the fuzz sources so a changed target restarts its corpus;
# restore-keys keep the rest.
- id: fuzzdir
run: echo "dir=$(go env GOCACHE)/fuzz" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.fuzzdir.outputs.dir }}
key: fuzz-corpus-${{ runner.os }}-${{ hashFiles('**/*_fuzz_test.go', '**/fuzz_test.go') }}
restore-keys: |
fuzz-corpus-${{ runner.os }}-

- name: Fuzz
run: just do test go fuzz

# A crasher is a bug with a reproducer attached; the log names the
# target, the input file is what reproduces it locally. Uploaded only
# on failure, and only if any was written.
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: fuzz-crashers
path: '**/testdata/fuzz/'
if-no-files-found: ignore

# Every pin resolves. The verify legs link only, and a link verifies
# nothing: an asset that is missing from a release, or that fails its
# checksum, would merge green and fail at first use. One linux leg pays for
# the real install; aqua's package store is cached on the exact pins, so a
# warm key costs seconds and a changed pin is always a real install. (The
# Go-built tools are tools/go.mod directives, built by the verify legs.)
tools:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install aqua (pinned, checksum-verified)
uses: ./.github/actions/setup-aqua

- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.AQUA_ROOT_DIR }}/pkgs
# The same key as the verify legs', so this job and the linux leg
# share one store — and no fallback key here: a changed pin must be
# a real install, which is what this job exists to prove.
key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }}

- name: Install every pinned tool
run: aqua install
9 changes: 4 additions & 5 deletions .github/workflows/security.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Seeded once by `limen fix`, then the project's own: limen never updates it.
# Realign it by hand when limen's canonical moves, and comment every project
# line so the next alignment keeps it.
# DO NOT EDIT MANUALLY: content-pinned by limen, reset on drift. A project's
# own scans go in its `security` recipe in the root .justfile, which this runs.
#
# The security lane, apart from ci: a scan's verdict moves with the
# vulnerability database, not with the tree, so this runs on a schedule as
Expand Down Expand Up @@ -51,7 +50,7 @@ jobs:
- name: Install aqua (pinned, checksum-verified)
uses: ./.github/actions/setup-aqua

# aqua's package store, keyed on the exact pins (see ci.yaml).
# aqua's package store, keyed on the exact pins (see limen-verify.yaml).
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.AQUA_ROOT_DIR }}/pkgs
Expand All @@ -64,7 +63,7 @@ jobs:
run: aqua install --only-link

# The Go build and module caches: the scan compiles the module graph
# once per supported platform, cold without this (see ci.yaml).
# once per supported platform, cold without this (see limen-verify.yaml).
- id: gocache
run: |
echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
Expand Down
5 changes: 4 additions & 1 deletion .limen/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"vulnerabilityAlerts: fix PRs from GitHub's Dependabot alerts (the baseline keeps the alerts on for exactly this; Dependabot's own update PRs are off, Renovate is the one bot). Stated explicitly because enabled: true here is what reaches // indirect Go modules, which the gomod manager skips by default. Fix PRs skip the cooldown and take the lowest fixed version, both Renovate defaults for this object.",
"postUpdateOptions gomodTidy: a Go module bump rewrites go.mod; without tidy, go.sum keeps the old module's lines and `just do lint go mod` (go mod tidy, diffed) fails every gomod PR. gomodUpdateImportPaths is what keeps tidy running on a MAJOR bump: without it Renovate skips tidy on every major (its guard for the /vN import-path rewrite, which must precede tidy), and a v0 to v1 bump is major while changing no path, so the PR landed a go.sum with the new go.mod hash, no h1 line and the old pseudo-version lines still in it, green for Renovate and red on the tidy diff. With the option, v0 to v1 does no path rewrite and tidies; a real /vN bump rewrites the imports with marwan-at-work/mod (which Renovate go-installs at run time, no image support needed) and then tidies.",
"constraints gomodMod: the version Renovate go-installs marwan-at-work/mod at for that rewrite; without it the install is @latest, a third-party binary that rewrites import paths across the tree fetched at whatever is newest the day a /vN bump lands. Pinned means by digest, and this lever takes only a v-prefixed tag, so this is the nearest the lever allows: a version pin, its content fixed and transparency-logged by the checksum database that go install verifies against. Named here as that exception rather than left implicit. Renovate's own config manager marks this constraint unsupported and never scans a preset, so limen's renovate.json watches the value with a custom manager and proposes its bumps there.",
"packageRules, content-pinned files: the checksum-update workflow, the setup-aqua action and everything under .limen/ are limen's byte for byte; a bump Renovate makes inside one is drift the next `limen check` rejects. Their pins move in limen's own repository, the one place Renovate keeps touching them, and reach every other repository through a limen release.",
"packageRules, content-pinned files: the checksum-update workflow, the shared CI lanes (limen-verify.yaml), the security workflow, the setup-aqua and windows-cache-image actions and everything under .limen/ are limen's byte for byte; a bump Renovate makes inside one is drift the next `limen check` rejects. Their pins move in limen's own repository, the one place Renovate keeps touching them, and reach every other repository through a limen release.",
"packageRules, Go-built tools: the tool directives under tools/ (git-validation, godolint, dot, the Go-source analyzers deadcode, govulncheck, go-licenses, and golangci-lint in tools/golangci-lint/go.mod, a module of its own so its analyzers' dependencies stay upstream's), which Go lists as // indirect and the gomod manager would skip; re-enabled exactly, by module. dot is forkcloser's own tagged module (upstream's cmd/dot is a nested module without tags), so it moves like the others, and as one of ours it is proposed without the cooldown.",
"packageRules, groups: one pull request per repository per kind, not one per dependency — a registry ref that moves most days and a dozen tool pins were arriving as a dozen pull requests a month per repository, each reviewed alone. Three groups: `aqua tools` (every third-party pin in .aqua/aqua.yaml, the standard-registry ref and aqua itself included), `github actions`, and `go modules` (non-major gomod bumps, the tools/go.mod directives included). Renovate keeps the one open pull request current as further versions arrive instead of opening another. Grouped, not scheduled: a bump still arrives as soon as its cooldown ends. Outside the groups on purpose: our own releases (immediate and alone, so a limen bump is its own reviewed change), major Go bumps (code may have to move), pins.yaml entries (each a deliberate supply-chain change), and security fixes, which Renovate's vulnerabilityAlerts defaults keep ungrouped. A repository that wants a dependency out of a group adds a later packageRule with groupName null.",
"packageRules, lychee: releases are tagged lychee-vX.Y.Z next to lychee-lib-vX.Y.Z and nightly; regex versioning that carries the prefix parses the pin and the candidates alike and excludes the others by construction.",
Expand Down Expand Up @@ -61,6 +61,9 @@
"matchFileNames": [
".github/workflows/update-aqua-checksum.yaml",
".github/actions/setup-aqua/**",
".github/actions/windows-cache-image/**",
".github/workflows/limen-verify.yaml",
".github/workflows/security.yaml",
".limen/**"
],
"matchRepositories": [
Expand Down
12 changes: 10 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ chapter; the procedure is limen's `skills/contribute`.
human's own work, the human is the author. No scratchpads (`AUDIT.md` and its kind):
they live under `_scratch/` at the repository root, which `.gitignore` ignores, and
nowhere else in the tree.
- **The pull request's title is its release note.** A release's notes are the titles of
the pull requests it merged, so a title says what changed for a consumer; no
`CHANGELOG.md` is kept by hand. One that breaks a consumer carries the `breaking` label
(`gh label create breaking` the first time a repository needs it).
- **One commit per thing.** Different things get different commits; iteration on the
same thing — a review round, a fix to your own commit — is squashed into the commit it
amends before the review is requested. Never a stack of fix-ups for one change.
Expand Down Expand Up @@ -59,8 +63,9 @@ chapter; the procedure is limen's `skills/contribute`.
- **A flake is fixed when it is noticed.** A check that fails, then passes on a rerun, gets
its root cause and its fix at once, in a pull request of its own: by whoever noticed it,
or by the owning session when it is another repository's. The rerun found the flake; it
did not fix it. The one exception is a flake whose cause is known and whose fix was
declined, documented as such (windows-11-arm's silent exit 4 or 127): it is rerun, and named.
did not fix it. The exceptions are flakes whose cause is known and whose fix was
declined, documented as such in the book's known upstream bugs (windows-11-arm's silent
exit 4 or 127, an aqua download that stalls with no timeout): each is rerun, and named.
- **Doctrine can lose the argument, never silently.** A fix that cuts against the book is
named as such and argued; it is decided, not discovered.
- **Broken tooling is reported, never worked around in silence.** The rig — limen, the
Expand All @@ -77,6 +82,9 @@ chapter; the procedure is limen's `skills/contribute`.
`just lint` and `just test` green, the commit message written — ready to commit and push
when the human is back, and say so once. Signing that never worked in the session is
broken tooling (above).
- **Read what the work needs, never the whole disk.** A targeted read outside the
repositories is fine when the work calls for it; a filesystem-wide walk is not: no
`find /`, `find ~`, disk-wide `mdfind`, or recursive grep over `/` or `~`.

## Communication

Expand Down
Loading