Editor Superpowers for Phobos β Introducing the Language Server and VS Code Extension
Writing a Phobos pipeline template used to be a "save, run, read the error, fix, repeat" loop. Today we're shipping the Phobos Language Server and VS Code extension β a pair of releases that close that loop in your editor. Type-mismatch on a variable, an unresolved reference to a sibling task, a deprecated namespace, a missing plugin block β every one of these surfaces as you type, with a one-click fix wherever the LSP can synthesise one.
This post is a tour of what the language server and extension actually do, what's new in this release (notably first-class support for .pbvars variables files), and how to get started.
What you getβ
Diagnostics that catch problems before the pipeline runsβ
The language server runs the same validation Phobos itself runs β parse errors, missing required attributes, schema violations, cross-field constraints (interval requires attempts, deployments require environment, mount_point volumes must be declared), stage_order correctness, duplicate names, reserved keywords β plus a dozen Phobos-specific checks the editor is uniquely positioned to surface:
- Unresolved
var.<X>,jwt.<X>,vcs_token.<X>,volume.<X>references. - Structural typos in
pipeline.*/this.*/phobos.*paths anchored to the specific bad segment (pipeline.stage.s.task.t.outp.xflagsoutpdirectly, not the whole expression). - Missing
plugin "<name>" {}configuration blocks for every action that needs one β including the bundledexecplugin. - Plugin-alias resolution:
action { plugin = <type>.<alias> }traversals that don't resolve, plus the "you must pick one" ambiguity case. - Dead action references (
this.action.<X>.outputs.*where<X>isn't declared in the same task). - Unused
variable,jwt,vcs_token,volumedeclarations. - Deprecated
phobos.*andaction_outputs.*references, rendered with strikethrough.
Every diagnostic is anchored at the precise expression range so a long file doesn't bury the cursor under a wall of text.
Context-aware completionβ
Block types, attribute names, enumerated string values, HCL built-in functions, and the named-value namespaces (var., jwt., vcs_token., system., pipeline., this.) all complete with type hints and inline documentation. Inside dependencies = [...] you get the names of sibling tasks, deployments, and nested pipelines in the same stage and phase. Inside action { plugin = <cursor> } you get every declared <type>.<alias> qualified name.
Hover documentationβ
Hover any block, attribute, action label, named value, or output reference to see the schema docs (with type, allowed values, and limits), the description from the plugin schema (for action attributes), or the resolved target β pipeline.stage.<s>.task.<t>.outputs.<o> follows the output "<o>" block back to its declaration.
Code actions and quick-fixesβ
When the LSP knows how to fix a diagnostic, it surfaces a Ctrl+. action:
- Install plugin β runs the right
phobos plugin installcommand, including any--versionconstraint pinned inplugin_requirements. - Declare missing
plugin "<name>" {}block β inserts the block at the natural anchor (afterplugin_requirementsif present, else after the lastpluginblock, else at top of file). - Declare aliased plugin block β when an action references an undeclared
plugin = <type>.<alias>. - Set plugin =
<type>.<alias>β one action per declared alias when the type has only aliased instances and the action must pick one explicitly. - Migrate deprecation β rewrites legacy
phobos.*/action_outputs.*references to the currentpipeline.*/this.action.*shape. Bridgingoutput "<o>"blocks are inserted atomically when needed. - Extract to variable / inline variable β refactors that move a literal in or out of a
variableblock. - Source-fix-all β the editor's "fix everything" key applies every preferred deprecation fix in one undoable edit.
Inlay hints and other nicetiesβ
Inlay hints add quiet annotations the cursor never lands on: variable types, variable defaults, per-task action + output counts (: 2 actions, 1 output), per-stage children counts (: 3 tasks, : 2 tasks, 1 pipeline), deployment environments, and per-action plugin-instance badges (: docker.builder when the action sets plugin = docker.builder). Each category is independently toggleable.
The extension also wires up:
- Pipeline Graph webview β a layered DAG of stages, tasks, deployments, and
dependencies = [...]edges. Click a node to jump to its declaration. - Evaluated Context panel β a live tree of every namespace the evaluator exposes at the cursor.
- Templates tree β every
pipeline.hcl/release_lifecycle.hclin the workspace expanded to its outline. - Installed Plugins tree β the LSP's plugin inventory, with a right-click "Browse Online" to open the plugin's registry page.
- Unified Phobos status chip β one glanceable token consolidating language-server state, CLI presence + version, plugin health, diagnostic counts, and active-file kind.
Snippetsβ
Built-in snippets cover the most common shapes β full templates (pipeline, multipipe), block scaffolds (stage, task, taskdeps, taskretry, pre, post, action, output, var, plugins, jwt, vcs, nested), and gates (approval for when = "manual" + approval_rules).
New in this release: .pbvars filesβ
Phobos has always supported file-based variable input (analogous to Terraform's .tfvars); this release wires that file format into the LSP as a first-class document type. Open a prod.pbvars next to a pipeline.hcl and you get:
- Diagnostics against the sibling template β unknown variable names error out, type mismatches against the declared
typeproduce friendly messages (variable "replicas" has type number but was assigned a value of type string), and unresolved references inside values surface HCL's own evaluation diagnostic. - Snippet-based completion β declared variable names from the sibling are filtered by typed prefix and inserted as typed snippets shaped to each variable's declared type.
stringinserts quotes-with-cursor-between,numberinserts a0placeholder selected for one-keystroke overwrite,boolinserts atrue/falsechoice dropdown,object({...})types pre-populate every declared field as its own tab stop with=aligned. Already-assigned variables are filtered out so you don't accidentally re-declare them. - Hover β surface the sibling variable's
description, declaredtype, and default-state.
The cross-file analysis is live β edits to the sibling template propagate to open .pbvars diagnostics, completions, and hover without saving first.
variable "environment" {
type = string
default = "staging"
}
variable "replicas" {
type = number
}
variable "service" {
type = object({
name = string
timeout = number
})
}
environment = "production"
replicas = 5
service = {
name = "checkout"
timeout = 30
}
The CLI's loading precedence (env vars β phobos.pbvars β *.auto.pbvars β --pb-var-file β --pb-var) hasn't changed; see Variables for the full ordering.
Mixed-tool workspacesβ
A common workspace has Phobos templates living next to Terraform, Nomad, or Packer files β all of them .hcl. The extension is intentionally narrow about which .hcl files it claims:
pipeline.hclandrelease_lifecycle.hclβ claimed by exact filename, always.*.pbvarsβ claimed by extension, always.- Any other
*.hclβ not claimed by default.
If you want every .hcl file to be treated as Phobos (e.g. a workspace that's exclusively Phobos), flip phobos.editor.associateAllHclFiles to true. Toggling on takes effect immediately for every currently-open .hcl document; toggling off only affects future opens.
Installationβ
VS Code, VSCodium, Cursor, and other VS Code-compatible editorsβ
The extension is published to two registries β pick the one your editor uses:
- VS Code Marketplace β for VS Code itself, plus Cursor, Kiro, and any other Microsoft-licensed VS Code distribution.
- Open VSX Registry β for VSCodium, Code OSS, Gitpod, Theia, and any other Eclipse-licensed VS Code fork that can't legally ship the Microsoft Marketplace.
Both registries publish the same VSIX. From the editor's Extensions view, search Phobos and install. Marketplace and Open VSX VSIXes ship without the language-server binary to keep the download small β on first launch the extension prompts:
Phobos language server not found. Install the latest version? [Install Latest] [Set Pathβ¦]
Choose Install Latest and the extension downloads, SHA-256-verifies, and installs the binary into your editor's global storage. You can rerun or upgrade from the command palette (Phobos: Install or Update Language Server).
For air-gapped setups, GitLab releases ship platform-specific VSIXes with the language-server bundled β install with code --install-extension mc-phobos-<version>-<platform>.vsix (or your editor's equivalent).
See the VS Code Extension guide for the full install matrix and configuration reference.
Neovim, Helix, Zed, Emacs, and other editorsβ
The language server is a standalone Go binary that speaks LSP over stdio. Install it from the language-server releases page (each release ships a sha256sums.txt manifest), or build from source with make build.
VERSION=0.1.0
PLATFORM=linux_amd64
BASE="https://gitlab.com/infor-cloud/martian-cloud/phobos/phobos-language-server/-/releases/${VERSION}/downloads"
curl -L -o phobos-language-server "${BASE}/phobos-language-server_${VERSION}_${PLATFORM}"
chmod +x phobos-language-server
sudo mv phobos-language-server /usr/local/bin/
Per-editor configuration snippets are in the Language Server guide β Neovim with nvim-lspconfig, Helix's languages.toml, Eglot for Emacs.
Under the hoodβ
Both the language server and extension are open source under MPL 2.0:
- phobos-language-server β Go, ~25k LoC. The same
pkg/hcl/configschema constants Phobos itself uses; a reflection-driven schema tree means new HCL-tagged fields onPipelineTemplate/LifecycleTemplateshow up in diagnostics, completion, and hover automatically. - vscode-phobos β TypeScript, hand-rolled LSP client + status-bar, tree views, and webviews. SHA-256-verified bundled binary on platform-specific releases.
The plugin-aware features (action-body diagnostics, completion, hover for installed plugins) work via the same gRPC Schema() endpoint the CLI uses; schemas are cached on disk so cold starts stay fast even when a plugin binary is gone or failing to spawn. A fsnotify watcher on ~/.phobos.d/plugins debounced at 500 ms means newly-installed plugins light up in the editor without any explicit refresh.
What's nextβ
Roadmap items we're already prototyping:
- Code-action quick fix for "not declared"
.pbvarsdiagnostics that adds thevariable "<name>" { ... }block to the sibling template. - Range-restricted type diagnostics β point at the offending sub-expression instead of the whole RHS for nested type errors inside object literals.
If you have a feature request, please open an issue on either repo. MRs welcome.
Get startedβ
- VS Code Extension guide β install matrix, settings reference, FAQ.
- Language Server guide β editor-agnostic setup for Neovim, Helix, Zed, Emacs, and beyond.
- Variables guide β including the new
.pbvarsreference. - HCL reference β syntax the LSP validates.