One command. Every language. A single index.scip.
SCIP Index Orchestrator — detect languages, download indexers, run them, merge the results.
SCIP-IO is a polyglot SCIP index orchestrator written in Rust. It takes the pain out of generating precise code-intelligence indexes for multi-language projects by doing the whole pipeline for you:
- Detects every supported language in your project from project config files, build files, and source files.
- Installs the latest compatible SCIP indexer binary for each language (downloading from GitHub releases, npm,
dotnet tool, Coursier, or reusing what's already on yourPATH). - Runs each indexer against your project with sensible defaults.
- Merges every successful per-language
.scipfile into a single deterministicindex.scip. - Validates the final index so you know it's wellformed.
If one or more language indexers fail after another language succeeds, SCIP-IO publishes the successful output as a partial index instead of hiding it behind a generic success message. The CLI text output, CLI JSON output, and GUI all mark the result as partial and include the failed language count.
It ships as both a CLI (scip-io) and a Tauri GUI with a dark/cyberpunk-corporate aesthetic — use whichever fits your workflow.
For CLI commands using --format json, command data is emitted only on stdout.
Diagnostics and tracing are written to stderr, so redirected stdout remains
valid JSON for scripts and CI.
SCIP is the modern successor to LSIF for code intelligence. Every language has its own indexer, every indexer has its own install method, flags, and output conventions, and merging them requires understanding the SCIP protobuf schema. SCIP-IO collapses that entire workflow into:
scip-io indexSCIP-IO currently orchestrates 11 languages across 9 different indexers:
| Language | Indexer | Install Method | Detection Evidence | Indexer Readiness |
|---|---|---|---|---|
| TypeScript | scip-typescript |
npm | *.ts, *.tsx, tsconfig.json, tsconfig.*.json |
Ready |
| JavaScript | scip-typescript |
npm | *.js, *.jsx, *.mjs, *.cjs, package.json |
Ready |
| Python | scip-python |
npm | *.py, *.pyi, *.pyw, pyproject.toml, setup.py, setup.cfg, requirements.txt, Pipfile |
Ready |
| Rust | rust-analyzer |
GitHub release (gz/zip) | *.rs, Cargo.toml, rust-project.json |
Cargo.toml or rust-project.json preferred |
| Go | scip-go |
GitHub release (tar.gz) | *.go, go.mod |
Ready |
| Java | scip-java |
managed launcher | *.java, pom.xml, build.gradle |
Ready |
| Scala | scip-java |
managed launcher | *.scala, *.sbt, build.sbt |
Ready |
| Kotlin | via scip-java |
managed launcher | *.kt, *.kts, build.gradle.kts, settings.gradle.kts |
Ready |
| C# | scip-dotnet |
dotnet tool |
*.cs, *.csproj, *.sln, *.vbproj |
Ready |
| Ruby | scip-ruby |
GitHub release / WSL / Docker | *.rb, Gemfile |
Native Windows unsupported upstream; WSL/Docker backend on Windows |
| C / C++ | scip-clang |
GitHub release / WSL / Docker | *.c, *.h, *.cc, *.hh, *.cpp, *.hpp, *.cxx, *.hxx, *.S, Makefile*, Kbuild*, Kconfig*, CMakeLists.txt, compile_commands.json |
compile_commands.json required; native Windows unsupported upstream |
SCIP-IO will also pick up supported indexers already on your system PATH before
downloading a fresh copy. The managed scip-java repair is an exception.
Managed scip-java repair: SCIP-IO 0.2.1 downloads the
pinned scip-java-v0.12.3-scip-io.1 payload automatically for Java, Scala, and
Kotlin. It fixes only the Windows Gradle path-serialization defect in upstream
0.12.3. The default uses a separate versioned cache and verifies the downloaded
files before use; it does not reuse an older cache entry or a scip-java on
PATH. An explicit CLI binary configuration still takes precedence. This repair
is not included in the v0.2.0 binaries and does not include the later
Kotlin compatibility, range, or Gradle-script work. See the
distribution guide and the
historical investigation.
Kotlin compatibility distribution: SCIP-IO 0.2.2 automatically downloads
the published scip-java-v0.12.3-scip-io.2 payload. It rebuilds 0.12.3 with
the path repair plus the tested Kotlin 2.3.21, source-range, and Gradle Kotlin
DSL patches. Its payload embeds the exact tested SemanticDB Kotlin plugin JAR,
so the plugin does not need the unpublished scripts4 Maven snapshot or
JAVA_TOOL_OPTIONS at index time. Gradle can still resolve the indexed
project's normal dependencies from its configured repositories. The immutable
.1 release remains the default for released SCIP-IO 0.2.1 binaries.
When you opt in with --include-additional-configs or the GUI's Extra configs
option, SCIP-IO also discovers secondary config files for indexers that accept
multiple config inputs. Today that includes root-level tsconfig.json and
tsconfig.*.json files for scip-typescript and .sln, .csproj, or .vbproj inputs for
scip-dotnet. For C/C++, SCIP-IO also discovers validated root and
build-output compile_commands.json files, merges duplicate compile commands,
and sends one combined database to scip-clang. Other languages continue to
use their normal project-root or workspace behavior until their indexers expose
a safe multi-config contract.
flowchart TB
subgraph Row1[" "]
direction LR
A[Project Root] --> B[Detect<br/>Languages]
B --> C[Resolve Indexers<br/>from Registry]
C --> D[Ensure Indexers<br/>Installed]
D --> E[Run Indexers<br/>in parallel]
end
subgraph Row2[" "]
direction LR
F[Per-language<br/>.scip files] --> G[Merge]
G --> H[(index.scip)]
H --> I[Validate]
end
E --> F
style Row1 fill:transparent,stroke:transparent
style Row2 fill:transparent,stroke:transparent
style A fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
style B fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
style C fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
style D fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
style E fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
style F fill:#1e2336,stroke:#8b5cf6,stroke-width:2px,color:#fff
style G fill:#1e2336,stroke:#ff00aa,stroke-width:2px,color:#fff
style H fill:#0a0e1b,stroke:#ff00aa,stroke-width:3px,color:#ff00aa
style I fill:#1e2336,stroke:#00ffcc,stroke-width:2px,color:#fff
Detection is evidence-driven. SCIP-IO reports supported languages from project config files, build files, and source files, then separately reports whether the default indexer has the setup it normally needs:
flowchart TD
Start([Start]) --> Walk[Walk project tree<br/>all non-ignored descendants]
Walk --> Skip{Hidden or<br/>ignored dir?<br/>.git, node_modules,<br/>target, build, dist,<br/>out, vendor, venv}
Skip -- Yes --> Walk
Skip -- No --> Match{Matches supported<br/>config, build, or source evidence?}
Match -- No --> Walk
Match -- Yes --> Record[Record language<br/>+ evidence path]
Record --> Ready[Attach indexer<br/>readiness]
Ready --> Walk
Walk --> Done([Dedupe + sort])
style Start fill:#0a0e1b,stroke:#00ffcc,color:#00ffcc
style Done fill:#0a0e1b,stroke:#ff00aa,color:#ff00aa
By default, indexing is repo-tree scoped: the selected root owns the full
non-ignored tree and SCIP-IO lets each upstream indexer decide which files it
can safely include. Use --scope configs when you want the legacy behavior that
promotes nested indexable manifests into their own project-root runs. That mode
covers TypeScript, JavaScript, Python, Rust, Go, Java, C#, Ruby, Kotlin, C/C++
with compile_commands.json, and Scala project configs. Parent roots exclude
those nested roots while scanning, so a parent repository does not accidentally
run a root-bound indexer against a child project's config.
C/C++ remains stricter than the other languages: compile_commands.json can
define an indexable nested root, but CMakeLists.txt, Makefiles, Kbuild, and
Kconfig files only prove that C/C++ exists. They do not provide enough
information for scip-clang unless a compile database is present. When
additional configs are enabled, SCIP-IO consumes existing root/build
compile_commands.json files and deduplicates identical commands; it does not
index raw C/C++ files without compile commands. CMake build directory
generation is available only when explicitly enabled with
--generate-cmake-compile-dbs or [cpp.cmake] generate_compile_databases = true; SCIP-IO runs CMake configure commands to produce more
compile_commands.json files, then indexes only the compile database entries
that scip-clang can consume.
For targeted runs, both the CLI and GUI can accept an explicit source-file
list. SCIP-IO validates that every listed file exists under the selected repo
root, runs only the represented languages, and filters SCIP documents to those
exact repo-relative files. C/C++ still requires matching compile_commands.json
entries; when explicit files are supplied, SCIP-IO filters the compile database
before invoking scip-clang and fails clearly if no command covers a selected
C/C++ file.
SCIP-IO supports several install methods because no two SCIP indexers ship the same way:
The managed scip-java repair is intentionally outside this general
lookup: it installs only the pinned, hash-verified release payload. An explicit
CLI binary configuration is still honored before that managed install.
flowchart TD
Need[Need indexer X] --> Java{Managed scip-java<br/>repair?}
Java -- Yes --> Override{Configured CLI binary?}
Override -- Yes --> Config[Use configured binary]
Override -- No --> RepairCached{Verified pinned<br/>cache?}
RepairCached -- Yes --> RepairUse[Use managed repair]
RepairCached -- No --> RepairDownload[Download and verify<br/>pinned payload]
RepairDownload --> RepairUse
Java -- No --> OnPath{Already on<br/>system PATH?}
OnPath -- Yes --> Use[Use existing binary]
OnPath -- No --> Cached{In local<br/>cache dir?}
Cached -- Yes --> Use
Cached -- No --> Method{Install method?}
Method -- GitHubBinary --> DL1[Download release asset]
Method -- GitHubGz --> DL2[Download + gunzip]
Method -- GitHubTarGz --> DL3[Download + untar]
Method -- GitHubZip --> DL4[Download + unzip]
Method -- GitHubLauncher --> DL5[Download Coursier<br/>launcher script]
Method -- Npm --> DL6[npm install into<br/>cache dir]
Method -- DotnetTool --> DL7[dotnet tool install]
Method -- Unsupported --> Fail[Error: manual install required]
DL1 --> Use
DL2 --> Use
DL3 --> Use
DL4 --> Use
DL5 --> Use
DL6 --> Use
DL7 --> Use
style Use fill:#0a0e1b,stroke:#00ffcc,color:#00ffcc
style RepairUse fill:#0a0e1b,stroke:#00ffcc,color:#00ffcc
style Config fill:#0a0e1b,stroke:#00ffcc,color:#00ffcc
style Fail fill:#0a0e1b,stroke:#ff3366,color:#ff3366
Both the CLI and GUI complete this "ensure installed" phase before launching
any indexer process, so a first run can resolve the latest compatible missing
indexer, download it, and still finish the requested index operation. Managed
installs record the installed version for later update checks. The GUI dashboard also exposes
Install/Uninstall actions for each registered indexer. Uninstall only removes
tools managed in SCIP-IO's local cache; binaries discovered on your system
PATH are reported as installed but must be removed outside SCIP-IO.
The CLI exposes the same lifecycle controls with scip-io install,
scip-io uninstall, and scip-io update; scip-io update checks installed
managed indexers, reports the results, and opens a terminal picker unless you
pass --lang or --all for non-interactive updates.
The Kotlin row is shown as covered by scip-java; installing or uninstalling
that row manages scip-java, which is the tool that provides Kotlin indexing.
The Settings update check inspects installed indexers, reports non-managed
tools separately, and offers per-indexer or bulk updates for managed installs
when a newer compatible version is available.
SCIP-IO also repairs managed scip-python npm installs affected by the
embedded Pyright wildcard-import assertion that can fail large Python indexes.
For the recognized 0.6.6 Pyright body, it also reuses completed specialization
checks within each traversal, keyed by type identity, depth and option flags.
This avoids repeatedly walking shared tuple/union subgraphs while preserving
recursion limits and discarding cached results before later type mutations.
For the pinned repaired bundle, solved-TypeVar and unique-function-signature
traversals also skip graphs proven inert for their respective transformation. Each call rebuilds that bounded proof; aliases,
defaults, functions, cycles, and uncertain cases retain Pyright's traversal.
Type equality comparisons reuse completed results only within one synchronous call,
keyed by ordered type identities, all comparison options, and exact recursion depth.
Cache collections are allocated only after a comparison exceeds 32 non-identity calls.
The bounded cache is discarded on return or exception; later mutations are rechecked.
Union construction also skips a duplicate equality check after that pair has already
failed comparison, while preserving literal erasure and union-collapse behavior.
Completed flow-reachability results are also reused within one synchronous call,
with an 8,192-entry cap. Active recursion, provisional callback results, mutable
finally gates, and depth cutoffs cannot populate the cache; callback finalization
invalidates prior entries. Reuse relies on the pinned emitter's binder-owned flow
structure and is not a general cache for arbitrary mutating evaluator callbacks.
Nonconverging loop antecedents stop after 256 evaluations, retaining accumulated types.
Callback-free class/tuple/union rendering uses a 4,096-visit expansion budget per
root type print; omitted expansions display ... (Any in Python-syntax mode).
This limits pathological diagnostic and inferred-return documentation expansion.
Small outputs and callback-bearing graphs retain their original behavior. The
precheck is bounded to 4,096 nodes and 16,384 edges; unsupported graphs fall back.
These are rendering-work limits, not a universal output-length limit.
For recognized scip-python bundles, SCIP-IO patches the actual dist/vendor.js subprocess helper through its known wrapper, setting unused stdin to ignore while retaining captured stdout and stderr. This prevents a sandbox stdin denial from producing a misleading exit-zero result with complete stdout. A missing expected helper or ambiguous dependency fails before changing the bundle.
Unknown bundle versions are not patched.
It also repairs the 0.6.6 emitter's wildcard-import symbol cache: functions
and callable variables re-exported through from module import * retain their
exact declaration targets instead of pointing to the first function resolved
from that import. The callable-variable repair applies only when Pyright
resolves the exact nonlocal declaration; missing or conflicting ownership keeps
upstream behavior. Both fresh and existing managed installs receive the repair;
custom indexer binaries remain caller-managed.
Managed 0.6.6 installs also honor explicit Python file targets under hidden
directories and emit distinct slot attributes and module-level annotated variables.
They preserve class documentation and inheritance metadata when a same-file
forward reference resolves the class before its declaration is visited.
They also emit metadata for nonlocal annotated bindings, nested or starred
destructuring, loop targets, with aliases, exception aliases, assignment
expressions, and pattern captures. Generic binding metadata retains the emitter's
resolved identities and source ranges, even when hover documentation is unavailable.
Assignment-target repair also corrects builtin-shadowing identities: for example,
for int in [1] and its reads resolve to the bound variable instead of builtin
int, retaining their source ranges. Destructuring and loop targets named _
remain ignored, matching the emitter's name visitor. Local identifiers,
attribute targets, and subscript targets keep their existing behavior.
Proven nonlocal member declarations receive metadata in their defining document,
including when a cross-file reference caches the member identity first. Ordinary
attribute reads and unrelated writes do not gain definition occurrences.
Parser recovery can add identity-only metadata for a standalone starred
definition, such as *FIRST = [1, 2, 3], only when one existing raw definition
occurrence has the same exact span and symbol. It neither infers a type nor
treats the source as valid Python, and leaves raw occurrences unchanged.
Conflicting, read-only, local, or nonmatching recovered nodes remain unresolved.
Explicit Python file selections prune only emitter shard entrypoints. The runner
reapplies that boundary to saved hints and out-of-memory retry splits, keeps the
original project and dependency-analysis context, preserves source paths and
conservative scheduling counts, and errors when no target matches. It does not
ignore hidden directories: an explicitly selected hidden .py or .pyi source
remains an entrypoint.
Managed Python indexing also avoids hover-derived external metadata on bare
stdlib module-container identities, where recursive module or assignment nodes
could otherwise borrow member documentation. Exact leaf symbols and explicit
module-initializer identities retain documentation; raw occurrences remain
unchanged.
Python .pyi stubs are accepted in explicit file manifests and detected as Python.
On Windows, it additionally repairs the upstream path.sep regex crash before
running the indexer.
To check the Python emitter against a real npm package, install
@sourcegraph/scip-python@0.6.6 into a disposable npm prefix, set
SCIP_IO_TEST_PYTHON_NPM_PREFIX to its absolute path, and run
cargo test -p scip-io-core repaired_python_ -- --ignored.
This check requires Node.js and Python, repairs that disposable package, and
verifies raw SCIP identities for re-exports, aliases, repeated calls, inherited
slots, annotated variables, an explicit file under a hidden directory, and
metadata for a forward-referenced class. Binding coverage compares raw occurrences
before and after repair, checks metadata uniqueness and missing-documentation
behavior, verifies wildcard function and callable-variable identities, and
exercises identity-only metadata for an exact recovered standalone-star definition.
Rejection controls keep missing, conflicting, local, or nondefinition recovery
nodes from gaining metadata. Shard-selection coverage keeps explicit hidden
sources while pruning unselected entrypoints, including saved-hint and retry
subdivisions, without removing dependency context. Stdlib metadata coverage keeps
exact leaves and module initializers while removing member hover metadata only from
bare module containers and preserving raw occurrences.
The shard regression compares a whole-project control with multiple root-level
and package-level loose files beside a directory shard. It checks exact document
paths, package identities, cross-file references, and preservation of the previous
output when the real emitter fails. Run it alone with
cargo test -p scip-io-core repaired_python_shards_retain_original_source_paths -- --ignored.
CI runs this regression on Linux, macOS, and Windows with a disposable emitter.
Upstream scip-ruby and scip-clang do not publish compatible native Windows
release assets. SCIP-IO keeps those native installs marked unsupported on
Windows, but scip-io index can run the upstream Linux binaries through WSL or
Docker and still publish normal ruby.scip or cpp.scip files through the same
temp-output, compaction, validation, and atomic publish path.
The automatic backend preference is WSL first, then Docker. You can pin a
backend in .scip-io.toml:
[indexer.scip-ruby]
backend = "wsl"
wsl_distro = "Ubuntu-24.04"
[indexer.scip-clang]
backend = "docker"
docker_image = "my/scip-clang-runtime:latest"When no Docker image is configured, SCIP-IO uses ubuntu:24.04 so upstream
Linux indexer binaries have a recent enough glibc. Override docker_image when
your project needs additional build tools, headers, or package managers inside
the container.
When wsl_distro is set, status probes, path translation, binary permission
fixups, and indexer execution all run against that distribution instead of the
default WSL distro. WSL is reported as available only when SCIP-IO can run a
command inside the selected/default distro, so an installed WSL executable
without a registered runnable distro is not treated as ready. Invalid
.scip-io.toml now fails status/indexing commands with a config error rather
than silently falling back to defaults.
Ruby app projects do not always include a .gemspec. For those projects,
SCIP-IO supplies deterministic local --gem-metadata <project>@0.0.0 when
running scip-ruby, so application repositories can produce ruby.scip
without pretending to be packaged gems.
C/C++ Linux backends require a Linux-compatible compile_commands.json.
SCIP-IO rejects Visual Studio or drive-letter compile commands such as
C:\..., cl.exe, or clang-cl.exe before invoking scip-clang; generate the
compile database inside WSL/Docker or use a matching Docker image/toolchain.
Every language indexer now runs through the same protected core runner used by
both the CLI and GUI. SCIP-IO writes each indexer result to a temporary file
first, normalizes document languages and repo-relative paths, compacts duplicate
documents, occurrences, and document symbols, validates the normalized SCIP, and
only then publishes <language>.scip. Failed or cancelled processes are killed
with the parent task, stdout/stderr are captured for diagnostics, and stale
successful outputs are left in place instead of being replaced by partial data.
When at least one indexer succeeds and another fails, the final output uses only
the successful .scip files and is reported as partial. CLI JSON includes
partial, successful_outputs, and failed_languages; the GUI completion
screen shows the same status plus per-language failure details.
Shard execution is capability-gated by upstream indexer contracts:
- Python uses
scip-python --target-onlyshards for large trees. Hidden Python files under scan-scope dot paths are added as explicit file targets even when the rest of the Python tree fits in one shard, becausescip-pythoncan skip hidden paths during a root scan. - TypeScript/JavaScript and C# keep the fast single invocation by default, then
retry memory failures with safe
tsconfig*,.sln,.csproj, or.vbprojproject-argument shards. Very large discovered config lists shard up front to avoid Windows command-line length limits. - C/C++ can split large
compile_commands.jsonfiles into command chunks. With additional configs enabled, SCIP-IO first merges validated root/build compile databases, removes duplicatefile + command/argumentsentries, and then chunks the merged command set. - Go, Rust, Java, Scala, and Kotlin are planned around module/build roots only when the upstream indexer can safely honor that boundary; otherwise they keep the protected single-run behavior.
- Ruby currently uses protected single-run behavior because
scip-rubydoes not expose a safe shard boundary in SCIP-IO.
scip-python runs on Node.js and can exceed the default V8 heap on large
repositories. SCIP-IO now handles that automatically: when a Python project has
more than 750 .py, .pyi, or .pyw files, it runs scip-python over bounded
--target-only shards, prefixes shard-relative document paths back to the
repository root, and merges the shard outputs into the same python.scip
callers already expect. Initial shard targets may contain up to 5,000 Python
files to avoid excessive process startup overhead. Small shards run with
conservative bounded parallelism, while large shards run alone to keep peak
memory predictable. Loose files inside oversized directories run as explicit
targets at their original source paths, retaining the original project working
directory and package identity. This costs one emitter startup per loose file;
staging batches outside the project can silently omit those files. Python files
under dot-path directories such as .ci/ and .github/ also use explicit targets
so upstream directory scans do not silently skip repository-scoped files. If a
large shard still hits a heap limit, SCIP-IO recursively splits it, records that
target as a local hint, and pre-splits the same target on later runs instead of
repeating the expensive failed attempt. Shard timing is logged so large-project
runs expose which targets dominate wall time. If scip-python emits an empty
document path for a single-file shard, SCIP-IO rewrites it to that shard's
repo-relative file path before merging so unrelated file facts cannot collapse
into one empty document.
Imported document paths containing .. are resolved against the original shard
target before merging, so references across shards retain their source paths.
A terminal shard failure still fails Python indexing instead of leaving a
misleading partial python.scip. Setting
NODE_OPTIONS=--max-old-space-size=... is no longer the recommended path for
large Python indexes; the bounded shard runner keeps peak memory lower for
machines with less RAM.
Merging is deterministic: same inputs always produce a byte-identical
index.scip, regardless of which order indexers finished. Overlapping
documents (e.g. generated files touched by more than one indexer) are combined
rather than duplicated. SCIP-IO also compacts every indexer output and final
merged or copied output so duplicate Document.relative_path entries, duplicate
occurrences, and duplicate document symbols do not reach downstream consumers.
When scip-io index merges multiple language outputs, the merged
Metadata.project_root is the selected repository root because document paths
have already been normalized relative to that root. Manual merges keep input
Metadata.project_root and Metadata.text_document_encoding only when the
non-empty/specified inputs agree. Conflicts are logged and left
empty/unspecified so the merged artifact does not claim a root or encoding that
is false for part of the index. The merged tool_info still records scip-io
as the producer of the combined artifact.
If a run is partial, the merge uses the successful language outputs and reports
the failed languages separately.
flowchart LR
T[typescript.scip] --> M{{merge_scip_files}}
P[python.scip] --> M
R[rust.scip] --> M
G[go.scip] --> M
D[….scip] --> M
M --> O[(index.scip)]
O --> V[Validate<br/>documents, symbols,<br/>occurrences, languages]
style M fill:#1e2336,stroke:#8b5cf6,color:#fff
style O fill:#0a0e1b,stroke:#ff00aa,color:#ff00aa
style V fill:#1e2336,stroke:#00ffcc,color:#fff
Linux / macOS:
curl -LsSf https://github.com/GlitterKill/scip-io/releases/latest/download/install.sh | shWindows (PowerShell):
irm https://github.com/GlitterKill/scip-io/releases/latest/download/install.ps1 | iexBoth scripts download the right binary for your OS/architecture, extract it to a user-writable location (~/.local/bin or %LOCALAPPDATA%\scip-io\bin), and update your PATH.
Grab the right file for your platform from the latest release:
| Platform | File |
|---|---|
| CLI — Windows x64 | scip-io-vX.Y.Z-x86_64-pc-windows-msvc.zip |
| CLI — macOS x64 (Intel) | scip-io-vX.Y.Z-x86_64-apple-darwin.tar.gz |
| CLI — macOS ARM (Silicon) | scip-io-vX.Y.Z-aarch64-apple-darwin.tar.gz |
| CLI — Linux x64 | scip-io-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz |
| GUI — Windows installer | SCIP-IO_X.Y.Z_x64-setup.exe or SCIP-IO_X.Y.Z_x64_en-US.msi |
| GUI — macOS disk image | SCIP-IO_X.Y.Z_x64.dmg / SCIP-IO_X.Y.Z_aarch64.dmg |
| GUI — Linux package | scip-io_X.Y.Z_amd64.deb or scip-io_X.Y.Z_amd64.AppImage |
Verify your download with SHA256SUMS.txt attached to the release.
Heads up — unsigned binaries: Until SCIP-IO is code-signed, Windows SmartScreen will warn "unrecognized app" (click More info → Run anyway) and macOS Gatekeeper will block first launch (right-click the app → Open, then confirm).
Requires Rust stable 1.80+ (rustup.rs). For the GUI you also need the Tauri 2 prerequisites (WebView2 on Windows, webkit2gtk on Linux).
git clone https://github.com/GlitterKill/scip-io.git
cd scip-io
# CLI only
cargo build --release -p scip-io-cli
./target/release/scip-io --help
# GUI
cargo install tauri-cli --version "^2.0"
cargo tauri buildThe CLI binary is scip-io; the GUI bundles as SCIP-IO via Tauri.
SCIP-IO downloads and manages indexer binaries, but some indexers need a language runtime installed on your system:
- Node.js — for
scip-typescriptandscip-python(both npm packages) - Go — for
scip-go - JDK/JVM — for
scip-java(Java, Scala, and Kotlin indexing) - .NET SDK — for
scip-dotnet(C# indexing)
SCIP-IO does not modify your persistent user or system PATH. For native
scip-go and scip-java runs, it probes project config, current environment,
PATH, and common install locations, then injects the discovered bin directory
into only the child indexer process. JAVA_HOME is injected only when SCIP-IO
has a validated Java home; PATH shims such as java.exe without a real JDK
home are used as executables without guessing JAVA_HOME. If auto-discovery
misses a valid install, set project-local homes in .scip-io.toml:
[toolchains.go]
home = "C:\\Program Files\\Go"
[toolchains.java]
home = "C:\\Program Files\\Eclipse Adoptium\\jdk-21"Other indexers (rust-analyzer and the non-Windows builds of
scip-ruby/scip-clang) ship as standalone binaries. On Windows,
scip-ruby and scip-clang require WSL or Docker because upstream does not
publish native Windows assets. Run scip-io status --verbose to see native,
backend, and toolchain readiness.
cd your/polyglot/project
scip-io detect # see what SCIP-IO found
scip-io index # download indexers, run them, merge → index.scip
scip-io validate index.scipThat's it. Point any SCIP-consuming tool at index.scip.
All commands accept --help. The examples below assume you're running them from inside the project you want to index.
Scan a project for supported languages without touching any indexers.
scip-io detect
scip-io detect --path ./services/api
scip-io detect --format json
scip-io detect --depth 5Example output:
v Detected: rust, typescript, javascript
* rust (found Cargo.toml; project_config; ready)
* typescript (found gui/tsconfig.json; project_config; ready)
* javascript (found gui/package.json; project_config; ready)
Options:
| Flag | Description |
|---|---|
-p, --path |
Project root (defaults to current directory) |
-f, --format |
text (default) or json |
-d, --depth |
Optional max directory depth to scan |
The main event: detect, install, run, merge, and write a single index.scip.
# Index everything in the current directory
scip-io index
# Only index specific languages
scip-io index --lang rust,typescript
# Custom merged output and isolated per-task language outputs
scip-io index --output ./artifacts/project.scip --intermediate-dir ./artifacts/run-001
# Dry run — see what would happen, don't touch anything
scip-io index --dry-run
# Use legacy config-root scheduling for nested project configs
scip-io index --scope configs
# Discover only config-bearing roots under the project
scip-io index --all-roots
# Include supported secondary config files such as tsconfig.test.json
scip-io index --include-additional-configs
# Index only explicit source files under the repo
scip-io index --files src/main.rs,tools/check.py
scip-io index --files-from changed-files.txt
# Index specific sub-project roots
scip-io index --roots ./services/api,./services/web
scip-io index --path ./repo --roots ./services/api,./services/web
# Keep the per-language .scip files instead of merging
scip-io index --no-merge
# Control concurrency and timeouts
scip-io index --parallel 4 --timeout 600Options:
| Flag | Description |
|---|---|
-p, --path |
Project root |
-l, --lang |
Comma-separated language filter (case-insensitive) |
--files |
Comma-separated explicit source files to index; relative paths resolve from --path |
--files-from |
Read explicit source files from a newline-delimited list file |
-o, --output |
Merged output path (default index.scip) |
--intermediate-dir |
Directory for per-task language outputs; defaults to each project root |
--no-merge |
Keep individual .scip files, skip the merge step |
--parallel |
Max concurrent indexer processes |
--timeout |
Per-indexer timeout in seconds |
-f, --format |
text or json progress output |
--dry-run |
Print the plan without running anything |
--roots |
Comma-separated sub-project roots to index; relative paths resolve from --path |
--scope |
repo-tree (default) or configs root scheduling |
--all-roots |
Compatibility alias for config-root discovery under --path, skipping ignored dirs |
--include-additional-configs |
Include supported secondary config files in each indexer run |
index --intermediate-dir places per-language publications and their atomic sidecars in distinct task-N subdirectories, preserving source roots and document identities. Use a unique directory per concurrent invocation; outputs remain there in both merged and --no-merge modes. This does not relocate upstream-generated project configuration or the source-root default-output backup/restore path.
scip-io index defaults to repo-tree scope, so the selected --path is the
single ownership boundary for the index. Some upstream indexers still use their
own config/build model internally; SCIP-IO reports readiness warnings when that
means a repo-tree run may be partial. Use --scope configs for the previous
config-root scheduling method: nested project configs are indexed from their own
roots, SCIP document paths are prefixed before merging, and child-owned output
is pruned from parent runs. Use --roots when you want an explicit subset. Use
--all-roots when you specifically want the older discovered-config-root sweep.
--roots, --scope configs, and --all-roots merge generated .scip files
into the requested output unless --no-merge is set.
Use --files or --files-from for a targeted index over known source files,
for example from a changed-files list. Listed files can be repo-relative or
absolute, must exist under --path, and must use a supported source extension.
--files-from accepts one file per line and ignores blank lines plus lines that
start with #. Explicit file lists restrict language planning and final SCIP
documents to the selected files. For C/C++, selected files must also appear in a
compile database entry; raw .cc/.cpp paths without compile commands are not
enough for scip-clang.
By default, SCIP-IO indexes the primary project config for a root. Use
--include-additional-configs when a repository keeps support scripts, test
targets, or secondary build surfaces in additional config files. For TypeScript,
SCIP-IO passes root-level tsconfig.json and tsconfig.*.json files to
scip-typescript in a single invocation, with tsconfig.json first. If that
combined run fails with a memory signature, SCIP-IO retries those configs as
sequential project-argument shards and merges the results. For .NET, SCIP-IO
passes all discovered .sln, .csproj, and .vbproj files to scip-dotnet
and uses the same safe retry behavior for memory failures. For C/C++, SCIP-IO
discovers root-level and build-output compile_commands.json files, skips
invalid, empty, fixture-like, or non-C/C++ databases, merges duplicate compile
commands, and reports selected database, skipped database, duplicate command,
unique file, and net-new-file counts in dry-run output. This can increase real
semantic coverage only when the extra databases contain compile commands that
the primary database does not already cover.
Large C/C++ repositories can produce many overlapping compile databases. Use
[cpp.coverage] to curate them before a full index run: include and
exclude match repo-relative compile_commands.json paths, and
min_new_files skips additional databases that do not add enough new source
files beyond databases already selected. The first selected database is kept as
the primary baseline; the threshold applies to later databases. Dry-run JSON
includes a compile_database_summary.databases entry for each discovered
database with selected/skipped state, skip reason, input command count,
duplicate command count, unique file count, and new unique file gain. If the
coverage profile filters out every discovered C/C++ compile database, SCIP-IO
treats that as a configuration error so a typo in include or exclude cannot
fall back to root-only compile_commands.json indexing.
For CMake projects, SCIP-IO can also generate build-output compile databases
before discovery. This is configure-only: it runs cmake -S ... -B ... with
CMAKE_EXPORT_COMPILE_COMMANDS=ON and never runs cmake --build. Existing
generated databases are reused unless refresh = true is set. For LLVM-style
monorepos, the built-in llvm-broad preset creates three build directories for
all LLVM targets, broad LLVM projects, and runtimes, then the normal C/C++
additional-config merge path deduplicates their compile commands.
On Windows, if scip-clang is configured for WSL or would auto-select WSL,
SCIP-IO runs the CMake configure commands through WSL as well so the generated
database contains Linux paths. Docker-backed CMake generation is not supported
yet; generate those databases inside the container or use WSL/native generation.
TypeScript and JavaScript share scip-typescript, but SCIP-IO keeps them as
separate language runs when both are detected. The TypeScript run uses the
normal TypeScript project config behavior. The JavaScript run creates a
temporary root-local project config with allowJs and JavaScript-only globs
(*.js, *.jsx, *.mjs, *.cjs) so standalone JavaScript files are emitted
alongside TypeScript in the final merge instead of being hidden by an existing
tsconfig.json.
Show which indexers are installed and where.
scip-io status
scip-io status --verbose
scip-io status --format json
scip-io status --check-updates--verbose and --format json include toolchain preflight details for
indexers that need a runtime, including whether Go or Java was found, where it
was found, and which config or environment source supplied it.
Example output:
Indexer Status:
scip-typescript v0.4.0 ● installed (npm cache)
scip-python v0.6.6 ● installed (npm cache)
rust-analyzer 2026-03-30 ● installed (PATH)
scip-java v0.12.3 ○ not installed
scip-clang v0.4.0 ○ not installed
Install one indexer by language, indexer name, or binary name. Installs resolve the latest compatible live version at runtime.
scip-io install python
scip-io install --lang rust
scip-io install scip-typescriptCheck installed indexers for newer compatible versions. With no arguments, the command reports update status and opens a terminal menu with per-indexer choices and an Update All option when more than one update is available.
scip-io update # report updates, then choose from a terminal menu
scip-io update --lang rust # non-interactive update for one language/indexer
scip-io update scip-dotnet # non-interactive update by indexer name
scip-io update --all # non-interactive update for every available updateIndexers installed outside SCIP-IO's managed cache are reported, but they are not updated or removed by SCIP-IO.
Remove one SCIP-IO-managed indexer by language, indexer name, or binary name.
scip-io uninstall python
scip-io uninstall --lang kotlin # removes managed scip-java
scip-io uninstall scip-python --dry-runManually merge a set of .scip files into one. Useful for CI where per-language files are built in separate stages.
The merged artifact preserves input project root and text encoding metadata
when those values are unambiguous while recording scip-io as the merge tool.
scip-io merge ts.scip py.scip rust.scip --output all.scip
scip-io merge *.scip --output merged.scip --validateOptions:
| Flag | Description |
|---|---|
<inputs> |
One or more .scip files (required) |
-o, --output |
Output path (default index.scip) |
--validate |
Validate the merged output after writing |
Parse a .scip file, check structural invariants, warn on incomplete
document metadata such as missing languages, and print stats.
scip-io validate index.scip
scip-io validate index.scip --format jsonExample output:
index.scip is valid
documents: 1,247
symbols: 38,091
occurrences: 214,563
languages: rust, typescript, javascript
Remove cached indexer binaries. Prefer scip-io uninstall <target> for a single
indexer. clean only removes SCIP-IO-managed cache files; indexers discovered
on your system PATH are skipped.
scip-io clean # remove all installed managed indexers
scip-io clean --lang rust # only rust-analyzer
scip-io clean --all # wipe the whole cache dir
scip-io clean --dry-run # show what would be removedLaunch the Tauri GUI (when the GUI crate is built).
scip-io gui
scip-io gui --path ./my-projectThe GUI dashboard exposes the same targeting through the optional Files field. Enter repo-relative or absolute source files separated by new lines or commas before starting indexing.
Drop a .scip-io.toml at the root of any project to override defaults:
# .scip-io.toml
output = "artifacts/index.scip"
scope = "repo-tree" # or "configs"
include_additional_configs = true
languages = ["typescript", "python"]
[cpp.cmake]
generate_compile_databases = true
preset = "llvm-broad"
generator = "Ninja"
# Optional. Without build_root, the preset uses build-scip-io-* directories.
build_root = "out/scip-io-cmake"
[cpp.coverage]
# Optional path filters for discovered compile_commands.json files.
include = ["compile_commands.json", "build-*/compile_commands.json"]
exclude = ["build-old/**", "out/tmp/**"]
# Keep later databases only when they add meaningful new C/C++ file coverage.
min_new_files = 25
[settings]
parallel = 4
timeout = 600
[indexer.typescript]
binary = "/usr/local/bin/scip-typescript" # use a custom binary
[indexer.rust]
args = ["scip", ".", "--exclude", "vendor"]
[indexer."scip-java"]
# Override the complete indexer command when a JVM build needs custom Gradle,
# SBT, or Maven targets. This applies to Java, Scala, and Kotlin because all
# three are handled by scip-java.
args = ["index", "--output", "index.scip", "--targetroot", "/tmp/scip-java-semanticdb", "--", "--no-parallel", "clean", "scipPrintDependencies", "scipCompileAll"]Indexer override tables can be keyed by SCIP-IO language name
(typescript, python, kotlin) or by indexer name (scip-typescript,
scip-java). CLI and GUI indexing share the config loader and runner.
CLI native indexing honors binary before consulting the managed installation.
Relative binary paths resolve from the directory containing the loaded config;
a missing file is an error, without fallback to the installed indexer. Language
tables take precedence over indexer-name tables. This fixes the ignored override
in released 0.1.9. JSON and text dry-run commands show the configured executable
path as well; see the Windows Gradle investigation.
scip-io/
├── crates/
│ ├── scip-io-core/ ← library: detection, registry, install, run, merge, validate
│ └── scip-io-cli/ ← scip-io binary (clap)
├── src-tauri/ ← Tauri v2 GUI backend
├── gui/ ← Vite + TypeScript frontend
├── docs/assets/ ← README images, diagrams
└── Cargo.toml ← workspace root
Issues and PRs welcome. Before sending a PR:
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo testThe CI GitHub Actions workflow runs these same checks on Linux, macOS, and Windows for every PR.
Releases are fully automated: pushing a vX.Y.Z git tag triggers .github/workflows/release.yml, which cross-compiles the CLI for Windows, macOS (Intel + Apple Silicon), and Linux, builds the Tauri GUI installers for all three OSes, and publishes everything to a GitHub Release with SHA-256 checksums and install scripts.
See RELEASING.md for the full release checklist and CHANGELOG.md for the version history.
