← ContentsHome
Chapter 10

For Tool Builders

Technical architecture, context gathering implementation, prompt synthesis, and IDE integration with code examples.

If you're building an AI coding tool, here's a technical roadmap for minimizing context friction.


Architecture Blueprint

┌─────────────────────────────────────────────────────────────┐
│                     Developer Interface                      │
│  (IDE plugin, terminal tool, test runner integration)       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                  Invocation Layer                            │
│  - Keyboard shortcuts                                        │
│  - Context menu commands                                     │
│  - Inline commands (e.g., comments like "// AI: fix this")  │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                 Context Gathering Engine                     │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │ File Context │  │Runtime Context│  │Project Context│     │
│  │- Current file│  │- Test results │  │- Git diff     │     │
│  │- AST parsing │  │- Logs         │  │- Deps/config  │     │
│  │- Imports     │  │- Errors       │  │- CI status    │     │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│               Context Synthesis & Ranking                    │
│  - Semantic labeling (problem/code/error/context)           │
│  - Relevance scoring (which files matter most)              │
│  - Truncation and summarization (long logs → key lines)     │
│  - Prompt construction                                       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                    Model Interface                           │
│  - API calls to LLM (OpenAI, Anthropic, local, etc.)        │
│  - Response parsing                                          │
│  - Error handling and retries                               │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                  Response Integration                        │
│  - Diff generation                                           │
│  - Syntax validation                                         │
│  - One-click apply                                           │
│  - Undo support                                              │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│              Telemetry & Learning                            │
│  - Log all interactions (with privacy controls)              │
│  - Track metrics (TTCAA, reorientation, context ratio)      │
│  - A/B test context gathering strategies                     │
└─────────────────────────────────────────────────────────────┘

Context Gathering: Implementation Details

File Context

def gather_file_context(current_file, cursor_position):
    context = {
        "current_file": current_file,
        "selected_lines": get_selected_lines(current_file, cursor_position),
        "function_scope": get_enclosing_function(current_file, cursor_position),
        "imports": parse_imports(current_file),
        "related_files": []
    }

    # Find files that import this one or are imported by this one
    for imp in context["imports"]:
        related_file = resolve_import(imp)
        if related_file:
            context["related_files"].append({
                "path": related_file,
                "content": read_file(related_file, max_lines=100)
            })

    return context

Runtime Context

def gather_runtime_context():
    context = {}

    # Get recent test results
    last_test_run = get_last_test_run()
    if last_test_run:
        context["test_results"] = {
            "passing": last_test_run.passing_count,
            "failing": last_test_run.failing_count,
            "failures": [
                {
                    "test_name": f.name,
                    "error": f.error_message,
                    "stack_trace": f.stack_trace[:500]  # Truncate
                }
                for f in last_test_run.failures[:3]  # Only top 3
            ]
        }

    # Get recent logs (last 5 minutes, errors only)
    recent_logs = get_recent_logs(
        since=now() - timedelta(minutes=5),
        level="ERROR"
    )
    context["recent_errors"] = [
        {"timestamp": log.timestamp, "message": log.message}
        for log in recent_logs[:10]
    ]

    return context

Project Context

def gather_project_context():
    context = {}

    # Git diff for current branch
    diff = run_command("git diff main...HEAD")
    context["git_diff"] = {
        "summary": generate_diff_summary(diff),  # Changed files
        "full_diff": diff[:5000]  # Truncate large diffs
    }

    # Read key config files
    config_files = [
        "package.json",
        "requirements.txt",
        "pyproject.toml",
        ".env.example"
    ]

    context["config"] = {}
    for cf in config_files:
        if file_exists(cf):
            context["config"][cf] = read_file(cf)

    # Get language/framework versions
    context["environment"] = detect_environment()
    # Returns: {"language": "Python", "version": "3.11", "framework": "Django 4.2"}

    return context

Context Synthesis: Prompt Construction

Don't just concatenate. Structure semantically.

def synthesize_prompt(user_query, file_ctx, runtime_ctx, project_ctx):
    sections = []

    # 1. Problem statement (what the developer asked)
    sections.append(f"## Problem\n{user_query}\n")

    # 2. Current code
    sections.append(f"## Current Code\n")
    sections.append(f"File: {file_ctx['current_file']}\n")
    sections.append(f"```\n{file_ctx['selected_lines']}\n```\n")

    # 3. Recent error (if any)
    if runtime_ctx.get("test_results", {}).get("failures"):
        failure = runtime_ctx["test_results"]["failures"][0]
        sections.append(f"## Recent Error\n")
        sections.append(f"Test: {failure['test_name']}\n")
        sections.append(f"Error: {failure['error']}\n")

    # 4. Environment
    env = project_ctx["environment"]
    sections.append(f"## Environment\n")
    sections.append(f"- Language: {env['language']} {env['version']}\n")
    sections.append(f"- Framework: {env['framework']}\n")

    # 5. Related code (only if relevant)
    if file_ctx["related_files"]:
        sections.append(f"## Related Files\n")
        for rf in file_ctx["related_files"][:2]:  # Max 2
            sections.append(f"File: {rf['path']}\n```\n{rf['content']}\n```\n")

    # 6. Constraints (optional, from user or project config)
    sections.append(f"## Constraints\n")
    sections.append(f"- Maintain existing API contracts\n")
    sections.append(f"- Follow project style guide\n")

    prompt = "\n".join(sections)
    return prompt

