Help us shape the future of Go! Take the 2026 Go Developer Survey

index

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package index keeps graph.db in sync with the working tree.

The entry point is Indexer.Freshen, which implements PLAN "Freshness algoritması": it locates the repository, works out which files changed since the last scan (git index stat shortcut, untracked walk with the persisted directory cache, parallel lstat sweep plus dirty hints, the racy-git rule and content keys), returns early when nothing changed, and otherwise takes the cross-process write lock and runs the pipeline: content filters, parse cache (cache.db), a guarded worker pool, a bounded channel and a single batched writer, followed by the name resolution pass.

Freshen is safe to call before every MCP tool call and hook: concurrent calls in one process share one run (singleflight), and a caller that cannot get the write lock within its budget gets a Result describing the stale state instead of blocking.

Facts are mapped onto graph rows in write.go; package rows and import targets are maintained by an order-independent post-pass (link.go) so an incremental update and a from-scratch build produce the same graph.

Module identities and import targets depend on other files (go.mod, tsconfig.json, whether a/b.py exists, ...). RepoCtx records every such answer with the file it was consulted for (store table dep_owners); when an answer changes, only those files are reprocessed, from cache.

Index

Constants

View Source
const DirName = ".graphmaster"

DirName is the per-repository state directory.

View Source
const EnvCrashAfterBatch = "GRAPHMASTER_TEST_CRASH_AFTER_BATCH"

EnvCrashAfterBatch makes the indexer exit abruptly right after the given number of writer commits. Tests only: it simulates a kill mid-build.

View Source
const NotGitHint = "not a git repository; run `graphmaster index --force`"

NotGitHint is the user-facing advice for ErrNotGitRepo.

Variables

View Source
var ErrNotGitRepo = repo.ErrNotGitRepo

ErrNotGitRepo is returned when no git working tree (and no forced non-git index) encloses the start directory.

Functions

func QName

func QName(lang model.Lang, module string, parts []string) string

QName returns the qualified name stored in nodes.qname. Without a QNamer the module and the container chain are joined with the language's member separator: "::" for C++ and Rust, "." otherwise, e.g. "example.com/app/store.Memory.Get" or "shop.models.Item.total".

Types

type DepOwner

type DepOwner struct{ Key, Owner string }

DepOwner attributes the dependency Key to the file Owner it was consulted for ("" = no owner).

type FullResolver

type FullResolver interface {
	ResolveAll(ctx context.Context, st *store.Store, rc model.RepoContext) error
}

FullResolver may be implemented by a Resolver to handle a full build (every name queued) more cheaply than ResolvePending, e.g. by skipping the dependent re-checks an incremental run needs. resolve.Resolver implements it.

type IncrementalHints

type IncrementalHints interface {
	// CommonNameLimit is the reference count above which a name is too
	// common for global guessing; crossing it changes how every
	// reference of the name resolves.
	CommonNameLimit() int
	// ReadsForeignSource reports whether resolving references may read the
	// source text of definitions of lang in other files, so a body-only
	// edit can change how references elsewhere resolve.
	ReadsForeignSource(lang model.Lang) bool
}

IncrementalHints may be implemented by a Resolver to let the writer keep a rewritten file's definitions in place when only their bodies or positions changed (see rewrite.go). Without it every rewrite replaces the file's graph and queues all of its names. resolve.Resolver implements it.

type Indexer

type Indexer struct {
	// contains filtered or unexported fields
}

Indexer keeps one repository's graph.db fresh. It is safe for concurrent use; Freshen calls are deduplicated within the process.

func Open

func Open(ctx context.Context, dir string, opts Options) (*Indexer, error)

Open locates the repository enclosing dir, prepares .graphmaster/ and opens graph.db and cache.db. Outside a git repository it fails with ErrNotGitRepo unless opts.AllowNoGit is set or the directory was previously indexed with it.

func (*Indexer) Close

func (ix *Indexer) Close() error

