Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
./target/release/stack check --help
./target/release/stack fmt --help
./target/release/stack render --help
./target/release/stack lsp --help
./target/release/stack icons --help
./target/release/stack icons list
./target/release/stack icons list aws s3
Expand All @@ -137,6 +138,7 @@ jobs:
test -s THIRD_PARTY_LICENSES.md
test -s CONTRIBUTING.md
test -s SECURITY.md
test -s docs/language-server.md
test -s docs/publication-audit.md
test -s docs/provider-icon-import.md
test -s docs/distribution.md
Expand All @@ -149,6 +151,7 @@ jobs:
test -s Cargo.toml
test -s Cargo.lock
test -s src/config.rs
test -s src/lsp.rs
test -s src/main.rs
test -s src/templates.rs
test -s src/provider.rs
Expand Down Expand Up @@ -186,6 +189,7 @@ jobs:
test -s templates/sources/01-minimal.stack
test -s tests/specification-revision
test -s tests/fixtures/render.stack
test -s tests/snapshots/lsp-help.txt

msrv:
name: Minimum supported Rust
Expand Down
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ serde = { version = "=1.0.229", features = ["derive"] }
serde_json = "=1.0.151"
serde_yaml_ng = "=0.10.0"
sha2 = "=0.11.0"
stack-engine = { git = "https://github.com/stack-sh/engine.git", rev = "2c9adabd2ffee627ac5fe3ee26331b0ad75bceac" }
stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820" }
stack-engine = { git = "https://github.com/stack-sh/engine.git", rev = "9af727aea79233b8389e0ed6fdbae7d3f388dc29" }
stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "7e208d6a3c90d255799f390a4e8b86248c73caee" }
ureq = { version = "=3.4.0", default-features = false, features = ["rustls"] }
zip = { version = "=6.0.0", default-features = false, features = ["deflate-flate2-zlib-rs"] }
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ stack fmt --check arch.stack
stack fmt -
stack render arch.stack
stack render arch.stack -o arch.svg
stack lsp
stack icons list
stack icons list aws s3
stack icons import gcp --accept-terms
Expand All @@ -55,6 +56,8 @@ stack render arch.stack -o arch.svg --notice arch.NOTICE.md

`stack render` uses the same engine pipeline to produce deterministic standalone SVG. Without `-o`, standard output contains only SVG. With `-o`, the output is written atomically in the destination directory. It discovers imported `aws`, `gcp`, `azure`, and `simple-icons` packs below the shared icon store. Use `--provider-pack <DIRECTORY>` for a project-local icon-store root, and use `--notice <NOTICE>` to save the exact provider pack revisions, terms, source archives, and icon IDs embedded in that artifact. Pack files are bounded and validated before rendering. Diagnostics remain on standard error, warnings preserve SVG, and Stack errors never create or replace output.

`stack lsp` runs a native [Language Server Protocol 3.18 adapter](./docs/language-server.md) over standard input and output. It provides incremental document synchronization, versioned diagnostics, completion, hover, hierarchical document symbols, and whole-document formatting for `.stack` files. The adapter negotiates UTF-8, UTF-16, or UTF-32 positions and delegates language semantics and formatting to the pinned compiler and engine rather than reimplementing them. Standard output is reserved for framed JSON-RPC messages.

`stack icons list [PROVIDER] [QUERY]` searches the asset-free catalog by ID, product name, or category. The catalog currently contains 1,051 IDs: 305 AWS, 45 Google Cloud, 639 Azure, and 62 curated developer and collaboration tool icons. This command reads only metadata embedded in the CLI.

`stack icons import <PROVIDER> --accept-terms` downloads the audited official archive set, verifies every complete SHA-256 before ZIP processing, reads allowlisted SVG entries with fixed size limits, sanitizes active and external content, preserves official colors and geometry, and writes the manifest, notice, and processed SVGs atomically. The default store is `$XDG_CONFIG_HOME/stack/icons`, falling back to `$HOME/.config/stack/icons`. `$XDG_CONFIG_HOME/stack/config.yaml` can set an absolute `default_icons_path`. Use `-o <DIRECTORY>` to put provider child directories below a project-local root. See [the provider icon guide](./docs/provider-icon-import.md) for configuration, project-local usage, sources, hashes, and rights.
Expand All @@ -65,7 +68,7 @@ stack render arch.stack -o arch.svg --notice arch.NOTICE.md
| One or more Stack error diagnostics, or `fmt --check` finds a difference | `1` |
| Invalid arguments, host I/O failure, or engine operational failure | `2` |

