- Flip pauseOnFailure default to false in cli.ts and local-job.ts
- Replace --exit-on-failure/-x with --pause-on-failure/-p
- Add .agents/rules.md requiring /agent-ci before completing work
- Rename verify workflow to agent-ci (aliased as validate) with -p flag
- Rename package from 'machinen' to 'agent-ci' across all source, tests, scripts, configs, and docs
- Rename CLI binary from 'machinen' to 'agent-ci'
- Rename .machinen → .agent-ci, .env.machinen → .env.agent-ci, MACHINEN_* → AGENT_CI_*
- Rename --runner flag to --name/-n (--runner still accepted for compat)
- agent-ci-dev script now builds DTU before running CLI
- Extract YAML compatibility tables to cli/compatibility.md
- Expand CLI usage docs with per-command flag tables
- Document DOCKER_HOST support for remote Docker
When running inside an AI agent (AI_AGENT=1 or --quiet/-q flag), replace
the 80ms logUpdate render loop with a synchronous callback that emits
plain-text step progress to stderr. This avoids massive token waste from
animated spinners/trees that are meaningless to LLMs.
Agent-mode output includes: runner start with log path, step progress
(running/completed/failed), and pause-on-failure details with retry command.
Also adds a tarball route to the DTU so actions/checkout doesn't 404 — it
returns an empty tar.gz since the workspace is already bind-mounted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace RenderContext with RunStateStore + pure state-renderer (no I/O in render)
- Fix direct container jobs failing to connect to ephemeral DTU (was hardcoded to 127.0.0.1:80)
- Bind ephemeral DTU to 0.0.0.0 so containers can reach it via bridge network
- Show runner name (dimmed) alongside job name in multi-job collapsed output
- Use project-relative .machinen dir inside Docker for correct bind-mount paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add RenderContext class that composites multiple workflow trees
into a single logUpdate frame, grouped by workflow name
- Multi-job workflows always share a RenderContext so all runners
appear in one composite tree (fixes overwriting bug)
- --all mode runs workflows in parallel via Promise.allSettled
- Completed jobs collapse to a single ✓/✗ summary line
- Paused jobs show retry hint as child node
- Suppress console.log/emit in shared context to prevent logUpdate
cursor corruption
- handleRun returns JobResult[] with single summary at the end
- Add warm-cache serialization for --all mode
- Add taskId to buildJob in multi-job path
- Simplify machinen-dev script to run tsx directly
- Skip 'Complete job' in main loop; add it last with correct number
- Show 'Post Setup' and 'Complete job' only after job finishes
- Display runner name (containerName) in boot spinner and tree
- Remove elapsed timer from workflow root label
- Use renderTree() for boot spinner to prevent layout shift
- Add 1-space left padding to tree renderer output
- Update tree-renderer tests for new padding
Discovers all .yml/.yaml files in .github/workflows/, filters by
pull_request/push triggers matching the current branch via
isWorkflowRelevant, and runs each sequentially.
- Document workflow relevance criteria for --all in README
- Add machinen-dev script alias to root package.json
- Make --pause-on-failure the default, add --exit-on-failure/-x to opt out
- Remove unused dev script, concurrently, and wait-on deps
- Move tagline from root README to cli/README
The runner sends step output via the Job-level feed (Job UUID as
recordId), which the DTU couldn't map to a step name. Now the
timeline handler tracks the currently in-progress step, and
writeStepOutputLines falls back to it when the feed recordId has
no direct mapping. This gives failing/paused steps their own
properly named log files (e.g. Run-assertion-test.log) instead of
an opaque UUID file.
Also adds Enter-to-retry: pressing Enter in the terminal while
paused triggers a workspace sync and retry signal directly.
When a step pauses on failure, the tree UI now shows:
- Last 20 lines of step output below the tree (assertion errors,
code context, etc.) matching the end-of-job summary quality
- Press Enter to retry directly from the paused terminal
- Ctrl+C to abort
The step log is resolved via sanitized name with fallback to the
most recently modified .log file (the DTU writes logs under feed
UUIDs that differ from timeline record IDs).
Allows rewinding to an earlier step when retrying a paused runner:
- --from-step <N>: skip steps before N, re-run from step N onwards
- --from-start: re-run all run: steps from the beginning
Each wrapped step embeds a 1-based index matching the tree UI and
checks a from-step signal file on entry. Steps below the target
exit 0 (skip), the matching step deletes the signal and continues.
Closes#35
When running 'machinen retry', the CLI now rsyncs local source files
into the run's workspace before sending the retry signal. This ensures
local code edits are visible inside the container without requiring a
full workflow restart.
Uses git ls-files + rsync to respect .gitignore and handle deletions,
while preserving node_modules and .git in the workspace.
Minimal vitest test that reads a fixture file and asserts its value,
designed to validate the pause-on-failure retry mechanism end-to-end.
- .github/workflows/retry-proof.yml
- experiments/retry-proof/ (package, test, fixture)
- Added retry-proof to pnpm workspace
- Extend computeLockfileHash() to detect package-lock.json, yarn.lock, bun.lock
- Replace PNPM-only .modules.yaml sentinel with universal hasInstallSentinel()
that checks .package-lock.json (NPM), .yarn-integrity (Yarn), .cache (Bun)
- Add NPM and Bun global cache bind-mounts into containers
- Extend virtualCachePatterns to skip tar for all PM cache keys
- Add benchmark scripts in experiments/pkg-cache-bench/
- 176 tests pass, TypeScript clean
When enabled, run: steps are wrapped in a bash retry loop that pauses
execution on failure and waits for an external signal before continuing.
- New step-wrapper utility wraps run: step scripts in a retry loop
- Signals directory bind-mounted for container ↔ CLI IPC
- CLI polls for paused signal and renders ⏸ icon with frozen timer
- Yellow 'Step failed attempt #N' child leaf in the tree output
- machinen retry --runner <name> / machinen abort --runner <name>
- Validates container is still running before sending signals
- Optional attempt field on JobResult for reporter
Closes#27
## Problem
derive had zero test infrastructure -- no vitest config, no test harness, no test files. We also had no mechanism to generate tests from the Gherkin specs that derive produces, which is the natural next step in the derive pipeline (conversations -> specs -> tests).
## Solution
We built e2e test infrastructure and a `derive tests` command, then dogfooded the command to generate the bulk of the test suite from derive's own specs.
**Hand-written foundation (2 test files, 6 tests)** -- We wrote two test files manually: `derive-one-shot.test.ts` (spec pipeline e2e) and `derive-tests.test.ts` (test generation command e2e). These establish the conventions, patterns, and infrastructure that generated tests build on. Supporting infrastructure includes:
- Three env var overrides (`CLAUDE_BIN`, `CLAUDE_PROJECTS_DIR`, `MACHINEN_DB`) for full isolation from the host system
- Two deterministic substitute binaries that replace `claude -p` in tests: `fake-claude-gen-specs` (keyword extraction -> Gherkin template) and `fake-claude-gen-tests` (reads specs, writes vitest files)
- A reusable test harness (`setupDeriveTest`) that handles temp directory setup, git init, JSONL fixture writing, derive invocation, and cleanup
**`derive tests` command** -- Spawns an agentic `claude -p` session with full filesystem tools, letting Claude read specs and existing test conventions, then write test files directly. Source code isolation is enforced by convention-based system prompt instruction (Claude reads only specs, tests, and config -- not implementation source). The command is independent of git state and conversation discovery -- it operates purely on spec files on disk.
**Generated test suite (8 test files, 30 tests)** -- We ran `derive tests --scope derive` against derive's own 10 `.feature` spec files. Claude read the specs and the two hand-written tests to learn the conventions, then generated 8 additional test files covering: CLI context detection, conversation discovery, incremental updates, multi-file spec storage, one-shot edge cases, reset mode, scope flag, and spec file location. 36 tests pass across 10 files total.
**Bug fixes discovered during manual testing** -- Running `derive tests` against the real Claude CLI surfaced two bugs: a `--` args leak where pnpm passed `--` as a literal argv element (preventing `derive tests` from dispatching), and an inaccurate `--keep-spec` spec scenario (it preserves content as LLM context, not files on disk). Both were corrected, along with the spec and one generated test that reflected the wrong semantics.
<img width="701" height="737" alt="Screenshot 2026-03-09 at 01 03 48" src="https://github.com/user-attachments/assets/0e7e5fce-10d3-424a-ae45-e126fed0a9a7" />