diff --git a/.github/workflows/default-images-pullable.yml b/.github/workflows/default-images-pullable.yml index a407b74bd..616020218 100644 --- a/.github/workflows/default-images-pullable.yml +++ b/.github/workflows/default-images-pullable.yml @@ -69,7 +69,9 @@ jobs: # itself, so a new pin is covered the moment it lands and there is no # second list. Each ref is repository:version@digest; the gate probes # repository@digest, which proves the pinned digest still exists. - go run ./tools/imagelist -platforms > /tmp/default-images.tsv + # Build, then run: `go run` leaves a linked executable in the Go build cache. + go build -o bin/imagelist ./tools/imagelist + bin/imagelist -platforms > /tmp/default-images.tsv if [ ! -s /tmp/default-images.tsv ]; then echo "::error::imagelist produced no images — the generator or the map is broken" exit 1 diff --git a/.github/workflows/doc-sync.yml b/.github/workflows/doc-sync.yml index 8325010a4..6bf846ac3 100644 --- a/.github/workflows/doc-sync.yml +++ b/.github/workflows/doc-sync.yml @@ -28,8 +28,13 @@ jobs: with: go-version-file: go.mod + # Build the generators once: `go run` would leave a linked executable in + # the Go build cache for each tool. + - name: Build doc generators + run: CGO_ENABLED=0 go build -mod=vendor -o bin/ ./tools/wikigen ./tools/cmdinventory ./tools/parity + - name: Wiki pages are current - run: CGO_ENABLED=0 go run -mod=vendor ./tools/wikigen -check + run: bin/wikigen -check - name: Wiki links resolve run: bash scripts/ci/wiki-link-audit.sh @@ -44,7 +49,7 @@ jobs: - name: Command inventory is current run: | - CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format json -depth 2 > /tmp/inventory.json + bin/cmdinventory -format json -depth 2 > /tmp/inventory.json diff -u .github/command-inventory.json /tmp/inventory.json # CLI-R17: the four-surface parity matrix (wiki page, MCP tool, env var @@ -52,7 +57,7 @@ jobs: # command-inventory.json this job already verified above. -check fails # if the committed .github/surface-parity.{md,json} are stale. - name: Parity matrix is current - run: CGO_ENABLED=0 go run -mod=vendor ./tools/parity -check + run: bin/parity -check # Docs are the minimum bar: a command with no wiki page fails the build. # (wiki-generated above already guarantees every command gets a page, so diff --git a/Makefile b/Makefile index bf6581374..3ac0e27be 100644 --- a/Makefile +++ b/Makefile @@ -12,11 +12,21 @@ LDFLAGS := -s -w \ -X $(MODULE)/internal/license.licensePubKeyHex=$(NSELF_LICENSE_PUBKEY_HEX) BUILDFLAGS := -trimpath -.PHONY: build clean test vet install cross dist verify-prod sport-f21 sport-f02 cmd-inventory registry canon core-services wiki-commands wiki-check flag-drift-audit parity schemas schemas-check contract-check sbom man fmt fmt-check +.PHONY: FORCE build clean test vet install cross dist verify-prod sport-f21 sport-f02 cmd-inventory registry canon core-services wiki-commands wiki-check flag-drift-audit parity schemas schemas-check contract-check sbom man fmt fmt-check verify-prod: @bash scripts/prod-verify/p87-verification.sh +## Generator tools (tools/) are built once into $(TOOLS_BIN)/ and then run. +## `go run` would store a fresh 35-50 MB linked executable in GOCACHE for every +## changed tool (Go 1.24+, no size cap); `go build -o` keeps it in the ignored bin/. +## FORCE rebuilds on every make invocation so an edited tool is never stale; the +## compile itself stays cached. +TOOLS_BIN := bin +$(TOOLS_BIN)/%: FORCE + @CGO_ENABLED=0 go build -mod=vendor -o $@ ./tools/$* +FORCE: + sport-f21: @bash scripts/sport/generate-f21.sh @@ -39,8 +49,8 @@ registry: cmd-inventory ## canon — P7-CANON-21. Regenerate cmd/commands/canon_moves_gen.go (the canon engine's ## move table) from internal/canon/canon.yaml and domains/*.yaml. Run after editing ## a canon fragment's rows; TestCanonTableCurrent fails when the file is stale. -canon: - @CGO_ENABLED=0 go run -mod=vendor ./tools/canongen +canon: $(TOOLS_BIN)/canongen + @$(TOOLS_BIN)/canongen ## core-services — CLI-R07. Regenerate .github/wiki/Core-Services.md from the ## compose service catalog. Set NSELF_SPORT_DIR to also refresh SPORT F08. @@ -51,12 +61,12 @@ core-services: ## the sidebar index and llms.txt. Human prose inside PROSE blocks is preserved; ## only the cobra-derived parts are rewritten. Use -report to list pages that ## still carry placeholder prose. -wiki-commands: - @CGO_ENABLED=0 go run -mod=vendor ./tools/wikigen -report +wiki-commands: $(TOOLS_BIN)/wikigen + @$(TOOLS_BIN)/wikigen -report ## wiki-check — verify the wiki is current and every [[link]] resolves. -wiki-check: - @CGO_ENABLED=0 go run -mod=vendor ./tools/wikigen -check +wiki-check: $(TOOLS_BIN)/wikigen + @$(TOOLS_BIN)/wikigen -check @bash scripts/ci/wiki-link-audit.sh ## flag-drift-audit — checks every `nself --flag` invocation in @@ -69,28 +79,28 @@ flag-drift-audit: ## mcp-docs — CLI-R15. Print the current MCP tool/resource/prompt list, ## generated from cmd/commands/mcp*.go — paste into cmd-mcp.md's PROSE blocks ## by hand after adding/removing a tool, resource, or prompt. -mcp-docs: - @CGO_ENABLED=0 go run -mod=vendor ./tools/mcpdoc +mcp-docs: $(TOOLS_BIN)/mcpdoc + @$(TOOLS_BIN)/mcpdoc ## parity — CLI-R17. Regenerate the four-surface parity matrix (wiki page, MCP ## tool, env var docs, OpenAPI route) for every top-level command. Writes ## .github/surface-parity.md and .github/surface-parity.json. internal/repoqa ## asserts the committed copies match; run this after adding, renaming, or ## removing a command, an MCP tool, or an env var. -parity: - @CGO_ENABLED=0 go run -mod=vendor ./tools/parity +parity: $(TOOLS_BIN)/parity + @$(TOOLS_BIN)/parity ## schemas — P7-REG-08. Regenerate schemas/ (JSON Schemas for the envelope, error ## object, command registry and every envelope command's data) from the Go types ## with tools/schemagen. Commit the result; internal/repoqa and `schemas-check` ## fail when the committed files drift. -schemas: - @CGO_ENABLED=0 go run -mod=vendor ./tools/schemagen +schemas: $(TOOLS_BIN)/schemagen + @$(TOOLS_BIN)/schemagen ## schemas-check — fail (exit 1) when any schemas/*.json differs from what ## tools/schemagen generates. Non-JSON files under schemas/ are ignored. -schemas-check: - @CGO_ENABLED=0 go run -mod=vendor ./tools/schemagen -check +schemas-check: $(TOOLS_BIN)/schemagen + @$(TOOLS_BIN)/schemagen -check ## contract-check — P7-REG-10. The one gate for every machine contract the CLI ## publishes: the committed schemas/, the registry and inventory, the compat @@ -111,14 +121,14 @@ CONTRACT_PKGS := ./internal/errs/... ./internal/output/... ./internal/canon/... ./internal/compat/... ./internal/portable/... ./internal/plugin/manifestv2/... ./tools/schemagen/... CONTRACT_REGEN := .github/command-registry.json .github/command-inventory.json .github/wiki/Commands.md \ .github/wiki/Command-Canon.md -contract-check: +contract-check: $(TOOLS_BIN)/schemagen $(TOOLS_BIN)/wikigen @set -e; \ fail() { echo "contract-check: FAILED: $$1" >&2; echo "contract-check: fix: $$2" >&2; exit 1; }; \ for f in $(CONTRACT_REGEN) schemas/index.json scripts/ci/compat-markers.sh; do \ [ -f "$$f" ] || fail "missing input $$f" "restore it; a gate input that is absent must fail, never pass"; \ done; \ echo "contract-check: schemas"; \ - CGO_ENABLED=0 go run -mod=vendor ./tools/schemagen -check \ + $(TOOLS_BIN)/schemagen -check \ || fail "schemas/ differs from tools/schemagen output" "run make schemas and commit; never hand-edit schemas/"; \ echo "contract-check: registry and inventory"; \ snap=$$(mktemp -d "$${TMPDIR:-/tmp}/contract-check.XXXXXX"); \ @@ -134,7 +144,7 @@ contract-check: done; \ restore; trap - EXIT; \ echo "contract-check: wiki contract pages"; \ - CGO_ENABLED=0 go run -mod=vendor ./tools/wikigen -check \ + $(TOOLS_BIN)/wikigen -check \ || fail "tools/wikigen -check found stale generated wiki pages" "run make wiki-commands and commit; never hand-edit a GENERATED block"; \ bash scripts/contract-check-wiki.sh \ || fail "a hand table in JSON-Output.md or Exit-Codes.md disagrees with its source" "edit the row the message names (scripts/contract-check-wiki.sh lists each check)"; \ diff --git a/scripts/ci/auth-smoke.sh b/scripts/ci/auth-smoke.sh index a65650433..2a994484f 100644 --- a/scripts/ci/auth-smoke.sh +++ b/scripts/ci/auth-smoke.sh @@ -123,7 +123,9 @@ fi case "$BIN" in /*) ;; *) BIN="$ROOT/$BIN" ;; esac [ -x "$BIN" ] || fatal "binary $BIN is not executable" -if CGO_ENABLED=0 go run -mod=vendor ./tools/imagelist > "$WORK/images.tsv" 2>"$WORK/imagelist.err"; then +# Build into the scratch dir: `go run` leaves a linked executable in GOCACHE. +if CGO_ENABLED=0 go build -mod=vendor -o "$WORK/imagelist" ./tools/imagelist 2>"$WORK/imagelist.err" \ + && "$WORK/imagelist" > "$WORK/images.tsv" 2>>"$WORK/imagelist.err"; then REF_V14="$(awk -F'\t' '$1=="auth-v14"{print $2}' "$WORK/images.tsv")" REF_NEW="$(awk -F'\t' '$1=="auth"{print $2}' "$WORK/images.tsv")" else diff --git a/scripts/ci/contract-smoke.sh b/scripts/ci/contract-smoke.sh index ab09141bd..9654d8e06 100644 --- a/scripts/ci/contract-smoke.sh +++ b/scripts/ci/contract-smoke.sh @@ -98,7 +98,9 @@ check "help-json: command is help" envelope help REG=".github/command-registry.json" check "help-json: data equals $REG minus _generated" \ test "$(jq -S '.data' "$OUT")" = "$(jq -S 'del(._generated)' "$ROOT/$REG")" -TOP="$(CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format names | wc -l | tr -d ' ')" +# Build the tool into the scratch dir: `go run` leaves a linked executable in GOCACHE. +CGO_ENABLED=0 go build -mod=vendor -o "$TMP/cmdinventory" ./tools/cmdinventory +TOP="$("$TMP/cmdinventory" -format names | wc -l | tr -d ' ')" check "help-json: counts.top_level equals cmdinventory ($TOP)" \ test "$(jq '.data.counts.top_level' "$OUT")" = "$TOP" check "help-json: counts.commands equals the commands listed" \ diff --git a/scripts/ci/flag-drift-audit.sh b/scripts/ci/flag-drift-audit.sh index 5ac5298d3..3985cfce9 100755 --- a/scripts/ci/flag-drift-audit.sh +++ b/scripts/ci/flag-drift-audit.sh @@ -19,4 +19,6 @@ set -euo pipefail cd "$(dirname "$0")/../.." -CGO_ENABLED=0 go run -mod=vendor ./tools/flagaudit +# Build once and run the binary: `go run` leaves a linked executable in GOCACHE. +CGO_ENABLED=0 go build -mod=vendor -o bin/flagaudit ./tools/flagaudit +bin/flagaudit diff --git a/scripts/sport/generate-core-services.sh b/scripts/sport/generate-core-services.sh index e14844abe..fafc3e4a5 100755 --- a/scripts/sport/generate-core-services.sh +++ b/scripts/sport/generate-core-services.sh @@ -13,6 +13,10 @@ cd "$(dirname "$0")/../.." OUT=".github/wiki/Core-Services.md" +# Build once and reuse: `go run` leaves a linked executable in GOCACHE per call. +SVCCAT="bin/servicecatalog" +CGO_ENABLED=0 go build -mod=vendor -o "$SVCCAT" ./tools/servicecatalog + echo "Generating $OUT ..." { echo "# Core Services" @@ -23,7 +27,7 @@ echo "Generating $OUT ..." echo "\`internal/compose/service_catalog.go\`. The same data backs" echo "\`nself service list --core\`, so the CLI and this page cannot disagree." echo - CGO_ENABLED=0 go run -mod=vendor ./tools/servicecatalog -format markdown + "$SVCCAT" -format markdown echo echo "## Checking your own stack" echo @@ -47,7 +51,7 @@ if [ -n "${NSELF_SPORT_DIR:-}" ] && [ -d "${NSELF_SPORT_DIR}" ]; then echo "Last generated: $(date -u +%Y-%m-%d)" echo "Source: \`cli/internal/compose/service_catalog.go\`." echo - CGO_ENABLED=0 go run -mod=vendor ./tools/servicecatalog -format markdown + "$SVCCAT" -format markdown } > "$SPORT_OUT" else echo "NSELF_SPORT_DIR not set or missing — skipping SPORT refresh." diff --git a/scripts/sport/generate-f02.sh b/scripts/sport/generate-f02.sh index 874b3ac0b..1e2d0877c 100755 --- a/scripts/sport/generate-f02.sh +++ b/scripts/sport/generate-f02.sh @@ -25,18 +25,22 @@ REG_OUT=".github/command-registry.json" WIKI_OUT=".github/wiki/Commands.md" CANON_OUT=".github/wiki/Command-Canon.md" +# Build once and reuse: `go run` leaves a linked executable in GOCACHE per call. +CMDINV="bin/cmdinventory" +CGO_ENABLED=0 go build -mod=vendor -o "$CMDINV" ./tools/cmdinventory + echo "Generating $JSON_OUT ..." -CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format json -depth 2 > "$JSON_OUT" +"$CMDINV" -format json -depth 2 > "$JSON_OUT" echo "Generating $REG_OUT ..." -CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format registry > "$REG_OUT" +"$CMDINV" -format registry > "$REG_OUT" echo "Generating $CANON_OUT ..." CANON_TMP=$(mktemp) -CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format canon -prose-from "$CANON_OUT" > "$CANON_TMP" +"$CMDINV" -format canon -prose-from "$CANON_OUT" > "$CANON_TMP" mv "$CANON_TMP" "$CANON_OUT" -COUNT=$(CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format names | wc -l | tr -d ' ') +COUNT=$("$CMDINV" -format names | wc -l | tr -d ' ') echo "Updating $WIKI_OUT ($COUNT top-level commands) ..." # The curated grouped index in Commands.md is hand-written and worth keeping. @@ -52,7 +56,7 @@ trap 'rm -f "$BLOCK"' EXIT echo "Generated from the cobra registration tree in \`cmd/commands/\`." echo "Run \`make cmd-inventory\` to refresh. **Total top-level commands: $COUNT**" echo - CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format markdown -depth 2 \ + "$CMDINV" -format markdown -depth 2 \ | sed '$d' | sed '$d' echo echo "" @@ -89,7 +93,7 @@ if [ -n "${NSELF_SPORT_DIR:-}" ] && [ -d "${NSELF_SPORT_DIR}" ]; then echo echo "**Counts: $COUNT top-level commands.** Subcommands listed but not counted separately." echo - CGO_ENABLED=0 go run -mod=vendor ./tools/cmdinventory -format markdown -depth 2 \ + "$CMDINV" -format markdown -depth 2 \ | sed '$d' | sed '$d' } > "$SPORT_OUT" else