v0.4.0
Summary
- Docker sandboxing as an opt-in (beta) feature: disabled by default, with explicit config required to enable. New dedicated documentation page consolidates setup and options.
- Agent inbox (channels) added: workflows can send (
channel <- echo "Message") and receive (channel -> workflow) messages via named channels for async event handling. ensure [rule] recovernow passes failed rule output as a parameter ($1) to the recover block, enabling context-aware recovery.- Logging system overhaul:
lognow outputs to stdout (for informational messages) whilelogerris introduced to write errors/warnings to stderr. - Run artifact naming and persistence improved: sequence-prefixed filenames guarantee unique, ordered artifacts across subshells and loops.
- E2E test suite greatly expanded: high-level helpers now assert on exact run artifacts and CLI output, improving coverage and test clarity.
Full Changelog: v0.3.0...v0.4.0
All changes
ensure ... recovernow forwards failed ensure output to recover as$1— In recover loops, Jaiph now captures the failed ensure invocation output (stdout+stderr) and temporarily binds it to positional arg$1while executing the recover body. This enables patterns likeensure ci_passes recover { echo "$1"; ... }and allows recover logic to inspect failure details directly. Original workflow args are restored after each recover attempt. Existing bounded retry behavior andJAIPH_ENSURE_MAX_RETRIEShandling remain unchanged.- Docker sandboxing is now opt-in (beta) — Docker sandbox is no longer enabled by default on local machines.
runtime.docker_enableddefaults tofalsein all environments; setruntime.docker_enabled = trueorJAIPH_DOCKER_ENABLED=trueto enable it. The CI-specific default logic (CI=true→ disabled) is removed — the default is simplyfalseeverywhere. Docker sandboxing documentation is moved fromconfiguration.mdto a new dedicated page: Sandboxing. All references in README, getting-started, configuration, and the homepage now link to the sandboxing page and mark the feature as beta. - Dockerfile-based runtime image with pre-installed agent backends — When no explicit
docker_imageis configured, the runtime checks for.jaiph/Dockerfilein the workspace root. If present, it runsdocker buildand tags the result asjaiph-runtime:latest, using it instead of the defaultubuntu:24.04. A shipped.jaiph/Dockerfileis included with Node.js LTS, Claude Code CLI, cursor-agent, and standard utilities (bash, curl, git, ca-certificates) pre-installed. Agent authentication env vars (ANTHROPIC_API_KEY,CURSOR_*) are now forwarded into the Docker container alongsideJAIPH_*variables. When an explicit image is set viaJAIPH_DOCKER_IMAGEorruntime.docker_image, the Dockerfile is ignored. Without a.jaiph/Dockerfile, the runtime falls back toubuntu:24.04as before. NewresolveImage()andbuildImageFromDockerfile()functions indocker.ts.DockerRunConfiggains animageExplicitfield to distinguish default vs. configured images. New E2E test (73_docker_dockerfile_detection.sh) covers Dockerfile detection, explicit-image bypass, fallback, and env var forwarding. logwrites to stdout; newlogerrkeyword writes to stderr —log "message"now echoes to stdout (previously stderr), making its output capturable and separable from error output. A new keywordlogerr "message"mirrorslogbut writes to stderr. Both emit events on fd 3 for the progress tree. In the CLI tree,loglines display with a dimℹ(unchanged);logerrlines display with a red!. The runtime functions arejaiph::log(stdout +LOGevent) andjaiph::logerr(stderr +LOGERRevent). Parser, transpiler, types, and event handling updated. New E2E test (92_log_logerr.sh) validates stdout/stderr separation and.out/.errartifact content for both keywords.- Fix: Docker runs now persist artifacts on the host — When Docker mode was enabled, run artifacts (
.outfiles,run_summary.jsonl) were written to a host-only absolute path inside the container filesystem and lost ondocker run --rm. The root cause:JAIPH_WORKSPACEwas forwarded unchanged into the container, sosteps.shcomputed run paths using the host absolute path (e.g./Users/me/project/.jaiph/runs/) which doesn't exist inside the container. The fix:buildDockerArgs()now callsremapDockerEnv()before forwardingJAIPH_*variables.JAIPH_WORKSPACEis always overridden to/jaiph/workspaceinside the container.JAIPH_RUNS_DIRis handled based on its value: relative paths pass through unchanged, absolute paths inside the host workspace are remapped to the equivalent container path under/jaiph/workspace, and absolute paths outside the host workspace cause a clearE_DOCKER_RUNS_DIRerror (unsupported in container path mapping). Non-Docker behavior is unchanged. New E2E test (72_docker_run_artifacts.sh) covers the happy path, relativeJAIPH_RUNS_DIR, absoluteJAIPH_RUNS_DIRinside workspace, and absoluteJAIPH_RUNS_DIRoutside workspace (gated on Docker availability). - Fix: Prompt output displayed multiple times in CLI — When an LLM backend returned the same answer in multiple JSON event formats (e.g. a
content_block_delta, then anassistantmessage, then aresultsummary), the stream parser wrote each copy to the progress tree, causing the prompt response to appear two or three times in the output. The parser (jaiph::stream_json_to_textinprompt.sh) now tracks whether a final-answer message has already been emitted (sawFinalMessageflag) and skips subsequent duplicates. The full response is still captured in the step's.outartifact file. Only the CLI display was affected — no data was lost. - Log messages use
ℹsymbol in CLI progress tree —loglines in the run progress tree now display with theℹ(information) symbol instead of thelogkeyword (e.g.ℹ saving impl.patchinstead oflog saving impl.patch). The symbol is rendered in dim/gray, matching the previouslogstyling. - Breaking: Channel identifier always on the left — Channel syntax is redesigned so the channel name is always on the left side of the operator. Send uses
channel <- command(wascommand -> channel); route declarations usechannel -> workflow(wason channel -> workflow). Theonkeyword is removed from route declarations. Standalone forwarding is nowchannel <-(was-> channel). The old right-sided send syntax is no longer supported. Parser, transpiler, CLI output, docs, E2E tests, and syntax highlighting are all updated. See Inbox & Dispatch. - Inbox: channel passed as named parameter to dispatched workflows — Dispatched workflows now receive the event channel as a named parameter (
channel=<name>) instead of displaying it via custom rendering logic. The channel appears in the CLI progress tree through the standardkey="value"parameter display pipeline (e.g.▸ workflow analyst (channel="findings")), removing the need for dispatch-specific display code inrun.ts. Internally,inbox.shsetsJAIPH_STEP_PARAM_KEYS='channel'on dispatch sojaiph::step_params_json()inevents.shemits the channel as a named param in JSONL events.JAIPH_DISPATCH_CHANNELis still set for event metadata tagging ("dispatched":true,"channel":"…"). The customformatStartLinebranch that handled channel+message formatting is removed; all step kinds now use the sameformatNamedParamsForDisplaypath. - Fix: Sequence counter lost across subshells in looped
runsteps — When a workflow usedruninside a loop (e.g.for item in a b c; do result = run sub "$item"; done), only the last iteration's artifact files were retained because every subshell started from the same in-memoryJAIPH_STEP_SEQvalue, producing identical file name prefixes that overwrote earlier iterations. The sequence counter is now persisted to a file ($JAIPH_RUN_DIR/.seq) that is created at run init and read/written atomically by each step. Subshells spawned byrunread the current value from disk, increment it, and write it back, so every step across all iterations receives a unique, monotonically increasing sequence number. New E2E test (71_loop_run_artifacts.sh) verifies that a three-iteration loop produces three distinct prompt.outfiles with correct sequence prefixes. - Unified parameter display with whitespace normalization — The CLI progress tree now uses a single
key="value"format for all step parameters everywhere. Positional arguments ($1,$2,argN) display as1="value",2="value", etc.; named arguments display asname="value". Line breaks, tabs, and consecutive spaces in parameter values are collapsed to a single space before display, fixing garbled multi-line output (e.g. when a prompt role or task body contained newlines). The$prefix on positional keys is removed. Internal display functions are consolidated:formatNamedParamsForDisplayis now used uniformly andformatParamsForDisplayis no longer used in the run renderer. A newnormalizeParamValueutility handles whitespace collapsing. - Prompt steps no longer show output in the CLI tree — When a
promptstep completes, the tree shows only the step line and ✓ — no Command/Prompt/Reasoning/Final answer block. To display agent output in the tree, uselogexplicitly:response = prompt "..."; log "$response". The step's.outfile in.jaiph/runs/still contains the full agent transcript for debugging. This keeps the progress tree clean and predictable; output appears only when the user opts in vialog. - Sequence-prefixed run artifact file names — Step
.outand.errfiles in.jaiph/runs/are now named with a zero-padded sequence prefix (000001-,000002-, ...) reflecting step execution order, instead of the previous timestamp-based prefix. This makes file names predictable and monotonically ordered across runs, enabling exact-name assertions in tests without glob matching. The sequence number is derived from the step counter (JAIPH_STEP_SEQ) assigned at step start. - E2E: human-readable test helpers (part 2) — Extended E2E helper library with
e2e::expect_run_file(assert content of a named artifact file by exact sequence-prefixed name),e2e::expect_run_file_at(same for custom run directories),e2e::expect_run_file_count/e2e::expect_run_file_count_at(assert total artifact file count),e2e::run_dir_at/e2e::latest_run_dir_at(locate run directories under custom base paths). Refactored tests in20_rule_and_prompt,40_nested_and_native_tests,70_run_artifacts,81_tty_progress_tree, and85_infile_metadatato replace manualnullglob/glob/printfboilerplate with the new helpers. Inline multi-line expected values replaceprintf '%s\n%s\n...'patterns for readability. - E2E: human-readable test helpers — Replaced low-level boilerplate in all 20+ E2E test files with high-level helpers in
e2e/lib/common.sh. Tests now follow an explicit Given / When / Then pattern usinge2e::file(write workflow from heredoc),e2e::run(build + run),e2e::expect_stdout(full tree output match via heredoc),e2e::expect_out/e2e::expect_rule_out(artifact content match),e2e::expect_out_files(artifact count),e2e::expect_file/e2e::expect_no_file(glob-based artifact content and absence assertions for.errand other files),e2e::expect_fail(assert non-zero exit),e2e::git_init, ande2e::git_current_branch. The helpers encapsulate run-directory discovery, glob matching, ANSI stripping, and time normalization so tests read like specifications rather than shell scripts. No runtime or compiler changes. - E2E: full
.outfile content assertions for all run artifacts — Every E2E test that executes a workflow now asserts on the content of.out(and.errwhere applicable) files written to.jaiph/runs/, not only the CLI tree output. Tests with deterministic stdout compare the full.outfile content viae2e::assert_equals; tests whose steps produce no stdout (redirected output,touch,test) assert that zero.outfiles exist. Covers 18 test files:10_basic_workflows,20_rule_and_prompt,22_assign_capture,30_filesystem_side_effects,40_nested_and_native_tests,60_ensure_conditionals,61_ensure_recover,65_fail_then_retry_pass,70_run_artifacts,81_tty_progress_tree,82_sibling_parse_error,85_infile_metadata,86_metadata_scope_nested,90_function_steps,91_inbox_dispatch,91_top_level_local. The runtime (events.sh,steps.sh) now recreates run directories withmkdir -pif they are removed mid-run (e.g. by test cleanup), preventing artifact-write failures. - Fix: prompt tree line shows named parameters instead of fake positional args — When a
promptstep references shell variables (e.g.prompt "$role does $task"), the progress tree now displays namedkey="value"pairs instead of misleading positional arguments. Before:▸ prompt "$role does $task" ("engineer", "Fix bugs")(implies$1,$2). After:▸ prompt "$role does $task" (role="engineer", task="Fix bugs"). The transpiler (emit-workflow.ts) extracts$var/${var}references from prompt text and emits them as explicit named arguments andJAIPH_STEP_PARAM_KEYS. The CLI (run.ts) detects named keys and delegates to a newformatNamedParamsForDisplay()formatter that renderskey="value"pairs. Prompts without variable references and prompts using only positional$1-style args retain the existing comma-separated value display. E2E and unit test coverage added. - E2E: unset agent env overrides in test harness —
e2e/lib/common.shnow unsetsJAIPH_AGENT_MODEL,JAIPH_AGENT_COMMAND,JAIPH_AGENT_BACKEND,JAIPH_AGENT_TRUSTED_WORKSPACE,JAIPH_AGENT_CURSOR_FLAGS, andJAIPH_AGENT_CLAUDE_FLAGSduringe2e::prepare_shared_context, preventing user/machine-level agent configuration from leaking into E2E runs and causing non-deterministic output. - E2E tests: full stdout assertions — Replaced partial
e2e::assert_containschecks on stdout with exacte2e::assert_output_equalscomparisons across seven test files (61_ensure_recover,90_function_steps,91_inbox_dispatch,22_assign_capture,20_rule_and_prompt,50_cli_and_parse_guards,82_sibling_parse_error). Each assertion now matches the complete normalized tree output, catching regressions in formatting, ordering, or extra/missing lines.e2e::normalize_outputgained an agent-command normalizer (cursor-agent/printf %slines →<agent-command>) so expected output remains deterministic across backends. - Unified runtime output reporting between Docker and non-Docker modes — The bash stdlib now always embeds
out_contentinSTEP_ENDevents (anderr_contentfor failed steps), regardless of whether the step was inbox-dispatched. The CLI uses this embedded content exclusively for display — no morereadFileSync(out_file)fallback inrun.ts. Inerrors.ts,readFailedStepOutput()prefers embeddedout_content/err_contentfrom the summary and falls back to reading files only for older summaries that lack them. Embedded content is capped at 1 MB; output exceeding this limit is truncated with a[truncated]marker. TheStepEventtype gains anerr_content: stringfield.out_file/err_fileartifacts remain on disk for debugging/archival. Docker TTY stream merging and line-based demuxing are unchanged and documented as a known limitation. - Fix: Dispatched step output now displays in Docker mode — When running inside the Docker sandbox,
logand stdout output from inbox-dispatched steps was silently lost because the CLI tried to readout_filefrom the host filesystem, but the file lived inside the container. The runtime now embeds stdout content directly in theSTEP_ENDevent asout_content. The CLI prefersout_contentwhen present and falls back to readingout_filefor non-dispatched steps. TheStepEventtype gains anout_content: stringfield. Non-Docker runs are unaffected. - Fix: Filter
__JAIPH_EVENT__lines from stdout in Docker mode — When Docker runs with-t(TTY passthrough), the container merges stderr into stdout, causing raw__JAIPH_EVENT__ {"type":"STEP_START",...}JSON lines to appear in terminal output. The CLI now buffers Docker stdout line-by-line and routes event lines through the same handler that processes stderr in non-Docker mode. Non-event lines pass through to the terminal unchanged. Any partial line remaining after the child process exits is flushed with the same filtering. Non-Docker runs are unaffected. The progress tree output is now identical regardless of whether Docker is enabled. - Fix: Dispatched inbox steps now render in the progress tree — The CLI progress output now correctly displays dispatched workflow steps with their channel name via the standard parameter display:
▸ workflow analyst (channel="findings"). Previously, dispatchedSTEP_START/STEP_ENDevents were emitted by the runtime but the CLI did not parse thedispatchedandchannelfields, so inbox-triggered steps were invisible in the progress tree. TheStepEventtype now includesdispatched: booleanandchannel: string. The channel is passed as a named parameter so it flows throughformatNamedParamsForDisplaylike any other step parameter. New e2e test verifies channel names appear in CLI output. - Harden inbox runtime for bash 3.2 and subshell correctness — The inbox dispatch system (
src/runtime/inbox.sh) no longer uses bash associative arrays (broken on bash 3.2 — reading a non-existent key can return the last inserted value). Routes are now stored as a newline-delimited list. The dispatch queue and sequence counter are file-backed (inbox/.queue,inbox/.seq) so that sends inside subshells (e.g.run_steppipelines) survive into the parent process. Newjaiph::_lookup_routehelper. No user-facing syntax changes. - E2E tests for inbox/dispatch — New
e2e/tests/91_inbox_dispatch.shcovers four scenarios: basic send + route, multi-target route dispatch, silent drop on unregistered channel, and inbox file written. Runs as part ofnpm run test:e2e. - Homepage inbox sample tab —
docs/index.htmlnow has a fourth sample tab (inbox_pipeline.jh) showing a two-stage workflow pipeline: scanner sends findings, analyst summarizes, reviewer echoes the result. Demonstrates->send,onroute, and chained dispatch. - Syntax highlighting:
local,on,->,expectNotContain— The docs syntax highlighter (docs/assets/js/main.js) now recognises thelocal,on, andexpectNotContainkeywords and the->arrow operator.local name = valuehighlights the variable name as a definition.on channel -> target1, target2highlightsonas a keyword,->as an operator, and the workflow targets as identifiers. The arrow->is tokenised as a two-character operator instead of two separate symbols. - Restructured
.jaiph/runs/directory layout — Run artifacts are now stored under<YYYY-MM-DD>/<HH-MM-SS>-<source-file>/instead of the previous flat<timestamp>-<run-id>/layout (e.g..jaiph/runs/2026-03-18/07-03-28-say_hello.jh/). The date and time use local time. If the same file is run twice within one second, a collision suffix (-2,-3, etc.) is appended.JAIPH_RUN_IDis still generated and available in the runtime environment for internal tracking and JSONL events.JAIPH_RUNS_DIRoverride is still respected — the date/time subdirectory structure applies under the custom root as well. The CLI now exportsJAIPH_SOURCE_FILE(basename of the input file) to the runtime environment. - Fix: Error message prefix
jai:renamed tojaiph:— All runtime and compiler diagnostic messages now use thejaiph:prefix instead of the abbreviatedjai:. Affects stderr messages from the stdlib (jaiph__die,jaiph__expect_contain,jaiph__expect_not_contain), prompt backend errors, mock dispatch errors, schema validation errors, and the generated bootstrap preamble (stdlib not found,incompatible runtime). No behavioral change — only the human-readable prefix is updated. - Fix: Docker enabled by default for local execution —
resolveDockerConfig()now returnsenabled: truewhen no env var or in-file config is set andCIis nottrue. Previously, Docker was never enabled unless explicitly configured becauseDEFAULTS.enabledwasfalse. CI environments (CI=true) continue to default to Docker disabled. Explicit overrides viaJAIPH_DOCKER_ENABLEDenv var orruntime.docker_enabledin-file config still take precedence.prepareGeneratedDir()now accepts an optionalbuildOutDirparameter and copies all*.shfiles from the build output directory into the Docker generated mount, so multi-file workflows with imports work correctly inside the container. Theinbox.shruntime module is now included in the Docker generated directory. - Top-level
localvariable declarations — Modules can now declare variables at the top level withlocal name = value. Values may be double-quoted strings (multi-line, same quoting rules asprompt), single-quoted strings, or bare values. Variables are module-scoped and transpile to prefixed bash variables using__as separator (e.g.local rolein moduleentrybecomesentry__role="..."). Inside each rule, function, and workflow body, alocalshim is emitted so$roleresolves to the prefixed variable. Variable names participate in the unified namespace — they cannot collide with rule, workflow, or function names (E_PARSE). Variables are not exportable; cross-module access is not supported. NewEnvDeclDeftype andenvDeclsfield onjaiphModule. Parser:src/parse/env.ts. Grammar:env_declproduction in Grammar. - Fix: single-file run no longer compiles sibling
.jhfiles —jaiph run file.jhnow compiles only the specified file and its transitive imports instead of every.jhfile in the parent directory. A parse error in a sibling file no longer prevents execution of unrelated files. Directory mode (jaiph build ./) is unchanged and continues to compile all files. The fix introducescollectFileWithImports()insrc/transpile/build.ts, which walks imports via the AST to build the minimal file set. - Inbox & dispatch: event passing between agent workflows — Workflows can now send messages to named channels with the
->send operator and declare routing rules withon <channel> -> <workflow>. The runtime dispatches messages sequentially via an in-memory queue — no filesystem watchers, no polling, noinotifywait/fswatch.echo "data" -> findingstranspiles tojaiph::send 'findings' "$(echo "data")". Standalone-> channelforwards$1.on findings -> analystregisters a route; when a message arrives onfindings,analystis called with the message content as$1. Multi-target routes (on ch -> wf1, wf2) dispatch sequentially in declaration order; each target receives the same message. Routes are static declarations stored inWorkflowDef.routes, not executable steps. The dispatch queue drains after the orchestrator completes; invoked workflows may produce further sends. Max dispatch depth of 100 guards against circular sends (E_DISPATCH_DEPTH). Send to an unregistered channel is a silent drop (message still written to inbox for audit). Non-zero exit from a dispatched workflow halts the queue (fail-fast). Inbox files are written asNNN-<channel>.txtunder.jaiph/runs/<run-id>/inbox/.name = cmd -> channelis a parse error (E_PARSE); use two steps instead. New runtime functions insrc/runtime/inbox.sh:jaiph::inbox_init,jaiph::send,jaiph::register_route,jaiph::drain_queue. Progress tree showsonroutes as nodes; dispatched calls appear as children withdispatched: trueandchannelmetadata. See Inbox & Dispatch. - Docker sandbox runtime for workflow execution — New optional Docker sandbox isolates workflow execution in a disposable container. Docker is enabled by default on local machines; disable with
runtime.docker_enabled = falseorJAIPH_DOCKER_ENABLED=false. The container receives only the transpiled bash script andjaiph_stdlib.sh— no Jaiph source, TypeScript, or Node.js. Newsrc/runtime/docker.tsmodule handles mount parsing/validation, image pull, UID/GID mapping (Linux), TTY passthrough, timeout enforcement (E_TIMEOUT), and Docker availability checks (E_DOCKER_NOT_FOUND). Mount strings support full form (host:container:mode) and shorthand (host:mode); exactly one mount must target/jaiph/workspace.JAIPH_STDLIBis set to/jaiph/generated/jaiph_stdlib.shinside the container.CI=truedisables Docker by default unless in-file override is set. Precedence: env vars (JAIPH_DOCKER_*) > in-file config > defaults.jaiph initnow recommends adding.jaiph/(not just.jaiph/runs/) to.gitignore. - Config parser: support integer and array value types —
parseMetadataValue()now handles bare integer literals (regex/^[0-9]+$/, returned asnumber) and bracket-delimited arrays of quoted strings (returned asstring[]). Multi-line arrays support trailing commas, inline#comments, and empty arrays (= []). A newruntime.*key namespace is added with five keys:runtime.docker_enabled(boolean, defaulttruelocally,falsein CI),runtime.docker_image(string, default"ubuntu:24.04"),runtime.docker_network(string, default"default"),runtime.docker_timeout(integer, default300), andruntime.workspace(string[], default[".:/jaiph/workspace:rw"]). Each key enforces its expected type at parse time (E_VALIDATEon mismatch). Unknownruntime.*keys produceE_PARSE. A newRuntimeConfiginterface is added tosrc/types.tsand an optionalruntimefield toWorkflowMetadata. - Enforce calling conventions and unify symbol namespace — Breaking change. Rules, workflows, and functions now share a single namespace per module; declaring two items with the same name (e.g. a rule
fooand a workflowfoo) yieldsE_PARSE. The compiler enforces calling conventions at compile time:ensuremust target a rule (E_VALIDATEif used on a workflow or function, with a message indicating the correct keyword),runmust target a workflow (E_VALIDATEif used on a rule or function), and functions cannot be used withensureorrun. These checks apply to both local and imported references. Internally, the bash symbol format is flattened from<module>::rule::<name>/<module>::workflow::<name>/<module>::function::<name>to<module>::<name>, with the step kind passed as an explicit argument tojaiph::run_stepandjaiph::run_step_passthrough. TheresolveShellFunctionRefspass is generalized toresolveShellRefs, resolving anyalias.namein shell context to the flatsymbol::nameform. External scripts that call generated bash functions by their old triple-prefix names will need to update to the new<module>::<name>format. - Docs: Update samples to use
loginstead ofcat— Thesay_hello.jhsample now useslog "$response"to display the agent's reply in the progress tree instead of writing to a file and usingcat. The run command in docs is simplified from./say_hello.jh Jakub && cat hello.txtto./say_hello.jh Jakub. Syntax highlighting JS updated accordingly. - Add
logkeyword for workflow messages — Workflows can now uselog "message"to display a message in the progress tree at the correct indentation level. At compile time (jaiph tree/--dry-run), log lines render as static tree nodes with the literal string (unexpanded variables shown as-is). At runtime,logemits aLOGevent (notSTEP_START/STEP_END) that the progress renderer displays inline at the correct depth — no spinner, no timing. Shell variable interpolation ($var,${var}) works inside the string at runtime. Thelogkeyword transpiles tojaiph::log "message", a runtime function that emits the event on fd 3 and echoes to stderr. New AST variant{ type: "log", message: string, loc: SourceLoc }inWorkflowStepDef. Parse error onlogwithout a double-quoted string argument. - Fix empty lines from prompt params in tree view —
formatParamsForDisplay()now filters out params whose value is empty or whitespace-only after stripping thekey=prefix. Previously, blank lines in a multilineprompt "..."arrived as empty-string params and rendered as empty quoted strings ("") or extra whitespace artifacts in the tree output. The function was also extracted fromrun.tsinto its ownformat-params.tsmodule for testability. - Support positive
if ensure ref args; thenin workflow parser — The parser now supports the positive formif ensure <rule_ref> [args]; then ... fiin addition to the existing negatedif ! ensure ...form. Both forms now accept optional arguments after the rule reference (e.g.if ensure check "$env"; then). Both forms supportelsebranches (if [!] ensure ref; then ... else ... fi). The parser emitsE_PARSEwhenensureappears inside an if-ensure then/else branch or in an unrecognised shell context, preventing silent pass-through to bash. - Remove dead code: tree branch characters — The run progress tree no longer uses Unicode box-drawing characters (
├──,└──,│). Nesting is shown with simple indentation only (matching the current·-prefixed display). Removed thebranchfield fromTreeRow, theRowStatetype, and unused exported functions:renderProgressTree,renderRunTree,RuntimeGraphStore,RuntimeNode,createRuntimeGraphStore,beginRuntimeNode,completeRuntimeNode,runtimeRunningLine,runtimeCompletedLine,runtimeRunningIndentLine,runtimeCompletedIndentLine,renderRuntimeIndentRows,renderRuntimeTreeRows. No user-visible change — the display already used the indent style. - Fix imported workflow metadata scope prefix —
prefixForImportedWorkflowCallnow resolves the module's emitted symbol (e.g.ensure_ci_passes) instead of the import alias (e.g.ci) when generatingwith_metadata_scopeprefixes. This fixes incorrect scope prefixes for imported workflows whose alias differs from their filename symbol. - Tolerant JSON extraction for typed prompts — When a
prompt "..." returns '{ ... }'step receives a response, the runtime now extracts JSON even when the agent returns text before the JSON object on the same line (e.g.Here is the result: {"field": "value"}). The parser tries multiple strategies in order: last non-empty line, fenced code blocks, a standalone{…}line, and embedded JSON within a line (text before{is stripped). The first candidate that parses as valid JSON wins. This makes typed prompts more robust against LLMs that add preamble text before their JSON output. - Business analyst review workflow (
ba_review.jh) — New workflow that reviews the first task inQUEUE.mdbefore development begins. An agent prompt evaluates the task for clarity, consistency with the codebase, absence of conflicts with other queued or documented features, and feasibility. If the task passes review, it is marked with a<!-- dev-ready -->comment; otherwise, the workflow exits with questions and recommendations that must be resolved first. The implementation workflow (implement_from_queue.jh) now gates on thedev-readymarker via a newfirst_task_is_dev_readyrule — tasks without the marker cannot proceed to implementation. The main entrypoint (main.jh) runs the BA review before the implementation step. - GitHub Pages template matching
docs/index.html— Extracted CSS, JS, and layout fromdocs/index.htmlinto a reusable Jekyll template (_layouts/default.html,assets/css/style.css,assets/js/main.js) with_config.yml. All doc pages (getting-started.md,cli.md,configuration.md,grammar.md,testing.md,hooks.md,jaiph-skill.md) now render through the shared template with consistent header, nav, card styling, code block copy buttons, and footer.docs/index.htmluseslayout: nullto keep its custom landing-page layout. Clean permalink URLs (e.g./getting-startedinstead of/getting-started.md) withredirect_fromfor backward compatibility. No duplicate CSS/JS — single source of truth inassets/. - Docs Jekyll theme: section-based layout — New
_layouts/docs.htmltemplate renders markdown pages with the same visual style asdocs/index.html: h1 and intro in a hero panel, h2 headers outside white card boxes, section content inside cards. Navigation links to all docs pages are provided by the template header — removed manual navigation blocks from individual markdown pages. Updateddocs_parity.jhprompts to enforce this pattern.