Skip to content

Editor Integration

SASE exposes two editor-facing surfaces for prompt and macro editing:

  • sase lsp is the interactive editor path. Configure your editor to launch it as a stdio language server when you want completions, snippets, hover, diagnostics, code actions, and jump-to-definition while editing a prompt.
  • sase editor helper-bridge ... is the integration/debugging path. It reads one JSON request from stdin and writes one JSON response to stdout, so clients can fetch the same catalogs without implementing an LSP client.

Language Server

sase lsp starts the SASE macro language server over stdio. Run --version or --help in a terminal to verify the wrapper, then point your editor's LSP configuration at sase lsp:

sase lsp --version
sase lsp --help

In editor configuration terms, the command is sase and the argument list is ["lsp"].

The wrapper resolves the server command in this order:

  1. SASE_MACRO_LSP_CMD, parsed as a shell-style command for development.
  2. A sase-macro-lsp binary in the current Python environment's bin/ directory.
  3. sase-macro-lsp on PATH.
  4. The newer debug or release sase-macro-lsp binary under a sibling ../sase-core checkout.
  5. cargo run --manifest-path ../sase-core/Cargo.toml -p sase_macro_lsp -- when cargo is available and the sibling checkout has a Cargo.toml.

Use SASE_MACRO_LSP_CMD when you need to point the editor wrapper at a different source checkout or command, including a custom binary that should beat the managed venv copy. Full editable-install SASE updates reinstall the server into the uv-tool venv when pulled sase-core commits change. The Justfile uses SASE_CORE_DIR and SASE_LINKED_REPO_SASE_CORE_DIR (with the legacy SASE_SIBLING_REPO_* variables as fallbacks) for local sase-core build/install targets, but sase lsp itself does not read those variables.

The macro LSP binary is built and installed from the local sase-core checkout (just rust-lsp-install for development/editor validation). It is separate from the sase-core-rs Python wheel; installing or rebuilding that wheel alone does not install the language server.

The wrapper also exports installed package macro locations, bundled default config, plugin macro directories, and plugin config paths to the Rust server. It also materializes a local artifact-reference catalog under ~/.sase/macro_lsp/ by default. The server refreshes its macro catalog when the LSP session starts, keeps a short cache for completion requests, and exposes a sase.macroLsp.refreshCatalog command for clients that surface LSP commands. Artifact-reference diagnostics and semantic highlighting re-read their catalog on each request, so a launcher refresh or external rewrite is visible on the next editor pass; artifact-reference completion reads the same catalog through a short-lived cache keyed by the catalog file's signature (see LSP Features).

LSP Features

The macro language server is focused on prompt and macro editing:

