Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ List commands that were actually run and mark anything blocked or not run.
```text
bash scripts/validate.sh
bash scripts/smoke-install.sh
bash scripts/e2e.sh
bash scripts/scan-secrets.sh
```

Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: e2e

on:
pull_request:
push:
branches: [main, master]

permissions:
contents: read

concurrency:
group: e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
e2e:
name: e2e (python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.12"]
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v5.6.0
with:
python-version: ${{ matrix.python-version }}
- uses: actions/setup-node@v7.0.0
with:
node-version: "22"
- run: python3 -m pip install pyyaml==6.0.3
- name: Run E2E suite (packaged CLI, fake host Agent, no API keys)
run: bash scripts/e2e.sh

e2e-gate:
# Single stable check name to mark as required in branch protection.
name: e2e-required
if: always()
needs: [e2e]
runs-on: ubuntu-latest
steps:
- name: Require every E2E job to pass
run: |
if [[ "${{ needs.e2e.result }}" != "success" ]]; then
echo "E2E result: ${{ needs.e2e.result }}" >&2
exit 1
fi
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ jobs:
bash scripts/smoke-install.sh
bash scripts/smoke-npm.sh
bash scripts/smoke-curl-install.sh
bash scripts/e2e.sh
bash scripts/scan-secrets.sh

- name: Log in to GHCR
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Run these commands before reporting a workflow change complete:
python3 scripts/build-classic-hosts.py --check
bash scripts/validate.sh
bash scripts/smoke-install.sh
bash scripts/e2e.sh
bash scripts/scan-secrets.sh
```

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Thanks for contributing to LoopForge.
3. Do not commit credentials, local permission configurations, or production data.
4. First indicate whether a change belongs to Classic, Portable, or shared behaviour contracts. Portable only modifies `skills/`; Classic `.cursor/.claude` files are not edited directly — run `build-classic-hosts.py --write`.
5. When modifying shared workflow contracts, describe the impact on both editions separately; do not copy runtime files between them.
6. Run `bash scripts/validate.sh` and `bash scripts/smoke-install.sh` before submitting.
6. Run `bash scripts/validate.sh`, `bash scripts/smoke-install.sh` and `bash scripts/e2e.sh` before submitting. The E2E suite drives the installed workflow with a fake host Agent and recorded stage outputs, so it needs no API key; see [tests/e2e/README.md](tests/e2e/README.md).

## Pull request requirements

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
3. 不提交任何凭据、本地权限配置或生产数据。
4. 先标明改动属于 Classic、Portable 或共享行为合同。Portable 只修改 `skills/`;Classic 的 `.cursor/.claude` 不直接修改,运行 `build-classic-hosts.py --write`。
5. 修改共享工作流契约时分别说明两个 edition 的影响;不要在二者之间复制运行文件。
6. 提交前运行 `bash scripts/validate.sh` 和 `bash scripts/smoke-install.sh`。
6. 提交前运行 `bash scripts/validate.sh`、`bash scripts/smoke-install.sh` 和 `bash scripts/e2e.sh`。E2E 用假宿主 Agent 和录制的阶段产物驱动已安装的工作流,不需要 API Key,说明见 [tests/e2e/README.md](tests/e2e/README.md)。

## Pull Request 要求

Expand Down
12 changes: 12 additions & 0 deletions scripts/e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# End-to-end tests: install the packaged CLI into throw-away projects and drive
# every host through install, the full Portable workflow (with a fake host
# Agent), resume, failure paths, Classic hooks and the local dashboard.
# Needs Python 3.8+, Node.js 20+ and npm; no network access or API keys.
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export PYTHONDONTWRITEBYTECODE=1

python3 -m unittest discover -s "$ROOT_DIR/tests/e2e" -t "$ROOT_DIR/tests/e2e" -p 'test_e2e_*.py' -v "$@"
echo "E2E tests passed."
20 changes: 20 additions & 0 deletions tests/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# E2E tests

`bash scripts/e2e.sh` runs the end-to-end suite. It needs Python 3.8+, Node.js 20+ and npm, and no network access, API key or real coding Agent.

What it does:

1. Builds the npm tarball once (`npm pack`), installs it into a temporary prefix, and uses that `loopforge` binary, which is the same path users take with `npx loopforge-cli`.
2. Installs every supported host and edition into throw-away git projects, then checks the entrypoint files, `status`, `doctor`, `update` and a clean `uninstall` (`test_e2e_install.py`).
3. Drives the installed Portable workflow end to end with [`fake_agent.py`](fake_agent.py), a deterministic stand-in for the host Agent. It follows the Skill's `resume`-driven protocol (`init`, `prepare --emit-prompt`, `assign`, `start`, `finish`, `approve`, `decide-knowledge`), but writes the stage outputs recorded in [`fixtures/scenario-medium.json`](fixtures/scenario-medium.json) instead of calling an LLM. The developer stage really changes [`fixtures/sample_project`](fixtures/sample_project/slugify.py), and the test stage really runs its tests. The suite covers every host, resume after a crash and after an interruption, the requirement confirmation gate, review rejection, unfilled artifacts and repeated failures (`test_e2e_portable_workflow.py`).
4. Runs the Classic CodeBuddy hooks from an installed project: auto-dispatch routing between roles, the fallbacks to the main Agent, and a recorded session flowing through the observability hooks into the local dashboard, which is served over HTTP (`test_e2e_classic_runtime.py`).

Useful options:

```bash
LOOPFORGE_E2E_SOURCE=1 bash scripts/e2e.sh # run the CLI from src/ and skip npm pack
bash scripts/e2e.sh -k resume # run only tests matching a pattern
python3 tests/e2e/fake_agent.py --help # drive a project by hand
```

When a test fails, the assertion message includes the fake Agent transcript (`.fake-agent/transcript.jsonl` in the project) that shows each action and the command that was rejected.
Loading
Loading