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
40 changes: 26 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ jobs:
- name: Install dependencies
run: uv sync --all-groups

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Build Rust extension
run: uv run maturin develop

- name: Lint with ruff
run: uv run ruff check src/ tests/

Expand All @@ -40,17 +46,23 @@ jobs:
- name: Run tests
run: uv run pytest tests/ -v

# ── Rust stubs (uncomment when devbrief-core crate is added) ──────────────
# rust-check:
# name: Rust clippy & test
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - name: Install Rust toolchain
# uses: dtolnay/rust-toolchain@stable
# with:
# components: clippy
# - name: Clippy
# run: cargo clippy --all-targets --all-features -- -D warnings
# - name: Rust tests
# run: cargo test
rust-check:
name: Rust clippy & test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Clippy
run: cargo clippy --manifest-path rust/Cargo.toml -- -D warnings
env:
PYO3_BUILD_EXTENSION_MODULE: "1"

- name: Rust unit tests
run: cargo test --manifest-path rust/Cargo.toml
env:
PYO3_BUILD_EXTENSION_MODULE: "1"
113 changes: 113 additions & 0 deletions .github/workflows/release-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Release Wheels

on:
push:
tags:
- "v*"

permissions:
contents: read
id-token: write # Required for PyPI OIDC trusted publishing

jobs:
linux:
name: Build wheels (Linux)
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist --interpreter python${{ matrix.python-version }}
manylinux: auto

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.python-version }}
path: dist/

macos:
name: Build wheels (macOS)
runs-on: macos-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist --interpreter python${{ matrix.python-version }}
target: universal2-apple-darwin

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.python-version }}
path: dist/

windows:
name: Build wheels (Windows)
runs-on: windows-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4

- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist --interpreter python${{ matrix.python-version }}

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.python-version }}
path: dist/

sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist

- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist/

publish-pypi:
name: Publish to PyPI
needs: [linux, macos, windows, sdist]
runs-on: ubuntu-latest
environment: pypi # Requires a "pypi" environment configured in repo settings

steps:
- name: Download all wheels
uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: dist/
merge-multiple: true

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# Uses OIDC trusted publishing — no API token needed.
# Configure at: https://pypi.org/manage/project/devbrief/settings/publishing/
13 changes: 11 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ devbrief/
| devbrief repo | LIVE | v0.3.2, cache layer (SHA-keyed, ~/.cache/devbrief/), --no-cache/--refresh |
| devbrief auth | LIVE | v0.2.0, key validation, config write/read/clear, 600 perms |
| devbrief logs | LIVE | v0.3.0, FastAPI+HTMX polling dashboard, ring buffer, file (1s tail)/stdin |
| devbrief env | PLANNED | Rust entry point via maturin/PyO3 |
| devbrief env | IN PROGRESS | v0.4.0, Rust active (maturin/PyO3), gitignore audit + .env drift + secret scan |
| devbrief api | PLANNED | |
| devbrief infra | PLANNED | |
| devbrief pr | PLANNED | |
Expand Down Expand Up @@ -128,6 +128,12 @@ devbrief/
- **Ruff** for linting and formatting — no other linters, no black, no flake8.
- **Type hints everywhere** — no untyped functions, no `Any` without explanation.
- **Rust:** `clippy` clean, zero warnings allowed.
- **Rust testing:** `cargo test` requires two things: `crate-type = ["cdylib", "rlib"]` (rlib lets
the linker produce a test binary) and `PYO3_BUILD_EXTENSION_MODULE=1` (tells PyO3 not to link
against libpython, which may not be available as a shared lib on the host). Our tests only call
pure Rust functions so they do not need a live Python interpreter. Run as:
`PYO3_BUILD_EXTENSION_MODULE=1 cargo test --manifest-path rust/Cargo.toml`.
The `rust-check` CI job sets this env var automatically.
- **Tests required** for every new command and every credential resolution path.
- **Default model:** `claude-sonnet-4-6`. Never hardcode a model string in any command file. Model is always resolved via `resolve_model()` in `devbrief.core.credentials` (env var `DEVBRIEF_MODEL` → `config.toml [anthropic] default_model` → `"claude-sonnet-4-6"`).
- **Graceful degradation:** If Rust extension is unavailable, fall back to Python implementation. Never hard-crash on missing native extension.
Expand All @@ -153,4 +159,7 @@ devbrief/
4. [x] v0.3.0: `devbrief logs` — FastAPI+HTMX polling dashboard, ring buffer, file/stdin
5. [x] v0.3.1: `devbrief repo` cache layer — SHA-keyed local cache, --no-cache/--refresh flags
6. [x] v0.3.2: `github.py` migrated from `requests` to `httpx` — closes HTTP client tech debt
7. [ ] Await spec card before touching any subcommand
7. [x] v0.4.0: `devbrief env` — gitignore audit, .env drift (Rust), secret scan (Rust), stub types
8. [x] Rust unit tests: `["cdylib","rlib"]`, `tempfile` dev-dep, 12 `#[cfg(test)]` tests,
`rust-check` CI job active, `PYO3_BUILD_EXTENSION_MODULE=1` for cargo test
9. [ ] Await spec card before touching any subcommand
89 changes: 71 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- **`devbrief repo`** — takes a GitHub URL, pulls repository metadata, README, and file tree, then asks Claude to produce a structured brief directly in your terminal.
- **`devbrief logs`** — streams a log file (or stdin) into a local browser dashboard with live filtering, level highlighting, and rolling metrics.
- **`devbrief env`** — audits a project directory for environment hygiene: `.gitignore` coverage, `.env` / `.env.example` key drift, and secret pattern detection in committed files.

