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
42 changes: 42 additions & 0 deletions .claude/rules/git.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
# Git & PR Discipline

## Branch Naming
- `feat/<subcommand-or-description>`
- `fix/<short-description>`
- `chore/<short-description>`
- `docs/<short-description>`
- `test/<short-description>`

Never work directly on `main`.

## Commit Format — Conventional Commits

```
type(scope): description
```

Types: `feat`, `fix`, `chore`, `docs`, `test`, `refactor`

- Atomic commits — one intention per commit.
- Never squash unless explicitly instructed.
- Never amend a commit that has already been pushed.

## PR Workflow
1. Create feature branch.
2. Work and commit atomically.
3. Push: `git push -u origin <branch>`
4. Open PR: `gh pr create` — title follows conventional commits.
5. **Stop. Do not merge. Sebastien reviews.**

## Versioning
- Semver. Python and Rust share the same version number.
- Update `pyproject.toml` and `rust/Cargo.toml` version + git tag simultaneously.
- Tag format: `v<major>.<minor>.<patch>`

## Hard Stops — Create BLOCKED.md and Halt
- Ambiguous spec on anything touching architecture.
- Test suite failing without an understood cause.
- Non-trivial merge conflict.
- Any operation touching credentials or `.env` files.
- Uncertainty about which branch to work on.
37 changes: 37 additions & 0 deletions .claude/rules/python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
# Python Conventions

## Runtime
- Python 3.11+ only. `tomllib` is stdlib — import directly (no third-party `tomli`).
- `pyproject.toml` sets `>=3.12` — do not lower this constraint.

## HTTP
- `httpx` async for all HTTP. No `requests`.
- FastAPI handlers must be `async def`.
- Existing `requests` usage is tech debt — migrate if touching those files.

## Typing
- Type hints on every function — no untyped functions.
- No `Any` without an inline comment explaining why.

## Linting & Formatting
- `ruff` only — no black, no flake8, no isort.
- Run: `uv run ruff check src/ tests/` and `uv run ruff format src/ tests/`

## Output
- All terminal output via `Rich` — no raw `print()` in command handlers.

## Model Resolution
- Default: `claude-sonnet-4-6`.
- Always resolved via `resolve_model()` in `devbrief.core.credentials`.
- Chain: `DEVBRIEF_MODEL` env → `config.toml [anthropic] default_model` → `"claude-sonnet-4-6"`.
- Never hardcode a model string in any command file.

## Credentials
- Never log or print credentials (even partially).
- Never commit `.env` or `config.toml` to the repo.
- Config file permissions: `600` on write.

## Rust Extension
- If `devbrief_core` is unavailable at import time, fall back to Python implementation.
- Never hard-crash on a missing native extension.
33 changes: 33 additions & 0 deletions .claude/rules/rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
# Rust Conventions

## Quality
- `clippy` clean — zero warnings allowed. CI enforces this.
- No `unwrap()` in library code — use `?` or explicit error handling.

## Build Setup (PyO3 / maturin)
- `Cargo.toml` must declare `crate-type = ["cdylib", "rlib"]`:
- `cdylib` — produces the Python extension module.
- `rlib` — lets the linker produce a test binary.
- `maturin develop` to build and install locally.

## Running Tests

```
PYO3_BUILD_EXTENSION_MODULE=1 cargo test --manifest-path rust/Cargo.toml
```

- `PYO3_BUILD_EXTENSION_MODULE=1` tells PyO3 not to link against libpython
(may not be available as a shared lib on the host).
- Tests only call pure Rust functions — no live Python interpreter needed.
- The `rust-check` CI job sets this env var automatically.

## Versioning
- Python and Rust share the same version number (semver).
- Update both `pyproject.toml` and `rust/Cargo.toml` versions simultaneously.
- Tag format: `v<major>.<minor>.<patch>`

## Scope
- Rust is used only for `devbrief env` (gitignore audit, .env drift, secret scan).
- Published as `devbrief-core` on crates.io.
- If unavailable at runtime, Python falls back gracefully — never hard-crash.
40 changes: 40 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# Testing Conventions

## Python — pytest

- **Required:** tests for every new command and every credential resolution path.
- Current count: ~122 Python tests across 5 test files.
- Test files mirror `src/devbrief/` structure:
- `test_cache.py` — cache module + repo cache integration
- `test_credentials.py` — credential resolution + auth command
- `test_logs.py` — log parser, ring buffer, polling endpoints
- `test_github.py` — GitHub fetchers
- `test_display.py` — Rich display functions

## Credential Mocking
- **Always** mock credential reads in tests — never use real API keys.
- Mock at the `devbrief.core.credentials` boundary, not deeper.

## Running Python Tests

```
uv run pytest
```

## Rust — cargo test

- Current count: 12 `#[cfg(test)]` tests in `rust/`.
- `dev-dependencies` must include `tempfile` for filesystem tests.

## Running Rust Tests

```
PYO3_BUILD_EXTENSION_MODULE=1 cargo test --manifest-path rust/Cargo.toml
```

See `rust.md` for why this env var is required.

