Commit Graph

19 Commits

Author SHA1 Message Date
Peter Pistorius 9ec9d45061 Isolate node_modules across parallel jobs (#380)
* Fix parallel node_modules cache races

* Fix cache race regression reporting
2026-07-13 22:03:28 +02:00
Peter Pistorius 06fa6e6f61 Harden DTU release remediation (#379)
* Harden DTU control plane

* Harden DTU release remediation

* Clean Rust smoke parity artifacts
2026-07-13 12:16:26 +02:00
Peter Pistorius c3316730b3 Harden Rust orchestration parity (#368)
* Harden Rust orchestration parity

* fix(ci): avoid nested rust buildx smoke hang

* fix(ci): skip nested pause smoke in rust parity

* test(rust): cover detached pause launcher

* feat(rust): expand reusable workflows in parity

* chore(ci): instrument rust smoke parity

* fix(dtu): advertise container ip for nested runners

* chore(ci): add rust smoke status diagnostics

* fix(runner): attach nested runners to parent network

* chore(ci): retrigger pr checks

* fix(rust): clean nested network endpoints
2026-05-26 23:05:26 +02:00
Peter Pistorius 7a349fdb43 Add opt-in Rust native execution
## Problem

Agent CI still depended on the TypeScript runner as the main execution path. That made it harder to ship a small native binary and harder to prove the Rust path matched the TypeScript path. Review also found places where Rust and TypeScript could drift before the Rust path becomes the default.

## Solution

This PR adds the opt-in Rust native `agent-ci` binary and platform npm packages while keeping the TypeScript fallback. It splits the Rust code into `agent-ci-core`, `agent-ci-runtime`, and a thin CLI/orchestration crate. Pure planning, reusable workflow expansion, event contracts, and run-result contracts now live in core; runtime owns Docker, DTU, runner, macOS VM, workspace, execution-plan adapters, and the generic wave worker pool.

The fixture checks now use real TypeScript helpers instead of a shadow planner. Shared fixtures cover plans, events, run results, Docker socket resolution, and default job-limit CPU/memory vectors. The Rust runner honors `--jobs`, probes Docker memory for the default job limit, supports macOS VM concurrency limits, avoids nested runner container name collisions, has `--all` smoke coverage, and stages `bin/agent-ci` in every platform package.

## Validation

- `cargo check --workspace --all-targets`
- `cargo fmt --all -- --check`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo test --workspace --all-targets`
- `pnpm check`
- `pnpm fixtures:check`
- `pnpm golden:cli`
- `pnpm rust:smoke:parity`
- `pnpm agent-ci-dev run --all -q -p --json --github-token`

## Notes

Rust execution is still opt-in. The TypeScript implementation and fallback remain in place until the later default-switch and removal gates pass.
2026-05-24 15:22:20 +02:00
Peter Pistorius ab075d94be chore: require Node 24 and drop tsx (#351)
* chore: require Node 24 and drop tsx

Node 24 ships native TypeScript stripping as a stable feature. We no
longer need tsx to execute .ts files in development scripts.

Changes:

- `engines.node` bumped from `>=22` to `>=24` in each package.
- `tsx` removed from every workspace's devDependencies.
- Each `tsx foo.ts` invocation in package scripts becomes `node foo.ts`.
  Includes `scripts/agent-ci-dev.sh` (the dev wrapper) and the demo
  package.
- tsconfigs gain `allowImportingTsExtensions`, `rewriteRelativeImportExtensions`,
  and `verbatimModuleSyntax` so source uses `.ts` extensions while tsc
  still emits `.js` paths in dist.
- All relative imports rewritten from `from "./foo.js"` to `from "./foo.ts"`
  across 72 source files. Dynamic imports updated too.
- Type-only imports (`Job`, `Polka`, MessageResponse, …) converted to
  `import type` to satisfy `verbatimModuleSyntax`.
- The json-loader hook registration in workflow-parser.ts picks `.ts`
  when the source file exists (dev) and `.js` otherwise (compiled dist).
- Root `package.json` adds `"type": "module"` (no existing .js files
  affected) and `yaml` as a devDep (already used by scripts/diff-parse.ts;
  previously hoisted through cli's deps).
- `tests.yml` CI bumps from Node 22 to Node 24. Smoke fixtures that set
  `node-version: 22` are left alone — they exercise `actions/setup-node`
  with various versions deliberately.

Verified:
- pnpm check: clean
- Node 24 dev run: `node packages/cli/src/cli.ts --help` works
- Full smoke suite (`pnpm agent-ci-dev run --all`): 45/45 jobs pass

* fix(shutdown.test): use `node --input-type=module` instead of `npx tsx`

The shutdown integration test spawns a fresh process to sidestep
vitest's mock system. It was using `npx tsx`, but `tsx` is no longer
a dependency. Node 24 reads TypeScript files natively, so we ask Node
directly via `--input-type=module`.
2026-05-11 18:38:37 +02:00
Peter Pistorius edce84b8b1 fix(cli): unblock pause-on-failure pipes + NDJSON event stream (#315, #289) (#323)
* fix(cli): unblock --pause-on-failure in non-TTY pipes (#315)

Spawn the run as a detached worker when stdout isn't a TTY (and we're
not under an LLM harness), then exit 77 the moment the worker emits a
`run.paused` NDJSON event — freeing the caller's pipe while the worker
keeps the container + DTU alive for `agent-ci retry`. Retry tails the
same log so a re-failure surfaces as another exit-77.

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

* feat(cli): NDJSON event stream behind --json (#289)

Extends the existing pause/finish sentinel emitter into a full lifecycle
event stream. New `--json` flag (and `AGENT_CI_JSON=1` env var) emits
`run.start`, `job.start`/`finish`, `step.start`/`finish`, `run.paused`,
`run.finish`, `diagnostic` events on stdout — one JSON object per line —
each carrying an `event` discriminator and a `schemaVersion: 1` on
`run.start`.

`--json` is decoupled from `--quiet` so existing `-q` callers keep silent
stdout; the diff renderer is auto-suppressed under `--json` so ANSI
sequences don't collide with the stream. Detached-worker mode (#315)
keeps emitting only sentinels — the launcher filters non-sentinel events
anyway, so non-JSON callers don't pay the cost.

Renames `run.completed` -> `run.finish` to match the issue's naming.

Closes #289.

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

* fix(cli): suppress printSummary under --json

The ━━━ FAILURES ━━━ / ━━━ SUMMARY ━━━ blocks write to stdout
unconditionally, which interleaves with the NDJSON event stream and
breaks line-based parsers. Skip them when isJsonMode() — consumers get
the same information from job.finish + run.finish events.

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

* feat(cli): AGENT_CI_DETACHED=1 forces detached launcher on TTY

Overload AGENT_CI_DETACHED so it can serve both roles:

  - `=<absolute path>` → set by the launcher on its worker child;
    `isDetachedWorker()` returns true.
  - `=1` (or any non-absolute value) → caller-side opt-in: force the
    launcher path even when stdout is a TTY. Useful for manual
    verification of the #315 pause-then-exit-77 flow without having to
    construct a non-TTY pipe.

The two are distinguished by whether the value starts with `/`. New
`isForceDetachedRequested()` helper drives a `forceDetached` arg into
`shouldLaunchDetached`, which still respects `alreadyWorker`,
`agentMode`, and the `pauseOnFailure` requirement.

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

* test(ci): smoke for --json NDJSON event stream (#289)

scripts/smoke-289.sh creates a tmp repo with a 2-step passing workflow,
runs `agent-ci run --json`, and asserts:
  - run.start carries schemaVersion=1
  - step.start + step.finish for each declared step
  - job.finish status=passed
  - run.finish status=passed
  - every JSON-shaped line on stdout has a recognized event field

.github/workflows/smoke-json-events.yml runs the script in CI on every
PR (gated by the same safe-to-run / collaborator predicate as the other
smokes).

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

* fix(ci): stop smoke-resource-mismatch from hanging on real GitHub runners

The fixture in .github/workflows/smoke-resource-mismatch.yml declares
`runs-on: ubuntu-latest-999-cores` so agent-ci's resource-fidelity
classifier deterministically tags the job as `degraded` on any host.
With its previous `pull_request_target` trigger, GitHub Actions also
picked it up on every PR and queued a job that no real runner can
satisfy — five queued runs piled up on PR #323 alone, each timing out
after hours.

Switch the workflow to `workflow_dispatch:`-only so GitHub never
auto-dispatches it, and teach `isWorkflowRelevant` to treat
`workflow_dispatch:`-only workflows as relevant for `agent-ci run --all`
so the local smoke keeps running. The opt-in is gated to the sole-event
case to avoid overriding `pull_request` / `push` path/branch filters on
workflows that legitimately combine triggers.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:39:04 +02:00
Peter Pistorius 37f98a6c37 test(diff-parse): expand to job + workflow scopes, vendor third-party corpus, gate via pnpm check (#306)
Closes #297. CI/scripts-only — no packages/ change, so no changeset required
per .claude/rules/changeset-required.md.

- scripts/diff-parse.ts: walks raw YAML at workflow + job scopes (in addition
  to the existing step scope) and grades every recognized key against a
  preservation predicate or a documented expected-drop. Reuses focused parsers
  (parseJobIf, parseJobOutputDefs, parseWorkflowContainer, parseWorkflowServices,
  parseMatrixDef, parseFailFast, parseJobDependencies, getWorkflowTemplate).
  defaults.run.shell preservation detects the heredoc-wrap marker emitted by
  wrapScriptForShell.
- third-party-workflows/: vendored scan-only corpus — turborepo-lint.yml,
  playwright-infra.yml, docker-validate.yml — each with attribution header.
  Picked up automatically by collectWorkflowFiles.
- package.json: parse:diff added to the parallel pnpm check pipeline so
  regressions fail standard CI.

Result: 37 workflows scanned (.github/workflows + third-party-workflows),
0 drifts. pnpm check exits 0; pnpm agent-ci-dev run --all reports 39/39.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 11:35:52 +02:00
Peter Pistorius af76237154 tooling: add pnpm parse:diff — step-level parser drift detector (#298)
For each .github/workflows/*.yml, read the raw YAML and compare against
what parseWorkflowSteps emits, flagging any step-level key our parser
silently drops without a documented expected-drop policy.

First run over all 26 workflows: 0 drifts. Job-level and workflow-level
scans are tracked as follow-ups in #297.

Refs #297.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 08:51:39 +02:00
Peter Pistorius e2fe5761ba feat(compat): JSON source of truth + fill gaps + fix defaults.run.working-directory (#291)
* feat(compat): make compatibility.json the source of truth

Flip the compatibility matrix so packages/cli/compatibility.json is
authoritative. scripts/gen-compatibility-md.mjs renders the .md from it,
pnpm check runs a drift check, and the website imports the JSON directly
and renders inline markdown in keys/notes.

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

* test: add compat smoke coverage for secrets, working-dir defaults, runner image

Fill three audit gaps identified against packages/cli/compatibility.json:

- smoke-secrets-env-file: secrets resolved from .env.agent-ci and from
  the shell environment fallback
- smoke-defaults-workdir: workflow- and job-level
  defaults.run.working-directory plus a step-level override
- smoke-runner-image: .github/agent-ci.Dockerfile is picked up and the
  resulting image is used at job start

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

* fix(workflow): propagate defaults.run.working-directory to steps

Parse defaults.run.working-directory at workflow and job level and merge
it into each run-step's inputs with step > job > workflow precedence.
Step-level working-directory already worked; the defaults blocks were
silently ignored.

Closes #290.

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

* feat(compat): add proof field to compatibility.json rows

Every `supported`/`partial` row now lists the smoke workflows that
exercise it end-to-end. `pnpm compat:gen`/`compat:check` fails if a
listed path does not resolve, catching renames.

Rows with an empty `proof: []` are the audit gaps tracked in #292.

Refs #292.

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

* fix(dtu): attach step env per-step so it does not leak to siblings

Step-level env was being aggregated into the job-wide EnvironmentVariables
map (last-wins), so a later step's `env: FOO=x` would override an earlier
step's effective FOO at runtime. Emit step.Env as the mapped step's own
`environment` instead.

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

* test(compat): fill audit gaps #292 — env, expressions, local action

Three smokes covering eight of the ten gaps listed in #292:
- smoke-env-scoping: workflow + job + step `env:` precedence
- smoke-expressions: hashFiles, format, join, runner.arch, type
  literals, startsWith/endsWith
- smoke-local-action: `steps[*].uses: ./.github/actions/...`
  (verified via marker file rather than steps.<id>.outputs.*)

Proof fields updated for every row these smokes cover. hashFiles
downgraded to ⚠️ partial — agent-ci emits SHA-1, GitHub Actions emits
SHA-256 (tracked by #294).

Two gaps remain open as filed issues:
- #293 defaults.run.shell / steps[*].shell not applied
- #295 ${{ steps.<id>.outputs.<name> }} leaks literal text in run
  scripts

Refs #292.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 08:47:20 +02:00
Peter Pistorius cbf0c44bf2 Close referenced issues on release, not on version-PR merge (#277)
During pnpm run version, scripts/release-closes.mjs captures
Closes|Fixes|Resolves #N references from pending changesets into
.release-closes.json, pairs each with the PR that added the
changeset, and rewrites the keywords to Refs #N so the 'Version
Packages' PR does not close them on merge. After the release
workflow publishes, a new step closes each captured issue with
the comment 'Closes Issue #N via PR #M.'
2026-04-18 13:48:34 +02:00
Peter Pistorius f660c11a4d feat: support composite action step outputs and changed-files actions (#174)
* feat: support composite action step outputs and push event context

Enable workflows that use composite actions with cross-step output
references (e.g. Khan/actions@get-changed-files + filter-files) by:

- Preserving steps.* expressions for runner-time evaluation instead
  of expanding them to empty string at parse time
- Using correct BasicExpression token type (type 3, not 6) matching
  the runner's wire format
- Adding ContextName to steps so the runner can resolve
  steps.<id>.outputs.<name> expressions
- Adding push event before/after SHAs and GITHUB_API_URL so
  changed-files actions can call the compare commits API
- Implementing compare commits and list-PRs-for-commit mock endpoints
  in the DTU server using git diff on the original repo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve symlinks in agent-ci-dev.sh so it works when invoked via symlink

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 23:02:21 +02:00
Peter Pistorius e9c5df5f6f feat: support reusable workflows (local and remote) (#163)
* feat: support local reusable workflows (uses: ./.github/workflows/...)

Inline called workflow jobs into the caller's dependency graph so that
`jobs.<id>.uses` with local refs works end-to-end. Includes dependency
rewiring, smoke tests, runner seed resilience fix, boot-failure log
surfacing, and dev script improvements.

Closes #149

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update website compatibility matrix for reusable workflows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add section descriptions to website compatibility matrix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add descriptions to compatibility matrix legend items

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add 'not planned' status icon to compatibility matrix

Distinguish architectural limitations (concurrency groups, GitHub
secrets) from features that could be implemented in the future.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: support remote reusable workflows (uses: owner/repo/path@ref)

Pre-fetch remote workflow YAML from GitHub Contents API before expansion,
cache SHA refs forever and always re-fetch mutable tag/branch refs. Also
changes missing workflow files from warn+skip to hard failure.

Closes #165

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: split monolithic smoke-tests.yml into discrete workflow files

Each smoke test area now has its own workflow file for isolated failure
visibility: matrix, artifacts/services, cross-job outputs, job conditions,
reusable workflows (local + remote), and binary smoke.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix formatting in .docs/ files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: clarify agent-ci-dev.sh is for local development only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 17:07:13 +02:00
Peter Pistorius c8dd79f478 chore: move cli and dtu-github-actions into packages/ 2026-03-13 00:07:29 +02:00
Peter Pistorius 5b8acda506 Rename machinen to agent-ci
- 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
2026-03-12 22:35:26 +02:00
Peter Pistorius f58618c67f Add dev containers script. 2026-03-10 13:04:04 +02:00
Peter Pistorius baabf8e0df Rename supervisor to cli. 2026-03-06 22:57:53 +02:00
Peter Pistorius 922b01df41 Rewrite the past. Closes #14. 2026-03-06 21:54:22 +02:00
Peter Pistorius 8db72ed72e Add run script. 2026-03-06 08:51:27 +02:00
Peter Pistorius a79d4435f8 Finally support playwright tests: concurrency, docker in docker emulation. 2026-03-04 16:13:50 +02:00