An AI supervisor for Claude Code. The tech lead reviews implementation plans before they reach you, catching over-engineering, scope creep, and unnecessary complexity.
Claude Code is capable but eager. Ask it to "add logging" and you might get a 6-file logging framework with interfaces, formatters, handlers, and configuration systems. By the time you see the plan, you're already parsing complexity.
The tech lead sits between Claude Code (the implementer) and you. Every plan gets reviewed before you see it:
┌─────────────────────────────────────────────────────────────────┐
│ You: "Add request logging" │
│ │
│ Implementer drafts plan → Tech Lead reviews │
│ │
│ Tech Lead: "Reject. 6 files for logging is over-engineering. │
│ Use slog directly. 2 files max." │
│ │
│ Implementer revises → Tech Lead reviews │
│ │
│ Tech Lead: "Approve. Clean, minimal, uses stdlib." │
│ │
│ You see: "I'll add request logging with slog. One file: │
│ internal/middleware/logging.go" │
└─────────────────────────────────────────────────────────────────┘
You only see vetted plans. The messy iterations happen before they reach you.
- Plan Review — Evaluates scope, complexity, and adherence to project standards
- Code Review — Post-implementation review catches bugs, anti-patterns, and missing tests
- Knowledge Storage — Learns from past decisions and reviews using vector embeddings
- Semantic Search — Finds similar past reviews even with different terminology
- Project Standards — Configurable thresholds for files, tests, and strictness
- MCP Integration — Works as an MCP server for Claude Code
- Hook Enforcement — Optional Claude hooks to enforce the workflow
- Go 1.21+
- Docker (for Ollama)
- Claude Code CLI
git clone https://github.com/jasonehmke/techlead.git
cd techlead
./scripts/install.shThis script:
- Builds and installs the
techleadbinary to~/.local/bin - Starts Ollama in Docker and pulls the embedding model
- Configures the MCP server globally for all Claude Code projects
After installation, the consult_tech_lead tool is available in every Claude Code session.
If you prefer to set things up manually:
# Clone and build
git clone https://github.com/jasonehmke/techlead.git
cd techlead
make build
# Install to PATH
cp bin/techlead ~/.local/bin/
# Add MCP server globally
claude mcp add techlead ~/.local/bin/techlead mcp -s userThe tech lead uses Ollama for generating embeddings. This enables semantic search across past decisions and reviews.
# Install Ollama (macOS)
brew install ollama
# Pull the embedding model
ollama pull nomic-embed-text
# Start Ollama (runs in background)
ollama servecd your-project
techlead initThis creates:
.techlead/
├── config.yaml # Project standards
└── knowledge.db # Vector database (created on first run)
techleadThe server starts on http://localhost:8080 by default. Set PORT environment variable to change.
If you used the install script, the MCP server is already configured globally. Skip to step 4.
Global install (all projects):
claude mcp add techlead ~/.local/bin/techlead mcp -s userProject-only install:
Add to your project's .mcp.json:
{
"mcpServers": {
"techlead": {
"command": "/path/to/techlead",
"args": ["mcp"]
}
}
}HTTP mode (for development/testing):
{
"mcpServers": {
"techlead": {
"type": "http",
"url": "http://localhost:8080"
}
}
}Copy context/implementer.md to your project's CLAUDE.md or append it to existing instructions. This tells Claude Code to consult the tech lead before showing you plans.
Edit .techlead/config.yaml:
standards:
require_linting: true
require_tests: true
max_files_per_task: 10
review:
# How hard to push: minimal, standard, strict, pedantic
strictness: standard
knowledge:
embeddings:
endpoint: http://localhost:11434
model: nomic-embed-text| Level | Behavior |
|---|---|
minimal |
Only catch egregious issues |
standard |
Clear issues + obvious improvements (default) |
strict |
Standard + questionable decisions |
pedantic |
Maximum scrutiny, question everything |
POST /consultcurl -X POST http://localhost:8080/consult \
-H "Content-Type: application/json" \
-d '{
"task": "Add request logging",
"plan_summary": "Create middleware that logs requests using slog",
"files": ["internal/middleware/logging.go", "internal/middleware/logging_test.go"],
"rationale": "Simple middleware with tests"
}'Response:
{
"decision": "approve",
"reason": "Clean, minimal approach. Uses stdlib slog, includes tests.",
"guidance": ""
}POST /knowledge/searchcurl -X POST http://localhost:8080/knowledge/search \
-H "Content-Type: application/json" \
-d '{"query": "logging and observability", "limit": 5}'Returns semantically similar decisions, learnings, and past reviews.
POST /knowledge/decisioncurl -X POST http://localhost:8080/knowledge/decision \
-H "Content-Type: application/json" \
-d '{
"content": "Use slog for all logging",
"context": "Stdlib is sufficient, no external dependencies needed"
}'POST /knowledge/learningcurl -X POST http://localhost:8080/knowledge/learning \
-H "Content-Type: application/json" \
-d '{
"pattern": "Implementer proposes 6+ files for simple task",
"response": "Reject. Scope to 2 files maximum.",
"severity": "high"
}'GET /knowledge/decisionsGET /knowledge/learningsPOST /reviewcurl -X POST http://localhost:8080/review \
-H "Content-Type: application/json" \
-d '{
"task": "Add user authentication",
"files": {
"internal/auth/handler.go": "package auth\n\nfunc Login(w http.ResponseWriter, r *http.Request) {\n // handler code\n}"
},
"description": "Login endpoint with JWT tokens"
}'Response:
{
"approved": true,
"summary": "Clean implementation with proper error handling",
"issues": [],
"praise": "Good use of stdlib http patterns"
}If issues are found:
{
"approved": false,
"summary": "Several issues need attention",
"issues": [
"SQL injection vulnerability in query construction",
"Error from db.Query is ignored",
"Missing nil check on user result"
],
"praise": ""
}GET /health- Implementer (Claude Code) drafts a plan
- Implementer calls
consult_tech_leadtool with the plan - Tech lead searches knowledge base for similar past reviews
- Tech lead evaluates plan against standards and historical context
- Tech lead returns: approve, modify, or reject
- If not approved, implementer revises and tries again
- Approved plan is shown to user
The tech lead learns from experience using vector embeddings:
"Add logging framework" → [0.12, -0.45, 0.78, ...] (768 numbers)
"Create observability system" → [0.11, -0.44, 0.76, ...] (similar meaning = similar numbers)
When a new plan comes in:
- Embed the plan summary
- Search for similar past reviews
- Include relevant context in the evaluation
- Store the new review for future reference
This means rejecting a "logging framework" informs future reviews of "monitoring systems"—they're semantically related even without shared words.
After implementation is complete:
- Implementer calls
review_codetool with the code - Tech lead reviews for bugs, anti-patterns, and quality issues
- Returns approval status with any issues found
- If issues exist, implementer fixes and re-reviews
- Clean code is presented to the user
Code review is lighter than plan review—it approves "good enough" code and only flags real issues.
By default, the tech lead relies on instructions in CLAUDE.md to guide Claude Code. For stricter enforcement, you can add Claude hooks that block code changes until the tech lead approves.
- PreToolUse hook blocks
Write,Edit, andMultiEdituntilconsult_tech_leadreturns approval - PostToolUse hook tracks approval status and reminds about code review after implementation
Create the enforcement hook at ~/.claude/hooks/techlead-enforce.sh:
#!/bin/bash
# Blocks code changes until tech lead approval
set -e
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id')
tool_name=$(echo "$input" | jq -r '.tool_name')
STATE_DIR="/tmp/claude-techlead"
mkdir -p "$STATE_DIR"
APPROVAL_FILE="$STATE_DIR/approved-$session_id"
# Only enforce in projects with .techlead/ directory
check_techlead_project() {
local dir=$(echo "$input" | jq -r '.cwd // "."')
[[ -d "$dir/.techlead" ]]
}
if ! check_techlead_project; then
exit 0
fi
case "$tool_name" in
Write|Edit|MultiEdit)
# Allow config files without approval
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
case "$file_path" in
*.md|*.txt|*.json|*.yaml|*.yml|*.toml|*.env*)
exit 0
;;
esac
# Check for approval
if [[ -f "$APPROVAL_FILE" ]] && [[ "$(cat "$APPROVAL_FILE")" == "approved" ]]; then
exit 0
fi
echo "BLOCKED: Call consult_tech_lead first" >&2
exit 2
;;
esac
exit 0Create the tracker hook at ~/.claude/hooks/techlead-tracker.sh:
#!/bin/bash
# Tracks tech lead approval status
set -e
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id')
tool_name=$(echo "$input" | jq -r '.tool_name')
STATE_DIR="/tmp/claude-techlead"
mkdir -p "$STATE_DIR"
APPROVAL_FILE="$STATE_DIR/approved-$session_id"
if [[ "$tool_name" == "mcp__techlead__consult_tech_lead" ]]; then
tool_output=$(echo "$input" | jq -r '.tool_output // ""')
if echo "$tool_output" | grep -qi '"decision"[[:space:]]*:[[:space:]]*"approve"'; then
echo "approved" > "$APPROVAL_FILE"
fi
fi
exit 0Make both executable:
chmod +x ~/.claude/hooks/techlead-enforce.sh
chmod +x ~/.claude/hooks/techlead-tracker.shAdd to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "/path/to/.claude/hooks/techlead-enforce.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "mcp__techlead__*|Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "/path/to/.claude/hooks/techlead-tracker.sh"
}
]
}
]
}
}Replace /path/to/ with your actual home directory path.
| Pattern | Response |
|---|---|
| Too many files | "This should be 2 files, not 8" |
| Unnecessary abstractions | "No interfaces needed here" |
| Scope creep | "Park that for later, finish this task first" |
| New dependencies | "Use stdlib, don't add external deps" |
| Over-engineering | "This is a simple task, keep it simple" |
| Stdlib reimplementation | "Use strings.Contains instead of custom helper" |
techlead
# or
techlead --port 9090Good for development and testing. Use curl to interact directly.
techlead mcpStandard MCP protocol over stdin/stdout. This is what Claude Code uses.
make buildmake testNote: Tests that require Ollama will be skipped if Ollama is not running.
make devRequires air.
techlead/
├── cmd/techlead/
│ ├── main.go # Entry point
│ └── templates/ # Default config templates
├── internal/
│ ├── config/ # Configuration loading
│ ├── knowledge/ # Vector database and embeddings
│ ├── mcp/ # MCP and HTTP servers
│ ├── plan/ # Plan schema
│ └── techlead/ # Core review logic
├── context/
│ └── implementer.md # Claude Code instructions
└── .techlead/
└── config.yaml # Project configuration
Ollama is not running or the model is not installed.
# Start Ollama
ollama serve
# Pull the model if not installed
ollama pull nomic-embed-textThis is a macOS warning, not an error. Apple deprecated this function in their bundled SQLite, but it still works. The warning can be safely ignored.
- Check that
.mcp.jsonis configured correctly - Verify the implementer instructions are in your
CLAUDE.md - Restart Claude Code after configuration changes
- Check that the tech lead server is running
The MCP server was likely added with local scope instead of user scope. Fix with:
claude mcp remove techlead -s local
claude mcp add techlead ~/.local/bin/techlead mcp -s userIncrease strictness in .techlead/config.yaml:
review:
strictness: strict # or pedanticRecord learnings for patterns you see:
curl -X POST http://localhost:8080/knowledge/learning \
-H "Content-Type: application/json" \
-d '{
"pattern": "Creates interfaces for single implementations",
"response": "Reject. No interface until there are 2+ implementations.",
"severity": "high"
}'MIT