diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..25bb89b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +All tasks use [poethepoet](https://github.com/nat-n/poethepoet). Run with `poe ` or `poetry run poe `: + +```bash +poe check # lint + format-check + typecheck + non-E2E tests (run before pushing) +poe lint # ruff check +poe format # ruff format (auto-fix) +poe typecheck # mypy src/planpilot +poe test # pytest -v --ignore=tests/e2e (includes coverage report) +poe test-e2e # offline E2E suite (tests/e2e/test_cli_e2e.py) +poe docs-links # validate internal markdown links +poe workflow-lint # actionlint on .github/workflows/ +``` + +Run a single test file: +```bash +poetry run pytest tests/engine/test_engine.py -v +``` + +Run a single test by name: +```bash +poetry run pytest tests/engine/test_engine.py -v -k "test_discovery" +``` + +Regenerate the GitHub GraphQL client (requires `gh` auth): +```bash +poe gen-gql +``` + +## Architecture + +planpilot is a CLI/SDK that syncs structured plan files (epics/stories/tasks) to GitHub Issues + Projects v2. It follows a strict layered architecture with downward-only dependencies: + +``` +Contracts → Core → SDK → CLI +``` + +**Contracts** (`src/planpilot/core/contracts/`) — Pure Pydantic models and ABCs. Six domains: `plan`, `item`, `sync`, `config`, `provider`, `renderer`. The `Provider` and `BodyRenderer` ABCs live here (not in Core) because they define what the system needs, not how it works. + +**Core** (`src/planpilot/core/`) — Runtime business logic. Key modules: +- `engine/engine.py` — `SyncEngine`: 5-phase async pipeline (Discovery → Upsert → Enrich → Relations → Result). Epics/stories/tasks are processed sequentially by type level; operations within a level run concurrently via `asyncio.TaskGroup` gated by `asyncio.Semaphore(config.max_concurrent)`. +- `plan/` — JSON plan loading, relational validation (`PlanValidator`), deterministic hashing (`PlanHasher`). +- `providers/github/` — GitHub REST+GraphQL adapter. The GraphQL client under `providers/github/github_gql/` is **generated** by `ariadne-codegen` — do not hand-edit it. +- `providers/dry_run.py` — In-memory `DryRunProvider` used in `--dry-run` mode; no external API calls. +- `renderers/markdown.py` — `MarkdownRenderer`: renders issue bodies with a `PLANPILOT_META_V1` block at the top for idempotent discovery. + +**SDK** (`src/planpilot/sdk.py`) — The sole composition root. `PlanPilot.from_config()` wires all Core domains together. This is the only place that sees all Core modules simultaneously. Re-exports selected Contracts types for external callers. + +**CLI** (`src/planpilot/cli/`) — Thin I/O wrapper. Imports only from the SDK public API and `cli/persistence/`. Never imports Core directly. + +## Key Conventions + +- **Layer discipline**: CLI → SDK only. SDK → Core. Core → Contracts. Never bypass layers. +- **Typing**: `disallow_untyped_defs = true` enforced by mypy on all runtime code in `src/planpilot`. Tests are not mypy-gated. +- **Tests are offline**: Use mocks/fakes (`tests/fakes/`) instead of live GitHub API calls. E2E tests call `planpilot.cli.main()` directly — no shell subprocess. +- **Test layout mirrors source**: `tests/engine/` → `src/planpilot/core/engine/`, etc. +- **Coverage target**: 90%+ branch coverage. +- **Commit format**: Conventional Commits required (`feat`, `fix`, `docs`, `chore`, etc.), max 72-char header. Enforced by CI (commitlint) and local hook (`./scripts/install-hooks.sh`). +- **Generated code**: `src/planpilot/core/providers/github/github_gql/` is excluded from mypy and coverage. Regenerate with `poe gen-gql`. + +## Anti-Patterns + +- Do not import GitHub-specific modules into `engine/` or `contracts/`. +- Do not add direct provider/network calls in tests. +- Do not bypass `PlanValidator` before sync execution. +- Do not write side effects in dry-run code paths. +- Do not re-introduce legacy root domain modules outside `core/` and `cli/`. +- Do not hand-edit the generated GraphQL client as the primary change workflow. + +## Documentation Update Policy + +For any user-visible behavior or architecture change, update docs in the same PR. Quick mapping: + +| Change | Docs to update | +|--------|---------------| +| CLI flags/commands | `README.md`, `docs/modules/cli.md`, `docs/how-it-works.md` | +| Engine/sync semantics | `docs/design/engine.md`, `docs/how-it-works.md`, `docs/modules/sdk.md` | +| Provider behavior | `docs/modules/providers.md`, `docs/modules/github-provider.md` | +| Config/schema | `README.md`, `docs/modules/config.md`, `docs/reference/plan-schemas.md` | +| CI/release | `RELEASE.md`, `docs/reference/workflows-reference.md` | + +Run `poe docs-links` after updating docs to verify no broken internal links. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 190c7b4..1c5da3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Development tasks are managed with [poethepoet](https://github.com/nat-n/poethep | `poe lint` | Run ruff linter (`ruff check .`) | | `poe format` | Auto-format code (`ruff format .`) | | `poe format-check` | Check formatting without changes (`ruff format --check .`) | -| `poe docs-links` | Validate local markdown links in `README.md` and `docs/` | +| `poe docs-links` | Validate local Markdown links in all root `.md` files and `docs/` | | `poe workflow-lint` | Lint GitHub Actions workflows (`./scripts/actionlint.sh`) | | `poe test` | Run non-E2E tests (`pytest -v --ignore=tests/e2e`) | | `poe test-e2e` | Run offline E2E suite (`pytest -v tests/e2e/test_cli_e2e.py`) | @@ -66,7 +66,7 @@ Primary references: - `docs/README.md` — docs index and navigation - `docs/design/documentation-architecture.md` — ownership map and update rules -- `docs/plans/2026-02-13-docs-refresh-execution-plan.md` — docs inventory and update strategy +- `docs/reference/docs-inventory.md` — docs inventory and update strategy Quick mapping: diff --git a/docs/design/documentation-architecture.md b/docs/design/documentation-architecture.md index d985588..adebab1 100644 --- a/docs/design/documentation-architecture.md +++ b/docs/design/documentation-architecture.md @@ -40,6 +40,7 @@ flowchart TD Reference --> R5[plan-schemas.md] Reference --> R6[workflows-reference.md] Reference --> R7[developer-workflow.md] + Reference --> R8[docs-inventory.md] ``` ## Code-to-Docs Ownership Map @@ -116,5 +117,4 @@ flowchart TD - `poe check` passes. - `poe docs-links` passes. - `poe test-e2e` passes when CLI behavior changes. -- Local markdown links resolve. - New docs files are linked from `docs/README.md`. diff --git a/docs/reference/developer-workflow.md b/docs/reference/developer-workflow.md index 6eb17f1..b359c63 100644 --- a/docs/reference/developer-workflow.md +++ b/docs/reference/developer-workflow.md @@ -19,7 +19,7 @@ flowchart TD | Command | Why it matters | |---|---| -| `poetry run poe docs-links` | Prevent broken local markdown links in `README.md` and `docs/` | +| `poetry run poe docs-links` | Prevent broken local Markdown links in all root `.md` files and `docs/` | | `poetry run poe workflow-lint` | Validate GitHub workflow YAML and expressions | | `poetry run poe check` | Run lint + format-check + mypy + non-E2E tests | | `poetry run poe test-e2e` | Validate CLI behavior through offline integration flows | diff --git a/docs/reference/workflows-reference.md b/docs/reference/workflows-reference.md index d076d8b..7985bf6 100644 --- a/docs/reference/workflows-reference.md +++ b/docs/reference/workflows-reference.md @@ -28,7 +28,7 @@ flowchart LR - Commit message linting on pull requests - Workflow linting (`actionlint`) - Ruff lint + format check -- Docs local-link integrity (`poetry run poe docs-links`) +- Docs local-link integrity (`poetry run poe docs-links`): scans all root-level `*.md` files and `docs/**/*.md`; exits 0 (success) when no broken links are found, exits 1 (failure) when broken links are detected; external URLs and anchor-only fragments are ignored; no dry-run mode - Mypy type-check (`poetry run poe typecheck`) - Unit/integration test matrix across Python 3.11-3.13 - E2E suite job diff --git a/scripts/check_markdown_links.py b/scripts/check_markdown_links.py index 05e8061..ee7fbcb 100644 --- a/scripts/check_markdown_links.py +++ b/scripts/check_markdown_links.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Validate local markdown links in README and docs.""" +"""Validate local markdown links in repository root and docs.""" from __future__ import annotations @@ -8,7 +8,7 @@ from dataclasses import dataclass from pathlib import Path -LINK_PATTERN = re.compile(r"\[[^\]]+\]\(([^)]+)\)") +LINK_PATTERN = re.compile(r"\[[^\]]+\]\(([^\s)]+)") IGNORED_PREFIXES = ("http://", "https://", "mailto:", "#") @@ -19,7 +19,7 @@ class BrokenLink: def _candidate_markdown_files(repo_root: Path) -> list[Path]: - files = [repo_root / "README.md"] + files = list(repo_root.glob("*.md")) files.extend((repo_root / "docs").rglob("*.md")) return [file for file in files if file.exists()]