From 8cf81a1d761fa92c86dd8e5a2072520639b0bf01 Mon Sep 17 00:00:00 2001 From: pufit Date: Tue, 23 Jun 2026 16:15:34 -0400 Subject: [PATCH 1/3] Add CI workflow: backend tests + frontend build GitHub Actions running pytest (Python 3.13) and the Vite frontend build on push/PR to main. Declares a [test] extra in pyproject for pytest + pytest-asyncio. --- .github/workflows/ci.yml | 69 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 9 ++++++ 2 files changed, 78 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7c44438 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +# Cancel superseded runs on the same branch/PR (e.g. rapid pushes). +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + backend-tests: + name: Backend tests (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # pyproject says requires-python ">=3.12", but the pinned + # memu-py==1.4.0 dependency requires >=3.13, so 3.13 is the real + # floor (and what the dev environment runs). Add more versions here + # if that pin is ever relaxed. + python-version: ["3.13"] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Create virtual environment + run: uv venv --python ${{ matrix.python-version }} + + - name: Install project and test dependencies + run: uv pip install -e ".[test]" + + - name: Run test suite + run: .venv/bin/pytest tests/ -v + + frontend-build: + name: Frontend build (Vite) + runs-on: ubuntu-latest + defaults: + run: + working-directory: web + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build frontend (tsc -b && vite build) + run: npm run build diff --git a/pyproject.toml b/pyproject.toml index 332c7ea..9dc6432 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,15 @@ dependencies = [ "opentelemetry-instrumentation-anthropic>=0.40.0", ] +[project.optional-dependencies] +# Test/dev dependencies. Install with: uv pip install -e ".[test]" +# Upper bounds guard CI against surprise major-version breaks (notably the +# pytest-asyncio 1.x event_loop changes the suite's conftest relies on). +test = [ + "pytest>=8,<10", + "pytest-asyncio>=0.24,<2", +] + [project.scripts] nerve = "nerve.cli:main" From 5699118f4d7a8a90734f3f23db190597f05f9372 Mon Sep 17 00:00:00 2001 From: pufit Date: Tue, 23 Jun 2026 16:28:47 -0400 Subject: [PATCH 2/3] Track Codex rollout test fixtures (un-ignore from *.jsonl) The blanket *.jsonl gitignore rule excluded tests/fixtures/codex/rollouts/ fixtures, so the codex source tests passed locally but failed on a clean checkout (CI) with FileNotFoundError. Add a .gitignore negation so the fixtures are tracked. Fixtures are fully synthetic (placeholder UUIDs/paths). --- .gitignore | 2 ++ tests/fixtures/codex/rollouts/in_scope.jsonl | 13 +++++++++++++ tests/fixtures/codex/rollouts/out_of_scope.jsonl | 3 +++ tests/fixtures/codex/rollouts/partial_tail.jsonl | 4 ++++ 4 files changed, 22 insertions(+) create mode 100644 tests/fixtures/codex/rollouts/in_scope.jsonl create mode 100644 tests/fixtures/codex/rollouts/out_of_scope.jsonl create mode 100644 tests/fixtures/codex/rollouts/partial_tail.jsonl diff --git a/.gitignore b/.gitignore index cc1ec04..002a57c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,8 @@ nerve.pid # Sync data sync_data/ *.jsonl +# ...except checked-in test fixtures, which the suite needs on a clean checkout +!tests/fixtures/**/*.jsonl # Docker (generated by 'nerve init' — safe to commit if sharing) Dockerfile diff --git a/tests/fixtures/codex/rollouts/in_scope.jsonl b/tests/fixtures/codex/rollouts/in_scope.jsonl new file mode 100644 index 0000000..ce8743b --- /dev/null +++ b/tests/fixtures/codex/rollouts/in_scope.jsonl @@ -0,0 +1,13 @@ +{"timestamp":"2026-05-19T16:52:04.073Z","type":"session_meta","payload":{"id":"11111111-2222-3333-4444-555555555555","timestamp":"2026-05-19T16:52:02.403Z","cwd":"/tmp/nerve-test-ws","originator":"codex_exec","cli_version":"0.130.0","source":"exec","model_provider":"openai","base_instructions":{"text":"You are Codex, a fixture."}}} +{"timestamp":"2026-05-19T16:52:04.075Z","type":"event_msg","payload":{"type":"task_started","turn_id":"turn-1","started_at":1779209522,"model_context_window":258400}} +{"timestamp":"2026-05-19T16:52:04.076Z","type":"response_item","payload":{"type":"message","role":"developer","content":[{"type":"input_text","text":"system instructions to ignore"}]}} +{"timestamp":"2026-05-19T16:52:04.077Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"AGENTS.md auto-injection — should be dropped"}]}} +{"timestamp":"2026-05-19T16:52:04.080Z","type":"turn_context","payload":{"turn_id":"turn-1","cwd":"/tmp/nerve-test-ws","model":"gpt-5.5"}} +{"timestamp":"2026-05-19T16:52:04.081Z","type":"event_msg","payload":{"type":"user_message","message":"hello fixture","images":[],"local_images":[],"text_elements":[]}} +{"timestamp":"2026-05-19T16:52:04.090Z","type":"response_item","payload":{"type":"reasoning","encrypted_content":"ENCRYPTED-BLOB-PLACEHOLDER","summary":[]}} +{"timestamp":"2026-05-19T16:52:04.100Z","type":"response_item","payload":{"type":"function_call","name":"exec_command","arguments":"{\"cmd\":\"ls\"}","call_id":"call_exec_1"}} +{"timestamp":"2026-05-19T16:52:04.110Z","type":"response_item","payload":{"type":"function_call_output","call_id":"call_exec_1","output":"Chunk ID: abc123\nWall time: 0.1234 seconds\nProcess exited with code 0\nOutput:\nfile1\nfile2\n"}} +{"timestamp":"2026-05-19T16:52:04.120Z","type":"response_item","payload":{"type":"function_call","name":"task_list","namespace":"mcp__nerve__","arguments":"{\"limit\":3}","call_id":"call_mcp_1"}} +{"timestamp":"2026-05-19T16:52:04.130Z","type":"event_msg","payload":{"type":"mcp_tool_call_end","call_id":"call_mcp_1","invocation":{"server":"nerve","tool":"task_list","arguments":{"limit":3}},"duration":{"secs":0,"nanos":1000},"result":{"Ok":"task list output here"}}} +{"timestamp":"2026-05-19T16:52:04.140Z","type":"event_msg","payload":{"type":"agent_message","message":"final reply","phase":"final_answer"}} +{"timestamp":"2026-05-19T16:52:04.150Z","type":"event_msg","payload":{"type":"task_complete","turn_id":"turn-1","last_agent_message":"final reply","completed_at":1779209524,"duration_ms":1450}} diff --git a/tests/fixtures/codex/rollouts/out_of_scope.jsonl b/tests/fixtures/codex/rollouts/out_of_scope.jsonl new file mode 100644 index 0000000..95c84e1 --- /dev/null +++ b/tests/fixtures/codex/rollouts/out_of_scope.jsonl @@ -0,0 +1,3 @@ +{"timestamp":"2026-05-19T17:00:00.000Z","type":"session_meta","payload":{"id":"99999999-aaaa-bbbb-cccc-dddddddddddd","timestamp":"2026-05-19T17:00:00.000Z","cwd":"/tmp/some-other-project","originator":"codex_exec","cli_version":"0.130.0","source":"exec","model_provider":"openai","base_instructions":{"text":""}}} +{"timestamp":"2026-05-19T17:00:01.000Z","type":"event_msg","payload":{"type":"user_message","message":"this thread is not in scope — must be skipped"}} +{"timestamp":"2026-05-19T17:00:02.000Z","type":"event_msg","payload":{"type":"agent_message","message":"agent reply we should NOT ingest","phase":"final_answer"}} diff --git a/tests/fixtures/codex/rollouts/partial_tail.jsonl b/tests/fixtures/codex/rollouts/partial_tail.jsonl new file mode 100644 index 0000000..c6d7116 --- /dev/null +++ b/tests/fixtures/codex/rollouts/partial_tail.jsonl @@ -0,0 +1,4 @@ +{"timestamp":"2026-05-19T18:00:00.000Z","type":"session_meta","payload":{"id":"22222222-3333-4444-5555-666666666666","cwd":"/tmp/nerve-test-ws","originator":"codex_tui","cli_version":"0.130.0","source":"tui","model_provider":"openai","base_instructions":{"text":""}}} +{"timestamp":"2026-05-19T18:00:01.000Z","type":"event_msg","payload":{"type":"user_message","message":"first complete line"}} +{"timestamp":"2026-05-19T18:00:02.000Z","type":"event_msg","payload":{"type":"agent_message","message":"second complete","phase":"final_answer"}} +{"timestamp":"2026-05-19T18:00:03.000Z","type":"event_msg","payload":{"type":"user_message","message":"third incomplete \ No newline at end of file From bcbf09f683a3c2264fe851e948ed0c70e744f88e Mon Sep 17 00:00:00 2001 From: pufit Date: Tue, 23 Jun 2026 16:30:42 -0400 Subject: [PATCH 3/3] CI: bump actions to Node24 runtime + key uv cache on pyproject.toml Silences the Node 20 deprecation warnings (checkout v5, setup-uv v6, setup-node v5) and gives the uv cache a real invalidation key. --- .github/workflows/ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c44438..ca5e237 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,14 @@ jobs: python-version: ["3.13"] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6 with: enable-cache: true + # Key the uv cache on the dependency source (this repo has no uv.lock). + cache-dependency-glob: "pyproject.toml" - name: Create virtual environment run: uv venv --python ${{ matrix.python-version }} @@ -53,10 +55,10 @@ jobs: working-directory: web steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: "22" cache: npm