diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..0acd8c6 --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,46 @@ +# SQLite databases +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm + +# Daemon runtime files +daemon.lock +daemon.log +daemon.pid +bd.sock +sync-state.json +last-touched + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Legacy database files +db.sqlite +bd.db + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Merge artifacts (temporary files from 3-way merge) +beads.base.jsonl +beads.base.meta.json +beads.left.jsonl +beads.left.meta.json +beads.right.jsonl +beads.right.meta.json + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +.jsonl.lock +sync_base.jsonl +export-state/ + +# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here. +# They would override fork protection in .git/info/exclude, allowing +# contributors to accidentally commit upstream issue databases. +# The JSONL files (issues.jsonl, interactions.jsonl) and config files +# are tracked by git by default since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..50f281f --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --status in_progress +bd update --status done + +# Sync with git remote +bd sync +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Intelligent JSONL merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..ff8bc92 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,67 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: load from JSONL, no SQLite, write back after each command +# When true, bd will use .beads/issues.jsonl as the source of truth +# instead of SQLite database +# no-db: false + +# Disable daemon for RPC communication (forces direct database access) +# no-daemon: false + +# Disable auto-flush of database to JSONL after mutations +# no-auto-flush: false + +# Disable auto-import from JSONL when it's newer than database +# no-auto-import: false + +# Enable JSON output by default +# json: false + +# Default actor for audit trails (overridden by BD_ACTOR or --actor) +# actor: "" + +# Path to database (overridden by BEADS_DB or --db) +# db: "" + +# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) +# auto-start-daemon: true + +# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) +# flush-debounce: "5s" + +# Export events (audit trail) to .beads/events.jsonl on each flush/sync +# When enabled, new events are appended incrementally using a high-water mark. +# Use 'bd export --events' to trigger manually regardless of this setting. +# events-export: false + +# Git branch for beads commits (bd sync will commit to this branch) +# IMPORTANT: Set this for team projects so all clones use the same sync branch. +# This setting persists across clones (unlike database config which is gitignored). +# Can also use BEADS_SYNC_BRANCH env var for local override. +# If not set, bd sync will require you to run 'bd config set sync.branch '. +# sync-branch: "beads-sync" + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct JSONL +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..c787975 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,4 @@ +{ + "database": "beads.db", + "jsonl_export": "issues.jsonl" +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..807d598 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a60c4ea --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,97 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Project Overview + +**Disasters API** — a REST + GraphQL API serving aggregated disaster data from official government feeds (Fogos.pt, PROCIV, NASA FIRMS). Built with Express 5, Apollo Server, PostgreSQL + PostGIS, and Prisma. + +### Tech Stack + +| Layer | Technology | +|-------|------------| +| Framework | Express 5.x | +| Language | TypeScript (ESM) | +| Database | PostgreSQL + PostGIS | +| ORM | Prisma (+ raw SQL for geospatial) | +| GraphQL | Apollo Server v5 | +| Validation | Joi | +| Auth | API key (Bearer token) | +| Docs | OpenAPI / Swagger UI at `/api-docs` | +| Testing | Jest + Supertest | +| Linting | ESLint + Prettier | + +### Key Resources + +- `openapi.json` — OpenAPI 3.x spec (source of truth for REST endpoints) +- `graphql/schema.ts` — GraphQL type definitions +- `proto/disaster.proto` — Protobuf schema for binary transport +- `CONTRACTS.md` — Shared data contract with disasters-mobile + +### Architecture + +- **REST**: `/api/v1/disasters` — CRUD, pagination, geospatial `/near` queries +- **GraphQL**: `/graphql` — Same capabilities, unified schema +- **Auth**: Bearer token API key on all `/api/*` and `/graphql` routes +- **Rate limit**: 100 req / 15 min (production) +- **Health**: `/healthz`, `/readyz`, `/metrics` (no auth) + +### Cross-Repo Contract + +This API serves the **disasters-mobile** app. Any changes to response shapes, field names, status enums, or error formats **must** be reflected in `CONTRACTS.md` in both repos. Tag contract-affecting issues with `-l contract`. + +## Beads Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd create "Title" -p 0 # Create priority-zero task +bd update --claim # Atomically claim task +bd update --status in_progress # Start work +bd close # Complete work +bd sync # Sync with git +bd list # List all issues +``` + +### Creating Issues + +- Use hierarchical IDs for epics: `disasters-api-a3f8` (Epic) > `disasters-api-a3f8.1` (Task) +- Always include priority: `-p 0` (critical), `-p 1` (high), `-p 2` (normal), `-p 3` (low) +- Tag cross-repo issues with `-l contract` when they affect mobile app compatibility +- Never use `bd edit` (opens interactive editor) — use `bd update --description "text"` instead + +### Quality Gates (before closing issues with code changes) + +```bash +npm run lint # ESLint +npm run format:check # Prettier +npm test # Jest suite +npm run build # TypeScript compilation +``` + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd sync + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds +- Include the beads issue ID in commit messages: `"Fix bug (disasters-api-xyz)"` diff --git a/CONTRACTS.md b/CONTRACTS.md new file mode 100644 index 0000000..6a25895 --- /dev/null +++ b/CONTRACTS.md @@ -0,0 +1,239 @@ +# API Contract: disasters-api <> disasters-mobile + +> This file is maintained in **both** repos. Changes must be synced manually. +> Tag related beads issues with `-l contract` in both repos. + +## Base URL + +``` +Production: TBD +Development: http://localhost:3000 +``` + +## Authentication + +All `/api/*` and `/graphql` routes require: + +``` +Authorization: Bearer +``` + +Rate limit: **100 requests / 15 minutes** per IP. + +--- + +## Core Data Model: Disaster + +### Response Shape (JSON) + +```typescript +interface Disaster { + id: string; // UUID v4 + type: string; // e.g. "wildfire", "flood", "earthquake" + location: { + type: "Point"; + coordinates: [number, number]; // [longitude, latitude] (GeoJSON order) + }; + date: string; // ISO 8601 datetime + description: string | null; + status: "active" | "contained" | "resolved"; + source: string; // "official" | "fogos_pt" | "prociv" | "nasa_firms" | "user" + externalId: string | null; // ID from the original data source + sourceUrl: string | null; // Link to original source + createdAt: string; // ISO 8601 + updatedAt: string; // ISO 8601 + distanceKm?: number; // Only present in /near responses +} +``` + +### Request Shape (for create/update) + +```typescript +interface DisasterInput { + type: string; // required + location: { + type: "Point"; // required + coordinates: [number, number]; // required [lng, lat] + }; + date: string; // required, ISO 8601 + description?: string; + status: "active" | "contained" | "resolved"; // required + source?: string; // defaults to "official" + external_id?: string | null; // NOTE: snake_case in request + source_url?: string | null; // NOTE: snake_case in request +} +``` + +> **Field name mapping**: Requests use `snake_case` (`external_id`, `source_url`). Responses use `camelCase` (`externalId`, `sourceUrl`). + +--- + +## REST Endpoints + +### GET /api/v1/disasters + +Paginated list with filters. + +| Parameter | Type | Default | Notes | +|-----------|------|---------|-------| +| `page` | int | 1 | | +| `limit` | int | 20 | max 100 | +| `type` | string | — | filter by disaster type | +| `dateFrom` | string | — | ISO date | +| `dateTo` | string | — | ISO date | +| `status` | enum | — | active, contained, resolved | +| `source` | string | — | filter by source | + +**Response:** +```json +{ + "data": [Disaster], + "page": 1, + "limit": 20, + "total": 150, + "totalPages": 8 +} +``` + +### GET /api/v1/disasters/:id + +Single disaster by UUID. + +**Response:** `Disaster` +**Errors:** 400 (invalid UUID), 404 (not found) + +### GET /api/v1/disasters/near + +Geospatial proximity search. + +| Parameter | Type | Required | Notes | +|-----------|------|----------|-------| +| `lat` | float | yes | -90 to 90 | +| `lng` | float | yes | -180 to 180 | +| `distance` | float | yes | radius in km | +| `status` | enum | no | | +| `source` | string | no | | + +**Response:** `Disaster[]` (ordered by distance, includes `distanceKm`) + +### POST /api/v1/disasters + +Create a disaster. Body: `DisasterInput`. +**Response:** `Disaster` (201) + +### PUT /api/v1/disasters/:id + +Update a disaster. Body: `DisasterInput` (all fields required). +**Response:** `Disaster` (200) + +### DELETE /api/v1/disasters/:id + +**Response:** 204 No Content + +--- + +## GraphQL + +**Endpoint:** `/graphql` + +### Queries + +```graphql +# Paginated list (same filters as REST) +disasters(page: Int, limit: Int, type: String, dateFrom: String, dateTo: String, status: DisasterStatus, source: String): DisasterPage! + +# Single by ID +disaster(id: ID!): Disaster + +# Proximity search +disastersNear(lat: Float!, lng: Float!, distance: Float!, status: DisasterStatus, source: String): [Disaster!]! +``` + +### Mutations + +```graphql +createDisaster(input: DisasterInput!): Disaster! +updateDisaster(id: ID!, input: DisasterInput!): Disaster! +deleteDisaster(id: ID!): Boolean! +``` + +### Types + +```graphql +enum DisasterStatus { active, contained, resolved } + +type Location { + type: String! + coordinates: [Float!]! +} + +type Disaster { + id: ID! + type: String! + location: Location! + date: String! + description: String + status: DisasterStatus! + source: String + externalId: String + sourceUrl: String + distanceKm: Float +} + +type DisasterPage { + data: [Disaster!]! + page: Int! + limit: Int! + total: Int! + totalPages: Int! +} +``` + +--- + +## Error Format + +```json +{ + "error": "Human-readable message", + "code": "ERROR_CODE", + "details": ["field-level detail", "..."] +} +``` + +| Code | HTTP | Meaning | +|------|------|---------| +| `INVALID_INPUT` | 400 | Validation failure | +| `INVALID_ID` | 400 | Bad UUID format | +| `NOT_FOUND` | 404 | Resource missing | +| `INVALID_QUERY` | 400 | Bad query params | +| — | 401 | Missing/invalid API key | +| — | 429 | Rate limit exceeded | + +--- + +## Status Enum + +| Value | Meaning | +|-------|---------| +| `active` | Ongoing disaster | +| `contained` | Under control but not resolved | +| `resolved` | No longer a threat | + +--- + +## Health Endpoints (no auth) + +| Endpoint | Purpose | +|----------|---------| +| `GET /healthz` | Liveness — `{ status: "ok", uptime, timestamp }` | +| `GET /readyz` | Readiness — `{ status: "ready", db: "connected" }` | +| `GET /metrics` | Prometheus metrics (text) | + +--- + +## Versioning & Breaking Changes + +- REST is versioned at `/api/v1/`. A new version path will be introduced for breaking changes. +- GraphQL changes must be additive (new fields/types). Removing or renaming fields is a breaking change. +- Any change to this contract requires a beads issue tagged `contract` in **both** repos.