Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
62c5b90
check-bench reads absent_text; the skill says what exit 4 means
addisdev Oct 16, 2026
267f37f
docs: catch up with 0.8.0 and the explore work
addisdev Oct 16, 2026
e73ca29
Merge pull request #88 from addisdev/docs/0.8-catchup
addisdev Oct 16, 2026
9543658
docs: hand-placed figures replace the last mermaid diagrams
addisdev Oct 16, 2026
ba14cef
CI smokes for launchd, the Windows task, arm64 and the desktop app; r…
addisdev Oct 16, 2026
6af87ae
Fix the Windows scheduled task, and the desktop app's exit paths
addisdev Oct 16, 2026
73355ad
Close 0.8.0's loose ends: legacy auth checks, ci-enqueue on /api/v1, …
addisdev Oct 16, 2026
a33a283
Docs and changelog for what the new smokes proved
addisdev Oct 16, 2026
a5b8b2f
Explore nights adapt: short nights, skips for missing builds, warm af…
addisdev Oct 16, 2026
90994c8
Merge pull request #90 from addisdev/docs/figures-no-mermaid
addisdev Oct 16, 2026
355fadb
Merge remote-tracking branch 'origin/develop' into feat/adaptive-nights
addisdev Oct 16, 2026
1f5f419
Merge remote-tracking branch 'origin/develop' into fix/080-loose-ends
addisdev Oct 16, 2026
d955150
Honest green: skipped is a job status, every failure says why, regist…
addisdev Oct 16, 2026
04bca7d
explore-night tests: build fake manifest paths with path.join; skip t…
addisdev Oct 16, 2026
82d0878
Merge remote-tracking branch 'origin/develop' into ci/never-run-list
addisdev Oct 16, 2026
3da6e9d
Merge pull request #92 from addisdev/fix/080-loose-ends
addisdev Oct 16, 2026
f9ff865
Merge remote-tracking branch 'origin/develop' into feat/adaptive-nights
addisdev Oct 16, 2026
e6fba6b
Merge remote-tracking branch 'origin/develop' into feat/honest-green
addisdev Oct 16, 2026
d35f1a1
Merge remote-tracking branch 'origin/develop' into ci/never-run-list
addisdev Oct 16, 2026
64f63a0
Merge pull request #93 from addisdev/feat/adaptive-nights
addisdev Oct 16, 2026
49a055e
Merge remote-tracking branch 'origin/develop' into feat/honest-green
addisdev Oct 16, 2026
1751d65
Merge remote-tracking branch 'origin/develop' into ci/never-run-list
addisdev Oct 16, 2026
3ab79c9
describe.test: one evalMatch import after merging the tvOS and Roku c…
addisdev Oct 16, 2026
0457d21
Merge pull request #94 from addisdev/feat/honest-green
addisdev Oct 16, 2026
aee60ed
Merge remote-tracking branch 'origin/develop' into ci/never-run-list
addisdev Oct 16, 2026
c1c176a
Merge pull request #89 from addisdev/ci/never-run-list
addisdev Oct 16, 2026
de4859a
WIP findings: fixed verdict, history, regressions store
addisdev Oct 16, 2026
4d3ae55
Merge remote-tracking branch 'origin/develop' into feat/findings-loop
addisdev Oct 16, 2026
91dd477
explore: load Holo4 at 45% free memory, not the shelf's 65%
addisdev Oct 16, 2026
60ce97c
changelog: explore warm threshold (#95)
addisdev Oct 16, 2026
822f812
Findings reach the code: /api/v1 findings, fleetr and MCP, fixed find…
addisdev Oct 16, 2026
063745f
Changelog, docs and the dashboard shortcut list for findings and regr…
addisdev Oct 16, 2026
e0ddbcf
Dashboard: read devices, jobs, templates and schedules through the v1…
addisdev Oct 16, 2026
498adce
CHANGELOG: the dashboard on the v1 client (#97)
addisdev Oct 16, 2026
a5eb74f
Merge pull request #95 from addisdev/chore/explore-warm-threshold
addisdev Oct 16, 2026
5112b61
Merge remote-tracking branch 'origin/develop' into feat/dash-v1-client
addisdev Oct 16, 2026
c6bc379
Merge pull request #97 from addisdev/feat/dash-v1-client
addisdev Oct 16, 2026
8d97234
Merge origin/develop (#95, #97); Findings page onto the v1 client; v1…
addisdev Oct 16, 2026
4a0f1b4
Merge pull request #96 from addisdev/feat/findings-loop
addisdev Oct 16, 2026
42d1493
Release 0.9.0
addisdev Oct 16, 2026
04265fe
Merge pull request #98 from addisdev/release/0.9.0
addisdev Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,42 @@ jobs:
- uses: actions/checkout@v4
- run: node scripts/version.mjs check

# Also unconditional, and for the same reason as `version`: the numbers it
# checks are written in README.md and docs/, so a docs-only pull request is
# exactly where a stale one arrives, and no component suite runs for that.
repo-scripts:
name: repo scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Unit tests for scripts/
run: node --test scripts/*.test.mjs
- name: Documented counts match the code
run: node scripts/check-counts.mjs

# A release pull request -- a `release/*` branch, or `develop` into `main` --
# must mention every pull request merged into develop since the last tag.
# Every other pull request skips this, and skipped counts as green below.
changelog:
name: changelog coverage
if: >-
github.event_name == 'pull_request' && (
startsWith(github.head_ref, 'release/') ||
(github.head_ref == 'develop' && github.base_ref == 'main'))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v4
with:
# The tags and the whole of develop's history since the last one.
fetch-depth: 0
- name: Every merged pull request is in the changelog
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/changelog-coverage.mjs --head "${{ github.event.pull_request.head.sha }}"

collector:
needs: changes
if: needs.changes.outputs.collector == 'true'
Expand Down Expand Up @@ -142,7 +178,7 @@ jobs:

ci-ok:
name: ci-ok
needs: [changes, version, collector, runner-android, runner-ios, runner-machine, fleet, runner-roku, desktop]
needs: [changes, version, repo-scripts, changelog, collector, runner-android, runner-ios, runner-machine, fleet, runner-roku, desktop]
# Without this the gate inherits the default "only if every dependency
# succeeded", which makes a skipped suite skip the gate -- and a skipped
# required check never reports, which is the exact bug being fixed.
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,66 @@ jobs:
./desktop/stage-sidecar.sh
- working-directory: desktop/src-tauri
run: cargo check --all-targets

launch:
# The first time the app ever ran was this job. `cargo check` proves the
# Tauri calls exist; this proves the binary starts, spawns `fleet up` as its
# sidecar, stays up, and that its settings window loads and answers over
# IPC. Linux rather than macOS because that is where Tauri's WebDriver
# support exists (tauri-driver wraps WebKitWebDriver; there is no macOS
# driver), and because Xvfb gives it a display without a logged-in desktop.
# What it cannot prove is anything macOS-specific: the tray in the menu bar,
# the Local Network grant, signing. See desktop/README.md.
name: launch smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22.13"
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri
key: linux-launch
# Tauri 2's Linux prerequisites (webkit2gtk 4.1, appindicator for the
# tray), plus what the smoke itself uses: Xvfb for a display, dbus for a
# session bus, WebKitWebDriver for tauri-driver, ImageMagick to capture
# the screen.
- name: Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev \
libssl-dev libayatana-appindicator3-dev librsvg2-dev \
xvfb x11-utils dbus-x11 webkit2gtk-driver imagemagick jq
- name: Stage the fleet sidecar
run: |
npm --prefix collector ci
npm --prefix fleet ci
./desktop/stage-sidecar.sh
- name: Build the app
working-directory: desktop/src-tauri
# custom-protocol is what `cargo tauri build` turns on: the frontend is
# embedded and served from the binary, as it is in a release, rather
# than expected from a dev server.
run: cargo build --features custom-protocol
- name: tauri-driver
run: cargo install tauri-driver --locked
- name: Launch it
run: ./desktop/ci/launch-smoke.sh
env:
OUT: ${{ runner.temp }}/desktop-smoke
- if: always()
name: What it printed
run: |
for f in "$RUNNER_TEMP"/desktop-smoke/*.log "$RUNNER_TEMP"/desktop-smoke/fleet-home/logs/*.log; do
[ -f "$f" ] || continue
echo "== $f"; tail -80 "$f"
done
- if: always()
uses: actions/upload-artifact@v4
with:
name: desktop-launch-smoke
path: ${{ runner.temp }}/desktop-smoke
if-no-files-found: warn
10 changes: 10 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ jobs:

- run: pip install -r requirements-docs.txt

# Diagrams are hand-placed figures in docs/figures/, rendered to PNG
# (docs/brand.md says why). mkdocs.yml no longer has a mermaid fence, so
# one would build as a plain code block rather than fail; this makes it fail.
- name: No mermaid
run: |
if grep -rn --include='*.md' '^ *```mermaid' docs/; then
echo "::error::mermaid diagram under docs/: draw it as a figure in docs/figures/ instead (docs/brand.md)"
exit 1
fi

# The cookbook includes collector/examples/jobs/*.json verbatim via the
# snippets extension, so a missing or renamed example fails the build
# here as well as failing the collector's own suite.
Expand Down
162 changes: 159 additions & 3 deletions .github/workflows/fleet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,21 @@ jobs:
# run it the way the docs say, and drive it through the CLI from outside:
# a container whose port is published but whose collector never answers is
# the failure a `docker build` alone would call green.
name: container smoke
runs-on: ubuntu-latest
#
# The arm64 row builds natively on GitHub's arm64 runner rather than under
# QEMU on the amd64 one. The release publishes linux/arm64 from a QEMU
# build, and an image that only ever ran emulated is the one a Raspberry Pi
# owner would be first to start for real.
name: container smoke${{ matrix.suffix }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
suffix: ""
- runner: ubuntu-24.04-arm
suffix: " (arm64)"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -99,13 +112,21 @@ jobs:
cache: npm
cache-dependency-path: fleet/package-lock.json
- working-directory: .
run: docker build -f fleet/Dockerfile -t fleet:ci .
run: |
docker build -f fleet/Dockerfile -t fleet:ci .
docker image inspect fleet:ci --format 'built for {{.Os}}/{{.Architecture}}'

- working-directory: .
run: docker run -d --name fleet -p 8788:8788 fleet:ci up --role brain,agent
- name: The collector answers, and the machine agent registers
shell: bash
env:
EXPECT_ARCH: ${{ matrix.runner == 'ubuntu-24.04-arm' && 'aarch64' || 'x86_64' }}
run: |
set -euo pipefail
arch=$(docker exec fleet uname -m)
echo "container is running on $arch"
[ "$arch" = "$EXPECT_ARCH" ] || { echo "::error::expected $EXPECT_ARCH, the container says $arch"; exit 1; }
for i in $(seq 1 60); do
curl -fsS http://127.0.0.1:8788/api/v1/health && break
sleep 2
Expand Down Expand Up @@ -165,3 +186,138 @@ jobs:
run: |
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
journalctl --user -u fleet.service --no-pager | tail -100 || true

launchd:
# The twin of the systemd smoke for macOS. `fleet service install` writes a
# user LaunchAgent and bootstraps it into the gui/<uid> domain; GitHub's
# macOS runners run their jobs inside a logged-in GUI session, so that
# domain exists here the way it does on a desk. The first step says so
# rather than assuming it, because a runner without one would make every
# later failure look like a fleet bug.
name: launchd smoke
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22.13"
cache: npm
cache-dependency-path: fleet/package-lock.json
- run: npm ci && npm --prefix ../collector ci && npm run build
- name: The runner has a GUI launchd domain
shell: bash
run: |
set -euo pipefail
echo "uid $(id -u), user $(id -un)"
launchctl print "gui/$(id -u)" | head -15
- name: Install as a LaunchAgent, it serves, and its agent registers
shell: bash
run: |
set -euo pipefail
mkdir -p "$HOME/.fleet"
cp -R dist/. "$HOME/.fleet/"
fleet() { node "$HOME/.fleet/bin/fleet.mjs" "$@"; }
plist="$HOME/Library/LaunchAgents/com.addisdev.fleet.plist"
fleet service install
plutil -lint "$plist"
launchctl print "gui/$(id -u)/com.addisdev.fleet" | head -20
for i in $(seq 1 60); do
curl -fsS http://127.0.0.1:8788/api/v1/health && break
sleep 2
done
curl -fsS http://127.0.0.1:8788/api/v1/health >/dev/null
n=0
for i in $(seq 1 30); do
n=$(fleet devices --url http://127.0.0.1:8788 --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).items.length))')
[ "$n" -gt 0 ] && break
sleep 2
done
[ "$n" -gt 0 ] || { echo "::error::the agent under launchd never registered"; exit 1; }
fleet devices --url http://127.0.0.1:8788
fleet service status
# Reinstalling is bootout-then-bootstrap, the race service.ts retries.
# Run it once here so the retry is exercised rather than trusted.
fleet service install
for i in $(seq 1 60); do
curl -fsS http://127.0.0.1:8788/api/v1/health >/dev/null 2>&1 && break
sleep 2
done
curl -fsS http://127.0.0.1:8788/api/v1/health >/dev/null
fleet service uninstall
sleep 5
if curl -fsS http://127.0.0.1:8788/api/v1/health 2>/dev/null; then
echo "::error::the collector is still answering after uninstall"
exit 1
fi
[ ! -e "$plist" ] || { echo "::error::$plist is still there after uninstall"; exit 1; }
if launchctl print "gui/$(id -u)/com.addisdev.fleet" >/dev/null 2>&1; then
echo "::error::launchd still has com.addisdev.fleet loaded after uninstall"
exit 1
fi
if fleet service status; then
echo "::error::fleet service status exited 0 after uninstall"
exit 1
fi
- if: failure()
shell: bash
run: |
tail -100 "$HOME/.fleet/logs/fleet.log" || true
for f in "$HOME"/.fleet/logs/*.log; do echo "== $f"; tail -40 "$f"; done || true

windows-task:
# `fleet service install` on Windows creates a scheduled task that runs
# `fleet up` at logon and starts it at once. Same lifecycle as the systemd
# and launchd smokes: install, it serves, its agent registers, status says
# so, uninstall, and nothing is left answering.
name: windows task smoke
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22.13"
cache: npm
cache-dependency-path: fleet/package-lock.json
- shell: bash
run: npm ci && npm --prefix ../collector ci && npm run build
- name: Install as a scheduled task, it serves, and its agent registers
shell: bash
run: |
set -euo pipefail
home="$(cygpath -u "$USERPROFILE")/.fleet"
mkdir -p "$home"
cp -R dist/. "$home/"
fleet() { node "$home/bin/fleet.mjs" "$@"; }
fleet service install
schtasks //Query //TN FleetRunner //FO LIST //V
for i in $(seq 1 60); do
curl -fsS http://127.0.0.1:8788/api/v1/health && break
sleep 2
done
curl -fsS http://127.0.0.1:8788/api/v1/health >/dev/null
n=0
for i in $(seq 1 30); do
n=$(fleet devices --url http://127.0.0.1:8788 --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).items.length))')
[ "$n" -gt 0 ] && break
sleep 2
done
[ "$n" -gt 0 ] || { echo "::error::the agent under the scheduled task never registered"; exit 1; }
fleet devices --url http://127.0.0.1:8788
fleet service status
fleet service uninstall
sleep 5
if curl -fsS http://127.0.0.1:8788/api/v1/health 2>/dev/null; then
echo "::error::the collector is still answering after uninstall"
exit 1
fi
if schtasks //Query //TN FleetRunner >/dev/null 2>&1; then
echo "::error::the FleetRunner task is still there after uninstall"
exit 1
fi
- if: failure()
shell: bash
run: |
home="$(cygpath -u "$USERPROFILE")/.fleet"
schtasks //Query //TN FleetRunner //FO LIST //V || true
for f in "$home"/logs/*.log; do echo "== $f"; tail -60 "$f"; done || true
tasklist //FI "IMAGENAME eq node.exe" || true
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ from an agent, see [`skills/fleet-runner/SKILL.md`](skills/fleet-runner/SKILL.md
| Path | What |
|---|---|
| `collector/` | The brain: Fastify + `node:sqlite`, the queue, the dashboard (`dash/`), the host executor |
| `collector/src/workloads/explore/` | Exploratory QA: a vision model drives an app from mission cards (`collector/examples/missions/`); see `docs/explore/internals.md` |
| `collector/src/api/v1/` | The control API. `routes.ts` is the single route table: it registers the routes, generates `openapi.json` and the typed client |
| `collector/src/client/` | The generated client (`api.gen.ts`) and its transport. The CLI and the MCP server use nothing else |
| `collector/src/client/` | The generated client (`api.gen.ts`) and its transport. The CLI and the MCP server use nothing else; the dashboard (`dash/src/client.ts`) uses it for everything `/api/v1` offers, and `scripts/check-dash-requests.ts` fails the suite if it calls a deprecated route |
| `fleet/` | The `fleet` / `fleetr` command: runs a brain or a runner, plus the work commands, `fleet mcp` and the remote gateway |
| `runner-machine/`, `runner-android/`, `runner-ios/`, `runner-roku/`, `desktop/` | Runners. They speak the runner protocol (`/devices/register`, `/devices/:id/next-job`, `/results`), not `/api/v1` |
| `desktop/` | A Tauri menu-bar app around `fleet`. A front door, not a runner; compiled in CI on macOS and launched in CI on Linux (`desktop/ci/launch-smoke.sh`), never run on a Mac |
| `runner-machine/`, `runner-android/`, `runner-ios/`, `runner-roku/`, `collector/runner-web/` | Runners. They speak the runner protocol (`/devices/register`, `/devices/:id/next-job`, `/results`), not `/api/v1` |
| `skills/fleet-runner/` | The agent skill. Its tables are generated |
| `bug-garden/` | An Android fixture app with twenty planted defects, for scoring exploratory QA |
| `docs/` | The mkdocs site. Built with `--strict`, so a broken link fails CI |

## Suites
Expand Down
Loading
Loading