Close releases the databases.

func (*Indexer) Config

func (ix *Indexer) Config() *config.Config

Config returns the effective configuration.

func (*Indexer) Freshen

func (ix *Indexer) Freshen(ctx context.Context, budget time.Duration) (*Result, error)

Freshen brings the graph up to date (PLAN "Freshness algoritması"). budget bounds the wait for the write lock: MCP 20s, PreToolUse 1.5s, SessionStart 590s, async 0. When the lock is not acquired in time the graph is left as is and the Result reports Stale (and Building during a first build in another process). Concurrent calls share one run.

func (*Indexer) Location

func (ix *Indexer) Location() *Location

Location returns where the repository and its state directory are.

func (*Indexer) Rebuild

func (ix *Indexer) Rebuild(ctx context.Context, budget time.Duration) (*Result, error)

Rebuild discards the graph (keeping cache.db) and builds it again.

func (*Indexer) Root

func (ix *Indexer) Root() *repo.Root

Root returns the located repository.

func (*Indexer) Status

func (ix *Indexer) Status(ctx context.Context) (*Status, error)

Status collects index statistics.

func (*Indexer) Store

func (ix *Indexer) Store() *store.Store

Store returns the open graph.db. It may be replaced by a later Freshen when another process rebuilt the database file.

type Location

type Location struct {
	Root *repo.Root
	// Dir is the absolute .graphmaster directory.
	Dir string
	// NoGit is set for a directory indexed without git.
	NoGit bool
}

Location is a located repository.

func Locate

func Locate(start string, allowNoGit bool) (*Location, error)

Locate finds the repository enclosing start: a git working tree, or a directory previously indexed with --force. With allowNoGit, a directory outside any repository becomes its own (non-git) root.

func (*Location) Prepare

func (l *Location) Prepare() error

Prepare creates the .graphmaster directory with a catch-all .gitignore and registers it in .git/info/exclude (never in a tracked .gitignore). It is idempotent.

type Options

type Options struct {
	// Config is the effective configuration; nil loads it for the
	// repository (falling back to config.Default on error).
	Config *config.Config
	// Resolver runs after every write; nil leaves pending_names queued.
	Resolver Resolver
	// Workers is the parse parallelism (default GOMAXPROCS).
	Workers int
	// BatchFiles and BatchDelay tune writer commits (store defaults).
	BatchFiles int
	BatchDelay time.Duration
	// AllowNoGit lets Open index a directory outside any git repository
	// (`graphmaster index --force`).
	AllowNoGit bool
	// Progress, if set, is called from the writer goroutine as files are
	// committed during a run that writes.
	Progress func(done, total int)
	// Now is the clock used for scan timestamps (racy-git rule); tests
	// only. Default time.Now.
	Now func() time.Time
	// Logf receives diagnostics; default discards.
	Logf func(format string, args ...any)
	// contains filtered or unexported fields
}

Options configure an Indexer.

type QNamer

type QNamer interface {
	QName(module string, parts []string) string
}

QNamer may be implemented by a model.LanguageSpec to control how a definition's qualified name is built from its module identity (LanguageSpec.ModuleOf) and container chain (Facts.QNameParts).

type RepoCtx

type RepoCtx struct {
	// contains filtered or unexported fields
}

RepoCtx is the real model.RepoContext: code-file existence comes from the set of files being indexed (so answers match the graph), everything else (go.mod, tsconfig.json, composer.json, Cargo.toml, *.csproj, directories) from the filesystem with cached reads. It is safe for concurrent use.

Every answer is recorded as a dependency (key -> signature). Answers given through a view returned by For are also attributed to that view's owner file, so a later change of the answer only reprocesses the owners; answers given by the RepoCtx itself have no owner and force a full rebuild when they change.

func NewRepoCtx

func NewRepoCtx(root *repo.Root, code map[string]struct{}) *RepoCtx

NewRepoCtx returns a context for the worktree of root. code is the set of repo-relative code paths being indexed; it must not be modified later.

