SF-Assistant (Zero-Dependency RAG Tool)

Role: Solo, end-to-end
Tech Stack: Java 11 (Stdlib Only), OpenAI/Anthropic APIs, StAX, com.sun.net.httpserver
Repository: Private. Demo available on request

Overview

SF-Assistant is a conversational codebase assistant for Salesforce projects, built under an unusual constraint: zero external dependencies. No Maven. No Gradle. No Jackson. Just Java 11 standard library.

That constraint is the whole point. I kept inheriting large Salesforce codebases with no documentation, working in environments where installing dependencies on the org’s machines wasn’t an option. Most RAG tooling assumes you can pip install your way to success. In the enterprise environments I actually deal with, you can’t.

The tool combines RAG (Retrieval-Augmented Generation) with call graph analysis to help developers understand legacy codebases through natural language. It provides:

  • Semantic Code Search, find code by describing what it does, not just matching keywords
  • Debug Log Analysis, parse Salesforce debug logs to identify performance bottlenecks and exceptions
  • Discovery Commands, systematically explore unfamiliar codebases with /search, /list, /graph, /stats
  • Dependency Analysis, trace execution paths, find callers, visualize dependency trees
  • Lean Format Transformation, automatically reduce verbose XML metadata by 70-90% for better embeddings
  • LLM Autonomous Research, the LLM can invoke tools like /graph and /deps to gather context before answering

It ships with both a terminal REPL and a web interface served by the embedded zero-dependency server, which streams per-command progress to the browser over SSE. Output renders per medium: an ANSI markdown renderer for the terminal, HTML for the web. All of it keeps memory footprint minimal through lazy loading.

System Architecture

The system runs a hybrid retrieval strategy, combining semantic search with structural code analysis for accurate, grounded responses.

sequenceDiagram
    participant User
    participant Repl
    participant EmbeddingService
    participant OpenAI_API
    participant IndexStore
    participant ContextBuilder
    participant LlmClient
    participant LLM_Provider_API

    User->>Repl: "How does AccountHandler work?"

    rect rgba(100, 180, 220, 0.2)
        note right of Repl: 1. Vectorize Query
        Repl->>EmbeddingService: getQueryEmbedding("...")
        EmbeddingService->>EmbeddingService: Check Cache
        alt Cache Miss
            EmbeddingService->>OpenAI_API: POST /embeddings
            OpenAI_API-->>EmbeddingService: Vector[1536]
        end
        EmbeddingService-->>Repl: float[] queryVector
    end

    rect rgba(140, 120, 200, 0.2)
        note right of Repl: 2. Retrieval & Context
        Repl->>ContextBuilder: buildPromptWithDependencies(...)

        ContextBuilder->>IndexStore: findRelevant(queryVector)
        IndexStore-->>ContextBuilder: List of ScoredUnit Seeds

        ContextBuilder->>ContextBuilder: DependencyExpander.expand()
        note right of ContextBuilder: BFS Traversal of Call Graph

        ContextBuilder->>ContextBuilder: Apply Token Budget
        note right of ContextBuilder: 1. System Prompt<br/>2. Pinned Files<br/>3. Retrieved Chunks<br/>4. Chat History

        ContextBuilder-->>Repl: Prompt Object
    end

    rect rgba(220, 140, 140, 0.2)
        note right of Repl: 3. LLM Reasoning
        Repl->>LlmClient: chat(Prompt)
        LlmClient->>LLM_Provider_API: POST /chat/completions
        LLM_Provider_API-->>LlmClient: "The AccountHandler class..."
    end

    LlmClient-->>Repl: Response String
    Repl-->>User: Display Answer

What’s Inside

  • Lazy-Loaded Indexer: stores file offsets instead of content, reading from disk only when needed for LLM context.
  • Hybrid Chunker: splits large files into CLASS_HEADER, METHOD, and TEXT_CHUNK units based on size thresholds.
  • Call Graph Expander: BFS traversal that automatically includes related methods (callers/callees) beyond semantic search results.
  • Provider Abstraction: unified interface for OpenAI and Anthropic APIs with automatic request/response format handling.
  • Debug Log Analyzer: parses profiling data and stack traces, automatically retrieving code at failure points.
  • Lean Format Transformer: converts verbose Salesforce XML to token-efficient format during indexing.

Debug Log Analysis

This is what turns SF-Assistant from a code Q&A tool into an actual debugging assistant.

The Challenge

Salesforce debug logs are massive (often 500k+ tokens) and nightmarish to analyze:

  • 90%+ of the content is noise (system code, timestamps, internal operations)
  • Critical data (governor limits, execution patterns) is buried in verbose output
  • Manual analysis means finding class files, understanding context, piecing together execution flow

