mlld
Version:
mlld: llm scripting language
756 lines (631 loc) • 91 kB
Markdown
# Changelog
All notable changes to the mlld project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0-rc68]
### Fixed
- Template executables detect JSON-looking strings and wrap them as structured values, so downstream pipelines receive native objects instead of escaped text (#435).
- Foreach iteration normalizes stage outputs that stringify JSON and passes parsed arrays/objects forward, restoring the behaviour users expect from `| @json` inputs.
- `/show` array rendering unwraps structured elements to their `.data` view when possible, keeping canonical text intact for load-content metadata and structured JSON displays.
## [2.0.0-rc67]
### Fixed
- Pipelines sanitize JSON-like shell output by escaping control characters inside string literals, so `/run` stages that echo newline-bearing JSON feed structured data forward correctly.
## [2.0.0-rc64]
### Fixed
- **Alligator section parsing with "as" substring**: Fixed grammar bug where section names containing "as" (like "Gotchas", "Installation", "Basic Usage") were rejected
- Changed `AlligatorSectionChar` rule from `!"as"` to `!(" as")` to only exclude the `as` keyword when used for renaming
- Section syntax like `<file.md # Gotchas>` now works correctly
- Distinguishes between section names with "as" and the rename keyword: `<file.md # Section> as "New Title"`
- Added test coverage in `tests/cases/slash/show/alligator-section-as-substring/`
- Preserved structured pipeline outputs across chained executables by wrapping JSON-like strings returned from JS/Node stages, preventing downstream stages from receiving `[object Object]` text (#435).
- Updated run/exec structured handling and regression fixtures so batch/parallel pipelines, foreach separators, and retry fallbacks assert native arrays/objects instead of stringified JSON, closing the remaining gaps from #435 user scenarios.
## [2.0.0-rc63]
### Fixed
- Fixed local resolver to recognize all mlld extensions as modules when they contain directives. Previously only .mlld.md files were explicitly treated as modules, causing "Import target is not a module" errors when importing .mld files via custom resolver prefixes like @context/.
- Improved content type detection to parse file contents for mlld directives across all module extensions, maintaining backward compatibility for files with non-standard extensions that contain valid mlld code.
- Missing `--tag` on cli added
- Update docs to cover modules, registry, resolvers
### Added
- Batch and condensed pipeline stages now receive the structured wrapper instead of raw strings, so helpers can work with native arrays/objects without JSON.parse.
- **Custom tag support for publishing**: `mlld publish --tag <name>` allows publishing modules with custom version tags
- Publish with beta/alpha tags: `mlld publish module.mld --tag beta`
- Tag validation ensures alphanumeric + hyphens only, 2-50 character length
- Reserved tags (`latest`, `stable`) are rejected with clear error messages
- Users can import using custom tags: `/import { @helper } from @alice/utils@beta`
## [2.0.0-rc62]
### Added
- **Batch pipelines for collection expressions**: `for` and `foreach` now accept a trailing `=> |` pipeline that runs after iteration completes. The batch phase reuses standard pipeline syntax, applies to the gathered array, and may return arrays, scalars, or objects. Grammar attaches the pipeline to `ForExpression.meta.batchPipeline` and `ForeachCommandExpression`, and the interpreter processes the results via `processPipeline()` before emitting the final variable or display output.
### Notes
- Batch pipelines behave like condensed pipelines: each stage receives string input, so helpers that expect arrays should parse the string back to JSON. Currently parallel groups (`||`) share the same semantics but are not fully supported/tested.
## [2.0.0-rc61]
### Added
- **Loose JSON parsing modes**: `@json` now accepts relaxed JSON syntax (single quotes, trailing commas, comments) using JSON5, with explicit `@json.loose` and `@json.strict` variants for opting in or enforcing strict parsing. Error messages direct users to the loose mode when strict parsing fails.
### Fixed
- **Structured data handling in field access**: Fixed array operations on nested StructuredValue wrappers
- Field access now properly unwraps nested StructuredValue before array operations
- Fixes potential runtime errors with deeply nested structured data (e.g., `@nested[0]` where `@nested` is a wrapped array)
- Related to #435 structured data edge cases
- Fixed in `interpreter/utils/field-access.ts:477` and `:248`
- **Exec invocation stdin handling**: Fixed stdin coercion missing StructuredValue unwrapping
- Exec invocations now properly unwrap StructuredValue when preparing stdin data
- Aligns with run.ts stdin handling (same pattern as the golden standard)
- Prevents double-wrapping or incorrect stringification of structured values passed via stdin
- Related to #435 structured data edge cases
- Fixed in `interpreter/eval/exec-invocation.ts:49`
- **Shell interpolation of structured values**: Complex arrays/objects now survive shell argument quoting
- Shared `classifyShellValue` helper drives `/run` and `@exe` stdin/argument coercion
- Interpolation tracks both single- and double-quoted spans, avoiding `[object Object]` and broken quoting
- File-content fixtures confirm literal `$`, `` ` ``, and quotes reach the shell intact
- Covers regressions from #435 user scenario
- **Variable assignment with wrapped values**: Fixed String() conversions producing [object Object]
- Variable assignments now use `valueToString()` helper that checks for StructuredValue wrappers
- Uses `asText()` helper for StructuredValue wrappers instead of naive String() conversion
- Applies fix to 7 locations in var.ts where String() was used on complex values (lines 725, 751, 763, 773, 782, 820, 823)
- Variable type detection now properly unwraps StructuredValue before Array.isArray() checks (3 locations: lines 719, 745, 757)
- Related to #435 structured data edge cases
- Fixed in `interpreter/eval/var.ts`
## [2.0.0-rc60]
### Fixed
- **Shell command interpolation with nested arrays**: Fixed arrays of objects/arrays being converted to `[object Object]` in shell commands
- Shell command context (e.g., `echo @array`) now properly JSON-stringifies complex array elements
- Previously `String(object)` produced `[object Object]`, breaking data flow through shell executables
- Example: `/exe @func(e) = run { echo @e }` now correctly outputs JSON for nested arrays
- Fixes remaining edge case from #435 (https://github.com/mlld-lang/mlld/issues/435#issuecomment-3386904732)
- Addressed instances of old mlld.lock.json file expectations throughout codebase
## [2.0.0-rc59]
### Changed
- **CLI commands aligned with new config file naming**: Updated all CLI commands to reference the new dual-file configuration system
- `mlld-config.json`: User-editable project settings (dependencies, preferences, resolver configuration)
- `mlld-lock.json`: Auto-generated lock file (versions, hashes, sources)
- Replaced `mlld.lock.json` references throughout CLI commands and help text
- Commands updated: `setup`, `alias`, `run`, `init-module`
- Editor integrations updated: Neovim LSP, VS Code Language Server
- Backward compatibility maintained: LSP and editor tooling check for old `mlld.lock.json` as fallback
- All commands now use `ProjectConfig` abstraction
## [2.0.0-rc58]
### Fixed
- **Foreach with structured values**: `foreach` now unwraps StructuredValue arguments
- Previously failed with "got structured text" when array came from pipeline
- Example: `/var @chunked = @data | @chunk(2)` then `foreach @process(@chunked)` now works
- Aligns with JavaScript stages which already unwrap automatically
## [2.0.0-rc57]
### Added
- **MCP server**: `mlld mcp` serves exported `/exe` functions as MCP tools
- Exposes functions over JSON-RPC stdio transport
- Default discovery: `llm/mcp/` directory when no path specified
- Config modules: `--config module.mld.md` exports `@config = { tools?, env? }`
- Environment overrides: `--env KEY=VAL` (MLLD_ prefix required)
- Tool filtering: `--tools tool1,tool2` or via config
- Duplicate tool names halt with error showing conflicting sources
- Example: `/exe @greet(name) = js { return \`Hello ${name}\`; }` becomes `greet` tool
### Changed
- **Data flow between stages**: Native types preserved throughout pipelines
- Loaders return parsed data: `<data.json>` yields object, not JSON string
- Pipeline stages pass arrays/objects directly: `@data | @process` receives native type
- JavaScript functions receive parsed values without `JSON.parse()`
- Templates and output convert to text automatically
- Fixes #435
### Breaking
- Remove `JSON.parse()` calls in JavaScript stages - will fail on already-parsed data
- Use `.text` to access stringified data, `.data` to get structured data in string context
- Pipelines expecting JSON strings will receive objects/arrays instead
## [2.0.0-rc56]
### Added
- **Import Types System**: Control how modules and resources are resolved
- `module` imports: Pre-installed registry modules (offline after install)
- `static` imports: Content embedded at parse time (zero runtime cost)
- `live` imports: Always fresh data (fetched every execution)
- `cached(TTL)` imports: Smart caching with time limits (5m, 1h, 7d, etc.)
- `local` imports: Direct access to development modules in `llm/modules/`
- Example: `/import module { api } from @corp/tools`, `/import cached(1h) <https://api.example.com> as @data`
- **Module management**:
- `mlld install @author/module`: Install modules from public registry
- `mlld update`: Update modules to latest compatible versions
- `mlld outdated`: Check for available updates
- `mlld ls`: View installed modules with status and sizes
- Registry integration with CDN-distributed module catalog
- **Configuration Files**:
- `mlld-config.json`: Your project settings (dependencies, preferences)
- `mlld-lock.json`: Auto-generated locks (versions, hashes, sources)
- **Simplified Development Workflow**:
- Use `/import local { helper } from @author/module` to access modules in `llm/modules/` using published name (if you are @author or can publish to private @author registry)
- Useful for iterating on modules before publishing
### Changed
- Import syntax now requires `@` prefix on imported names: `/import { @helper } from module`
- Module publishing requires explicit `/export { ... }` manifests
- Import failures now stop execution (exit code 1) instead of continuing
- Smart import type inference based on source patterns
- Pipelines support leading `||` operator for immediate parallel execution: `/var @result = || @a() || @b() || @c()` runs all three functions concurrently
- Leading parallel syntax works in `/var`, `/run`, and `/exe` definitions
- Pipeline concurrency controls: `(n, wait)` shorthand syntax and `with { parallel: n, delay: wait }` for caps and pacing
### Fixed
- Module installation fetches from real registry instead of placeholders
- Version resolution respects "latest" tags and semantic versioning
- Module integrity verified with SHA-256 hashes
## [2.0.0-rc55]
### Added
- Stdin support for `/run` directive and `/exe` definitions:
- New syntax: `/run { command } with { stdin: @variable }` passes data directly via stdin without shell escaping
- Pipe sugar: `/run @data | { command }` normalizes to `with { stdin: @data }` for cleaner syntax
- Works in executable definitions: `/exe @func(data) = run { command } with { stdin: @data }`
- Pipe sugar in executables: `/exe @func(data) = run @data | { command }`
- Eliminates JSON double-stringification when passing structured data to commands like `jq`, `cat`, etc.
- Preserves shell safety while enabling proper JSON/CSV/XML data flow through pipelines
- JSON data access pattern for JavaScript functions (addresses #428):
- `.data` and `.json` accessors parse JSON strings during variable evaluation before passing to functions
- `.text` and `.content` accessors preserve original string content
- Eliminates need for manual `JSON.parse()` calls in JavaScript functions
- Works consistently across files, variables, and command output
- Example: `/var @json = '{"items": []}'; /run @process(@json.data)` passes parsed array to function
- Native mlld functions in pipelines:
- `/exe` functions using `for` and `foreach` constructs now work as pipeline stages
- Fixes "Unsupported code language: mlld-foreach" errors
- Enables seamless composition: `/var @result = @data.data | @filterNative | @transformJS | @json`
- Mixed pipelines with native mlld, JavaScript, and shell commands all work together
## [2.0.0-rc54]
### Added
- Expose structured module dependency resolution with `ModuleInstaller.resolveDependencies` so CLI flows reuse aggregated metadata.
- Add dependency summaries across install/update/outdated/info commands via shared helper, with optional dev-dependency inclusion.
- Introduce `cli/utils/dependency-summary.ts` to normalize runtime/tool/package output and conflict warnings.
### Changed
- Cache modules with structured needs/dependency metadata to avoid re-parsing frontmatter.
- `ResolverManager` persists structured metadata when fetching modules, enabling downstream analysis.
### Added
- Directive execution guard suppresses `/run`, `/output`, and `/show` while modules import, eliminating unintended side effects.
- Imported executables and templates now capture their module environment so command references resolve sibling functions consistently.
- Registry module imports now enforce `mlld.lock` versions, failing fast on mismatches while remaining backward-compatible with legacy lock entries.
- Explicit `/export { ... }` manifests for modules: grammar, AST, evaluation, and import pipeline honour declared bindings while falling back to auto-export for manifest-less files.
- Import collision protection surfaces `IMPORT_NAME_CONFLICT` with precise locations when multiple directives bind the same name, covering both namespace and selective imports.
- End-to-end fixture ensures exported shadow-environment helpers retain access to nested helpers and mlld functions across module boundaries.
- Inline template loops: `/for … /end` inside templates
- Supported in backticks and `::…::` templates; line-start only for both `/for` and `/end` within the template body
- Not supported in `:::…:::` or `[[…]]` templates
- Interpreter uses existing TemplateForBlock evaluation; no changes to runtime semantics outside template contexts
- AST selectors in alligator expressions `<file.ext { methodName (variable) }>` covering JavaScript, TypeScript, Python, Go, Rust, Ruby, Java, C#, Solidity, C, and C++.
### Fixed
- Foreach templates now keep long numeric strings intact during interpolation
- Command-reference executables now preserve array and object types when passing arguments to nested functions (previously JSON.stringify'd them)
- Imported arrays preserve array behaviour after module import, so `.length` and `/for` iteration no longer fail after crossing module boundaries
- Triple-colon template exports keep their template metadata, rendering `{{ }}` placeholders and leaving `<@...>` markers unaltered when imported
- JavaScript `@` syntax misuse surfaces the educational guidance even when V8 reports "Unexpected token", keeping the fix-it copy visible
- Regression fixtures cover imported arrays, triple-colon imports, triple alligator literals, and JS `@` misuse to prevent regressions
## [2.0.0-rc53]
### Fixed
- Large integers were getting wrongly rounded by js auto-parsing
## [2.0.0-rc52]
### Fixed
- `::: {{var}} :::` template syntax had issues with <alligators>.
## [2.0.0-rc51]
### Fixed
- Language Server transport defaults to stdio when no explicit flag is provided
- Prevents Neovim startup error: "Connection input stream is not set"
- Preserves VS Code behavior when it passes `--stdio` (or other transports)
## [2.0.0-rc50]
### Added
- **`mlld nvim-setup` command**: Auto-configure Neovim LSP support
- Detects Neovim setup (LazyVim, vanilla, etc.) and creates appropriate config
- Cross-platform: uses `where` on Windows, `which` on Unix
- Alias: `mlld nvim` for convenience
- **LSP/Editor updates**: Semantic tokens cover pipeline parallel groups (`||`), with.pipeline (incl. nested) and `with { format: ... }`, and `/for parallel`; directive/completion tweaks include `/log` and format values. VS Code extension runs semantic-only (legacy providers removed); fallback TextMate grammar highlights `parallel` and `format`.
- Expression system completeness:
- when-expressions usable in `/var` assignments, array literals, and function arguments
- `when` allowed in `for` collection RHS; `none => skip` filters non-matches
- `foreach` allowed in `/exe` RHS; executable foreach callable like any other function
- `/show foreach @func(@arrays)` with `with { separator, template }` formatting options
### Fixed
- #411: Nested `/for` collection returns `[]` for empty arrays in both plain `/show` and when piped to `@json`. Removes accidental `{}` output.
- `isLoadContentResultArray` does not match untagged empty arrays; prevents misclassification of generic empty arrays.
## [2.0.0-rc49]
### Added
- **Pipeline parallel groups**: `A || B || C` executes commands concurrently as a single stage
- With-clause parity: nested arrays represent a parallel group (e.g., `with { pipeline: [ [@left, @right], @combine ] }`)
- Concurrency capped by `MLLD_PARALLEL_LIMIT` (default `4`); results preserve declaration order and flow to the next stage as a JSON array string
- **Rate-limit resilience in pipelines**: 429/"rate limit" errors trigger exponential backoff with bounded retries per stage
- **Unified effect attachment**: Single helper attaches inline builtin effects (show/log/output) to preceding stages and to each branch of parallel groups
- **/for parallel execution**: Parallel iteration with optional cap and pacing
- Default cap from `MLLD_PARALLEL_LIMIT`; override per loop: `/for 3 parallel @x in @items => ...`
- Optional pacing between starts: `/for (3, 1s) parallel @x in @items => ...` (units: ms, s, m, h)
- Directive form streams effects as iterations complete; collection form preserves input order in results
### Fixed
- **Retry in parallel groups**: Returning `retry` from within a parallel group rejects with a clear error (retry is unsupported inside the group)
- **Parallel limit hardening**: `MLLD_PARALLEL_LIMIT` parsing clamps invalid/low values to defaults; limit is read per execution to respect environment overrides
### Documentation
- Updated developer docs for parallel execution: shorthand `||` rule (no leading `||`), with-clause nested group syntax, effect behavior on groups, and references to tests
- Updated iterator docs to include `/for parallel` with cap overrides and pacing; clarified iterator vs pipeline parallelism and rate-limit behavior
## [2.0.0-rc48]
### Added
- **Large variable support for bash/shell executors**: Automatic handling of variables exceeding Node.js environment limits
- Shell mode (`/run sh {...}`) automatically injects large variables directly into scripts, bypassing Node's ~128KB limit
- Works transparently - use `$varname` as usual, mlld handles the injection method based on size
- Enabled by default via `MLLD_BASH_HEREDOC` (can be disabled if needed)
- Configurable threshold via `MLLD_MAX_BASH_ENV_VAR_SIZE` (default: 131072 bytes)
### Fixed
- **E2BIG errors with large data**: Fixed Node.js throwing errors when passing large variables to shell commands
- Common when loading entire codebases: `<**/*.js>`, `<**/*.sol>`, etc.
- Affects audit workflows processing multiple files simultaneously
- Simple `/run {...}` commands now provide helpful error messages suggesting shell mode
### Documentation
- Updated large variables documentation with clearer, more accessible language
- Removed unnecessary configuration details since feature is enabled by default
- Added explanation of why shell mode works (direct script injection vs environment passing)
## [2.0.0-rc47]
### Added
- e2e tests for method chaining and templates
- Deprecation tracker and DeprecationError
- Deprecation notice for array dot notation
### Changed
- Interpolation precedence for quotes/templates
### Fixed
- Post-field/index on execs across contexts
- Tail pipeline on builtin methods
- Template method calls
## [2.0.0-rc46]
### Fixed
- **Method calls in when conditions**: Fixed grammar bug preventing method calls on function results in `/when` and `/exe...when` conditions
- **CommendRef interpolation issue**: Fixed grammar bug preventing full interpolation of values inside quotes/templates inside executables
## [2.0.0-rc45]
### Added
- **Builtin methods for arrays and strings**: Common JavaScript methods available on variables
- Array methods: `.includes(value)`, `.indexOf(value)`, `.length()`, `.join(separator)`
- String methods: `.includes(substring)`, `.indexOf(substring)`, `.length()`, `.toLowerCase()`, `.toUpperCase()`, `.trim()`, `.startsWith(prefix)`, `.endsWith(suffix)`, `.split(separator)`
- Methods work with both literal and variable arguments: `@list.includes("item")` or `@list.includes(@search)`
- Implemented as field access exec patterns, treated as ExecInvocations internally
- Example: `/show @fruits.includes("banana")` returns `true` if the array contains "banana"
- Eliminates need for JavaScript wrappers for common operations
- **External template file support**: `.att` and `.mtt`
- `.att` (at template): interpolates `@vars` and `<file.md>` references
- `.mtt` (mustache template): interpolates `{{vars}}` (simple mustache‑style)
- Define as executables: `/exe @name(params) = template "path/to/file.att|.mtt"`
- Invoke with arguments: `/show @name("val1", "val2")`
- **Testing improvements**:
- Basic documentation tests to ensure published docs have valid syntax
- Performance test suite
### Changed
- `/import` no longer accepts `.att`/`.mtt`. Importing these files emits an educational error with the proper usage example (use `/exe ... = template "path"`).
### Fixed
- **Incorrect docs:** Corrected errant syntax in docs, added testing infrastructure for ensuring published docs' syntax is always valid.
- **when-expression `none` condition evaluation**: Fixed bug where variable assignments prevented `none` conditions from executing
- Variable assignments (`@var = value`) in when expressions are now correctly treated as side effects, not return values, enabling the `none` condition to execute when no value-producing actions match (e.g., `show`, function calls, `retry`). Most importantly, conditions that only assign variables no longer prevent `none` from executing when later conditions don't match
- **Triple-colon template interpolation in executables (#379)**: Fixed bug where triple-colon templates with `{{var}}` syntax weren't being interpolated when passed as arguments to executable functions
- **Undefined variable syntax preservation**: Fixed bug where undefined variables in triple-colon templates incorrectly displayed as `@varname` instead of preserving the original `{{varname}}` syntax
- **Parser incorrectly matching variables in plain text**: Fixed 3+ month old bug where `{{var}}` syntax was being parsed as variable references in plain text/markdown content
## [2.0.0-rc44]
### Fixed
- when-expression in `/exe`: local assignments now visible to subsequent actions; conditions evaluate against accumulated env.
- Effect streaming restored for when-actions; `show` tagged and handled pipeline-aware to avoid unintended echoes at stage end.
- Pipeline retries with `show` in stage: preserve attempt output and continue by forwarding prior input; final stage suppresses echo.
- `/run` output handling hardened: always stringified before newline; mlld-when returns unwrap tagged `show` for expected echo.
### Tests
- Add fixture verifying local assignment visibility within `/exe` when-expressions.
## [2.0.0-rc43]
### Added
- **`--allow-absolute` flag**: Override project root restrictions for file access
- Permits loading files from absolute paths outside project directory
- Applies to `<file>` syntax, `/path` directives, and `/import` statements
- Security opt-in: default behavior maintains project root isolation
- Persists in `mlld.lock.json` under `security.allowAbsolutePaths` when configured
## [2.0.0-rc42]
### Fixed
- **Removed command timeout restrictions for LLM workflows**: Completely removed 30-second timeout limits from all command executors
- LLM commands can now run as long as needed without timing out
- Previously, commands would silently fail after 30 seconds, causing issues with large prompts or complex reasoning tasks
- Affects all shell commands, JavaScript execution, and Node.js subprocess execution
## [2.0.0-rc41]
### Fixed
- **CLI markdown streaming and document output**: Fixed effects system to properly handle markdown content in CLI output (#342)
- CLI now displays markdown content progressively during execution (streaming mode)
- `/output "file.md"` directive correctly outputs complete document including both markdown and directive results
- Markdown content from mlld files is now included in CLI output alongside directive results
- Updated test expectations to reflect correct behavior with preserved newlines from markdown content
- Added basic architectural docs for effects system
## [2.0.0-rc40]
### Added
- **`/log` directive support in action contexts**: Extended `/log` to work in for loops and when blocks
- `/for @item in @items => log @item` - Log each item during iteration
- `/when [ condition => log "message" ]` - Log in when block actions
- Produces identical output to `/output to stdout` with cleaner syntax
- Grammar implementation follows DRY principles using existing OutputSource patterns
- **Pipeline inline effects**: Builtins `| log`, `| output`, and `| show` work as inline effects
- Attach to preceding functional stage, re-run on each retry attempt
- `log` outputs to stderr, `output` to files/streams/env, `show` to document
- `output to file` resolves `@base/...` and relative paths from project root
- **Pipeline context variables**: New `@ctx` and `@p`/`@pipeline` variables in pipelines
- `@ctx`: Lightweight per-stage context with `try`, `tries`, `stage`, `input`, `hint`, `lastOutput`, `isPipeline`
- `@p`/`@pipeline`: Array-like outputs with positive/negative indexing, `@p[-1]` gets latest output
- `@p.retries.all` provides full retry history for audit trails
- **Retry hints**: The `retry` action can now carry hints to the next attempt
- String hints: `retry "need more detail"`
- Object hints: `retry { temperature: 0.8 }`
- Function hints: `retry @somefunc(@input)`
- Access via `@ctx.hint` in the retried stage
- **Effect architecture**: Complete overhaul of how side effects (show, output, log directives) are handled
- New EffectHandler system for managing output operations
- Immediate effect execution in for loops and when blocks
- Effects in exe+when blocks called from for expressions now execute immediately
- Progress messages appear in real-time during long-running operations
- **Automatic JSON parsing**: Shell commands returning JSON are now automatically parsed into objects/arrays
- Eliminates need for manual `JSON.parse()` calls when working with APIs and JSON-returning commands
- Configurable via `MLLD_AUTO_PARSE_JSON` environment variable (defaults to enabled)
- **Shell alias resolution**: Automatic resolution of shell aliases in command execution
- Commands like `claude`, `ll`, `la` now work in mlld scripts when defined as shell aliases
- Configurable via `MLLD_RESOLVE_ALIASES` environment variable (defaults to enabled)
- Debug output available with `MLLD_DEBUG_ALIASES=true` to see alias resolution in action
- **Fixed `none` keyword in when expressions**: Corrected bug where `none` was always executing
- The `none` keyword now properly executes only when no other conditions match
- Affects when expressions used in `/exe` functions (e.g., `/exe @func() = when [...]`)
- Side effects in when expressions now work correctly without duplication
### Fixed
- **Grammar ordering for `/when` bare blocks**: Fixed PEG parser ordering issue preventing bare `/when [...]` blocks from working
- `/when [ condition => action ]` now works correctly with all action types including `log`
- **`/show` directive in for loops**: Fixed `/show` not working properly in for loops
- Show directives now emit output immediately during iteration
- **Field access in `/output` directive grammar**: Fixed field access bug when outputting object fields
- `/output @data.field to "file.txt"` now correctly outputs just the field value
- **LoadContentResult metadata preservation**: Auto-unwrap shelf preserves metadata across JS transforms
- Files loaded with `<file.md>` retain their metadata properties after JS transformations
- #362: field access for special variables (@input, @ctx)
### Internal
- **AST-based `@base` handling**: Now properly resolved for file I/O and show paths
- **Stage numbering**: Stages are 1-indexed and count only functional transforms (builtins don't create stages)
## [2.0.0-rc39]
### Added
- **`/log` directive**: New syntactic sugar for `/output to stdout` for more concise console output (#357)
### Fixed
- **When expression behavior**: Bare `when` expressions now correctly evaluate ALL matching conditions
- Previously, `when [...]` in `/exe` functions incorrectly stopped at the first match (switch-like behavior)
- Now properly evaluates all conditions and returns the last matching value
- Added support for `when first [...]` modifier for explicit switch-case semantics
- Fixed doubled output from `/show` directives in for loops with when expressions
- Side effects (show, output directives) inside when expressions now execute correctly
- **Field access in /output directive source**: Fixed field access not working when outputting object fields
- `/output @data.content to "file.txt"` now correctly outputs just the field value, not the entire object
### Changed
- **No longer supporting `/` on RHS**:
- Previously mlld allowed slashes in directives on the right side (`=> /show` or `= /run` etc)
- Removed to emphasize the `/` is purposeful meaning "start of line interpreted as mlld"
- Now if you use `/` on RHS, you get an educational error explaining the `/` is only for start of line
- **When expression semantics**: Clear distinction between bare `when` and `when first`
- `when [...]` - Evaluates ALL matching conditions, returns last value
- `when first [...]` - Stops at first match (classic switch behavior)
- Updated 11 test files that expected switch-like behavior to use `when first`
- Grammar now properly supports `when first` modifier in `/exe` expressions
### Added
- **None keyword for /when blocks**: New `none` keyword that matches when no other conditions have matched
- Provides semantic fallback: `/when [ @x > 5 => show "high", none => show "default" ]`
- Multiple `none` conditions allowed at end of block: all execute in bare `/when`, first executes in `/when first`
- Works in `/exe` when expressions: `/exe @handler() = when: [ @valid => @value, none => "fallback" ]`
- Must appear as the last condition(s) in a when block (validated at parse time)
- Cannot appear after wildcard `*` (would be unreachable)
- Clearer than using `*` or complex negations like `!(@a || @b || @c)`
- **Test coverage for when expressions**: New test demonstrating bare `when` evaluates all conditions
- `tests/cases/valid/slash/when/exe-when-all-matches/` shows the difference between `when` and `when first`
## [2.0.0-rc38]
### Added
- Error enhancement system for JS errors
### Fixed
- For loop bugs / missing implementation details
## [2.0.0-rc37]
### Added
- **Nested For Loops**: The `/for` directive now supports nesting for multi-dimensional iteration
- Nest multiple for loops: `/for @x in @outer => for @y in @inner => show "@x-@y"`
- Unlimited nesting depth: Can chain any number of for loops together
- Each nested loop maintains its own scope with access to parent variables
- Works with all for loop features: arrays, objects (with `_key` access), and expressions
- Example triple nesting: `/for @x in ["A", "B"] => for @y in [1, 2] => for @z in ["X", "Y"] => show "@x-@y-@z"`
- Enables complex iteration patterns for data processing and code review automation
### Fixed
- **Array Literal Evaluation**: Fixed interpreter to properly handle array literal nodes from grammar
- Objects with `type: 'array'` from the grammar are now correctly evaluated as arrays
- Enables literal arrays in for loops: `/for @x in [1, 2, 3]` now works properly
## [2.0.0-rc36]
### Added
- **Array Slice Operations**: Native array slicing syntax for extracting subsets of arrays
- Basic slicing: `@array[0:5]` extracts items from index 0 to 5 (exclusive)
- Negative indices: `@array[-3:]` gets last 3 items, `@array[:-1]` gets all except last
- Open-ended slices: `@array[2:]` from index 2 to end, `@array[:3]` from start to index 3
- Works with all array types including LoadContentResult arrays from glob patterns
- Preserves metadata through slice operations (e.g., `<*.md>[0:5]` maintains file metadata)
- Grammar foundation laid for future filter operations (`@array[?field>value]` syntax reserved)
### Fixed
- **Shell Command Validation**: Replaced buggy regex-based shell operator detection with proper `shell-quote` library
- Fixed false positives where legitimate `>` characters in content were incorrectly flagged as dangerous redirects
- Pipes (`|`) continue to work correctly for command chaining
- Removed overly restrictive blocking of redirect operators (`>`, `>>`, `<`) since they only affect local files
- Dangerous operators (`&&`, `||`, `;`, `&`) remain blocked to prevent command injection and zombie processes
- Improved error messages now show the rejected command and suggest using `/run sh { ... }` for less restrictive execution
- Resolves issues with multiline content containing angle brackets being rejected
## [2.0.0-rc35]
### Added
- **Pipeline Context Variable**: The `@pipeline` context variable provides access to pipeline execution state
- Array indexing: `@pipeline[0]` (pipeline input), `@pipeline[1]` (first stage output), `@pipeline[-1]` (previous stage output)
- Retry tracking: `@pipeline.try` increments with each retry attempt (starts at 1)
- Stage information: `@pipeline.stage` shows current pipeline stage number
- Output history: `@pipeline.length` indicates number of completed stages
- Attempt history: `@pipeline.tries` array contains all retry attempts for current stage
- **Pipeline Retry Mechanism**: The `retry` keyword enables automatic re-execution of pipeline stages
- Return `retry` from functions to re-execute the previous pipeline stage
- Access attempt number via `@pipeline.try` (starts at 1, increments with each retry)
- Guard retries with conditions to prevent infinite loops: `@pipeline.try < 3 => retry`
- All retry attempts stored in `@pipeline.tries` array for best-of-N selection patterns
- Each retry context limited to 10 attempts, with global limit of 20 retries per stage
- Works seamlessly with `/exe` functions using `when` expressions for validation logic
- Example: `/exe @validate() = when: [@isValid(@_) => @_, @pipeline.try < 3 => retry, * => null]`
- Simplified architecture: Only the immediately previous stage can be retried (no nested retries)
- **Issue #342 – Pipeline whitespace and stacked pipes**:
- Outside templates/quotes, pipelines now support spaced and multi-line stacked forms for variables and `<file>` values
- Inside templates/quotes/interpolation, condensed-only `|@transform` remains supported adjacent to the value
- Node-level attachment: pipelines attach to the value node (variable or load-content), not directive tail
- Added fixtures under `tests/cases/valid/feat/pipeline/*`; updated grammar unit tests accordingly
- Optional-whitespace pipelines outside templates now support full arguments via `CommandArgumentList` (objects, arrays, nested execs, and variable field access like `@var.field`)
- Introduced dedicated TemplatePipe (no-args) for template contexts; template pipes do not accept arguments to avoid ambiguity
- Internal grammar cleanup: consolidated non-template pipe handling under the optional-whitespace form; condensed-pipe pattern retained only for template interpolation
- **When/Exe syntax improvements**:
- Optional colon support for `/when` block and match forms, and for `/exe` RHS when expressions
- `when [ ... ]` works alongside `when: [ ... ]` (backward compatible)
- Grammar support for switch-style `/exe` when-expression modifier: `/exe @fn() = when first [ ... ]`
- Modifier is parsed and attached to `WhenExpression.meta.modifier`
- Interpreter behavior for `first` in exe when-expressions will land in the next release
### Fixed
- **Pipeline State Management**: Enhanced state tracking across pipeline stages with proper attempt counting and history preservation
- **Issue #341 - `/exe...when` RHS Actions**: `/exe` functions with `when:` expressions now support all `/when` RHS actions (show, variable assignment, output, function calls) with local variable semantics
- **Removed `/var...when`**: Eliminated redundant feature in favor of more capable `/exe...when`
- **Unified Template/Quote Grammar**: Consolidated duplicate grammar patterns
- **Prohibited Implicit Executables in `/when` RHS**: Removed ability to define executables within when actions for cleaner separation
- **Field access in with-clause pipeline arguments**: Fixed evaluation of field access (e.g., `@p.try`) in `with { pipeline: [...] }` arguments by using multi-field access resolution; resolves "Unknown field access type: undefined" during pipeline execution
- **LoadContentResult metadata preservation in pipelines**: Metadata (filename, frontmatter, etc.) now automatically preserved when LoadContentResult objects pass through JavaScript transformations in pipelines
- Single files: Auto-reattachment of metadata to transformed content
- Arrays: Exact content matching restores metadata where possible
- Transparent to JS functions - they receive content strings as before
- Enables patterns like `<file.md> | @transform` where `@transform` result still has `.filename` property available
## [2.0.0-rc34]
### Added
- **Array Index Support in For Loops**: The `_key` pattern
now provides array indices when iterating over arrays
- Arrays provide their indices as keys: `0`, `1`, `2`, etc.
- Example: `/for @item in ["a", "b", "c"] => /show
"@item_key: @item"` outputs `0: a`, `1: b`, `2: c`
- Objects continue to provide property names as keys
- Enables consistent key access patterns across all
collection types
- **Dot Escape Sequence**: Added `\.` to escape sequences for
literal dots in strings
- Disambiguates between field access and string
concatenation
- `@variable.field` - attempts to access the `field`
property
- `@variable\.txt` - produces the string value followed by
`.txt`
- Works in all string contexts: double quotes, backticks,
and templates
- Example: `/output @content to "file-@num\.txt"` creates
`file-42.txt`
- **Metadata Shelf for Alligator Arrays**: Preserves LoadContentResult metadata when arrays pass through JavaScript functions
- When `<*.md>` arrays are passed to JS functions like `slice()`, metadata (filename, frontmatter, etc.) is preserved
- Enables patterns like: `/var @subset = @slice(@files, 0, 5)` followed by `/for @file in @subset => /show @file.filename`
- Transparent to JS functions - they receive content strings as before
- Fixes issue where `@file.filename` would fail after JS array operations
### Fixed
- **Missing Slash in For Actions**: Fixed syntax error on
line 18 of `llm/run/testing.mld` where `/show` was missing
its slash prefix
- **LoadContentResult Preservation in For Loops**: For loops now properly preserve LoadContentResult objects
- `@file` in `/for @file in <*.md>` maintains its properties (filename, content, fm, etc.)
- Field access like `@file.filename` works correctly in all for loop contexts
## [2.0.0-rc33]
### Added
- **Wildcard (*) Literal**: New wildcard literal that always evaluates to true in conditional contexts -- specifically useful as a catch-all in a multiple condition /when sequence in order to be more immediately understandable than '/when... true'
- Basic usage: `/when * => /show "Always executes"`
- Default handler in when blocks: `/when [@condition => action, * => "default"]`
- Catch-all pattern in exe functions: `/exe @handler() = when: [* => "default response"]`
- Works with logical operators: `/when * && @check => action`
- Evaluates to true in ternary expressions: `/var @result = * ? "yes" : "no"`
- Follows Unix glob convention where `*` means "match anything"
### Fixed
- **Template Variable References**: Fixed parsing bug where tail modifier keywords (`with`, `pipeline`, `needs`, `as`, `trust`) were incorrectly interpreted inside template contexts
- Created separate `TemplateVariableReference` pattern for template interpolation that doesn't check for tail modifiers
- Keywords like "with" can now appear as literal text after variables in templates
- Fixes: `/exe @claude(prompt,tools) = `@prompt with @tools`` now parses correctly
- Affects backtick templates, double-colon templates, and double-quoted strings
- Template variables should never have tail modifiers - those constructs only make sense in command contexts
- **Shell Escaping in /for Loops**: Fixed shell command escaping issues when iterating over arrays with special characters
- Loop variables are now properly quoted when used in shell commands
- Handles filenames with spaces, quotes, and other special characters correctly
- Example: `/for @file in <*.md> => /run echo "@file"` now works with "file with spaces.md"
- **Nested Function Execution**: Fixed execution of nested functions in imported modules
- Functions like `@module.category.function()` now execute correctly instead of returning string representations
- Deeply nested module exports are now properly resolved as executable functions
- Affects complex module structures with multiple levels of organization
## [2.0.0-rc32]
### Added
- **For Loop Iteration**: New `/for` directive for iteration over arrays and objects
- Output form: `/for @item in @collection => action` - Executes action for each item
- Collection form: `/var @results = for @item in @collection => expression` - Collects results into array
- Array iteration: `/for @item in ["a", "b", "c"] => /show @item`
- Object iteration: `/for @value in {"x": 1, "y": 2} => /show @value`
- Object key access: `@value_key` pattern provides access to keys when iterating objects
- Works with all iterable values including globs: `/for @file in <*.md> => /show @file.filename`
- Preserves variable type information throughout iteration for consistent behavior
- Semantic token support in LSP for syntax highlighting
- Compatible with pipelines and transformations
## [2.0.0-rc31]
### Added
- **Enhanced error display with source context**: Errors now show the exact source location with surrounding context and a visual pointer
- Compiler-style error messages with line numbers and caret indicators pointing to the precise error location
- **Improved Error Pattern System**: Complete refactor of parse error enhancement for better performance and maintainability
- Patterns are now pure functions that extract variables (no imports allowed)
- Templates use `${VARIABLE}` placeholders for dynamic error messages
- Build-time compilation: All patterns compile into single `parse-errors.generated.js` file
- Convention-over-configuration pair of `pattern.js`, `error.md`, and `example.md`
- Build integration: `npm run build:errors` compiles all patterns
- **LSP Semantic Tokens Support**: Full semantic highlighting via Language Server Protocol
- Context-aware highlighting for all template types (backtick, double-colon, triple-colon)
- Proper interpolation detection based on template context (@var vs {{var}})
- Command content interpolation with @variable support
- Field access and array indexing highlighting (@user.profile.name, @items[0])
- Embedded language region marking for editor syntax injection
- Mixed array/object support - highlights mlld constructs within data structures
- Operator highlighting for logical (&&, ||, !), comparison (==, !=, <, >), and ternary (? :)
- Error recovery and graceful handling of partial ASTs
- Performance optimizations with text caching
- Available in VSCode and any LSP-compatible editor (Neovim, etc.)
- **Enhanced LSP Error Reporting**: Precise error locations and improved error messages
- Errors now use exact start/end positions from parser's mlldErrorLocation data
- Full-line highlighting when errors occur at the beginning of a line
- Multi-line error messages display with proper formatting in VSCode
- Parser error messages can be edited directly in the grammar files
- Example error messages include all valid syntax patterns
## [2.0.0-rc30]
This release allows mlld to function as a logical router
### Added
- **Logical and Comparison Operators in Expressions**
- New operators for `/var` assignments and `/when` conditions: `&&`, `||`, `==`, `!=`, `!`, `?`, `:`
- Expression parsing with proper operator precedence: `@a && @b || @c` parses as `((@a && @b) || @c)`
- Ternary conditional expressions: `/var @result = @test ? @trueVal : @falseVal`
- Binary expressions with comparison: `/var @isEqual = @x == @y`, `/var @different = @a != @b`
- Unary negation: `/var @opposite = !@condition`
- Parentheses for explicit precedence: `/var @complex = (@a || @b) && (@c != @d)`
- Full expression support in when conditions: `/when @tokens > 1000 && @mode == "production" => /show "High usage detected"`
- Short-circuit evaluation: `&&` and `||` operators properly short-circuit for performance
- Type coercion following mlld semantics: `"true" == true` → true, `null == undefined` → true
- Comparison operators: `<`, `>`, `<=`, `>=` for numeric comparisons
- **Implicit When Actions**
- Simplified syntax within `/when` blocks - directive prefix is now optional
- Variable assignments: `/when @prod => @config = "production"` (no `/var` needed)
- Function calls: `/when @ready => @setupDatabase()` (no `/run` needed)
- Exec assignments: `/when @processing => @transform() = @processData(@input)` (no `/exe` needed)
- Mixed implicit/explicit actions in blocks: `/when @cond => [@x = "value", /var @y = "other"]`
- **RHS When Expressions (Value-Returning)**
- When expressions as values in `/var` assignments: `/var @greeting = when: [@time < 12 => "Good morning", @time < 18 => "Good afternoon", true => "Good evening"]`
- When expressions in `/exe` definitions: `/exe @processData(type, data) = when: [@type == "json" => @jsonProcessor(@data), @type == "xml" => @xmlProcessor(@data), true => @genericProcessor(@data)]`
- First-match semantics - returns the first matching condition's value
- Returns `null` when no conditions match
- Lazy evaluation in variables - re-evaluates on each access
- Pipeline support: `/var @result = when: [...] | @uppercase`
- **Enhanced String Interpolation**
- Fixed file reference interpolation in double-quoted strings: `"Content from <file.md> here"`
- Consistent handling of both `@variable` and `<file.md>` interpolation patterns
- Proper support for `wrapperType: 'doubleQuote'` in interpreter evaluation
- Safety checks prevent empty value arrays from causing "missing value" errors
### Changed
- **Hybrid console.log behavior in JavaScript execution**
- `console.log()` now always outputs to stdout for debugging visibility
- When a function has an explicit return value, that value is stored in the variable
- When a function has no return value but uses console.log, the console output becomes the result (backward compatibility)
- This approach maintains compatibility with existing tests while providing better debugging experience
- Example: `js { console.log("debug"); return "result" }` shows "debug" on stdout and stores "result"
- Example: `js { console.log("output") }` shows "output" on stdout AND stores "output" as the result
### Fixed
- **Grammar and Parser Improvements**
- Fixed CommandReference type mismatches between grammar output and TypeScript expectations
- Added translation layer in evaluators to handle both legacy and new AST formats
- Improved error recovery and backward compatibility for when directive patterns
- **Test Infrastructure Stability**
- Updated test expectations to align with new console.log behavior
- Fixed test cases that relied on specific output formatting
- Resolved shadow environment test issues with variable interpolation in literal strings
## [2.0.0-rc28]
### Fixed
- **ImportResolver PathContext issue in ephemeral mode**
- Fixed TypeError when running mlld scripts via `npx mlldx@latest` with local file paths
- ImportResolver was not receiving PathContext when Environment.setEphemeralMode() recreated it
- Ephemeral mode now properly passes PathContext to ImportResolver