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