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
Binary file added .coverage
Binary file not shown.
90 changes: 60 additions & 30 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,72 @@ on:
schedule:
- cron: "0 2 * * 0"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
security-events: write

jobs:
gate:
name: Tests + Lint + Types
lint:
name: Lint + Format
runs-on: ubuntu-latest
if: github.event_name != 'schedule'
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
python-version: "3.13"
- run: uv sync --all-extras --group dev
- run: uv run ruff check src tests
- run: uv run ruff format --check src tests

typecheck:
name: Types (mypy strict)
runs-on: ubuntu-latest
if: github.event_name != 'schedule'
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
python-version: "3.13"
- run: uv sync --all-extras --group dev
- run: uv run mypy

test:
name: Tests + Coverage
runs-on: ubuntu-latest
if: github.event_name != 'schedule'
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install
run: python -m pip install -e ".[dev]"
- name: Ruff
run: ruff check src tests
- name: Mypy
run: mypy src
- name: Pytest
run: pytest -q
enable-cache: true
python-version: ${{ matrix.python-version }}
- run: uv sync --all-extras --group dev
- run: uv run pytest --cov=wardline --cov-report=term-missing --cov-fail-under=90

self-hosting-scan:
name: Self-Hosting Scan (dogfood)
runs-on: ubuntu-latest
needs: gate
needs: test
if: github.event_name != 'schedule'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install
run: python -m pip install -e ".[dev]"
enable-cache: true
python-version: "3.13"
- run: uv sync --all-extras --group dev
- name: Scan self -> SARIF
run: wardline scan src/wardline --format sarif --output results.sarif
run: uv run wardline scan src/ --format sarif --output results.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
Expand All @@ -56,32 +86,32 @@ jobs:
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install
run: python -m pip install -e ".[dev]"
enable-cache: true
python-version: "3.13"
- run: uv sync --all-extras --group dev
- name: Network tests
run: pytest -m network -v
run: uv run pytest -m network -v
env:
WARDLINE_OPENROUTER_API_KEY: ${{ secrets.WARDLINE_OPENROUTER_API_KEY }}

docs:
name: Docs (build + deploy)
runs-on: ubuntu-latest
needs: gate
needs: test
if: github.event_name != 'schedule'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install
run: python -m pip install -e ".[docs]"
enable-cache: true
python-version: "3.13"
- run: uv sync --extra docs
- name: Build (strict)
run: mkdocs build --strict
run: uv run mkdocs build --strict
- name: Deploy (main only)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: mkdocs gh-deploy --force
run: uv run mkdocs gh-deploy --force
9 changes: 4 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
enable-cache: true
python-version: "3.13"
- name: Build
run: |
python -m pip install build
python -m build
run: uv build
- uses: actions/upload-artifact@v4
with:
name: dist
Expand Down
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
112 changes: 101 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,117 @@
# Contributing to Wardline

Wardline is a lightweight semantic-tainting static analyzer for Python, built
for small teams who want capable tooling without enterprise weight.
for small teams who want capable tooling without enterprise weight. Bug reports,
feature ideas, docs fixes, and code changes are all welcome.

## Reporting bugs