The Solution

SF-Assistant implements a Parse β†’ Cross-Reference β†’ Correlate pipeline:

/profiling debug.log

PROFILING ANALYSIS
═══════════════════════════════════════════════════════════

GOVERNOR LIMITS
  SOQL Queries: 94/100 (94%)
  CPU Time: 8,234/10,000ms (82%)

METHOD EXECUTION
  AccountService.processUpdates:67      47 calls    7,892ms total
  AccountTriggerHandler.onAfterUpdate   47 calls    234ms total

SOQL EXECUTION
  SELECT Id, Amount FROM Opportunity WHERE AccountId = :acc.Id
    47 executions    892ms total

CORRELATIONS
  β€’ AccountService.processUpdates and SOQL query both executed 47 times
  β€’ processUpdates consumed 96% of total CPU time

RETRIEVED CODE (auto-pinned)
  βœ“ AccountService.cls
  βœ“ AccountTriggerHandler.cls

Key Design Principle

β€œStructure data, retrieve code, present facts. Let the LLM diagnose.”

The analyzer identifies correlations (β€œmethod and query both executed 47 times”) but deliberately avoids pre-labeling them as anti-patterns. The same pattern could be:

  • Legitimate trigger recursion (bulkified code)
  • Intentional batch processing
  • An actual SOQL-in-loop problem

The LLM examines the actual code alongside the metrics to determine root cause, which is something algorithmic pattern-matching cannot reliably do.

Token Savings: debug log analysis achieves 80-95% token reduction from raw logs while preserving all diagnostic information.

Discovery & Navigation

Commands for systematic codebase exploration, added in v0.5.0.

/list [type]: Codebase Inventory

> /list
Codebase Inventory:

Type                    Count    Avg Tokens    Total Tokens
────────────────────────────────────────────────────────────
CLASS                      89        342         30,438
METHOD                    654        127         83,058
TRIGGER                    12        456          5,472
FLOW                       23        234          5,382
VALIDATION_RULE            45        156          7,020
────────────────────────────────────────────────────────────
TOTAL                   1,080        181        195,260
> /search SOQL queries with limits

1. AccountService.cls:method:getAccounts (similarity: 0.87)
   Preview: public List<Account> getAccounts() { return [SELECT Id FROM Account LIMIT 100]; }

2. ContactHelper.cls:method:fetchContacts (similarity: 0.82)
   Preview: Database.query('SELECT Id FROM Contact WHERE Email = \'' + email + '\' LIMIT 50');

/graph <classname>: Call Relationships

> /graph AccountService

OUTGOING CALLS (What this class calls):
  β”œβ”€ ContactService.updateContacts()
  β”œβ”€ AccountValidator.validate()
  └─ Database.insert() [System]

INCOMING CALLS (What calls this class):
  β”œβ”€ AccountTriggerHandler.onUpdate()
  β”œβ”€ AccountController.saveAccount()
  └─ AccountBatch.execute()

These tools let developers understand codebase structure before asking targeted questions, which cuts down on β€œI don’t have that information” responses significantly.

Dependency Analysis

Tools for understanding code relationships and execution paths.

/deps <class> [depth]: Dependency Tree

Visualize multi-level dependency chains showing what a class calls and what those call recursively.

> /deps AccountService 2

Dependency Tree for AccountService (depth: 2)
═══════════════════════════════════════════════════════════

AccountService
  β”œβ”€ ContactService.updateContacts()
  β”‚    β”œβ”€ Database.update() [System]
  β”‚    └─ ContactValidator.validate()
  β”œβ”€ AccountValidator.validate()
  β”‚    └─ ValidationUtils.checkRequired()
  └─ Database.insert() [System]

Statistics:
  Total Units: 5
  Total Dependencies: 6
  Max Depth: 2

/callers <method>: Reverse Call Lookup

Find all code that calls a specific method. Essential for impact analysis before refactoring.

> /callers AccountService.validate

Callers of AccountService.validate
═══════════════════════════════════════════════════════════

Found 4 callers:

1. AccountTriggerHandler.onBeforeInsert()
2. AccountTriggerHandler.onBeforeUpdate()
3. AccountController.saveAccount()
4. AccountBatchJob.execute()

Impact Assessment: MEDIUM (4 callers)

/flow [flowname]: Flow Definition Viewer

Display Salesforce flow definitions in lean format, or list all flows in the codebase.

> /flow Account_Validation_Flow

Flow: Account_Validation_Flow
═══════════════════════════════════════════════════════════

Type: Record-Triggered Flow
Status: Active
Object: Account
Trigger: BeforeInsert, BeforeUpdate

