Skip to main content

Salak

Deterministic, provenance-tagged code knowledge graph generator.

Salak parses a source repository and emits a single JSON file — repo-graph.json — describing its structure and the relations between its symbols, so an AI coding assistant can answer "what depends on this module?" from a recorded fact instead of a guess.

The graph is a build artifact, not a database. It is regenerated from source, committed to git, and read by tools. Salak owns the generation and nothing else.

Package name

Published on PyPI as salak. The name salak is taken on npm; if a JavaScript distribution is ever needed it ships as salak-graph (decision D5).

Status

0.1.0, shipped 2026-08-26 — ship verdict at the v1.0 exit gate. Every phase gate (TASK-039, TASK-062, TASK-083, TASK-098) returned ship. salak scan, salak check, salak diff, salak validate, salak doctor and salak version all work today, verified against the published PyPI package on real repositories, not just this checkout. 0.1.0 rather than 1.0.0 on purpose (decision D55) — the WBS's internal "v1.0" phase label was never a semver stability promise.

Currently published on PyPI: 0.3.0 (2026-09-26) — the Go adapter (v1.1), salak init, salak query, JavaScript and .vue/.svelte/.astro files, multi-module Go; release notes on the GitHub v0.3.0 release. Rehearsed as 0.3.0rc1 on TestPyPI and verified from a clean install before publishing.

0.2.0 (2026-08-26, TASK-106/D58 onward — adapters[].emits coverage declaration, class-level attributes as variable nodes, schema 1.1, the salak check exit-code/message fixes S20/S49). Not a WBS phase release — the phase table below tracks the four gated deliverables above, and this bump carries none of their own DoD; it exists because the fixes and features landed and were adopted, not because a phase closed. Don't read "0.2.0" as "the WBS's v0.2 phase, re-shipped" — that phase's own work shipped inside 0.1.0 already, per D55.

Phase Deliverable State
Spike 0 Feasibility and readiness done
v0.1 Structure pass: files/nodes/defines/imports/depends_on, published JSON Schema, salak validate shipped
v0.2 Resolution pass (§4.1 Levels 1–2), extends/implements/instantiates/references, full provenance shipped
v0.3 Incremental scan (the run_incremental library entry point), salak check, salak diff shipped
v1.0 Packaging, salak doctor, --stdout, docs, clean-machine install proof shipped
v1.1 Go adapter: structure, go.mod-driven imports, gopls-backed calls shipped in 0.3.0 (exit gate TASK-117, 2026-09-25)

Also in 0.3.0, outside any WBS phase: .vue, .svelte and .astro component files are scanned by the TypeScript adapter through their <script> blocks (and Astro frontmatter) — decision D84. They keep language: "typescript"; markup is not read, so a component used only in a template is represented by the import that brought it in. JavaScript (.js/.jsx/.mjs/.cjs, D86) — previously never scanned at all — is read by the same adapter, also as language: "typescript". salak init (D83), salak query (D87) and a truthful scan.lsp (D82) ship in the same release.

No further phase is currently open. --update (incremental scanning via salak scan, using the run_incremental library entry point already proven correct at TASK-081/083) was wired in as its own decision, D63 (2026-08-27) — not a phase deliverable, the same way the 0.2.0 PyPI release above was a version bump rather than a phase.

Level 3 (method-call resolution) was decided void for TypeScript and Python at the v0.1 exit gate (K15, decision D43): it resolved only 23.1% of call sites against 95.4–99.6% for Levels 1–2, with no language server at all. Go is the exception (D74): gopls resolved 97.7% of Go method calls, so the Go adapter consults it and Go is the only language that emits calls edges. See "Degraded mode and resolution levels" below.

Design principles

  1. Deterministic over clever. Same input, same bytes out. No LLM anywhere — the pipeline is offline, free and reproducible.
  2. Precision over recall. An extracted edge that is wrong is worse than a missing edge. When unsure, downgrade the provenance tag; never upgrade it.
  3. The schema is the contract. Consumers talk to the schema, never to Salak internals. A formal JSON Schema ships with the package and is authoritative over the prose (SDD §3.7).
  4. One language, one adapter. Adding a language must not modify core; adapters register through an entry-point group (decision D3), not an import in core.
  5. A graph declares its own scope. Reading the file's scan block tells you which languages ran, whether anything failed, and what the run did not attempt.

