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
- Variables
- func QName(lang model.Lang, module string, parts []string) string
- type DepOwner
- type FullResolver
- type IncrementalHints
- type Indexer
- func (ix *Indexer) Close() error
- func (ix *Indexer) Config() *config.Config
- func (ix *Indexer) Freshen(ctx context.Context, budget time.Duration) (*Result, error)
- func (ix *Indexer) Location() *Location
- func (ix *Indexer) Rebuild(ctx context.Context, budget time.Duration) (*Result, error)
- func (ix *Indexer) Root() *repo.Root
- func (ix *Indexer) Status(ctx context.Context) (*Status, error)
- func (ix *Indexer) Store() *store.Store
- type Location
- type Options
- type QNamer
- type RepoCtx
- func (c *RepoCtx) Deps() map[string]string
- func (c *RepoCtx) DirExists(rel string) bool
- func (c *RepoCtx) FileExists(rel string) bool
- func (c *RepoCtx) For(owner string) model.RepoContext
- func (c *RepoCtx) ListDir(dir string) []string
- func (c *RepoCtx) ReadFile(rel string) ([]byte, error)
- func (c *RepoCtx) TakeNewDeps() map[string]string
- func (c *RepoCtx) TakeNewOwners() []DepOwner
- func (c *RepoCtx) Unrecorded() model.RepoContext
- type Resolver
- type Result
- type Status
Constants ¶
const DirName = ".graphmaster"
DirName is the per-repository state directory.
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.
const NotGitHint = "not a git repository; run `graphmaster index --force`"
NotGitHint is the user-facing advice for ErrNotGitRepo.
Variables ¶
var ErrNotGitRepo = repo.ErrNotGitRepo
ErrNotGitRepo is returned when no git working tree (and no forced non-git index) encloses the start directory.
Functions ¶
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 ¶
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) Freshen ¶
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.
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.
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 ¶
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 ¶
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) FileExists ¶
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) TakeNewDeps ¶
TakeNewDeps returns the dependencies recorded since the previous call, for incremental persistence.
func (*RepoCtx) TakeNewOwners ¶
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 ¶
Banner is the one-line freshness summary, e.g. "fresh (+2 files, 41ms)", "⚠ stale: 3 files pending" or "⚠ building 37% (partial graph)".
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.