Elements:
  β”œβ”€ Get_Related_Contacts (recordLookup)
  β”œβ”€ Check_Required_Fields (decision)
  β”‚    β”œβ”€ True β†’ Set_Error_Message
  β”‚    └─ False β†’ Update_Status
  └─ Final_Validation (decision)

Codebase Analysis

/similar <filename>: Find Similar Code

Discover semantically similar code units for pattern detection and deduplication opportunities.

> /similar AccountTriggerHandler

Similar Code Units to AccountTriggerHandler
═══════════════════════════════════════════════════════════

1. ContactTriggerHandler.cls:class (similarity: 0.89)
2. OpportunityTriggerHandler.cls:class (similarity: 0.85)
3. LeadTriggerHandler.cls:class (similarity: 0.78)

Analysis: This appears to be a trigger handler class.
Consider following consistent patterns across handlers.

/stats: Comprehensive Statistics

> /stats

═══════════════════════════════════════════════════════════
SF-ASSISTANT CODEBASE STATISTICS
═══════════════════════════════════════════════════════════

INDEX INFORMATION
  Total Code Units: 1,247
  Total Tokens: 342,567
  Average Unit Size: 274 tokens

CODE DISTRIBUTION
  Apex Classes: 89 (45,678 tokens)
  Apex Methods: 654 (83,058 tokens)
  Apex Triggers: 12 (5,472 tokens)

CALL GRAPH
  Total Edges: 1,892
  Most Called: ValidationUtils.checkRequired() (67 refs)
  Most Active: AccountService.processUpdates() (23 calls)

Lean Format Transformation

SF-Assistant automatically transforms verbose Salesforce XML metadata into token-efficient β€œlean format” during indexing.

Token Savings

Metadata TypeToken ReductionExample
Flows70-90%13,130 tokens β†’ 2,534 tokens
Validation Rules35-50%155 tokens β†’ 94 tokens
Formula Fields60-70%Varies by complexity
Custom Metadata60-70%Varies by field count

Example Transformation

Before (XML):

<decisions>
    <processMetadataValues>
        <name>index</name>
        <value><numberValue>0.0</numberValue></value>
    </processMetadataValues>
    <name>Decision_Amount_Check</name>
    <locationX>182</locationX>
    <locationY>350</locationY>
    <defaultConnector><targetReference>Screen_Rejected</targetReference></defaultConnector>
    <rules>
        <name>High_Value</name>
        <conditions>
            <leftValueReference>Amount</leftValueReference>
            <operator>GreaterThan</operator>
            <rightValue><numberValue>10000.0</numberValue></rightValue>
        </conditions>
        <connector><targetReference>Action_Notify_Manager</targetReference></connector>
    </rules>
</decisions>

After (Lean Format):

=== DECISIONS ===

Decision: Decision_Amount_Check (Amount Check)
  Default β†’ Screen_Rejected

  Rule: High_Value (High Value)
    If: Amount > 10000.0
    Then β†’ Action_Notify_Manager

Benefits: better embeddings (more semantic meaning per token), improved search relevance (less XML noise), lower API costs (fewer tokens to embed and retrieve).

Engineering Details

1. Zero External Dependencies

The tool has to run in restricted enterprise environments where external libraries are flat-out prohibited. So I implemented all the infrastructure from scratch using only Java 11 stdlib.

  • Custom JSON Parser: recursive descent parser (SimpleJsonParser) handles API serialization/deserialization without Jackson/Gson.
  • Custom HTTP Client: built on java.net.HttpURLConnection with provider-specific authentication and request formats.
  • Custom XML Parser: uses StAX (javax.xml.stream) for Salesforce metadata extraction with XXE protection.
  • Embedded Web Server: built on com.sun.net.httpserver for the browser-based terminal UI with zero dependencies.

Single JAR deployment. No build tools required, just javac and jar.

2. Memory-Efficient Lazy Loading

Traditional RAG systems load entire codebases into memory, which causes OOM errors on large projects. So I built a lazy-loading architecture where CodeUnit objects store file offsets instead of content.

  • Content reads from disk only when needed (during LLM context assembly).
  • A redactβ†’strip pipeline runs at read-time:
    1. Secrets redacted first (catches api_key, password patterns in comments)
    2. Comments stripped second (noise reduction for LLM)
  • Embeddings are cached to .sf-assistant-embeddings.cache for instant subsequent runs.

Handles 1000+ file codebases without running out of memory.

3. Hybrid Chunking Strategy

LLM context windows are limited. You’re always balancing granularity vs. completeness, and the wrong call in either direction hurts retrieval quality. So I went with size-aware structural chunking.

  • Small files (<1000 tokens): indexed as single CLASS unit
  • Large files: split into CLASS_HEADER + individual METHOD units
  • Huge methods (>1,500 tokens): further split into overlapping TEXT_CHUNK units (800 tokens, 160 overlap)