The CLI links `stack-engine` as a native Rust dependency. It owns filesystem and standard-stream behavior, process exit codes, configuration discovery, provider-pack import, notice output, and command presentation. It must not duplicate compiler, formatter, layout, or SVG-rendering logic.
The CLI links `stack-engine` and the protocol-neutral `stack-compiler` language-intelligence API as native Rust dependencies. It owns filesystem and standard-stream behavior, process exit codes, configuration discovery, provider-pack import, LSP transport and document state, notice output, and command presentation. It must not duplicate compiler, formatter, layout, or SVG-rendering logic.

The bundled engine resolves 30 provider-neutral core icons: `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, `observability`, `gateway`, `load-balancer`, `dns`, `cdn`, `firewall`, `network`, `event`, `stream`, `search`, `analytics`, `repository`, `pipeline`, `secret`, `document`, `task`, `chat`, `email`, and `ai`. User-managed provider packs preserve upstream artwork and attach source, archive hash, transformation, terms, and notice metadata. Rendering resolves namespaced IDs such as `aws:s3`, preserves the authored semantic `kind`, embeds the selected local asset, and writes its provenance into SVG metadata and the optional notice sidecar.

Expand Down
4 changes: 2 additions & 2 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Audit date: 2026-09-05

| Component | Version or revision | License | Source | Distribution note |
| --- | --- | --- | --- | --- |
| `stack-engine` / `stack-formatter` | `2c9adabd2ffee627ac5fe3ee26331b0ad75bceac` | Apache-2.0 | <https://github.com/stack-sh/engine> | Linked into the native binary; validates and renders caller-owned provider packs without bundling vendor assets. |
| `stack-compiler` | `4a18fac42afc2256a1bb3a6ff13d12d732a391e7` | Apache-2.0 | <https://github.com/stack-sh/compiler> | Linked transitively through `stack-engine`; preserves namespaced provider icon identifiers in normalized IR. |
| `stack-engine` / `stack-formatter` | `9af727aea79233b8389e0ed6fdbae7d3f388dc29` | Apache-2.0 | <https://github.com/stack-sh/engine> | Linked into the native binary; validates and renders caller-owned provider packs without bundling vendor assets. |
| `stack-compiler` | `84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820` | Apache-2.0 | <https://github.com/stack-sh/compiler> | Linked directly for protocol-neutral language intelligence and transitively through `stack-engine`; performs no runtime I/O. |
| `stack-theme` | `7e208d6a3c90d255799f390a4e8b86248c73caee` | Apache-2.0 | <https://github.com/stack-sh/theme> | Linked directly and through `stack-engine`; its 30 fallback and 12 explicit core SVGs are Stack-authored Apache-2.0 assets. It also provides the asset-free provider-pack contract and types. |
| `roxmltree` | `0.21.1` | MIT OR Apache-2.0 | <https://github.com/RazrFalcon/roxmltree> | Parses untrusted local SVG into a read-only tree before allowlisted serialization. |
| `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | MIT OR Apache-2.0 | <https://github.com/RustCrypto> | Computes complete archive and per-asset SHA-256 identities. |
Expand Down
72 changes: 72 additions & 0 deletions docs/language-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Native Stack language server

`stack lsp` exposes Stack language intelligence through the Language Server Protocol (LSP) 3.18. It is a long-running standard-input/standard-output process intended to be launched by an editor or another LSP client:

```sh
stack lsp
```

Standard output is reserved for `Content-Length` framed JSON-RPC messages. Operational framing failures are written to standard error and terminate the process with exit status `2`. Stack source diagnostics are sent through `textDocument/publishDiagnostics` and do not use the process exit status.

## Capabilities

The server advertises these static capabilities during `initialize`:

