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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ jobs:
- name: Enforce layout runtime budget
run: cargo +stable test --release -p stack-engine --test layout_corpus layout_runtime_stays_within_budget --locked -- --ignored --nocapture

- name: Enforce editor language-intelligence latency budget
run: cargo +stable test --release -p stack-engine --test language_intelligence language_intelligence_runtime_stays_within_budget --locked -- --ignored --nocapture

- name: Build layout regression gallery
run: npm run layout:gallery

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

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

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

`stack-sh/engine` is the pure Rust execution engine for Stack architecture diagrams.

The workspace provides canonical Stack source formatting, the pure `stack-engine` operation facade, deterministic theme-aware scene layout and orthogonal edge routing, safe standalone SVG rendering, and a typed browser WebAssembly adapter.
The workspace provides canonical Stack source formatting, protocol-neutral language intelligence, the pure `stack-engine` operation facade, deterministic theme-aware scene layout and orthogonal edge routing, safe standalone SVG rendering, and a typed browser WebAssembly adapter.

## Workspace

- `stack-engine`: operation/output boundary, theme and icon fallback resolution, deterministic scene layout, edge routing, validation beyond the compiler stage, and standalone SVG rendering;
- `stack-engine`: operation/output boundary, theme and provider-aware completion catalogs, semantic hover, theme and icon fallback resolution, deterministic scene layout, edge routing, validation beyond the compiler stage, and standalone SVG rendering;
- `stack-formatter`: comment-preserving canonical formatting for Stack source files (implemented);
- `stack-engine-wasm` and npm `@stack-sh/engine`: a thin browser adapter exposing the same pure operations and portable result model.

Expand Down Expand Up @@ -47,18 +47,19 @@ The versioned representative layout corpus covers small, medium, and dense diagr
```sh
cargo test -p stack-engine --test layout_corpus --locked
cargo test --release -p stack-engine --test layout_corpus layout_runtime_stays_within_budget --locked -- --ignored --nocapture
cargo test --release -p stack-engine --test language_intelligence language_intelligence_runtime_stays_within_budget --locked -- --ignored --nocapture
npm run layout:gallery
```

The review-first snapshot policy and corpus contract are documented in [`layout-corpus/README.md`](./layout-corpus/README.md).

`stack-formatter` is pure and accepts source bytes or UTF-8 text. Lexical and syntax errors return diagnostics without formatted output. Syntactically valid source remains formattable when semantic diagnostics exist.

`stack-engine` exposes byte-oriented `format`, `check`, and `render` methods through an engine bound to the embedded or a caller-provided validated catalog. `ProviderPack::new` accepts a typed user-imported manifest and caller-owned SVG strings, verifies exact asset hashes and safe SVG structure, and computes a deterministic content revision before `Engine::with_provider_packs` can resolve namespaced IDs. Every normal output carries engine, authored language, theme catalog version, and theme catalog revision metadata. User-source failures stay in ordered portable diagnostics. Invalid provided catalogs or provider packs and violated normalized pipeline invariants use a separate operational-error channel. Checks and renders resolve the requested theme and provider packs, validate deterministic integer geometry, and route ordered edges outside node interiors. Missing themes and icons produce source-mapped `STK6001` and `STK5001` warnings while a fallback SVG remains available. An unsatisfied authored order hint produces `STK4001` at its source-map range; a satisfied hint does not.
`stack-engine` exposes byte-oriented `format`, `check`, and `render` methods plus UTF-8 `completion` and `hover` methods through an engine bound to the embedded or a caller-provided validated catalog. Language-intelligence results implement schema version 1.0 from the pinned compiler and echo the caller's document version. The Engine derives completion entries from its core theme catalog and validated provider packs, while the compiler remains the single owner of grammar, context, diagnostics, hover semantics, and text edits. `ProviderPack::new` accepts a typed user-imported manifest and caller-owned SVG strings, verifies exact asset hashes and safe SVG structure, and computes a deterministic content revision before `Engine::with_provider_packs` can resolve namespaced IDs. Every normal format, check, or render output carries engine, authored language, theme catalog version, and theme catalog revision metadata. User-source failures stay in ordered portable diagnostics. Invalid provided catalogs or provider packs, invalid language-intelligence positions, and violated normalized pipeline invariants use a separate operational-error channel. Checks and renders resolve the requested theme and provider packs, validate deterministic integer geometry, and route ordered edges outside node interiors. Missing themes and icons produce source-mapped `STK6001` and `STK5001` warnings while a fallback SVG remains available. An unsatisfied authored order hint produces `STK4001` at its source-map range; a satisfied hint does not.

The renderer emits fixed-dimension standalone SVG with embedded catalog or provider icons, local marker references, escaped authored text, accessible title and description metadata, and no script, event handler, external URL, host font measurement, or runtime I/O. Provider artwork preserves the authored node `kind`; each render returns the exact used-asset notices and writes provider ID, icon IDs, and pack revision into SVG metadata. The bundled catalog provides 30 first-party explicit icon identifiers in every core theme: `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`. Canonical renderer and representative-layout SVG snapshots are byte-stable and parsed by `scripts/validate-svg.py`; set `UPDATE_STACK_SNAPSHOTS=1` or `UPDATE_STACK_LAYOUT_SNAPSHOTS=1` only when intentionally regenerating the corresponding reviewed references. CI also executes one exact numeric geometry fixture in both the native suite and a WASI build.

