Skip to content

v0.9.5#17

Open
dzikowski wants to merge 66 commits into
mainfrom
nightly
Open

v0.9.5#17
dzikowski wants to merge 66 commits into
mainfrom
nightly

Conversation

@dzikowski

Copy link
Copy Markdown
Contributor

No description provided.

dzikowski and others added 30 commits June 19, 2026 13:28
Signed-off-by: Jakub Dzikowski <jakub.t.dzikowski@gmail.com>
Replace the sequential execSync clone loop in src/cli/commands/install.ts
with a small bounded-concurrency executor (default 4 in flight) using
spawn("git", ["clone", "--depth", "1", ...]) so independent network and
process latency overlap when several libraries are missing. The user
contract is unchanged: warm-path libraries (target directory exists and
--force is absent) still skip without invoking git for both explicit args
and restore-from-lock; failed clones still exit non-zero and do not
produce a lock entry; restore-from-lock still does not invent new lock
entries. runInstall is now async and exposes injectable CloneRunner and
concurrency options for testing. Tests cover concurrent overlap, warm-
path skipping for explicit args and restore, invalid-remote and unknown-
ref failure paths, and mixed success/failure lockfile bookkeeping.
The default local `jaiph run <file.jh>` path no longer parses the
entry module twice and no longer re-parses the full import closure
inside the spawned `node-workflow-runner` child. A new
`prepareCompile` (`src/transpile/compile-prep.ts`) walks the entry
plus its transitive `.jh` imports exactly once and returns a
`CompilePrep` record (`{ entryFile, workspaceRoot, astByFile }`).
`src/cli/commands/run.ts` reuses the entry AST for the banner,
passes the prep into `buildScripts(..., prep)` so emission skips
per-file reads/parses, and writes a deterministic JSON snapshot to
`<outDir>/.jaiph-compile-prep.json`. The spawned runner reads it
via the internal `JAIPH_COMPILE_PREP_FILE` env var and forwards
the deserialized prep to `buildRuntimeGraph`, which consumes the
cached `Map<file, jaiphModule>` instead of re-walking the import
closure on disk. The env var is set only for non-Docker host runs;
`jaiph run --raw`, `jaiph test`, and Docker launches keep their
existing parse paths. User-visible run semantics — banner, hooks,
run artifacts, summaries, return values, exit codes, and
`__JAIPH_EVENT__` streaming — are unchanged. New tests corrupt
every source file on disk after `prepareCompile`, then exercise
`buildScripts` + `buildRuntimeGraph` to prove no second parse
happens, and cover cross-module workflow/rule/script resolution
plus the serialize → deserialize → graph round-trip across the
parent → child process boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add design/2026-05-15-parser-compiler-simplification.md documenting
five load-bearing refactors (ModuleGraph, Expr-AST collapse,
visitor-table validator, unified catch/recover, tokenizer + RD parser)
plus five secondary improvements (Trivia/CST split, typed Arg[],
single-pass validator walk, Diagnostics collector, validator/runtime
decoupling). Append all ten as standalone, #dev-ready tasks to
QUEUE.md in the recommended implementation order.

