Skip to content

Releases: jaiphlang/jaiph

v0.9.2

17 Apr 12:19

Choose a tag to compare

Summary

  • Runtime: Remove JAIPH_LIB in favor of workspace-relative paths and import script; strip inherited JAIPH_LIB so parent shells cannot inject stale library paths.
  • Runtime: Write a heartbeat file in the run directory (refreshed every 10s) so external tooling can tell whether a run is still alive.
  • Docker: Base images without jaiph now work generically: Jaiph builds a thin derived image, installs the current package into it, and writes artifacts directly to the configured host runs root.
  • Docs / E2E: Align documentation and tests with the JAIPH_LIB removal.

Full Changelog: v0.9.1...v0.9.2

v0.9.1

16 Apr 10:57

Choose a tag to compare

Summary

  • CLI: jaiph init now scaffolds a managed .jaiph/Dockerfile, emits a compilable bootstrap prompt, and strengthens bootstrap guidance for sandboxed projects.
  • Docker: Docker-backed runs now exit cleanly, stream live events correctly, and use a non-root managed image setup that works with stricter agent CLIs.
  • Language/CLI: recover is renamed to catch, import script adds external script-file imports, and jaiph report has been removed.

v0.9.0

10 Apr 15:55

Choose a tag to compare

Summary

  • Language: Major syntax overhaul — triple-quoted strings ("""...""") for multiline text, backtick/fenced delimiters for scripts, explicit recover bindings, required parentheses on definitions, bare identifier arguments, export visibility on modules, match arm runtime execution, and ${...} shell expansion allowed in fenced script blocks. The if keyword was initially removed in favor of recover/match, then re-added as a conditional guard (if var == "value" { … }, if var =~ /pattern/ { … }). Seven deprecated constructs removed with migration hints.
  • CLI: jaiph install for project-scoped libraries with lockfile, jaiph format for canonical .jh formatting, jaiph init with .gitignore/bootstrap/skill scaffolding, async branch numbering in progress tree, prompt backend/model display, inbox dispatch parameter display, and file shorthand routing.
  • Runtime: codex backend (OpenAI Chat Completions API), custom agent commands, Docker sandbox fixes (tree UI, failed-step output, path remapping), model auto-detection, return run/return ensure direct managed calls, and string/script type crossing enforcement.
  • Testing: Golden AST tests for parse tree shape, txtar-based compiler test suite with 238 cases, E2E full-output comparison audit across 27 test files, and Playwright landing-page sample verification.
  • Docs: Complete documentation overhaul — all 11 pages revisited against source code, agent skill improved with examples/testing/CLI table, README audience routing, and landing page updated with all new features.

Full Changelog: v0.8.0...v0.9.0

v0.8.0

03 Apr 11:36