Requirements

  • Python 3.11+
  • Node.js — optional. pyright and typescript-language-server are the two language servers Salak knows how to detect, but method-call resolution (Level 3) is void (K15/D43), and Levels 1–2 resolve from tree-sitter and filesystem heuristics alone — no module under salak.resolution imports salak.lsp. The practical result: today, repo-graph.json comes out byte-for-byte the same with or without Node installed. salak doctor still reports Node/server presence and version drift as information (decision D45 — a missing server is never treated as unhealthy), because that detection work is what a future Level-3 revival would need, not because anything currently emitted depends on it.
  • TypeScript ≤ 5.9.x as the tsserver host, only relevant if you install the language servers for salak doctor to report on. TypeScript 7 ships no lib/tsserver.js, and typescript-language-server works by spawning tsserver, so the latest dist-tag is broken for this purpose (decision D15).
  • Go projects only (v1.1): the go toolchain and gopls — both optional, both change the output. Unlike the two servers above, these do matter. Without go, standard-library imports cannot be told apart from third-party ones (GO_TOOLCHAIN_UNAVAILABLE); without gopls, cross-file Go calls edges are recorded as unresolved (LSP_UNAVAILABLE). Install with go install golang.org/x/tools/gopls@latest. Each cross-file call site costs one gopls question (~0.13 s); a very large Go tree is better served by --update after the first scan, or --no-lsp (see docs/troubleshooting.md).

Installing

From PyPI (recommended for using Salak on your own repositories):

uv tool install salak

(No uv? pip install salak works the same way, into whatever environment pip targets.) That's it for TypeScript and Python — no Node, no language servers required to run salak scan and get a full graph. Go repositories also want go and gopls on PATH (see Requirements). This puts a plain salak command on your PATH; verify with:

salak version

If you also want salak doctor to report on the language servers (informational only — see Requirements below; installing them changes nothing about what scan emits), install Node.js and then, globally or in whatever project you're scanning:

npm i -g pyright@1.1.411 typescript-language-server@5.3.0 typescript@5.9.3

From source (for contributing to Salak itself):

git clone https://github.com/edhoferdian/salak
cd salak
uv sync

uv run salak ... now works from inside the checkout. uv tool install --editable . puts an editable salak on your PATH instead of prefixing every call with uv run. npm ci (from this checkout) installs the exact pinned language-server versions the test suite spawns — only needed for Salak's own development, not for using it.

Quickstart

Three commands, from install to a generated graph, run against any repository on your machine:

uv tool install salak
salak scan /path/to/your/repo
salak validate /path/to/your/repo/project-memory/repo-graph.json

scan writes the graph; validate confirms it matches its own declared schema version. salak init /path/to/your/repo then writes a block into that repository's CLAUDE.md telling AI assistants how to consult the graph — generated from the graph itself, and refreshed in place on re-run (--file AGENTS.md for another file). salak query importers src/lib/x.ts (also imports, symbols, callers) answers one question from the graph without anyone reading the whole file (D87). salak check /path/to/your/repo confirms the graph is still fresh against the working tree (it will be, immediately after a scan) — useful as a pre-commit hook or CI step once the graph is committed alongside your source. Run against this repository itself, scan reports:

salak: wrote <repo>/project-memory/repo-graph.json
salak: 249 parsed, 2 failed, 0 skipped · 3849 nodes, 11856 edges · 2 error(s), 0 warning(s)

(Exact counts drift as the repository grows; the shape — most files parsed, two deliberately-broken fixtures failing, the run still completing — is what to expect.)

The two PARSE_FAILED errors above are two deliberately-broken fixture files under tests/fixtures/broken/ used to test the parser's recovery path — exit code 2 ("completed with diagnostics", not a crash) is expected there. On an ordinary repository, scan exits 0.

Default output path

salak scan PATH writes to PATH/project-memory/repo-graph.json unless --out overrides it. From SDD §5: "chosen so the artifact lands where dev-kickoff already keeps project memory." Overridable per run; --stdout skips the file entirely and writes the graph to stdout instead (for CI or piping into salak validate -).

Point --out outside the repository when scanning this repository, or the scan overwrites its own working tree — use an absolute path or a sibling directory that isn't reached through ..:

