Back to .md Directory

ADR-002: AST-Pure Chunking

Replaces a broken chunker with AST-pure logic so every line belongs to exactly one semantically typed chunk and no orphans remain.

May 2, 2026
0 downloads
0 views
ai rag
View source

What this file does

Replaces a broken chunker with AST-pure logic so every line belongs to exactly one semantically typed chunk and no orphans remain.

When to use it

  • Your code chunker leaves orphan lines or splits methods mid-statement
  • You need chunks that preserve AST node types like function, class, or module
  • You want JSDoc comments attached to their method chunk for better embeddings
  • You are building a code indexing or retrieval system that consumes chunk metadata

Assumes this stack

TypeScriptAST

ADR-002: AST-Pure Chunking

Status: Accepted Date: 2026-03-02 Supersedes: Previous extractChunks() + splitLargeChunk() approach Related: ADR-001 (chunk size limits)

Problem

The previous chunker had two critical flaws:

1. ~16% of code was orphaned (not in any chunk)

Tested on indexer.ts (1562 lines): 227 non-empty lines were not covered by any chunk.

Orphan typeExampleWhy missed
File importsimport { foo } from "./bar"import_statement not in getChunkType()
Module constantsconst MAX = 100lexical_declaration not in getChunkType()
Class fieldsprivate field: stringpublic_field_definition not recognized
Static propertiesstatic readonly ITEMS = [...]Same — field definition
JSDoc comments/** Does something */comment nodes separate from method nodes
Small methodsclear(): void { ... }Below MIN_CHUNK_TOKENS threshold

2. Oversized nodes got blind line-split

splitLargeChunk() cut by line count with zero AST awareness. A 640-line method became 22 arbitrary 29-line blocks that started/ended mid-statement.

Decision

Replace extractChunks() + splitLargeChunk() with AST-pure chunking:

Every line of source code belongs to exactly one chunk. Every chunk maps to an AST node (or connected group of sibling nodes). No orphans. No blind splits.

New Architecture

Step 1: Top-down AST walk with size-aware decisions (processChildren)

For each child node:
  if recognized type && fits MAX_CHUNK_TOKENS → emit as one chunk
  if recognized type && too large && container (class/module) → descend into body
  if recognized type && too large && leaf (function/method) → split into connected parts
  if unrecognized (import, field, comment) → accumulate as gap

Step 2: Connected parts for oversized leaves (splitIntoConnectedParts)

Oversized functions/methods split by line count at MAX_CHUNK_TOKENS boundaries, but each part carries:

  • Original chunkType (not "block")
  • Name with (part K/N) suffix
  • partIndex / totalParts for downstream consumers

Step 3: Gap absorption (flushGap)

Non-chunk siblings (imports, fields, comments) are handled:

  1. JSDoc comments immediately before a method → included in that method's chunk
  2. Field declarations in class body → merged into a "fields" module chunk
  3. File header (imports + constants before first function) → "preamble" module chunk
  4. Trivial gaps (< 10 chars) → dropped

Data Model Changes

Added to ParsedChunk and CodeChunk:

partIndex?: number;   // 1-based index within the group
totalParts?: number;  // total parts in the group

Consequences

Positive

  • ~100% code coverage (no orphan lines)
  • Every chunk has semantic type (function, method, class, module)
  • JSDoc attached to its method (better embeddings)
  • Class fields grouped as meaningful chunks
  • File preamble (imports/constants) preserved
  • Oversized splits preserve original type + are linked via partIndex/totalParts
  • splitLargeChunk() eliminated (source of "block" type + cross-boundary splits)

Negative

  • Chunk count increases ~10-15% (gap chunks for fields/imports)
  • Gap chunks ("fields", "preamble") have lower search value but prevent orphans
  • Comment attachment is heuristic (JSDoc goes to next sibling — correct 99% of time)

Migration

  • Requires claudemem index --force to re-index after this change
  • splitLargeChunk() removed, replaced by splitIntoConnectedParts()
  • extractChunks() + walkTree() removed, replaced by extractChunksAST() + processChildren()
  • fallbackChunk() kept for truly unparseable files only

What's inside

Problem analysis, decision statement, three-step architecture, data model changes, and consequences with migration notes

Change this for your project

  • Replace indexer.ts with your own test file path if you run the orphan analysis
  • Replace claudemem index --force with your own re-index command
  • Replace MAX_CHUNK_TOKENS and MIN_CHUNK_TOKENS with your own thresholds

Where it goes

Keep it in your repository where the agent or team that needs it will read it.

Worth borrowing

  • Gap absorption that attaches JSDoc to the next sibling and groups field declarations into a single chunk
  • Oversized leaf splitting that preserves the original chunk type and links parts via partIndex/totalParts

Related Documents