pnpm -r test was including the root workspace, whose test script
is itself `pnpm -r test`, causing every test to run twice in parallel.
Both runs share /tmp/dtu_cache, and when one run's beforeEach cleanup
deleted temp files the other run's server was using → ENOENT.
Add --include-workspace-root=false to prevent the root workspace from
being matched by recursive test runs.
When a runner-specific job was seeded, it was stored in both state.jobs
(generic pool) and state.runnerJobs (runner-targeted). A runner polling
before its own job was seeded could fall back to state.jobs and steal
another runner's job, leaving the original runner spinning indefinitely.
Fix: runner-targeted jobs now go only into state.runnerJobs. The generic
pool is reserved for jobs without runner affinity.
Adds three tests covering the race condition, concurrent stress with
8 runners, and the job-theft scenario.
Closes#47
The runner's FinalizeJob deletes _temp/_runner_file_commands/ inside the
container before exit, making files unreachable from the host.
Solution: inject a synthetic 'Capture outputs' step (runs with if:always())
that reads all set_output_* files and echoes them as ::agent-ci-output::key=value.
The DTU parses these prefixed lines and persists them to outputs.json in the
runner's log directory. On job completion, local-job.ts reads outputs.json and
attaches the outputs to the JobResult.
Pipeline verified end-to-end — all 8 smoke tests pass:
- Cross-job outputs (setup-outputs → consume-outputs) ✅
- Job-level if:always() ✅
- Matrix shards with fail-fast:false ✅
- Artifacts, services, env vars ✅
All 279 unit tests pass.
- Fix extractStepOutputs path: _temp/_runner_file_commands/ (not root)
- DTU outputs endpoint now captures and persists outputs.json
- local-job.ts reads outputs.json from log dir instead of work dir
- Add smoke test jobs for cross-job outputs and if:always()
All 279 tests pass.
#54 - Job-level if conditions:
- shouldSkipJob() evaluates if: conditions before dispatching
- Tracks upstream job results (success/failure/skipped)
- Creates synthetic skipped results for skipped jobs
- Default behavior: skip if any upstream dependency failed
#55 - Respect fail-fast setting:
- Replaces unconditional wave abort with parseFailFast check
- Only aborts if not all jobs in wave have fail-fast: false
- Logs when continuing despite failures
All 279 tests pass.
- parseWorkflowSteps now accepts needsContext parameter
- All expandExpressions calls in step parsing thread needsContext
- cli.ts wave loop accumulates job outputs and builds needsContext
for downstream jobs
- Step names now use raw YAML value for expression preservation
2 new tests, all 279 pass.
resolveJobOutputs() resolves job output templates (e.g.
${{ steps.check.outputs.skip }}) against the flat step outputs
captured by extractStepOutputs().
6 new tests, all 277 pass.
- 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>