Skip to content

AST Engine & Package Context Slicing

Triage’s core architectural differentiator is selective on-demand AST slicing with multi-file package context. Rather than sending monolithic files or losing context with single-function fragments, Triage isolates the crash site and automatically resolves its cross-file type definitions, receiver structs, constructors, and package helpers.

When a Go runtime panic occurs (such as a nil pointer dereference, nil map write, or slice bounds out of range), passing only a single function declaration or an entire source file creates significant diagnostic problems:

  1. Single-Function Blindness: If a crash happens on user.Profile.Settings.Theme = "dark" in handler.go, but User, Profile, and Settings are defined in models.go, an isolated function snippet gives the LLM zero visibility into which fields are pointers (*Profile), value types (Profile), or uninitialized structures.
  2. Constructor & Initializer Gaps: If a method func (s *Service) Process() panics on s.cache.Set(...), the root cause is almost always an uninitialized field in NewService() in another file (service.go or factory.go).
  3. Token Cost & Dilution of Full Files: Ingesting 2,000-line source files wastes thousands of tokens per incident and dilutes the model’s attention with unrelated business logic.

Triage solves this by combining Go’s official go/parser and go/ast packages to extract the exact crashing function plus its selectively pruned package-level dependencies, achieving over 90% token reduction while delivering 100% semantic clarity.


1. Panic Telemetry arrives (file + line + commit_sha)
│
▼
2. 3-Tier Layered Cache Lookup
├── Tier 1: In-Memory KV Cache (< 1.5ms)
├── Tier 2: Embedded SQLite ast_nodes (< 2ms)
└── Tier 3: GitHub Contents API (< 25ms)
│
▼
3. Package AST Symbol Resolver (go/parser & go/ast)
├── Parse all package files in directory (parser.ParseDir)
├── Locate crashing *ast.FuncDecl enclosing targetLine
├── Resolve receiver struct (e.g. type UserHandler struct)
├── Transitive struct field resolution (depth 2 across pointers/slices/maps)
├── Locate associated constructors (New<Receiver>, New<Type>)
└── Extract called package-level helpers and variables
│
▼
4. Formatted Multi-File AST Context Snippet

The Triage Engine parses source packages using standard Go AST tooling:

  • go/parser: parser.ParseDir(fset, dir, filter, parser.ParseComments)
  • go/ast: ast.Inspect(node, func(n ast.Node) bool)
  • go/printer: printer.Fprint(&buf, fset, decl)

When a panic occurs, Triage builds a PackageContext symbol table and selectively extracts referenced symbols:

// 1. Receiver Struct Resolution
if targetFn.ReceiverType != "" {
referencedTypeNames[targetFn.ReceiverType] = true
}
// 2. AST Identifier Traversal
ast.Inspect(targetFn.Node, func(n ast.Node) bool {
switch expr := n.(type) {
case *ast.Ident:
if _, exists := ctx.Types[expr.Name]; exists {
referencedTypeNames[expr.Name] = true
}
if _, exists := ctx.Vars[expr.Name]; exists {
referencedVarNames[expr.Name] = true
}
case *ast.CallExpr:
if ident, ok := expr.Fun.(*ast.Ident); ok {
referencedFuncNames[ident.Name] = true
}
}
return true
})
// 3. Transitive Struct Field Traversal (depth 2)
for tName := range referencedTypeNames {
if typeDef, ok := ctx.Types[tName]; ok {
if structType, ok := typeDef.Node.Type.(*ast.StructType); ok {
for _, field := range structType.Fields.List {
if fieldType := getTypeName(field.Type); fieldType != "" {
referencedTypeNames[fieldType] = true
}
}
}
}
}

Here is what the Triage Engine generates and passes to the configured AI model:

// ==============================================================================
// Crash Site: user.go (lines 25-32)
// ==============================================================================
func (h *UserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
if validateRequest("update") {
h.Notifier.WebhookURL = "https://hooks.slack.com/services/..."
}
}
// ==============================================================================
// Package Context: Struct & Type Definitions (handler)
// ==============================================================================
// File: types.go (lines 5-8)
type SlackNotifier struct {
WebhookURL string
Client *http.Client
}
// File: types.go (lines 10-14)
type UserHandler struct {
Notifier *SlackNotifier
MaxUsers int
}
// ==============================================================================
// Package Context: Related Constructors & Helper Functions (handler)
// ==============================================================================
// File: init.go (lines 3-8)
func NewUserHandler() *UserHandler {
return &UserHandler{
Notifier: nil, // Root cause: Notifier is left uninitialized
MaxUsers: 100,
}
}
// File: helpers.go (lines 5-7)
func validateRequest(action string) bool {
return len(action) > 0
}

With this complete context, the AI model immediately pinpoints that UserHandler.Notifier is nil because NewUserHandler() left it uninitialized, and proposes the exact fix in NewUserHandler() or adds a nil check before dereference.


In monorepos where Go backends live in subfolders (e.g. backend, apps/api), runtime panic stack traces may reference files either relative to the module root (e.g. handlers/payment.go) or the repository root (backend/handlers/payment.go).

Triage handles this transparently:

  1. Path Normalization: The engine normalizes candidate paths using the project’s registered root_dir, ensuring files are accurately fetched from the GitHub Contents API without path mismatch.
  2. Dual-Key AST Indexing: When indexing monorepo AST nodes, Triage indexes both the repository-relative path and the module-relative path in SQLite, guaranteeing $O(1)$ symbolication lookups regardless of how the runtime frame is formatted.

Rather than caching purely on an exact line_number or requiring manual repository-wide pre-indexing, Triage uses an intelligent Function-Wise AST Caching engine:

  1. Boundary-Aware In-Memory Caching: When a file is fetched and parsed from GitHub on-demand, Triage identifies the enclosing function declaration’s boundary coordinates [start_line..end_line].
  2. Sub-Millisecond Hit Rate: If a crash happens anywhere within that function boundary (e.g. lines 45–120 of ProcessPayment), subsequent panics on any line in that range hit the memory cache in < 1ms with zero redundant GitHub API requests.
  3. Persistent SQLite Tier: Extracted AST function snippets are stored in SQLite (ast_nodes table) to survive container or engine restarts.
  4. Commit Content-Addressing: Cache keys are scoped by Git commit SHA (owner/repo@commit:file), guaranteeing 100% cache consistency without stale data when code is deployed.