indexer

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package indexer builds and refreshes the knowledge index.

It joins the three pieces that have existed separately until now: discovery decides what may be indexed, a fetcher gets the content, and the store holds it. Everything it depends on is an interface, so the whole cycle runs in a test without a forge, a clone or a network.

Index

Constants

View Source
const DefaultInterval = time.Hour

DefaultInterval is how often the corpus is re-read when configuration says nothing.

Documentation changes on the timescale of a working day, not a minute, and every refresh clones the repositories that moved. An hour keeps the index current enough to answer from without turning the bot into a polling load on the forge.

Variables

View Source
var ErrNotStarted = errors.New("indexer: not started")

ErrNotStarted is reported by readiness before the service has run.

Functions

func Collectable

func Collectable(name string, size int64) bool

Collectable reports whether a file is worth reading into memory.

Filtering before the read rather than after is what keeps an indexing run proportional to the documentation rather than to the repository: a vendored blob or a generated dump costs the same to read as to index, and answers nothing either way.

Types

type CloneFetcher

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

CloneFetcher gets repository content by shallow-cloning in memory.

In memory rather than to disk deliberately. The corpus predicate is a security boundary, and content that never touches the filesystem cannot be left behind by a crash between the clone and the check that should have rejected it.

func NewCloneFetcher

func NewCloneFetcher(token string) *CloneFetcher

NewCloneFetcher builds a fetcher over go/repo.

func (*CloneFetcher) Fetch

func (f *CloneFetcher) Fetch(ctx context.Context, r forge.Repository) (*Snapshot, error)

Fetch clones a repository and returns the files the corpus indexes.

Filtering happens here rather than after: a file the corpus will not index is never read into memory, so the size of an indexing run tracks the documentation rather than the repository.

type Discoverer

type Discoverer interface {
	Discover(ctx context.Context) ([]corpus.Source, error)
}

Discoverer enumerates the corpus and reports a verdict for every candidate.

Declared here rather than taken as *corpus.Discoverer so the indexing cycle is testable against a fixed set of sources, without standing up three forge capabilities to exercise a loop that only consumes their result.

type Fetcher

type Fetcher interface {
	Fetch(ctx context.Context, r forge.Repository) (*Snapshot, error)
}

Fetcher gets a repository's indexable content.

An interface rather than a clone, so the indexing cycle is testable without a network — and so the shallow-clone implementation is one replaceable piece rather than something threaded through the whole package.

type Indexer

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

Indexer builds and refreshes the index.

func New

func New(d Discoverer, f Fetcher, w *index.Writer, db *store.DB) *Indexer

New builds an indexer.

func (*Indexer) Build

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

Build indexes every qualifying source from scratch.

func (*Indexer) Refresh

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

Refresh re-indexes only the sources whose commit has moved.

func (*Indexer) Sources

func (ix *Indexer) Sources(ctx context.Context) ([]corpus.Source, error)

Sources reports every candidate and the verdict reached about it.

Rejections included, because a listing has to explain an absence: an operator asking why infra is not indexed should get the clause that excluded it rather than silence.

func (*Indexer) Status

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

Status reports what is indexed, per source.

type Report

type Report struct {
	Sources   int
	Indexed   int
	Skipped   int
	Documents int
	Chunks    int

	// Failed names the sources that could not be indexed, and why. A run
	// continues past a failure, so this is how an operator learns one happened.
	Failed map[string]string
}

Report summarises an indexing run.

type Service

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

Service keeps the index current on a schedule.

It calls the same Refresh the CLI command calls, so the scheduled and manual routes cannot drift apart.

func NewService

func NewService(ix *Indexer, opts ...ServiceOption) *Service

NewService builds a refresher over an indexer.

func (*Service) Failures

func (s *Service) Failures() int64

Failures counts cycles that could not complete.

func (*Service) Live

func (s *Service) Live() error

Live reports whether the process should keep running. It always may.

A refresh failing does not make the service unhealthy: the index it already holds is still answerable, and a restart would not reach the forge either. The loop has no other way to end — it runs until Stop closes it — so there is no unexpected exit for liveness to catch. Refresh health is reported through the counters and `index status`, which is where an operator can act on it.

func (*Service) Ready

func (s *Service) Ready() error

Ready reports whether the index is usable.

Staleness is deliberately not a readiness failure. It is the same judgement as a reconnect that lost events: an index that has not refreshed recently is still usable, answering from it with a caveat beats not answering, and taking the bot out of service would not fix a forge nobody can reach. Staleness is reported through `index status` and the refresh counters instead.

func (*Service) Refreshes

func (s *Service) Refreshes() int64

Refreshes counts successful cycles.

func (*Service) Running

func (s *Service) Running() bool

Running reports whether a refresh is in progress.

func (*Service) Start

func (s *Service) Start(ctx context.Context) error

Start begins refreshing.

It returns immediately and refreshes in the background, including the first one. Indexing the corpus takes minutes, and a controller waiting on Start would report the whole daemon as failing to come up while it was working correctly.

func (*Service) Stop

func (s *Service) Stop(ctx context.Context)

Stop ends the refresh loop and waits for any refresh already running.

Waiting rather than returning immediately: a refresh is mid-write when shutdown arrives, and returning would let the process exit between writing a document and recording the commit it came from, leaving the index claiming to hold content it does not. The wait is bounded by the shutdown deadline in ctx, after which the refresh is cancelled and the transaction rolls back.

Safe unstarted and safe to call twice.

type ServiceOption

type ServiceOption func(*Service)

ServiceOption configures a Service.

func WithInterval

func WithInterval(d time.Duration) ServiceOption

WithInterval sets how often the index refreshes.

func WithLogger

func WithLogger(l *slog.Logger) ServiceOption

WithLogger sets the logger.

type Snapshot

type Snapshot struct {
	CommitSHA string

	// Files is path to content, already filtered to what the corpus indexes.
	Files map[string][]byte
}

Snapshot is a repository's content at one commit.

type SourceStatus

type SourceStatus struct {
	Path        string
	CommitSHA   string
	Documents   int
	Chunks      int
	LastIndexed time.Time
	LastError   string
}

SourceStatus is what `index status` reports for one source.

Jump to

Keyboard shortcuts

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