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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,27 @@ jobs:

- name: Validate formatter fixtures
run: python scripts/validate-formatter-fixtures.py

language-package:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 24.18.0
package-manager-cache: false

- name: Install dependencies
run: npm ci

- name: Test language assets
run: npm run test:language

- name: Check package types
run: npm run typecheck:language

- name: Verify package contents
run: npm run pack:check
76 changes: 76 additions & 0 deletions .github/workflows/release-language.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Release language package

on:
release:
types:
- published
workflow_dispatch:
inputs:
release_tag:
description: Existing language package release tag to verify or resume
required: true
type: string

env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}

permissions:
contents: read
id-token: write

jobs:
npm:
name: npm
if: startsWith(github.event.release.tag_name || inputs.release_tag, 'language-v')
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ env.RELEASE_TAG }}

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 24.18.0
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Install trusted publishing npm CLI
run: npm install --global npm@11.5.1

- name: Install dependencies
run: npm ci

- name: Verify release version
run: |
package_version=$(node --print "require('./packages/language/package.json').version")
test "$RELEASE_TAG" = "language-v$package_version"
release_commit=$(git rev-list --max-count=1 "$RELEASE_TAG")
test -n "$release_commit"
test "$(git rev-parse HEAD)" = "$release_commit"
git merge-base --is-ancestor "$release_commit" origin/main

- name: Test language assets
run: npm run test:language

- name: Check package types
run: npm run typecheck:language

- name: Verify package contents
run: npm run pack:check

- name: Check whether version is already published
id: package
run: |
package_version=$(node --print "require('./packages/language/package.json').version")
if npm view "@stack-sh/language@$package_version" version >/dev/null 2>&1; then
echo "published=true" >> "$GITHUB_OUTPUT"
else
echo "published=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish package
if: steps.package.outputs.published != 'true'
run: npm publish --workspace @stack-sh/language --access public
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
*.tgz
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Stack is an opinionated DSL for describing software architecture and technology-stack diagrams. It is designed to be concise enough for humans and language models to write, while remaining constrained enough for renderers to produce consistently polished diagrams.

This repository is the canonical source for the Stack language. It defines the language contract; it does not contain a renderer, server, editor, or CLI.
This repository is the canonical source for the Stack language. It defines the language contract and distributes shared editor language assets; it does not contain a renderer, interactive editor, server, or CLI.

## Status

Expand All @@ -16,8 +16,10 @@ The language is currently a proposal for Stack 1.0. No compatibility guarantee a
- [ADR-0001: Adopt a constrained declarative topology language](./docs/decisions/0001-constrained-declarative-language.md)
- [ADR-0002: Make the canonical theme catalog own icons](./docs/decisions/0002-theme-owned-icons.md)
- [ADR-0003: Standardize compiler interchange and conformance fixtures](./docs/decisions/0003-standardize-compiler-interchange-and-conformance.md)
- [ADR-0004: Distribute shared editor language assets from the specification](./docs/decisions/0004-distribute-editor-language-assets.md)
- [Examples](./examples)
- [Conformance suite](./conformance)
- [`@stack-sh/language`](./packages/language)

## Example

Expand Down Expand Up @@ -64,6 +66,9 @@ check-jsonschema --schemafile schemas/normalized-ir.schema.json conformance/vali
find conformance -name expected.diagnostics.json -print0 | xargs -0 check-jsonschema --schemafile schemas/diagnostic-expectations.schema.json
python scripts/validate-compiler-diagnostics.py
python scripts/validate-formatter-fixtures.py
npm ci
npm run test:language
npm run pack:check
```

## Design Principles
Expand Down
67 changes: 67 additions & 0 deletions docs/decisions/0004-distribute-editor-language-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ADR-0004: Distribute Shared Editor Language Assets from the Specification

## Status

Accepted

## Date

2026-09-03

## Context

Stack source is edited in the browser playground today and may later appear in documentation code blocks, desktop editors, and IDE extensions. These consumers need consistent lexical classification for comments, strings, declarations, properties, enum values, operators, numbers, punctuation, and identifiers.

A TextMate grammar is a presentation-oriented description of the language's lexical surface. It is useful to many editor integrations, but it cannot enforce Stack semantics and must not become a second language definition. Stack 1.0 also has contextual keywords, so lexical highlighting cannot always reproduce parser context.

Placing the grammar in a future language-server repository would make editor presentation depend on a protocol server and encourage that server to own syntax. Placing separate copies in the Web and editor repositories would allow the copies to drift.

## Decision

The specification repository owns and tests shared editor language assets under `packages/language` and distributes them as the public `@stack-sh/language` npm package.

The package contains:

- a TextMate grammar with the stable root scope `source.stack`;
- a portable editor language configuration for comments, brackets, surrounding pairs, and identifier words;
- dependency-free JavaScript and TypeScript entry points for consuming those assets.

The normative prose grammar in `SPECIFICATION.md` remains authoritative. The TextMate grammar follows that contract and only classifies source for presentation. Highlighting never establishes that a token or document is valid.

The package does not depend on Shiki, an editor runtime, a theme, the Stack compiler, or an LSP implementation. Consumers select those integrations themselves and pin a released package version.

The reference compiler continues to own parsing, semantic validation, structured diagnostics, source ranges, and normalized IR. A future language server will own LSP document lifecycle and protocol features such as completion, hover, navigation, and diagnostic publication. It will consume compiler capabilities and this package rather than defining a separate Stack grammar.

## Alternatives Considered

### Put the grammar in the Web repository

- Pros: The first consumer can evolve quickly.
- Cons: Documentation and editor integrations must copy or depend on a product-specific repository.
- Rejected: The grammar represents shared language tooling, not Web UI behavior.

### Put the grammar in a future LSP repository

- Pros: Most editor-facing assets would be colocated.
- Cons: Syntax highlighting would depend organizationally on a protocol integration that does not yet exist, and non-LSP consumers would inherit that coupling.
- Rejected: The language server consumes the language contract; it does not own it.

### Generate the grammar from the EBNF

- Pros: Reduces duplicated keyword lists in principle.
- Cons: TextMate scopes, string recovery, and contextual-keyword approximation require presentation decisions that the normative EBNF does not express.
- Rejected for Stack 1.0: Tests and review provide a smaller and clearer synchronization mechanism. Generation can be reconsidered if the grammar grows substantially.

### Publish a Shiki-specific package

- Pros: Gives the Web consumer a ready-made highlighter.
- Cons: Couples shared language data to one runtime, theme strategy, and release cadence.
- Rejected: The package should remain usable by Shiki, VS Code-compatible tooling, and other TextMate consumers.

## Consequences

- Web, documentation, and editor integrations can share one versioned grammar.
- Grammar changes are reviewed beside language changes and can be tested with specification examples.
- Consumers must provide their own highlighter and visual theme.
- Contextual keywords may be highlighted as keywords even where the parser accepts them as identifiers; this is presentation behavior, not a semantic restriction.
- Compiler and language-server implementations must not use TextMate scopes as parser input.
Loading