Skip to content

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:

php
// 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:

php
// 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:

php
// Indexed with full context
add_action('init', function() {
    register_post_type('custom_type', [
        'public' => true,
        'supports' => ['title', 'editor']
    ]);
});

JavaScript Files ​

Functions:

javascript
// 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:

jsx
// 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 ​

scss
// 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 ​

json
{
  "name": "wdg/custom-block",
  "title": "Custom Block",
  "category": "wdg-blocks",
  "attributes": {
    "content": {
      "type": "string",
      "default": ""
    }
  }
}

Indexing Strategies ​

Git hooks automatically trigger indexing:

bash
# 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 changes

Manual Indexing ​

bash
# 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 --update

There 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: demo

Vector 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:

json
{
  "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 ​

bash
# 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: ~5MB

Incremental Updates ​

bash
# 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: Negligible

Performance Optimization ​

Batch Processing:

python
# Indexer processes files in batches
batch_size = 32
embeddings = model.encode(
    code_chunks,
    batch_size=batch_size,
    show_progress_bar=True
)

Caching:

python
# Only re-index if file changed
if file_hash != cached_hash:
    index_file(file)
else:
    skip_file(file)

Search Capabilities ​

Find code by meaning, not just keywords:

bash
# Search query: "validate user email address"
# Finds:
- is_valid_email($email)
- validateEmailAddress(email)
- checkUserEmailFormat()
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/
bash
# 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 ​

bash
# Search: "custom post type registration"
# Finds all register_post_type() calls with context:
- Portfolio custom post type
- Testimonials CPT
- Events post type
- Product catalog

Code Chunking Strategy ​

PHP Chunking ​

php
// 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 ​

javascript
// 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 ​

scss
// 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 ​

bash
wdg collections list

Output (counts are illustrative):

Available collections:
  - wdg_framework: 15234 vectors
  - platform: 4120 vectors
  - project_my_site: 1450 vectors
  - project_client_site: 3892 vectors

Delete Collection ​

bash
# Delete project collection
wdg collections delete project_old_site

# Re-create by re-indexing
wdg index old-site

Git 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) ​

bash
#!/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 ​

bash
# 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-merge

Indexing Best Practices ​

1. Commit Frequently ​

bash
# Each commit triggers incremental indexing
git commit -m "Add user authentication"  # Indexes auth code
git commit -m "Add email validation"     # Indexes validation

2. Use Descriptive Commits ​

bash
# 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 ​

php
// 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 ​

php
/**
 * 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 ​

bash
# 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 project

Troubleshooting ​

Indexing Not Triggering ​

bash
# 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-commit

Slow Indexing ​

bash
# Check system resources
docker stats wdg-indexer

# Use faster model
# Edit .env: EMBEDDING_MODEL=all-MiniLM-L6-v2

# Restart indexer
docker-compose restart indexer

Missing Results ​

bash
# 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-site

Out of Disk Space ​

bash
# Check collection sizes
wdg collections list

# Delete old collections
wdg collections delete project_old_*

# Prune Docker volumes
docker system prune -v

Advanced Configuration ​

Custom Embedding Model ​

bash
# 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 project

Indexing 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 various venv/cache dirs, and worktrees (git and agent worktrees). Third-party /plugins/ and the bundled twenty* themes are also excluded; mu-plugins are 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:

bash
# 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 ​

bash
# Real-time logs
wdg logs indexer --follow

# Last 100 lines
wdg logs indexer --tail 100

Indexing Status ​

bash
# Overall status
wdg status

# Project-specific status
wdg status my-site

Next Steps: