Codebase Indexer — Parse your source code into structured Markdown documentation using tree-sitter.
Sunset scans your project, extracts functions, types, imports, and docstrings, then generates a set of Markdown files with YAML frontmatter — ready for AI consumption, documentation, or codebase exploration.
- 🌳 Tree-sitter powered — Accurate parsing via concrete syntax trees (CST)
- 📝 Markdown output — YAML frontmatter + structured body per file
- 🔍 Summary & Full CST — Choose between function-level overview or full tree dump
- 📦 Multi-language — Go, JavaScript, TypeScript, Python out of the box
- ⚡ Incremental — SHA256 caching, only re-parse changed files (29x speedup)
- 🔗 Dependency graph — Import resolution with circular dependency detection
- 🚀 Parallel — Worker pool with configurable concurrency
The current public release is v1.0.0. Download available stable archives
and checksums.txt from the GitHub Releases
page; it is the authoritative record of public availability.
v1.0.1 is a draft-validation release and is not a public installation source.
| OS | Arch | Archive | Verified on |
|---|---|---|---|
| Linux | amd64 | sunset_1.0.1_linux_amd64.tar.gz |
ubuntu-24.04 |
| Linux | arm64 | sunset_1.0.1_linux_arm64.tar.gz |
ubuntu-24.04-arm |
| macOS | amd64 | sunset_1.0.1_darwin_amd64.tar.gz |
macos-15-intel |
| macOS | arm64 | sunset_1.0.1_darwin_arm64.tar.gz |
macos-15 |
| Windows | amd64 | sunset_1.0.1_windows_amd64.zip |
windows-2025 |
Targets are built and verified on the named GitHub Actions runner environments. No minimum OS or libc compatibility is claimed.
These maintainer snippets verify a staged v1.0.1 archive. They require
SUNSET_BASE_URL to point at a staging or loopback server and are not public
installation instructions.
SHA-256 detects corruption and byte mismatches but does NOT authenticate the publisher. Signing and attestations remain future work.
#!/usr/bin/env bash
# install-linux.sh — Verify a staged sunset v1.0.1 archive on Linux (amd64 or arm64).
#
# Downloads ONLY the target archive plus checksums.txt, verifies the matching
# SHA-256 entry, extracts the archive, and runs `sunset version`.
#
# v1.0.1 is a draft-validation artifact, not a public installation source.
# Set SUNSET_BASE_URL to the staging or loopback server:
#
# SUNSET_BASE_URL=http://127.0.0.1:8080 ./install-linux.sh [amd64|arm64]
#
# SHA-256 detects corruption and byte mismatches. It does NOT authenticate
# the publisher; signing and attestations remain future work.
set -euo pipefail
VERSION="1.0.1"
BASE_URL="${SUNSET_BASE_URL:?SUNSET_BASE_URL is required for draft verification}"
ARCH="${1:-$(uname -m)}"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
amd64|arm64) ;;
*)
echo "install-linux: unsupported arch '$ARCH' (expected amd64 or arm64)" >&2
exit 2
;;
esac
ARCHIVE="sunset_${VERSION}_linux_${ARCH}.tar.gz"
INSTALL_DIR="${SUNSET_INSTALL_DIR:-/usr/local/bin}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
echo "==> Downloading $ARCHIVE and checksums.txt"
curl -fsSL -o "$WORK/$ARCHIVE" "${BASE_URL}/${ARCHIVE}"
curl -fsSL -o "$WORK/checksums.txt" "${BASE_URL}/checksums.txt"
echo "==> Verifying SHA-256 (selecting the $ARCHIVE entry)"
( cd "$WORK" && grep -F "$ARCHIVE" checksums.txt | sha256sum --check - )
echo "==> Extracting and installing to $INSTALL_DIR"
tar -xzf "$WORK/$ARCHIVE" -C "$WORK"
if [ -w "$INSTALL_DIR" ]; then
mv "$WORK/sunset" "$INSTALL_DIR/sunset"
else
sudo mv "$WORK/sunset" "$INSTALL_DIR/sunset"
fi
echo "==> Verifying install"
"$INSTALL_DIR/sunset" version#!/usr/bin/env bash
# install-macos.sh — Verify a staged sunset v1.0.1 archive on macOS (amd64 or arm64).
#
# Downloads ONLY the target archive plus checksums.txt, verifies the matching
# SHA-256 entry, extracts the archive, and runs `sunset version`.
#
# v1.0.1 is a draft-validation artifact, not a public installation source.
# Set SUNSET_BASE_URL to the staging or loopback server:
#
# SUNSET_BASE_URL=http://127.0.0.1:8080 ./install-macos.sh [amd64|arm64]
#
# SHA-256 detects corruption and byte mismatches. It does NOT authenticate
# the publisher; signing and attestations remain future work.
set -euo pipefail
VERSION="1.0.1"
BASE_URL="${SUNSET_BASE_URL:?SUNSET_BASE_URL is required for draft verification}"
ARCH="${1:-$(uname -m)}"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
arm64) ARCH="arm64" ;;
amd64|arm64) ;;
*)
echo "install-macos: unsupported arch '$ARCH' (expected amd64 or arm64)" >&2
exit 2
;;
esac
ARCHIVE="sunset_${VERSION}_darwin_${ARCH}.tar.gz"
INSTALL_DIR="${SUNSET_INSTALL_DIR:-/usr/local/bin}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
echo "==> Downloading $ARCHIVE and checksums.txt"
curl -fsSL -o "$WORK/$ARCHIVE" "${BASE_URL}/${ARCHIVE}"
curl -fsSL -o "$WORK/checksums.txt" "${BASE_URL}/checksums.txt"
echo "==> Verifying SHA-256 (selecting the $ARCHIVE entry)"
( cd "$WORK" && grep -F "$ARCHIVE" checksums.txt | shasum -a 256 -c - )
echo "==> Extracting and installing to $INSTALL_DIR"
tar -xzf "$WORK/$ARCHIVE" -C "$WORK"
if [ -w "$INSTALL_DIR" ]; then
mv "$WORK/sunset" "$INSTALL_DIR/sunset"
else
sudo mv "$WORK/sunset" "$INSTALL_DIR/sunset"
fi
echo "==> Verifying install"
"$INSTALL_DIR/sunset" version# install-windows.ps1 — Verify a staged sunset v1.0.1 archive on Windows (amd64).
#
# Downloads ONLY the target archive plus checksums.txt, verifies the matching
# SHA-256 digest, extracts the archive, and runs `sunset version`.
#
# v1.0.1 is a draft-validation artifact, not a public installation source.
# Set SUNSET_BASE_URL to the staging or loopback server:
#
# $env:SUNSET_BASE_URL = "http://127.0.0.1:8080"; .\install-windows.ps1
#
# SHA-256 detects corruption and byte mismatches. It does NOT authenticate
# the publisher; signing and attestations remain future work.
$ErrorActionPreference = "Stop"
$Version = "1.0.1"
$BaseUrl = $env:SUNSET_BASE_URL
if (-not $BaseUrl) { throw "SUNSET_BASE_URL is required for draft verification" }
$Arch = "amd64"
$Archive = "sunset_${Version}_windows_${Arch}.zip"
$InstallDir = if ($env:SUNSET_INSTALL_DIR) { $env:SUNSET_INSTALL_DIR } else { "$env:LOCALAPPDATA\Programs\sunset" }
$Work = Join-Path $env:TEMP "sunset-install-$([guid]::NewGuid())"
New-Item -ItemType Directory -Path $Work -Force | Out-Null
try {
Write-Host "==> Downloading $Archive and checksums.txt"
Invoke-WebRequest -Uri "$BaseUrl/$Archive" -OutFile "$Work\$Archive" -UseBasicParsing
Invoke-WebRequest -Uri "$BaseUrl/checksums.txt" -OutFile "$Work\checksums.txt" -UseBasicParsing
Write-Host "==> Verifying SHA-256 (selecting the $Archive entry)"
$line = Get-Content "$Work\checksums.txt" | Where-Object { $_ -like "*$Archive" }
if (-not $line) { throw "no checksum entry for $Archive in checksums.txt" }
$expected = ($line -split '\s+')[0].Trim()
$actual = (Get-FileHash -Algorithm SHA256 "$Work\$Archive").Hash
if ($actual.ToLower() -ne $expected.ToLower()) {
throw "checksum mismatch for $Archive (expected $expected, got $actual)"
}
Write-Host "==> Extracting and installing to $InstallDir"
Expand-Archive -Path "$Work\$Archive" -DestinationPath $Work -Force
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
Move-Item -Path "$Work\sunset.exe" -Destination "$InstallDir\sunset.exe" -Force
Write-Host "==> Verifying install"
& "$InstallDir\sunset.exe" version
}
finally {
Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue
}Clone the public repository and install from the checkout. This follows the main branch and is intended for development; use a published release for a stable installation.
git clone https://github.com/enolalabs/sunset.git
cd sunset
go install ./cmd/sunset
sunset versionImport the library:
import "github.com/enolalabs/sunset/pkg/sunset"git clone https://github.com/enolalabs/sunset.git
cd sunset
make build
# Binary: bin/sunset# Parse current directory
sunset parse .
# Parse a specific project
sunset parse /path/to/project
# Full CST output
sunset parse . --detail full
# Exclude test files
sunset parse . --exclude "*_test.go,*.test.ts"
# Incremental update (only changed files)
sunset update
# List supported languages
sunset languages
# Clean generated files
sunset cleanyour-project/
└── .sunset/
├── output/
│ ├── index.md # Project overview
│ └── files/
│ ├── main.go.md # Per-file documentation
│ ├── handler_user.go.md
│ └── utils_helper.py.md
└── cache/
└── cache.json # File hashes for incremental updates
---
file: main.go
language: go
package: main
lines: 28
function_count: 2
type_count: 0
import_count: 3
tags:
- has-functions
- has-imports
---## Functions
### main
- **Signature**: `func main()`
- **Line**: 15-19
- **Doc**: main starts the HTTP server and registers routes.
### setupRouter
- **Signature**: `func setupRouter() *http.ServeMux`
- **Line**: 23-27
- **Doc**: setupRouter creates and configures the HTTP router.
## Imports
| Import | Line |
|---|---|
| fmt | 5 |
| net/http | 6 || Command | Description |
|---|---|
sunset parse <path> |
Parse files and generate Markdown |
sunset update [path] |
Incremental update (re-parse changed only) |
sunset languages |
List supported languages |
sunset version |
Show version |
sunset clean [path] |
Remove cache and output |
| Flag | Default | Description |
|---|---|---|
--output |
<path>/.sunset/output |
Output directory |
--detail |
summary |
summary or full (full CST) |
--exclude |
— | Comma-separated glob patterns |
--concurrency |
NumCPU | Max parallel parsers |
--max-depth |
0 (unlimited) | Tree depth limit for full mode |
--no-cache |
false | Force full re-parse |
--quiet |
false | Suppress non-error output |
| Language | Extensions | Docstring Format |
|---|---|---|
| Go | .go |
// Comment above declarations |
| JavaScript | .js, .jsx |
/** JSDoc */ |
| TypeScript | .ts, .tsx |
/** JSDoc */ |
| Python | .py |
"""docstring""" inside body |
Adding a new language requires only one file — see CONTRIBUTING.md.
package main
import (
"fmt"
"github.com/enolalabs/sunset/pkg/sunset"
)
func main() {
result, err := sunset.ParseFile("main.go")
if err != nil {
panic(err)
}
defer result.Close()
root := result.Tree.RootNode()
fmt.Printf("Language: %s\n", result.Language)
fmt.Printf("Root: %s (%d children)\n", root.Type(), root.ChildCount())
// Walk the tree
result.Tree.Walk(func(node *sunset.Node, depth int) bool {
if node.Type() == "function_declaration" {
position := node.Position()
fmt.Printf("Function at %d:%d\n", position.Row, position.Column)
}
return true
})
}Tested on popular open-source repositories (single run, --no-cache, default concurrency)
| Repository | Language | Files | Functions | Types | Duration | Files/sec | Output |
|---|---|---|---|---|---|---|---|
| kubernetes/kubernetes | Go | 12,615 | 104,925 | 18,165 | 70.4s | 179 | 69 MB |
| golang/go | Go | 10,302 | 89,294 | 17,999 | 57.3s | 180 | 55 MB |
| microsoft/vscode | TypeScript | 10,002 | 31,640 | 16,404 | 59.1s | 169 | 44 MB |
| facebook/react | JS/TS | 4,345 | 12,885 | 535 | 9.3s | 467 | 18 MB |
| tensorflow/tensorflow | Python/Go | 3,194 | 64,695 | 6,864 | 125.9s | 25 | 25 MB |
| django/django | Python/JS | 2,945 | 31,928 | 10,951 | 6.2s | 475 | 15 MB |
Total: 43,403 files parsed · 335,367 functions extracted · 70,918 types · 226 MB of structured documentation
| Metric | Value |
|---|---|
| Full parse | ~20ms |
| Incremental (1 changed) | ~0.7ms (29x faster) |
| Memory per file | ~66KB |
| Walker: 5,800 nodes | ~1.1ms |
make test # Run all tests with race detector
make lint # Run golangci-lint
make bench # Run benchmarks
make test-coverage # Generate coverage report
make clean # Remove build artifactsMIT