Also drop .jaiph/language_redesign_spec.md (superseded).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace three divergent module-discovery strategies (validator
callbacks, transpiler re-parse paths, and the file-system walk in
buildScripts) with a single ModuleGraph representation. Both
validate(graph) and emit(graph, outDir) now operate entirely
in-memory; ValidateContext and the optional prep cache are gone.
parsejaiph is provably fs-free, enforced by a stub-fs test against
the full fixture corpus. LSP edits and full compiles share the
pipeline — only the graph root differs.
Around ten formatter-only fields — leadingComments on imports / script
imports / channels / const decls / test blocks, configLeadingComments,
trailingTopLevelComments, configBodySequence, topLevelOrder, bareSource
on return, tripleQuoted flags on literal/return/log/logerr/fail/send/
const, and prompt / script bodyKind / bodyIdentifier discriminators —
are removed from jaiphModule, WorkflowStepDef, ConstRhs, SendRhsDef,
WorkflowMetadata, ImportDef, ScriptImportDef, ChannelDef, ScriptDef, and
TestBlockDef and re-homed in a new parallel Trivia store
(src/parse/trivia.ts) keyed by AST-node identity plus a small
ModuleTrivia record. The parser exposes parsejaiphWithTrivia → {ast,
trivia}; legacy parsejaiph drops trivia for validator / transpiler /
runtime / loadModuleGraph. The formatter (emitModule(ast, trivia, opts?))
is Trivia's only consumer. New tests pin the invariants:
trivia-ast-shape.test.ts (AC1, type-level), trivia-grep.test.ts (AC2),
and roundtrip.test.ts (AC3, parse → format → parse → format bit-for-bit
on every fixture under examples/ and test-fixtures/golden-ast/fixtures/).
Golden AST fixtures regenerated to drop the moved fields. User-visible
contracts (CLI behavior, format round-trip, run artifacts, banner,
hooks, exit codes, __JAIPH_EVENT__ streaming) are unchanged. Implements
design/2026-05-15-parser-compiler-simplification.md § Appendix A.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace every call-bearing node's `args: string` + `bareIdentifierArgs?:
string[]` pair with a single `args?: Arg[]`, where each Arg is either
`{ kind: "literal"; raw }` or `{ kind: "var"; name }`. The parser does
the bare-identifier classification once at parse time, and the
validator and emitter consume the typed list directly without any
downstream re-parse of the raw `args` string. Drops the
`validateBareIdentifierArgs` helper; its scope check now lives in the
per-step validator that already walks the call. Adds grep and
AST-shape regression tests so neither `bareIdentifierArgs` nor an args
re-parse can reappear under src/parse or src/transpile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the three "managed call that yields a value" encodings — the `run`
statement, the `run_capture` / `ensure_capture` / `prompt_capture` /
`run_inline_script_capture` / `match_expr` ConstRhs branches, and the
`managed:` sidecar on `return` / `log` / `logerr` (with placeholder strings
like `"__match__"` and `"run inline_script"`) — with one tagged `Expr` union
used everywhere a value can appear. `ConstRhs` and `SendRhsDef` are gone;
the placeholder strings are gone; the sidecar field is gone. `WorkflowStepDef`
collapses from 14 variants to 8 (`exec`, `const`, `return`, `send`, `say`,
`if`, `for_lines`, `trivia`), with `exec` covering the prior `run` / `ensure`
/ `run_inline_script` / `prompt` / `shell` / standalone `match` statement
shapes and `say` covering the prior `log` / `logerr` / `fail`. Parser,
validator, formatter, emitter, runtime, and golden AST fixtures are migrated
in lockstep; a new `src/types-shape.test.ts` enforces the acceptance criteria
(no placeholder strings, exactly 8 step variants, no exported ConstRhs /
SendRhsDef).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
src/transpile/validate.ts used to descend each workflow's / rule's step
tree four times before the main check loop finished -- collectKnownVars,
collectPromptSchemas, validateImmutableBindings, and the per-step
validator -- each re-implementing the same recursion over if /
for_lines / catch / recover with subtly different rules. The three
pre-pass helpers are gone. A new walkStepTree descends the tree once,
accumulating knownVars, promptSchemas (gated by withPromptSchemas so
rules skip schema collection), and enforcing immutable-binding /
script-collision rules inline through a shared bindings map (with a
fresh inner map under each for_lines body so loop iterators only shadow
inside the body). It emits a flat FlatStepEntry[] of every step in tree
order with the enclosing catch / recover failure binding attached; the
main per-workflow and per-rule validator loops iterate that flat list
non-recursively. walkStepTree's internal descend is now the only
recursive helper in the file that takes a WorkflowStepDef[]. All
existing E_VALIDATE error messages and locations are preserved
bit-for-bit. New tests in validate-single-walk.test.ts pin both
invariants (no reappearance of the deleted helpers by name; at most one
WorkflowStepDef[] walker). Docs updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace fail-fast `throw jaiphError(...)` in the validator with a new
`Diagnostics` collector (`src/diagnostics.ts`) that accumulates every
recoverable error per compile. `collectDiagnostics(graph)` walks the
import closure and returns the populated collector; the legacy
`validateReferences(graph)` is now a thin wrapper that throws the first
sorted diagnostic so existing per-error tests and the script-emit path
stay intact. Each top-level validation unit (per-import block, per-rule
walk, per-rule step, per-workflow walk, per-workflow step,
per-test-block step, per-channel route) is wrapped in `diag.capture(fn)`
so a bailout from one error unwinds only that unit; the four leaf
helpers (validate-ref-resolution, validate-string,
validate-prompt-schema, shell-jaiph-guard) still throw and every caller
captures them. `jaiph compile` routes through `collectDiagnostics`,
prints the full sorted set (stderr lines or a single JSON array under
`--json`), and exits non-zero on any non-empty set. New tests in
`src/transpile/diagnostics-collector.test.ts` pin all five acceptance
criteria, including a three-error fixture and a source-tree allowlist
scan over remaining `throw jaiphError(` sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the 1,441-LoC validate.ts monolith — two near-identical inner
walkers (validateRuleStep, validateStep) plus the five-check call-shape
sequence repeated at 12+ sites — with a two-file split. validate.ts
(~430 LoC) keeps the outer layer: import / channel-route / test-block
checks and walkStepTree (the single descent that builds knownVars,
promptSchemas, and the flat step list). validate-step.ts (~1,025 LoC)
holds the per-step visitor: one validateStep(step, ctx) entry, a
VALIDATORS: Record<WorkflowStepDef["type"], StepValidator> table with
one row per variant, a validateExpr(expr, ...) dispatcher over the 8
Expr.kind values, and a single validateCallable(expr, ctx) helper that
runs the five managed-call-shape checks once for both call (run) and
ensure_call (ensure). Rule-vs-workflow differences are captured in a
Scope value (WORKFLOW_SCOPE / RULE_SCOPE) with allowSteps (single
set-lookup gate at the top of validateStep), runRefExpect, and
withPromptSchemas. Every E_VALIDATE message and source location is
preserved bit-for-bit. New tests in validate-visitor.test.ts pin the
invariants: a ≤700-line cap on validate.ts (AC1), a JSON snapshot over
every validate-* txtar fixture asserting each diagnostic's
{ code, line, col, message } bit-for-bit (AC3), and an "unknown step
type" test asserting a synthetic variant produces exactly one
internal: no validator for step type "…" diagnostic in both scopes
(AC4). The diagnostics-collector fatal-allowlist test now sums throw
jaiphError / diag.error counts across both files. Docs updated in
docs/architecture.md, docs/contributing.md, and docs/grammar.md.
Implements design/2026-05-15-parser-compiler-simplification.md §
Refactor 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`validate-step.ts` used to import `tripleQuotedRawForRuntime` from
`src/runtime/orchestration-text.ts` to compute "what the runtime will
see" for triple-quoted match-arm bodies — a one-way compile-time
dependency on runtime semantics. Move the helper into the parser layer
as `canonicalizeTripleQuotedString` in `src/parse/triple-quote.ts`;
both the validator and the runtime now import it from `src/parse/`,
and the `src/runtime/orchestration-text.ts` wrapper is deleted. New
tests pin the invariants: `no-runtime-imports.test.ts` greps every
non-test `*.ts` under `src/transpile/` and fails on any `from
"…/runtime/…"` import, and `canonicalize-triple-quoted.test.ts` walks
every triple-quoted match-arm body in `test-fixtures/` and `examples/`
and asserts bit-for-bit parity against an inlined legacy baseline.
`src/parse/steps.ts` used to contain three near-identical 100+ line
functions — `parseEnsureStep`, `parseRunCatchStep`, `parseRunRecoverStep`
— that parsed the same `<host-step> <keyword> (binding) { body }` shape
and differed only in which host step they decorated and the literal
keyword. Their body parser, `parseCatchStatement` (~280 lines), was a
stripped-down copy of `parseBlockStatement` recognizing only a fixed
subset of statement forms, so the same fix had to land in two places
and divergence wasn't always caught by tests. Collapse all four into
one `parseAttachedBlock(keyword, host)` entry point in `steps.ts` that
parses the bindings and dispatches body statements through the **same**
`parseBlockStatement` used at the top level — no mini parser remains.
The host side moves to a single `parseRunOrEnsure` helper in
`workflow-brace.ts`. `src/parse/steps.ts` drops from 757 → 141 lines;
every existing parse error message and location is preserved bit-for-bit
(verified by snapshot fixtures), and the full parser/validator/emitter
golden corpus passes byte-for-byte. A new `parse-attached-block.test.ts`
pins the invariant that any statement form accepted at top level is
accepted identically inside `catch (e) { … }` and `recover(e) { … }`
without parser changes inside the catch/recover code path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parseBlockStatement used to dispatch each statement form via an ordered
cascade of startsWith + regex tests (run async before run, prompt before
bare assignment, etc.), so adding a keyword meant finding the right slot
and any reordering risked changing which branch fired. The cascade is
replaced by a STATEMENT: Record<string, BlockHandler> table keyed by the
leading keyword: the dispatcher tokenizes the first identifier on the
trimmed line, looks it up, and invokes the matching handler — which
returns a result, returns null to fall through, or calls fail(...). The
shared per-line context is now a BlockCtx record threaded into every
handler. Assignment-shape guards run in applyAssignmentGuards before
the lookup. Surface syntax and every existing parse-error message /
line / col are preserved. New tests pin the invariants: an error
snapshot test compares { file, line, col, code, message } for every
fixture in parse-errors.txt against parse-errors-snapshot.json, and a
synthetic-keyword test patches STATEMENT at runtime to prove adding a
top-level keyword is a two-place change (STATEMENT row + JAIPH_KEYWORDS
entry). The wider tokenizer rewrite remains future work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite docs/jaiph-skill.md for agents asked to automate repetitive
tasks: mental model first, authoring loop (format/compile/test/run),
six core rules, compact syntax reference with compile-error fix table,
runtime model, testing, and ready-to-adapt patterns. All embedded
examples are verified with jaiph compile; newly documented constraints:
inline scripts reject catch/recover, if/match subjects must be plain
identifiers, log/logerr are invalid match arm bodies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bugs: Docker exit-listener leak, imported-channel sends never
dispatching, missing inbox dispatch cap, ignored workflow-level
run.recover_limit, cross-module run ignoring callee config.
Language: if/else, catch/recover on inline scripts, dot-notation
subjects in if/match. DX: per-subcommand --help, jaiph test exit 0 on
empty discovery, reject mixed mock prompt styles, formatter quote
stripping on top-level const. Plus error-message quality, lazy
overlay-script load, dead-code removal, Docker env-allowlist docs,
and fleshed-out docs-writer skill adoption task.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add .jaiph/lib_common.jh with shared exported scripts; dedupe
  architect_review and ensure_ci_passes (echo-based file writer with
  swapped arg order replaced by python3 (path, content) version).
- ensure_ci_passes: replace unbounded recursive catch with a
  run ... recover loop bounded by run.recover_limit.
- engineer: normalize classifier output (lowercase + regex match)
  before select_role; dedupe safety constraints into consts; cite
  QUEUE.md rule 7 verbatim in the implement prompt; drop dead script.
- Add .jaiph/security_review.jh: diff-scoped review gated on HIGH
  findings, methodology adapted from anthropics/claude-code-security-
  review, with read-only worktree guard and report artifact.
- Remove .jaiph/main.jh (engineer is invoked directly; also removes
  the "queue"-as-role bug) and scratch .jaiph/testing.jh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendor github/awesome-copilot's `documentation-writer` SKILL.md into
`.jaiph/skills/documentation-writer/SKILL.md` (committed, header records
upstream URL / blob SHA / copy date for re-sync). Update the three
prompts in `.jaiph/docs_parity.jh` (`update_from_task`, `docs_page`,
`docs_overview`) to read and follow the skill file by explicit path
before doing anything else, so both Claude and Cursor backends can `Read`
it without depending on agent-specific auto-discovery dirs. Slim the
inline `role` const to project-specific context only — TypeScript/Bash
fluency for verifying docs against source, `docs/architecture.md` as
single source of truth, and the Jekyll-template navigation constraint —
dropping clarity/accuracy/consistency boilerplate the skill already
covers. CHANGELOG, docs/index.html, and the QUEUE entry updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NodeWorkflowRuntime now layers the callee module-level config and then
the callee workflow-level config on top of the caller's effective env
when a `run alias.workflow()` crosses module boundaries — same mechanics
as the root-entry path, respecting `${NAME}_LOCKED` env flags. Previously
both were silently ignored on the cross-module path, so a workflow in
module A calling `run b.show()` ran with A's env even when module B
declared its own `config { ... }`. This bit `.jaiph/ensure_ci_passes.jh`
(`agent.backend = "cursor"`) when callers like `engineer.jh` (backend
`claude`) reached it. Behavior now matches cross-module `ensure` and
root entry; same-module `run` still layers only workflow-level config
because module config is already in the caller's effective env. The
caller's scope is restored exactly when the call returns, preserving
sibling isolation. Updates `docs/configuration.md` "Scoping across
nested calls" table, removes the now-stale NOTE in
`.jaiph/ensure_ci_passes.jh`, and refreshes the kernel and e2e tests
that previously pinned the ignore behavior (each flip pairs with a
rationale in the diff).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In runWorkflow, the process.on("exit") cleanup guard registered for
Docker-path runs was only removed after waitForRunExit resolved
normally. Any throw between registration and removal (stream wiring,
the awaited child exit, buffer draining) left the listener on the
process for the rest of its lifetime and re-invoked cleanupDocker on
an already-cleaned container at exit. Introduce withDockerExitGuard
in src/runtime/docker.ts: it registers the guard, runs the body
inside try, and in finally — on both normal return and on throw —
removes the listener and calls cleanupDocker exactly once. The guard
itself stays registered for the abnormal-exit case (that is its
purpose). cleanupDocker is already idempotent via the cleaned flag;
its JSDoc now states that contract explicitly. New tests pin the
invariants: cleanupDocker called twice is a no-op the second time,
process.listeners("exit") returns to its pre-run value after a
successful body, the same holds when the body throws, and no
listener is registered when dockerResult is undefined.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Channel routes are registered under bare channel names, but sends
matched the verbatim token left of <-, so lib.topic never hit a route
keyed as topic and messages were silently dropped. Strip the alias.
prefix after validateChannelRef confirms the import, use the bare name
for route lookup, INBOX_ENQUEUE, and inbox audit filenames. Add E2E
coverage and rewrite docs/inbox.md module-scope behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a hard limit (default 1000) on messages drained per workflow frame
in drainWorkflowQueue. Exceeding the cap fails the workflow with
E_INBOX_DISPATCH_LIMIT naming the channel; override via
JAIPH_INBOX_MAX_DISPATCH. Add kernel tests for circular sends, env
override, and fan-out below the cap. Document in inbox.md, cli.md, and
jaiph-skill.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
resolveRecoverLimit now consults the active workflow's metadata before
falling back to module-level config and the default of 10, matching the
precedence documented for other run keys. Add integration tests for
workflow-level override and sibling workflow isolation; remove the stale
module-only exception from configuration, grammar, language, and skill docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Support `} else {` on the same line as the closing if brace with identical
step constraints in then and else blocks. Reject standalone else, else-if
chaining, and orphaned else with E_PARSE fix hints. Add txtar and golden
AST fixtures, formatter idempotence test, runtime e2e for both branches in
workflows and rules, and update grammar, language, and skill docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Named-ref run steps already supported catch/recover failure handling,
but inline scripts rejected trailing catch/recover clauses. Extend the
inline-script parse path (single-backtick and fenced forms) to accept
the same optional suffixes with identical semantics, wire runtime
retry/catch through node-workflow-runtime, add txtar and e2e tests,
and update grammar/language docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Accept IDENT.IDENT as the subject of if and match when the base is a typed
prompt capture with a matching returns schema field. Reuse the same
E_VALIDATE diagnostics as ${var.field} interpolation; runtime resolves field
values via resolveSubjectValue. Adds txtar, golden AST, e2e, and docs updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
Every subcommand (run, test, compile, format, init, install, use) now
recognizes -h/--help anywhere in its argument list before positional
processing, prints command-specific usage to stdout, and exits 0. Shared
hasHelpFlag helper respects -- so workflow args are not intercepted.
Adds integration test covering all seven commands; updates docs/cli.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
In discovery mode (no path or a directory path), zero matches now print
a "(nothing to do)" notice to stderr and exit 0, so CI and agent loops
can call jaiph test unconditionally. Explicit file paths that are missing
or not *.test.jh still exit 1. Updates e2e tests and cli/testing/skill
docs; removes the completed queue item.

Co-authored-by: Cursor <cursoragent@cursor.com>
When a single test block contained both a pattern-dispatch `mock prompt { … }`
and queue-style `mock prompt "…"` / `mock prompt <const>` lines, the queued
entries were silently ignored at runtime. validateTestBlocks now emits E_VALIDATE
with a clear message at the second offending mock's location. Per-block scope:
separate test blocks in one file may still use different styles. Fixtures and
docs updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
jaiph format previously stripped double quotes from top-level const values
when the string contained no whitespace, producing inconsistent quoted vs bare
output within one file. EnvDeclDef now carries wasQuoted from parseEnvDecl;
emitEnvDecl re-emits quoted values with JSON.stringify and bare tokens
unchanged. Formatter tests cover preservation, idempotency, and round-trip
value equality. No golden AST regeneration was needed (no top-level const in
fixtures). Docs updated for the canonical quoting rule.

Co-authored-by: Cursor <cursoragent@cursor.com>
dzikowski and others added 15 commits June 19, 2026 13:28
Replace the curl|bash installer’s git clone + npm build + node shim with
per-platform release asset downloads verified via SHA256SUMS. Keep the
JAIPH_FROM_LOCAL path building dist/jaiph via build:standalone for e2e
and developers; add installer acceptance tests and update setup/docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
`jaiph install` now accepts registry names (`name` or `name@version`)
alongside git clone URLs. A new registry module defines the index format
and loadRegistryIndex() for file:// or remote sources via JAIPH_REGISTRY
or the default https://jaiph.org/registry URL. The registry key names the
.jaiph/libs directory and lock entry; restore-from-lock still uses stored
URLs only and never contacts the registry. Docs, usage text, and unit
tests cover name@version, error shapes, and lock restore isolation.

Co-authored-by: Cursor <cursoragent@cursor.com>
After each successful clone, jaiph install validates that the tree contains
at least one .jh module, records the cloned HEAD as a 40-char commit in
.jaiph/libs.lock, and removes .git so installed libs are plain files.
Restore re-verifies pinned commits and fails with both SHAs when a tag has
moved; legacy lockfiles without commit still restore. Includes local git
fixture tests and a docker.test.ts fix to avoid parallel overlay races.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add scripts/build-registry.mjs to fetch jaiphlang/registry, validate via
loadRegistryIndex, and write docs/registry atomically without touching
the file on failure. Seed the shipped index, wire npm run registry:build,
add shipped-file and build-script tests, and document publishing flow,
lockfile semantics, and JAIPH_REGISTRY in docs/libraries.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce .jaiph/prepare_release.jh to automate release prep: version
bump, dirty-tree/tag preflight, docs/install ref update, displayed
--version check on built artifacts, and registry rebuild. Codegen
src/version.ts from package.json via embed-assets so jaiph --version
stays in sync for both tsc and bun --compile builds without runtime
package.json reads. Add workflow tests, unit/E2E version assertions,
and document the workflow in contributing.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Jakub Dzikowski <jakub.t.dzikowski@gmail.com>
Add two QUEUE.md tasks: an inplace Docker sandbox mode (live host
edits, machine still isolated, warn+confirm safeguard) and CLI flags
(--workspace/--inplace/--unsafe/--yes) that front-end the sandbox env
switches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the `inplace` sandbox mode (live host edits, machine still
isolated) with warn+confirm safeguard, env-leak prevention, and banner
surfacing; full tests and docs. Raise default docker_timeout_seconds
to 14400 (4h). Update QUEUE.md (inplace task done; run-flags + prompt
retry remain) and switch the vendored git.jh commit workflow backend
to claude.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds first-class CLI surface for sandbox/runtime switches that were
previously env-var only. parseArgs in src/cli/shared/usage.ts now
recognizes --workspace <dir>, --inplace, --unsafe, and --yes/-y, all
stopping at -- like the existing flags. runWorkflow resolves the
workspace via the explicit flag (mirroring jaiph compile) and threads
boolean flags through a new applySandboxFlags helper in
src/cli/run/env.ts that mutates the local runtime env map only — never
process.env — before resolveDockerConfig/selectSandboxMode consume it,
keeping the env layer the single source of truth. --inplace + --unsafe
(or the mixed flag/env equivalent) fails fast with E_FLAG_CONFLICT.
Wrap executePrompt in runPromptStep with an automatic retry loop on
transport failure (result.status !== 0) using a fixed escalating
backoff: 15s, 1m, 10m, 30m, 2h — five delays, six total attempts.
Deterministic post-processing failures (invalid JSON, schema
validation) are not retried and still fail immediately. Retry composes
below recover/catch: backoff is exhausted before the failure reaches
the enclosing recover loop, and each attempt is a fresh executePrompt
with its own PROMPT_START/PROMPT_END events. Every failed attempt and
the final termination emit a LOGERR through emitter.emitLog with the
attempt number, backend, error summary, and (for retries) the next
delay. The sleep is injectable and the schedule parameterizable via
JAIPH_PROMPT_RETRY_DELAYS (comma-separated ms/duration list);
JAIPH_PROMPT_RETRY=0 disables retry entirely; invalid override values
error clearly. Backoff sleeps are interruptible — SIGINT/abort during
a wait stops retrying promptly. The full backoff (~2h41m) exceeds the
default runtime.docker_timeout_seconds (3600s); under Docker, retries
are effectively capped by the container timeout unless raised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parseArgs matched flags by exact token, so --workspace=/path (and
--target=/path) fell through to positional instead of being parsed.
Split long options on the first '=' to accept both --flag value and
--flag=value; reject =value on boolean flags with a clear error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-flight that checks the API key / token for each backend the entry
workflow uses (claude/cursor/codex), failing fast (Docker) or warning
(host login path), with the .jh file + config scope named in the error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a host-side credential pre-flight in runWorkflow that runs after
the module graph, effective config, and Docker mode are resolved but
before launching the workflow runner or container. The pre-flight
scans the entry .jh file's module-level and workflow-level config
blocks (plus the effective default from JAIPH_AGENT_BACKEND) to
collect the distinct backends the run could reach, then checks the
required credential against the env that actually arrives at the
agent (runtimeEnv on host, or the forwarded allowlisted env under
Docker via isEnvAllowed). Per-backend rules: codex hard-fails on
both host and Docker when OPENAI_API_KEY is missing (no CLI-login
fallback exists); claude (ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN)
and cursor (CURSOR_API_KEY) hard-fail under Docker but only warn on
host (a stored CLI login may still authenticate). Hard failures use
the stable E_AGENT_CREDENTIALS code, exit non-zero, and spawn no
runner or container. Every error and warning names the backend, the
model when agent.default_model is set, the entry .jh file path, the
config scope that selected the backend, and the concrete remedy.
The --raw path is intentionally skipped (embedded inner runner, not
a user-facing launch). The codex runtime guard inside runCodexBackend
is kept as defense-in-depth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JAIPH_UNSAFE / --unsafe is the explicit run-on-host escape hatch, so the
credential pre-flight should not warn or hard-fail there — a stored CLI
login works and the codex runtime guard remains as backstop. Early-return
empty when runtimeEnv.JAIPH_UNSAFE === "true"; two tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minor bump 0.9.4 -> 0.10.0 (package.json, package-lock, docs/install
REPO_REF). Stamp CHANGELOG # Unreleased -> # 0.10.0 with Summary + All
changes; add the missing --flag=value fix and 4h default-timeout entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…skill

Eight-task docs redesign in QUEUE.md (quarantine -> foundation -> explanation
-> how-to -> reference -> tutorials -> nav -> docs_parity gate), each
standalone with an anti-bias draft-from-source-then-reconcile protocol.
Add .jaiph/skills.lock pinning the vendored documentation-writer skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dzikowski and others added 14 commits June 19, 2026 18:25
…ph run)

Task 8 runs `jaiph run .jaiph/docs_parity.jh` on a clean worktree rather
than editing files, so it cannot be auto-picked by the queue agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move every flat-mix docs page into docs/_legacy/ except architecture.md
and jaiph-skill.md (both stay live — architecture.md is read by
.jaiph/docs_parity.jh; jaiph-skill.md is fetched raw by agents). Add
_legacy to the Jekyll exclude list so the quarantined pages stay in git
but are not published, trim docs/_layouts/docs.html nav to the still-live
pages, and repoint README + docker.test.ts parity checks at the new
_legacy paths. A new integration/docs-legacy-quarantine.test.ts asserts
the move, that no nav entry targets a quarantined permalink, and (when
Bundler is available) that bundle exec jekyll build emits no _site/_legacy
output. No runtime/CLI/language changes — purely a docs-site reorg to
prevent agent anchoring during the Diataxis redesign (task 1/8).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establishes the machine-checkable backbone for the remaining six tasks
of the Diátaxis docs redesign (3–8). Defines a `diataxis:` front-matter
key valued tutorial | how-to | reference | explanation | contributor and
applies it to the two currently live pages — architecture.md (explanation)
and jaiph-skill.md (contributor). A new integration/docs-structure.test.ts
runs five docs-lint checks via Node --test (auto-picked up by npm test):
every published docs/*.md must declare a valid diataxis value; every nav
permalink in docs/_layouts/docs.html must correspond to a published page
and every page must be linked from nav exactly once; every internal
markdown link / permalink / redirect_from must resolve to a known route
(with anchor checking against kramdown/GFM heading slugs and IALs); and
every historical nav permalink mined from git log must still resolve via
a current page or redirect_from — the redirect-coverage check. A sixth
test pins the legacy-quarantine boundary so docs/_legacy/*.md stay
exempt. The architecture and jaiph-skill pages absorb the historical
permalinks (/getting-started, /setup, /libraries, /artifacts, /language,
/grammar, /cli, /configuration, /testing, /spec-async-handles,
/spec-async-isolated, /target-design, /inbox, /hooks, /sandboxing,
/reporting, /contributing) under redirect_from so external links keep
working until tasks 3–8 own each one. The Jekyll nav flips the Agent
Skill entry from the raw GitHub URL to the in-site permalink now that
the page is a first-class published quadrant. The legacy-quarantine test
is loosened to permit jekyll-redirect-from meta-refresh stubs while
still failing if quarantined prose leaks into _site/. The embedded
JAIPH_SKILL_MD_BASE64 in src/runtime/embedded-assets.ts is regenerated
to match the new front-matter byte-for-byte. No runtime, CLI, or
language changes — purely the verification harness.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recreate inbox, sandboxing, spec-async-handles, and why-jaiph as live
explanation pages authored greenfield from source and then reconciled
against their docs/_legacy/ copies. Each declares `diataxis: explanation`,
picks up its permalink (and a redirect_from where the legacy slug
differs), and is wired into the docs nav. The sandboxing page covers
the model + threat split only — the enabling procedure and the
runtime.docker_* key table are deferred to the how-to (task 4) and
reference (task 5). architecture.md stays live; its redirect_from list
drops the slugs now owned by the new pages and a couple of transitional
deep links collapse to bare sandboxing.md. New
integration/docs-explanation-task3.test.ts grades all five acceptance
criteria; the legacy-quarantine and structure harnesses are loosened
to model the recreated-with-legacy-reference state. README.md surfaces
the new pages and trims them from the legacy index. No runtime, CLI,
or language behavior changes.
Adds the eight task-oriented How-to recipes to `docs/` as published
Diátaxis how-to pages, each authored greenfield from the TypeScript/Bash
source plus `docs/architecture.md` first and then reconciled against the
corresponding `docs/_legacy/` copy (per the anti-bias protocol in
`.jaiph/skills/documentation-writer/SKILL.md`). Each recipe is shaped
goal → prerequisites → numbered steps → Verification → Related, with
no conceptual digressions: install & switch versions
(`docs/setup.md`, `/how-to/install`), run in a Docker sandbox
(`docs/sandbox-run.md`, `/how-to/sandbox-run`), authenticate agent
backends (`docs/agent-auth.md`, `/how-to/agent-auth`, NEW), configure
backend & model (`docs/configure-backend.md`,
`/how-to/configure-backend`), add a hook (`docs/hooks.md`,
`/how-to/hooks`), use & publish a library (`docs/libraries.md`,
`/how-to/libraries`), save artifacts (`docs/artifacts.md`,
`/how-to/artifacts`), and write & run tests (`docs/testing.md`,
`/how-to/testing`). Each page declares `diataxis: how-to` and a
matching `permalink:`, absorbs any retired permalink via
`redirect_from:`, and is reachable from a new How-to group in the
Jekyll nav. `docs/architecture.md` drops the now-owned slugs from its
own `redirect_from:`. The legacy-quarantine harness graduates the five
recreated pages from `QUARANTINED_PAGES` to `RECREATED_WITH_LEGACY`,
repoints its publish-side leak probe from `/hooks` to
`/getting-started`, and resolves nav entries by declared
front-matter `permalink:` so custom `/how-to/*` slugs validate
cleanly. A new `integration/docs-how-to-task4.test.ts` grades the
quadrant end-to-end (front-matter, redirect absorption, nav reach,
agent-auth literal credentials + error code, recipe shape).
README.md and `docs/index.html` are repointed at the new how-to
pages. No runtime, CLI, or language behavior changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build the Reference quadrant of the Diátaxis docs rewrite — five
information-oriented lookup pages authored greenfield from the
TypeScript/Bash source and `docs/architecture.md`, then reconciled
against their quarantined `docs/_legacy/` copies per the anti-bias
protocol in `.jaiph/skills/documentation-writer/SKILL.md`.

New pages (all `diataxis: reference`, table-driven, no walkthroughs or
opinion):

- `docs/cli.md` — every `jaiph` invocation form, subcommand, flag, exit
  code, progress-marker glyph, and `run_summary.jsonl` event type.
- `docs/configuration.md` — every config key with type, default, env
  equivalent, and precedence rules.
- `docs/grammar.md` — authoritative grammar reference.
- `docs/language.md` — exhaustive step-type and expression reference
  (any "why"/walkthrough prose deferred to the Explanation quadrant).
- `docs/env-vars.md` — consolidated reference aggregating every
  `JAIPH_*` / `ANTHROPIC_*` / `CURSOR_*` / `CLAUDE_*` / `OPENAI_*`
  variable from source, absorbing the sandboxing config/failure-mode
  tables excluded in task 3.

Acceptance harness: `integration/docs-reference-task5.test.ts` enforces
source-parity for the env-var reference (every `JAIPH_*` name in `src/`
appears in the table and vice versa — fails on drift in either
direction) and grep-asserts the reference pages contain no
second-person tutorial prose or numbered walkthroughs. docs-lint,
internal-link, and redirect-coverage checks (task 2) remain green.

Nav updated in `docs/_layouts/docs.html` to surface the new Reference
group; legacy quarantine test updated for the now-published pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Land the Tutorials quadrant (task 6) and wire the finished Diátaxis
structure together (task 7) as one commit.

Tutorials: docs/first-workflow.md (permalink /tutorials/first-workflow,
absorbs /getting-started) and docs/first-agent-run.md (permalink
/tutorials/first-agent-run) author the two learning-oriented entry
points from source first and reconcile against docs/_legacy/.

IA finalization: docs/_layouts/docs.html nav is regrouped into the
five Diátaxis sections in order — Tutorials, How-to guides, Reference,
Explanation, Contributing — each listing exactly its published pages
with active-page highlighting preserved. docs/index.html's top nav and
footer lead with the first tutorial and the how-to index instead of a
flat docs link. jaiph-skill.md absorbs /contributing.md for parity
with the other live pages. architecture.md drops /getting-started
from its redirect_from list (now owned by the new tutorial), so each
historical permalink resolves through exactly one stub.

Two new integration tests grade the work: docs-tutorials-task6.test.ts
asserts the tutorial front-matter, nav presence, redirect ownership,
and that the first .jh fence in first-workflow.md is executable end-
to-end against the CLI; docs-nav-structure-task7.test.ts asserts the
five nav-group headings appear in order and that every published
diataxis: page sits under the matching section exactly once.

README.md is updated to surface the two tutorials in the top link bar
and the Docs-note callout, and to repoint the remaining legacy
getting-started references at the live tutorial.

No runtime, CLI, or language behavior changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…anup

.jaiph/docs_parity_redesign.jh is a redesign-aware copy of docs_parity.jh:
lists docs recursively, excludes docs/_legacy/, and VERIFIES the Diataxis
structure against source instead of re-consolidating it (the stock workflow
would merge/split pages and undo the redesign). Rework QUEUE task 8/8 into
the agent-doable post-parity cleanup (delete the _legacy quarantine, drop
the Jekyll exclude, re-run tests + build) that runs after the maintainer's
manual parity pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parity is run out-of-band by the maintainer; the remaining cleanup
(delete docs/_legacy, drop the Jekyll exclude, re-run tests + build) is
agent-doable, so mark it #dev-ready with the precondition noted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the +x mode of the other .jaiph/*.jh workflows so the shebang
runs directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ard)

- Exclude docs/vendor and docs/_site from the page list (was only _legacy),
  which had it processing 226 files incl. ~204 gem READMEs.
- Make the changed-files guard pattern-based instead of a frozen snapshot,
  so prompt-created docs pages pass while src/tests/.jaiph/scratch fail.
- Prompts now forbid editing tests/.jaiph and creating scratch scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The checker mapped foo.md -> /foo, which breaks for nested permalinks
(e.g. /tutorials/first-workflow) that jekyll-relative-links resolves by
target file. Resolve a .md link to the target page's declared permalink
instead, so prompt-created pages with nested permalinks pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Output of .jaiph/docs_parity_redesign.jh over the redesigned docs: 22
pages verified against source, new contributing.md (Contributor quadrant,
with quarantine-test update), README/CHANGELOG/index/nav updates. Fix two
stale cross-page anchors (em-dash slugs collapse to a single dash; the
artifacts section moved to artifacts.md). All 31 docs-lint/structure/nav/
link tests green; 0 broken internal links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerated by npm run build so the embedded skill asset matches the
updated docs/jaiph-skill.md committed in the redesign parity pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant