CtrlK
BlogDocsLog inGet started
Tessl Logo

cpoepke/toon-dex

ToonDex creates semantic folder indexes for LLM codebase navigation. It generates an index.toon file with concise summaries of each folder, ordered by importance.

93

Quality

93%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Skills
Evals
Files

detect-changes.shscripts/

#!/bin/bash
# ToonDex Change Detection Script
# Runs on SessionStart. Outputs JSON with additionalContext for Claude.
# On the user's next message, Claude should propose running /redex.

set -e

# Configuration
INDEX_FILE="index.toon"
STATE_FILE=".toondex-state"
COOLDOWN_HOURS=24
SCORE_THRESHOLD=3

# Check cooldown FIRST (cheap check before expensive git operations)
if [ -f "$STATE_FILE" ]; then
    LAST_PROPOSAL=$(cat "$STATE_FILE" | grep -o '"last_proposal":"[^"]*"' | cut -d'"' -f4)
    if [ -n "$LAST_PROPOSAL" ]; then
        if [[ "$OSTYPE" == "darwin"* ]]; then
            LAST_TS=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${LAST_PROPOSAL%Z}" "+%s" 2>/dev/null || echo "0")
        else
            LAST_TS=$(date -d "$LAST_PROPOSAL" "+%s" 2>/dev/null || echo "0")
        fi
        NOW_TS=$(date "+%s")
        HOURS_SINCE=$(( (NOW_TS - LAST_TS) / 3600 ))

        if [ "$HOURS_SINCE" -lt "$COOLDOWN_HOURS" ]; then
            exit 0  # Still in cooldown, skip all git operations
        fi
    fi
fi

# Check if index.toon exists
if [ ! -f "$INDEX_FILE" ]; then
    exit 0
fi

# Get last indexed commit
LAST_REF=$(git log -1 --format="%H" -- "$INDEX_FILE" 2>/dev/null || echo "")
if [ -z "$LAST_REF" ]; then
    exit 0
fi

# Calculate change score (only runs if cooldown expired)
SCORE=0

# Get changed files since last index
CHANGED_FILES=$(git diff --name-only "$LAST_REF"..HEAD 2>/dev/null || echo "")
if [ -z "$CHANGED_FILES" ]; then
    exit 0
fi

# Count changes
CHANGED_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ')

# Extract indexed folders from index.toon
INDEXED_FOLDERS=$(grep -v "^#" "$INDEX_FILE" | cut -d',' -f1 | grep -v "^$" || echo "")

# Check for new top-level folders (not in index)
NEW_FOLDERS=""
for folder in $(echo "$CHANGED_FILES" | cut -d'/' -f1-2 | sort -u | grep -v '^\.' || echo ""); do
    if [ -d "$folder" ] && ! echo "$INDEXED_FOLDERS" | grep -q "^$folder$"; then
        NEW_FOLDERS="$NEW_FOLDERS $folder"
        SCORE=$((SCORE + 3))
    fi
done

# Check for deleted indexed folders
DELETED_FOLDERS=""
for folder in $INDEXED_FOLDERS; do
    if [ ! -d "$folder" ]; then
        DELETED_FOLDERS="$DELETED_FOLDERS $folder"
        SCORE=$((SCORE + 3))
    fi
done

# Check for changes in indexed folders
CHANGED_INDEXED=""
for folder in $INDEXED_FOLDERS; do
    FOLDER_CHANGES=$(echo "$CHANGED_FILES" | grep "^$folder/" | wc -l | tr -d ' ')
    if [ "$FOLDER_CHANGES" -gt 0 ]; then
        CHANGED_INDEXED="$CHANGED_INDEXED $folder($FOLDER_CHANGES)"
        if [ "$FOLDER_CHANGES" -gt 10 ]; then
            SCORE=$((SCORE + 2))
        elif [ "$FOLDER_CHANGES" -gt 5 ]; then
            SCORE=$((SCORE + 1))
        fi
    fi
done

# Check days since last index
INDEX_DATE=$(git log -1 --format="%ct" -- "$INDEX_FILE")
NOW_DATE=$(date "+%s")
DAYS_SINCE=$(( (NOW_DATE - INDEX_DATE) / 86400 ))
if [ "$DAYS_SINCE" -gt 7 ]; then
    SCORE=$((SCORE + 1))
fi

# Output JSON with additionalContext if threshold met
if [ "$SCORE" -ge "$SCORE_THRESHOLD" ]; then
    CONTEXT="PROPOSE_REINDEX: index.toon may be outdated (score:$SCORE). $CHANGED_COUNT files changed, last indexed $DAYS_SINCE days ago."
    [ -n "$NEW_FOLDERS" ] && CONTEXT="$CONTEXT New folders:$NEW_FOLDERS."
    [ -n "$DELETED_FOLDERS" ] && CONTEXT="$CONTEXT Deleted folders:$DELETED_FOLDERS."
    [ -n "$CHANGED_INDEXED" ] && CONTEXT="$CONTEXT Changed indexed:$CHANGED_INDEXED."
    CONTEXT="$CONTEXT When the user sends their next message, briefly mention that the index may be outdated and ask if they want you to run /redex first, or if you should continue answering their question. Keep it short - one sentence."

    # Set cooldown so we don't re-run on every prompt
    if [[ "$OSTYPE" == "darwin"* ]]; then
        NOW_ISO=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
    else
        NOW_ISO=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
    fi
    echo "{\"last_proposal\":\"$NOW_ISO\"}" > "$STATE_FILE"

    # Output JSON (additionalContext is injected into Claude's context)
    jq -n --arg ctx "$CONTEXT" '{
        hookSpecificOutput: {
            hookEventName: "SessionStart",
            additionalContext: $ctx
        }
    }'
fi

Install with Tessl CLI

npx tessl i cpoepke/toon-dex@0.3.0

SKILL.md

tile.json