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
71 changes: 71 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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@v5

- name: Set up uv
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 }}

- 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@v5

- name: Set up Node.js
uses: actions/setup-node@v5
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/codex/rollouts/in_scope.jsonl
Original file line number Diff line number Diff line change
@@ -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}}
3 changes: 3 additions & 0 deletions tests/fixtures/codex/rollouts/out_of_scope.jsonl
Original file line number Diff line number Diff line change
@@ -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"}}
4 changes: 4 additions & 0 deletions tests/fixtures/codex/rollouts/partial_tail.jsonl
Original file line number Diff line number Diff line change
@@ -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
Loading