![devbrief repo cache demo](assets/devbrief-cache.gif)

Expand Down Expand Up @@ -70,6 +71,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
│ repo Analyze a GitHub repository. │
│ auth Manage API credentials. │
│ logs Stream logs into a live dashboard. │
│ env Check project environment health. │
╰──────────────────────────────────────────────────────────────────────────────╯
```

Expand Down Expand Up @@ -139,6 +141,43 @@ devbrief logs /var/log/app.log --port 8080

The dashboard auto-detects common log formats (JSON structured logs, ISO timestamp prefix, `[LEVEL]`, `LEVEL:`) and supports live client-side filtering by level, keyword, and time range. New lines appended to the file appear within ~3 seconds.

### devbrief env

```bash
devbrief env [PATH] [--strict] [--quiet]
```

Audits a project directory for common environment-hygiene issues. Three checks run in sequence:

1. **.gitignore audit** — verifies the file exists and warns on each missing advisory entry (`.env`, `.env.local`, `.env.*.local`, `*.pem`, `*.key`, `id_rsa`, `id_rsa.*`, `.aws/credentials`).
2. **.env drift** — compares keys between `.env` and `.env.example`; warns on keys missing from `.env` or undocumented in `.env.example`.
3. **Secret scan** — walks the directory tree (respecting `.gitignore`) and flags lines matching five patterns: Anthropic API keys, OpenAI API keys, AWS access key IDs, GitHub tokens, and PEM private key headers. Implemented as a compiled Rust extension for near-native performance on large trees.

**Examples:**

```bash
# Audit the current directory
devbrief env

# Audit a specific project root
devbrief env /path/to/project

# Exit code 1 on any warning (CI-friendly strict mode)
devbrief env --strict

