Releases: jaiphlang/jaiph
Releases · jaiphlang/jaiph
v0.9.2
Summary
- Runtime: Remove
JAIPH_LIBin favor of workspace-relative paths andimport script; strip inheritedJAIPH_LIBso parent shells cannot inject stale library paths. - Runtime: Write a
heartbeatfile in the run directory (refreshed every 10s) so external tooling can tell whether a run is still alive. - Docker: Base images without
jaiphnow 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_LIBremoval.
Full Changelog: v0.9.1...v0.9.2
v0.9.1
Summary
- CLI:
jaiph initnow 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:
recoveris renamed tocatch,import scriptadds external script-file imports, andjaiph reporthas been removed.
v0.9.0
Summary
- Language: Major syntax overhaul — triple-quoted strings (
"""...""") for multiline text, backtick/fenced delimiters for scripts, explicitrecoverbindings, required parentheses on definitions, bare identifier arguments,exportvisibility on modules,matcharm runtime execution, and${...}shell expansion allowed in fenced script blocks. Theifkeyword was initially removed in favor ofrecover/match, then re-added as a conditional guard (if var == "value" { … },if var =~ /pattern/ { … }). Seven deprecated constructs removed with migration hints. - CLI:
jaiph installfor project-scoped libraries with lockfile,jaiph formatfor canonical.jhformatting,jaiph initwith.gitignore/bootstrap/skill scaffolding, async branch numbering in progress tree, prompt backend/model display, inbox dispatch parameter display, and file shorthand routing. - Runtime:
codexbackend (OpenAI Chat Completions API), custom agent commands, Docker sandbox fixes (tree UI, failed-step output, path remapping), model auto-detection,return run/return ensuredirect 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
- Breaking — Language: Match — keyword always first, no dollar prefix —
matchnow 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 ismatch var { … }for statements,const x = match var { … }for expressions, andreturn match var { … }for return position. Using$varor${var}as the match subject is a hard parse error with guidance:match subject should be a bare identifier: match varName { ... }. The ASTMatchExprDef.subjectnow 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: rewritematch $var { … }→match var { … }and$var match { … }/${var} match { … }→match var { … }. Implementation:validateMatchSubject()insrc/parse/match.ts;extractPostfixMatchSubject()removed; prefix-form detection insrc/parse/const-rhs.tsandsrc/parse/workflow-brace.ts; emitter updated insrc/format/emit.ts; direct identifier lookup insrc/runtime/kernel/node-workflow-runtime.ts. All.jhsources, 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 onchanneldeclarations, not inside workflow bodies.channel findings -> analystdeclares both the channel and its route target in one line;channel events -> handler_a, handler_bsupports 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 fromChannelDef.routesinstead 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 emitschannel name -> targetin canonical output. Migration: move allchannel_ref -> workflowlines from workflow bodies to their corresponding top-levelchanneldeclarations. All.jhsources, E2E fixtures, compiler tests, examples, and docs updated. Implementation:ChannelDef.routesinsrc/types.ts;parseChannelLineextended insrc/parse/channels.ts;->rejected insrc/parse/workflows.ts; route map built fromChannelDefinsrc/runtime/kernel/node-workflow-runtime.ts; route target validation insrc/transpile/validate.ts; formatter insrc/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 declaresworkflow greet(name),${name}is the only binding;${arg1}is nowE_VALIDATE: unknown identifier. CLI positional arguments map to declared parameter names by position. Inbox route targets must declare exactly 3 parameters — fewer or more isE_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 inensure … recoverblocks as the merged stdout+stderr from the failed rule execution — this is a special runtime variable, not a positional parameter. Migration: all.jhsources, E2E fixtures, compiler tests, examples, and docs updated to use named parameters exclusively. Implementation:/^arg([1-9])$/check andmaxPositionalSlotsremoved fromvalidateSimpleInterpolationIdentifiers()insrc/transpile/validate-string.ts;arg1…arg9injection removed fromNodeWorkflowRuntimeorchestration scope insrc/runtime/kernel/node-workflow-runtime.ts; inbox dispatch switched fromarg1/arg2/arg3to positional binding against declared parameter names; CLI arg injection updated insrc/runtime/kernel/workflow-launch.ts. - Language: Optional parentheses at call sites —
run,ensure, andifcall sites now allow omitting parentheses when passing zero arguments.run setupis equivalent torun setup(),ensure checkis equivalent toensure check(), andif 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 braceif/else ifconditions. 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 formatnormalizes zero-arg calls to the parenthesized form (ref()) for unambiguous output. Implementation:parseCallReffallback insrc/parse/core.ts, call-path routing insrc/parse/steps.ts,src/parse/const-rhs.ts, andsrc/parse/workflow-brace.ts. Parser tests cover bare-identifier call sites in all positions; all existingref()tests pass unchanged. - Language: String/script type crossing enforcement —
stringandscriptare 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 scriptName→E_VALIDATE: scripts are not promptable; use a string const instead; (2)run constName()whereconstNameis a string →E_VALIDATE: strings are not executable; use a script instead; (3)const x = scriptName→E_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 indocs/grammar.mdexplains the string/script distinction as the conceptual foundation of the type system. Compiler tests cover all four invalid crossings plus valid usage. Implementation:localScriptsset threaded throughvalidateReferences()insrc/transpile/validate.ts,localScriptsparameter added tovalidateSimpleInterpolationIdentifiers()insrc/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-levelconst, workflow/ruleconst,log,logerr,fail,return, andsend(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: sharedparseTripleQuoteBlock()utility insrc/parse/triple-quote.tsreused acrossenv.ts,const-rhs.ts,prompt.ts,send-rhs.ts, andworkflow-brace.ts.tripleQuoteBodyToRaw()normalizes triple-quoted content to the internal quoted-string format. Formatter:src/format/emit.tsdetects newlines in string values and re-emits as"""..."""blocks. Error messages: unclosed multiline double-quotes produce guidance likemultiline strings use triple quotes: const name = """...""". Migration: all.jhsources, 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 withbodyKind: "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-lineprompt "text"and bare identifierprompt myVarare unchanged. AST:bodyKindreplaces"fenced"with"triple_quoted"for prompt steps; scriptbodyKindkeeps"fenced". Parser:parseTripleQuoteBlock()insrc/parse/prompt.tshandles"""..."""blocks;parseFencedBlock()remains for scripts only. Formatter updated to emit"""delimiters for prompts. Migration: all.jhsources, 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. Thescriptkeyword 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 userun `body`(args)andrun ```lang...body...```(args)— the oldrun script() "body"form is removed. All scripts use...
v0.7.0
- 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 knownconst, capture, named parameter, or positionalarg1–arg9. 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/*.jhwith strict assertions —e2e/tests/110_examples.shnow runs every example file through the full CLI pipeline with stricte2e::expect_stdoutand 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 anyexamples/*.jhorexamples/*.test.jhfile is unaccounted for.ensure_ci_passes.jhis excluded (requires realnpm run test:ci);async.jhis excluded (requires real agent backends). New coverage:ensure_ci_passes.test.jhviajaiph test. See Contributing — Example matrix guard. - Docs: Revisit
docs/jaiph-skill.md— addmatchto the Concepts bullets (Rules and Workflows) and to the Steps reference section so the agent skill accurately describes all supported constructs. Cross-checked againstdocs/grammar.md,docs/architecture.md,docs/index.html, andsrc/cli/shared/usage.ts; no other drift found. - E2E: Detect and remove stale e2e samples — five
.jhfixtures undere2e/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, andsleepsort.jh. These files duplicated functionality already covered by wired-in tests andexamples/, causing confusion about which samples were canonical. A new guard scripte2e/check_orphan_samples.shscans for unreferenced.jhand.test.jhfiles undere2e/(checking both direct references in test scripts and indirect references via other.jhfiles). Run it manually withbash e2e/check_orphan_samples.shor 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 isdocs/architecture.mdonly. README gains a short Core components section with a link to the full doc. UpdatedAGENT.md,QUEUE.md,e2e/lib/common.sh, and.jaiph/*.jh/.jaiph/*.shorchestration prompts to referencedocs/architecture.md. - Docs: Split concerns —
docs/architecture.mdfocuses 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 indocs/contributing.md. Cross-links updated indocs/testing.md,docs/inbox.md, andREADME.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
.jhfixture ingolden-ast/fixtures/is parsed and serialized to deterministic JSON (locations and file paths stripped, keys sorted) viaserializeAstForTest()insrc/golden-ast-runner.ts. The result is compared against a checked-in.jsongolden file ingolden-ast/expected/. Initial fixtures cover 8 language features: brace-if, imports, log, match, params, prompt-capture, run-ensure, and script-defs. Run withnpm run test:golden-ast; also wired intonpm 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 forrun,ensure,ifconditions,return run/return ensure,send … <- run, andconst x = run …now require bare identifiers for passing in-scope variables:run greet(name)instead ofrun greet("${name}"). The compiler rejects a standalone"${identifier}"in call arguments withE_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 positionalarg1–arg9); unknown names produceE_VALIDATE. Jaiph keywords cannot be used as bare identifiers.logandlogerralso accept a bare identifier form:log statusexpands tolog "${status}"— the variable's value is logged. Migration: all.jhfiles, E2E fixtures, compiler tests, examples, and docs updated to use bare identifiers. Implementation:validateNoQuotedSingleInterpolation()insrc/transpile/validate.ts, log bare identifier support insrc/parse/steps.tsandsrc/format/emit.ts. - Breaking — Language: Parentheses required on all workflow and rule definitions — Every
workflowandruledefinition 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 positionalarg1…arg9(e.g.${task}and${arg1}both work). The compiler validates call-site arity when the callee declares parameters — a mismatch producesE_VALIDATEwith 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.jhsources, E2E fixtures, compiler tests, examples, and docs updated to use()on every definition. Implementation:parseParamList()insrc/parse/core.ts,paramsfield onRuleDefandWorkflowDefinsrc/types.ts,collectKnownVars()andvalidateArity()insrc/transpile/validate.ts, runtime binding insrc/runtime/kernel/node-workflow-runtime.ts, formatter insrc/format/emit.ts. - Breaking — Language: Scripts and
run: braces out; strings or fences in — Namedscriptdefinitions now usescript name = <rhs>with three body forms: (1) single-line stringscript name = "body"(default shell runtime); (2) bare identifierscript name = varNamereferencing an existing binding whose string value becomes the body; (3) fenced blockscript name = ``` ... ```with optional lang tag (opening```on the same line as=). The{ ... }brace-delimited script bodies,script:langprefix forms, and theINTERPRETER_TAGShardcoded 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"orrun script() ``` ... ```— the previous body-inside-parens formrun script("body")is removed. Inline scripts support fenced blocks with lang tags for polyglot one-liners (e.g.run script() ```python3 ... ```).constcapture with inline scripts works with both body forms:const x = run script() "echo val"andconst x = run script() ``` ... ```. TheScriptDefAST type replacescommands: string[]withbody: string+ optionallang?: stringand gainsbodyKind("string"|"identifier"|"fenced") andbodyIdentifierfields. TheConstRhsinline 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 —
promptnow accepts three body forms: (1) single-line string literalprompt "text"with${...}interpolation; (2) bare identifierprompt myVarreferencing an existing binding; (3) fenced blockprompt ``` ... ```for multiline text (opening```on the same line asprompt). Multiline double-quoted strings are removed — a"with no closing quote on the same line now producesE_PARSE:"multiline prompt strings are no longer supported; use a fenced block instead". All three forms work in standalone, assignment capture (answer = prompt ...), andconstcapture (const x = prompt ...) positions.returnsis recognized as a keyword only after a complete body form. AST types (WorkflowStepDef,ConstRhs) gainbodyKind("string"|"identifier"|"fenced") andbodyIdentifierfields. The formatter emits identifier bodies as bare names and fenced bodies as```blocks. Fenced blocks useparseFencedBlockfromsrc/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.jhfixtur...
v0.6.0
Summary
- Runtime:
jaiph runandjaiph testexecute through the Node workflow runtime (NodeWorkflowRuntime) only — no bash workflow transpilation on the execution path; Docker uses the same Node runner;jaiph buildis no longer a user-facing command;jaiph testuses a pure Node test runner. Legacyjaiph_stdlib.shworkflow orchestration and user-facing workflow.shoutput 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 mayrunscripts only (not workflows). - Breaking changes: Call sites require parentheses; definitions use
rule/script/workflowwithout empty()before{; orchestration strings allow only${identifier}interpolation;mock function→mock script;.jphremoved — 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 likeconstorfail, 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 embeddednode -eheredocs, inline Python,constassignments 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:returnnormalization,local/export/readonlyspacing, and import alias resolution (alias.name→symbol::name). Non-bash scripts (custom shebang) emit the body verbatim. Previously, line-based rules such as "constis not allowed in script bodies" caused false positives for real scripts where a line began withconstinside embedded JS/Python. - CI: Verify landing-page samples (Playwright) — The
docs-localCI job now builds Jaiph, installs Playwright (Chromium), and runsnpx playwright testagainst 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 usingdata-sampleDOM attributes, compares source blocks against checked-inexamples/*.jhfiles, and runs deterministic samples through the CLI to verify that displayed output matches actual behavior. A newnpm run test:samplesscript provides the same check locally. The landing page (docs/index.html) now carries stabledata-sample,data-sample-file,data-sample-source, anddata-sample-outputattributes 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 on127.0.0.1:4000using Ruby 3.2 withbundler-cache, waits up to 30 seconds for the server to respond, then asserts HTTP 200 on/and/getting-startedviacurl. Runs onubuntu-latestonly (no OS matrix). The job has no dependency onjaiph.org— it validates the docs site entirely from the repositorydocs/directory. Future tasks will extend this job with sample verification against the served HTML. - E2E: verify
*.test.jhtesting end-to-end (105_test_jh_verification.sh) — New dedicated E2E test that exercisesjaiph testacross four scenarios: (1) a representative passing test file usingimport,mock prompt,mock rule,mock workflow,mock script, andexpectContainwith full output verification; (2) a deliberately failing test asserting non-zero exit and the expected failure report format (expectEqualmismatch diff); (3) rejection of the deprecatedmock functionsyntax 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.jhfiles. Acts as a regression gate for the test runner contract after themock function→mock scriptrename. - Fix:
JAIPH_LIBnot inherited from parent shell —resolveRuntimeEnvnow deletesJAIPH_LIBfrom the inherited environment before launchingjaiph runorjaiph test, matching the existing treatment ofJAIPH_SCRIPTSandJAIPH_RUN_STEP_MODULE. The Node runtime defaultsJAIPH_LIBto<JAIPH_WORKSPACE>/.jaiph/libwhen unset. Previously, a parent shell exportingJAIPH_LIB(pointing to a different workspace) would silently override the per-workspace default, causingsource "$JAIPH_LIB/…"in scripts to resolve against the wrong project. - Fix: add
JAIPH_WORKSPACEto unit test env forNodeWorkflowRuntime— Thenode-workflow-runtime.artifacts.test.tsrecover-payload test now includesJAIPH_WORKSPACEin the env map passed toNodeWorkflowRuntime, matching the contract thatresolveRuntimeEnvestablishes 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 withE_PARSEand 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 torun,ensure,run async, braceifconditions, 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.jhsources, fixtures, E2E tests, and documentation are migrated to the new shape. Supersedes the earlier "rule,script, andworkflowrequire()before{" entry below. - Feat: inline capture interpolation
${run ...}/${ensure ...}— Orchestration strings (log,logerr,fail,prompt,return, send literals,constRHS expressions) now support inline managed captures. Writelog "Got: ${run some_script}"orlog "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 standalonerun/ensuresteps (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 withE_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 testintegration, and compile-time rejection of nested/invalid forms. - E2E: verify custom shebang polyglot scripts for Python and Node —
e2e/tests/92_custom_shebang_polyglot.shnow exercises both#!/usr/bin/env python3and#!/usr/bin/env nodeshebangs in separate sections. Each interpreter gets its own test environment and fixture (polyglot.jhfor Python,polyglot_node.jhfor Node), asserting full CLI stdout and artifact file counts. When an interpreter is not inPATH, its section is skipped withe2e::skip— the test no longer exits early on a missingpython3, so both interpreters are tested independently. - E2E tests: migrate to full-output and full-artifact contracts — Converted 28
e2e::assert_containscalls 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) toe2e::assert_equalswith full expected content. Side-effect files,.out/.errartifacts, and inbox file contents now use exact-match assertions instead of substring checks. Remainingassert_containsusages are categorized as justified exceptions (nondeterministic output, unbounded logs, platform-dependent text) with inline comments, or tracked as convertible indocs/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.jhorchestration workflows. - Remove workflow bash transpilation and source maps — The compiler no longer emits workflow-level bash,
build(),transpileFile(), or.jaiph.mapsidecars. The only build output isbuildScripts()→ per-scriptfiles underscripts/viaemitScriptsForModule. Promptreturnsschema validation (unsupported types,returnswithout capture) lives invalidateReferences(validate-prompt-schema.ts). Tests and docs updated accordingly. - Breaking:
rule,script, andworkflowrequire()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 { … }, orscript bar { … }) are rejected at parse time withE_PARSEand a fix hint (e.g.rule declarations require parentheses: rule foo() { … }). Forms with()but with...
v0.5.0
jaiph report: read-only local HTTP server and UI over.jaiph/runswith REST endpoints for runs, step trees, log streaming, aggregates, and in-progress runs (JAIPH_REPORT_*env overrides)run_summary.jsonlas 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.jsonlplus 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_ENDembeddedout_content/err_contentso 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:
ensurefor rules,runfor 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
returnvalues for Jaiph callees; stdout/stderr go to artifacts; shell capture unchanged;returnas a workflow step; ensure/recover passes failed rule return as$1 runforwards callee stdout under shell redirection (>,|) while still writing.out/.errartifacts- 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) withmkdir-based locks for inbox seq, step seq, andrun_summary.jsonl; stress E2E and hardening decision record - Step completion lines include kind and name (e.g.
✓ workflow scanner (0s)) - Workflow-scoped
config { ... }(onlyagent.*andrun.*; before steps; propagates across importedrunand restores afterward) if run/if ! runconditionals alongside existingif ensureforms- Non-prompt steps stream stdout/stderr live into sequence-prefixed
.out/.errfiles during execution - Inbox-routed receivers get
$1message,$2channel,$3sender; 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
promptinsideensure ... recover; top-levellocalreference expansion everywhere; regression guard for.jaiph/main.jhimports
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-, ...) reflecti...
v0.3.0
- Typed
promptschema validation withreturns— You can declare the shape of the agent's JSON response withresult = 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 exportsname_fieldfor 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 withE_SCHEMA; prompt withreturnsbut without a capture variable fails withE_PARSE. Line continuation with\after the prompt string is supported for multilinereturnsclauses. Test withjaiph testby 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-linecmd || { echo "failed"; exit 1; }and multi-linecmd || { ... }compile and transpile correctly. Existingif ! cmd; then ...; fipatterns continue to work. - Prompt line in tree: prompt preview and capped args — The progress tree line for a
promptstep 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). Existingresult = 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 whenclaudeis selected but not on PATH. Output capture (result = prompt "...") andjaiph testprompt 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 remainscursorand 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 showsRUNNING 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 blockensure ref recover { stmt; stmt; }. Max retries default to 10; override withJAIPH_ENSURE_MAX_RETRIES. Bareensure ref(no recover) unchanged. - Run tree: step parameters inline — When
jaiph runprints the step tree,workflow,prompt, andfunctionsteps 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
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 ifclaudenot 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_shellworkflow steps for conditional flows- CI checks for compilation and testing; e2e tests aligned with current output
- Nested workflows and step functions in run tree;
rundisallowed insideruleblocks (useensureor 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 testruns*.test.jh/*.test.jphwith inline prompt mocks (mock prompt "..."ormock prompt { if $1 contains "..." ; then respond "..." ; fi }). No external.test.tomlfiles.- Runtime config is env vars and in-file metadata only;
.jaiph/config.tomland global config files are no longer read. jaiph initno longer creates.jaiph/config.toml..jhextension recommended for new files;.jphsupported but deprecated (CLI shows migration hint when running.jphfiles)jaiph initcreates.jaiph/bootstrap.jhand.jaiph/jaiph-skill.md- Import resolution prefers
.jhover.jphwhen both exist JAIPH_INSTALL_COMMANDenvironment variable forjaiph use(default:curl -fsSL https://jaiph.org/install | bash)runis not allowed inside aruleblock; useensureto call another rule or move the call to a workflow