Choose a tag to compare

  • Breaking — Language: Match — keyword always first, no dollar prefix — match now always precedes the subject in both statement and expression positions, and the subject is a bare identifier (no $ or ${}). The old postfix expression form (${var} match { … }) is removed. The new uniform syntax is match var { … } for statements, const x = match var { … } for expressions, and return match var { … } for return position. Using $var or ${var} as the match subject is a hard parse error with guidance: match subject should be a bare identifier: match varName { ... }. The AST MatchExprDef.subject now stores a plain identifier string instead of a bash-style $var/${var} reference. The runtime resolves the bare identifier against the variable scope directly instead of interpolating a bash expression. Migration: rewrite match $var { … }match var { … } and $var match { … } / ${var} match { … }match var { … }. Implementation: validateMatchSubject() in src/parse/match.ts; extractPostfixMatchSubject() removed; prefix-form detection in src/parse/const-rhs.ts and src/parse/workflow-brace.ts; emitter updated in src/format/emit.ts; direct identifier lookup in src/runtime/kernel/node-workflow-runtime.ts. All .jh sources, compiler tests, golden AST fixtures, E2E tests, and docs updated.
  • Breaking — Language: Channel declarations with inline routing — Route declarations (->) now belong at the top level on channel declarations, not inside workflow bodies. channel findings -> analyst declares both the channel and its route target in one line; channel events -> handler_a, handler_b supports multiple comma-separated targets. A -> route inside a workflow body is now a hard parse error with guidance: route declarations belong at the top level: channel <name> -> <targets>. The runtime reads routes from ChannelDef.routes instead of workflow-level route statements, and the validator checks that every route target is an existing workflow with exactly 3 declared parameters (rules and scripts are rejected). The formatter emits channel name -> target in canonical output. Migration: move all channel_ref -> workflow lines from workflow bodies to their corresponding top-level channel declarations. All .jh sources, E2E fixtures, compiler tests, examples, and docs updated. Implementation: ChannelDef.routes in src/types.ts; parseChannelLine extended in src/parse/channels.ts; -> rejected in src/parse/workflows.ts; route map built from ChannelDef in src/runtime/kernel/node-workflow-runtime.ts; route target validation in src/transpile/validate.ts; formatter in src/format/emit.ts.
  • Breaking — Language: Remove ${argN} positional parameter access — Named workflow/rule parameters are now the only way to access argument values in orchestration strings. The positional ${arg1}, ${arg2}, … ${arg9} slots are removed from both the validator and the runtime. If a workflow declares workflow greet(name), ${name} is the only binding; ${arg1} is now E_VALIDATE: unknown identifier. CLI positional arguments map to declared parameter names by position. Inbox route targets must declare exactly 3 parameters — fewer or more is E_VALIDATE; the runtime binds message, channel, and sender to whatever names the target declares (e.g. workflow handler(msg, ch, who)${msg}, ${ch}, ${who}). Exception: ${arg1} is still injected in ensure … recover blocks as the merged stdout+stderr from the failed rule execution — this is a special runtime variable, not a positional parameter. Migration: all .jh sources, E2E fixtures, compiler tests, examples, and docs updated to use named parameters exclusively. Implementation: /^arg([1-9])$/ check and maxPositionalSlots removed from validateSimpleInterpolationIdentifiers() in src/transpile/validate-string.ts; arg1arg9 injection removed from NodeWorkflowRuntime orchestration scope in src/runtime/kernel/node-workflow-runtime.ts; inbox dispatch switched from arg1/arg2/arg3 to positional binding against declared parameter names; CLI arg injection updated in src/runtime/kernel/workflow-launch.ts.
  • Language: Optional parentheses at call sites — run, ensure, and if call sites now allow omitting parentheses when passing zero arguments. run setup is equivalent to run setup(), ensure check is equivalent to ensure check(), and if not run file_exists { ... } works without parens. This applies uniformly to all call-site positions: standalone steps, const x = run ref, return run ref, return ensure ref, channel <- run ref, and brace if/else if conditions. When arguments are present, parentheses remain required: run deploy(env, version). Definitions always require ()workflow setup { ... } without parens is still a parse error. Arity checking works identically with the bare form (zero arguments). jaiph format normalizes zero-arg calls to the parenthesized form (ref()) for unambiguous output. Implementation: parseCallRef fallback in src/parse/core.ts, call-path routing in src/parse/steps.ts, src/parse/const-rhs.ts, and src/parse/workflow-brace.ts. Parser tests cover bare-identifier call sites in all positions; all existing ref() tests pass unchanged.
  • Language: String/script type crossing enforcement — string and script are now enforced as structurally distinct primitive types at every call site. The validator tracks which names are scripts vs. strings in the symbol table and rejects four invalid crossings: (1) prompt scriptNameE_VALIDATE: scripts are not promptable; use a string const instead; (2) run constName() where constName is a string → E_VALIDATE: strings are not executable; use a script instead; (3) const x = scriptNameE_VALIDATE: scripts are not values; (4) ${scriptName} interpolation → E_VALIDATE: scripts cannot be interpolated. Valid usage (prompt stringName, run scriptName()) is unchanged. Enforcement applies in both workflow and rule bodies, including capture forms (const x = run …, const x = prompt …). A new "Types" section in docs/grammar.md explains the string/script distinction as the conceptual foundation of the type system. Compiler tests cover all four invalid crossings plus valid usage. Implementation: localScripts set threaded through validateReferences() in src/transpile/validate.ts, localScripts parameter added to validateSimpleInterpolationIdentifiers() in src/transpile/validate-string.ts.
  • Breaking — Language: Triple-quoted strings ("""...""") — """...""" is now the universal multiline string form. "..." is single-line only; a double-quoted string that spans multiple lines is a hard parse error with guidance pointing to triple quotes. Triple-quoted strings work everywhere strings appear: top-level const, workflow/rule const, log, logerr, fail, return, and send (channel <- """..."""). ${...} interpolation works inside triple-quoted blocks, same rules as single-line strings. This completes the four-delimiter system: " single-line string, """ multiline string, ` single-line script, ``` multiline script. Parser: shared parseTripleQuoteBlock() utility in src/parse/triple-quote.ts reused across env.ts, const-rhs.ts, prompt.ts, send-rhs.ts, and workflow-brace.ts. tripleQuoteBodyToRaw() normalizes triple-quoted content to the internal quoted-string format. Formatter: src/format/emit.ts detects newlines in string values and re-emits as """...""" blocks. Error messages: unclosed multiline double-quotes produce guidance like multiline strings use triple quotes: const name = """...""". Migration: all .jh sources, fixtures, compiler tests, golden AST, E2E tests, and docs updated. Implementation: src/parse/triple-quote.ts, src/parse/env.ts, src/parse/const-rhs.ts, src/parse/send-rhs.ts, src/parse/workflow-brace.ts, src/format/emit.ts.
  • Breaking — Language: Prompt triple-quoted blocks — Prompt multiline bodies now use triple-quote delimiters ("""...""") instead of triple backticks. Triple backticks are reserved exclusively for scripts. The delimiter structurally identifies content type: natural language (quotes) vs. executable code (backticks). prompt """...""" parses as a block prompt with bodyKind: "triple_quoted". Using triple backticks in prompt context is a hard parse error with guidance: prompt blocks use triple quotes: prompt """..."""; triple backticks are for scripts. ${...} interpolation works inside triple-quoted blocks. returns "{ ... }" works after the closing """ (same line or next line). Single-line prompt "text" and bare identifier prompt myVar are unchanged. AST: bodyKind replaces "fenced" with "triple_quoted" for prompt steps; script bodyKind keeps "fenced". Parser: parseTripleQuoteBlock() in src/parse/prompt.ts handles """...""" blocks; parseFencedBlock() remains for scripts only. Formatter updated to emit """ delimiters for prompts. Migration: all .jh sources, fixtures, compiler tests, golden AST, E2E tests, examples, and docs updated. Implementation: src/parse/prompt.ts, src/types.ts, src/format/emit.ts.
  • Breaking — Language: Script delimiters — backtick for inline, triple-backtick fence for block. Named scripts now use single backtick (script name = `body`) for single-line and triple-backtick fence (script name = ```lang ... ```) for multi-line bodies. The script keyword is kept for named declarations only. The previous double-quoted string body (script name = "body") and bare identifier body (script name = varName) forms are removed — both produce parse errors with guidance to use backtick delimiters. Anonymous inline scripts use run `body`(args) and run ```lang...body...```(args) — the old run script() "body" form is removed. All scripts use...
