CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# 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.