From code-index
This skill should be used INSTEAD OF reading whole files or grepping source code for definitions. Use it when: reading code ("read the UserService class", "show me the get_user function" — use the index to read only the exact lines, not the whole file), before editing code ("change the get_user method" — find the exact symbol range first), finding where symbols are defined ("where is UserService", "which file has get_user" — use the index instead of Glob or Grep on source), understanding project structure ("what classes exist", "show me an outline"), navigating between related symbols ("find all classes in this module", "what does this package export"), spotting duplicate definitions ("are there two classes named Config", "same function name in multiple files"), or searching across all your projects ("where did I implement retry logic", "find UserService across projects"). Index once with ctags, then search and read precisely. Supports local (in-project) and global (~/.local/share/code-index/) storage modes. Requires universal-ctags.
How this skill is triggered — by the user, by Claude, or both
Slash command
/code-index:code-indexingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when you need to explore or navigate a codebase without reading
Use this skill when you need to explore or navigate a codebase without reading whole files. Index once with ctags, then search symbols precisely. Default to this over reading whole files.
Core rule: never read a full file when you only need part of it. Use the index to find the symbol's start and end lines, then read only that range.
Allowed tools: Use Bash to run ctags commands (indexing, version checks).
Use Grep to search tags files. Use Read with offset/limit for symbol retrieval.
Before indexing, verify universal-ctags is installed:
command -v ctags && ctags --version
The output must contain "Universal Ctags". If it shows "Exuberant Ctags" or the command is not found, the user needs to install universal-ctags:
brew install universal-ctagsapt install universal-ctagsapk add ctagscurl -LO https://github.com/universal-ctags/ctags-nightly-build/releases/latest/download/uctags-$(date +%Y.%m.%d)-linux-x86_64.release.tar.gz
tar xzf uctags-*.tar.gz && cp uctags-*/bin/ctags /usr/local/bin/
For aarch64, replace x86_64 with aarch64. Browse all builds at
https://github.com/universal-ctags/ctags-nightly-build/releasesIf a shell alias interferes with ctags, use the full binary path instead (e.g.,
/opt/homebrew/bin/ctags on macOS with Homebrew, /usr/local/bin/ctags for
static binary installs). Find it with which -a ctags.
If universal-ctags is not available or the wrong version is detected, tell the user and stop. Do not attempt to index with Exuberant Ctags — the output format is incompatible.
The skill supports two storage modes for config and tags files:
Config and tags live in the project root:
<project-root>/
├── .ctags.d/code-index.ctags # config (excludes, required options)
└── .ctags # tags file (relative paths)
.ctags.d/ is committed to git (project configuration).ctags is added to .gitignore (generated artifact)Config and tags live in ~/.local/share/code-index/:
~/.local/share/code-index/
├── config.ctags # shared required options (all projects)
├── a3f2b1e9.tags # project index (absolute paths)
├── a3f2b1e9.ctags # project config (excludes)
├── 7c9e4d82.tags # another project
└── 7c9e4d82.ctags
Each project is identified by a hash of its absolute root path:
project_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
hash="$(echo -n "$project_root" | shasum -a 256 | cut -c1-8)"
On first use in a session, determine which index to use:
~/.local/share/code-index/<hash>.tags — if it exists, use global mode.<project-root>/.ctags — if it exists, use local mode.The existence of the tags file IS the preference — no separate config needed. Once resolved, use that mode for the rest of the session.
These options are non-negotiable — the skill cannot work without them:
--fields=+neKS — end line (e), line number (n), full kind name (K),
signature (S). The + adds to existing fields.--extras=+q — qualified tags (class-qualified entries).--output-format=u-ctags — structured field output format.Run this on the first index of the session — whether the config exists or not.
The project root is the directory containing .git — find it with
git rev-parse --show-toplevel if unsure. If there's no git, use the working
directory and skip all git-related steps (.gitignore, commit suggestions).
Check for existing configs — read all files in .ctags.d/ (if it exists).
Check compatibility — look for conflicts with required options:
--output-format=e-ctags — incompatible. Inform the user: "This skill
requires u-ctags output format for structured field parsing (line:,
end:, kind:). The existing config sets e-ctags. How would you like
to proceed?"--fields=-n or --fields=-e — incompatible. Inform the user: "This
skill needs line and end fields. The existing config removes them."--languages, --exclude, --recurse — compatible. Note what's
already covered so you don't duplicate it.Languages — do not set --languages. Universal-ctags supports 100+
languages out of the box. Let it index everything it recognizes. Only add
--languages if the user explicitly asks to restrict indexing.
Autodetect excludes — scan the project and add excludes for all known
build output, vendored dependencies, and generated files. Excludes match by
name anywhere in the tree (e.g., --exclude=.venv catches .venv/ at any
depth). Skip any already covered by other configs.
.git__pycache__, .venv, venv, .tox, *.pycnode_modules, dist, build, *.min.js, package-lock.json, yarn.lock, pnpm-lock.yamlvendortarget*.class, .gradle, buildvendor/bundle*.o, *.so, *.acoverage, .cache, .tmpCreate or update .ctags.d/code-index.ctags — include required options
plus only what's not already covered by existing configs.
Inform the user — briefly summarize what was detected and configured.
If any changes were made to .ctags.d/ or .gitignore, offer to commit
them (it's project configuration, like .editorconfig).
Create the global directory if it doesn't exist:
mkdir -p ~/.local/share/code-index
Create or verify config.ctags — the shared required options file at
~/.local/share/code-index/config.ctags:
--fields=+neKS
--extras=+q
--output-format=u-ctags
Create <hash>.ctags — the per-project config at
~/.local/share/code-index/<hash>.ctags. This contains only --recurse and
excludes (required options are in config.ctags):
--recurse
--exclude=.git
--exclude=node_modules
--exclude=__pycache__
...
Use the same autodetect excludes logic as local mode.
Inform the user — briefly summarize what was configured.
Local mode — no existing configs (typical case — Python + TypeScript project):
--recurse
--fields=+neKS
--extras=+q
--output-format=u-ctags
--exclude=.git
--exclude=node_modules
--exclude=__pycache__
--exclude=.venv
--exclude=venv
--exclude=.tox
--exclude=*.pyc
--exclude=dist
--exclude=build
--exclude=*.min.js
--exclude=package-lock.json
--exclude=yarn.lock
--exclude=coverage
Local mode — project already has configs with --recurse, --languages, and excludes:
--fields=+neKS
--extras=+q
--output-format=u-ctags
Global mode — shared config (~/.local/share/code-index/config.ctags):
--fields=+neKS
--extras=+q
--output-format=u-ctags
Global mode — per-project config (~/.local/share/code-index/<hash>.ctags):
--recurse
--exclude=.git
--exclude=node_modules
--exclude=__pycache__
--exclude=.venv
--exclude=dist
--exclude=build
--exclude=coverage
Local mode: Only update .ctags.d/code-index.ctags when the user explicitly
asks — e.g., "exclude the migrations directory", "only index Python files", "add
more excludes". Do not re-run autodetection on every index.
Global mode: Only update ~/.local/share/code-index/<hash>.ctags when the
user explicitly asks. Same rules apply.
.git lives):
ctags -f .ctags
This auto-discovers all configs from .ctags.d/..ctags is not in .gitignore, append it:
grep -qxF '.ctags' .gitignore 2>/dev/null || echo '.ctags' >> .gitignore
project_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
hash="$(echo -n "$project_root" | shasum -a 256 | cut -c1-8)"
cd "$project_root" && ctags \
--options=~/.local/share/code-index/config.ctags \
--options=~/.local/share/code-index/$hash.ctags \
--tag-relative=never \
-f ~/.local/share/code-index/$hash.tags
--tag-relative=never writes absolute paths so tags are self-describing.offset + limit), not whole files — use
line:N and end:M from the ctags entry to read only the exact definitionscope: fields to navigate hierarchies — to find all methods of a
class, grep for scope:ClassName instead of reading the whole class.ctags.d/ for local
mode, --options= for global mode)Index resolution picks global over local. To switch modes, delete the old mode's tags file — the index will regenerate automatically on next use.
<project-root>/.ctags~/.local/share/code-index/<hash>.tags~/.local/share/code-index/Instead of reading a whole file, use the index:
Grep pattern="^get_user\t" path="<tags-file>" output_mode="content"line:N and end:M in the ctags entry.Read file_path="/absolute/path/to/src/services/user.py" offset=N limit=(M-N+1)Always use absolute paths for Read — source files may not be in your current working directory. In global mode, the tags file already contains absolute paths. In local mode, prepend the project root.
Search the tags file using Grep. The ctags format is tab-delimited:
symbol<TAB>file<TAB>pattern<TAB>fields...
In local mode, file paths in the tags are relative. In global mode, they are absolute. Use the appropriate path form when searching by file.
Find a symbol by exact name, prefix, or substring:
# Exact match (local mode — path to project's .ctags)
Grep pattern="^UserService\t" path="/absolute/path/to/project/.ctags" output_mode="content"
# Exact match (global mode — path to global tags file)
Grep pattern="^UserService\t" path="~/.local/share/code-index/<hash>.tags" output_mode="content"
# Prefix match (e.g., all User* symbols)
Grep pattern="^User[^\t]*\t" path="<tags-file>" output_mode="content"
# Substring match (broad search)
Grep pattern="User" path="<tags-file>" output_mode="content"
The kind is a tab-separated column (e.g., \tclass\t, \tfunction\t):
Grep pattern="\tclass\t" path="<tags-file>" output_mode="content"
Find all symbols defined in a specific file:
# Local mode (relative path)
Grep pattern="\tsrc/services/user.py\t" path="<tags-file>" output_mode="content"
# Global mode (absolute path)
Grep pattern="\t/absolute/path/to/project/src/services/user.py\t" path="<tags-file>" output_mode="content"
Chain name and kind for precise lookup — search by name first, then filter results by kind:
Grep pattern="^SymbolName\t" path="<tags-file>" output_mode="content"
Then visually confirm the kind: field in the results matches what you need
(e.g., kind:class vs kind:function).
When the user asks about a symbol across all their projects, or when a symbol is not found in the current project index, search all global indexes:
Grep pattern="^UserService\t" path="~/.local/share/code-index" glob="*.tags" output_mode="content"
This searches every *.tags file in the global directory. Results contain
absolute paths, so you can immediately see which project each symbol belongs to.
Use cross-project search when:
Once you find a symbol in the ctags output, extract its location to read just that symbol — not the entire file.
line:N from the ctags entry to get the start line.end:M from the ctags entry to get the end line.L = end - line + 1.Read file_path="path/to/file.py" offset=N limit=L
In global mode, the file path in the ctags entry is already absolute — use it directly. In local mode, prepend the project root to get the absolute path.
If end: is not present (some symbol kinds omit it), read a reasonable chunk
(e.g., 30 lines) and adjust if the definition continues.
To get an overview of a single file, grep all its symbols from the index and present them as a structured list:
Grep pattern="\tpath/to/file.py\t" path="<tags-file>" output_mode="content"
class ClassName line:10
method method_one line:15
method method_two line:30
function standalone_func line:55
Indent methods and members under their parent class using the scope: field
from the ctags output to determine hierarchy.
To understand the overall project structure, grep for top-level symbols only:
Grep pattern="\t(class|function|module)\t" path="<tags-file>" output_mode="content"
Group results by file path and present as a tree:
src/auth/service.py
class AuthService line:12
function create_token line:85
src/users/repository.py
class UserRepository line:8
class UserNotFound line:95
This gives a high-level map of the codebase without reading any source files.
npx claudepluginhub kcem/code-index --plugin code-indexGuides token-efficient code navigation using ctags index to find symbol definitions and references instead of broad grep searches.
Navigates codebases using .codemap/ structural indexes to find symbol definitions (classes, functions, methods), explore file structures, and locate code by name. Enables targeted line-range reads to reduce token consumption.
Search code symbols (functions, classes, methods, types), find call sites and references, list file symbols, get outlines, check index status, and query static analysis findings. Requires indexed codebase.