Code Indexing
Automatic code indexing transforms your codebase into searchable knowledge, enabling AI assistants to understand and work with your WordPress projects using semantic search.
Overview
Code indexing in WDG:
- Automatic: Triggered by git commits and pulls
- Incremental: Only indexes changed files
- Semantic: Understands code meaning, not just keywords
- Project-Scoped: Each project has isolated collections
- Fast: Local embeddings require no external API calls
How It Works
Indexing Pipeline
%%{init: {'theme':'neutral'}}%%
sequenceDiagram
participant Git
participant Hook as post-commit hook
participant Service as indexer service (:8666)
participant Indexer as WDGCodeIndexer
participant Qdrant
Git->>Hook: git commit
Hook->>Service: POST /index/incremental {files, project}
Service->>Indexer: index_file() per path
Indexer->>Indexer: chunk_code() + embed in-process (all-MiniLM-L6-v2)
Indexer->>Qdrant: upsert (batches of 50)
Service->>Hook: IndexResponse
What Gets Indexed
PHP Files
Functions:
// Indexed as complete unit with context
function get_user_posts($user_id, $post_type = 'post') {
global $wpdb;
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts}
WHERE post_author = %d
AND post_type = %s
AND post_status = 'publish'",
$user_id,
$post_type
)
);
}Classes:
// Indexed with all methods and properties
class CustomPostType {
private $post_type;
public function __construct($type) {
$this->post_type = $type;
$this->register();
}
public function register() {
// Method implementation
}
}WordPress Hooks:
// Indexed with full context
add_action('init', function() {
register_post_type('custom_type', [
'public' => true,
'supports' => ['title', 'editor']
]);
});JavaScript Files
Functions:
// Regular functions
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// Arrow functions
const fetchUserData = async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
};React Components:
// Indexed with props and hooks
function UserProfile({ userId, showEmail = false }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUserData(userId).then(setUser);
}, [userId]);
return (
<div className="user-profile">
<h2>{user?.name}</h2>
{showEmail && <p>{user?.email}</p>}
</div>
);
}CSS/SCSS Files
// Chunked by logical sections
.user-profile {
display: flex;
padding: 2rem;
&__header {
font-size: 1.5rem;
color: var(--primary-color);
}
@media (max-width: 768px) {
flex-direction: column;
}
}Block Configurations
{
"name": "wdg/custom-block",
"title": "Custom Block",
"category": "wdg-blocks",
"attributes": {
"content": {
"type": "string",
"default": ""
}
}
}Indexing Strategies
Automatic Indexing (Recommended)
Git hooks automatically trigger indexing:
# After committing changes
git add .
git commit -m "Add new feature"
# → Automatically indexes changed files
# After pulling updates
git pull origin main
# → Automatically indexes merged changesManual Indexing
# Index Wikit framework (default target)
wdg index
# Index a specific project's repositories
wdg index my-site
# Index the platform codebase (CLI, MCP server, indexer, dashboard, docs)
wdg index --platform
# Index Wikit documentation
wdg index docs
# Pull latest Wikit repos, then re-index
wdg index --updateThere are no --types, --path, --exclude, --force, or --all flags on wdg index. Re-indexing simply re-runs the relevant target; the indexer upserts points by a deterministic id (md5(file_path:line_number:name)), so re-running overwrites the existing vectors for unchanged components. The set of indexable files and the directories that are skipped are fixed by the indexer engine (see Indexer Internals), not selectable per run.
Vector Collections
Collection Structure
Each project gets its own collection in Qdrant:
Collections:
├── wdg_framework # Wikit core framework
├── platform # CLI, MCP server, indexer, dashboard, docs
├── project_my_site # Project: my-site
├── project_client_site # Project: client-site
└── project_demo # Project: demoVector Metadata
Each indexed chunk is a Qdrant point with a flat payload (no nested metadata object) and an id of md5(file_path:line_number:name). Core keys:
{
"id": "md5(file_path:line_number:name)",
"vector": [0.1, 0.2, 0.3, "..."],
"payload": {
"project": "my-site",
"file_path": "wp-content/themes/custom/functions.php",
"line_number": 45,
"name": "get_user_posts",
"component_type": "function",
"language": "php",
"content": "function get_user_posts($user_id...) {...}"
}
}The indexer also stores file_extension, is_wikit, and repository, plus component-specific optional fields (e.g. calls, extends, implements, ACF/hook metadata). There is no line_start/line_end, docblock, commit_hash, branch, or indexed_at field.
Indexing Performance
Initial Indexing
# Wikit Framework (~5,000 files)
Time: 5-7 minutes
Vectors created: ~15,000
Disk space: ~50MB
# Typical project (~500 files)
Time: 30-60 seconds
Vectors created: ~1,500
Disk space: ~5MBIncremental Updates
# Single file change
Time: <1 second
Vectors updated: 1-10
Overhead: Minimal
# Pull with 20 changed files
Time: 5-10 seconds
Vectors updated: 50-200
Overhead: NegligiblePerformance Optimization
Batch Processing:
# Indexer processes files in batches
batch_size = 32
embeddings = model.encode(
code_chunks,
batch_size=batch_size,
show_progress_bar=True
)Caching:
# Only re-index if file changed
if file_hash != cached_hash:
index_file(file)
else:
skip_file(file)Search Capabilities
Semantic Search
Find code by meaning, not just keywords:
# Search query: "validate user email address"
# Finds:
- is_valid_email($email)
- validateEmailAddress(email)
- checkUserEmailFormat()
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/Cross-Language Search
# Search: "fetch data from API"
# Finds across languages:
PHP: wp_remote_get($url)
JS: fetch(url).then(r => r.json())
JS: axios.get(url)Pattern Recognition
# Search: "custom post type registration"
# Finds all register_post_type() calls with context:
- Portfolio custom post type
- Testimonials CPT
- Events post type
- Product catalogCode Chunking Strategy
PHP Chunking
// Chunk 1: Function with full body
function calculate_total($items) {
$total = 0;
foreach ($items as $item) {
$total += $item->price;
}
return $total;
}
// Chunk 2: Separate function
function apply_discount($total, $discount) {
return $total * (1 - $discount);
}JavaScript Chunking
// Chunk 1: Component definition
function ProductCard({ product }) {
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
);
}
// Chunk 2: Helper function
const formatPrice = (price) => {
return `$${price.toFixed(2)}`;
};CSS Chunking
// Chunk 1: Component styles
.product-card {
display: flex;
padding: 1rem;
h3 {
font-size: 1.2rem;
}
}
// Chunk 2: Media queries
@media (max-width: 768px) {
.product-card {
flex-direction: column;
}
}Managing Collections
List Collections
wdg collections listOutput (counts are illustrative):
Available collections:
- wdg_framework: 15234 vectors
- platform: 4120 vectors
- project_my_site: 1450 vectors
- project_client_site: 3892 vectorsDelete Collection
# Delete project collection
wdg collections delete project_old_site
# Re-create by re-indexing
wdg index old-siteGit Hook Integration
Two hooks ship in the platform's hooks/ directory — post-commit and post-merge. Both POST the changed file list to the running indexer service rather than shelling out to wdg index. (See Git Hooks for the full implementation.)
Post-Commit Hook (summary)
#!/usr/bin/env bash
# .git/hooks/post-commit (excerpt)
# Skip silently unless the indexer container is up
docker ps --format '{{.Names}}' | grep -q "wdg-indexer" || exit 0
# Changed files, with host paths rewritten to the container's /workspace
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD \
| while read -r f; do echo "$REPO_DIR/$f"; done \
| sed "s|${WDG_ROOT}|/workspace|g")
FILES_JSON=$(echo "$CHANGED_FILES" | jq -R -s -c 'split("\n") | map(select(length > 0))')
# Fire-and-forget POST to the indexer (backgrounded so the commit isn't blocked)
(curl -s --max-time 300 -X POST http://localhost:8666/index/incremental \
-H "Content-Type: application/json" \
-d "{\"files\": $FILES_JSON, \"project\": \"$INDEX_PROJECT\"}" \
> /dev/null 2>&1) &The post-merge hook is identical except it diffs from ORIG_HEAD to capture everything a pull/merge changed.
Installing Hooks
# Hooks are installed automatically when:
# 1. Creating a project with --init-wikit
wdg create my-site --init-wikit
# 2. Adding a repository to a project
wdg my-site repo add https://github.com/client/repo
# 3. Manually (the two shipped hooks)
hooks/install-hooks.sh /path/to/repo
# or
cp hooks/post-commit hooks/post-merge /path/to/repo/.git/hooks/
chmod +x /path/to/repo/.git/hooks/post-commit /path/to/repo/.git/hooks/post-mergeIndexing Best Practices
1. Commit Frequently
# Each commit triggers incremental indexing
git commit -m "Add user authentication" # Indexes auth code
git commit -m "Add email validation" # Indexes validation2. Use Descriptive Commits
# Good: AI can understand context
git commit -m "Add custom post type for portfolio items"
# Bad: Less context for AI
git commit -m "Update code"3. Structure Code Well
// Good: Clear function separation
function get_user() { }
function validate_user() { }
function save_user() { }
// Bad: Monolithic function (harder to search)
function handle_user() {
// 200 lines of mixed logic
}4. Include DocBlocks
/**
* Calculate discounted price for user
*
* @param float $price Original price
* @param int $user_id User ID for discount lookup
* @return float Discounted price
*/
function calculate_discount($price, $user_id) {
// Implementation
}5. Regular Maintenance
# Weekly: Update and re-index framework
wdg update
# Monthly: validate, then clean orphaned collections
wdg collections validate
wdg collections clean --confirm
# Quarterly: re-index each scope
wdg index # Wikit framework
wdg index --platform # platform code
wdg index my-site # each projectTroubleshooting
Indexing Not Triggering
# Check if hooks are installed
ls -la .git/hooks/post-commit
# Verify hook is executable
chmod +x .git/hooks/post-commit
# Test hook manually
.git/hooks/post-commitSlow Indexing
# Check system resources
docker stats wdg-indexer
# Use faster model
# Edit .env: EMBEDDING_MODEL=all-MiniLM-L6-v2
# Restart indexer
docker-compose restart indexerMissing Results
# Verify collection exists
wdg collections list
# Check vector count
curl http://localhost:6333/collections/project_my_site
# Re-index if needed (overwrites existing vectors)
wdg index my-siteOut of Disk Space
# Check collection sizes
wdg collections list
# Delete old collections
wdg collections delete project_old_*
# Prune Docker volumes
docker system prune -vAdvanced Configuration
Custom Embedding Model
# Edit .env
EMBEDDING_MODEL=all-mpnet-base-v2 # Higher quality, slower
# or
EMBEDDING_MODEL=all-MiniLM-L6-v2 # Faster, default
# Restart indexer service to load the new model
docker compose restart indexer
# Re-index each scope with the new model
wdg index # Wikit framework
wdg index --platform # platform code
wdg index my-site # each projectIndexing Filters
Filtering is not configurable per project — there is no .wdg/indexing.json file, and there are no chunk_size/overlap settings. The indexer engine applies a fixed policy:
- Indexable extensions are fixed (php, js, jsx, ts, tsx, css, scss, sass, json, md, yml, yaml, py, sh).
- Skipped directories are hardcoded:
node_modules,vendor,dist,build,.git,__pycache__, the variousvenv/cache dirs, andworktrees(git and agent worktrees). Third-party/plugins/and the bundledtwenty*themes are also excluded;mu-pluginsare kept. - Chunking is fixed: semantic components (functions, classes, hooks, blocks, etc.) where the language supports it, otherwise 50-line windows. There is no configurable chunk size or overlap.
See Indexer Internals for the authoritative extension list and exclusion rules.
Integration with AI Assistants
The indexed code becomes instantly searchable by AI:
# AI can now answer:
"Where do we register custom post types?"
"Show me how we handle user authentication"
"Find similar implementations of email validation"
"What Wikit blocks are used in this project?"Monitoring Indexing
View Indexing Logs
# Real-time logs
wdg logs indexer --follow
# Last 100 lines
wdg logs indexer --tail 100Indexing Status
# Overall status
wdg status
# Project-specific status
wdg status my-siteNext Steps:
- Learn about Local Embeddings
- Configure MCP Server
- Review Git Hooks