Code indexing
Skill kcem/code-index/plugins/code-index/skills/code-indexing
Use when navigating source code by symbol with Universal Ctags: finding definitions, reading exact class/function ranges, building file/project outlines, checking duplicate symbols, or searching indexed symbols across projects. Prefer normal read/search for docs, config files, tiny files, or non-symbol text search.From its SKILL.md
npx -y skills add kcem/code-index --skill code-indexingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
17.6 KB, ~4.6k tokens by cl100k_base, as published. Nobody here has run it
Code Indexing — Fast, Precise Codebase Exploration
Use this skill when you need to explore or navigate source code by symbol without reading whole files. Index once with ctags, then search symbols precisely.
Core rule: don't read a full source file when a symbol range will answer. Use the index to find the symbol's start and end lines, then read only that range.
Use shell/Bash to run ctags commands. Search tags files with the available
search tool (Grep, grep, or rg). Retrieve code with the available
range-read mechanism (Read offset/limit, sed -n, or equivalent). Prefer
normal file reads for small files, documentation, config files, and non-symbol
text search.
Prerequisites
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:
- macOS:
brew install universal-ctags - Ubuntu/Debian:
apt install universal-ctags - Alpine:
apk add ctags - Fallback — if no package is available (e.g., Wolfi, minimal containers),
ask before downloading a static binary. Use a matching asset from
https://github.com/universal-ctags/ctags-nightly-build/releases. Do not
construct date-based
latest/downloadURLs; nightly asset names change by release date and architecture.
If 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.
Storage Modes
The skill supports two storage modes for config and tags files:
Local mode (in-project)
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).ctagsis added to.gitignore(generated artifact)- Tags contain relative paths
- Cross-project search is not available
Global mode (stealth)
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
- Zero files in the project — nothing to gitignore, nothing committed
- Tags contain absolute paths (self-describing)
- Cross-project search available across all global indexes
Project key
Each project is identified by a hash of its absolute root path:
project_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
hash="$(printf '%s' "$project_root" | shasum -a 256 2>/dev/null | cut -c1-8)"
if [ -z "$hash" ]; then
hash="$(printf '%s' "$project_root" | sha256sum | cut -c1-8)"
fi
Index Resolution
On first use in a session, determine which index to use:
- Compute the project key (see above).
- Check
~/.local/share/code-index/<hash>.tags— if it exists, use global mode. - Check
<project-root>/.ctags— if it exists, use local mode. - Neither exists — ask the user: "Store index locally (in project, tracked by git) or globally (~/.local/share/code-index/, no project files touched)?" If the sandbox cannot write to the global directory, ask for approval or 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.
Ctags Configuration
Required options
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.
First-time setup — local mode
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 requiresu-ctagsoutput format for structured field parsing (line:,end:,kind:). The existing config setse-ctags. How would you like to proceed?"--fields=-nor--fields=-e— incompatible. Inform the user: "This skill needslineandendfields. 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--languagesif 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=.venvcatches.venv/at any depth). Skip any already covered by other configs.- Always:
.git - Python:
__pycache__,.venv,venv,.tox,*.pyc - Node/JS/TS:
node_modules,dist,build,*.min.js,package-lock.json,yarn.lock,pnpm-lock.yaml - Go:
vendor - Rust:
target - Java/Kotlin:
*.class,.gradle,build - Ruby:
vendor/bundle - C/C++:
*.o,*.so,*.a - General:
coverage,.cache,.tmp - Add any other large generated/vendored directories visible in the project.
- Always:
-
Create 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).
First-time setup — global mode
-
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--recurseand excludes (required options are inconfig.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.
Config examples
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
Updating configuration
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.
Index Generation
Local mode
- On the first index of the session, run the first-time setup (see Ctags Configuration above) to ensure the config is current.
- Run the index command from the project root (where
.gitlives):
This auto-discovers all configs fromctags -f .ctags.ctags.d/. - If
.ctagsis not in.gitignore, append it:grep -qxF '.ctags' .gitignore 2>/dev/null || echo '.ctags' >> .gitignore
Global mode
- On the first index of the session, run the first-time setup (see Ctags Configuration above) to ensure the config and directory exist.
- Compute the project key:
project_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" hash="$(printf '%s' "$project_root" | shasum -a 256 2>/dev/null | cut -c1-8)" if [ -z "$hash" ]; then hash="$(printf '%s' "$project_root" | sha256sum | cut -c1-8)" fi - Run the index command from the project root:
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=neverwrites absolute paths so tags are self-describing.
When to Index
- First symbol lookup — if the tags file doesn't exist, index now.
- Symbol not found but source file exists — stale index, re-index.
- Line numbers don't match actual code — index is outdated, re-index.
- User explicitly asks to re-index or refresh — re-index.
- When in doubt, re-index — ctags runs in seconds. Don't overthink staleness, just re-index.
- Index on first read or search, not before — don't index proactively on session start. Index the moment you need to read, search, or edit code.
Do
- Search the tags file before reading any file — a symbol lookup is faster and more focused than reading an entire module
- Read symbols by line range (
offset+limit), not whole files — useline:Nandend:Mfrom the ctags entry to read only the exact definition - Prefer outlines over full reads — when the user asks "what's in this file", produce a file outline from the index instead of reading the file
- Use scope fields to navigate hierarchies — to find all methods of a
class, grep for fields like
class:ClassName,namespace:Name,struct:Name,interface:Name, orsection:Nameinstead of reading the whole parent symbol - Batch related lookups — if you need multiple symbols, run parallel Grep calls against the tags file rather than sequential file reads
- Use cross-project search when a symbol isn't found in the current project or when the user asks about symbols across projects
- Re-index when results look wrong or stale — ctags runs in seconds, don't overthink staleness
Don't
- Read a whole file to find one function or class — use the index
- Use Glob or Grep on source files to find definitions — search the tags file instead
- Run ctags with inline flags — use config files (
.ctags.d/for local mode,--options=for global mode) - Override existing project ctags configs without asking the user
- Mix storage modes for the same project — use whichever mode the index resolution determined, don't create both local and global indexes
- Update config on every index — only update when the user explicitly asks
Switching Modes
Index resolution picks global over local. To switch modes, delete the old mode's tags file — the index will regenerate automatically on next use.
- Local → global: delete
<project-root>/.ctags - Global → local: delete
~/.local/share/code-index/<hash>.tags - Project moved/renamed: old hash is orphaned, delete it from
~/.local/share/code-index/
Example: Reading a Symbol
Instead of reading a whole file, use the index:
- Search the index for the symbol (use the resolved tags file path — see
Index Resolution):
Grep pattern="^get_user\t" path="<tags-file>" output_mode="content" - Parse the result — find
line:Nandend:Min the ctags entry. - Read only the symbol's lines:
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.
Symbol Search
Search the tags file using Grep, grep, or rg. The examples below use
Claude-style Grep notation; in shell, the equivalent is rg '<pattern>' <tags-file> or grep -E '<pattern>' <tags-file>.
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.
By name
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"
By kind
The kind is a tab-separated column (e.g., \tclass\t, \tfunction\t):
Grep pattern="\tclass\t" path="<tags-file>" output_mode="content"
By file
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"
Combined
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 bare kind field in the results matches what you need
(e.g., class vs function).
Cross-project search
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:
- The user explicitly asks to search across projects
- A symbol is not found in the current project but might exist elsewhere
- The user asks "where did I implement X" without specifying a project
Symbol Retrieval
Once you find a symbol in the ctags output, extract its location to read just that symbol — not the entire file.
- Parse
line:Nfrom the ctags entry to get the start line. - Parse
end:Mfrom the ctags entry to get the end line. - Compute limit:
L = end - line + 1. - Read the symbol:
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.
File Outline
To get an overview of a single file, grep all its symbols from the index and present them as a structured list:
- Search:
Grep pattern="\tpath/to/file.py\t" path="<tags-file>" output_mode="content" - Present results as a structured outline:
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 using scope-like fields from the
ctags output (class:, namespace:, struct:, interface:, section:, etc.)
to determine hierarchy.
Project Outline
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.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.