Onboarding
New engineers don’t struggle to set up their machines or reproduce CI behavior locally
Aspect CLI is a programmable task runner built on top of Bazel that extends the build system with powerful workflow automation capabilities. At its core is AXL (Aspect Extension Language), a Starlark dialect designed to fill the gaps in Bazel’s developer experience.
| Feature | Description |
|---|---|
| What it is | Task runner and developer workflow automation for Bazel |
| Language | AXL - a Starlark dialect with extended capabilities |
| License | Apache 2.0 (fully open source) |
| Current Version | 2025.19.5+ |
| Repository | github.com/aspect-build/aspect-cli |
| Implementation | Rust (rewritten from Go in 2025.42) |
Bazel excels at three core capabilities:
query/cquery)aquery)build/test)However, Bazel has significant gaps when it comes to developer workflows. As the Aspect CLI README explains:
Bazel is extensible, but only for defining build rules that produce additional output files. It falls short on customizing developer workflows. In fact the Bazel commands not mentioned above are not-so-excellent attempts at adding a few developer workflows for use within Google.
The reality is that many organizations end up scripting around Bazel with brittle shell scripts, and these local development scripts inevitably drift from CI testing scripts. This demonstrates a missing layer: a task runner built on top of Bazel’s query and build primitives.
Aspect CLI addresses these common pain points:
Onboarding
New engineers don’t struggle to set up their machines or reproduce CI behavior locally
Productivity
Product engineers regain control over their own productivity
Consistency
DevInfra teams can integrate tooling into every developer’s routine, not just CI
Maintainability
Say goodbye to brittle Bash wrappers and delete your Makefile
Aspect CLI installs alongside Bazel (it no longer shadows the bazel command as it did in versions before 2025.42):
# Using Homebrewbrew install aspect-build/aspect/aspect
# Or download directly from GitHub releases# https://github.com/aspect-build/aspect-cli/releasesAfter installation, you’ll have both bazel and aspect commands available:
# Run vanilla Bazelbazel build //...
# Run Aspect CLI (with AXL extensions)aspect build //...Aspect CLI uses several configuration files:
| File | Purpose |
|---|---|
MODULE.aspect | Module-level dependency configuration for AXL extensions |
.aspect/config.axl | Repository configuration script |
.aspect/*.axl | Auto-discovered task definitions |
.aspect/modules/*/ | Local AXL modules |
.aspect/user/*.axl | User-specific tasks (typically gitignored) |
AXL (Aspect Extension Language) is a dialect of Starlark designed specifically for extending Bazel’s functionality. Just as .bzl files provide Bazel-specific standard libraries for build rules, .axl files provide extension points for developer workflows.
According to Aspect’s documentation:
AXL is a dialect of Starlark, the language you already know from Bazel BUILD files and .bzl rules. This lets you write extensions that are deterministic, with no hidden side effects, no unbounded execution.
AXL files use the .axl extension. To enable syntax highlighting in VS Code:
Settings > Files > Associations: Add .axl and .aspect mapped to starlark
Every AXL task follows this pattern:
def impl(ctx: TaskContext) -> int: """Task implementation that receives context and returns exit code.""" print("Hello from AXL!") return 0
my_task = task( implementation = impl, args = {}, # Define command-line arguments)Source: This pattern is demonstrated in /Users/adsc/dev/refs/aspect-cli/.aspect/user-task.axl
TaskContext is the primary interface for AXL tasks, providing access to:
def impl(ctx: TaskContext) -> int: # Access to arguments provided by the caller target = ctx.args.target_pattern
# Access to Bazel functionality build = ctx.bazel.build("//...")
# Standard library (filesystem, process, I/O, environment) home = ctx.std.env.home_dir() ctx.std.fs.write("/tmp/output.txt", "content")
# HTTP client for network operations response = ctx.http().get(url="https://example.com").block()
# Template rendering (Jinja2, Handlebars, Liquid) result = ctx.template.jinja2("Hello, {{ name }}!", {"name": "World"})
# Task configuration (if defined) config = ctx.config
return 0AXL provides a rich argument system:
# From /Users/adsc/dev/refs/aspect-cli/crates/axl-runtime/src/builtins/aspect/build.axl
build = task( implementation = impl, args = { # Positional arguments (like target patterns) "target_pattern": args.positional(minimum=1, maximum=512, default=["..."]),
# String flags (--flag=value) "bazel_flag": args.string_list(), "bazel_startup_flag": args.string_list(), "bes_backend": args.string_list(), "bes_header": args.string_list(), })Available argument types:
| Type | Description | Example |
|---|---|---|
args.positional() | Positional arguments | aspect build //target |
args.string() | Single string flag | --config=release |
args.string_list() | Repeatable string flag | --flag=a --flag=b |
args.boolean() | Boolean flag | --verbose or --verbose=true |
args.int() / args.uint() | Integer flags | --jobs=4 |
args.trailing_var_args() | Capture remaining args | aspect run target -- extra args |
Tasks can be organized into groups for better CLI organization:
# From /Users/adsc/dev/refs/aspect-cli/.aspect/axl.axl
axl = task( group = ["tests"], # Shows under 'aspect tests axl' implementation = impl, args = {})# From /Users/adsc/dev/refs/aspect-cli/.aspect/modules/dev/build.axl
build = task( implementation = _build_impl, group = ["dev"], # Shows under 'aspect dev build' args = { "target_pattern": args.positional(minimum=1, maximum=512, default=["..."]), })Tasks can define configuration schemas that can be customized:
# From /Users/adsc/dev/refs/aspect-cli/.aspect/user-task.axl
UserTaskConfig = record( message=field(str, "hello world"), count=field(int, 1), customize_message=field(typing.Callable[[str], str], default=lambda s: s),)
def _impl(ctx: TaskContext) -> int: # Access configuration for i in range(ctx.config.count): print(ctx.config.customize_message(ctx.config.message)) return 0
user_task = task( group = ["user"], implementation = _impl, args = {}, config = UserTaskConfig(), # Default configuration)Configuration can be customized in .aspect/config.axl:
# From /Users/adsc/dev/refs/aspect-cli/.aspect/config.axl
def config(ctx: ConfigContext): # Customize a task's configuration for task in ctx.tasks: if task.name == "user_task" and task.group == ["user"]: task.config = UserTaskConfig( message = "hello axl", count = 2, customize_message = lambda s: s + "!" )AXL provides a non-blocking API for Bazel builds:
# From /Users/adsc/dev/refs/aspect-cli/crates/axl-runtime/src/builtins/aspect/build.axl
def impl(ctx: TaskContext) -> int: # Build with options build = ctx.bazel.build( *ctx.args.target_pattern, build_events = True, # Enable Build Event Stream flags = ["--isatty=" + str(int(ctx.std.io.stdout.is_tty))], startup_flags = [], )
# Wait for completion build_status = build.wait() return build_status.codeKey build() parameters:
| Parameter | Type | Description |
|---|---|---|
*targets | str | Target patterns to build |
build_events | bool | list[BuildEventSink] | Enable BES streaming |
workspace_events | bool | Enable workspace events |
execution_logs | bool | Enable execution logs |
flags | list[str] | Bazel command flags |
startup_flags | list[str] | Bazel startup flags |
inherit_stdout | bool | Pass through stdout |
inherit_stderr | bool | Pass through stderr (default: True) |
current_dir | str | None | Working directory |
AXL can process the Build Event Stream for custom UIs:
# From /Users/adsc/dev/refs/aspect-cli/.aspect/modules/dev/lib/tui.axl
def _fancy_tui(ctx: TaskContext, build: bazel.build.Build) -> int: events = build.build_events() in_flight = {}
for timer in forever(tick_ms): event = events.try_pop()
if event != None: if event.kind == "target_configured": in_flight[event.id.label] = timer
elif event.kind == "target_completed": start = in_flight.pop(event.id.label, None) print("Built {} in {} ticks".format(event.id.label, (timer - start)))
elif event.kind == "progress": ctx.std.io.stdout.write(event.payload.stdout) ctx.std.io.stderr.write(event.payload.stderr)
if events.done(): return build.wait().codeCommon build event types:
build_started - Build initializationconfiguration / configured / target_configured - Configuration eventstarget_completed - Target finished buildingprogress - Progress messages (stdout/stderr)AXL provides a fluent API for Bazel queries:
# From /Users/adsc/dev/refs/aspect-cli/.aspect/modules/dev/query.axl
def _query_impl(ctx: TaskContext) -> int: # Simple query using raw expression build = ctx.bazel.query().raw(ctx.args.pattern[0]) for result in build.eval(): print(result.name) return 0Fluent query API:
# Query dependencies of a targetdeps = ctx.bazel.query().targets("//myapp:main").deps()all_deps = deps.eval()
# Chain multiple operationssources = ctx.bazel.query().targets("//myapp:main") \ .deps() \ .kind("source file") \ .eval()
# Complex intersection query using raw expressioncomplex = ctx.bazel.query().raw("deps(//foo) intersect kind('test', //bar:*)")
# Path-based querypath_query = ctx.bazel.query().raw("somepath(//start, //end)")The test API mirrors the build API:
# From /Users/adsc/dev/refs/aspect-cli/crates/axl-runtime/src/builtins/aspect/test.axl
def _test_impl(ctx: TaskContext) -> int: test = ctx.bazel.test( *ctx.args.target_pattern, build_events = True, flags = ["--isatty=" + str(int(ctx.std.io.stdout.is_tty))], startup_flags = [], )
build_status = test.wait() return build_status.codeThe std module provides cross-platform system access.
def impl(ctx: TaskContext) -> int: env = ctx.std.env
# Directory information home = env.home_dir() # User's home directory current = env.current_dir() # Current working directory root = env.root_dir() # Project root (where MODULE.aspect lives) temp = env.temp_dir() # System temp directory
# System information os = env.os() # "linux", "macos", "windows" arch = env.arch() # "x86_64", "aarch64"
# Environment variables path = env.var("PATH") # Get variable (returns None if not set) all_vars = env.vars() # List of (name, value) tuples
# Aspect CLI information version = env.aspect_cli_version() exe = env.current_exe()
return 0# From /Users/adsc/dev/refs/aspect-cli/.aspect/axl.axl (comprehensive test suite)
def test_fs(ctx: TaskContext) -> int: fs = ctx.std.fs
# Path checks if fs.exists("/path"): if fs.is_file("/path"): content = fs.read_to_string("/path") elif fs.is_dir("/path"): entries = fs.read_dir("/path")
# File operations fs.write("/path/file.txt", "content") fs.copy("/src", "/dst") fs.rename("/old", "/new") fs.hard_link("/original", "/link") fs.remove_file("/path/file.txt")
# Directory operations fs.create_dir("/path/dir") fs.create_dir_all("/path/deep/nested/dir") fs.remove_dir("/empty/dir") fs.remove_dir_all("/dir/with/contents")
# Metadata meta = fs.metadata("/path") # meta.is_file, meta.is_dir, meta.is_symlink # meta.size, meta.modified, meta.accessed, meta.created # meta.readonly
sym_meta = fs.symlink_metadata("/path") # Don't follow symlinks
# Read directory contents for entry in fs.read_dir("/path"): print(entry.path, entry.is_file, entry.is_dir)
return 0def impl(ctx: TaskContext) -> int: proc = ctx.std.process
# Spawn process with piped I/O child = proc.command("cat") \ .stdin("piped") \ .stdout("piped") \ .spawn()
stdin = child.stdin() stdout = child.stdout()
stdin.write("Hello from AXL!\n") stdin.flush() stdin.close() # Signal EOF
output = stdout.read_to_string() status = child.wait()
print("Output:", output) print("Exit code:", status.code)
return status.codedef impl(ctx: TaskContext) -> int: io = ctx.std.io
# Check if connected to a terminal if io.stdout.is_tty: # Use fancy terminal output io.stdout.write("\033[32mGreen text\033[0m\n") else: # Plain output io.stdout.write("Plain text\n")
io.stdout.flush() io.stderr.write("Error message\n") io.stderr.flush()
# Read from stdin data = io.stdin.read(1024)
return 0AXL includes a built-in HTTP client for network operations.
def impl(ctx: TaskContext) -> int: http = ctx.http()
# GET request response = http.get( url = "https://api.example.com/data", headers = {"Authorization": "Bearer token"} ).block()
print("Status:", response.status) print("Body:", response.body)
# POST request response = http.post( "https://api.example.com/data", headers = {"Content-Type": "application/json"}, data = '{"key": "value"}' ).block()
return 0# From /Users/adsc/dev/refs/aspect-cli/.aspect/axl.axl
def test_http(ctx: TaskContext) -> int: url = "https://raw.githubusercontent.com/aspect-build/aspect-cli/refs/heads/main/LICENSE" expected_sha256 = "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594" expected_integrity = "sha256-DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ="
# Download with SHA256 verification (Bazel-style hex format) resp = ctx.http().download( url = url, output = "/tmp/license.txt", mode = 0o644, sha256 = expected_sha256 ).block()
# Download with SRI integrity verification resp = ctx.http().download( url = url, output = "/tmp/license2.txt", mode = 0o644, integrity = expected_integrity ).block()
return 0AXL supports three template engines for generating content.
result = ctx.template.jinja2( "Hello, {{ name }}! You have {{ count }} messages.", {"name": "World", "count": 42})# Result: "Hello, World! You have 42 messages."result = ctx.template.handlebars( "Hello, {{name}}!", {"name": "World"})# Result: "Hello, World!"result = ctx.template.liquid( "Hello, {{ name }}!", {"name": "World"})# Result: "Hello, World!"AXL uses load statements similar to Starlark, with security-focused restrictions.
Source: /Users/adsc/dev/refs/aspect-cli/docs/load.md
# Relative paths (from current file's directory)load("./utils.axl", "helper_function")load("../shared/common.axl", "shared_function")
# Repository-root relative pathsload("path/to/script.axl", "function")
# Module paths (from vendored modules)load("@module_name/path.axl", "function")Security restrictions:
/ (no absolute OS paths)// (double slashes)The MODULE.aspect file configures AXL dependencies:
# From /Users/adsc/dev/refs/aspect-cli/MODULE.aspect
# Local development moduleaxl_local_dep( name = "demo", path = ".aspect/modules/demo", auto_use_tasks = True,)
axl_local_dep( name = "dev", path = ".aspect/modules/dev", auto_use_tasks = True,)
# Remote module from GitHub archiveaxl_archive_dep( name = "aspect_rules_lint", urls = ["https://github.com/aspect-build/rules_lint/archive/65525d8...tar.gz"], integrity = "sha512-TGcxutWr8FwxrK3G+uthb...", strip_prefix = "rules_lint-65525d871f677071877d3ea1ec096499ff7dd147", auto_use_tasks = True, dev = True,)
# Manual task importuse_task(".aspect/user/user-task-manual.axl", "user_task_manual")AXL provides a built-in command for adding dependencies:
# Add latest releaseaspect axl add gh:owner/repo
# Add specific versionaspect axl add gh:owner/repo@v1.0.0
# With custom nameaspect axl add gh:owner/repo --name=custom_nameSource: The axl add implementation in /Users/adsc/dev/refs/aspect-cli/crates/axl-runtime/src/builtins/aspect/axl_add.axl shows the full workflow:
axl_archive_dep() to MODULE.aspectAXL includes experimental WebAssembly support for extending tasks with compiled code.
# From /Users/adsc/dev/refs/aspect-cli/examples/dummywasm/.aspect/dummy.axl
def impl(ctx: TaskContext) -> int: wasm_path = ctx.std.env.current_dir() + "/dummy.wasm"
# Instantiate WASM module dummy = ctx.wasm.instantiate(wasm_path) memory = dummy.get_memory("memory")
# Initialize runtime (required for Go wasip1 modules) dummy.start()
# Call exported WASM functions result = dummy.exports.get_dummy_json()
# Decode packed pointer/length (Go wasmexport pattern) ptr = result & 0xFFFFFFFF length = (result >> 32) & 0xFFFFFFFF
# Read data from WASM linear memory json_bytes = memory.read(ptr, length) print("JSON data:", str(json_bytes))
return 0You can import Starlark functions as WASM host functions:
# host_funcs.axl - Must be frozen (loaded from separate file)def get_magic_number() -> int: return 42
def add_numbers(a: int, b: int) -> int: return a + b# From /Users/adsc/dev/refs/aspect-cli/examples/dummywasm/.aspect/host_test.axl
load("./host_funcs.axl", "get_magic_number", "add_numbers")
def impl(ctx: TaskContext) -> int: wasm_path = ctx.std.env.current_dir() + "/host_test.wasm"
# Instantiate with host function imports instance = ctx.wasm.instantiate( wasm_path, imports = { "env": { "get_magic_number": get_magic_number, "add_numbers": add_numbers, } }, )
instance.start()
# Call WASM functions that use host imports magic = instance.exports.call_get_magic() # Returns 42 sum_result = instance.exports.call_add(10, 32) # Returns 42
return 0Both AXL and BXL are Starlark-based extension languages for build systems, but they serve different ecosystems.
| Feature | AXL (Aspect CLI) | BXL (Buck2) |
|---|---|---|
| Build System | Bazel | Buck2 |
| Primary Purpose | Developer workflow automation | Graph introspection and analysis |
| File Extension | .axl | .bxl |
| Starlark Interpreter | starlark-rust | starlark-rust |
| Task Definition | task() function | bxl() function |
| Graph Access | Query API, Build Events | Direct graph introspection |
| Caching | Via Bazel | Built-in incremental caching |
| Stability | Early preview (stable early 2026) | Mostly stable |
BXL focuses on graph introspection:
According to Buck2’s BXL documentation:
BXL is a Starlark-based script that enables integrators to inspect and interact with the Buck2 graph… Introspection of the Buck2 graph can occur at the unconfigured, configured, providers, and action stages.
Key BXL capabilities:
AXL focuses on workflow automation:
According to Aspect’s AXL page:
AXL enables developers to “fill Bazel usability gaps” by creating custom extensions that address workflow challenges not natively supported by Bazel.
Key AXL capabilities:
Use AXL when:
Use BXL when:
Aspect Build publishes extensions at github.com/aspect-extensions.
rules_lint provides first-class linting in Bazel:
axl_archive_dep( name = "aspect_rules_lint", urls = ["https://github.com/aspect-build/rules_lint/archive/...tar.gz"], integrity = "sha512-...", strip_prefix = "rules_lint-...", auto_use_tasks = True,)Then run:
aspect lint //...aspect format //...Key features:
Supported linters include:
To create your own extension:
.axl filestask() functionaspect axl add gh:owner/repoThe .aspect/config.axl file runs during CLI initialization.
# From /Users/adsc/dev/refs/aspect-cli/.aspect/config.axl
def config(ctx: ConfigContext): # Access standard library home = ctx.std.env.home_dir()
# HTTP client http = ctx.http()
# Template rendering result = ctx.template.jinja2("...", {})
# WASM support (experimental) wasm = ctx.wasm
# Iterate and modify tasks for task in ctx.tasks: print(task.name, task.group)
# Add new tasks dynamically ctx.tasks.add(my_custom_task)
# Customize existing task configuration for task in ctx.tasks: if task.name == "my_task": task.config = MyConfig(option = "value")# From /Users/adsc/dev/refs/aspect-cli/.aspect/config.axl
def _user_task_impl(ctx: TaskContext) -> int: print("I am a task added by .aspect/config.axl") return 0
_user_task = task( name = "user-task-added-by-config", group = ["user"], implementation = _user_task_impl, args = {})
def config(ctx: ConfigContext): ctx.tasks.add(_user_task)| Capability | Vanilla Bazel | With Aspect CLI |
|---|---|---|
| Custom commands | Shell scripts | AXL tasks |
| Progress UI | Fixed format | Customizable via BES |
| Multiple BES backends | Single | Multiple |
| Build event processing | External tools | Built-in AXL API |
| Package management | http_archive | aspect axl add |
| Code formatting | External tools | aspect format |
| Linting | External tools | aspect lint |
| Template rendering | None | Jinja2, Handlebars, Liquid |
| HTTP client | None | Built-in |
| WASM support | None | Experimental |
Moving from shell scripts to AXL:
Before (Makefile):
.PHONY: buildbuild: bazel build //...
.PHONY: testtest: bazel test //... --test_output=errors
.PHONY: lintlint: ./scripts/run_linters.shAfter (AXL):
def _build_impl(ctx: TaskContext) -> int: build = ctx.bazel.build("//...", build_events=True) return build.wait().code
build = task( implementation = _build_impl, args = {"target_pattern": args.positional(default=["//..."])})
def _test_impl(ctx: TaskContext) -> int: test = ctx.bazel.test( "//...", flags = ["--test_output=errors"], build_events = True ) return test.wait().code
test = task( implementation = _test_impl, args = {"target_pattern": args.positional(default=["//..."])})The Aspect CLI repository is organized into several Rust crates:
Source: /Users/adsc/dev/refs/aspect-cli/crates/
| Crate | Purpose |
|---|---|
aspect-cli | Main CLI binary and task runner |
aspect-launcher | Fetches and manages CLI versions |
aspect-telemetry | Telemetry collection |
axl-runtime | AXL interpreter and built-in functions |
axl-lsp | Language Server Protocol for AXL |
axl-docgen | Documentation generator |
axl-proto | Protocol buffer definitions |
build-event-stream | Bazel BES parsing |
galvanize | Build system abstraction |
pty-multiplex | PTY multiplexing for terminal UI |
.axl file in .aspect/ directory root is loadedauto_use_tasks = True export their tasksuse_task() in MODULE.aspect for explicit importsconfig()aspect <command> [args] │ ▼┌──────────────────┐│ aspect-launcher │ ─── Resolves CLI version└────────┬─────────┘ │ ▼┌──────────────────┐│ aspect-cli │ ─── Loads MODULE.aspect└────────┬─────────┘ │ ▼┌──────────────────┐│ .aspect/config │ ─── Runs config(ctx)└────────┬─────────┘ │ ▼┌──────────────────┐│ Task lookup │ ─── Matches command to task└────────┬─────────┘ │ ▼┌──────────────────┐│ Task execution │ ─── Runs impl(ctx)└────────┬─────────┘ │ ▼┌──────────────────┐│ Bazel calls │ ─── build/test/query└──────────────────┘Here’s a complete example of a custom CI task that builds, tests, and reports results:
"""CI task that builds, tests, and generates a report."""
def _ci_impl(ctx: TaskContext) -> int: io = ctx.std.io fs = ctx.std.fs
targets = ctx.args.targets output_dir = ctx.args.output_dir or "./ci-output"
# Ensure output directory exists if not fs.exists(output_dir): fs.create_dir_all(output_dir)
print("=" * 60) print("CI Pipeline Starting") print("=" * 60) print("")
# Step 1: Build print("Step 1: Building targets...") build = ctx.bazel.build( *targets, build_events = True, flags = ["--keep_going"], )
build_events = [] for event in build.build_events(): if event.kind == "target_completed": build_events.append({ "target": event.id.label, "success": event.payload.success if hasattr(event.payload, "success") else True }) elif event.kind == "progress": io.stderr.write(event.payload.stderr)
build_status = build.wait() print("Build completed with exit code:", build_status.code) print("")
if build_status.code != 0: print("Build failed, skipping tests") return build_status.code
# Step 2: Test print("Step 2: Running tests...") test = ctx.bazel.test( *targets, build_events = True, flags = ["--test_output=errors", "--keep_going"], )
test_results = [] for event in test.build_events(): if event.kind == "test_result": test_results.append({ "target": event.id.label, "status": event.payload.status, }) elif event.kind == "progress": io.stderr.write(event.payload.stderr)
test_status = test.wait() print("Tests completed with exit code:", test_status.code) print("")
# Step 3: Generate report print("Step 3: Generating report...") report = { "build_status": build_status.code, "test_status": test_status.code, "build_events": build_events, "test_results": test_results, }
report_content = ctx.template.jinja2("""# CI Report
## Build Summary- Exit Code: {{ build_status }}- Targets Built: {{ build_events | length }}
## Test Summary- Exit Code: {{ test_status }}- Tests Run: {{ test_results | length }}
## Build Details{% for event in build_events %}- {{ event.target }}: {{ "PASS" if event.success else "FAIL" }}{% endfor %}
## Test Details{% for result in test_results %}- {{ result.target }}: {{ result.status }}{% endfor %} """, report)
report_path = output_dir + "/ci-report.md" fs.write(report_path, report_content) print("Report written to:", report_path)
return test_status.code
ci = task( description = "Run CI pipeline: build, test, and generate report", implementation = _ci_impl, args = { "targets": args.positional(minimum = 1, default = ["//..."]), "output_dir": args.string(default = "./ci-output"), })Run with:
aspect ci //my/project/...aspect ci //... --output_dir=/tmp/reportsaspect-extensions topic: github.com/topics/aspect-extensions| Version | Date | Changes |
|---|---|---|
| 2025.42+ | Nov 2025 | Rust rewrite, AXL replaces gRPC plugins |
| 2025.19.5 | Current | Latest stable release |
| Pre-2025.42 | Legacy | Go implementation (maintenance mode at aspect-cli-legacy) |