| LSP capability | Method or notification | Behavior |
| --- | --- | --- |
| Incremental synchronization | `textDocument/didOpen`, `didChange`, `didClose` | Maintains one current, monotonically versioned UTF-8 snapshot per open URI and applies ranged changes in order. |
| Diagnostics | `textDocument/publishDiagnostics` | Publishes compiler errors and warnings after open and every accepted change, with the matching document version, and clears them on close. |
| Completion | `textDocument/completion` | Returns syntax- and scope-aware keywords, properties, enum values, document identifiers, and bundled core icon IDs. |
| Hover | `textDocument/hover` | Returns plain-text information for declarations, references, edges, and properties. |
| Document symbols | `textDocument/documentSymbol` | Returns a hierarchical diagram, group, node, and edge outline. |
| Formatting | `textDocument/formatting` | Returns one whole-document edit when the engine formatter changes valid source, or no edits for canonical or syntactically invalid source. |

Completion uses the bundled Stack core icon catalog. User-imported provider packs remain local rendering inputs and are not read by the language-server process.

## Client setup

Configure the client to associate the `stack` language ID and `.stack` extension with the command `stack lsp`. The executable must be available in the environment inherited by the editor. A minimal Neovim setup is:

```lua
vim.filetype.add({ extension = { stack = "stack" } })

vim.api.nvim_create_autocmd("FileType", {
pattern = "stack",
callback = function(args)
vim.lsp.start({
name = "stack",
cmd = { "stack", "lsp" },
root_dir = vim.fs.root(args.buf, { ".git" }) or vim.fn.getcwd(),
})
end,
})
```

Restart the editor after installing or replacing the binary so the new process uses the expected version. Run `stack lsp --help` in the same environment when diagnosing executable discovery.

## Protocol lifecycle and positions

The server accepts `initialize` exactly once, then normal requests and notifications, then `shutdown` followed by `exit`. Requests before initialization return `ServerNotInitialized`; requests after shutdown return `InvalidRequest`. An `exit` before `shutdown` terminates with status `1`, as required by the LSP lifecycle.

The client may list `general.positionEncodings` in preference order. The server selects the first supported value among `utf-8`, `utf-16`, and `utf-32`, and defaults to the LSP-required UTF-16 encoding when the client omits the list. Ranges are end-exclusive. Incremental edits that split a Unicode scalar, address a missing line, move backwards, or do not increase the document version are rejected without changing the stored snapshot.

The adapter processes messages serially. It accepts `$/cancelRequest`, bounds remembered request IDs, returns `RequestCancelled` when an ID is cancelled before its request begins, and never replaces an already committed result with cancellation. Work already executing in this synchronous MVP is not preempted. Versioned diagnostics and serialized snapshot access prevent results for an older accepted change from being published as current.

## Resource and failure boundaries

| Input | Limit |
| --- | ---: |
| One JSON-RPC payload | 8 MiB |
| One header block | 32 KiB |
| One open document | 4 MiB |
| Open documents | 64 |
| Remembered cancelled or completed request IDs | 1,024 per set |
| Document URI | 4,096 Unicode scalars |

Malformed JSON produces a JSON-RPC parse error and the next correctly framed message can still be processed. Invalid methods, parameters, document versions, and source positions produce standard JSON-RPC or LSP errors. Invalid notification parameters are ignored and reported with `window/logMessage`. Invalid framing, truncated bodies, unsupported declared charsets, and I/O failures terminate the transport without attempting to resynchronize an untrusted byte stream.

## Ownership boundary

The CLI owns LSP framing, lifecycle, negotiated coordinate conversion, bounded open-document state, incremental changes, cancellation bookkeeping, and conversion to protocol values. `stack-compiler` owns diagnostics, completion, hover, symbol semantics, stable codes, and authored source spans for one immutable snapshot. `stack-engine` owns canonical formatting, and `stack-theme` owns bundled core icon metadata. None of those pure libraries perform editor transport, filesystem, network, clock, or process-environment access for an LSP request.