func (*RepoCtx) Deps

func (c *RepoCtx) Deps() map[string]string

Deps returns a copy of the recorded dependencies.

func (*RepoCtx) DirExists

func (c *RepoCtx) DirExists(rel string) bool

func (*RepoCtx) FileExists

func (c *RepoCtx) FileExists(rel string) bool

func (*RepoCtx) For

func (c *RepoCtx) For(owner string) model.RepoContext

For returns a view of c whose answers are attributed to the file owner (repo-relative path), e.g. the file whose module identity or imports are being resolved.

func (*RepoCtx) ListDir

func (c *RepoCtx) ListDir(dir string) []string

func (*RepoCtx) ReadFile

func (c *RepoCtx) ReadFile(rel string) ([]byte, error)

func (*RepoCtx) TakeNewDeps

func (c *RepoCtx) TakeNewDeps() map[string]string

TakeNewDeps returns the dependencies recorded since the previous call, for incremental persistence.

func (*RepoCtx) TakeNewOwners

func (c *RepoCtx) TakeNewOwners() []DepOwner

TakeNewOwners returns the dependency attributions recorded since the previous call.

func (*RepoCtx) Unrecorded

func (c *RepoCtx) Unrecorded() model.RepoContext

Unrecorded returns a view whose answers are not recorded at all. It is for derived data that is recomputed whenever any dependency or the file set changes (import targets, see linker).

type Resolver

type Resolver interface {
	ResolvePending(ctx context.Context, st *store.Store, rc model.RepoContext) error
}

Resolver resolves the names queued in pending_names after files were written. It must replace (not append) the edges of every name it processes and clear them from the queue. It runs while the caller holds the write lock and no Writer is open. internal/resolve provides the real implementation; index only depends on this interface.

type Result

type Result struct {
	// File changes applied (or, when Stale, detected but not applied).
	Added, Modified, Deleted, StatOnly int
	// Parsed files went through tree-sitter; CacheHits came from cache.db.
	Parsed, CacheHits, ParseErrors int
	// FullBuild is set when the graph was built from an empty database.
	FullBuild bool
	// Rebuilt explains why the graph was reset (schema mismatch, config or
	// manifest change, --force), "" otherwise.
	Rebuilt string
	// Stale is set when changes were found but the write lock could not be
	// acquired within the budget; Pending counts the files waiting.
	Stale   bool
	Pending int
	// Building is set while the first build is still in progress
	// (another process holds the lock); Progress is its percentage.
	Building bool
	Progress int
	// Waited is the time spent waiting for the write lock.
	Waited time.Duration
	// Duration is the wall time of the whole run.
	Duration time.Duration
}

Result describes one Freshen run. MCP tools and hooks render Banner as the first line of their output.

func (*Result) Banner

func (r *Result) Banner() string

Banner is the one-line freshness summary, e.g. "fresh (+2 files, 41ms)", "⚠ stale: 3 files pending" or "⚠ building 37% (partial graph)".

func (*Result) Changed

func (r *Result) Changed() int

Changed is the number of files whose graph changed.

func (*Result) Clean

func (r *Result) Clean() bool

Clean reports whether the graph was already up to date.

type Status

type Status struct {
	Root  string
	NoGit bool
	Mode  config.Mode
	// State is "ready", "building" or "" (never indexed); Progress is the
	// build percentage while building.
	State    string
	Progress int
	// Files counts files rows by status; Langs counts indexed files
	// (ok or parse_err) by language.
	Files map[model.FileStatus]int64
	Langs map[model.Lang]int64
	store.Counts
	Pending    int64 // names, files and dependents waiting for resolution
	CacheBytes int64 // cache.db on disk
	LastScan   time.Time
}

Status summarizes the index for `graphmaster status` and the MCP status tool. It reads the database only; it does not freshen.

func (*Status) Format

func (s *Status) Format() string

Format renders the status as plain text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL