Metadata-Version: 2.5
Name: salak
Version: 0.3.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 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 (`salak init`, D83, is newer and not yet
published). `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.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` | **exit gate passed** 2026-09-25 (TASK-117); not yet published to PyPI |

**Also unpublished, added 2026-09-25:** `.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. `salak init`
(D83) and a truthful `scan.lsp` (D82) are in the same unpublished set, as is JavaScript
(`.js`/`.jsx`/`.mjs`/`.cjs`, D86) — previously never scanned at all; now read by the same
adapter, also as `language: "typescript"`.

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

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

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

```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 (...)
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`](docs/schema-guide.md) | How to read `repo-graph.json` as a consumer: node/edge kinds, provenance semantics. |
| [`docs/AI-CONSUMER-PLAYBOOK.md`](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`](docs/cli-reference.md) | Every subcommand, flag, and exit code. |
| [`docs/troubleshooting.md`](docs/troubleshooting.md) | What each diagnostic code means and how to act on it. |
| [`docs/adding-a-language.md`](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`](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

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