The generated cask used `pre_install` / `post_install`. Those are formula
DSL methods; a cask has no such stanzas, so brew rejected the file at load
time with
Error: Cask 'gortex' definition is invalid: undefined method 'pre_install'
Every brew command that touches the cask parses it, so `brew install` and
`brew upgrade` both failed for every macOS user on both arches, starting
with v0.63.7. Closes#639.
Two further defects made a straight rename insufficient:
* The cask DSL's `system_command` is `SystemCommand.run!`, which raises on
a non-zero exit. `gortex daemon status` exits 1 exactly when no daemon
is reachable, so `next unless status.success?` could never run — brew
would have aborted the install outright on any machine without a live
daemon. The probe now passes `must_succeed: false`.
* `preflight` was the wrong hook for stopping the daemon. On upgrade brew
unlinks the old cask's binary (`start_upgrade` -> `uninstall_artifacts`)
before installing the new cask's artifacts, so by the time a preflight
block runs there is no gortex on disk to ask. The stop half could never
fire on the path it was written for.
`postflight` handles both halves instead: after the new binary is linked,
probe for a daemon and, only if one answers, `daemon restart` — which stops
the old process (blocking until it exits, releasing the store lock) before
starting the new one, so a store migration still runs alone. A fresh install
and CI have no daemon answering and skip it.
The cask body moves out of the release.yml heredoc into
.github/homebrew/gortex.rb.tmpl, rendered by scripts/render-cask.sh. That
makes it reviewable as Ruby, removes the shell-expansion hazards of an
unquoted heredoc, and — the point — lets a real brew load it before
publication. The renderer also refuses a malformed sha256, a version with a
leading "v", or a placeholder that survived substitution. Rendered output is
byte-identical to the published cask apart from the hook block.
Nothing could have caught this: the file was valid Ruby, valid YAML, and the
tap has no CI, so the first machine to evaluate the cask was a user's.
scripts/validate-cask.sh renders the template with dummy values and loads it
through a real brew. It runs on every PR that touches the cask
(.github/workflows/homebrew-cask.yml) and again in build-darwin as the last
gate before the release job pushes to the tap. Both are macOS-only —
Homebrew on Linux cannot load casks. Verified by reintroducing `pre_install`
in the template: the validator fails with the exact error users reported.
The linux release was dynamically linked despite the README promising a
single static binary with no dependency chain. goreleaser-cross:v1.26 is
Ubuntu 24.04 / GCC 13.3, so the artifacts carried that host's floor —
GLIBCXX_3.4.32 and GLIBC_2.34 — and refused to start anywhere older:
gortex: /lib/x86_64-linux-gnu/libstdc++.so.6:
version `GLIBCXX_3.4.32' not found (required by gortex)
Debian 12 caps at GLIBCXX_3.4.30 and Ubuntu 22.04 at 3.4.29, so in practice
the binary only ran on Ubuntu 24.04+/Debian 13+. libstdc++ is in the link at
all because some tree-sitter grammars ship C++ external scanners. Windows
already linked -static; only linux was left dynamic.
Link -static with netgo, osusergo and static_link, which between them remove
every dlopen the binary would otherwise perform at runtime.
verify-static-elf.sh asserts no DT_NEEDED, no PT_INTERP and no symbol-version
requirements. It runs as a goreleaser build post-hook rather than a workflow
step because `goreleaser release` builds and publishes in the same run — a
check after it would only ever be a post-mortem on an already-published
artifact. The release job re-asserts it on the packaged tarballs and then
proves it by execution on debian:11 and alpine.
The 0.52.2 cask aborted at load on macOS Sequoia/Tahoe with
"__DATA_CONST segment missing SG_READ_ONLY flag" (#176). darwin was
cross-compiled inside goreleaser-cross, whose osxcross ld64 omits the
SG_READ_ONLY flag on the __DATA_CONST segment that the macOS 15+ dyld now
enforces. Apple's ld sets it, so locally built binaries always worked and
the bug only surfaced in the released artifact.
Build darwin on a native macos-15 runner instead:
- new build-darwin job links arm64 natively + amd64 via `clang -arch
x86_64`, codesigns with rcodesign, smoke-tests the segment flag, and
runs `gortex version` on the enforcing dyld before handing off archives.
- the release job now builds linux only, merges the darwin tarballs,
notarizes, appends them to checksums.txt, signs, and uploads.
goreleaser's prebuilt builder is Pro-only, so the homebrew cask (which
references all four os/arch tarballs) is assembled and pushed to the tap
from the release job once every platform's hash exists, instead of by
goreleaser. Completions are still generated at brew-install time.
scripts/verify-macho-readonly.sh is the guard: it fails the release if a
darwin binary ships without SG_READ_ONLY, so this can never regress.
Extend the built-in cost-avoided table beyond Anthropic so the dashboard
prices real usage for every provider gortex's LLM features talk to: add
the current OpenAI (GPT-5.x, GPT-4.1, o3/o4-mini), Google Gemini (3.x and
2.5) and DeepSeek models. Replace the unreachable claude-fable-5 with the
live claude-mythos-preview flagship. Refresh the pricing tests, the
landing-page generator's cost_avoided_usd keys, and the savings doc to
the new model ids.
Actualise the savings docs for the sidecar-backed ledger: where it
lives, the transactional durability story, the legacy flat-file import,
and the real recording surface (the read family records too, and the
per-call value is server-side accounting, not a response field).
The windows release used to ship gortex.exe plus the mingw C/C++ runtime
DLLs (libstdc++-6 / libgcc_s_seh-1 / libwinpthread-1) it linked
dynamically — the C++ stdlib is pulled in because some tree-sitter
grammars carry C++ external scanners (e.g. go-sitter-forest norg). Link
them statically via -extldflags=-static so the .exe is a single
self-contained binary, and replace the DLL-staging step (the brittle
find_dll scan) with an objdump guard that fails the release if any mingw
runtime import leaks through.
The zip stays — install.ps1, checksums.txt, cosign signing, and
windows/unix artifact parity are all built around it, and it compresses
the large CGo binary — but it now contains only gortex.exe. install.ps1
drops the multi-file / DLL-count install path accordingly.
The ladybug/Kuzu backend was removed; its build/CI scaffolding is now
dead. Delete scripts/fetch-lbug.sh and drop the liblbug-specific pieces
from Makefile, the GitHub Actions workflows, .goreleaser.yml, .gitignore,
and install.ps1: the fetch steps, the -Wl,--whole-archive /
CGO_LDFLAGS_ALLOW allowlist, and the windows lbug_shared.dll bundling.
Generic CGo (CGO_ENABLED=1 + the C/C++ toolchain) is preserved
everywhere because tree-sitter still needs it. go build ./... is green
with no fetch step.
The windows release is now a zip containing gortex.exe + lbug_shared.dll
+ the mingw and VC++ runtime DLLs (gortex links liblbug dynamically on
windows). install.ps1 moved only gortex.exe into the install dir, so the
installed binary couldn't start (missing DLLs). It now installs the whole
archive — exe + DLLs together — since windows resolves DLLs from the
executable's own directory.
The windows zip is built by the separate native-windows release job, so
it isn't in goreleaser's checksums.txt and install.ps1 was silently
skipping SHA-256 verification on windows. The windows job now appends the
zip's sha256 to the release checksums.txt, restoring verification.
install.sh (unix) is unchanged — static linking keeps the tar.gz a single
self-contained binary.
liblbug native libs are no longer committed — scripts/fetch-lbug.sh
fetches them (pinned LBUG_VERSION=0.17.0) for make / CI / release:
- linux + darwin: STATIC (liblbug.a linked in -> self-contained binary;
libstdc++ forced static via -Wl,-Bstatic so the binary carries no
runtime libstdc++.so dependency).
- windows: DYNAMIC — lbug's windows build is MSVC and can't be
static-linked from mingw; the .exe links lbug_shared.dll directly
(-l:lbug_shared.dll) and ships the DLL + mingw and VC++ runtime
alongside.
cgo_shared.go now points at lib/static/<os>-<arch>/ (unix) and
lib/dynamic/windows/ (windows). The committed darwin dylib and the old
download_lbug.sh are removed; .gitignore ignores the fetched lib tree.
CI: every job that builds cmd/gortex or runs go test ./... fetches
liblbug first (ci.yml test/build-windows/build-onnx, init-smoke), so the
link is validated natively on all three OSes.
Release: .goreleaser.yml builds the unix targets only (static); a new
native-windows job in release.yml builds the dynamic .exe, bundles the
runtime DLLs (hard-failing if any is missing), zips, cosign-signs and
appends to the release. Scoop manifest is a follow-up (windows is no
longer a goreleaser artifact).
Validated on darwin: static build is self-contained (no liblbug runtime
dep) and the store_ladybug suite passes against the static lib. Linux and
windows links are validated by CI on their native runners.
Gortex now builds and runs on windows/amd64.
- internal/platform isolates OS-specific runtime primitives — shutdown
signals, process liveness/termination, and detached-spawn attributes
— behind unix/windows files. The daemon, CLI, and servers call
through it instead of using syscall directly.
- internal/daemon: a Windows fdlimit stub, daemon state relocated to
%LocalAppData%\gortex, and the AF_UNIX socket transport reused as-is
(supported on Windows 10 1803+).
- .goreleaser.yml builds a windows/amd64 zip archive and a Scoop
manifest; Makefile gains build-windows; release.yml signs, hashes,
and scans the zip alongside the other artifacts.
- scripts/install.ps1 is a PowerShell installer; install.sh now points
native-Windows users at it.
- CI builds every package for windows and parse-checks install.ps1.
- internal/thirdparty/renameio vendors github.com/google/renameio with
an added Windows implementation — upstream v1.0.1 is Unix-only and
blocked the build through the github.com/coder/hnsw dependency.
daemon install-service stays unsupported on Windows; users run
gortex daemon start --detach instead.
Three small marketing artifacts that ride on substrate already
shipped:
cmd/gortex/audit.go
New `gortex audit` top-level command. Indexes the target repo
(default `.`), computes per-symbol complexity-axis health score
(fan-in × 2 + fan-out × 1.5 with the same arithmetic shape as
the `analyze kind=health_score` MCP analyzer's complexity
component), averages across callable symbols, maps to A-F
grade.
Three output modes:
--format svg (default) shields.io-style SVG, default at
.gortex/badge.svg. Embed in README via
. Stdout also
prints the paste-ready markdown snippet.
--format json structured report: symbol count, mean score,
grade, per-grade counts, top-5 worst symbols.
--format text one-line "<grade> · <score>" for CLI use.
10 tests cover the math: grade boundary checks (39.99→F /
84.99→B / 85.01→A), grade-count sum invariant, non-callable
kinds skipped, badge SVG validity per grade, colour mapping,
JSON round-trip.
Smoke run on the gortex repo: 9,181 callable symbols · mean
complexity-health 62.3 · grade C.
scripts/landing/per-tool-savings.sh + docs/landing-pages/per-tool-savings.md
Regenerator script + published landing-page tables sourced from
the existing cumulative savings store (`gortex savings --verbose
--json`). Three sections:
1. Headline totals: source-reading calls, tokens saved,
efficiency ratio, USD avoided across all 5 priced models.
2. By language: per-language breakdown sorted by tokens
saved.
3. By repository: per-repo breakdown sorted by tokens saved.
4. By MCP tool: populated once the JSONL event log accumulates
rows (the JSONL surface landed 2026-05-18; stores predating
that don't yet have per-tool data). Section emits a clean
placeholder + re-run note when empty.
Re-run protocol: `bash scripts/landing/per-tool-savings.sh`.
Honest about scope: bails clean when the store is empty
("use gortex via MCP for a while, then re-run"); never pads
with synthetic numbers.
Initial run on the operator's machine: 1,926 calls · 11.5M
tokens saved · ≈$172.50 avoided at claude-opus-4 input pricing
across 9 languages and 12 repos.
ROADMAP.md (untracked local working doc)
Adds a serena-focused scorecard sub-table (11 categories where
it actually contests Gortex; the main 4-column matrix stays
unchanged) and a "Phase H reframe" section that re-aims the
agent-ergonomics narrative against serena's adoption + IDE-
polish strengths via the gap-analysis paths already on the
roadmap (C8 npm wrapper, D5 JetBrains plugin, N44 sub-agent
installer).
Full repo: 4666 / 125 packages green (+10 new tests). go vet +
golangci-lint clean.
Hybrid BM25 + vector search using Reciprocal Rank Fusion. Four embedding
tiers: built-in GloVe word vectors (20k words, 50d, always available),
API provider (Ollama/OpenAI), ONNX backend (GTE-small 384d via
onnxruntime, build tag embeddings_onnx), and Hugot/GoMLX backends
(MiniLM via pure Go or XLA, build tags embeddings_hugot/embeddings_gomlx).
Pure Go HNSW index via coder/hnsw. Pluggable Provider interface with
NewLocalProvider dispatcher. CLI flags --embeddings, --embeddings-url,
--embeddings-model on serve and bridge commands. Makefile targets for
build variants and dependency setup.