# Contributing to GrayMatter Thanks for taking the time. GrayMatter is small and intentional — contributions that keep it that way are the most welcome. --- ## What we're looking for **Great fits:** - Bug fixes with a failing test that reproduces the issue - Coverage improvements for uncovered code paths - New embedding backend adapters (implement `VectorStore`, drop in) - Performance improvements with `go test -bench` numbers before/after - Docs corrections — especially if something in the README doesn't match the code **Out of scope:** - External service dependencies in the core library (`pkg/`) - Changes that require CGO - Features that belong in the CLI binary being added to the core module When in doubt, open an issue first. A quick description of what you're building and why saves everyone time. --- ## Setup **Requirements:** Go 1.25.5+, no Docker, no external services. The workspace declares `go 1.25.5`, so an older toolchain either fetches 1.25.5 or refuses outright when `GOTOOLCHAIN=local` — which is what container images set. The shipped binary is built without CGO; that constraint is about the release artifact, not about your machine. See the note on `-race` below. `cmd/graymatter/go.mod` additionally declares `toolchain go1.26.9`. That line is inert inside the workspace (go.work governs toolchain selection there), but it means someone running `go install github.com/angelnicolasc/graymatter/cmd/graymatter@latest` on an older Go gets a binary linked against a patched standard library rather than whatever their toolchain happens to ship. When you bump it, bump the `go-version` in CI, release and Maintenance smoke to match. CI runs `govulncheck` as a blocking gate on both modules, so a regression here fails the build rather than a report nobody reads. ```bash git clone https://github.com/angelnicolasc/graymatter cd graymatter go work sync # resolves both modules: root + cmd/graymatter ``` The repo uses a `go.work` workspace with two modules: | Module | Path | What's in it | |--------|------|--------------| | `github.com/angelnicolasc/graymatter` | `.` | Core library — memory, embedding, storage | | `github.com/angelnicolasc/graymatter/cmd/graymatter` | `./cmd/graymatter` | CLI, TUI, MCP server, REST server, plugins | Keep them separate. CLI dependencies (bubbletea, cobra, etc.) must not appear in the root `go.mod`. For version alignment, release validation, signed tags and publication checks, see [Publishing a release](docs/releasing.md). --- ## Running tests ```bash # Core library and the public API surface — no network, no LLM, no Docker go test -count=1 -timeout=1200s ./... # CLI, TUI, server and the command entrypoint cd cmd/graymatter go test -count=1 -timeout=600s ./... ``` Use `./...` rather than naming packages. Both commands used to enumerate subtrees, which quietly excluded the root package and `cmd/graymatter` itself, so tests living there passed locally and were never run by anyone else. Run each command block from the workspace root. The root `./...` also includes benchmark packages, whose complete checks can need the 1200-second ceiling used by CI. For a shorter correctness pass, use `go test -short -count=1 -timeout=600s ./...` at the root. The CLI's full-suite ceiling is 600 seconds, including its real-binary acceptance tests. ### The race detector CI runs every one of those packages under `-race`. The commands above do not, so a change can pass locally and still fail the pull request. Add the flag when you touch anything concurrent — the daemon, the RPC layer, the store handles, the TUI's background loads: ```bash go test -race -count=1 -timeout=1200s ./... cd cmd/graymatter go test -race -count=1 -timeout=600s ./... ``` `-race` is the one thing here that needs a C compiler, because the detector is built on CGO. Linux and macOS usually have one already; on Windows install mingw-w64 or TDM-GCC. Without it the flag fails with `-race requires cgo`, and the honest fallback is to run the suite without it and let CI catch what you cannot. That is a gap in your local coverage, not a licence to skip the flag when you have the toolchain. All tests use `t.TempDir()` with injected stubs. No real embedding model is required — tests that need one use a fixed-vector stub or keyword mode. **Coverage gate:** the CI enforces ≥ 70% statement coverage on `pkg/memory`. If your change drops coverage, add tests for the new code paths before submitting. ```bash # Check coverage locally before pushing go test -coverprofile=coverage.out -covermode=atomic ./pkg/memory/... go tool cover -func=coverage.out | grep '^total:' ``` **Fuzz targets:** seed them, don't break them. ```bash # Run a fuzz target for 30 seconds locally go test -fuzz=FuzzTokenize -fuzztime=30s ./pkg/memory/... ``` --- ## Code conventions - **No global state** in the core library. Everything goes through `StoreConfig` or function arguments. - **Errors propagate, never print.** Internal helpers return `error`; callers decide what to log. - **Context everywhere.** Any function that does I/O must accept `context.Context` as its first parameter. - **No `init()` side effects** that affect behavior — only for package-level var initialization. - Standard formatting: `gofmt` / `goimports`. The CI runs `go vet ./...`; keep it clean. - **A status surface must check the thing it reports on.** If code answers "is this healthy / ready / set up", it has to reach the dependency it is speaking for, and it needs a test proving it goes red when that dependency is gone. This one is written down because we got it wrong four times in the same release: `doctor` reported "everything looks good" for a project that had never stored a fact, `/healthz` answered ok with no store and again with a dead daemon, and the TUI dashboard drew zeros as though they were data. All four were indistinguishable from working, which is worse than an outage: a crash gets reported, a confident wrong answer gets believed. Applies equally to CI, which reported green for packages it was not running. --- ## Submitting a PR 1. Fork → branch off `main` → make your changes. 2. Add or update tests. The CI will enforce coverage. 3. Run the full test suite locally: ```bash go test -count=1 -timeout=1200s ./... && \ cd cmd/graymatter && go test -count=1 -timeout=600s ./... ``` 4. Open the PR against `main`. Keep the title concise (`fix:`, `feat:`, `test:`, `docs:`, `refactor:`). PR descriptions don't need a template. Just explain what the change does and why. If it fixes a bug, link the issue or include a one-liner that shows the broken behavior. --- ## Adding a vector backend `VectorStore` is the extension point. Implement the interface and pass it via `StoreConfig.VectorBackend`: ```go type VectorStore interface { AddDocument(ctx context.Context, collection, id, content string, embedding []float32, metadata map[string]string) error Query(ctx context.Context, collection string, embedding []float32, n int) ([]VectorResult, error) EnsureCollection(collection string) error Close() error } ``` Implementations must be safe for concurrent use. See `pkg/memory/vectorstore.go` for the chromem-go reference adapter. --- ## License By contributing, you agree that your changes will be licensed under the same [MIT License](LICENSE) as the rest of the project.