v0.2.0
Immutable
release. Only release title and notes can be modified.
Added
- Schema contract closeout (v0.2.0, AUDIT F4) —
output_schema_refis now a
first-class, enforceable contract end to end.- One shared validator. The five duplicated
resolve→validatebranches in
langgraph/nodes.mjs(stub / command / regression / scoring + the LLM
handoff) collapse into a singlevalidateRolePayload(roleDef, payload)helper
inschemas/index.mjs, returning the same{ ok, errors, schema }evidence
(ornullwhen nothing is declared). - Opt-in strict enforcement. A role may set
enforce_output_schema: trueto
promote soft validation to a hard halt — a non-conforming payload routes to
$haltwith a typedoutput_schema_violationblocker instead of recording
soft evidence and continuing. Soft validation stays the default; the flag is
type-checked inworkflow-validate.mjs(warns when set without a schema). - TUI round-trip guard.
writeWorkflowstrips a ref-derivedoutput_schema
before persisting, so editing a ref-declared workflow keepsoutput_schema_ref
authoritative and never bakes the inline schema onto disk.
- One shared validator. The five duplicated
full-audit-sweep-gatedworkflow template — the documented gated example
(ratifies the default-workflow gate decision): the leanfull-audit-sweep
shipsscoringwith no gates (informational), whilefull-audit-sweep-gated
opts in to exactly oneno_high_severity_findingsgate (reviewer-severity →
$halt).npm run test:terminal— a named lane that runs the full suite under
MAESTRO_BACKEND=terminal, pinning the zero-dependency terminal backend (the
default) as a first-class tested configuration in CI. README/CONTRIBUTING now
document the terminal backend as the default andherdras optional
acceleration.maestro servemulti-service management —servebecomes a service
manager: register, run, and supervise multiple tracker-backed services from a
single state dir. Each service is a named definition (serve add <name> --slug … [--port --workflow --var --workspace --shared-state]) backed by an
owner-checked0600definition + pid-record store. Lifecycle is
identity-verified against the recorded pid via/proc(liveness,stop,
pause,resume) with a detached worker spawn under an exclusive start lock,
so a stale pid can't be killed or double-started.serve list/status
derive live state;serve logs <name> [-f] [-n N]tails a bounded worker log;
serve adoptmaterializes a legacy single-tracker config as adefault
service. Service overlays resolve theserverconfig block with a var
denylist and port/api-key validation, failing fast at start on an unset
api-key var or a port collision.- Portable roles — Maestro Role Convention (MRC) — author a role once and
reuse it across workflows, and consume Claude Code subagents and skills
directly. A role loader normalizes.claude/agents,SKILL.md, and native
.maestro/rolesunits into oneRoleDef; a role'ssourceplus inline
overrides compose (nosource⇒ unchanged behavior). Per-role
tools/deny_toolsallowlists thread through the adapter seam — claude
hard-enforces via--allowedTools/--disallowedTools, codex folds Bash scope
into--sandbox, other providers record advisory scope in the run manifest.
Shipstriage(classifier branch) andresearch(gather→synthesize) demo
workflows,maestro role list|show|lint+import-agentCLI,
classification/research schemas, anddocs/role-convention.md. - Per-alias env for multi-account CLIs — provider aliases may now be objects
{name, command?, env?}, so multiple accounts of the same CLI (e.g. two
Claude logins on differentCLAUDE_CONFIG_DIRs) work purely through
config.jsoninstead of hand-written shell aliases. Alias env merges over
provider env (alias wins) with~/$VARexpansion; the resolved binary is
spawned directly (nobash -ic) while the account name stays the routing
identity. Bare-string aliases are unchanged. New modulesrc/providers.mjs
plus a full TUI account manager (add/edit/delete + env editor with denylist
rejection). - Opt-in autonomous claude write mode — a write-permission role may set
MAESTRO_CLAUDE_WRITE_MODE(e.g.acceptEdits,bypassPermissions) so a
non-interactive claude applies edits without a human at the CLI, matching
codex'sapproval_policy=never. Unset preserves the legacy permission mode. - TUI & CLI authoring —
maestro setup trackerwizard writes
server.tracker(Linear) and chains theLINEAR_API_KEYprompt; the
full-screen TUI gains workflow editing (switch / new / delete / remove-role /
apply-template / validate) and detail-screen actions (run / edit /
approve-substitution / skip-role / switch-provider). Role detail and footer
keybinds wrap (ANSI-aware) instead of clipping.
Changed (BREAKING)
maestro serveis now a subcommand group, not a one-shot foreground
server. The v0.1.1maestro serve [--config] [--state-dir] [--port]
invocation no longer starts a server (maestro serve --config …errors with
unknown serve subcommand). Usemaestro serve start <name>afterserve add, or start a server directly with the flag-first formmaestro [--config <path>] [--port <n>](noserveword).
Changed
- Bare
maestroprints help (like git/docker/npm) instead of defaulting to
server mode, which previously died with a cryptic
unsupported_tracker_kind: missing. Flag-first invocations (maestro --port 4100) still start the server.
Fixed
- Engine — robust agent failure handling. Failure classifiers
(isUsageLimitFailure/isContextWindowFailure) now read only the error
channel (message + stderr + genuine stream-json error lines), so an agent that
merely discusses "rate limits" or "context windows" no longer false-trips a
retry. A claude run that emits a terminalsubtype:"success"is salvaged
instead of discarded asagent_failedon a non-zero exit (e.g. after gated
tool denials). A custom event routed to$completenow finalizes the run as
succeeded instead of stranding it as running. F7: SIGTERM timeouts escalate to
SIGKILL after a 2s grace. F8: stream tails decode through aStringDecoderso
a multibyte codepoint split across chunks reassembles. - Persistence — data-integrity + resource bounds. F5:
updateTaskreads
synchronously so concurrent updates to one id can't interleave and drop a
patch. F6: SQLite opens with WAL +busy_timeout=5000. F9: the rate-limiter
evicts the LRU bucket pastmaxBuckets. F4 (base):output_schema_refexpands
to an inline schema at workflow-load time (guarded byassertInsideDir) — the
foundation the schema-contract closeout above completes. - Project cleanup tolerates out-of-band-removed worktrees — a vanished
worktree path is no longercd'd into (which failed with an opaquespawn git ENOENT); cleanup clears git metadata best-effort and stays idempotent.
Security
- Dashboard XSS (F1) — the inlined snapshot JSON neutralized only lowercase
</script>; every<is now escaped, closing mixed-case</Script>and
<!--breakouts via attacker-influenced issue titles/descriptions. - Symlink-escape read boundary (F2/F3) —
assertInsideDirReal/
isInsideDirReal(realpath both ends) gate the MCP read tools, so a symlink
planted in an agent-writable run dir can't exfiltrate arbitrary files; the
assertInsideDirdocstring is corrected to state it is lexical-only. - Role
sourcepath-escape — the engine's source-resolution loop now guards
role.sourcewithisSafeRelativeRefbeforeloadRole(a../absolute
source becomes abad_role_sourceblocker), the sole enforceable gate since
composeRolestripssourcebefore the non-blocking validator. An
imported/shared workflow could otherwise read an arbitrary file into the agent
prompt and the run-manifest. - Secret handling (F10/F11) — stored secrets skip
ENV_KEY_DENYLISTkeys on
load (noLD_PRELOAD/NODE_OPTIONSpromotion intoprocess.env); KDF
N/r/pread from the secret envelope are bounds-checked before scrypt, so
a tampered envelope can't drive a decrypt-time CPU/memory DoS. servehardening — service overlays apply a var denylist and reject name
traversal; start fails fast on an unset api-key var or a port collision.- Dependency — bump the transitive
honopin to 4.12.25
(GHSA-wwfh-h76j-fc44, path traversal);npm audit --omit=devreports 0
vulnerabilities.