From 71fe3854281509a9e4735d3c795d6d99c006a1aa Mon Sep 17 00:00:00 2001 From: Christian De Leon Date: Thu, 12 Feb 2026 15:29:38 -0500 Subject: [PATCH 1/3] chore: add github project framework --- .agents/skills/commit/SKILL.md | 33 ++++ .agents/skills/pr/SKILL.md | 49 +++++ .agents/skills/validate/SKILL.md | 17 ++ .claude/settings.json | 12 +- .github/ISSUE_TEMPLATE/bug_report.md | 34 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 21 +++ .github/pull_request_template.md | 12 ++ .github/workflows/ci.yml | 50 +++++ AGENTS.md | 128 +++++++++++++ CLAUDE.md | 219 +--------------------- CONTRIBUTING.md | 107 +++++++++++ justfile | 9 + 12 files changed, 472 insertions(+), 219 deletions(-) create mode 100644 .agents/skills/commit/SKILL.md create mode 100644 .agents/skills/pr/SKILL.md create mode 100644 .agents/skills/validate/SKILL.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md create mode 100644 CONTRIBUTING.md diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md new file mode 100644 index 0000000..a9ee2fc --- /dev/null +++ b/.agents/skills/commit/SKILL.md @@ -0,0 +1,33 @@ +--- +name: commit +description: Stage changes and create a Conventional Commits message +--- + +# Commit + +Stage changes and create a commit with a Conventional Commits message. + +## Steps + +1. **Review changes** — Run `git status` and `git diff` to understand what changed. + +2. **Stage specific files** — Add files individually with `git add `. Never use `git add -A` or `git add .` to avoid accidentally staging secrets or generated files. + +3. **Craft commit message** — Write a Conventional Commits message: + ``` + : + ``` + Types: `feat`, `fix`, `chore`, `docs`, `ci`, `test`, `refactor` + + - Keep the description concise (under 72 characters) + - Focus on *why*, not *what* + - Use imperative mood ("add feature" not "added feature") + +4. **Commit** — Create the commit and verify with `git status`. + +## Rules + +- Never commit `.tokens.json`, `.env`, credentials, or secrets +- Never use `--no-verify` unless explicitly requested +- Never amend previous commits unless explicitly requested +- If a pre-commit hook fails, fix the issue and create a NEW commit diff --git a/.agents/skills/pr/SKILL.md b/.agents/skills/pr/SKILL.md new file mode 100644 index 0000000..3ab5388 --- /dev/null +++ b/.agents/skills/pr/SKILL.md @@ -0,0 +1,49 @@ +--- +name: pr +description: Create a branch, push, and open a pull request +--- + +# Pull Request + +Create a properly formatted pull request targeting `main`. + +## Steps + +1. **Create branch** — If not already on a feature branch, create one: + ``` + feature/ # New features + fix/ # Bug fixes + chore/ # Maintenance + docs/ # Documentation + ``` + Use kebab-case for the description. + +2. **Push branch** — Push with upstream tracking: + ```bash + git push -u origin + ``` + +3. **Create PR** — Use `gh pr create` with: + - **Title**: Conventional Commits format (`: `) + - **Body**: Summary + Test Plan using the repo's PR template + - **Target**: `main` + + ```bash + gh pr create --title ": " --body "$(cat <<'EOF' + ## Summary + - + + ## Test Plan + - [ ] `just lint` passes + - [ ] `just test-unit` passes + EOF + )" + ``` + +4. **Return PR URL** — Share the URL so the user can review. + +## Rules + +- One logical change per PR +- PR title must be Conventional Commits format (it becomes the squash commit message) +- Always target `main` diff --git a/.agents/skills/validate/SKILL.md b/.agents/skills/validate/SKILL.md new file mode 100644 index 0000000..4077222 --- /dev/null +++ b/.agents/skills/validate/SKILL.md @@ -0,0 +1,17 @@ +--- +name: validate +description: Run lint and unit tests to validate code quality +--- + +# Validate + +Run formatting checks and unit tests to ensure code quality. + +## Steps + +1. **Check formatting** — Run `just lint` to verify Black formatting. + - If it fails, run `just fmt` to auto-fix, then run `just lint` again to confirm. + +2. **Run unit tests** — Run `just test-unit` to execute unit tests in Docker. + +3. **Report results** — Summarize pass/fail status for both lint and tests. diff --git a/.claude/settings.json b/.claude/settings.json index 4e0ede0..545e546 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,7 +2,17 @@ "permissions": { "allow": [ "Bash(poetry add:*)", - "Bash(poetry remove:*)" + "Bash(poetry remove:*)", + "Bash(just lint)", + "Bash(just fmt)", + "Bash(just test-unit)", + "Bash(just test-unit:*)", + "Bash(just check)", + "Bash(just install)", + "Bash(just build)", + "Bash(gh pr create:*)", + "Bash(gh label create:*)", + "Bash(gh label list:*)" ] } } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c29c640 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug Report +about: Report a bug or unexpected behavior +labels: bug +--- + +## Description + +A clear description of the bug. + +## Steps to Reproduce + +1. ... +2. ... +3. ... + +## Expected Behavior + +What you expected to happen. + +## Actual Behavior + +What actually happened. Include error messages or logs if available. + +## Environment + +- Package: (sdk / api / client / bot) +- Version: +- Python version: +- OS: + +## Additional Context + +Any other relevant information. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..7e1ac60 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature Request +about: Suggest a new feature or improvement +labels: enhancement +--- + +## Description + +A clear description of the feature. + +## Motivation + +Why is this feature needed? What problem does it solve? + +## Proposed Solution + +How should this work? Include API examples or code snippets if relevant. + +## Alternatives + +Any alternative solutions or workarounds you've considered. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..be139c9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Summary + + + +## Test Plan + +- [ ] `just lint` passes +- [ ] `just test-unit` passes + +## Notes + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..95b694c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + lint: + name: Format Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Check formatting with Black + run: pip install black && black --check . + + test: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: extractions/setup-just@v3 + + - name: Run unit tests + run: just test-unit + + pr-title: + name: PR Title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + chore + docs + ci + test + refactor diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1722475 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,128 @@ +# AGENTS.md — PySequence + +## Quick Reference + +```bash +just install # Install dependencies (for IDE support) +just fmt # Format code with Black +just lint # Check formatting (no changes) +just test-unit # Run unit tests in Docker +just check # Run lint + unit tests (full local CI) +just build # Build all Docker images +just api-up # Start the API server +just bot-up # Start the Telegram bot +``` + +Add `--op` to inject secrets via 1Password: `just test-unit --op` + +## Architecture Overview + +**Monorepo — 4 packages, each independently deployable:** + +``` +packages/ + pysequence-sdk/ # GraphQL SDK (curl_cffi + Playwright) + pysequence-api/ # FastAPI REST server (depends on SDK) + pysequence-client/ # HTTP client for the REST server (standalone) + pysequence-bot/ # Optional Telegram bot (depends on SDK) +``` + +**Dependency graph:** +``` +pysequence-client → (HTTP) → pysequence-api → pysequence-sdk → (GraphQL) → Sequence.io + ↑ + pysequence-bot ────────┘ +``` + +- **SDK** — Core. Auth0 tokens, GraphQL client, Pydantic models, shared safeguards. +- **API** — REST trust boundary for external services. API-key auth, transfer limits, audit trail. +- **Client** — Standalone HTTP client for consuming the REST API. No SDK dependency. +- **Bot** — Telegram bot with Claude AI agent. Uses SDK directly (not the API). + +## Code Conventions + +### Commits + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +``` +: +``` + +Types: `feat`, `fix`, `chore`, `docs`, `ci`, `test`, `refactor` + +Examples: +- `feat: add pod transfer history endpoint` +- `fix: handle empty agent response in Telegram bot` +- `chore: bump version to 0.3.1` + +### Formatting + +- **Black** — all Python code, enforced in CI +- Run `just fmt` to format, `just lint` to check + +### Testing + +- **pytest** in Docker via `just test-unit` +- Unit tests: no external dependencies, mock the SDK +- Integration tests: `just test-integration --op` (requires 1Password secrets) + +### Branch Naming + +``` +feature/ +fix/ +chore/ +docs/ +``` + +## Contribution Rules + +- **One logical change per PR** — don't mix unrelated changes +- **Squash merge** — PR title becomes the commit message on `main` +- **CI must pass** — `just lint` + `just test-unit` + PR title check +- **PR title = Conventional Commits format** — enforced by CI + +## Common Patterns + +### Browser-Compatible HTTP + +The SDK uses `curl_cffi` with Chrome TLS fingerprinting. All HTTP requests must include browser-matching headers (`origin`, `referer`, `sec-fetch-*`, `x-request-id: webapp-`). + +### GraphQL Must Match the Webapp + +Query strings, fragment names, field selections, and `__typename` inclusions must exactly match what the Sequence webapp sends. Do not invent new queries. + +### Shared Safeguards + +`AuditLog` and `DailyLimitTracker` live in `pysequence_sdk.safeguards` and are shared by both the API server and the bot. Do not duplicate safeguard logic. + +### Single SequenceClient Per Server + +All requests share one `SequenceClient` instance so rate limiting works correctly. Do not create multiple instances. + +### Secrets vs Config + +- **Secrets** (`SEQUENCE_*`, `TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`) — always environment variables +- **Non-secret config** (model, limits, system prompt) — `bot-config.yaml`, mounted into Docker + +### Token Management + +Fully automated via `get_access_token()`. Tokens cached in `.tokens.json`. Consumers never manage tokens directly. + +## Do Not + +- Add Node.js dependencies to this project +- Use raw `poetry run` when a `just` recipe exists +- Modify GraphQL queries without matching the webapp +- Commit `.tokens.json`, `.env`, or any secrets +- Create multiple `SequenceClient` instances in a single server +- Over-engineer — only build what's needed + +## Skills + +Agent skills are defined in `.agents/skills/`. Each skill has a `SKILL.md` with instructions: + +- **validate** — Run lint + unit tests, auto-fix formatting issues +- **commit** — Stage changes and create a Conventional Commits message +- **pr** — Create a branch, push, and open a PR with proper formatting diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index da1ccf9..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,218 +0,0 @@ -# PySequence - -Unofficial Python SDK for the [GetSequence](https://getsequence.io/) personal finance platform. - -## Philosophy - -This project is a **toolkit, not a monolith**. Users pick what they need: - -- **SDK only** — Import `pysequence-sdk` into your own Python project and talk to the Sequence GraphQL API directly. No server, no bot, no Docker. -- **API server** — A REST trust boundary for external services you don't control (e.g. OpenClaw). Sits in front of the SDK with API-key auth and transfer safeguards. -- **Telegram bot** — Uses the SDK directly (not the API). Trusted because it has its own safeguards (daily limits, audit trail, transfer confirmation) and a deliberately limited toolset (no shell access). Deploy standalone with Docker Compose. - -Each component is independently deployable. The API and bot have separate Docker Compose files — a bot-only deployer never sees API config, and vice versa. - -## Monorepo Structure - -``` -packages/ - pysequence-sdk/ # GraphQL SDK (curl_cffi + Playwright) - pysequence-api/ # FastAPI REST server (depends on SDK) - pysequence-client/ # HTTP client for the REST server (standalone) - pysequence-bot/ # Optional Telegram bot (depends on SDK) -``` - -**Dependency graph:** -``` -pysequence-client → (HTTP) → pysequence-api → pysequence-sdk → (GraphQL) → Sequence.io - ↑ - pysequence-bot ────────┘ -``` - -## Packages - -### pysequence-sdk -Core SDK that communicates with the Sequence GraphQL API. - -- `auth.py` — Auth0 token management (Playwright browser login + refresh + caching) -- `client.py` — `SequenceClient` GraphQL HTTP client -- `config.py` — `SequenceCredentials`, `SequenceConfig`, env var loading -- `models.py` — Pydantic response models (Pod, Transfer, etc.) -- `types.py` — Enums (TransferStatus, Direction, etc.) -- `exceptions.py` — SDK error types -- `graphql/queries.py` — GraphQL query strings -- `graphql/mutations.py` — GraphQL mutation strings -- `safeguards/` — Shared `AuditLog` + `DailyLimitTracker` (used by both API and bot) - -### pysequence-api -FastAPI REST server wrapping the SDK with financial safeguards. - -- `app.py` — App factory with lifespan -- `config.py` — `ServerConfig`, env var loading -- `dependencies.py` — API key auth, shared client -- `models.py` — Pydantic request models -- `routes/` — Route modules (health, pods, accounts, activity, transfers) -- `safeguards/` — Re-exports from `pysequence_sdk.safeguards` - -### pysequence-client -Standalone HTTP client for consuming the REST API. No SDK dependency. - -- `client.py` — `SequenceApiClient` -- `models.py` — Response models -- `exceptions.py` — `ApiError` - -### pysequence-bot -Optional Telegram bot that uses the SDK directly with a Claude AI agent. - -- `config.py` — `SdkConfig`, `AgentConfig`, `TelegramConfig`, YAML config loading (`load_config()`) -- `ai/agent.py` — Claude-powered agent with tool-use loop -- `ai/tools.py` — Tool definitions and execution against the SDK -- `ai/memory.py` — Persistent JSON-backed memory store -- `telegram/bot.py` — Telegram bot handlers, rate limiting, transfer confirmation - -Non-secret configuration (model, limits, rate limits, system prompt, user mappings) lives in `bot-config.yaml` at the repo root. The bot loads this file on startup. Users copy it, edit it, and mount it into Docker — no code changes needed. Secrets (`TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`, `SEQUENCE_*`) always come from environment variables. - -## SDK Usage - -```python -from pysequence_sdk import SequenceClient, get_access_token - -token = get_access_token() -with SequenceClient(token) as client: - pods = client.get_pods() - balance = client.get_total_balance() - detail = client.get_pod_detail(org_id, pod_id) - result = client.transfer(kyc_id, source_id, dest_id, amount_cents=500) -``` - -For long-running use, pass a `token_provider` to auto-refresh expired tokens: - -```python -client = SequenceClient(get_access_token(), token_provider=get_access_token) -``` - -## API Server Endpoints - -All routes except `/api/health` require `X-API-Key` header. - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/health` | Health check (no auth) | -| GET | `/api/pods` | List all pods with balances | -| GET | `/api/pods/balance` | Total balance across all pods | -| GET | `/api/pods/{pod_name}/balance` | Single pod balance by name | -| GET | `/api/pods/detail/{pod_id}` | Pod detail (uses default org_id) | -| GET | `/api/pods/{org_id}/{pod_id}` | Pod detail with explicit org_id | -| GET | `/api/accounts` | All accounts (pods, ports, external) | -| GET | `/api/activity/summary` | Monthly activity summary | -| GET | `/api/activity` | Paginated transfer activity | -| GET | `/api/transfers/{transfer_id}` | Single transfer detail | -| GET | `/api/activity/{org_id}/{transfer_id}` | Transfer detail with explicit org_id | -| POST | `/api/transfers` | Transfer funds (with safeguards) | - -### Transfer Safeguards - -- **Per-transfer limit** — rejects transfers above `$10,000` (configurable) -- **Global daily limit** — rejects when cumulative daily total exceeds `$25,000` (configurable) -- **Audit trail** — every transfer attempt logged to `.audit.jsonl` - -## HTTP Client Configuration - -The SDK uses `curl_cffi` with Chrome configuration for browser-compatible HTTP requests. This ensures proper TLS fingerprinting and HTTP/2 behavior when communicating with the GraphQL API. - -**What curl_cffi handles:** -- TLS fingerprint matching Chrome -- HTTP/2 SETTINGS and pseudo-header order -- User-Agent and standard browser headers - -**Additional request headers:** -- `origin`, `referer`, `sec-fetch-*` headers matching browser XHR behavior -- `x-request-id: webapp-` matching the webapp's request ID format -- `accept` header matching the webapp's GraphQL accept string -- Rate limiting between requests - -## Configuration - -| Env Var | Required | Default | Description | -|---|---|---|---| -| `SEQUENCE_EMAIL` | Yes | — | Login email | -| `SEQUENCE_PASSWORD` | Yes | — | Login password | -| `SEQUENCE_TOTP` | Yes | — | Current TOTP code | -| `SEQUENCE_ORG_ID` | Yes | — | Organization ID | -| `SEQUENCE_KYC_ID` | Yes | — | KYC ID | -| `SEQUENCE_AUTH0_CLIENT_ID` | Yes | — | Auth0 client ID | -| `SEQUENCE_API_KEY` | Server | — | API key for server authentication | -| `SEQUENCE_DATA_DIR` | No | `.` | Directory for data files (.tokens.json, etc.) | -| `SEQUENCE_SERVER_HOST` | No | `0.0.0.0` | Server bind host | -| `SEQUENCE_SERVER_PORT` | No | `8720` | Server bind port | -| `SEQUENCE_MAX_TRANSFER_CENTS` | No | `1000000` | Per-transfer limit ($10,000) | -| `SEQUENCE_MAX_DAILY_TRANSFER_CENTS` | No | `2500000` | Daily transfer limit ($25,000) | -| `TELEGRAM_BOT_TOKEN` | Bot | — | Telegram bot token | -| `ANTHROPIC_API_KEY` | Bot | — | Anthropic API key for Claude | -| `BOT_CONFIG` | No | `BOT_DATA_DIR/bot-config.yaml` | Path to `bot-config.yaml` | -| `BOT_DATA_DIR` | No | `.` | Directory for bot data files (memories, limits) | - -## API Details - -- **Endpoint:** `POST https://app.getsequence.io/api/graphql` -- **Auth:** Bearer token from Auth0 (domain: `auth.getsequence.io`) -- **Token lifetime:** ~24 hours -- **Token management:** Automated via `get_access_token()` - -## Auth0 Token Flow - -Token management is fully automated via `auth.py`. Consumers just call `get_access_token()`. - -1. Check `.tokens.json` for a cached token -2. If valid → return immediately -3. If expired but refresh token exists → use `refresh_token` grant -4. If no tokens or refresh fails → full re-authentication via Playwright browser login - -## Development - -Use `@justfile` recipes for common operations. Run `just` to see available recipes. - -```bash -just test-unit # Run unit tests in Docker -just test-integration # Run integration tests in Docker -just test-all # Run all tests (unit + integration) in Docker -just api-up # Start the API server -just api-down # Stop the API server -just api-logs # Follow API server logs -just bot-up # Start the Telegram bot -just bot-down # Stop the Telegram bot -just bot-logs # Follow Telegram bot logs -just build # Build all Docker images -just install # Install dependencies (for IDE support) -just reauth # Delete cached tokens to force re-authentication -just fmt # Format code with Black -``` - -All tests and the API server run in Docker. Add `--op` to any recipe to inject secrets via 1Password: - -```bash -just test-unit --op # Unit tests with 1Password secrets -just api-up --op # Start API server with 1Password secrets -just bot-up --op # Start Telegram bot with 1Password secrets -``` - -## Docker - -All Docker files live at the project root: - -- `Dockerfile` — Multi-stage build: `prod` (API server), `bot` (Telegram bot), `dev` (testing) -- `compose.api.yaml` — API server deployment -- `compose.bot.yaml` — Telegram bot deployment -- `compose.dev.yaml` — Dev/test environment - -The API and bot have separate compose files so each can be deployed independently. - -## Requirements & Constraints - -- **Real money — production quality:** This service moves real money. Correctness, reliability, and safety are non-negotiable. -- **Browser-compatible requests:** HTTP requests must use browser-compatible TLS and header configurations. -- **Not over-engineered:** Only build what's needed. -- **GraphQL queries must exactly match the webapp:** Query strings, fragment names, field selections, and `__typename` inclusions must match. -- **Use Justfile recipes:** Use `@justfile` recipes instead of raw `poetry run` commands when a recipe exists. -- **Single SequenceClient per server:** All requests share one instance so rate limiting works correctly. -- **Shared safeguards:** `AuditLog` and `DailyLimitTracker` live in `pysequence_sdk.safeguards` and are shared by both the API server and the bot. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f023969 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,107 @@ +# Contributing to PySequence + +## Prerequisites + +- Python 3.11+ +- [Poetry 2.x](https://python-poetry.org/) +- [Docker](https://www.docker.com/) (tests run in containers) +- [just](https://github.com/casey/just) (task runner) + +## Development Setup + +```bash +# Install dependencies locally (for IDE support) +just install + +# Run unit tests +just test-unit + +# Format code +just fmt + +# Check formatting without changes +just lint + +# Full local CI check (lint + tests) +just check +``` + +Add `--op` to any recipe to inject secrets via 1Password: + +```bash +just test-unit --op +``` + +## Commit Conventions + +This project uses [Conventional Commits](https://www.conventionalcommits.org/). Every commit message (and PR title) must follow: + +``` +: +``` + +### Types + +| Type | When to use | +|------|-------------| +| `feat` | New feature or capability | +| `fix` | Bug fix | +| `chore` | Maintenance (deps, version bumps, config) | +| `docs` | Documentation changes | +| `ci` | CI/CD workflow changes | +| `test` | Adding or updating tests | +| `refactor` | Code restructuring without behavior change | + +### Examples + +``` +feat: add pod transfer history endpoint +fix: handle empty agent response in Telegram bot +chore: bump version to 0.3.1 +docs: update SDK usage examples +ci: add format check to PR workflow +test: add unit tests for daily limit tracker +refactor: extract GraphQL query builder +``` + +## Branch Naming + +Create branches with a type prefix and kebab-case description: + +``` +feature/add-transfer-history +fix/empty-response-handling +chore/bump-dependencies +docs/update-api-examples +``` + +## Pull Request Process + +1. **One logical change per PR** — don't mix unrelated changes +2. **Branch from `main`** — keep your branch up to date +3. **PR title = Conventional Commits format** — this becomes the squash commit message +4. **CI must pass** — format check, unit tests, and PR title validation +5. **Squash merge** — all PRs are squash merged into `main` + +### Before Submitting + +```bash +# Run the full local CI check +just check +``` + +This runs `just lint` (format check) and `just test-unit` (unit tests in Docker). + +## Testing + +- **Unit tests**: `just test-unit` — run in Docker, no external dependencies +- **Integration tests**: `just test-integration --op` — require 1Password secrets +- **All tests**: `just test-all --op` — unit + integration + +Write tests for new functionality. Unit tests should mock external dependencies (SDK client, API calls). + +## Code Style + +- **Formatter**: [Black](https://black.readthedocs.io/) — enforced in CI +- Run `just fmt` before committing to auto-format +- Run `just lint` to check without making changes diff --git a/justfile b/justfile index f5f4379..9b713e3 100644 --- a/justfile +++ b/justfile @@ -155,6 +155,15 @@ reauth: fmt: black . +# Check formatting with Black (no changes) +lint: + black --check . + +# Run lint + unit tests (full local CI check) +check: + just lint + just test-unit + # Get code statistics with cloc loc: cloc --fmt=1 --thousands-delimiter=, --vcs=git . From 44ed0c5bf754d030a9d0517af8c26037753f26e4 Mon Sep 17 00:00:00 2001 From: Christian De Leon Date: Thu, 12 Feb 2026 15:49:16 -0500 Subject: [PATCH 2/3] chore: update agent skills to reference project docs --- .agents/skills/commit/SKILL.md | 43 +++++++++++------------ .agents/skills/pr/SKILL.md | 58 +++++++++++--------------------- .agents/skills/validate/SKILL.md | 18 +++++----- 3 files changed, 49 insertions(+), 70 deletions(-) diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md index a9ee2fc..86f7cd1 100644 --- a/.agents/skills/commit/SKILL.md +++ b/.agents/skills/commit/SKILL.md @@ -1,33 +1,30 @@ --- name: commit -description: Stage changes and create a Conventional Commits message +description: Stage and commit changes following project conventions. + Use after all quality checks pass. --- # Commit -Stage changes and create a commit with a Conventional Commits message. +Stage and commit changes using the conventions defined in +CONTRIBUTING.md. -## Steps - -1. **Review changes** — Run `git status` and `git diff` to understand what changed. - -2. **Stage specific files** — Add files individually with `git add `. Never use `git add -A` or `git add .` to avoid accidentally staging secrets or generated files. +## Prerequisites -3. **Craft commit message** — Write a Conventional Commits message: - ``` - : - ``` - Types: `feat`, `fix`, `chore`, `docs`, `ci`, `test`, `refactor` +- Run the validate skill first. All checks must pass. - - Keep the description concise (under 72 characters) - - Focus on *why*, not *what* - - Use imperative mood ("add feature" not "added feature") - -4. **Commit** — Create the commit and verify with `git status`. - -## Rules +## Steps -- Never commit `.tokens.json`, `.env`, credentials, or secrets -- Never use `--no-verify` unless explicitly requested -- Never amend previous commits unless explicitly requested -- If a pre-commit hook fails, fix the issue and create a NEW commit +1. Read the Commit Conventions section of CONTRIBUTING.md for + the format rules. +2. Review the staged and unstaged changes to understand what + was changed. +3. Group changes into logical units. Each commit should represent + one coherent change. If changes span multiple concerns (e.g., a + bug fix and a refactor), they should be separate commits. +4. For each logical group: + a. Determine the appropriate commit type and scope. + b. Construct a commit message that follows the documented format. + c. Stage only the files belonging to that group. + d. Commit with the constructed message. +5. Confirm all commits were created and display the messages. diff --git a/.agents/skills/pr/SKILL.md b/.agents/skills/pr/SKILL.md index 3ab5388..55ed624 100644 --- a/.agents/skills/pr/SKILL.md +++ b/.agents/skills/pr/SKILL.md @@ -1,49 +1,29 @@ --- name: pr -description: Create a branch, push, and open a pull request +description: Create a branch and open a pull request via GitHub CLI. + Use after changes are committed. --- # Pull Request -Create a properly formatted pull request targeting `main`. +Create a feature branch, push changes, and open a PR using `gh` CLI. -## Steps - -1. **Create branch** — If not already on a feature branch, create one: - ``` - feature/ # New features - fix/ # Bug fixes - chore/ # Maintenance - docs/ # Documentation - ``` - Use kebab-case for the description. - -2. **Push branch** — Push with upstream tracking: - ```bash - git push -u origin - ``` - -3. **Create PR** — Use `gh pr create` with: - - **Title**: Conventional Commits format (`: `) - - **Body**: Summary + Test Plan using the repo's PR template - - **Target**: `main` +## Prerequisites - ```bash - gh pr create --title ": " --body "$(cat <<'EOF' - ## Summary - - +- `gh` CLI must be installed and authenticated. +- Changes must already be committed using the commit skill. - ## Test Plan - - [ ] `just lint` passes - - [ ] `just test-unit` passes - EOF - )" - ``` - -4. **Return PR URL** — Share the URL so the user can review. - -## Rules +## Steps -- One logical change per PR -- PR title must be Conventional Commits format (it becomes the squash commit message) -- Always target `main` +1. Read the Branch Naming section of CONTRIBUTING.md for the + naming convention. +2. Create a new branch from the current HEAD following the + naming convention. +3. Push the branch to the remote. +4. Read .github/pull_request_template.md for the expected PR + body structure. +5. Construct the PR title following the Conventional Commits + format documented in CONTRIBUTING.md. +6. Fill in the PR body template based on the committed changes. +7. Open the PR using `gh pr create` targeting the default branch. +8. Display the PR URL. diff --git a/.agents/skills/validate/SKILL.md b/.agents/skills/validate/SKILL.md index 4077222..8147f18 100644 --- a/.agents/skills/validate/SKILL.md +++ b/.agents/skills/validate/SKILL.md @@ -1,17 +1,19 @@ --- name: validate -description: Run lint and unit tests to validate code quality +description: Run all project quality checks and report results. + Use before committing or opening a PR. --- # Validate -Run formatting checks and unit tests to ensure code quality. +Run all project quality checks and report results. ## Steps -1. **Check formatting** — Run `just lint` to verify Black formatting. - - If it fails, run `just fmt` to auto-fix, then run `just lint` again to confirm. - -2. **Run unit tests** — Run `just test-unit` to execute unit tests in Docker. - -3. **Report results** — Summarize pass/fail status for both lint and tests. +1. Read the Quick Reference section of AGENTS.md for the exact commands. +2. Run the format check command. Report pass or fail. +3. Run the lint command. Report pass or fail. +4. Run the build command. Report pass or fail. +5. Run the test command. Report pass or fail. +6. Summarize results. If anything failed, stop here — do not + proceed to commit or PR. From 7911a353ff9a1df9af18cdd42866072cfe634643 Mon Sep 17 00:00:00 2001 From: Christian De Leon Date: Thu, 12 Feb 2026 15:56:01 -0500 Subject: [PATCH 3/3] fix: use single-line YAML frontmatter descriptions in skill files --- .agents/skills/commit/SKILL.md | 3 +-- .agents/skills/pr/SKILL.md | 3 +-- .agents/skills/validate/SKILL.md | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md index 86f7cd1..480d0ad 100644 --- a/.agents/skills/commit/SKILL.md +++ b/.agents/skills/commit/SKILL.md @@ -1,7 +1,6 @@ --- name: commit -description: Stage and commit changes following project conventions. - Use after all quality checks pass. +description: Stage and commit changes following project conventions. Use after all quality checks pass. --- # Commit diff --git a/.agents/skills/pr/SKILL.md b/.agents/skills/pr/SKILL.md index 55ed624..9753a13 100644 --- a/.agents/skills/pr/SKILL.md +++ b/.agents/skills/pr/SKILL.md @@ -1,7 +1,6 @@ --- name: pr -description: Create a branch and open a pull request via GitHub CLI. - Use after changes are committed. +description: Create a branch and open a pull request via GitHub CLI. Use after changes are committed. --- # Pull Request diff --git a/.agents/skills/validate/SKILL.md b/.agents/skills/validate/SKILL.md index 8147f18..887691b 100644 --- a/.agents/skills/validate/SKILL.md +++ b/.agents/skills/validate/SKILL.md @@ -1,7 +1,6 @@ --- name: validate -description: Run all project quality checks and report results. - Use before committing or opening a PR. +description: Run all project quality checks and report results. Use before committing or opening a PR. --- # Validate