diff --git a/.claude/hookify.function-length.md b/.claude/hookify.function-length.md index 34be56aa9d..1440994d65 100644 --- a/.claude/hookify.function-length.md +++ b/.claude/hookify.function-length.md @@ -5,10 +5,10 @@ event: file conditions: - field: file_path operator: regex_match - value: "src/ai_company/.*\\.py$" + pattern: "src/ai_company/.*\\.py$" - field: new_text operator: regex_match - value: "^\\s*(?:async\\s+)?def " + pattern: "^\\s*(?:async\\s+)?def " action: warn --- diff --git a/.claude/hookify.missing-logger.md b/.claude/hookify.missing-logger.md index c388670dd2..bd1df66d09 100644 --- a/.claude/hookify.missing-logger.md +++ b/.claude/hookify.missing-logger.md @@ -5,31 +5,31 @@ event: file conditions: - field: file_path operator: regex_match - value: "src/ai_company/.*\\.py$" + pattern: "src/ai_company/.*\\.py$" - field: file_path operator: not_contains - value: "__init__" + pattern: "__init__" - field: file_path operator: not_contains - value: "enums.py" + pattern: "enums.py" - field: file_path operator: not_contains - value: "errors.py" + pattern: "errors.py" - field: file_path operator: not_contains - value: "types.py" + pattern: "types.py" - field: file_path operator: not_contains - value: "models.py" + pattern: "models.py" - field: file_path operator: not_contains - value: "protocol.py" + pattern: "protocol.py" - field: new_text operator: regex_match - value: "^\\s*(?:async\\s+)?(?:def |class )" + pattern: "^\\s*(?:async\\s+)?(?:def |class )" - field: file_content operator: not_contains - value: "get_logger" + pattern: "get_logger" action: warn --- diff --git a/.claude/hookify.no-future-annotations.md b/.claude/hookify.no-future-annotations.md new file mode 100644 index 0000000000..a4bea1b369 --- /dev/null +++ b/.claude/hookify.no-future-annotations.md @@ -0,0 +1,14 @@ +--- +name: no-future-annotations +enabled: true +event: file +pattern: from\s+__future__\s+import\s+annotations +action: block +--- + +**`from __future__ import annotations` is forbidden in this project.** + +Python 3.14 has PEP 649 native lazy annotations, making this import unnecessary. +The project CLAUDE.md explicitly states: "No `from __future__ import annotations`". + +Remove this import and use native type hints directly. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 473e140c8d..e8070ff970 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,7 @@ jobs: test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + environment: ci permissions: contents: read strategy: diff --git a/.github/workflows/pages-preview.yml b/.github/workflows/pages-preview.yml index 284904369d..ff9226afd9 100644 --- a/.github/workflows/pages-preview.yml +++ b/.github/workflows/pages-preview.yml @@ -11,6 +11,7 @@ on: - "pyproject.toml" - "uv.lock" - "src/ai_company/**" + - "scripts/**" - ".github/workflows/pages-preview.yml" permissions: {} @@ -46,6 +47,9 @@ jobs: - name: Install docs dependencies run: uv sync --group docs --no-dev + - name: Export OpenAPI schema + run: uv run python scripts/export_openapi.py + - name: Build MkDocs run: uv run mkdocs build --strict @@ -142,6 +146,7 @@ jobs: needs: build if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + environment: cloudflare-preview timeout-minutes: 5 permissions: contents: read @@ -219,6 +224,7 @@ jobs: github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + environment: cloudflare-preview timeout-minutes: 5 permissions: contents: read diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 66cf62f81f..b3099d94d9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -10,6 +10,7 @@ on: - "pyproject.toml" - "uv.lock" - "src/ai_company/**" + - "scripts/**" - ".github/workflows/pages.yml" workflow_dispatch: @@ -44,6 +45,9 @@ jobs: - name: Install docs dependencies run: uv sync --group docs --no-dev + - name: Export OpenAPI schema + run: uv run python scripts/export_openapi.py + - name: Build MkDocs run: uv run mkdocs build --strict diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16eb04485e..799c835217 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ jobs: release-please: name: Release Please runs-on: ubuntu-latest + environment: release permissions: contents: write pull-requests: write diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 426fe13db1..90c1534ff2 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -5,10 +5,14 @@ on: branches: [main] paths: - ".github/workflows/**" + - ".github/dependabot.yml" + - ".zizmor.yml" pull_request: branches: [main] paths: - ".github/workflows/**" + - ".github/dependabot.yml" + - ".zizmor.yml" workflow_dispatch: permissions: {} @@ -29,4 +33,5 @@ jobs: - name: Run zizmor uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 with: + config: .zizmor.yml advanced-security: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} diff --git a/.gitignore b/.gitignore index 6976c27de9..dfd9ff1e47 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ web/dist/ # Documentation build output (MkDocs) _site/ +# Generated OpenAPI schema (built by scripts/export_openapi.py) +docs/_generated/ + # Astro / Node.js (landing page) site/node_modules/ site/dist/ diff --git a/.zizmor.yml b/.zizmor.yml new file mode 100644 index 0000000000..9c5cc729ee --- /dev/null +++ b/.zizmor.yml @@ -0,0 +1,5 @@ +rules: + # Daily Dependabot updates are intentional — see CLAUDE.md and dependabot.yml. + # The project uses daily checks with grouped minor/patch to stay current. + dependabot-cooldown: + action: skip diff --git a/CLAUDE.md b/CLAUDE.md index b54b6c87ac..2730dd9efd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,21 +38,25 @@ uv run pytest tests/ -m integration -n auto # integration tests only uv run pytest tests/ -m e2e -n auto # e2e tests only uv run pytest tests/ -n auto --cov=ai_company --cov-fail-under=80 # full suite + coverage uv run pre-commit run --all-files # all pre-commit hooks +uv run python scripts/export_openapi.py # export OpenAPI schema (needed before mkdocs build) uv run mkdocs build --strict # build docs (output: _site/docs/) uv run mkdocs serve # local docs preview (http://127.0.0.1:8000) ``` ## Documentation -- **Docs source**: `docs/` (MkDocs markdown + mkdocstrings auto-generated API reference) +- **Docs source**: `docs/` (MkDocs markdown) - **Design spec**: `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations) - **Architecture**: `docs/architecture/` (overview, tech-stack, decision log) - **Roadmap**: `docs/roadmap/` (status, open questions, future vision) - **Reference**: `docs/reference/` (research, standards) +- **REST API reference**: `docs/rest-api.md` — static Scalar UI page, loads OpenAPI schema from `docs/_generated/openapi.json` (generated by `scripts/export_openapi.py` in CI) +- **Library reference**: `docs/api/` — auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports) +- **Custom templates**: `docs/overrides/` (MkDocs `custom_dir` — e.g. `rest-api.html` for the Scalar API page) +- **Scripts**: `scripts/` — CI/build utility scripts (relaxed ruff rules: `print` and deferred imports allowed) - **Landing page**: `site/` (Astro, Concept C hybrid design) - **Config**: `mkdocs.yml` at repo root -- **API reference**: auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports) -- **CI**: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages +- **CI**: `.github/workflows/pages.yml` — exports OpenAPI schema (`scripts/export_openapi.py`), builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages - **Architecture decisions**: `docs/architecture/decisions.md` (decision log) - **Dependencies**: `docs` group in `pyproject.toml` (`mkdocs-material`, `mkdocstrings[python]`, `griffe-pydantic`) @@ -176,7 +180,7 @@ src/ai_company/ ## CI - **Jobs**: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass (gate) -- **Pages**: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main +- **Pages**: `.github/workflows/pages.yml` — exports OpenAPI schema (`scripts/export_openapi.py`), builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main. Triggers on `docs/**`, `site/**`, `mkdocs.yml`, `pyproject.toml`, `uv.lock`, `src/ai_company/**`, `scripts/**`, and workflow file changes. - **PR Preview**: `.github/workflows/pages-preview.yml` - Builds site on PRs (same path triggers as Pages), injects "Development Preview" banner, deploys to Cloudflare Pages (`synthorg-pr-preview` project) via wrangler CLI - Each PR gets a unique preview URL at `pr-.synthorg-pr-preview.pages.dev` diff --git a/README.md b/README.md index 12b77ce2ce..5ab446311c 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,8 @@ graph TB |---------|-------------| | [Design Specification](docs/design/index.md) | Vision, agents, communication, engine, memory, operations | | [Architecture](docs/architecture/index.md) | System overview, tech stack, decision log | -| [API Reference](docs/api/index.md) | Auto-generated from docstrings | +| [API Reference](docs/rest-api.md) | REST API reference (Scalar/OpenAPI) | +| [Library Reference](docs/api/index.md) | Auto-generated from docstrings | | [Developer Setup](docs/getting_started.md) | Clone, test, lint, contribute | | [User Guide](docs/user_guide.md) | Install, configure, run via Docker | diff --git a/docs/api/index.md b/docs/api/index.md index 3a264efea3..9aeea5da10 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,4 +1,4 @@ -# API Reference +# Library Reference Auto-generated reference documentation from source code docstrings. diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 5cf4405b67..104cda53de 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -62,4 +62,5 @@ graph TB - [Design Specification](../design/index.md) — Full design spec split into 7 focused pages - [Tech Stack](tech-stack.md) — Technology choices and engineering conventions - [Decision Log](decisions.md) — All design decisions, organized by domain -- [API Reference](../api/index.md) — Auto-generated from source code +- [API Reference](../rest-api.md) — REST API reference (Scalar/OpenAPI) +- [Library Reference](../api/index.md) — Auto-generated from source code diff --git a/docs/design/operations.md b/docs/design/operations.md index 7c383f41da..bd06684fee 100644 --- a/docs/design/operations.md +++ b/docs/design/operations.md @@ -942,7 +942,7 @@ future CLI tool are thin clients that call the API -- they contain no business l If needed, a thin CLI utility wrapping the REST API with terminal formatting (Typer + Rich or similar). Not a priority -- the API is fully self-sufficient. To be determined whether a dedicated CLI is warranted or whether `curl`/`httpie` and the interactive Scalar docs at - `/docs/api` suffice. + `/docs/api` (Scalar UI) and `/docs` (OpenAPI JSON) suffice. ### API Surface diff --git a/docs/index.md b/docs/index.md index 1b6c660da5..0057719563 100644 --- a/docs/index.md +++ b/docs/index.md @@ -121,7 +121,8 @@ The design spec covers the full architecture of SynthOrg — from agent identity | [Architecture](architecture/index.md) | System overview, module map, design principles | | [Tech Stack](architecture/tech-stack.md) | Technology choices and engineering conventions | | [Decision Log](architecture/decisions.md) | All design decisions, organized by domain | -| [API Reference](api/index.md) | Auto-generated from docstrings | +| [API Reference](rest-api.md) | REST API reference (Scalar/OpenAPI) | +| [Library Reference](api/index.md) | Auto-generated from docstrings | | [Roadmap](roadmap/index.md) | Status, open questions, future vision | --- diff --git a/docs/overrides/.gitkeep b/docs/overrides/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/overrides/rest-api.html b/docs/overrides/rest-api.html new file mode 100644 index 0000000000..ac9536e143 --- /dev/null +++ b/docs/overrides/rest-api.html @@ -0,0 +1,59 @@ +{% extends "main.html" %} + +{% block content %} + + +
+ This is a static snapshot of the OpenAPI schema. + When the server is running, interactive docs are available at + /docs/api (Scalar) and /docs (OpenAPI JSON). +
+ + + +{% endblock %} diff --git a/docs/rest-api.md b/docs/rest-api.md new file mode 100644 index 0000000000..398e8e8247 --- /dev/null +++ b/docs/rest-api.md @@ -0,0 +1,5 @@ +--- +template: rest-api.html +--- + +# REST API Reference diff --git a/mkdocs.yml b/mkdocs.yml index e60ed26dd0..f2555e1966 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -121,7 +121,8 @@ nav: - Reference: - reference/research.md - reference/standards.md - - API Reference: + - API Reference: rest-api.md + - Library Reference: - api/index.md - Core: api/core.md - Engine: api/engine.md diff --git a/pyproject.toml b/pyproject.toml index 7c15496752..1df5d46a6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,6 +136,10 @@ convention = "google" "PLC0415", # local imports in test functions ] "__init__.py" = ["F401"] +"scripts/**/*.py" = [ + "T20", # print allowed in CLI scripts + "PLC0415", # deferred imports in scripts +] [tool.ruff.lint.isort] known-first-party = ["ai_company"] diff --git a/scripts/export_openapi.py b/scripts/export_openapi.py new file mode 100644 index 0000000000..2efa0581c1 --- /dev/null +++ b/scripts/export_openapi.py @@ -0,0 +1,39 @@ +"""Export the Litestar OpenAPI schema to a static JSON file. + +Used by CI (pages.yml, pages-preview.yml) to generate +``docs/_generated/openapi.json`` before the MkDocs build, so the +static Scalar-based REST API reference page in MkDocs can load the schema. +""" + +import json +import sys +from pathlib import Path + +# Repository root is the parent of the scripts/ directory +REPO_ROOT = Path(__file__).resolve().parent.parent +OUTPUT_DIR = REPO_ROOT / "docs" / "_generated" +OUTPUT_FILE = OUTPUT_DIR / "openapi.json" + + +def main() -> int: + """Instantiate the app, extract the OpenAPI schema, and write JSON.""" + try: + from ai_company.api.app import create_app + + app = create_app() + schema_dict = app.openapi_schema.to_schema() + except Exception as exc: + print(f"Failed to export OpenAPI schema: {exc}", file=sys.stderr) + return 1 + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + OUTPUT_FILE.write_text( + json.dumps(schema_dict, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + print(f"Wrote OpenAPI schema to {OUTPUT_FILE.relative_to(REPO_ROOT)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())