## CI
- `ci.yml` runs lint + type-check + Python tests on every PR and push to `main`.
- `rust-check` CI job runs Rust tests with `PYO3_BUILD_EXTENSION_MODULE=1` set automatically.
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://claude.ai/claude-code/settings.json",
"permissions": {
"allow": [
"Bash(uv:*)",
"Bash(cargo:*)",
"Bash(maturin:*)",
"Bash(pytest:*)",
"Bash(ruff:*)",
"Bash(git:*)",
"Bash(gh:*)",
"Bash(python3 -m json.tool *)"
],
"deny": []
}
}
189 changes: 50 additions & 139 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,167 +1,78 @@
# CLAUDE.md — DevBrief Agent Memory
# CLAUDE.md — DevBrief

## 1. Project Identity
## Project

DevBrief is a developer CLI tool for **project situational awareness**: given a GitHub repository URL (and later: log streams, API endpoints, infra configs, and PRs), it fetches structured data and generates a human-readable brief via Claude AI. The tool is designed for developers who need rapid context on any project without reading every file manually.
DevBrief: developer CLI for **project situational awareness** — fetches structured data from a GitHub repo and generates a human-readable brief via Claude AI.

**Tagline:** Project situational awareness
**Distribution:** PyPI (`devbrief`) + crates.io (`devbrief-core`)
**Stack:** Python (uv) + Rust (maturin/PyO3). No React, no Docker, no webpack.

---

## 2. Tech Stack
## Commands

**Python layer:**
- `typer` — CLI framework (migration target from current `click`)
- `fastapi` + `jinja2` + HTMX — web UI for future `devbrief serve`
- SSE (Server-Sent Events) — streaming output in web UI
- `httpx` (async) — HTTP client (migration target from current `requests`)
- `boto3` — AWS integration for `devbrief logs`
- `anthropic` SDK — Claude AI integration
- `tomllib` (stdlib, Python 3.11+) — config file parsing
- `uv` — package manager and virtual env

**Rust layer:**
- `maturin` + `PyO3` — Rust extensions callable from Python
- Enters via `devbrief env` subcommand
- Published as `devbrief-core` on crates.io

**Not used:** React, Node, webpack, Docker (end users install via pip/cargo only)

---

## 3. Project Structure

```
devbrief/
├── src/
│ └── devbrief/
│ ├── __init__.py # Package init
│ ├── cli.py # Typer app — registers all subcommands
│ ├── commands/
│ │ ├── repo.py # devbrief repo (cache-aware)
│ │ ├── auth.py # devbrief auth
│ │ └── logs.py # devbrief logs — FastAPI server, log parser, ring buffer
│ ├── core/
│ │ ├── credentials.py # API key + model resolution chain
│ │ ├── config.py # Config file read/write (~/.config/devbrief/config.toml)
│ │ └── cache.py # Brief cache keyed by sha256(url+commit_sha) → ~/.cache/devbrief/
│ ├── github.py # GitHub REST API fetchers (+ fetch_latest_commit_sha)
│ ├── brief.py # Claude prompt builder and generate_brief()
│ └── display.py # Rich terminal display functions
├── tests/
│ ├── __init__.py
│ ├── test_cache.py # Cache module + repo cache integration tests
│ ├── test_credentials.py # Credential resolution + auth command tests
│ ├── test_logs.py # Log parser, ring buffer, polling endpoints
│ ├── test_github.py # Unit tests for GitHub fetchers
│ └── test_display.py # Unit tests for Rich display functions
├── dist/ # Built distributions (gitignored except .gitignore)
├── .github/
│ └── workflows/
│ ├── ci.yml # CI: lint + test on every PR and push to main
│ └── release.yml # Release: build + publish to PyPI on git tag v*
├── pyproject.toml # Project metadata, deps, build config (maturin)
├── uv.lock # Locked dependency tree
├── README.md # PyPI-ready README
├── assets/
│ ├── devbrief-cache.gif # Demo GIF for devbrief repo (excluded from wheel)
│ ├── devbrief-env.gif # Demo GIF for devbrief env (excluded from wheel)
│ └── vhs/
│ ├── devbrief-cache.tape # VHS tape source for devbrief-cache.gif
│ └── devbrief-env.tape # VHS tape source for devbrief-env.gif
├── LICENSE # MIT
├── CLAUDE.md # This file — agent persistent memory
└── .gitignore
```

**Assets policy:** `assets/` is excluded from the PyPI wheel via `[tool.maturin] exclude`. GIFs and tapes are repo-only. To regenerate: `vhs assets/vhs/devbrief-cache.tape` or `vhs assets/vhs/devbrief-env.tape`.
| Task | Command |
|------|---------|
| Install deps | `uv sync` |
| Run tests | `uv run pytest` |
| Lint | `uv run ruff check src/ tests/` |
| Format | `uv run ruff format src/ tests/` |
| Type check | `uv run mypy src/` |
| Rust tests | `PYO3_BUILD_EXTENSION_MODULE=1 cargo test --manifest-path rust/Cargo.toml` |
| Build wheel | `maturin develop` |

