Metadata-Version: 2.5
Name: salak
Version: 0.2.0
Summary: Deterministic, provenance-tagged code knowledge graph generator
Project-URL: Homepage, https://github.com/edhoferdian/salak
Author: Edho Ferdian
License-Expression: MIT
License-File: LICENSE
Keywords: knowledge-graph,lsp,static-analysis,tree-sitter
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: tree-sitter-language-pack<2,>=0.7
Requires-Dist: tree-sitter<0.27,>=0.23
Description-Content-Type: text/markdown

# 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`](https://pypi.org/project/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](project-memory/v1.0-exit-gate-2026-08-26.md).**
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.

| 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** |

No further phase is currently open. `--update` (incremental scanning via the CLI,
rather than the `run_incremental` library entry point) has no flag yet — the library
is proven correct and ships in the package, but nothing wires it to `salak scan`.

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

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).

## Installing

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

```bash
uv tool install salak
```

(No `uv`? `pip install salak` works the same way, into whatever environment `pip`
targets.) That's it — no Node, no language servers required to run `salak scan` and
get a full graph. This puts a plain `salak` command on your `PATH`; verify with:

```bash
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:

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

**From source (for contributing to Salak itself):**

```bash
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:

```bash
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 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: 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 `..`:

```bash
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 (...)
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/AI-CONSUMER-PLAYBOOK.md` | Task-oriented companion for an AI assistant consuming `repo-graph.json` — freshness check first, question-to-field map, copy-pasteable `CLAUDE.md`/`AGENTS.md` snippet. |
| `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

```bash
uv sync
```
```bash
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`](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.
