Chapter 9: Code Symbol and Declaration Indexing

Technical documentation rarely lives apart from the source code it describes. Architecture Decision Records cite Go interfaces (search.Chunker), frontend guides reference TypeScript types and React components, data engineering notes point to Python pipelines, and systems runbooks reference Rust structs and traits.

Rather than treating source files as flat plain text or requiring heavyweight language servers (LSP) to be running in the background, AutoDoc ships a built-in declaration-level code chunker (core/registrations/builtin.go and golib/search/chunk/code). It partitions Go (.go), TypeScript / JavaScript (.ts, .tsx, .js, .jsx, .mjs), Python (.py), and Rust (.rs) files at declaration boundaries, pairs verbatim code bodies with declaration-focused embedding surfaces (breadcrumb + doc + signature), and exposes custom compile-time chunker hooks (app.Options.Chunkers) and plain-text extensions (workspace.set_text_extensions).

flowchart LR
    SRC["Source File (.go, .ts, .tsx, .js, .py, .rs, + custom)"] --> ROUTER["Extension Router (core/registrations)"]
    ROUTER --> GOAST["Go AST Chunker (go/parser, code-go-2+text-5)"]
    ROUTER --> SCAN["Scanner Chunkers (TypeScript code-ts-2, Python code-py-2, Rust code-rs-2)"]
    ROUTER --> CUSTOM["Custom Chunkers & text_extensions (app.Options.Chunkers / chunk.Text)"]
    GOAST --> SPLIT["Two-Surface Unit (Body = Verbatim Code, Embed = Breadcrumb + Doc + Signature)"]
    SCAN --> SPLIT
    CUSTOM --> SPLIT
    SPLIT --> STORE["SQLite chunks & FTS5 & Vector Tables"]
    STORE --> QUERY["search.query (Lexical + Semantic + Reranked Code Retrieval)"]

1. Declaration-Level Code Chunking (golib/search/chunk/code)

When the workspace indexer (core/index) encounters a registered code extension, it routes the file to its language-specific search.Chunker:

1. Exact Go AST Chunker (.go, code-go-2+text-5)

Because Go's standard library includes go/parser, go/ast, and go/token, AutoDoc parses .go files into a complete Abstract Syntax Tree:

2. String-Aware Scanner Chunkers for TypeScript/JS, Python, and Rust

For TypeScript/JavaScript, Python, and Rust, golib/search/chunk/code uses fast, string-and-comment-aware scanners where braces or keywords inside strings, template literals, raw strings, char literals, and comments never start or end a unit:

Illustrative Declaration Chunking Throughput Profile by Chunker050100150200250300350400450Illustrative Throughput (Chunks / ms)Go (code-go-2)Rust (code-rs-2)TypeScript (code-ts-2)Python (code-py-2)Text (chunk.Text)Built-in ChunkerChunkerGo (code-go-2)Python (code-py-2)Rust (code-rs-2)Text (chunk.Text)TypeScript (code-ts-2)

Note: The chart above shows an illustrative relative throughput profile comparing AST-based Go chunking, scanner-based TypeScript/Python/Rust declaration chunkers, and plain-text fallback chunking.


2. The Partition Invariant and Two-Surface Embedding Design

Every built-in code chunker in golib/search/chunk/code obeys a strict lossless partition invariant across the file:

  1. Contiguous, Non-Overlapping Spans: Chunk byte spans ascend and never overlap, only whitespace lies outside them, and Body == string(Doc.Text[ByteStart:ByteEnd]). Detached comments, preprocessor directives, and top-level glue statements become header or glue chunks so no line of code is ever dropped from lexical search.
  2. Two Surfaces (Body vs. Embed):
    • Body (Lexical FTS5 & Display): Holds the verbatim source code of the declaration (Doc.Text[ByteStart:ByteEnd]), enabling exact identifier and literal searches in FTS5.
    • Embed (Semantic Vector Surface): Constructed strictly from the breadcrumb, doc comment, and declaration signature—never the raw function body implementation. When you run a semantic query like "how do we reclaim unused model vectors in batches", the vector embedding for func (s *Store) reclaim(ctx context.Context, fp string, write writeFn) and its doc comment scores a clean semantic hit without noise from loop variables.
  3. Bounded Embeddings & Oversized Declarations: No Embed text ever exceeds the chunk's token budget (long doc comments are trimmed while keeping the signature whole). When an oversized function or class exceeds the token budget, the chunker splits it at statement boundaries: every fragment's breadcrumb retains the enclosing signature, and continuation fragments embed the signature alongside their local comments and the identifiers they declare and call.
CategoryExtensionsChunker_or_Extractor_IDExtracted_Units_and_DeclarationsEmbedding_and_Fallback_Behavior
Go (Built-in).gocode-go-2+text-5 (go/parser)File header (build tags, package doc, imports), functions, receiver methods, type specs, const/var groupsEmbeds breadcrumb + doc + signature; falls back to chunk.Text if file has syntax errors
TypeScript / JavaScript (Built-in).ts, .tsx, .js, .jsx, .mjscode-ts-2 (string-aware scanner)Functions, classes and methods, interfaces, type aliases, exported const arrow functionsPreserves JSDoc comments and @decorators; splits oversized declarations at statement boundaries
Python (Built-in).pycode-py-2 (indentation scanner)def, async def, class and methods by indentation scopePreserves @decorators and triple-quoted docstrings; embeds breadcrumb + docstring + signature
Rust (Built-in).rscode-rs-2 (string-aware scanner)fn, struct, enum, union, trait, and impl blocks with their associated functionsPreserves /// doc comments and #[...] attributes; embeds breadcrumb + doc + signature
Derived Documents (Built-in).docx, .htm, .htmlgolib/extract/docx & golib/extract/htmlHeading-structured text extracted in memory (bounded by MaxContainer and MaxText)Chunked by heading hierarchy; served as derived Markdown via doc.read and raw bytes via doc.read_raw
Plain-Text Extensions (Configurable)text_extensions (e.g. .sql, .proto, .lua, .toml)chunk.Text (text-5)Paragraph and token-budget windows across declared UTF-8 text extensionsConfigured via workspace.set_text_extensions; refused if extension collides with a registered code chunker
Custom Compile-Time Chunkersapp.Options.Chunkers (e.g. .dsl)Custom search.Chunker IDDomain-specific declaration units defined by custom binary build (ADR 0216)Preserved as held (current, stale, unchecked) when opened by a community binary without that chunker