Read more

v0.7.0

02 Apr 12:56

Choose a tag to compare

  • Language: Strict scope enforcement for bare args and ${…} interpolation — both bare identifier arguments (run greet(name)) and braced interpolation (log "hello ${name}") now enforce the same scope rules: the identifier must be a known const, capture, named parameter, or positional arg1arg9. Unknown names are rejected at compile time in both forms. The bare-argument error message no longer suggests ${name} as a workaround, since braced interpolation applies the same strict check. Compiler tests cover both unknown-bare-arg and unknown-${ident} cases.
  • E2E: Exercise examples/*.jh with strict assertions — e2e/tests/110_examples.sh now runs every example file through the full CLI pipeline with strict e2e::expect_stdout and artifact assertions, replacing the previous substring checks. Examples are tracked via three arrays (COVERED_RUN, COVERED_TEST, EXCLUDED) forming an example matrix; an orphan guard at the bottom fails CI if any examples/*.jh or examples/*.test.jh file is unaccounted for. ensure_ci_passes.jh is excluded (requires real npm run test:ci); async.jh is excluded (requires real agent backends). New coverage: ensure_ci_passes.test.jh via jaiph test. See Contributing — Example matrix guard.
  • Docs: Revisit docs/jaiph-skill.md — add match to the Concepts bullets (Rules and Workflows) and to the Steps reference section so the agent skill accurately describes all supported constructs. Cross-checked against docs/grammar.md, docs/architecture.md, docs/index.html, and src/cli/shared/usage.ts; no other drift found.
  • E2E: Detect and remove stale e2e samples — five .jh fixtures under e2e/ that were not referenced by any test script (e2e/tests/*.sh, e2e/test_all.sh) have been deleted: log_keyword.jh, log_keyword.test.jh, say_hello_json.jh, say_hello_json.test.jh, and sleepsort.jh. These files duplicated functionality already covered by wired-in tests and examples/, causing confusion about which samples were canonical. A new guard script e2e/check_orphan_samples.sh scans for unreferenced .jh and .test.jh files under e2e/ (checking both direct references in test scripts and indirect references via other .jh files). Run it manually with bash e2e/check_orphan_samples.sh or wire it into CI to prevent stale samples from accumulating. See Contributing — Orphan sample guard.
  • Docs: Single architecture source — remove root ARCHITECTURE.md; canonical architecture is docs/architecture.md only. README gains a short Core components section with a link to the full doc. Updated AGENT.md, QUEUE.md, e2e/lib/common.sh, and .jaiph/*.jh / .jaiph/*.sh orchestration prompts to reference docs/architecture.md.
  • Docs: Split concerns — docs/architecture.md focuses on how Jaiph is built (components, pipelines, contracts, diagrams, durable artifact layout as a runtime contract). JS kernel bullet now names individual modules (run-step-exec.ts, stream-parser.ts, schema.ts, mock.ts). E2E testing philosophy, normalization rules, and TypeScript test layout live in docs/contributing.md. Cross-links updated in docs/testing.md, docs/inbox.md, and README.md.
  • Testing: Golden AST tests for parse tree shape — a new test layer that locks in the AST structure produced by successful parses, so refactors cannot silently change tree shape. Each .jh fixture in golden-ast/fixtures/ is parsed and serialized to deterministic JSON (locations and file paths stripped, keys sorted) via serializeAstForTest() in src/golden-ast-runner.ts. The result is compared against a checked-in .json golden file in golden-ast/expected/. Initial fixtures cover 8 language features: brace-if, imports, log, match, params, prompt-capture, run-ensure, and script-defs. Run with npm run test:golden-ast; also wired into npm test. To regenerate goldens after an intentional parser change: UPDATE_GOLDEN=1 npm run test:golden-ast. See Testing — Golden AST tests for details.
  • Breaking — Language: Bare identifier arguments required; standalone "${identifier}" rejected — Call sites for run, ensure, if conditions, return run/return ensure, send … <- run, and const x = run … now require bare identifiers for passing in-scope variables: run greet(name) instead of run greet("${name}"). The compiler rejects a standalone "${identifier}" in call arguments with E_VALIDATE: do not use "${name}" in call arguments; use a bare identifier: ...(name). Quoted strings with additional text around the interpolation (e.g. "prefix_${name}") remain allowed since they cannot be expressed as bare identifiers. Bare identifier arguments must reference a known variable (const, capture, named parameter, or positional arg1arg9); unknown names produce E_VALIDATE. Jaiph keywords cannot be used as bare identifiers. log and logerr also accept a bare identifier form: log status expands to log "${status}" — the variable's value is logged. Migration: all .jh files, E2E fixtures, compiler tests, examples, and docs updated to use bare identifiers. Implementation: validateNoQuotedSingleInterpolation() in src/transpile/validate.ts, log bare identifier support in src/parse/steps.ts and src/format/emit.ts.
  • Breaking — Language: Parentheses required on all workflow and rule definitions — Every workflow and rule definition must include () before {, even when parameterless: workflow default() { … }, rule check() { … }. Omitting () is a parse error with a fix hint. Named parameters go inside the parentheses: workflow implement(task, role) { ... }, rule gate(path) { ... }. Parameter names follow identifier rules, must not be reserved keywords, and must be unique. At runtime, named parameters are bound alongside positional arg1arg9 (e.g. ${task} and ${arg1} both work). The compiler validates call-site arity when the callee declares parameters — a mismatch produces E_VALIDATE with an actionable message naming the expected parameters. When the callee has no declared parameters, no arity check is performed. The formatter emits () for empty parameter lists. Named parameters are the preferred style for new code. Migration: all .jh sources, E2E fixtures, compiler tests, examples, and docs updated to use () on every definition. Implementation: parseParamList() in src/parse/core.ts, params field on RuleDef and WorkflowDef in src/types.ts, collectKnownVars() and validateArity() in src/transpile/validate.ts, runtime binding in src/runtime/kernel/node-workflow-runtime.ts, formatter in src/format/emit.ts.
  • Breaking — Language: Scripts and run: braces out; strings or fences in — Named script definitions now use script name = <rhs> with three body forms: (1) single-line string script name = "body" (default shell runtime); (2) bare identifier script name = varName referencing an existing binding whose string value becomes the body; (3) fenced block script name = ``` ... ``` with optional lang tag (opening ``` on the same line as =). The { ... } brace-delimited script bodies, script:lang prefix forms, and the INTERPRETER_TAGS hardcoded allowlist are removed. Language selection now uses fence lang tags exclusively: ```<tag> maps to #!/usr/bin/env <tag> — any tag is valid (no hardcoded allowlist). If both a fence tag and a manual #! shebang are present, it is an error. Anonymous inline scripts now use body-after-parens syntax: run script() "body" or run script() ``` ... ``` — the previous body-inside-parens form run script("body") is removed. Inline scripts support fenced blocks with lang tags for polyglot one-liners (e.g. run script() ```python3 ... ```). const capture with inline scripts works with both body forms: const x = run script() "echo val" and const x = run script() ``` ... ```. The ScriptDef AST type replaces commands: string[] with body: string + optional lang?: string and gains bodyKind ("string" | "identifier" | "fenced") and bodyIdentifier fields. The ConstRhs inline script capture variant (kind: "run_inline_script_capture") accepts the new body position (body follows parens, not inside them). The formatter emits the new forms. Implementation: src/parse/scripts.ts, src/parse/inline-script.ts, src/parse/steps.ts, src/parse/const-rhs.ts, src/types.ts, src/format/emit.ts, src/transpile/emit-script.ts. All fixtures, compiler tests, E2E tests, and docs migrated.
  • Breaking — Language: Prompt body: single-line string, identifier, or fenced block — prompt now accepts three body forms: (1) single-line string literal prompt "text" with ${...} interpolation; (2) bare identifier prompt myVar referencing an existing binding; (3) fenced block prompt ``` ... ``` for multiline text (opening ``` on the same line as prompt). Multiline double-quoted strings are removed — a " with no closing quote on the same line now produces E_PARSE: "multiline prompt strings are no longer supported; use a fenced block instead". All three forms work in standalone, assignment capture (answer = prompt ...), and const capture (const x = prompt ...) positions. returns is recognized as a keyword only after a complete body form. AST types (WorkflowStepDef, ConstRhs) gain bodyKind ("string" | "identifier" | "fenced") and bodyIdentifier fields. The formatter emits identifier bodies as bare names and fenced bodies as ``` blocks. Fenced blocks use parseFencedBlock from src/parse/fence.ts. Implementation: src/parse/prompt.ts, src/parse/const-rhs.ts, src/types.ts, src/format/emit.ts. Compiler tests, E2E tests, and all internal .jh fixtur...
Read more

v0.6.0

30 Mar 20:00

Choose a tag to compare

Summary

  • Runtime: jaiph run and jaiph test execute through the Node workflow runtime (NodeWorkflowRuntime) only — no bash workflow transpilation on the execution path; Docker uses the same Node runner; jaiph build is no longer a user-facing command; jaiph test uses a pure Node test runner. Legacy jaiph_stdlib.sh workflow orchestration and user-facing workflow .sh output are gone from the runtime path.
  • Scripts vs orchestration: Bash, polyglot shebangs, and arbitrary shell live only in top-level script { } blocks (opaque bodies; the compiler does not parse them as Jaiph steps). Workflow and rule bodies are Jaiph steps only — no raw shell lines; rules may run scripts only (not workflows).
  • Breaking changes: Call sites require parentheses; definitions use rule / script / workflow without empty () before {; orchestration strings allow only ${identifier} interpolation; mock functionmock script; .jph removed — see the detailed entries below.

All changes

  • Fix: Treat script bodies as opaque bash — The compiler no longer parses script { … } bodies as Jaiph steps, rejects keywords like const or fail, strips outer quotes from commands, or validates cross-script calls line-by-line. All script bodies — bash and polyglot — are now opaque text to the compiler, which means embedded node -e heredocs, inline Python, const assignments in JS, and any other valid shell construct compile without interference. For bash scripts (no shebang or #!/usr/bin/env bash), the emitter still applies lightweight transforms: return normalization, local/export/readonly spacing, and import alias resolution (alias.namesymbol::name). Non-bash scripts (custom shebang) emit the body verbatim. Previously, line-based rules such as "const is not allowed in script bodies" caused false positives for real scripts where a line began with const inside embedded JS/Python.
  • CI: Verify landing-page samples (Playwright) — The docs-local CI job now builds Jaiph, installs Playwright (Chromium), and runs npx playwright test against the locally served Jekyll site. The Playwright suite (tests/e2e-samples/landing-page.spec.ts) extracts sample source and expected output from the landing page using data-sample DOM attributes, compares source blocks against checked-in examples/*.jh files, and runs deterministic samples through the CLI to verify that displayed output matches actual behavior. A new npm run test:samples script provides the same check locally. The landing page (docs/index.html) now carries stable data-sample, data-sample-file, data-sample-source, and data-sample-output attributes on each tab panel for reliable extraction.
  • CI: Getting started (local) — Jekyll smoke-check job — New CI job docs-local ("Getting started (local)") builds and serves the Jekyll documentation site locally on 127.0.0.1:4000 using Ruby 3.2 with bundler-cache, waits up to 30 seconds for the server to respond, then asserts HTTP 200 on / and /getting-started via curl. Runs on ubuntu-latest only (no OS matrix). The job has no dependency on jaiph.org — it validates the docs site entirely from the repository docs/ directory. Future tasks will extend this job with sample verification against the served HTML.
  • E2E: verify *.test.jh testing end-to-end (105_test_jh_verification.sh) — New dedicated E2E test that exercises jaiph test across four scenarios: (1) a representative passing test file using import, mock prompt, mock rule, mock workflow, mock script, and expectContain with full output verification; (2) a deliberately failing test asserting non-zero exit and the expected failure report format (expectEqual mismatch diff); (3) rejection of the deprecated mock function syntax with a migration error containing "mock function" is no longer supported; use "mock script"; (4) directory discovery (jaiph test <dir>) finding and running multiple *.test.jh files. Acts as a regression gate for the test runner contract after the mock functionmock script rename.
  • Fix: JAIPH_LIB not inherited from parent shellresolveRuntimeEnv now deletes JAIPH_LIB from the inherited environment before launching jaiph run or jaiph test, matching the existing treatment of JAIPH_SCRIPTS and JAIPH_RUN_STEP_MODULE. The Node runtime defaults JAIPH_LIB to <JAIPH_WORKSPACE>/.jaiph/lib when unset. Previously, a parent shell exporting JAIPH_LIB (pointing to a different workspace) would silently override the per-workspace default, causing source "$JAIPH_LIB/…" in scripts to resolve against the wrong project.
  • Fix: add JAIPH_WORKSPACE to unit test env for NodeWorkflowRuntime — The node-workflow-runtime.artifacts.test.ts recover-payload test now includes JAIPH_WORKSPACE in the env map passed to NodeWorkflowRuntime, matching the contract that resolveRuntimeEnv establishes for real CLI runs. Without it, workspace-dependent defaults (e.g. JAIPH_LIB) could resolve incorrectly in test isolation.
  • Breaking: definition/call syntax — no parens on defs, call parens required — Top-level definitions (rule, script, workflow) must not use parentheses after the identifier: rule name { … }, script name { … }, workflow name { … }. Forms with () on definitions (e.g. rule foo() { … }) are rejected at parse time with E_PARSE and a fix hint. Call sites now always require parentheses — either empty () for zero-argument calls or comma-separated arguments inside (...): ensure check(), run helper("arg1", "arg2"), run other_workflow(). This applies uniformly to run, ensure, run async, brace if conditions, inline capture interpolation (${run ref()}, ${ensure ref()}), and send RHS (channel <- run fmt()). The previous syntax that allowed bare calls without parentheses (e.g. ensure my_rule, run my_script "arg") is no longer accepted. This change makes arity visible at every call site and removes the misleading empty () on definitions that never declared real parameters. All internal .jh sources, fixtures, E2E tests, and documentation are migrated to the new shape. Supersedes the earlier "rule, script, and workflow require () before {" entry below.
  • Feat: inline capture interpolation ${run ...} / ${ensure ...} — Orchestration strings (log, logerr, fail, prompt, return, send literals, const RHS expressions) now support inline managed captures. Write log "Got: ${run some_script}" or log "Status: ${ensure check_ok}" instead of extracting one-time values into temporary variables. At runtime, each inline capture executes as a managed call and the result replaces the interpolation expression; regular ${var} interpolation applies after all captures resolve. If any inline capture fails, the enclosing step fails immediately. Validation: inline capture references follow the same rules as standalone run / ensure steps (correct target kind, no shell redirection). Nested inline captures (e.g. ${run foo ${run bar}}) and invalid references (e.g. ${run 123bad}) are rejected at compile time with E_PARSE. Covered by unit tests (validate-string.test.ts) and E2E tests (100_inline_capture_interpolation.sh) across runtime execution, failure propagation, mixed interpolation, jaiph test integration, and compile-time rejection of nested/invalid forms.
  • E2E: verify custom shebang polyglot scripts for Python and Nodee2e/tests/92_custom_shebang_polyglot.sh now exercises both #!/usr/bin/env python3 and #!/usr/bin/env node shebangs in separate sections. Each interpreter gets its own test environment and fixture (polyglot.jh for Python, polyglot_node.jh for Node), asserting full CLI stdout and artifact file counts. When an interpreter is not in PATH, its section is skipped with e2e::skip — the test no longer exits early on a missing python3, so both interpreters are tested independently.
  • E2E tests: migrate to full-output and full-artifact contracts — Converted 28 e2e::assert_contains calls across 8 test files (30_filesystem_side_effects.sh, 74_live_step_output.sh, 91_inbox_dispatch.sh, 92_log_logerr.sh, 93_inbox_stress.sh, 97_step_output_contract.sh, 98_ensure_recover_value.sh, 101_script_isolation.sh) to e2e::assert_equals with full expected content. Side-effect files, .out/.err artifacts, and inbox file contents now use exact-match assertions instead of substring checks. Remaining assert_contains usages are categorized as justified exceptions (nondeterministic output, unbounded logs, platform-dependent text) with inline comments, or tracked as convertible in docs/contributing.md. The default E2E contract — full equality on CLI stdout, artifact files, and inbox contents — is documented in ARCHITECTURE.md, docs/contributing.md, docs/testing.md, e2e/lib/common.sh, and the .jaiph/engineer.jh / .jaiph/qa.jh orchestration workflows.
  • Remove workflow bash transpilation and source maps — The compiler no longer emits workflow-level bash, build(), transpileFile(), or .jaiph.map sidecars. The only build output is buildScripts() → per-script files under scripts/ via emitScriptsForModule. Prompt returns schema validation (unsupported types, returns without capture) lives in validateReferences (validate-prompt-schema.ts). Tests and docs updated accordingly.
  • Breaking: rule, script, and workflow require () before { — Top-level definitions must use an empty parameter list and braces: rule name() { … }, script name() { … }, workflow name() { … }. Forms without () (e.g. rule foo { … }, workflow default { … }, or script bar { … }) are rejected at parse time with E_PARSE and a fix hint (e.g. rule declarations require parentheses: rule foo() { … }). Forms with () but with...
Read more

v0.5.0

24 Mar 19:24

Choose a tag to compare

  • jaiph report: read-only local HTTP server and UI over .jaiph/runs with REST endpoints for runs, step trees, log streaming, aggregates, and in-progress runs (JAIPH_REPORT_* env overrides)
  • run_summary.jsonl as versioned JSONL reporting stream (event_version: 1) with workflow/step/inbox lifecycle events; parallel inbox mode serializes all summary appends via the summary lock
  • Documented reporting correlation rules for run_summary.jsonl plus E2E contract coverage under parallel inbox
  • Non-TTY progress heartbeats for long steps (JAIPH_NON_TTY_HEARTBEAT_*); TTY behavior unchanged
  • RFC 8259 JSON string escaping for STEP_END embedded out_content / err_content so event lines stay valid JSON
  • Keyword-first shell guard shared across $(...) and workflow shell lines; first word on each shell line cannot be a Jaiph symbol (stricter than 0.4.x when $(...) was present)
  • Managed-call contract: ensure for rules, run for workflows/functions; compile-time rejection of callees in $(...), bare function calls in workflows, and wrong keywords; function bodies restricted accordingly
  • Run tree: trim leading blank lines before first visible prompt/log text; TTY completion indent fallback from step depth when start mapping is missing
  • Step output model: assignment capture uses explicit return values for Jaiph callees; stdout/stderr go to artifacts; shell capture unchanged; return as a workflow step; ensure/recover passes failed rule return as $1
  • run forwards callee stdout under shell redirection (>, |) while still writing .out/.err artifacts
  • Workflow shell steps support bash job control (& / wait) with shared artifact capture and documented constraints on Jaiph calls from background jobs
  • Optional parallel inbox dispatch (run.inbox_parallel / JAIPH_INBOX_PARALLEL) with mkdir-based locks for inbox seq, step seq, and run_summary.jsonl; stress E2E and hardening decision record
  • Step completion lines include kind and name (e.g. ✓ workflow scanner (0s))
  • Workflow-scoped config { ... } (only agent.* and run.*; before steps; propagates across imported run and restores afterward)
  • if run / if ! run conditionals alongside existing if ensure forms
  • Non-prompt steps stream stdout/stderr live into sequence-prefixed .out/.err files during execution
  • Inbox-routed receivers get $1 message, $2 channel, $3 sender; metadata and CLI param display updated (JAIPH_DISPATCH_* env compatibility)
  • Broadened unit test coverage across parsers, emitters, CLI display, and runtime env resolution
  • Fixes: multiline prompt inside ensure ... recover; top-level local reference expansion everywhere; regression guard for .jaiph/main.jh imports

v0.4.0

21 Mar 09:07

Choose a tag to compare

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] recover now passes failed rule output as a parameter ($1) to the recover block, enabling context-aware recovery.
  • Logging system overhaul: log now outputs to stdout (for informational messages) while logerr is 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 ... recover now 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 $1 while executing the recover body. This enables patterns like ensure 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 and JAIPH_ENSURE_MAX_RETRIES handling remain unchanged.
  • Docker sandboxing is now opt-in (beta) — Docker sandbox is no longer enabled by default on local machines. runtime.docker_enabled defaults to false in all environments; set runtime.docker_enabled = true or JAIPH_DOCKER_ENABLED=true to enable it. The CI-specific default logic (CI=true → disabled) is removed — the default is simply false everywhere. Docker sandboxing documentation is moved from configuration.md to 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_image is configured, the runtime checks for .jaiph/Dockerfile in the workspace root. If present, it runs docker build and tags the result as jaiph-runtime:latest, using it instead of the default ubuntu:24.04. A shipped .jaiph/Dockerfile is 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 alongside JAIPH_* variables. When an explicit image is set via JAIPH_DOCKER_IMAGE or runtime.docker_image, the Dockerfile is ignored. Without a .jaiph/Dockerfile, the runtime falls back to ubuntu:24.04 as before. New resolveImage() and buildImageFromDockerfile() functions in docker.ts. DockerRunConfig gains an imageExplicit field 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.
  • log writes to stdout; new logerr keyword writes to stderrlog "message" now echoes to stdout (previously stderr), making its output capturable and separable from error output. A new keyword logerr "message" mirrors log but writes to stderr. Both emit events on fd 3 for the progress tree. In the CLI tree, log lines display with a dim (unchanged); logerr lines display with a red !. The runtime functions are jaiph::log (stdout + LOG event) and jaiph::logerr (stderr + LOGERR event). Parser, transpiler, types, and event handling updated. New E2E test (92_log_logerr.sh) validates stdout/stderr separation and .out/.err artifact content for both keywords.
  • Fix: Docker runs now persist artifacts on the host — When Docker mode was enabled, run artifacts (.out files, run_summary.jsonl) were written to a host-only absolute path inside the container filesystem and lost on docker run --rm. The root cause: JAIPH_WORKSPACE was forwarded unchanged into the container, so steps.sh computed 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 calls remapDockerEnv() before forwarding JAIPH_* variables. JAIPH_WORKSPACE is always overridden to /jaiph/workspace inside the container. JAIPH_RUNS_DIR is 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 clear E_DOCKER_RUNS_DIR error (unsupported in container path mapping). Non-Docker behavior is unchanged. New E2E test (72_docker_run_artifacts.sh) covers the happy path, relative JAIPH_RUNS_DIR, absolute JAIPH_RUNS_DIR inside workspace, and absolute JAIPH_RUNS_DIR outside 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 an assistant message, then a result summary), 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_text in prompt.sh) now tracks whether a final-answer message has already been emitted (sawFinalMessage flag) and skips subsequent duplicates. The full response is still captured in the step's .out artifact file. Only the CLI display was affected — no data was lost.
  • Log messages use symbol in CLI progress treelog lines in the run progress tree now display with the (information) symbol instead of the log keyword (e.g. ℹ saving impl.patch instead of log saving impl.patch). The symbol is rendered in dim/gray, matching the previous log styling.
  • 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 (was command -> channel); route declarations use channel -> workflow (was on channel -> workflow). The on keyword is removed from route declarations. Standalone forwarding is now channel <- (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 standard key="value" parameter display pipeline (e.g. ▸ workflow analyst (channel="findings")), removing the need for dispatch-specific display code in run.ts. Internally, inbox.sh sets JAIPH_STEP_PARAM_KEYS='channel' on dispatch so jaiph::step_params_json() in events.sh emits the channel as a named param in JSONL events. JAIPH_DISPATCH_CHANNEL is still set for event metadata tagging ("dispatched":true,"channel":"…"). The custom formatStartLine branch that handled channel+message formatting is removed; all step kinds now use the same formatNamedParamsForDisplay path.
  • Fix: Sequence counter lost across subshells in looped run steps — When a workflow used run inside 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-memory JAIPH_STEP_SEQ value, 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 by run read 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 .out files 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 as 1="value", 2="value", etc.; named arguments display as name="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: formatNamedParamsForDisplay is now used uniformly and formatParamsForDisplay is no longer used in the run renderer. A new normalizeParamValue utility handles whitespace collapsing.
  • Prompt steps no longer show output in the CLI tree — When a prompt step completes, the tree shows only the step line and ✓ — no Command/Prompt/Reasoning/Final answer block. To display agent output in the tree, use log explicitly: response = prompt "..."; log "$response". The step's .out file 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 via log.
  • Sequence-prefixed run artifact file names — Step .out and .err files in .jaiph/runs/ are now named with a zero-padded sequence prefix (000001-, 000002-, ...) reflecti...
Read more

v0.3.0

12 Mar 14:44

Choose a tag to compare

  • Typed prompt schema validation with returns — You can declare the shape of the agent's JSON response with result = prompt "..." returns '{ type: string, risk: string, summary: string }'. The schema is flat only (no nested objects, arrays, or union types in v1). Allowed field types: string, number, boolean. The compiler appends instructions to the prompt; the runtime parses the last non-empty line as JSON and validates it. Valid response sets the capture variable to the raw JSON and exports name_field for each field (e.g. $result_type, $result_risk). Distinct failure modes: JSON parse error (exit 1), missing required field (exit 2), type mismatch (exit 3). Unsupported schema type or invalid schema syntax fails at compile time with E_SCHEMA; prompt with returns but without a capture variable fails with E_PARSE. Line continuation with \ after the prompt string is supported for multiline returns clauses. Test with jaiph test by mocking the prompt with valid JSON that satisfies the schema.
  • Inline brace-group short-circuit (cmd || { ... }) — The parser now accepts short-circuit brace-group patterns in rule, workflow, and function bodies. Single-line cmd || { echo "failed"; exit 1; } and multi-line cmd || { ... } compile and transpile correctly. Existing if ! cmd; then ...; fi patterns continue to work.
  • Prompt line in tree: prompt preview and capped args — The progress tree line for a prompt step now shows a truncated preview of the prompt text (first 24 characters, then ... if longer) and the argument list (arg1, arg2, ...) is capped at 24 characters total (truncated with ... if longer). Example: ▸ prompt "Say hello to $1 and..." (greeting) instead of only ▸ prompt (greeting). Non-prompt steps are unchanged. Makes it easier to tell which prompt is running when multiple prompts exist and keeps tree lines bounded.
  • Assignment capture for any step — You can capture stdout from any step with name = <step>: e.g. response = ensure tests_pass, out = run helper, line = echo hello. Only stdout is captured; stderr is not included unless the command redirects it (e.g. 2>&1). If the command fails, the step fails unless you explicitly short-circuit (e.g. ... || true). Existing result = prompt "..." behavior is unchanged. Bash-consistent semantics: see Grammar.
  • Project-local and global hooks — Support for ~/.jaiph/hooks.json (global) and <project>/.jaiph/hooks.json (project-local). Hook commands run at workflow/step lifecycle events (workflow_start, workflow_end, step_start, step_end). Project-local entries override global per event. Payload is JSON on stdin; hook failures are logged but do not block the run. See Hooks.
  • Claude CLI as prompt backend — File-level agent.backend (cursor | claude) with env override (JAIPH_AGENT_BACKEND). Prompt execution is routed through a backend abstraction; clear error when claude is selected but not on PATH. Output capture (result = prompt "...") and jaiph test prompt mocks work with both backends; when a prompt is not mocked, the selected backend runs (including Claude CLI). No prompt-level backend override; default backend remains cursor and backward compatible.
  • TTY run progress: single bottom line only — In TTY mode, the progress tree is printed the same as in non-TTY: each task line with icon and final time when the step completes (e.g. ✓ 0s, ▸ prompt "First 24 chars..." (arg1) then on completion ✓ 2s). No per-step live counters or in-place updates on tree lines. A single extra line at the bottom shows RUNNING workflow <name> (X.Xs) (RUNNING yellow, "workflow" bold, workflow name default, time dim) and is the only line updated in place (e.g. every second). When the run finishes, that line is removed. Non-TTY unchanged (no RUNNING line, no timer).
  • ensure … recover (retry loop)ensure <rule_ref>(args) recover <body> runs the rule and, on failure, runs the recover body in a bounded retry loop until the rule passes or max retries is reached (then exit 1). Recover body: single statement (e.g. ensure dep recover run install_deps) or block ensure ref recover { stmt; stmt; }. Max retries default to 10; override with JAIPH_ENSURE_MAX_RETRIES. Bare ensure ref (no recover) unchanged.
  • Run tree: step parameters inline — When jaiph run prints the step tree, workflow, prompt, and function steps invoked with arguments show those argument values inline in gray after the step name (e.g. ▸ function fib (3), ▸ workflow docs_page (docs/cli.md, strict)). Format: comma-separated values in parentheses; no parameter names or internal refs (e.g. ::impl) are shown. Values are truncated to 32 characters with ... when longer. Parameter order is stable for diff-friendly output. Steps without parameters are unchanged.

Full Changelog: v0.2.0...v0.3.0

v0.2.0

05 Mar 22:10

Choose a tag to compare

  • config { ... } block for runtime behavior: agent.backend ("cursor" | "claude"), agent.trusted_workspace, and existing env-backed options
  • Claude CLI as alternative agent backend when agent.backend = "claude" (with clear error if claude not in PATH)
  • Trusted workspace and metadata scoping; config docs
  • Run progress driven by runtime event graph (not only CLI); normalized e2e output
  • First-class mocking in tests: mock workflows, rules, and functions (not only prompts)
  • if_not_ensure_then / if_not_ensure_then_run / if_not_ensure_then_shell workflow steps for conditional flows
  • CI checks for compilation and testing; e2e tests aligned with current output
  • Nested workflows and step functions in run tree; run disallowed inside rule blocks (use ensure or move to workflow)
  • Prompt capture fixes (assignments as final answer); improved test failure output
  • Documentation and docs site updates (getting started, testing, styling, mobile)
  • jaiph test runs *.test.jh / *.test.jph with inline prompt mocks (mock prompt "..." or mock prompt { if $1 contains "..." ; then respond "..." ; fi }). No external .test.toml files.
  • Runtime config is env vars and in-file metadata only; .jaiph/config.toml and global config files are no longer read.
  • jaiph init no longer creates .jaiph/config.toml.
  • .jh extension recommended for new files; .jph supported but deprecated (CLI shows migration hint when running .jph files)
  • jaiph init creates .jaiph/bootstrap.jh and .jaiph/jaiph-skill.md
  • Import resolution prefers .jh over .jph when both exist
  • JAIPH_INSTALL_COMMAND environment variable for jaiph use (default: curl -fsSL https://jaiph.org/install | bash)
  • run is not allowed inside a rule block; use ensure to call another rule or move the call to a workflow