From 1cf681faa22b921039e8143ff4cb173753695701 Mon Sep 17 00:00:00 2001 From: UnstoppableMango Date: Sat, 25 Jul 2026 00:26:14 -0500 Subject: [PATCH 1/2] Agent file --- CLAUDE.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bf1270d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +`devctl` — Go CLI (cobra-based) for development productivity: listing source files by language, and a convention-based dependency-version system backed by plaintext files in `.versions/`. + +## Commands + +Build/test go through `make`, which drives itself using `devctl` (dogfooding — `bin/devctl list --go` discovers Go source files for its own build deps). + +```shell +make build # builds bin/devctl +make test # ginkgo run, non-E2E specs only (uses .make/test as a stamp file) +make test_all # ginkgo run -r ./ , including E2E specs +make format # dprint fmt (README.md, .github/renovate.json, .vscode/extensions.json, .dprint.json) +make check # nix flake check --all-systems +make tidy # regenerates go.sum and gomod2nix.toml from go.mod +``` + +Run a single spec/package directly with ginkgo (installed as a go tool): + +```shell +go tool ginkgo run --label-filter '!E2E' ./pkg/version/... +go tool ginkgo run -r --focus "RelPath" ./pkg/version/ +``` + +E2E specs are tagged with the Ginkgo label `E2E` (see `test/e2e/`) and build the real binary via `gexec.Build` in `BeforeSuite` — they're excluded from `make test` (`TEST_FLAGS := --label-filter !E2E`) unless `CI` is set, but included in `make test_all`. + +Tests use Ginkgo v2 + Gomega exclusively (no stdlib `testing.T` assertions). New test files/packages follow the Ginkgo bootstrap/generate convention: + +```shell +cd pkg/foo && go tool ginkgo bootstrap # creates foo_suite_test.go +cd pkg/foo && go tool ginkgo generate bar # creates bar_test.go for bar.go +``` +(Also wired as Makefile pattern rules: `%_suite_test.go` and `%_test.go`.) + +No standalone lint command; formatting is via `dprint` (`.dprint.json`) for non-Go files, `gofmt`/`go vet` conventions for Go. + +## Environment + +Nix flake + `direnv` (`.envrc`) is the primary dev environment (`devShells.default` in `flake.nix` provides `go`, `gomod2nix`, `dprint`, `nixfmt`, etc). Building the Nix package itself runs `ginkgo run --label-filter=!E2E -r .` as its `checkPhase`. + +`gomod2nix.toml` must stay in sync with `go.mod`/`go.sum` — regenerate with `go tool gomod2nix` after dependency changes (CI's `gomod2nix` job fails the PR if it drifts). + +Go module tools are declared in the `tool (...)` block of `go.mod` (ginkgo, gomod2nix) and invoked via `go tool ` rather than being globally installed. + +## Architecture + +### Command layer (`cmd/`) +Cobra commands. `cmd/root.go` wires subcommands: `initialize`/`init` (`cmd/initialize/`), `config` (`cmd/config/`), `install`, `list`, `localbin`, `version`. Each subpackage under `cmd/` defines a package-level `Cmd`/`*Cmd` var plus a `New*()` constructor so commands are testable/composable independent of the global `root`. + +Commands consistently resolve their working directory through `pkg/work` (either `work.Load(ctx)` — tries git root, then cwd — or an explicit `--chdir/-C` flag via `work.ChdirOptions`/`work.ChdirFlag`), rather than assuming cwd. + +### `pkg/work` — the workspace abstraction +`work.Directory` is a string-typed path with `.Fs()` (returns an `afero.Fs` base-pathed at that directory) and `.Join()` helpers. Nearly every other package takes a `work.Directory` rather than a raw path, and filesystem access goes through the returned `afero.Fs` (enables afero's in-memory FS for tests, e.g. `work.WithRoot(afero.NewMemMapFs())`). + +### `pkg/version` — the `.versions/` convention +Core convention: a repo's root `.versions/` file holds a plaintext version string (optionally `v`-prefixed). `version.RelPath`, `version.Regex`, `version.Clean`/`Prefixed` are the low-level string helpers; `version.Cat`/`Init`/`WriteMakefile` do the file I/O and Makefile-snippet generation. `version.Source` is the interface for resolving a version from elsewhere (`version.String` is a literal; `version.GitHub` — via `github.com/unmango/aferox/github` — is stubbed/unimplemented (`panic("unimplemented")` for `Latest`/`Name`)). `version.GuessSource` picks a `Source` by pattern-matching the input (looks like a semver → `String`, contains "github" → `GitHub`). + +### `pkg/config` — devctl's own config file +`devctl.yml` (or `.yaml`) in the workspace root, loaded via viper (`pkg/config.Viper`/`FromDirectory`/`Init`). Schema is just `Config{ Tools map[string]tool.Config }`. + +### `pkg/tool` — tool installation +`tool.Config` (`url`, `version`, `script`) + `tool.Tool` describes an installable binary; `Tool.Install` downloads (`.tar.gz` auto-extracted via `Untar`) into a `work.Directory`'s `bin/`, chmod'ing it executable if it has no file extension. + +### `pkg/list` — source file discovery +Language-aware source listing (`--go`, `--ts`, `--proto`, `--cs`, `--fs`, `--dotnet`, with `--exclude-tests`/`--absolute`) used both as a CLI feature and by the Makefile to compute Go build dependencies (`bin/devctl: $(shell $(DEVCTL) list --go --exclude-tests)`). + +### `pkg/renovate` — generated schema +`pkg/renovate/zz_generated.schema.go` is generated from `.make/renovate-schema.json` (downloaded from docs.renovatebot.com, filtered by `hack/renovate/*.jq`, then run through `go-jsonschema`). Regenerate via `make pkg/renovate/zz_generated.schema.go`; don't hand-edit the `zz_generated` file. CI's `clean` job fails if this file is out of sync with its inputs. + +## Conventions + +- Command errors: use `github.com/unmango/go/cli.Fail(err)` in `Run` funcs (prints and exits non-zero) rather than returning errors from cobra's `RunE`. +- Logging: `github.com/charmbracelet/log`, with `ReportTimestamp: false`. +- External deps favor the `unmango/*` and `unmango/aferox/*` ecosystem (companion libraries by the same author) over reinventing filesystem/git/CLI helpers. From 406dcd5a3012c26d8221ab49a1951ad5c6add8f0 Mon Sep 17 00:00:00 2001 From: UnstoppableMango Date: Sat, 25 Jul 2026 00:29:11 -0500 Subject: [PATCH 2/2] Regen gomod2nix --- gomod2nix.toml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 4dece31..bd7ddec 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -61,6 +61,9 @@ schema = 3 [mod."github.com/google/go-github/v81"] version = "v81.0.0" hash = "sha256-+ufveUz5MphSn3KVU0v+5mlV0sPyeZC5g7jyR37kg0Q=" + [mod."github.com/google/go-github/v84"] + version = "v84.0.0" + hash = "sha256-PCi2rQfQ4L2pmgGwOFSIW1u+exocIaq94K0OzuA++5I=" [mod."github.com/google/go-querystring"] version = "v1.2.0" hash = "sha256-F/Ve4oDaEqho8RryvdGSRR22/DbYHWZQa6M60n6oSYM=" @@ -92,11 +95,11 @@ schema = 3 version = "v1.7.0" hash = "sha256-KSsBTMpggbVO4AmI0JaXbALrXeygr2sT+IuoVAfkNqw=" [mod."github.com/onsi/ginkgo/v2"] - version = "v2.27.5" - hash = "sha256-4YtMCQDR+odfRReIah0jHLXFdG8UyX+EOaDT00XpMr0=" + version = "v2.28.1" + hash = "sha256-ndaovLQb6s4cztI8MXXJajJDY1kMsuz5zEbVjlfKgAE=" [mod."github.com/onsi/gomega"] - version = "v1.39.0" - hash = "sha256-in2eEUjcDC3JGDSAQxBI/mWdT0E2X1yCBKoGXemlWaE=" + version = "v1.39.1" + hash = "sha256-ZlbQhUVwQBzmhBWCQ9iPWoJOVr+OqqIHB4iCNDFMEds=" [mod."github.com/pelletier/go-toml/v2"] version = "v2.2.4" hash = "sha256-8qQIPldbsS5RO8v/FW/se3ZsAyvLzexiivzJCbGRg2Q=" @@ -128,11 +131,11 @@ schema = 3 version = "v1.6.0" hash = "sha256-LspbjTniiq2xAICSXmgqP7carwlNaLqnCTQfw2pa80A=" [mod."github.com/unmango/aferox"] - version = "v0.3.3" - hash = "sha256-pdIvZefa8Yl5zK1PFQYab+ExPHALTKgfKDZ5WLEcT3A=" + version = "v0.5.0" + hash = "sha256-ugi7ILxoL3R6xjgl/8ruxzx+BAaux7IEJNnfae3FqeM=" [mod."github.com/unmango/aferox/github"] - version = "v0.0.4" - hash = "sha256-tz0jH4jLGR2S4rQPpHngzy9Ho/OAuQTBgqWmt9xR51s=" + version = "v0.0.6" + hash = "sha256-8OinTfBi4FYya+TU6zUtwwhPBsw6Xwf7ueah0pSrCHM=" [mod."github.com/unmango/go"] version = "v0.15.1" hash = "sha256-iaw6AuhEYu7LdLQnOL6Zmu9kp41nRwaz+lpeZ5ihRww="