The protocol reference is the [Language Server Protocol 3.18 specification](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/lsp/3.18/specification.md).
4 changes: 4 additions & 0 deletions scripts/verify_release_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def verify_commands(binary, version):
require(command([binary, "--version"]) == expected_version, "--version output does not match Cargo version")
require(command([binary, "version"]) == expected_version, "version command output does not match Cargo version")
require(b"Usage:" in command([binary, "help"]), "help output is missing usage")
require(
b"stack lsp" in command([binary, "lsp", "--help"]),
"LSP help output is missing usage",
)

with tempfile.TemporaryDirectory(prefix="stack-release-smoke-") as temporary:
working_directory = Path(temporary)
Expand Down
58 changes: 55 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use stack_engine::{
};

mod config;
mod lsp;
mod provider;
mod provider_catalog;
mod templates;
Expand All @@ -38,6 +39,7 @@ Commands:
check Validate a Stack source file without modifying it
fmt Format a file in place or read from standard input
render Render standalone SVG to standard output or a file
lsp Run the Stack language server over standard input and output
icons List catalogs and import audited provider icon archives
help Print this message or the help of a subcommand
version Print version information
Expand All @@ -52,6 +54,7 @@ Examples:
stack check arch.stack
stack fmt --check arch.stack
stack render arch.stack -o arch.svg
stack lsp
stack icons list aws s3
";
const INIT_HELP: &str = "\
Expand Down Expand Up @@ -146,6 +149,22 @@ Examples:
stack render arch.stack -o arch.svg
stack render arch.stack --notice arch.NOTICE.md -o arch.svg
";
const LSP_HELP: &str = "\
Run the Stack language server over standard input and output

Usage:
stack lsp

Options:
-h, --help Print help

Protocol:
LSP 3.18 JSON-RPC messages use Content-Length framing over standard input
and output. Standard output is reserved for protocol messages.

Examples:
stack lsp
";
const ICONS_HELP: &str = "\
Manage local provider icon packs

Expand Down Expand Up @@ -219,7 +238,7 @@ Usage:
stack help icons <COMMAND>

Arguments:
<COMMAND> init, check, fmt, render, icons, help, or version
<COMMAND> init, check, fmt, render, lsp, icons, help, or version

Examples:
stack help
Expand Down Expand Up @@ -303,14 +322,19 @@ pub fn run(
if command == OsStr::new("render") {
return run_render(arguments, stdout, stderr);
}
if command == OsStr::new("lsp") {
return run_lsp(arguments, stdin, stdout, stderr);
}
if command == OsStr::new("icons") {
return run_icons(&mut arguments, stdout, stderr);
}

unknown_command_error(
"stack",
&command,
&["init", "check", "fmt", "render", "icons", "help", "version"],
&[
"init", "check", "fmt", "render", "lsp", "icons", "help", "version",
],
"stack help",
stderr,
)
Expand Down Expand Up @@ -345,6 +369,8 @@ fn run_help(
FORMAT_HELP
} else if command == OsStr::new("render") {
RENDER_HELP
} else if command == OsStr::new("lsp") {
LSP_HELP
} else if command == OsStr::new("help") {
HELP_HELP
} else if command == OsStr::new("version") {
Expand All @@ -353,7 +379,9 @@ fn run_help(
return unknown_command_error(
"stack help",
&command,
&["init", "check", "fmt", "render", "icons", "help", "version"],
&[
"init", "check", "fmt", "render", "lsp", "icons", "help", "version",
],
"stack help",
stderr,
);
Expand Down Expand Up @@ -526,6 +554,30 @@ fn run_version(
)
}

fn run_lsp(
mut arguments: impl Iterator<Item = OsString>,
stdin: &mut dyn Read,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> u8 {
let Some(argument) = arguments.next() else {
return lsp::run(stdin, stdout, stderr);
};
if is_help_flag(&argument) {
if let Some(extra) = arguments.next() {
return argument_error(
&format!("unexpected argument '{}'", extra.to_string_lossy()),
stderr,
);
}
return write_stdout(LSP_HELP, stdout, stderr);
}
argument_error(
&format!("unexpected argument '{}'", argument.to_string_lossy()),
stderr,
)
}

fn run_icons(
arguments: &mut dyn Iterator<Item = OsString>,
stdout: &mut dyn Write,
Expand Down
Loading