Returns specific methods rather than dumping entire files into context.

4. Call Graph-Based Dependency Expansion

Semantic search alone misses structurally related code, methods that call or are called by retrieved results. So the indexer builds a call graph and the context builder expands via BFS traversal.

  • Detects method calls using regex patterns for instance/static/constructor calls
  • Expands top-K semantic matches with their dependencies (configurable depth 1-3 hops)
  • Applies score decay (0.7^depth) to prioritize direct matches over distant dependencies

More complete context without requiring manual /file pinning commands.

5. Multi-Provider LLM Support

Different LLM providers (OpenAI, Anthropic) have incompatible APIs. Provider abstraction with routing logic handles the differences.

  • HttpLlmClient routes to provider-specific request builders (buildOpenAIRequest vs. buildAnthropicRequest)
  • Handles authentication differences (Bearer token vs. x-api-key header)
  • Automatic token budget detection based on model name, deliberately conservative: the budgets are usable working room (~100k for Gemini, ~75k for Claude and GPT-4o class models), not raw context-window ceilings, which leaves headroom for the response and keeps retrieval focused

Switch between providers with a single config property change.

6. LLM Autonomous Tool Invocation

Users often need multiple pieces of context to answer a question, which without some automation means a lot of back-and-forth. So the LLM can autonomously invoke research tools during conversations.

When the LLM decides it needs more context, it outputs commands like:

COMMAND: /graph AccountService
COMMAND: /deps AccountTriggerHandler 2

SF-Assistant executes these and adds the results to the conversation, letting the LLM gather complete context before answering.

The loop runs governed, not open-ended: commands per turn are capped (5), research iterations are capped (3), duplicate invocations are suppressed, and every limit is configurable. The model gets tools, not free rein.

Roughly halved the back-and-forth in day-to-day use.

7. Smart Embedding Batching

Google’s text-embedding-005 has strict limits (10 items per batch, ~20k tokens per batch). Intelligent batching respects both constraints, with an 18k-token budget per batch as safety margin under the hard limit.

if (batch.size >= maxBatchSize || batchTokens + unitTokens > maxTokensPerBatch):
    submit batch
    start new batch
  • Small units (100 tokens): packs up to 10 items per batch
  • Large units (5,000 tokens): uses only ~3 items per batch
  • Mixed sizes: dynamically adjusts batch boundaries

Also supports automatic input_type handling for text-embedding-005 (search_document for indexing, search_query for queries).

Indexing runs are paced by a configurable rate limiter (delay plus jitter, max requests per minute), and a --test connectivity mode validates the API config on a handful of units before committing to a full index run.

Supported Metadata Types

The indexer handles 15+ Salesforce metadata types across three tiers:

Tier 0 (Apex): Classes, Triggers
Tier 1 (Declarative Logic): Flows, Validation Rules, Formula Fields, Custom Metadata
Tier 2 (UI Components): LWC, Aura, Visualforce, Page Layouts, Record Types

Each type has a specialized extractor that preserves semantic context (flow trigger types, validation rule formulas, etc.).

REPL Commands

Discovery & Navigation

CommandDescription
/search <query>Semantic code search with similarity scores
/list [type]Codebase inventory (all types or specific type)
/graph <classname>Show call relationships for a class
/outline <classname>Show class structure (methods, tokens, calls)
/similar <filename>Find semantically similar code units
/statsShow comprehensive codebase statistics

Dependency Analysis

CommandDescription
/deps <class> [depth]Dependency tree visualization
/callers <method>Find all code that calls a specific method
/flow [flowname]Show flow definition or list all flows
/trace <method>Execution path tracing

File & Method Operations

CommandDescription
/file <name>Pin a file for focused exploration
/method <class.method>Pin a specific method
/usage <name>Find where a class/method is used

Debug Log Analysis

CommandDescription
/profiling [path]Parse profiling data from debug log
/stacktrace [path]Parse exception stack traces

System Commands

CommandDescription
/contextDebug what code was included in last query
/tokensShow token usage breakdown
/dump [options]Export code units to file with filtering
/reindexRe-scan project after code changes
/clearClear conversation history

Tech Stack

  • Language: Java 11 (Standard Library Only)
  • XML Parsing: StAX (javax.xml.stream)
  • Web Server: com.sun.net.httpserver
  • Build: javac + jar (no Maven/Gradle)
  • Testing: 1,092 passing tests across 77 test classes, run by a dependency-free custom test harness (no JUnit, keeping the zero-dependency constraint honest even in the test suite)