clew (n.) a ball of thread; the origin of the word clue.
Ariadne's thread for your codebase.
What is it? • Features • Getting Started • Usage • Commands • Integration • Background • Changelog
Warning
Early development. This README describes the intended v1. Sections marked 🚧 are not implemented yet. The indexing pipeline it is built on has been validated end-to-end (Java + Angular superproject, cross-file go-to-definition working); the plugin around it is new.
A Neovim plugin that gives you go-to-definition and find-references served from a precomputed SCIP index, with no language server running inside your editor. It works across single repositories, git superprojects (submodules) and monorepos alike, and the answers are compiler-grade rather than heuristic, because the index was built by a real compiler. Java/Kotlin and TypeScript/JavaScript are wired up today; every other SCIP indexer is an addition to clew, never a change to your editor.
Instead of running a semantic engine inside your editor, clew.nvim runs it out-of-band, on save, on build or in CI, and leaves Neovim querying a static index. You keep compiler-grade correctness and give up nothing but freshness.
- No resident language server. No JVM in your editor session. Indexing happens when you ask, not continuously in the background.
- Compiler-grade, not heuristic.
scip-javaindexes throughjavacitself, so you get real overload resolution, real generics and real classpath awareness, including versioned symbols for third-party jars and the JDK. - One index for the whole project. clew discovers units, meaning build roots such as a
pom.xml, abuild.gradleor apackage.json, and merges whatever it finds into one index. Fifty submodules produce one index and one server process, not fifty. - Navigate between submodules. Separate language servers each know only their own repo; a merged SCIP index resolves across them, because SCIP symbols are globally-scoped strings.
- Language-agnostic by construction. Every indexer's output lands in the same index behind the same query path, so adding a language means teaching clew to drive one more SCIP indexer, never touching the editor side. Java/Kotlin and TypeScript/JavaScript ship today.
- Native Neovim integration.
clewis an ordinary LSP server over stdio, driven by Neovim's built-in LSP client, sogd,gr,<C-]>, Telescope, fzf-lua and aerial.nvim all work with no extra glue. - No build modification required. Your
pom.xmlis never touched. Maven is invoked only for dependency resolution, never for a build.
Project layout is not a special case. A single-project repository is simply the case where there is one unit:
| Shape | What clew does |
|---|---|
| Single repository | One unit, one index. No configuration needed. |
| Git superproject | One unit per submodule, merged into one index. Navigation crosses submodule boundaries. |
| Monorepo | One unit per build root, merged the same way. Submodules are not required. |
macOS, Linux, and Windows via WSL.
Native Windows is not supported. clew drives each indexer as a shell command and the ones it ships assume a POSIX shell; several SCIP indexers need WSL or a container on Windows regardless of what clew does.
neovim 0.11+, forvim.lsp.configandvim.lsp.enable- The
clewbinary (see Installation)
Per language, only for the languages you actually index:
| Language | Needs | Provided by |
|---|---|---|
| Java / Kotlin | JDK 17+, Maven or Gradle | scip-java |
| TypeScript / JavaScript | Node.js 18+, plus the project's own package manager — pnpm, yarn or bun — when its lockfile names one |
scip-typescript |
Other languages work as soon as clew learns to drive their indexer; see the SCIP indexer list for what exists.
Optional, for nicer pickers:
lazy.nvim
{
"rafacm/clew.nvim",
build = "make", -- builds the `clew` binary
opts = {},
}Packer
require("packer").startup(function()
use({
"rafacm/clew.nvim",
run = "make",
config = function() require("clew").setup() end,
})
end)Neovim native package
git clone --depth=1 https://github.com/rafacm/clew.nvim.git \
"${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/clew/start/clew.nvim
cd "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/clew/start/clew.nvim && makeThe defaults are intended to work with no configuration:
require("clew").setup()Everything below is optional:
require("clew").setup({
-- Where the index lives, relative to the project root.
index_path = ".clew/index.scip",
-- Filetypes clew attaches to.
filetypes = { "java", "kotlin", "typescript", "typescriptreact", "javascript",
"javascriptreact" },
-- Root detection. clew always prefers the OUTERMOST match so a superproject
-- yields ONE server, not one per submodule.
root = {
markers = { ".clew/config.toml", ".clew/index.scip", ".gitmodules", ".git" },
-- Units to index. Empty = auto-discover build roots (pom.xml, build.gradle,
-- package.json + angular.json, ...) beneath the project root.
include = {},
exclude = { "vendor/**", "third_party/**", "node_modules/**" },
},
-- Rebuild the index automatically. "never" | "save" | "manual"
auto_index = "never",
-- Warn when the index is older than the working tree.
staleness_check = true,
})The workflow is deliberately explicit: you decide when the index is built.
- Open any file inside your project.
- Run
:ClewIndexonce. clew discovers indexable units, runs the right indexer for each, and merges the results into a single index at.clew/index.scip. - Navigate with the standard Neovim LSP mappings:
gd,gr,<C-]>,:Telescope lsp_references. - Re-run
:ClewIndexafter a significant change. Individual units can be refreshed with:ClewIndex <unit>, which is much faster than a full rebuild.
Because the index is a plain file, it can equally be produced in CI and then committed or cached. The editor does not care who built it.
Note
clew reports positions from the index, so after heavy edits results drift until you re-index. With staleness_check enabled, clew tells you when that has happened rather than letting you chase a phantom line number. 🚧
| Command | Description |
|---|---|
:ClewIndex |
Build or rebuild the index for the whole project |
:ClewIndex {unit} |
Rebuild a single unit, for example one submodule |
:ClewStatus |
Show project root, the marker that matched, binary location, index size and age, and attached client count |
:ClewRestart |
Restart the language server; it reattaches on the next matching buffer |
:checkhealth clew |
Verify Neovim version, binary, toolchains, root detection and index presence |
:ClewUnits |
List discovered indexable units and the indexer chosen for each 🚧 |
:ClewLog |
Open the clew server log 🚧 |
Until :ClewUnits exists, clew units --root . from a shell does the same job.
clew is an ordinary LSP server speaking stdio, so it plugs into everything Neovim already has. There is no clew-specific API to learn.
| What you want | How | Notes |
|---|---|---|
| Go to definition | vim.lsp.buf.definition() / gd |
Cross-file and cross-submodule |
| Find references | vim.lsp.buf.references() / gr |
Whole-project, from the merged index |
| Tag jump | <C-]>, :tag, <C-w>] |
Free via vim.lsp.tagfunc; the tag stack works normally |
| Document symbols | vim.lsp.buf.document_symbol() |
Powers aerial.nvim |
| Workspace symbols | vim.lsp.buf.workspace_symbol() |
Fuzzy jump across every unit at once |
| Fuzzy pickers | :Telescope lsp_references, fzf-lua lsp_definitions |
No extra configuration |
| Hover | vim.lsp.buf.hover() / K |
Signature and documentation where the index carries it 🚧 |
Deliberately not provided: completion, diagnostics, formatting and rename. A static index is the wrong tool for those, and pretending otherwise would produce confidently wrong results. Keep a real language server attached if you need them. clew coexists with one happily, and is useful precisely when you would rather not run one.
See doc/README.md:
- Why a precomputed index?, on resident semantic servers versus static indexes, and why SCIP is the right substrate.
- Related Projects, the prior art clew was weighed against, and what is not covered yet.
- Credits, the projects clew is assembled from.
Notable changes are recorded in CHANGELOG.md, following the Keep a Changelog format with dated sections.
Released under the MIT License. Copyright (c) 2026 Rafael Cordones.