Before making any design decision, read SOUL.md. It defines the two problems Rekal exists to solve and the seven beliefs that guide every choice. If a decision conflicts with the soul, the decision is wrong.
When working on a problem, consult Rekal's own memories first:
rekal "<describe the problem>"The prior context for what you're working on may already exist.
- Keep this file up to date. Any change to commands, packages, files, or behavior must be reflected here. Update
--helptext when command behavior changes. Updatedocs/spec/command/when a command spec changes. Stale docs are worse than no docs. - Consult
SOUL.mdbefore design decisions. Consultrekalbefore starting work on a problem.
Single binary. Everything embedded — CLI, database engine, embedding model, compression dictionary.
- CLI: Cobra (
github.com/spf13/cobra) - Storage: DuckDB via
github.com/marcboeker/go-duckdb(database/sqlinterface) - Compression: zstd via
github.com/klauspost/compresswith preset dictionary - IDs: ULID via
github.com/oklog/ulid/v2 - Embeddings: LSA (gonum) + Nomic (platform-specific builds)
- Build: mise, go modules
- Lint: golangci-lint v2 (2.8.0)
- Language: Go 1.25.6
Two databases in .rekal/:
data.db— immutable source of truth. Append-only. Pushed to git.index.db— local derived index. Rebuilt from data.db. Never pushed.
This split is a direct consequence of the soul: thin on the wire, rich on the machine.
main.go: Entry point
root.go: Root command (recall is the default) + command registrationrecall.go: Hybrid search — BM25 + LSA + Nomic rankingcheckpoint.go: Capture session after commitpush.go: Push data to remote branchsync.go: Sync team contextsync_remote.go: Remote sync implementationexport.go: Encode checkpoints to wire format for pushimport.go: Decode wire format during syncinit.go: Bootstrap Rekal in a git repoclean.go: Remove Rekal setup — completely, no residueindex_cmd.go: Rebuild index DB from data DBlog.go: Show recent checkpointsquery.go: Raw SQL accessversion.go: Version constant (set via ldflags)errors.go: SilentError pattern for clean error outputpreconditions.go: Shared checks (git repo, init done, index exists)
codec/: Binary wire format — frame encoding/decoding, body, dictionary, preset zstd dictionarysession/: Claude Code.jsonlparsing — extract turns, tool calls, deduplicatedb/: DuckDB backend — open, close, schema, insert helpers, index populationlsa/: Latent Semantic Analysis embeddingsnomic/: Nomic-embed-text deep semantic embeddings (platform build tags)skill/: Rekal Skill definition for Claude Code integrationversioncheck/: Auto-update notificationintegration_test/: Integration tests (//go:build integration)
DEVELOPMENT.md: Dev process, testing, CI/CDgit-transportation.md: Git transport layer designdb/: Database schema and designspec/preconditions.md: Shared checks for all commandsspec/command/: One file per command — checkpoint, clean, index, init, log, push, query, recall, sync
mise run test # Unit tests only
mise run test:integration # Integration tests only
mise run test:ci # All tests (unit + integration) with race detectionmise run fmt # Format code (gofmt)
mise run lint # Lint check (gofmt + golangci-lint)mise run build # Build binary with version from git tag
mise run build:all # Build for all platforms (snapshot)mise run fmt && mise run lint && mise run test:ciUnit tests (_test.go next to source, same package). Always use t.Parallel().
Integration tests (integration_test/, //go:build integration). Use TestEnv pattern — isolated temp git repos per test. Tests public API only. Cannot be parallelized (uses os.Chdir).
root.gosetsSilenceErrors: trueglobally- Commands return
NewSilentError(err)when they've already printed a user-friendly message - For normal errors, return the error directly
if err := EnsureGitRoot(); err != nil {
cmd.SilenceUsage = true
fmt.Fprintln(cmd.ErrOrStderr(), err)
return NewSilentError(err)
}All commands except init and clean must call both:
EnsureGitRoot()— verifies inside a git repoEnsureInitDone(gitRoot)— verifies.rekal/exists
func newFooCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "foo",
Short: "Short description",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
gitRoot, err := EnsureGitRoot()
if err != nil {
fmt.Fprintln(cmd.ErrOrStderr(), err)
return NewSilentError(err)
}
if err := EnsureInitDone(gitRoot); err != nil {
fmt.Fprintln(cmd.ErrOrStderr(), err)
return NewSilentError(err)
}
// command logic
return nil
},
}
return cmd
}From the soul: short sentences, plain words, say what happened, say what to do, stop.
rekal: not a git repository (run this inside a project)
rekal: captured 3 sessions, 847 turns
rekal: no sessions match "JWT expiry" in src/auth/
No exclamation marks. No emoji. No "oops."
- Write lint-compliant Go code on the first attempt
- Follow standard Go idioms: proper error handling, no unused variables/imports
- Handle all errors explicitly
- Reference
.golangci.yamlfor enabled linters (govet, errcheck, ineffassign, staticcheck, unused)
- Ensure main is green (CI, Lint, License Check)
- Tag and push:
git tag v0.x.y git push origin v0.x.y
- Release workflow validates then publishes via GoReleaser