Commit Graph

82 Commits

Author SHA1 Message Date
Peter Pistorius c8da13a8dc feat(cli): add var-file workflow vars (#361) 2026-05-19 14:38:33 +02:00
Peter Pistorius ef7029adf3 chore(workflows): pin GitHub Actions to full SHAs (#356)
Replace mutable tag refs (@v4, @v5, @v1, @main) with 40-char commit
SHAs and a trailing `# vX.Y.Z` comment, per GitHub's hardening guidance.
Covers .github/workflows/, .docs/marketing/demo-recording/, and the
skill-evals fixture repos. Embedded YAML inside heredocs is also pinned.

No packages/ code changes, so no changeset.
2026-05-12 05:38:50 +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 daa536c61e feat(cli): colocate run logs with checks JSON; add prune + clean (#312) (#325)
Move per-run log artifacts out of the runner's tmp working dir into a
stable, agent-ci-owned directory next to the per-branch checks JSON
(default `<stateDir>/logs/`, override via AGENT_CI_LOG_DIR). Log paths
recorded in the run-result JSON now survive OS-level pruning of
`os.tmpdir()`.

Add an opportunistic, throttled cleanup at the start of `agent-ci run`
plus an explicit `agent-ci clean` subcommand. Policy: never delete a
run dir referenced by any branch's checks JSON, then keep newest N
runs, then drop runs older than M days. Knobs:
AGENT_CI_LOG_RETAIN_DAYS=7, AGENT_CI_LOG_RETAIN_RUNS=20,
AGENT_CI_LOG_PRUNE=0 to disable. O_EXCL lockfile + last-prune stamp
guard against concurrent runs and per-job fan-out.

Also fix `buildStepEnv` in workflow-parser.ts to thread an envContext
through `expandExpressions`, completing PR #320's env.* expansion
support — `${{ env.JOB_KEY }}` inside another step's `env:` value now
resolves to the workflow- or job-level value instead of an empty
string. This made smoke-env-context.yml pass under agent-ci-dev.

Closes #312.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:58:36 +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
Will Roever 6a26caefa6 feat(workflow): support env.* context references in expressions (#320)
* feat(workflow): support expansion of env context references in expressions

`${{ env.* }}` references in workflow expressions silently resolved to empty
string because `resolveExprAtom` had no `env.` handler and no env context was
threaded through the evaluator. The fix adds initial support for expanding these
references in `run:` scripts, `with:` inputs, `name:`, and `env:` value
expressions — matching GitHub Actions behavior. `if:` conditions remain
unsupported (noted in `compatibility.md`).

* chore(smoke): add smoke-env-context.yml as proof for env.* row

Adds a smoke workflow that exercises ${{ env.* }} expansion in `run:`
bodies, in another step's `env:` value, in `with:` inputs (the original
Rails CI bug surface), and verifies the unknown-key → empty-string
contract. Also wires it as the `proof` reference on the env.* row in
compatibility.json.

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

---------

Co-authored-by: Peter Pistorius <peter.pistorius@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:10:17 +02:00
Simon 44595b134e feat(cli): surface degraded local runs for resource mismatches (#230)
* feat(cli): surface degraded local runs for resource mismatches

Add resource fidelity classification that compares local host resources
against workflow-declared runner/resource hints. When the local machine
is smaller than what the workflow requests, jobs are marked as degraded
and a warning is shown before execution starts.

Features:
- Parse runs-on labels and NODE_OPTIONS from workflows
- Classify jobs as faithful or degraded based on CPU/memory hints
- Show [degraded] tag and actionable warnings in CLI output
- Add smoke test for degraded classification
- Update documentation with degraded mode guidance

Complements PR #226 which adds memory-aware concurrency limiting.
Together they replace the approach in closed PR #78.

* refactor(cli): use runner specs instead of NODE_OPTIONS for resource classification

Replace NODE_OPTIONS parsing with GitHub runner label heuristics.
This is more accurate and works for all workloads (not just Node).

Changes:
- Add RUNNER_SPECS mapping for known GitHub runner sizes
- Add parseRunnerSpecs() to look up full {cpu, memory} from labels
- Remove parseNodeMaxOldSpaceMb() and NODE_OPTIONS dependency
- Update tests to use runner specs
- Simplify smoke test fixture

Runner specs used:
- ubuntu-latest: 2 CPU, 7GB RAM
- ubuntu-latest-4-cores: 4 CPU, 16GB RAM
- ubuntu-latest-8-cores: 8 CPU, 32GB RAM
- ubuntu-latest-16-cores: 16 CPU, 64GB RAM

* feat(cli): warn when using unknown runner labels

When a workflow uses an unknown runner label (e.g., 'self-hosted' or custom
labels not in RUNNER_SPECS), the job is now marked as degraded with a
warning that explains we're assuming local host capacity.

This makes the behavior transparent - users know we're making an assumption
about the runner size rather than silently defaulting to faithful mode.

* Address PR review comments: remove smoke tests, resource warnings, and writeStdoutLine refactor

* Remove remote Docker daemon suggestion from degraded resource warnings

* chore(compat): add resource-mismatch smoke and document degraded mode

- Add smoke-resource-mismatch.yml fixture that uses ubuntu-latest-999-cores
  to deterministically trigger the degraded classification on any host.
- Extend the runs-on row in compatibility.json with a sentence describing
  the degraded warning and reference the smoke as proof.
- Regenerate compatibility.md.
- Add changeset.

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

* fix(classifier): only flag unknown labels for the ubuntu family

The "unknown runner label" reason fired for any label missing from
RUNNER_SPECS, including macos-* / windows-* / self-hosted — labels that
runs-on-compat already handles and that the resource classifier has no
useful opinion on. Restrict the warning to labels that look like a
hosted ubuntu variant (`ubuntu` or `ubuntu-*`), which is the family
RUNNER_SPECS actually covers; this still catches drift when GitHub
adds a new ubuntu label without us noticing.

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

* test(smoke): de-flake bun smoke by pre-fetching the binary

The smoke depended on oven-sh/setup-bun@v2 pulling bun-linux-aarch64.zip
from github.com/oven-sh/bun/releases inside the nested runner. The
upstream action has no per-request timeout or retry, so a stalled CDN
socket presented as a hung step with one log line ("Downloading a new
version of Bun: …") and nothing else — pure flake, no signal about
agent-ci.

Pre-fetch the binary on the outer host with curl --retry, ship it into
the inner workspace as a fixture, and replace the setup-bun call in the
inner workflow with two narrower steps that exercise the actual #212
failure modes: an actions/cache@v4 probe against a randomized `bun-…`
key (must miss — guards against the virtualCachePatterns substring
fake-hit) and a manual `mkdir -p $HOME/.bun/bin` install (exercises the
bind-mount permissions path).

Local run: 16s, deterministic.

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

---------

Co-authored-by: Peter Pistorius <peter.pistorius@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:10:03 +02:00
Peter Pistorius 76b46f9929 Revert smolvm backend (#287) (#319)
* Revert "feat(runner): smolvm backend foundation (WIP, blocked on smolvm#177) (#287)"

This reverts commit 813036b543.

* chore: add changeset for smolvm backend revert

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-24 20:30:18 +02:00
Peter Pistorius 813036b543 feat(runner): smolvm backend foundation (WIP, blocked on smolvm#177) (#287)
* feat(runner): add opt-in smolvm backend foundation

WIP — blocked on smol-machines/smolvm#177 (TSI idle keep-alive drop).

Pure-shell + checkout workflows run end-to-end; anything using
actions/setup-* deadlocks once libkrun kills the idle listener
connection. Foundation (CLI wrapper, host-capability probe, image
mapping, pack cache, dispatch flag, workspace + shim mounts) is in
place and useful regardless of the networking resolution.

Refs #284.

* chore(runner): bump smolvm CLI-surface marker to 0.5.20

Verified end-to-end against smolvm 0.5.20: the previously-deadlocking
actions/setup-node@v4 smoke now passes in ~3 min, confirming the TSI
idle keep-alive fix (smolvm PR #184 / libkrun abd07c8) lands the
closure for smol-machines/smolvm#177.

The three 0.5.19-observed workaround comments in smolvm-job.ts are
left intact — SQLite single-writer, repeated-pull DNS drop, and
virtiofs small-file hang were not mentioned in the 0.5.20 release
notes and were not re-tested here.

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-23 13:20:23 +02:00
Peter Pistorius f5f7dbd724 fix(workflow): honor step-level if: conditions (#313)
parseWorkflowSteps never extracted `if:` from the workflow, so the DTU
server fell back to `condition: "success()"` for every step. Custom
gates — `contains(...)`, `always()`, even literal `${{ false }}` — were
silently ignored and every step ran regardless. A new `parseStepIf`
helper strips the optional `${{ }}` wrapper and threads the naked
expression through to the runner's EvaluateStepIf, so step gates now
behave as they do on real GitHub Actions. Adds unit coverage for the
helper and a smoke workflow that fails loudly if any gate leaks.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:19:59 +02:00
Peter Pistorius 044de23532 fix(runner): forward --env/--label from container.options into createContainer (#303)
The YAML parser extracted `jobs.<id>.container.options` into job.container
but the docker createContainer call never consumed it, so `options: --env
FOO=bar` silently produced a container without FOO.

parseContainerOptions now pulls --env/-e and --label/-l out of the options
string and merges them into the container's Env array and Labels map. All
other Docker flags in options: are still ignored by design — --privileged,
--user, --network, --cap-add, --workdir tend to clash with agent-ci's own
container orchestration, and silently dropping them is safer than a partial
honoring that breaks the runner's invariants. Documented in the
compatibility.json note for jobs.<id>.container.

Also updated two nearby notes while running through #296:

  * actions/cache — documents the lack of ref-based isolation (branches
    share cache keys locally, unlike GitHub's per-ref scoping)
  * GITHUB_TOKEN — documents that OIDC id-token issuance is not wired up,
    so permissions: id-token: write → federated cloud auth does not work

Refs #296.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:38:36 +02:00
Peter Pistorius ab410c79c9 fix(workflow): toJSON pretty-print + empty-string numeric coercion (#302)
Two small expression-engine fixes surfaced by extending smoke-expressions
with assertions for #296's "questionable claim" rows:

1. toJSON now emits 2-space-indented JSON (was compact), so hashFiles
   keys that hash a toJSON(x) match GitHub. Parses rawValue first so
   toJSON(fromJSON(x)) round-trips cleanly.

2. compareValues() no longer bails out of the numeric path when either
   side is empty. `''`, `null`, and numeric strings all coerce to
   numbers per the spec; non-numeric strings become NaN (string compare
   path, always false).

Five new assertions (contains-on-array, toJSON pretty-print, coercion,
hashFiles multi-pattern, hashFiles negation) all pass. hashFiles
multi-pattern and negation were never broken — cleared off #296 with
proof rather than a fix.

Refs #296.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:38:12 +02:00
Peter Pistorius f44620bf35 fix(workflow): hashFiles + steps.*.outputs.* — two small correctness fixes (#300)
* fix(workflow): hashFiles descends into dotted directories

findFiles was skipping any directory starting with `.`, so a pattern
like hashFiles('.github/workflows/*.yml') never matched a file and
returned the 40-zero placeholder. Skip only node_modules now.

The smoke assertion is tightened back to 64 chars (SHA-256) since the
real digest now appears.

Closes #294.

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

* fix(workflow): resolve steps.*.outputs.* to empty at parse time

The parser was returning a sentinel so that `${{ steps.X.outputs.Y }}`
expressions would be preserved verbatim for the runner to evaluate at
runtime. The runner does not re-evaluate expressions inside run-script
bodies, so the literal `${{ }}` reached bash as "bad substitution".

Per the long-standing compatibility contract, these expressions now
resolve to an empty string at parse time. Cross-job values still work
through needs.*.outputs.*.

Closes #295.

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 10:37:41 +02:00
Peter Pistorius 78e3e01266 fix(workflow): honor shell: for non-bash via parse-time heredoc wrap (#299)
The runner executes every run: step with bash regardless of inputs.shell,
so setting shell: sh / python / pwsh had no effect. Wrap the expanded
script at parse time — `<interpreter> <flags> <<'EOF' ... EOF` — so the
requested shell actually runs the user's code. Workflow, job, and step
scopes compose via the existing resolveStepRunDefault helper.

Adds smoke-shell-defaults.yml (removed in an earlier bug-filing cut)
covering all three scopes with a BASH_VERSION assertion.

Closes #293.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:36:30 +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 e727f313f3 ci: add manual-only macOS smoke workflow
Single job on macos-14, workflow_dispatch-only. Confirms the
GitHub-hosted macOS runner is healthy: OS info, toolchain sanity.

CI-only change — no changeset needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:05:53 +02:00
Peter Pistorius e320288f73 fix(runner): unique sibling container names when nested (#282)
When two `agent-ci-dev` runs execute in parallel, each nested CLI
process starts in its own isolated filesystem and allocates the same
sibling container name (`agent-ci-1`). The pre-spawn
`docker rm -f <name>` then kills the other nested run's sibling.

Include the outer container's hostname in the name prefix when
`/.dockerenv` is present so sibling names stay unique across
concurrent nested runs.

Also update smoke-docker-buildx.yml to look for nested-run logs under
both `/tmp/agent-ci` (GitHub-hosted runner) and
`$GITHUB_WORKSPACE/packages/cli/.agent-ci` (nested under
agent-ci-dev), since the nested working dir differs in each case.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:37:29 +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 b6b9310bcc Fix docker/setup-buildx-action permission denied on native Linux Docker (#257) (#267)
* test: smoke reproducer for #257 docker/setup-buildx-action

* chore: retrigger CI

* ci: switch smoke to pull_request so it triggers from the PR branch

* ci(smoke): dump agent-ci step logs + upload as artifact on failure

* fix: use MAYBE_SUDO to chmod bind-mounted docker socket (#257)

On native Linux Docker the socket is owned root:docker 0660. The runner
user isn't in the host's docker group, so the existing plain 'chmod 666
/var/run/docker.sock' silently failed — leaving every subsequent Docker
client call (docker/setup-buildx-action@v4, docker login, compose, buildx)
to fail with 'permission denied while trying to connect to the docker
API'. Escalating via MAYBE_SUDO matches the surrounding entrypoint ops.

* ci(smoke): assert against Verify-buildx.log, not top-level agent-ci stdout

---------

Co-authored-by: Peter Pistorius <peter@redwoodjs.com>
2026-04-17 21:29:12 +02:00
Peter Pistorius a6f52d55af fix(smoke-vars-preflight): make generated test.yml retain literal vars refs (#270)
Two bugs in the smoke test, both making it fail every run since #260:

1. The header comment contained `${{ vars.FOO }}` inside backticks.
   extractVarRefs() is a text-level regex scan and doesn't understand
   YAML comments, so the outer preflight falsely required `--var FOO`
   before the workflow would even start. Rephrase the comment to just
   `vars.FOO`.

2. The heredoc that writes the inner test.yml used
   `env: API_URL: ${{ vars.API_URL }}`. GitHub Actions' template engine
   processes `${{ ... }}` in every `run:` script regardless of shell
   heredoc quoting (`<< 'WF'` doesn't protect against it), so the
   expression was being substituted at outer-run time — stripping the
   vars.X references the inner agent-ci needs to exercise preflight.
   Result: every Case 1 (no --var flags) run saw test.yml with no
   refs, preflight passed, the workflow ran with empty env, and the
   assertion against 'https://api.example.com' failed.

Fix: build the literal '${{' at shell runtime from non-triggering parts
(D='$' + "{{") so the outer template engine never sees the pattern in
source. Switch heredoc to unquoted (<<WF) so the shell variable expands,
and escape \/\ to keep them literal for the inner
job's shell.

Validated via `pnpm agent-ci-dev run --workflow smoke-vars-preflight.yml
--var API_URL=https://api.example.com --var DEPLOY_ENV=production`:
all three cases now pass (14s).

No changeset: workflow-only change.
2026-04-17 20:44:07 +02:00
Peter Pistorius 3b16523aea fix: handle 404 on remote workflow fetch as private-repo auth failure (#261)
* kindling: start task [skip ci]

* harness: save uncommitted work from Analyst (cycle 1) [skip ci]

* Fix misleading 401/403 error hint when GitHub token is already provided

When fetching a remote workflow from a private repo with a token supplied,
a 401 or 403 from GitHub now says the token may be invalid, expired, or
lack the required 'repo' scope — instead of repeating instructions to use
--github-token that the user already followed.

Added two tests covering the with-token 401 and 403 paths, which were
previously uncovered.

Fixes #255

* harness: save uncommitted work from Developer (cycle 2) [skip ci]

* harness: save uncommitted work from Reviewer (cycle 3) [skip ci]

* harness: pre-DONE sweep [skip ci]

* kindling: ready for CI

* harness: save uncommitted work from Analyst (cycle 1) [skip ci]

* harness: save uncommitted work from Developer (cycle 2) [skip ci]

* harness: save uncommitted work from Reviewer (cycle 3) [skip ci]

* harness: save uncommitted work from Developer (cycle 4) [skip ci]

* harness: save uncommitted work from Reviewer (cycle 5) [skip ci]

* harness: pre-DONE sweep [skip ci]

* fix: handle 404 on remote workflow fetch as private-repo auth failure

GitHub's Contents API returns HTTP 404 (not 401/403) when a caller lacks
access to a private repository — this is intentional to avoid leaking repo
existence. The previous error hint only covered 401/403, so users hitting
the most common private-repo failure modes (missing token, classic PAT
without 'repo' scope, fine-grained PAT without 'contents: read', unauthorized
SSO PAT) saw a bare "HTTP 404" with no guidance.

Unify hint generation into buildAuthHint() covering 401/403/404 × with/
without token. Hints now include explicit gh auth login instructions and
spell out the token-insufficient scenarios.

Verified end-to-end against a private repro repo (peterp/agent-ci-private):
no-token → 404 path now emits full auth guidance, invalid-token → 401 path
emits token-insufficient hint, valid-token → resolves.

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

* test: smoke test for private-repo remote workflow fetch (#261)

Adds .github/workflows/smoke-remote-private-workflow.yml — the end-to-end
verification workflow used to reproduce and prove the #261 fix, now preserved
as a regression-guard smoke test.

It references a reusable workflow in a private repo
(peterp/agent-ci-private/.github/workflows/lint.yml@main). Under agent-ci this
exercises prefetchRemoteWorkflows against the GitHub Contents API with the
supplied token, covering the success path (HTTP 200 from a private repo when
a valid token is attached) that the unit tests in
packages/cli/src/workflow/remote-workflow-fetch.test.ts don't cover.

Verified locally with 'pnpm agent-ci-dev run --workflow … --github-token':

    Status:    ✓ 2 passed (2 total)
    Duration:  9s

Without a token, the same smoke surfaces the improved #261 hint chain verbatim
(HTTP 404 → 'The repository or ref was not found …' → auth instructions).

---------

Co-authored-by: kindling <kindling@harness>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:08:26 +02:00
Peter Pistorius 2bb4e572e3 Add --var flag so workflow vars work locally (#260)
* kindling: start task [skip ci]

* Add behavioral specs and tests for vars context support (SPEC-V-001 through SPEC-V-015)

Tests cover loading vars from dotenv file, env-var allowlist fallback, file-over-env
precedence, extracting ${{ vars.X }} refs from workflow YAML (whole-file and job-scoped),
validating that all referenced vars are supplied (error names missing keys and config file
path), and that workflows with no vars refs run without any config present.  Tests mirror
the existing secrets test conventions and will pass once the implementation (Track A) is
in place.

* harness: save uncommitted work from Developer+QA (cycle 1) [skip ci]

* harness: save uncommitted work from Reviewer (cycle 2) [skip ci]

* harness: save uncommitted work from Developer (cycle 3) [skip ci]

* harness: save uncommitted work from Reviewer (cycle 4) [skip ci]

* Add vars context loading, extraction, validation, and expression substitution

- loadMachineVars in config.ts reads .env.agent-ci.vars with the same
  KEY=VALUE parsing and envFallbackKeys allowlist as loadMachineSecrets
- extractVarRefs in workflow-parser.ts scans workflow YAML for
  ${{ vars.NAME }} references using the same pattern as extractSecretRefs
- validateVars throws a descriptive error listing missing var names and
  the expected file path, mirroring validateSecrets
- resolveExprAtom gains a vars? 7th param and a vars. prefix case after
  the secrets. case; all 11 internal evaluateExprValue calls for
  fromJSON/toJSON/format/contains/startsWith/endsWith/join updated
- evaluateExprValue gains vars? 7th param threaded through all 6
  recursive calls (||, &&, !, comparisons, parens) and the
  resolveExprAtom leaf at line 606
- expandExpressions and parseWorkflowSteps each gain vars? as optional
  7th param; all internal expandExpressions call sites updated

* Wire vars context into all three CLI call sites

- Import loadMachineVars, extractVarRefs, validateVars from their
  respective modules
- Single-job path: extract refs, load vars, validate, thread vars
  through expandExpressions (inputsContext expansion) and
  parseWorkflowSteps
- buildJob: extract refs, load vars, validate (no parseWorkflowSteps
  call here, consistent with how secrets are handled)
- runJob: extract refs, load vars, validate, thread vars through
  resolveInputsForJob and parseWorkflowSteps
- resolveInputsForJob: accepts vars? as 4th optional param and threads
  it through its expandExpressions call

* Add Vars section to README and minor-bump changeset

README documents .env.agent-ci.vars file and shell env fallback,
explaining that vars are non-sensitive and can be committed.

Changeset bumps both @redwoodjs/agent-ci and dtu-github-actions minor
as they are in the same fixed versioning group.

* Fix loadMachineVars test helper to write .env.agent-ci.vars

The QA-authored loadMachineVars tests used .env.agent-ci as the
fixture file name, but loadMachineVars reads from .env.agent-ci.vars
per the approved RFC. Updated the writeEnvFile helper and test
description in the loadMachineVars block to match the correct file.

* harness: save uncommitted work from Developer (cycle 5) [skip ci]

* Replace file+env vars sourcing with --var CLI flag

Address PR feedback: workflow vars (${{ vars.FOO }}) are now supplied
exclusively via the repeatable --var KEY=VALUE CLI flag. Removes the
.env.agent-ci.vars file and the shell-env fallback so workflow vars
don't overload shell env vars. Missing vars fail fast with an error
listing the --var flags needed.

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

* Pre-flight missing vars across all workflows before setup

Scan every workflow for ${{ vars.* }} refs at the start of the run
and fail with one combined message listing every missing var across
every file. Happens before Docker prefetch and workspace setup, so
users find out immediately instead of waiting through bootstrap.

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

* Add smoke test for --var pre-flight

Covers three cases against a temp workflow referencing vars.API_URL
and vars.DEPLOY_ENV:

1. No --var flags → exit 1, error lists both vars with --var syntax
2. Partial fill → error lists only the still-missing var
3. All vars supplied → run proceeds and the job succeeds

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

---------

Co-authored-by: kindling <kindling@harness>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:50:50 +02:00
Peter Pistorius e482875a95 fix: setup-node "Bad credentials" + stale-toolcache hint (#252)
* fix: setup-node "Bad credentials" + stale-toolcache hint (#171, #249)

Route setup-node's versions-manifest fetch through the DTU so it no
longer 401s against real api.github.com, and surface an actionable
hint when a stale /opt/hostedtoolcache bind mount blocks tar extract.

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

* test: unit + smoke coverage for setup-node manifest mock

Adds unit tests for rewriteSetupNodeTarball and for the cached tree/blob
route paths, plus a nested-agent-ci smoke workflow that fails if
setup-node logs "Bad credentials" (the symptom of the manifest mock
breaking and falling back to nodejs.org).

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:46:11 +02:00
Peter Pistorius 25c1c5decd fix: Cypress install fails with EACCES on /home/runner/.cache (#238)
* test: add smoke test reproducing Cypress cache permission failure (#234)

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

* fix: make /home/runner/.cache world-writable so Cypress can install (#234)

The Playwright bind mount causes Docker to create /home/runner/.cache as
root, so Cypress postinstall fails with EACCES when it tries to mkdir
/home/runner/.cache/Cypress. chmod 1777 in the entrypoint fixes it.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:21:47 +02:00
Peter Pistorius 7a06aec813 fix: setup-bun cache collision (#219)
* test: smoke repro for setup-bun cache collision (#212)

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

* fix: setup-bun fails in bun projects (#212)

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:21:49 +02:00
Peter Pistorius 66ac2a4b6b test: smoke repro for missing cc in runner image (#207)
* test: add smoke repro for missing cc in runner image

Minimal smoke workflow that calls `command -v cc` and compiles a
trivial C program. Reproduces a cargo/Rust workflow failure where
`linker 'cc' not found` breaks builds under agent-ci-dev because
ghcr.io/actions/actions-runner:latest does not ship build-essential.

CI-only change (no packages/ code modified) — no changeset.

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

* feat: pluggable runner image via .github/agent-ci.Dockerfile (#208)

agent-ci now discovers a user-provided Dockerfile at
.github/agent-ci.Dockerfile (or .github/agent-ci/Dockerfile for builds
with a COPY context), hashes its contents, builds it locally via
`docker build`, and uses the resulting agent-ci-runner:<hash> tag as
the default runner image. Edits trigger automatic rebuilds; identical
contents reuse the cached image.

This closes the gap where the minimal ghcr.io/actions/actions-runner
image lacks build-essential, python3, and other toolchains that
GitHub's hosted ubuntu-latest VM ships preinstalled. Workflows green
on GitHub but failing locally with `linker 'cc' not found` can now
opt into a richer image by dropping a 5-line Dockerfile into .github/.

Resolution order (highest wins):
  1. per-job `container:` (unchanged)
  2. AGENT_CI_RUNNER_IMAGE env var
  3. .github/agent-ci/Dockerfile (directory form, supports COPY)
  4. .github/agent-ci.Dockerfile (simple form, empty context)
  5. ghcr.io/actions/actions-runner:latest (default)

Also adds an error-hint heuristic: when a step fails with "command not
found" for cc/gcc/make/python3/pkg-config and the user is still on the
default image, the failure summary includes a paste-ready Dockerfile
pointing at the fix. Docs at packages/cli/runner-image.md.

Dogfoods the feature by shipping .github/agent-ci.Dockerfile for this
repo, which makes smoke-cc-compiler.yml (the issue #208 reproduction)
pass end-to-end.

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

* feat: announce first-run runner image build

Print a one-time stderr notice before `docker build` fires so users
understand why the first run pauses for ~60s. The message lives in
stderr so log-update can't clobber it and stays in scrollback after.

Docs updated to match.

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

* docs: clarify that container: runs verbatim, no Dockerfile layering

Spell out explicitly that a job-level container: directive bypasses
.github/agent-ci.Dockerfile entirely — agent-ci does not layer the
Dockerfile on top. This preserves the "same YAML runs locally and on
GitHub" parity promise.

Also document the two recommended ways to combine a custom base
image with agent-ci's extras.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 20:12:21 +02:00
Peter Pistorius 02741dcf0a fix: mount warm node_modules directly at workspace path (#190)
The previous approach symlinked workspace/node_modules → /tmp/node_modules,
which broke esbuild path resolution in pnpm monorepos. Tools like rwsdk's
directive scanner resolved paths through the symlink incorrectly, producing
paths like apps/web/tmp/node_modules/.pnpm/... instead of /tmp/node_modules/.pnpm/...

Mount the warm cache as a Docker bind mount directly at the workspace
node_modules path. This makes the filesystem layout identical to production
GitHub Actions. The original EBUSY concern is already mitigated by the git
shim (blocks git clean) and checkout patching (clean: false).

Adds a smoke test exercising esbuild resolution through pnpm workspace
symlinks to prevent regression.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:13:16 +02:00
Peter Pistorius 912ed83465 fix: preserve git-tracked symlinks in workspace snapshot (#179)
* fix: mount warm-modules at /tmp/node_modules for pnpm TypeScript compat

TypeScript resolves @types/* by walking up from realpath(). With pnpm's
.pnpm virtual store symlinked to /tmp/warm-modules, the upward walk never
finds a node_modules directory. Mounting at /tmp/node_modules instead means
TypeScript's walk from /tmp/node_modules/.pnpm/... naturally finds
/tmp/node_modules/@types/.

Closes #176

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

* fix: preserve git-tracked symlinks in workspace snapshot

copyWorkspace was using cp/fs.cpSync which dereference symlinks, causing
git mode 120000 entries to be dropped from the workspace commit. Now we
check lstat() before copying and recreate symlinks via symlinkSync.

Closes #178

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

* test: add pnpm symlink smoke test reproducing #176 and #178

Adds a committed fixture at .github/fixtures/pnpm-symlinks/ with a
git-tracked symlink (mode 120000) and a TSX file that requires
@types/react resolution through pnpm's .pnpm virtual store.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 10:30:28 +02:00
Peter Pistorius 789e403c74 feat: support reusable workflow inputs/outputs passing (#173)
Wire `with:` inputs from caller workflows to called workflows via
`inputs.*` context, resolve `on.workflow_call.inputs` defaults, and
propagate `on.workflow_call.outputs` back so downstream jobs can
access `needs.<callerJobId>.outputs.*`. Also fixes intra-workflow
`needs.*` resolution for composite job IDs.

Adds smoke test, unit tests, /validate command, and CLAUDE.md agent rules.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:08:59 +02:00
Peter Pistorius cf31ce145a feat: support nested reusable workflows up to 4 levels deep (#172)
Replace the nesting guard in expandReusableJobs with recursive expansion,
cycle detection, and a depth limit of 4 matching GitHub Actions' max.

Closes #167

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:13:29 +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 1e9d7caa9d feat: support pull_request_target in workflow relevance check (#124)
* feat: support pull_request_target in workflow relevance check

Treat pull_request_target identically to pull_request in isWorkflowRelevant(),
applying the same branch and paths filter logic. This fixes agent-ci --all
skipping workflows that use pull_request_target.

Closes #106

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

* fix: Docker container name collision when running multiple workflows via --all

Pre-allocate unique base run numbers per workflow before launching them
in parallel, so job suffixes (-j1, -j2, -j3) never produce duplicate
container names across concurrent workflows.

Closes #125

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

* fix: resolveDtuHost test and smoke-tests build

Clear AGENT_CI_DTU_HOST in resolveDtuHost tests so the bridge gateway
test exercises the correct code path when running inside agent-ci's
Docker containers (which inject AGENT_CI_DTU_HOST).

Scope binary-smoke build to @redwoodjs/agent-ci... to avoid building
apps/website which fails due to warm-modules symlink resolution.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 10:16:01 +02:00
Peter Pistorius 2a4bd27a5c feat(website): add Copy Agent Instructions button (#111)
* feat(website): add Copy Agent Instructions button to hero and quick start sections

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

* chore: remove retry-proof failure test from CI

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 06:19:13 +02:00
Peter Pistorius 8ddffdd1db ci: run checks on PR open for repo members
Add 'opened' to pull_request_target types so checks fire immediately
when a PR is created by an owner/member/collaborator. External
contributors still require the 'safe-to-run' label (handled by the
existing if: condition on each job).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:19:22 +11:00
Peter Pistorius d273b76c64 fix: show full per-step log in failure summary
Replace the 20-line tail (with "Last output:" label and indentation)
with the full content of the per-step log file already captured at
failedStepLogPath. Falls back to lastOutputLines when no log file
exists (e.g. container crash).

Adds reporter.test.ts to prove the behaviour, and an end-to-end check
in tests.yml that runs the retry-proof workflow and asserts the step
output appears in stdout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:15:11 +11:00
Peter Pistorius ad7482fcac fix: auto-run CI for repo members without requiring safe-to-run label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 03:29:56 +13:00
Peter Pistorius f80dd3253d fix: remove push trigger from CI workflows to eliminate duplicate checks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 03:23:01 +13:00
Peter Pistorius 1916d5d1ef ci: add binary smoke test to smoke-tests workflow
Build and execute the released binary as part of smoke tests: verify shebang is correct, run built binary directly via node, and run built binary via npx (simulates npx install). Catches shebang issues, broken imports, and bin-field misconfigurations before publishing.
2026-03-18 08:11:25 +13:00
Peter Pistorius ed4a92ebd5 Fix pull-requests. 2026-03-17 21:30:04 +13:00
Peter Pistorius e8e2e823ea chore: update README, add engines field, scope release workflow paths 2026-03-16 21:26:58 +13:00
Peter Pistorius 1ad079b338 chore: remove derive package and clean up references 2026-03-16 10:41:20 +13:00
Peter Pistorius c088fb5063 fix: use Node.js 24 for npm OIDC trusted publishing support
npm OIDC requires npm CLI >= 11.5.1, which is bundled with Node 24.
Node 22's npm doesn't support the OIDC handshake, causing 404 errors.
2026-03-16 07:48:21 +13:00
Peter Pistorius bad063f192 fix: add NPM_CONFIG_PROVENANCE for OIDC npm publishing 2026-03-16 07:26:44 +13:00
Peter Pistorius 9c1a67bf51 fix: restore derive filter in release test step 2026-03-16 07:23:48 +13:00
Peter Pistorius f009084c59 fix: remove derive filter from release test step 2026-03-16 07:21:27 +13:00
Peter Pistorius 399a1972ba fix: add id-token permission for npm OIDC provenance publishing 2026-03-16 07:09:14 +13:00
Peter Pistorius d2023fc9f3 fix: prevent recursive double test execution in pnpm -r test
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.
2026-03-16 07:00:42 +13:00
Peter Pistorius b2c061ba1f fix: capture step outputs via DTU and fix _runner_file_commands path
- 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.
2026-03-15 19:22:17 +13:00
Peter Pistorius 24125d64f6 v0.2.0. 2026-03-13 16:11:03 +02:00
Peter Pistorius d20287e73b v0.1.1 2026-03-13 16:03:38 +02:00