# Plain text output (no Rich formatting, useful for scripts)
devbrief env --quiet
```

| Option | Description |
|---|---|
| `PATH` | Project root to scan (default: current directory) |
| `--strict` | Treat warnings as errors — exit 1 if any warnings present |
| `--quiet` | Suppress Rich formatting; plain text output only |
| `--help` | Show usage and exit |

Exit code `0` when all checks pass (or warnings only without `--strict`). Exit code `1` on any error, or any warning under `--strict`.

---

## Credential resolution order
Expand Down Expand Up @@ -198,24 +237,30 @@ If the GitHub API is unreachable, the most recent cached brief for that URL is s

## Development

Requires [uv](https://docs.astral.sh/uv/).
Requires [uv](https://docs.astral.sh/uv/) and [Rust](https://rustup.rs/) (stable toolchain).

```bash
git clone https://github.com/s3bc40/devbrief
cd devbrief
uv sync --all-groups
uv run maturin develop # compile the Rust extension
```

### Run locally

```bash
uv run devbrief repo https://github.com/s3bc40/devbrief
uv run devbrief env .
```

### Run tests

```bash
# Python test suite (122 tests)
uv run pytest

# Rust unit tests (12 tests)
PYO3_BUILD_EXTENSION_MODULE=1 cargo test --manifest-path rust/Cargo.toml
```

### Lint
Expand All @@ -229,28 +274,36 @@ uv run ruff format src/ tests/

```
src/devbrief/
├── cli.py # Typer app — registers all subcommands
├── cli.py # Typer app — registers all subcommands
├── _devbrief_core.pyi # Type stubs for the Rust extension
├── py.typed # PEP 561 marker
├── commands/
│ ├── repo.py # devbrief repo
│ ├── auth.py # devbrief auth
│ └── logs.py # devbrief logs — FastAPI server, log parser, ring buffer
│ ├── repo.py # devbrief repo
│ ├── auth.py # devbrief auth
│ ├── logs.py # devbrief logs — FastAPI server, log parser, ring buffer
│ └── env.py # devbrief env — gitignore audit, .env drift, secret scan
├── templates/
│ ├── base.html # Base HTML layout (HTMX)
│ ├── base.html # Base HTML layout (HTMX)
│ └── logs/
│ └── dashboard.html # Log dashboard template
│ └── dashboard.html # Log dashboard template
├── core/
│ ├── credentials.py # API key + model resolution chain
│ ├── config.py # Config file read/write (~/.config/devbrief/config.toml)
│ └── cache.py # Local brief cache (~/.cache/devbrief/)
├── github.py # GitHub REST API fetchers
├── brief.py # Prompt construction and Claude API call
└── display.py # Rich terminal rendering
│ ├── credentials.py # API key + model resolution chain
│ ├── config.py # Config file read/write (~/.config/devbrief/config.toml)
│ └── cache.py # Local brief cache (~/.cache/devbrief/)
├── github.py # GitHub REST API fetchers
├── brief.py # Prompt construction and Claude API call
└── display.py # Rich terminal rendering
rust/
├── Cargo.toml # Rust crate (cdylib + rlib)
└── src/lib.rs # PyO3 extension: diff_env_files, scan_secrets
tests/
├── test_credentials.py # Credential resolution + auth command tests
├── test_logs.py # Log parser, ring buffer, polling endpoints
├── test_cache.py # Cache module + repo cache integration tests
├── test_github.py
└── test_display.py
├── test_credentials.py # Credential resolution + auth command tests
├── test_logs.py # Log parser, ring buffer, polling endpoints
├── test_cache.py # Cache module + repo cache integration tests
├── test_github.py # GitHub fetcher tests
├── test_display.py # Rich display tests
├── test_env.py # env command tests (Python orchestration layer)
└── MANUAL_QA.md # Manual QA scenarios for devbrief env v0.4.0
```

---
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "devbrief"
version = "0.3.2"
version = "0.4.0"
description = "Generate a human-readable brief for any GitHub repository using Claude AI"
readme = "README.md"
requires-python = ">=3.12"
Expand Down Expand Up @@ -40,7 +40,6 @@ Repository = "https://github.com/s3bc40/devbrief"
devbrief = "devbrief.cli:app"

[tool.maturin]
features = ["pyo3/extension-module"]
python-source = "src"
module-name = "devbrief._devbrief_core"
exclude = [
Expand Down
Loading
Loading