---

## 4. Subcommand Status

| Subcommand | Status | Notes |
|-----------------|-------------|------------------------------------------------|
| 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 | LIVE | v0.4.2, Rust active (maturin/PyO3), gitignore audit + .env drift + secret scan |
| devbrief api | PLANNED | |
| devbrief infra | PLANNED | |
| devbrief pr | PLANNED | |

## Architecture — Do Not Change Without a Spec Card

- **Credential resolution:** env var → `.env` file → `~/.config/devbrief/config.toml` → keychain (future). Implemented in `devbrief.core.credentials`.
- **Cache key:** `sha256(url + commit_sha)` → `~/.cache/devbrief/`
- **Config file:** `~/.config/devbrief/config.toml`, permissions `600` on write.
- **Model:** always resolved via `resolve_model()` — never hardcoded in command files.
- **Rust extension:** `devbrief env` only. If unavailable at runtime, fall back to Python.
- **Assets:** `assets/` excluded from PyPI wheel via `[tool.maturin] exclude`.

---

## 5. Credential System

**Layered resolution chain (highest priority first):**
1. Environment variable (e.g. `ANTHROPIC_API_KEY`, `GITHUB_TOKEN`)
2. `.env` file in working directory (loaded via `python-dotenv`)
3. `~/.config/devbrief/config.toml` — user-level config file
4. System keychain (future, not yet implemented)
## Subcommand Status

**Config file:** `~/.config/devbrief/config.toml`
**File permissions:** `600` (user read/write only — enforce on write)
**Rules:**
- Never log credentials
- Never print credentials (even partially) in normal output
- Never commit `.env` files or `config.toml` to the repo
- Tests must mock credential reads — never use real keys in tests
| Subcommand | Status | Notes |
|-----------------|----------|----------------------------------------------------------------|
| devbrief repo | LIVE | v0.3.2, SHA-keyed cache, --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/stdin|
| devbrief env | LIVE | v0.4.2, gitignore audit + .env drift + secret scan (Rust) |
| devbrief api | PLANNED | |
| devbrief infra | PLANNED | |
| devbrief pr | PLANNED | |

---

## 6. CI/CD Rules
## Current Sprint

- **`ci.yml`**: Runs on every PR and push to `main`. Steps: lint (ruff), type-check, test (pytest).
- **`release.yml`**: Runs on git tag push matching `v*`. Steps: build wheels only (no sdist — Rust extension requires Rust to build from source), publish to PyPI via trusted publishing (OIDC).
- **Branch strategy:** `main` is protected. Feature branches: `feat/<subcommand-name>` or `feat/<short-description>`.
- **Conventional commits:** `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`
- **Versioning:** semver. Python and Rust share the same version number. Update `pyproject.toml` version and tag simultaneously.
All subcommands through v0.4.2 are LIVE. **Next action:** await spec card before touching any subcommand.

---

## 7. Coding Rules (Enforce Always)

- **Python 3.11+ only** — `tomllib` is stdlib, import it directly. (`pyproject.toml` currently sets `>=3.12`.)
- **Async-first:** Use `httpx` async for all HTTP. FastAPI handlers must be `async def`. (Current `requests` usage is tech debt to migrate.)
- **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.
- **Rich** for all terminal output — no raw `print()` in command handlers.
- **CI:** `ci.yml` runs lint + type-check + test on every PR and push to `main`.
- **Release:** `release.yml` on git tag `v*` — wheels only (no sdist), PyPI OIDC.
- **Versioning:** semver. Python and Rust share version. Update `pyproject.toml` + tag simultaneously.

---

## 8. Agent Boundaries
## What NOT to Touch

- **You implement. You do not decide architecture.**
- If a spec is ambiguous, stop and ask before writing code.
- If a decision would be hard to reverse (schema changes, public API shape, breaking changes), flag it before proceeding.
- When a task is complete, summarize: what was built, what files changed, what tests cover it.
- Read this file at the start of every session. If a subcommand ships or status changes, update the table in section 4.
- Do not push to `main` or merge PRs — Sebastien reviews.
- Do not hardcode model strings — always use `resolve_model()`.
- Do not use `print()` in command handlers — use Rich.
- Do not commit `.env` or `config.toml`.
- Do not build `sdist` — Rust extension requires Rust toolchain; wheels only.
- Do not implement new subcommands without a spec card.

---

## 9. Current Task Queue

1. [x] Create CLAUDE.md
2. [x] Set up CI/CD pipeline (`ci.yml` + `release.yml`) — Rust steps present as commented stubs
3. [x] v0.2.0: CLI restructure (`devbrief repo`), `devbrief auth`, credential + model resolution
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. [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
## Conventions

See `.claude/rules/` for enforced coding conventions:
- `python.md` — Python conventions, async, typing, Rich, model resolution
- `rust.md` — PyO3/maturin, clippy, cargo test setup
- `testing.md` — pytest structure, cargo test, credential mocking
- `git.md` — branch naming, conventional commits, PR discipline, hard stops
Loading