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:

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

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:

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:

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.

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.

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.

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.

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.

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.

Dropped average turns per question from ~3 to ~1.5.

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

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