Open a [bug report](https://github.com/foundryside-dev/wardline/issues/new?template=bug_report.yml). Include:

- Wardline version (`wardline --version`)
- Whether you hit it via the CLI or the MCP server
- A minimal decorated snippet that reproduces the finding (or its absence)
- Expected vs actual behavior
- Python version and OS

## Suggesting features

Open a [feature request](https://github.com/foundryside-dev/wardline/issues/new?template=feature_request.yml). Describe the problem you are solving and your proposed approach.

## Development setup

Wardline uses [uv](https://docs.astral.sh/uv/).

```bash
git clone https://github.com/foundryside-dev/wardline
cd wardline
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
uv sync --all-extras --group dev
```

This installs the base package, every runtime extra (`scanner`, `clarion`,
`docs`), and the dev tooling (ruff, mypy, pytest) into `.venv`.

## Code style

- **Linter / formatter:** [ruff](https://docs.astral.sh/ruff/) (config in `pyproject.toml`, line-length 120)
- **Type checker:** mypy in strict mode (`src/wardline` only)
- **Tests:** pytest, run under `pytest-randomly` (order-dependence is a real bug)

Before committing:

```bash
make format # auto-fix formatting and lint
make lint # check without modifying (same as CI)
make typecheck # mypy strict
```

## Before opening a PR
A ruff pre-commit hook is available — `uv run --with pre-commit pre-commit install`.

Run the full gate and make sure it is green:
## Running tests

```bash
pytest -q
ruff check src tests
mypy src
make test # quick run
make test-cov # with coverage; CI enforces a 90% floor
```

The `network` (live OpenRouter judge) and `clarion_e2e` (real `clarion serve`)
suites are deselected by default. Opt in with `uv run pytest -m network` /
`uv run pytest -m clarion_e2e` (the latter needs a route-capable Clarion binary —
see `CLAUDE.md`).

## Conventions

- **TDD.** Write the failing test first.
- Keep PRs focused — one logical change per PR.
- New behavior needs tests. New `wardline.yaml` keys need a `config_schema.py` update.
- No back-compat shims for unreleased specs — make clean changes.
- Wardline scans its own source as a CI gate; keep the tree finding-clean (or baselined).

## Commit messages

This project uses [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>: <short description>
```

| Type | When to use |
|------|-------------|
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `test` | Adding or updating tests |
| `ci` | CI/CD pipeline changes |
| `build` | Build system or packaging changes |
| `refactor` | Neither fixes a bug nor adds a feature |
| `style` | Formatting only |
| `chore` | Maintenance (deps, config) |

Use `!` after the type for breaking changes: `refactor!: rename public API`.

## Pull request process

1. Branch from `main`.
2. Make your change (test-first).
3. Run `make ci` until green (ruff check + format check + mypy strict + pytest with the 90% coverage floor).
4. Open a PR against `main`, describing what and why; link related issues.
5. Ensure the CI checks pass.

## First-time contributors

Good starting points: documentation improvements, tests for uncovered paths, and
CLI help-text polish.

## Architecture

The big-picture developer guide — the L1/L2/L3 taint pipeline, the package map,
and the conventions — lives in [CLAUDE.md](CLAUDE.md). Read it before a
non-trivial change.

## Code of Conduct

This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md).

## License

- Follow TDD: write the failing test first.
- Keep changes focused; one concern per PR.
- New behaviour needs tests. New `wardline.yaml` keys need a `config_schema.py` update.
By contributing, you agree your contributions are licensed under the [MIT License](LICENSE).
40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.DEFAULT_GOAL := help
.PHONY: help install lint format typecheck test test-cov scan-self docs build clean ci

help: ## Show this help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

install: ## Install all extras + dev tooling
uv sync --all-extras --group dev

lint: ## Run linter + format check
uv run ruff check src tests
uv run ruff format --check src tests

format: ## Auto-format and fix lint
uv run ruff format src tests
uv run ruff check --fix src tests

typecheck: ## Run mypy strict
uv run mypy

test: ## Run tests (no coverage)
uv run pytest -q

test-cov: ## Run tests with coverage gate (90%)
uv run pytest --cov=wardline --cov-report=term-missing --cov-fail-under=90

scan-self: ## Dogfood: scan wardline's own source
uv run wardline scan src/wardline --fail-on ERROR

docs: ## Serve the docs site locally
uv run mkdocs serve

build: ## Build sdist + wheel
uv build

clean: ## Remove build + cache artifacts
rm -rf dist/ build/ *.egg-info .mypy_cache .ruff_cache .pytest_cache .coverage coverage.json
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

ci: lint typecheck test-cov ## Run the full local CI gate
Loading
Loading