salak scan . --out /path/outside/this/repo/salak-graph.json

Degraded mode and resolution levels

A run with no language server available, or one whose version doesn't match the pin, is not an error — it's a supported, expected state (rule 6). health_check() never raises, and a missing or mismatched server is reported as an LSP_VERSION_MISMATCH warning or plain information in doctor, never as unavailable (decision D45).

Concretely, on this machine right now:

$ salak doctor
Node.js: found (...)
go: ok — tree-sitter-go grammar available; resolution is §4.1 Levels 1-2 from
  structure and imports alone, plus a `gopls`-backed Level 3 `calls` pass ...
python: ok — tree-sitter grammars available; resolution is §4.1 Levels 1-2 and needs
  no language server (K15/D43). pyright 1.1.411 ...
typescript: ok — tree-sitter grammars available; resolution is §4.1 Levels 1-2 and
  needs no language server (K15/D43). typescript-language-server 5.3.0 ...

Every adapter reports ok whether or not a language server is present (D45). For TypeScript and Python that is because neither adapter's resolution ever queries one: their edges come from tree-sitter structure and filesystem/alias/barrel heuristics alone (§4.1 Levels 1–2), tagged extracted only when the relation needs no further guessing (rule 1, rule 10). Go does query gopls, for calls it cannot resolve locally; without it the Go scan still completes, but those calls stay unresolved and the run carries an LSP_UNAVAILABLE warning. The scan.lsp field in the artifact records whether a server was actually consulted during the run (note N2, D82) — so it is true only for a run whose Go pass reached gopls.

A broken source file behaves the same way at the file level: a parse failure produces a PARSE_FAILED diagnostic and a thinner entry for that one file (no declarations, but its file node and any import edges above the syntax error still appear, D41/D42) — the rest of the scan completes and the graph is still written (rule 7).

Documentation

Document Role
docs/schema-guide.md How to read repo-graph.json as a consumer: node/edge kinds, provenance semantics.
docs/AI-CONSUMER-PLAYBOOK.md Task-oriented companion for an AI assistant consuming repo-graph.json — freshness check first, question-to-field map.
docs/cli-reference.md Every subcommand, flag, and exit code.
docs/troubleshooting.md What each diagnostic code means and how to act on it.
docs/adding-a-language.md Contributor guide for a new language adapter: what is reusable, what is not, the traps already found, and the gates.
docs/RELEASING.md TestPyPI rehearsal and real PyPI publish runbook (owner-only).

The project's internal specification, plan, and decision history are kept locally and are not part of this public repository.

Development

uv sync
uv run ruff check . && uv run mypy src tests && uv run pytest

mypy is given src tests explicitly — the test suite carries part of the contract (a Protocol-conformance check lives in a test file), so excluding tests would turn that guarantee into a comment.

Licence

MIT — see LICENSE.

Chosen 2026-07-29, closing gap-analysis item G2. MIT rather than Apache-2.0 because Salak's job is to unblock other projects: it is a build tool that emits a JSON file, its own §1 says it is "not meant to become a product", and the fewest possible conditions on reuse serves that. Apache-2.0's express patent grant is the one thing MIT lacks; it would be the better choice for something with patentable algorithms or corporate contributors, and Salak has neither. Reversible at no cost until the first PyPI upload, and effectively permanent after.

Release files for salak 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for salak 0.3.1
File Size Uploaded
salak-0.3.1.tar.gz 1.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for salak 0.3.1
File Interpreter ABI Platform
salak-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / salak-0.3.1.tar.gz

Download URL salak-0.3.1.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
486fe9b47d9de4b152a817c40546df12406665fab2de1986a06d470360288541
BLAKE2b-256 checksum
How to use checksums
1e5acd1ffa9a826fd37e4f7e18dfd5b5af62a6d1d00f1401cffe763237146041
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / salak-0.3.1-py3-none-any.whl

Download URL salak-0.3.1-py3-none-any.whl
Size 386.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df199731a8d044e7b38dd528257550f214086ff0c583001817c7226bd35aa616
BLAKE2b-256 checksum
How to use checksums
8c13a0eb4d63b0a3a5ce78b38a1ff66882d35b8ed2b5f4ffce4ff5f307f010f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page