3. Configuring a Workspace to Index Source Trees Alongside Docs

By default, a newly created AutoDoc workspace includes **/*.md, **/*.txt, **/*.yaml, and **/*.yml and excludes .git/** and **/node_modules/**. You can enable code indexing either in the UI or via workspace.configure / workspace.set_patterns.

Enabling Code File Types in the TUI / GUI

  1. Open Go › Manage workspaces… (Ctrl+W then Manage…), select your workspace, and choose Edit… (or open Options › Editor preferences… › File types…).
  2. Toggle the Code row on. AutoDoc automatically adds the code include patterns and appends vendor/**, target/**, dist/**, and build/** to the workspace's exclude list so third-party dependencies and compiled artifacts are never walked or watched.

Configuring include, exclude, and text_extensions via RPC

For repositories where documentation (docs/, adrs/) lives beside source code (core/, rpc/, sql/, lua/), configure include, exclude, and text_extensions atomically with workspace.configure:

# Configure workspace "autodoc-repo" to index Markdown, YAML, built-in code (.go, .rs, .py, .ts, .js),
# and custom UTF-8 text extensions (.sql, .proto, .lua)
autodoc --call workspace.configure '[
  "autodoc-repo",
  {
    "include": [
      "**/*.md",
      "**/*.yaml",
      "**/*.yml",
      "**/*.{go,rs,py,ts,tsx,js,jsx,mjs,sql,proto,lua}"
    ],
    "exclude": [
      ".git/**",
      "**/node_modules/**",
      "vendor/**",
      "target/**",
      "dist/**",
      "build/**",
      "**/*_test.go"
    ],
    "text_extensions": [".sql", ".proto", ".lua", ".conf"]
  }
]'

Understanding text_extensions vs. Registered Code Chunkers (text_collisions)


4. Querying Code Declarations and Inspecting Registered Chunkers

Once code files are indexed, search.query searches across both Markdown documentation and code declarations simultaneously, or you can scope paths to specific source packages.

1. Searching Code Declarations with search.query

Every code hit returned by search.query includes the declaration's breadcrumb (carrying the package/type and signature), 1-based line_start and line_end, and byte offsets:

# Search Go declarations under core/index for unused model vector reclamation
autodoc --call search.query '[
  "autodoc-repo",
  "reclaim unused model vectors in batches",
  {
    "paths": ["core/index"],
    "limit": 5
  }
]'

Example of one element of the response's hits array (the full hitJSON shape from rpc/search.go; the response also carries mode_used, semantic, rank, and stages, covered in Chapter 05). Values are illustrative:

{
  "path": "core/index/reclaim.go",
  "breadcrumb": "core/index/reclaim.go › func (x *Indexer) Sweep(ctx context.Context) ([]Reclaimed, error)",
  "snippet": "// Sweep reclaims vectors of models no workspace uses...",
  "generation": 42,
  "byte_start": 4120,
  "byte_end": 4388,
  "line_start": 144,
  "line_end": 151,
  "score": 0.0325,
  "relevance": 0.91,
  "via": ["lexical", "semantic"],
  "hold": "",
  "rank_score": 0.87
}

2. Inspecting Registered Chunkers and Derived Formats (sys.capabilities)

You can inspect the active daemon's registered code chunker versions and derived format extractors at any time via sys.capabilities:

# Inspect registered chunkers (.go, .ts, .py, .rs, plus any custom chunkers) and formats (.docx, .html)
autodoc --call sys.capabilities '[]'

5. Custom Compile-Time Chunkers (app.Options.Chunkers) and Held Files

cmd/autodoc is the community build: app.Main with nothing of its own. Every build, the community one included, has golib's code chunkers (search/chunk/code) and derives .docx, .htm and .html (golib's extract/docx and extract/html; ADR 1791429611). Another main can hand app.Main more chunkers and a deriver (ADR 0216):

func main() {
	os.Exit(app.Main(context.Background(), os.Args[1:], app.Options{
		Version:  version,                                       // -X main.version, as cmd/autodoc's
		Chunkers: map[string]search.Chunker{".zig": zigChunker}, // golib's search.Chunker, by extension
		Deriver:  pdfDeriver,                                    // more formats' text, read-only
		Rank:     app.Rank{Ranker: slmRanker, Window: 20},       // golib's rank.Ranker: the only one in use
	}))
}

Because multiple AutoDoc builds can share a single SQLite store (autodoc.db), a community binary that attaches to a store containing .zig chunks produced by a custom build never overwrites or drops those chunks. Instead, it marks those files as held (Hit.Hold set to "current", "stale", or "unchecked", and counted under held in index.status), keeping every declaration searchable across builds.


← Previous: Chapter 08: Knowledge Graphs & ADR Lifecycle | Index | Next: Chapter 10: Todo Task Engine →