Feature Behavior
Macro completion Completes #name, #!workflow, namespaced references, and slash-skill references from the structured catalog. Skills complete as #skill/<name> after # and as /<name> after a slash. Memory notes complete as #memory/<stem> and never appear in slash completion.
Project/Patch tags Completes +query at the start of the prompt or after whitespace, {, or \| from enabled projects and active Patches. Project rows insert the +<project> tag (or #<workflow>:<name> when the name is not tag-shaped); Patch rows insert #<workflow>:<patch>. Accepting a row also removes the other project tags, and any line-leading # workspace refs, in the same --- segment. Tags also get hover, diagnostics, quick fixes, and a #<workflow>:<name> to +<project> rewrite action. See Project Tags.
VCS ref roots Completes #gh:, #git:, and other registered VCS workflow ref roots from project, Patch, and namespace catalog rows.
VCS repositories Completes repository names after namespace slashes such as #gh:owner/ through the owning workspace provider.
Argument assistance Completes named arguments, path inputs, closed-set enum values (inline, builtin effort, and plugin <dist>@<id>), bool values, and model tokens for typed macro inputs where the catalog exposes input metadata. Enum and bool rows come from the shared Rust candidate builder: they insert the canonical value (quoted when argument syntax requires it), replace the whole current value span, and carry labels, descriptions, and a default badge without preselecting the default. model rows reuse the %model catalog (aliases on @, provider drill-down, effort suffix). Frontmatter type: values — including shortform scalars such as env: word — complete from the advertised input-type catalog. See Typed Inputs.
Directive completion Completes the shared directive matrix: directive names and aliases, fixed values, %model: catalog rows and provider drill-down, parenthesized %model(..., alias=...) keys, and %id / %clan / %wait(...) / %queue(...) / %hold(...) keyword rows and values. Static %if(should_run=...) and %hold assistance is always available, as are %if::, %proc, and type: code assistance.
Model shortcuts Completes =alias and ==model at prompt/line start or after an ASCII space into canonical %m: values, using the same shared Rust filters and edit plans as sase's TUI. = is an LSP trigger character; manual completion also works inside a valid equals token. See Equals model shortcuts.
Artifact references Fuzzy-completes bare @ and @query tokens as canonical artifact kinds, adding local paths on a kind-prefix miss or manual completion request, then completes local payloads after @kind:, including local stitch references.
File completion Completes path-like tokens and recent file-history entries; @-prefixed local paths appear automatically when no artifact kind prefix-matches, or on manual invocation.
Snippets Offers SASE snippets after bare trigger words when the client advertises LSP snippet support.
Jinja completion Inside {{ }} and {% %}, completes variables (declared input: properties first, then template locals, sase run-time names, positional/provider vars, and Jinja globals), filters after \|, tests after is, members after wait./loop., and statement keywords (open-block closers first) inside {% %}. Uses the same Rust engine as the TUI prompt menu. { and \| are LSP trigger characters; outside tags they stay silent so literal braces and alternation pipes never pop a menu. gitcommit documents get no Jinja completion.
Hover Shows macro metadata, descriptions, previews, source display paths, tags, and active input hints. Argument hover uses the shared type label, the input type's source (builtin or plugin <distribution> from a qualified named type), the default, and a value/label/description table capped at 12 rows with an exact remaining count. Agent arguments hover the same way. Memory entries also show kind memory and the current UI's tier label (core/reference), which is the note's memory type. Inside Jinja tags, hover shows the engine's markdown for the variable, member, filter, or test under the cursor, including availability reasons for out-of-scope builtins.
Diagnostics Reports macro/directive issues, macro call arguments that are unknown (unknown_macro_arg), repeated (duplicate_macro_arg), or the wrong type for the declared input (invalid_macro_arg_type), invalid closed-set values as Errors (invalid_macro_arg_choice) with ranked suggestions in diagnostic data, and unroutable model tokens as Warnings (invalid_macro_arg_model) with a preferred "Replace with" fix. See Typed Inputs. Frontmatter carries source-indexed fixes for an unknown type (Change type to ...), deprecated string (Use \line`), and unquoted PyYAML-typed choices/defaults (Quote `yes`). Also reports malformed or unresolved filesystem-backed artifact references outside prompt literal zones, and project-tag issues: a disabled+tag warns with ``+is disabled —sase project enable `` (disabled_project_tag), an anchored unknown tag warns (unknown_project_tag; unanchored unknown tags are plain text), an ambiguous tag errors (ambiguous_project_tag), and a tag with no VCS provider warns (providerless_project_tag`).
Quick fixes Diagnostic-driven quickfix actions consume published diagnostic data: invalid enum arguments get a preferred Replace with `brief` edit that replaces only the value; frontmatter unknown types, string→line, and quote fixes use the same path. Existing refactors, canonical-marker fixes, tag quick fixes, and catalog refresh remain.
Semantic highlighting Highlights macro reference names, directive names, and their argument lists, plus the kind, payload, and supported fragment of known artifact references, glossary phrases, and +<project> project tags, outside prompt literal zones using LSP semantic tokens. See Semantic token legend.
Definition Jumps from macro and slash-skill references to real source files when the catalog provides a resolvable path, including the backing note for #memory/<stem>.

When a client enables LSP on-type formatting for (, the server shares the prompt input's argument shorthand edits. Typing ( after an argument-opening : removes that colon; typing it after :: plus zero or more ASCII spaces inserts () before the delimiter and leaves the caret inside the pair, for example #review:: body becomes #review():: body. Typing it after a closed macro argument list adds a comma and moves the caret before ), including when a :: text tail follows. These are on-type text edits only; the argument menu comes from the client's next completion request. The edits preserve authored spacing and suffix text, and the syntax/literal-region exclusions match the TUI prompt input.

Snippet completions come from the same registry sase's TUI uses: macros with snippet front matter plus user-defined ace.snippets, with ace.snippets winning on trigger collisions. The server asks the host helper bridge for that authoritative registry and falls back to native Rust loading only for simple macro snippets and configured ace.snippets when the helper is unavailable. In sase's TUI, that shared registry expands into nested snippet sessions: expanding a trigger while another snippet's tabstops are live visits the inner tabstops first, then returns to the remaining outer tabstops. LSP clients receive ordinary editor snippets from the same catalog, so placeholder navigation in external editors is handled by the editor.

Directive completion uses UTF-16 LSP ranges, so accepting a row replaces the correct token even after non-BMP characters earlier on the line. %wait: remains positional and never offers bead= or time=; those keywords are only in parenthesized %wait(...). capacity=, priority=/p=, and weight=/w= complete on %queue/%q only. Keyword menus omit duplicate and conflicting entries, and model keyword values omit a self-reference such as @medium while completing %model(..., medium=...). Static directive names, aliases, keyword rows, and fixed values still complete if the helper bridge cannot refresh dynamic catalogs; model, agent, bead, repository, and artifact inventories degrade independently.

Feature flags

The sase lsp wrapper snapshots the launch-related feature flags when it starts the server and passes them to the Rust process, so completion matches what a launch from the same machine would accept. The %queue capacity budget needs no flag: capacity help always describes this launch's runner-capacity budget, and %q: suggests 1, 100, and 1.5x.

The server reads these values once, so restart the LSP session after changing a flag.

Equals model shortcuts

Typing = in an eligible prompt document requests model shortcut completion through the same Rust contracts sase's TUI uses. A valid equals token sits at prompt offset zero, at the start of a logical line, or immediately after an ASCII space, with the caret after the equals marker. Escaped or embedded equals signs, Markdown-style =text= / ==text== marker pairs, paths, inline and fenced code, disabled prompt regions, frontmatter, Jinja, placeholders, and directive-owned input stay ordinary text. The *alias and **model forms are compatibility spellings that remain ordinary text.

The =alias menu lists only effective implicit_alias and user_alias rows from the launcher-materialized model catalog, in canonical catalog order, with case-insensitive prefix matching. Accepting @large replaces the whole =query token, including any suffix to the right of a mid-token caret, with %m:@large. The ==model menu lists concrete model rows only, including provider-qualified matches such as ==codex/g; accepting gpt-6.1-sol replaces the whole ==query token with %m:gpt-6.1-sol.

At prompt or line end the expansion appends one ASCII space; before a tab it appends none; before an existing ASCII space it consumes and reinserts that first space so later whitespace is preserved and the caret lands after the first space.

A valid shortcut context with no matching rows returns an empty incomplete completion list instead of falling through to prose, file, or snippet rows. Missing or malformed catalogs do the same. The list is isIncomplete with filterText set to the typed equals prefix so clients re-request as the query changes instead of dropping the row because the source text differs from the inserted %m: value. The catalog is a launch-time snapshot: restart the LSP after config or plugin changes. Manual editor completion also works while the caret is in a valid equals token.

ace.prompt_completion.auto_directive_menu remains a setting for the prompt bar in sase's TUI and does not govern an editor client's trigger policy. The TUI prompt input uses these same shortcuts; see Prompt Input and Equals model shortcuts.

Artifact assistance is local-only. Before a : appears, @ completion withholds local file rows whenever the query prefix-matches an artifact kind (including bare @), and returns them automatically when no kind prefix-matches. A manually invoked completion request includes the file rows explicitly. Completion labels are the reference that gets inserted, such as @plan: and @src/. Document kinds (including dynamic sidecar roles), indexed artifact files, Patches, beads, agents, and stitches are enumerated or resolved from the selected project's local catalog roots and checkout paths. bead and agent payloads resolve locally from generated sidecar pages, and stitch payloads are enumerated from local git checkouts, excluding SDD sidecar repositories (plans, beads, agents, research) since their commits are machine-written bookkeeping rather than a human's recent work. A sidecar stitch reference still resolves when written out in full, such as @stitch:plans@<sha> — the exclusion only curates what completion offers. Historical aliases such as @commit:, @plans:, @chat:, and @bug: are not offered by completion. The LSP never contacts git hosts, issue trackers, or other network providers. Unknown @kind: text remains ordinary prose.

This canonical-only rule is specific to the editor LSP. sase's TUI prompt bar currently also lists recognized aliases and historical kinds, and its repository-history payload picker remains attached to @commit:; both @commit: and @stitch: resolve through the same launch resolver.

Matching is fuzzy and ranked on the server for every enumerated kind — document roles, indexed artifact files, Patches, beads, agents, and stitches — against the inserted payload, the row's title, and, for scoped rows, the qualified scope@title target. For example, @stitch:core@fix can match the sase-core repository scope and a commit subject containing fix in the same query. Likewise, @research:site finds @research:202607/sase_sites_hub_and_pages/sase_sites_hub_and_pages.md, @agent:blue finds @agent:team.blue.checker from a mid-name fragment, and @file:panel finds a default:<hex> indexed file by its file name. Rows are grouped into tiers so a fuzzy hit never outranks a literal one, then ordered by score, provider rank where a provider declares one, shorter text, and case-insensitive text:

Tier Meaning Example query against 202607/sase_sites_hub_and_pages/…
0 query is a prefix of the primary text 202607/
1 query is a prefix of the basename segment sase_sites
2 query is a contiguous substring hub_and
3 query is an ordered subsequence site, shubp

An empty query is not ranked at all: each group keeps its provider order (builtin order for kinds, directories-before-files for paths, and provider order for payloads). Kind rows and the trailing partial of a local path are matched the same way, but a @ path token's directory portion stays exact, so @src/ still lists src/ and @src/fcb can still find src/…/_file_completion_base.py. Stitch SHA text is still part of the lowest fuzzy tier, so hex-like queries such as add can subsequence-match a SHA; subject and repository matches outrank those tier-3 rows.

Bounds are disclosed rather than silent. Filesystem-backed enumeration walks up to 5000 payloads per root, so a root larger than that is matched only over the rows the walk reached. Stitch completion reads at most 200 revisions per repository, gives each repository two seconds to answer, and merges at most 1000 commit rows across repositories. Matching then returns at most 200 rows per group to the editor. Whichever bound bites, the count of payloads left out is appended to every item's detail as at least N additional payloads not shown, on top of the list's isIncomplete: true. Because the walk is query-independent, the enumerated and titled inventory is cached in-process per project and per catalog signature (path, mtime, size) with a two-second TTL, so a keystroke re-ranks a warm corpus instead of re-walking the filesystem. A watched catalog write or the sase.macroLsp.refreshCatalog command invalidates it immediately.

Keeping server-ranked rows alive in the client is an explicit contract. Every artifact-reference item sets filterText to the reference text as typed (@research:site in the payload stage, @rsch in the kind stage) rather than to the inserted reference, because a client that prefix-filters @research:202607/… against @research:site would discard every fuzzy row. The response is a CompletionList with isIncomplete: true, which makes clients re-request on each keystroke instead of re-filtering a stale list and, in Neovim's native completion, disables the client's own fuzzy re-sort so the server's sortText order survives. Insertion is unaffected: each item carries the full reference in textEdit.newText.

Editors cannot highlight individual characters inside a completion label, so the "why is this row here" affordance moves into the preview. labelDetails.description keeps the group word (artifact kind, file, directory, or a payload kind such as stitch, patch, research, bead, or agent). labelDetails.detail adds the row's title when it differs from the label — a document's frontmatter title, an indexed file's basename, a Patch or bead title, an agent's short name, or a commit subject — and markdown documentation shows the matched payload with the matched runs wrapped in **, followed by that title on a second line. Stitch rows also include the bounded commit body when one is available.

Semantic token legend

Most semantic tokens use a standard LSP token type, so editors style them with their normal semantic-token theme. The exception is project tags, which use the custom saseProjectTag token type that the server declares in its legend:

Prompt element Token type Modifiers
Macro reference name (#name) function —
Directive name (%queue, %if, ...) macro —
Argument delimiters and = operator —
Argument key (count=) parameter —
String argument value, including [[...]] text string —
Numeric argument value number —
Boolean argument value keyword —
Artifact-reference kind namespace documentation for dynamic document-role references
Artifact-reference payload string documentation for dynamic document-role references
Artifact-reference fragment number documentation for dynamic document-role references
Glossary phrase type —
Project-tag sigil (+) saseProjectTag sigil plus the name's modifier below
Project-tag name (+<project>) saseProjectTag accent0–accent17, unknown, or disabled

A resolved tag name carries the accentN modifier for its project, matching the accent sase's TUI uses. An unknown or ambiguous tag name carries unknown instead, and a disabled tag — or a resolved tag whose project has no detected provider — carries disabled alongside a warning diagnostic (see LSP Features). The server also advertises the catalog's accent palette at experimental.sase.projectTagPalette in its initialize result, so clients can paint accentN-modified tags with the same colors as sase's TUI.

Argument spans come from the same sase-core argument parser that colors arguments in sase's TUI prompt input, so both surfaces agree on roles. Argument tokens that belong to a key the macro does not declare carry the deprecated modifier; type mismatches and duplicate keys are reported only through diagnostics. A call that is still being typed (no closing parenthesis yet) gets structural tokens without validity markers. Multi-line [[...]] values are tokenized line by line with UTF-16 positions, and an artifact reference inside an argument value keeps its own kind and payload tokens.

sase-nvim keeps the glossary token's theme color and adds an overridable SaseGlossaryTerm underline on top through Neovim's LspTokenUpdate hook.

Helper Bridge

Editor integrations that do not need live LSP behavior can call fixed helper operations directly:

printf '{"schema_version":1,"project":"sase"}\n' | sase editor helper-bridge macro-catalog
printf '{"schema_version":1,"project":"sase"}\n' | sase editor helper-bridge snippet-catalog
printf '{"schema_version":1}\n' | sase editor helper-bridge agent-catalog
printf '{"schema_version":1}\n' | sase editor helper-bridge finalizer-catalog
printf '{"schema_version":1,"workflow":"gh","namespace":"sase-org"}\n' \
  | sase editor helper-bridge vcs-repo-catalog

macro-catalog returns the structured macro catalog used by mobile/editor clients, including insertion text, reference prefix, kind, tags, typed inputs, display/source fields, and definition_path when SASE can resolve a real file.

snippet-catalog returns the composed snippet registry:

  • Macro-derived snippets from markdown files with snippet front matter.
  • User snippets from ace.snippets in merged SASE config.
  • Valid trigger words only; user snippets override macro snippets on collision.
  • #[trigger] snippet references resolved after the macro/user merge.
  • Generated initial-capital aliases (foo → Foo, uppercasing only the first character of the trigger and template) composed after that merge, so the registry matches sase's TUI. Explicit Foo definitions are never overwritten.

agent-catalog requires only {"schema_version":1} and reads across projects. It returns active and recent ordinary agent rows, de-duplicated by name, with status and project; monitor rows use kind: monitor. When the same artifact snapshot contains usable group metadata, the response adds the latest identifiable generation of each session and clan plus @tribe references derived from stored tribe assignments and clan declarations. Every row has name, kind, member_count, and display-ready detail; clan rows also have status, which follows the Agents-tab clan rule (a lone running member's status, otherwise the aggregate).

Session rows carry a detail of session · N members by default. For the 20 sessions with the most recent activity — and only those, so the short-lived helper subprocess stays fast enough for interactive completion — the helper additionally tries to resolve an associated plan or bead. It checks concrete session members newest-first and uses the session root only as a compatibility fallback, then enriches the row in three descending rungs:

  1. A plan or bead resolved with a title: detail becomes <kind> · <structure> · <title>, such as epic · 5 phases · 2 waves · Bead review hardening. kind is the raw tale/epic/plan/phase/task token. structure is the N phases · M waves fragment, present only when a plan exposes its phase list; bead-derived rows never have it.
  2. A kind resolved but no title (an untitled, missing, or unreadable plan file): a cleaned snippet of the session's launch prompt fills the title slot in that same shape.
  3. Nothing resolved but a usable prompt snippet exists: detail becomes session · N members · <snippet>.

Rungs 1 and 2 also attach Markdown documentation — the plan's goal, its epic phase list, or a phase/task bead's parent context, closed by a session · N members · <status> footer. Rung 3 and unenriched sessions have no documentation. Every session keeps its plain session · N members detail if resolution fails outright, and group enrichment is additive throughout, so missing plans or malformed compatibility metadata never hide ordinary agent rows.

finalizer-catalog requires {"schema_version":1} and optionally accepts project. Unknown request fields are ignored. It returns the effective configured finalizer instances in policy order (required, remaining defaults, then optional, each group alphabetically) with value, provider_ref, required/default flags, after, max_attempts, documentation, and provenance_id. Malformed finalizer configuration returns status: error with an empty entries list instead of invented rows, and the builder never loads provider code.

vcs-repo-catalog requires a workflow and namespace, then asks that workflow's registered workspace provider for repositories. The response reports status, error_kind, message, provider_display, and whether returned cache data is stale. Each entry has a short name and a full ref such as sase-org/sase; replace the current VCS ref with ref rather than appending it after the namespace.

All five helper operations read one JSON object from stdin and write one compact JSON object to stdout. They are fixed catalog operations, not a general shell or filesystem bridge.

Authoring Snippets

Use ace.snippets for local trigger-word templates:

ace:
  snippets:
    fix: "Please fix the following issue:\n$0"
    review: "Review this code for correctness, performance, and style.\n$0"

Use macro front matter when a reusable prompt should also appear as a snippet:

---
name: review
snippet: true
input:
  path: path
---

Review {{ path }} for correctness, tests, and maintainability.

Required macro inputs become snippet tabstops. Optional inputs are pre-filled from defaults. Macros with complex Jinja control flow are skipped by snippet conversion so the generated editor template stays predictable.

Snippet templates can reuse other snippets by trigger with #[trigger]. Positional forms such as #[trigger(value)] and #[trigger:value] fill the referenced snippet's tabstops before the composed template is renumbered.

Snippet templates can also reference the prompt's target project with #{project} (for example the #{project}-$1 bead). The LSP resolves it from the document's leading +<project> tag or #gh:/#git: VCS ref, then the SASE current project; when nothing resolves, the token stays verbatim. Unknown #{name} tokens are never touched.

When the composed registry is used in sase's TUI, Tab moves forward through $1, $2, ... and $0, while Shift+Tab retreats through visited tabstops. Expanding a second trigger from inside an active snippet nests it instead of discarding the remaining outer stops.

Troubleshooting

Symptom Check
sase lsp cannot start Run sase lsp --version; run a full editable SASE update, build ../sase-core, or set SASE_MACRO_LSP_CMD.
Snippets do not appear Confirm the editor advertises LSP completionItem.snippetSupport; inspect sase editor helper-bridge snippet-catalog.
Completion catalog looks stale Restart the LSP session after changing installed plugin resources; for rewritten artifact catalogs, retry completion or diagnostics.
%proc rows are missing Restart the LSP session so the server reloads the directive contract and catalogs.
Jump-to-definition is missing Check whether the catalog entry has a real definition_path; plugin or built-in virtual entries may only have display paths.
A user snippet is ignored Trigger names must contain only ASCII letters, digits, or _.

sase editor

sase editor exposes JSON-over-stdin helper operations for editor integrations. It is intentionally a fixed-operation bridge rather than a generic shell or filesystem API.

Moved to sase editor.

Moved to Editor Integration.

Command behavior

The finalizer-catalog request is {"schema_version":1} with an optional project hint; unknown fields are ignored. The response is a compact status/message/entries envelope. Malformed finalizer configuration returns status: error and no rows, and the builder never loads provider code.

The agent-catalog request is just {"schema_version":1}; it has no project filter and reads the cross-project agent snapshot. Ordinary rows are de-duplicated by name and include status and project, with kind: agent for agents and kind: monitor for monitors. When group metadata is available, additive session, clan, and @tribe rows include kind, member_count, and display-ready detail; clan rows also include aggregate status. For the 20 most recently active sessions, SASE tries to enrich detail with the associated plan or bead's kind, structure, and title, and to add Markdown documentation carrying the goal, phase list, or parent/task context plus a session status footer. Unresolved or older sessions keep the plain member-count detail, and enrichment failure never removes ordinary rows; see Editor Integration: Helper Bridge for the exact fallback ladder. The structured macro catalog includes insertion metadata (insertion, reference_prefix, kind), typed argument metadata, display/source fields, and definition_path when SASE can resolve a real file to jump to.

The snippet catalog uses the same source ordering as sase's TUI: macros marked with snippet front matter plus user-defined ace.snippets, with ace.snippets winning on trigger collisions. It also includes the generated initial-capital aliases (foo → Foo), so editor completion and the native fallback expose exactly the same trigger/template pairs as the TUI.

sase lsp

Starts the macro language server over stdio for editor integrations. SASE_MACRO_LSP_CMD can override the server command during development. Without that override, sase lsp uses the current Python environment's bin/sase-macro-lsp, then sase-macro-lsp from PATH, then the newer debug/release binary from a sibling ../sase-core checkout, then falls back to cargo run from that sibling checkout when Cargo is available. Full editable-install SASE updates reinstall the server into the uv-tool venv when pulled sase-core commits change.

Moved to sase lsp.

_[project]: A sase project is a named unit of work registered with SASE. _[workspace]: A sase workspace is a numbered clone of a project's primary repo, managed by the workspace store and tracked in that project's registry.json. _[agent]: A sase agent is an agent session or a single agent that does not belong to a session. _[prompt]: A prompt is the text SASE hands to a provider to start an agent turn. _[macro]: A macro is a reusable prompt triggered with #name in agent prompts. _[Patch]: A Patch is SASE's local unit of change. _[bead]: A bead is one tracked work item in SASE's git-native issue tracker. _[plan]: A plan is a persisted, approved artifact recording the intent behind agent work. _[TUI]: The TUI is SASE's terminal interface for running agents and reviewing results. _[epic]: An epic is an executable multi-phase plan run with sase bead work. _[gate]: A sase gate is a durable, command-backed request for one user decision, created when work must pause for the user. _[monitor]: A sase monitor is a monitor turn that runs one long command under a detached supervisor, so the command outlives the agent that started it. *[stitch]: A stitch is the lightweight ordered change record inside a Patch's STITCHES: section.