Key insight: Labeled sections help the model understand what each piece of information represents.


Response Integration: Diff Application

Never return raw code blocks. Always return diffs.

def generate_diff(original_code, suggested_code):
    """
    Returns a unified diff that can be displayed and applied.
    """
    diff = unified_diff(
        original_code.splitlines(keepends=True),
        suggested_code.splitlines(keepends=True),
        fromfile="original",
        tofile="suggested"
    )
    return "".join(diff)

def apply_diff(file_path, diff):
    """
    Applies a diff to a file.
    """
    original = read_file(file_path)
    patched = apply_patch(original, diff)
    write_file(file_path, patched)

In the UI:

Show the diff with syntax highlighting:

  function processUser(user) {
+   if (!user) return null;
+   if (!user.email) throw new Error("Email required");
    return {
      id: user.id,
      email: user.email.toLowerCase()
    };
  }

Buttons: [Apply] [Edit] [Reject]


Telemetry: What to Log

def log_interaction(event_data):
    """
    Log AI interaction for metrics and improvement.
    Privacy: hash user IDs, don't log proprietary code.
    """
    telemetry.log({
        "event": "ai_interaction",
        "timestamp": now(),
        "user_id": hash(user_id),  # Privacy-preserving

        # Context metrics
        "context_auto_lines": event_data["auto_context_lines"],
        "context_manual_lines": event_data["manual_context_lines"],
        "context_files_used": event_data["files_used"],

        # Timing
        "response_time_ms": event_data["response_time"],
        "time_to_next_edit_sec": event_data["time_to_edit"],

        # Outcome
        "suggestion_applied": event_data["applied"],
        "suggestion_edited_before_apply": event_data["edited"],

        # Flow metrics (requires editor integration)
        "time_since_last_commit_min": event_data["time_since_commit"]
    })

Aggregate metrics:

  • TTCAA: time_to_next_commit - timestamp
  • Context Provision Ratio: auto_lines / (auto_lines + manual_lines)
  • Reorientation Time: time_to_next_edit_sec

IDE Integration: How to Build It

VS Code Extension:

// extension.js
const vscode = require('vscode');

function activate(context) {
    let disposable = vscode.commands.registerCommand(
        'yourTool.askAI',
        async function () {
            const editor = vscode.window.activeTextEditor;
            if (!editor) return;

            // Gather context
            const currentFile = editor.document.fileName;
            const selection = editor.document.getText(editor.selection);
            const cursorPosition = editor.selection.active;

            // Call your context gathering service
            const context = await gatherContext(currentFile, cursorPosition);

            // Show input box for user query
            const query = await vscode.window.showInputBox({
                prompt: "What would you like to know?"
            });

            // Send to AI
            const response = await callAI(query, context);

            // Show diff in a new panel
            showDiffPanel(response.diff);
        }
    );

    context.subscriptions.push(disposable);
}

JetBrains Plugin (IntelliJ, PyCharm, etc.):

// AskAIAction.kt
class AskAIAction : AnAction() {
    override fun actionPerformed(e: AnActionEvent) {
        val project = e.project ?: return
        val editor = e.getData(CommonDataKeys.EDITOR) ?: return

        // Gather context
        val currentFile = FileDocumentManager.getInstance()
            .getFile(editor.document)?.path
        val selection = editor.selectionModel.selectedText

        // Show dialog
        val query = Messages.showInputDialog(
            project,
            "What would you like to know?",
            "Ask AI",
            null
        ) ?: return

        // Call AI service
        val context = gatherContext(currentFile, editor.caretModel.offset)
        val response = callAI(query, context)

        // Show diff
        showDiffWindow(project, response.diff)
    }
}

Cost Optimization

Context gathering can be expensive (many API calls). Optimize:

1. Cache aggressively

  • File ASTs: parse once, cache until file changes
  • Project structure: cache for 5 minutes
  • Configuration: cache for 1 hour

2. Lazy loading

  • Don't gather all context upfront
  • Start with file context
  • Add runtime/project context only if the query seems to need it
  • Use a fast model to classify the query first

3. Truncation strategies

  • Large files: send only the enclosing function + imports
  • Large logs: send only errors + last 10 lines
  • Large diffs: send summary + changed files, not full diff

4. Use cheaper models for context synthesis

  • Use a fast/cheap model to rank and filter context
  • Use expensive model only for final answer