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
5 changes: 5 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Run tests before every commit. Blocks the commit if any test fails.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=run-tests.sh
source "$SCRIPT_DIR/run-tests.sh" commit
5 changes: 5 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Run tests before every push. Last safety net before hitting the remote.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=run-tests.sh
source "$SCRIPT_DIR/run-tests.sh" push
47 changes: 47 additions & 0 deletions .githooks/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Shared test runner sourced by pre-commit and pre-push hooks.
# Usage: source run-tests.sh <context> (context = "commit" | "push")
#
# NOTE: uses `return` not `exit` — this script is sourced, not executed.
# `exit` in a sourced script terminates the parent shell; `return` only
# exits the script's scope, leaving the caller's shell intact.
set -euo pipefail

CONTEXT="${1:-commit}"

# Use the virtualenv Python if active, otherwise fall back to system python3.
# Check python3 first inside the venv (most venvs only create python3, not python).
if [[ -n "${VIRTUAL_ENV:-}" ]]; then
if [[ -x "$VIRTUAL_ENV/bin/python3" ]]; then
PYTHON="$VIRTUAL_ENV/bin/python3"
elif [[ -x "$VIRTUAL_ENV/bin/python" ]]; then
PYTHON="$VIRTUAL_ENV/bin/python"
else
PYTHON="$(command -v python3 2>/dev/null)"
fi
else
PYTHON="$(command -v python3 2>/dev/null)"
fi

if [[ -z "$PYTHON" ]]; then
echo "❌ node9: python3 not found on PATH. Install Python 3.10+ and try again." >&2
return 1
fi

# Sanity-check: require Python 3.10+ (matches pyproject.toml requires-python)
PYVER=$("$PYTHON" -c 'import sys; print(sys.version_info >= (3,10))' 2>/dev/null)
if [[ "$PYVER" != "True" ]]; then
echo "❌ node9: Python 3.10+ required (found: $("$PYTHON" --version 2>&1))" >&2
return 1
fi

echo "🧪 node9: running tests before ${CONTEXT}..."

if ! "$PYTHON" -m pytest tests/ -p no:anyio -q --tb=short; then
echo ""
echo "❌ Tests failed — ${CONTEXT} blocked. Fix the failures above and try again."
echo " To skip (unsafe): git ${CONTEXT} --no-verify"
return 1
fi

echo "✅ All tests passed."
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

<!-- version list -->

## v2.0.0 (2026-04-07)

### Breaking Changes

- **`Node9Agent`** base class introduced — framework-agnostic governed agent with `@tool`, `@internal`, `dispatch()`, `build_tools_anthropic()`, `build_tools_openai()`
- **`safe_path(filename, workspace=...)`** — `workspace` is now keyword-only (prevents silent arg swap)
- **`configure()`** — replaces direct env-var mutation; thread-safe via `threading.RLock`

### New Features

- `Node9Agent`: zero-dependency governed agent base class with DLP, path safety, and audit built-in
- `@tool` decorator: DLP scan + path traversal check + `evaluate()` on every call
- `@internal` decorator: infrastructure methods (no governance); warns if applied to a public method
- `dispatch()`: LLM-safe router — always returns `str`, handles async tools, unknown tools return descriptive error
- `build_tools_anthropic()` / `build_tools_openai()`: auto-generate tool specs from type annotations
- `new_session()`: fresh `run_id` for server/multi-session deployments
- Offline mode warns loudly when `policy=require_approval` but no daemon/API key is available
- `NODE9_SKIP=1` emits `warnings.warn()` at import time AND per `evaluate()` call
- All SDK status output moved to stderr (stdout stays clean for LLM tool parsers)

### Migration from 1.x

```python
# Before (1.x) — positional workspace arg
safe_path(filename, workspace_dir)

# After (2.0) — keyword-only
safe_path(filename, workspace=workspace_dir)
```

`@protect` and `configure()` are fully backwards-compatible. Only `safe_path` call sites need updating.

## v1.0.0 (2026-04-04)

- Initial Release
Expand Down
Loading
Loading