diff --git a/.github/workflows/limen-verify.yaml b/.github/workflows/limen-verify.yaml index 25714dd..086943c 100644 --- a/.github/workflows/limen-verify.yaml +++ b/.github/workflows/limen-verify.yaml @@ -19,6 +19,14 @@ on: 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"]' + submodules: + # A test suite that reads its fixtures from a git submodule (a + # conformance corpus kept upstream) has them only if the checkout + # fetches them. Forwarded to actions/checkout by the jobs that run + # tests, verify and fuzz; tools installs pins and never needs it. + description: Submodule checkout for the test jobs, as actions/checkout takes it ("false", "true" or "recursive"). + type: string + default: 'false' # No default token permissions: each job states what it needs, within what # the caller grants (contents: read). @@ -47,6 +55,7 @@ jobs: # The token is not left behind in .git/config: nothing in this # workflow talks to GitHub after checkout. persist-credentials: false + submodules: ${{ inputs.submodules }} # 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 @@ -94,6 +103,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + submodules: ${{ inputs.submodules }} - name: Install hermetic (pinned, verified) uses: ./.github/actions/setup-hermetic diff --git a/.hermetic/aqua-checksums.json b/.hermetic/aqua-checksums.json index c2b19bd..1922316 100644 --- a/.hermetic/aqua-checksums.json +++ b/.hermetic/aqua-checksums.json @@ -46,28 +46,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.15.1/limen_0.15.1_darwin_arm64.tar.gz", - "checksum": "B7D7EE4E515E2FDC64B89CA2A3C52FADF7A0DF35F6EFE172CFC337B69A51789B", + "id": "github_release/github.com/farcloser/limen/v0.16.0/limen_0.16.0_darwin_arm64.tar.gz", + "checksum": "A7AD03A9F501636531136B3A7DFFC1CEE4DFA412700BC541BE48DD236D8CF451", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.15.1/limen_0.15.1_linux_amd64.tar.gz", - "checksum": "4A2932003514F5C682FCB95F057DD9A5EE2932810DEF38CD08F0DC1D18B10B44", + "id": "github_release/github.com/farcloser/limen/v0.16.0/limen_0.16.0_linux_amd64.tar.gz", + "checksum": "7373510BEC311B1159733A2838ED1BBA1A0C691D1616E40EF2F0FE7B6717EF1A", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.15.1/limen_0.15.1_linux_arm64.tar.gz", - "checksum": "37E85C3D7C41A39D7E8BA3F98BFD29946C4F76305F7E6330FBFA196EB6154B3C", + "id": "github_release/github.com/farcloser/limen/v0.16.0/limen_0.16.0_linux_arm64.tar.gz", + "checksum": "2DEFD677A47496BF88CEFC7317F2463DE6104155E697720F71D369EA60235830", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.15.1/limen_0.15.1_windows_amd64.tar.gz", - "checksum": "D5B9D6D2F957A4D0380DC2A4DD1E37F8CF0E41633790E3231C4B2460A1B323D0", + "id": "github_release/github.com/farcloser/limen/v0.16.0/limen_0.16.0_windows_amd64.tar.gz", + "checksum": "A8025B69076A774C872E43F42AD358295930CF8A5CF07D03FBF2AE14C7434971", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.15.1/limen_0.15.1_windows_arm64.tar.gz", - "checksum": "B82FF644AB4DDFADDED490CF977770CD9BFDD717634F4B1679C646B8F2A64826", + "id": "github_release/github.com/farcloser/limen/v0.16.0/limen_0.16.0_windows_arm64.tar.gz", + "checksum": "F4603D256E221255F277C2087645828D778DC14B108A5360241115E637533524", "algorithm": "sha256" }, { diff --git a/.hermetic/hermetic.yaml b/.hermetic/hermetic.yaml index e8b95cb..bbfd1aa 100644 --- a/.hermetic/hermetic.yaml +++ b/.hermetic/hermetic.yaml @@ -19,7 +19,7 @@ registries: packages: # The limen this repository runs, and with it every canonical file it # writes: the one canonical pin kept here, where Renovate bumps it. - - name: farcloser/limen@v0.15.1 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.16.0 # renovate: depName=farcloser/limen registry: local # The Go toolchain is the repository's own pin, seeded from limen's own # manifest once and Renovate's here afterwards: a standard-library fix diff --git a/.limen/just/build-go.just b/.limen/just/build-go.just index 00c38da..cd27413 100644 --- a/.limen/just/build-go.just +++ b/.limen/just/build-go.just @@ -21,8 +21,11 @@ import 'lib.just' # netgo/osusergo force the pure-Go resolver and user lookups. No-ops when # CGO_ENABLED=0 (pure Go already); with CGO they keep DNS and user handling -# out of libc, which is exactly what makes `static` viable on glibc. -go_tags := 'netgo,osusergo' +# out of glibc, which is exactly what makes `static` viable there. Linux only: +# on macOS libc is the one road to Open Directory users (the pure-Go os/user +# reads /etc/passwd alone, so user.Lookup of a real account fails) and to +# scoped DNS, and there is no static libc to keep clear of. +go_tags := if os() == 'linux' { 'netgo,osusergo' } else { '' } # CGO is off unless a project exports GO_CGO := '1' (or CGO_ENABLED=1 for one # run); resolution in lib.just. diff --git a/.limen/just/lint-homebrew.just b/.limen/just/lint-homebrew.just index f2a5f50..d34ef3c 100644 --- a/.limen/just/lint-homebrew.just +++ b/.limen/just/lint-homebrew.just @@ -14,9 +14,12 @@ import 'lib.just' # RuboCop would not know the formula cops. brew is not on the hermetic PATH on # purpose — it is the subject under test, not a substitute for a pinned tool; # BREW_BIN is captured from the ambient PATH in main.just. `style` (and its -# fixing twin) may run from another brew than the machine's: BREW_STYLE_BIN, -# exported by the project's .justfile, defaults to BREW_BIN and leaves `audit` -# and `test` on the machine's, which installs and audits for real. +# fixing twin) and `audit` may each run from another brew than the machine's: +# BREW_STYLE_BIN and BREW_AUDIT_BIN, exported by the project's .justfile, +# default to BREW_BIN. The machine's brew cannot run under a session sandbox +# (it writes under its own prefix and caches) and refuses a tap it already has +# installed, so without them `audit` is CI-only in a tap repository. `test` +# stays on the machine's, which installs for real. default: style audit @@ -89,7 +92,7 @@ audit: (_banner "lint homebrew" "audit") exit 0 fi # shellcheck disable=SC2154 # BREW_BIN is exported by the canonical .justfile (main.just). - brew="${BREW_BIN:-}" + brew="${BREW_AUDIT_BIN:-${BREW_BIN:-}}" {{ _require_brew }} {{ _tap_link }} # shellcheck disable=SC2086 # deliberate word-split of the flags knob. @@ -108,8 +111,8 @@ _skip_unless_macos := ''' ''' # Refuses when the brew the recipe chose ($brew) is empty: main.just's ambient -# capture came up empty and the project exported no BREW_STYLE_BIN. Twin in -# fix-homebrew.just. +# capture came up empty and the project exported no BREW_STYLE_BIN or +# BREW_AUDIT_BIN. Twin in fix-homebrew.just. [private] _require_brew := ''' if [ -z "${brew:-}" ]; then diff --git a/.limen/just/perf-go.just b/.limen/just/perf-go.just index b2d6726..46fcbcd 100644 --- a/.limen/just/perf-go.just +++ b/.limen/just/perf-go.just @@ -113,14 +113,10 @@ profile: (_banner "perf go" "profile") (_go-tool "dot" "github.com/forkcloser/do docs="$LIMEN_BUILD/profiles-docs" mkdir -p "$prof" "$docs" # Rendered from pprof's -dot text rather than pprof's own -png, which - # shells out to a dot it expects on PATH. -o with a pre-created file: under - # a sandbox the WASM runtime can overwrite files but not always create - # them. Paths stay cwd-relative for the same reason (the runtime preopens - # only the working directory). + # shells out to a dot it expects on PATH. render() { src=$1; png=$2; shift 2 go tool pprof -dot -nodecount=20 "$@" "$src" > "$png.dot" 2>/dev/null - : > "$png" "$LIMEN_BUILD/tools/dot" -Tpng -o "$png" "$png.dot" rm -f "$png.dot" echo " -> $png" @@ -128,7 +124,7 @@ profile: (_banner "perf go" "profile") (_go-tool "dot" "github.com/forkcloser/do for pkg in $(go list ./...); do # One directory per package keyed by the FULL import path: flattening # collides (example/a__b vs example/a/b, '_' being legal in import - # paths). Paths stay cwd-relative for the WASM dot (see render). + # paths). mkdir -p "$prof/$pkg" "$docs/$pkg" echo "Profiling $pkg..." go test -count=1 -o "$prof/$pkg/pkg.test" "$pkg" \ diff --git a/AGENTS.md b/AGENTS.md index 005a4e2..cfa66b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,13 @@ is the [engineering book](https://github.com/farcloser/limen/tree/main/book). recipe covers the need — say so when you do it. `just --list` shows every task. - Always name the recipe; module defaults are curated subsets, not "run everything". - Never bump a tool version by hand: `just do tools set` / `just do tools update`. +- **Every git command is `git c …`, never raw `git`**, local commands included: the + harness sets `GIT_SSH_COMMAND`, which sends raw git's ssh to the system ssh and the + machine's own keys; the sessions' `c` alias (`env -u GIT_SSH_COMMAND git`) drops it so + the rig's ssh and the bot's key are used. A network command also takes the rig's + transport explicitly, + `git c -c core.sshCommand="$(git c config -f ~/.claude/gitconfig core.sshCommand)" fetch`, + and the same for `push` and `pull`. The alias composes with `-C`: `git -C c …`. ## Contributing @@ -97,13 +104,14 @@ chapter; the procedure is limen's `skills/contribute`. place — another transport, a variable set by hand per command, a manual step for a recipe — carried on as if the rig worked: that hides the defect. A workaround is used only after the breakage is reported and the human agrees, and is named as one every time. -- **A locked laptop is not broken tooling.** The bot's key answers only while the human's - session is unlocked: once the screen locks, the agent refuses to sign or authenticate. So - when signing or pushing fails after it worked earlier in the session, the human is away, - not the rig broken. No retry loop, no workaround: finish the work in the worktree — done, - `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). +- **A locked screen does not stop the key.** The bot's key is created to sign while the + screen is locked, from the human's first unlock after boot until the next boot (taciturn, + `--use locked`). So a signing or push failure that follows earlier successes in the session + is a defect and is reported as broken tooling (above), never waited out. Only a machine + still on Secretive refuses once the screen locks; there the human is away, not the rig + broken: no retry loop, no workaround, finish the work in the worktree — done, `just lint` + and `just test` green, the commit message written — ready to commit and push when the + human is back, and say so once. - **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 `~`. @@ -133,11 +141,12 @@ chapter; the procedure is limen's `skills/contribute`. assessment or misreads the code, say so with the evidence and settle it between the two sessions, the reviewer's 🛑 included (the reviewer may miss what the owner knows; the owner may miss the cross-cutting picture). A deadlock goes to the human, as the exception. -- **No GitHub issues unless the human asks for one.** The issue tracker is the human's. - A defect or a request that belongs to another repository goes to the session that owns - that repository, as a message with what, why, and where; that session fixes it, and - the human hears about it as a pull request. An issue you opened on your own is closed - once the owning session has the work. +- **A follow-up is a GitHub issue; a defect in another repository is a message.** Anything + left for later, a follow-up or parked work, is filed as an issue in the repository it + belongs to, one per real item, written so its owner can act on it; never a scratch, + `TODO` or notes file, and never an issue for a passing note. A defect or a request in a + repository another session owns goes to that session as a message with what, why and + where; that session fixes it, and the human hears about it as a pull request. ## Code