The npm package exports synchronous `format`, `check`, `render`, `checkWithProviderPacks`, and `renderWithProviderPacks` functions after asynchronous module initialization. Provider-pack operations accept JSON-compatible local manifest and SVG data; they never discover a path or initiate a request. Each operation accepts `string | Uint8Array` source and returns a specific typed result with camel-case metadata and portable diagnostics. Diagnostics preserve the compiler's primary range, ordered `expected` values, corrective help, and related source locations. Invalid UTF-8 remains a normal `STK1001` result. Unsupported JavaScript input types and internal operational failures throw at the adapter boundary. Shared fixtures exercise native and WebAssembly provider resolution. Artifact validation audits WebAssembly imports and package contents; browser consumers retain responsibility for loading the module and performing any DOM, filesystem, network, or clock work.
The npm package exports synchronous `format`, `check`, `render`, `completion`, and `hover` functions after asynchronous module initialization, with provider-aware variants for check, render, and completion. Provider-pack operations accept JSON-compatible local manifest and SVG data; they never discover a path or initiate a request. Format, check, and render accept `string | Uint8Array`; completion and hover require a UTF-8 string plus a safe-integer document version and a `{ byteOffset, line, column }` position. Results use explicit TypeScript contracts, camel-case fields, plain-text documentation, end-exclusive UTF-8 ranges, and ordered portable diagnostics. Invalid UTF-8 remains a normal `STK1001` result for byte-oriented operations. Unsupported JavaScript input types, inconsistent positions, and internal operational failures throw at the adapter boundary. Shared fixtures exercise native and WebAssembly parity for provider resolution, contextual completion, document-version echo, multilingual positions, and hover. Artifact validation audits WebAssembly imports and package contents; browser consumers retain responsibility for module loading, stale-result suppression, and every DOM, filesystem, network, or clock interaction.

Public npm releases are produced from GitHub Releases after the repository checks pass. See [RELEASING.md](./RELEASING.md) for the first-release bootstrap and subsequent trusted-publishing flow.

Expand All @@ -70,6 +71,7 @@ Public npm releases are produced from GitHub Releases after the repository check
- [`docs/decisions/0004-route-orthogonal-edges-on-a-visibility-grid.md`](./docs/decisions/0004-route-orthogonal-edges-on-a-visibility-grid.md)
- [`docs/decisions/0005-serialize-safe-standalone-svg.md`](./docs/decisions/0005-serialize-safe-standalone-svg.md)
- [`docs/decisions/0006-expose-one-typed-browser-wasm-adapter.md`](./docs/decisions/0006-expose-one-typed-browser-wasm-adapter.md)
- [`docs/decisions/0007-adapt-language-intelligence-with-engine-catalogs.md`](./docs/decisions/0007-adapt-language-intelligence-with-engine-catalogs.md)
- [`docs/dependency-audit.md`](./docs/dependency-audit.md)

## Licensing
Expand Down
2 changes: 1 addition & 1 deletion crates/stack-engine-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stack-engine-wasm"
version = "0.6.0"
version = "0.7.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
Expand Down
94 changes: 94 additions & 0 deletions crates/stack-engine-wasm/examples/language-intelligence-parity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::error::Error;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use stack_engine_wasm::{CompletionResult, HoverResult, SourcePosition};

const CURSOR: &str = "<|>";

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct FixtureCase {
name: String,
document_version: u64,
source_with_cursor: String,
provider_packs: bool,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FixtureOutput {
name: String,
completion: CompletionResult,
hover: HoverResult,
}

fn source_and_position(marked: &str) -> Result<(String, SourcePosition), Box<dyn Error>> {
let marker = marked
.find(CURSOR)
.ok_or("fixture cursor marker is missing")?;
if marked[marker + CURSOR.len()..].contains(CURSOR) {
return Err("fixture contains more than one cursor marker".into());
}
let mut source = marked.to_owned();
source.replace_range(marker..marker + CURSOR.len(), "");
let mut line = 1_u64;
let mut column = 1_u64;
let mut characters = source[..marker].chars().peekable();
while let Some(character) = characters.next() {
if character == '\r' && characters.peek() == Some(&'\n') {
characters.next();
line += 1;
column = 1;
} else if character == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
Ok((
source,
SourcePosition {
byte_offset: marker as u64,
line,
column,
},
))
}

fn main() -> Result<(), Box<dyn Error>> {
let fixture_path = std::env::args_os()
.nth(1)
.map(PathBuf::from)
.ok_or("usage: language-intelligence-parity <fixture-path> <provider-pack-path>")?;
let provider_path = std::env::args_os()
.nth(2)
.map(PathBuf::from)
.ok_or("usage: language-intelligence-parity <fixture-path> <provider-pack-path>")?;
let fixtures = serde_json::from_slice::<Vec<FixtureCase>>(&std::fs::read(fixture_path)?)?;
let provider_packs = std::fs::read_to_string(provider_path)?;
let outputs = fixtures
.into_iter()
.map(|fixture| {
let (source, position) = source_and_position(&fixture.source_with_cursor)?;
let completion = if fixture.provider_packs {
stack_engine_wasm::completion_with_provider_packs_text(
&source,
fixture.document_version,
position,
&provider_packs,
)?
} else {
stack_engine_wasm::completion_text(&source, fixture.document_version, position)?
};
Ok(FixtureOutput {
name: fixture.name,
completion,
hover: stack_engine_wasm::hover_text(&source, fixture.document_version, position)?,
})
})
.collect::<Result<Vec<_>, Box<dyn Error>>>()?;
println!("{}", serde_json::to_string(&outputs)?);
Ok(())
}
Loading