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
Will publish 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
Pre-release — 0.1.0.dev0. Phases 1–3 are shipped: each phase gate (TASK-039,
TASK-062, TASK-083) returned ship, and salak scan, salak check, salak diff,
salak validate, salak doctor and salak version all work today, against real
repositories. Not yet on PyPI — publication is TASK-086/087, deliberately
deferred until the owner runs it with publish credentials. Install from source for
now (below).
| 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 |
in progress |
Level 3 (method-call resolution) was decided void at the v0.1 exit gate (K15, decision D43): it resolved only 23.1% of import edges against 95.4–99.6% for Levels 1–2, with no language server at all. See "Degraded mode and resolution levels" below.
Design principles
- Deterministic over clever. Same input, same bytes out. No LLM anywhere — the pipeline is offline, free and reproducible.
- Precision over recall. An
extractededge that is wrong is worse than a missing edge. When unsure, downgrade the provenance tag; never upgrade it. - 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).
- 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. - A graph declares its own scope. Reading the file's
scanblock tells you which languages ran, whether anything failed, and what the run did not attempt.
Requirements
- Python 3.11+
- Node.js — optional.
pyrightandtypescript-language-serverare 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 undersalak.resolutionimportssalak.lsp. The practical result: today,repo-graph.jsoncomes out byte-for-byte the same with or without Node installed.salak doctorstill 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 doctorto report on. TypeScript 7 ships nolib/tsserver.js, andtypescript-language-serverworks by spawning tsserver, so thelatestdist-tag is broken for this purpose (decision D15).
Installing from source
Not on PyPI yet, so install from a clone:
git clone https://github.com/edhoferdian/salak
cd salak
uv sync
That's it — no Node, no language servers required to run salak scan and get a full
graph. uv run salak ... now works from inside the salak checkout.
To get a plain salak command on your PATH instead of prefixing every call with
uv run from inside this checkout:
uv tool install --editable .
(Verified on this machine: uv tool install --editable . installs cleanly, and the
resulting salak version prints the same output as uv run salak version.)
If you also want salak doctor to report on the language servers (informational only
— see Requirements above), install Node.js, then from this checkout:
npm ci
Quickstart
Four commands, from install to a generated graph, run against any repository on your machine — not just this one:
uv sync
uv run salak scan /path/to/your/repo
uv run salak validate /path/to/your/repo/project-memory/repo-graph.json
uv run salak check /path/to/your/repo
scan writes the graph; validate confirms it matches its own declared schema
version; check confirms the graph is still fresh against the working tree (it will
be, immediately after a scan). Run against this repository itself, scan reports:
salak: wrote <repo>/project-memory/repo-graph.json
salak: 213 parsed, 2 failed, 0 skipped · 2824 nodes, 9524 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 ..:
uv run 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:
$ uv run salak doctor
Node.js: found (...)
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 ...
Both adapters report ok regardless of whether the language server was actually
consulted, because after K15/D43 neither adapter's resolution ever queries one — the
scan.lsp field in the artifact records whether a server was available to be
consulted, not whether an edge came from it (note N2). Every edge in repo-graph.json
today is confirmed by 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).
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/SALAK-SDD-v1_4.md |
Binding specification. §3 is a contract. |
docs/salak-wbs-v1_5.md |
Binding plan. Task list, dependency graph, sprints. |
docs/schema-guide.md |
How to read repo-graph.json as a consumer: node/edge kinds, provenance semantics. |
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/RELEASING.md |
TestPyPI rehearsal and real PyPI publish runbook (owner-only). |
project-memory/ |
Decision register, gap analysis, progress ledger, Spike 0 findings. |
CLAUDE.md · AGENTS.md · context-pack.md |
Context packs for AI assistants working in this repo. |
Earlier SDD (v1_3, v1_2, v1_1, v1.0) and WBS (v1_4, v1_3, v1_2, v1_1)
revisions are superseded archives. Do not implement from them.
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file salak-0.1.0.tar.gz.
File metadata
- Download URL: salak-0.1.0.tar.gz
- Upload date:
- Size: 1.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b8fc2ba685cb1f891ab885d1237647c66129eb0230e35c9f295710db16b8d7f
|
|
| MD5 |
3f2aebc7e33a074b3c9a5ec944a44a00
|
|
| BLAKE2b-256 |
08a3926a025829cabda6aaa7efec4d1a7c20f0a46a58fc21ccc7bf2a73e5fd2b
|
Provenance
The following attestation bundles were made for salak-0.1.0.tar.gz:
Publisher:
release.yml on edhoferdian/salak
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
salak-0.1.0.tar.gz -
Subject digest:
9b8fc2ba685cb1f891ab885d1237647c66129eb0230e35c9f295710db16b8d7f - Sigstore transparency entry: 2598407294
- Sigstore integration time:
-
Permalink:
edhoferdian/salak@89e97dfa467bb4833999a5cb528980bc06d0c658 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/edhoferdian
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89e97dfa467bb4833999a5cb528980bc06d0c658 -
Trigger Event:
push
-
Statement type:
File details
Details for the file salak-0.1.0-py3-none-any.whl.
File metadata
- Download URL: salak-0.1.0-py3-none-any.whl
- Upload date:
- Size: 298.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71402ff9bc507363676dd005bee0f42ff498ff9fff5f3ba5158b26f7afcc4050
|
|
| MD5 |
1e095021dc86c490a29ab4e6ff93abe0
|
|
| BLAKE2b-256 |
d9c9532b013dd5cb262249e35c657ec99cb501c9b49413834bdeb7ecaf67f3bc
|
Provenance
The following attestation bundles were made for salak-0.1.0-py3-none-any.whl:
Publisher:
release.yml on edhoferdian/salak
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
salak-0.1.0-py3-none-any.whl -
Subject digest:
71402ff9bc507363676dd005bee0f42ff498ff9fff5f3ba5158b26f7afcc4050 - Sigstore transparency entry: 2598407369
- Sigstore integration time:
-
Permalink:
edhoferdian/salak@89e97dfa467bb4833999a5cb528980bc06d0c658 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/edhoferdian
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89e97dfa467bb4833999a5cb528980bc06d0c658 -
Trigger Event:
push
-
Statement type: