feat: add static OpenAPI reference page with Scalar UI#318
Conversation
Export the Litestar OpenAPI schema to a static JSON file at build time and serve it via a Scalar-powered docs page. Renames the mkdocstrings "API Reference" nav section to "Library Reference" to distinguish Python library docs from the REST API reference. - Add scripts/export_openapi.py to generate docs/_generated/openapi.json - Add docs/rest-api.md + docs/overrides/rest-api.html (Scalar CDN embed) - Update both pages.yml and pages-preview.yml CI workflows - Rename API Reference → Library Reference in mkdocs.yml and docs/api/index.md Closes #296
Fix function-length and missing-logger rules that used `value:` instead of `pattern:` in conditions (hookify expects `pattern:`). Remove unsupported `file_content` field from missing-logger rule. Add new no-future-annotations rule to block `from __future__ import annotations` (unnecessary on Python 3.14 with PEP 649).
Align CLAUDE.md, README.md, docs/index.md, docs/architecture/index.md, and docs/design/operations.md with the nav restructuring from the Scalar OpenAPI reference page addition. Add ruff per-file-ignores for scripts/. Pre-reviewed by 1 agent, 12 findings addressed
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds an OpenAPI export script integrated into CI Pages workflows, reorganizes docs to separate REST API and Library references with a custom OpenAPI template, updates hook configurations, CI workflow triggers/environments, ignores generated OpenAPI artifacts, and adds Ruff per-file ignores for scripts. Changes
Sequence Diagram(s)sequenceDiagram
participant GH as "GitHub Actions"
participant Script as "scripts/export_openapi.py"
participant App as "ai_company.api.app"
participant FS as "Filesystem"
participant MkDocs as "MkDocs Build"
GH->>Script: run scripts/export_openapi.py
Script->>App: import create_app() & build app
App-->>Script: return app.openapi_schema.to_schema()
Script->>FS: write docs/_generated/openapi.json
Script-->>GH: exit 0 (prints path)
GH->>MkDocs: run mkdocs build (template reads generated JSON)
MkDocs->>FS: read docs/_generated/openapi.json
MkDocs-->>GH: produce site artifacts
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the project's documentation by integrating a static OpenAPI reference page using Scalar UI. It introduces a dedicated script and CI steps to generate and serve the OpenAPI schema, providing a clear distinction between the REST API documentation and the auto-generated Python library reference. This change improves the discoverability and usability of the API documentation for developers and users alike, while also refining internal tooling and documentation structure. Highlights
Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds a static OpenAPI reference page using Scalar UI, which is a great enhancement for API documentation. The changes include a new script to export the OpenAPI schema, updates to CI to run this script, and restructuring of the documentation to differentiate between the REST API reference and the library reference.
My review has two main points:
- A security and reliability improvement for the new Scalar UI page by pinning the external script version and using Subresource Integrity (SRI).
- A correction to a
hookifyrule for detecting missing loggers, which appears to have become overly broad and would likely cause a lot of noise for developers.
Overall, the changes are well-structured and the documentation updates are clear. Addressing these points will improve the robustness and developer experience of the new features.
| - field: file_content | ||
| operator: not_contains | ||
| value: "get_logger" | ||
| pattern: "^\\s*(?:async\\s+)?(?:def |class )" |
There was a problem hiding this comment.
This missing-logger rule seems to have been made overly broad by the removal of the condition that checks for the presence of get_logger. As it is now, it will trigger a warning for every new class or function, even in files that already have a logger. This is likely to create a lot of unnecessary warnings for developers.
To fix this, I suggest re-introducing the check for the absence of get_logger. You could add the following condition to the list:
- field: file_content
operator: not_contains
pattern: "get_logger"| data-url="../_generated/openapi.json" | ||
| data-configuration='{"theme": "default"}' | ||
| ></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script> |
There was a problem hiding this comment.
For better reproducibility and to avoid unexpected breaking changes, it's a good practice to pin the version of external scripts loaded from a CDN.
Additionally, for security, consider adding a Subresource Integrity (SRI) hash to ensure the script hasn't been tampered with. This is especially important for a security-conscious project.
<script
src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.20.11"
integrity="sha384-your-hash-here"
crossorigin="anonymous"
></script>You'll need to generate the correct integrity hash for the script.
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.20.11"></script>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #318 +/- ##
=======================================
Coverage 93.64% 93.64%
=======================================
Files 427 427
Lines 19177 19177
Branches 1846 1846
=======================================
Hits 17959 17959
Misses 943 943
Partials 275 275 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR introduces a static OpenAPI reference page powered by Scalar UI, wired into the MkDocs documentation site. A new Key changes:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant CI as CI Workflow
participant Script as export_openapi.py
participant App as create_app()
participant FS as docs/_generated/
participant MkDocs as MkDocs Build
participant Scalar as Scalar UI (CDN)
participant Browser as Browser
CI->>Script: uv run python scripts/export_openapi.py
Script->>App: import & instantiate
App-->>Script: app.openapi_schema.to_schema()
Script->>FS: write openapi.json
FS-->>CI: docs/_generated/openapi.json
CI->>MkDocs: uv run mkdocs build --strict
MkDocs->>FS: copy openapi.json → _site/docs/_generated/
MkDocs-->>CI: _site/ (static HTML)
Browser->>MkDocs: GET /docs/rest-api/
MkDocs-->>Browser: rest-api.html (Scalar template)
Browser->>Scalar: load @scalar/api-reference@1.48.5 (CDN)
Scalar-->>Browser: Scalar JS bundle
Browser->>FS: fetch ../_generated/openapi.json
FS-->>Browser: openapi.json
Scalar-->>Browser: render interactive API reference
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .claude/hookify.no-future-annotations.md
Line: 5-6
Comment:
**Top-level `pattern` field may be silently ignored**
The other two hookify rules in this repo (`function-length.md`, `missing-logger.md`) place all matching criteria inside a `conditions:` array of `{field, operator, pattern}` objects. This rule instead puts `pattern:` at the top level of the frontmatter alongside `event:` and `action:`.
If hookify only evaluates match criteria when they appear inside a `conditions:` block, this top-level `pattern` field will be silently ignored and the rule will **always** fire as a block action on every file-write event (because there are no conditions to fail). Or, depending on hookify's schema validation, it may be treated as a no-op entirely and never fire.
To align with the schema used by the other rules, the condition should be expressed as:
```yaml
conditions:
- field: new_text
operator: regex_match
pattern: "from\\s+__future__\\s+import\\s+annotations"
```
or, if you want to match against the full file content:
```yaml
conditions:
- field: file_content
operator: regex_match
pattern: "from\\s+__future__\\s+import\\s+annotations"
```
Either way, the top-level bare `pattern:` field is inconsistent with the rest of the hookify schema in use here and risks making the rule a no-op.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: scripts/export_openapi.py
Line: 29-34
Comment:
**File write errors produce a raw traceback instead of a clean error message**
The `OUTPUT_DIR.mkdir(...)` and `OUTPUT_FILE.write_text(...)` calls are outside the `try/except` block. If either fails (e.g., permission denied, disk full), Python will emit an unhandled exception traceback directly to stderr with no contextual message, and the exit code will be `1` from Python's default handler — which is correct behaviour, but the error output will be harder to interpret in CI logs than the clean message produced for the import/schema failure path.
Consider wrapping the write phase in the same (or a separate) `try/except` with a diagnostic `print(..., file=sys.stderr)` so both failure modes produce equally readable CI output:
```python
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
try:
OUTPUT_FILE.write_text(
json.dumps(schema_dict, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
except OSError as exc:
print(f"Failed to write OpenAPI schema to {OUTPUT_FILE}: {exc}", file=sys.stderr)
return 1
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 2d73ff9 |
There was a problem hiding this comment.
Pull request overview
Adds a static REST API reference page to the MkDocs site using Scalar, backed by a CI-generated OpenAPI JSON export from the Litestar app. Also updates documentation navigation and CLAUDE/hookify guidance to reflect the new REST vs library reference split.
Changes:
- Add
scripts/export_openapi.pyand run it in Pages/Preview workflows to generatedocs/_generated/openapi.jsonbefore MkDocs builds. - Add a new MkDocs page (
docs/rest-api.md) using a custom Material template (docs/overrides/rest-api.html) to render Scalar against the generated schema. - Update nav/links and contributor docs (README/docs index/CLAUDE) to distinguish REST API reference vs mkdocstrings library reference.
Reviewed changes
Copilot reviewed 16 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/export_openapi.py | New CLI script to instantiate the Litestar app and write a static OpenAPI JSON into docs/_generated/. |
| README.md | Updates documentation table links to point to REST API vs library reference pages. |
| pyproject.toml | Relaxes ruff rules for scripts/** (allow print and deferred imports). |
| mkdocs.yml | Adds “API Reference” (REST/Scalar) and renames mkdocstrings output to “Library Reference”. |
| docs/rest-api.md | New MkDocs page that selects the custom rest-api.html template. |
| docs/overrides/rest-api.html | New Material override template embedding Scalar and loading the generated OpenAPI JSON. |
| docs/index.md | Updates docs landing page links to REST API vs library reference. |
| docs/design/operations.md | Corrects referenced interactive docs endpoints to /api (Scalar) and /docs (OpenAPI JSON). |
| docs/architecture/index.md | Updates architecture index links to REST API vs library reference. |
| docs/api/index.md | Renames the mkdocstrings section header to “Library Reference”. |
| CLAUDE.md | Documents the new OpenAPI export step, REST API page, and scripts directory conventions. |
| .gitignore | Ignores docs/_generated/ output produced during docs builds. |
| .github/workflows/pages.yml | Adds scripts/** trigger and runs OpenAPI export before mkdocs build. |
| .github/workflows/pages-preview.yml | Adds scripts/** trigger and runs OpenAPI export before mkdocs build. |
| .claude/hookify.no-future-annotations.md | Adds a hookify rule blocking from __future__ import annotations. |
| .claude/hookify.missing-logger.md | Updates hookify rule fields/structure for logger reminders. |
| .claude/hookify.function-length.md | Updates hookify rule fields/structure for function length reminders. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| operator: not_contains | ||
| value: "protocol.py" | ||
| pattern: "protocol.py" | ||
| - field: new_text | ||
| operator: regex_match | ||
| value: "^\\s*(?:async\\s+)?(?:def |class )" | ||
| - field: file_content | ||
| operator: not_contains | ||
| value: "get_logger" | ||
| pattern: "^\\s*(?:async\\s+)?(?:def |class )" | ||
| action: warn |
There was a problem hiding this comment.
This missing-logger hook no longer checks whether the module actually lacks get_logger/logger = get_logger(__name__) (the prior file_content not_contains get_logger condition was removed). As written, it will warn on any change that adds/modifies a def/class in src/ai_company/** even when the logger is already present, which doesn’t match the rule’s intent/message. Consider reintroducing a file_content condition (or equivalent) to only warn when the logger import/assignment is missing, or update the rule text/name to reflect the new behavior.
| data-url="../_generated/openapi.json" | ||
| data-configuration='{"theme": "default"}' | ||
| ></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script> |
There was a problem hiding this comment.
The Scalar UI bundle is loaded from jsDelivr without a pinned version. This is a supply-chain / reproducibility risk (the docs can change or break when the CDN content updates). Consider pinning @scalar/api-reference to an explicit version and ideally adding Subresource Integrity (or vendoring the asset) so the rendered API docs are deterministic and safer.
| <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.25.109"></script> |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/hookify.missing-logger.md:
- Around line 27-29: The rule currently fires on any change where field
"new_text" matches the regex pattern for leading "def" or "class" but doesn't
check that the logger import and instantiation are absent; update the predicate
for the hook (the "new_text" regex_match rule) to include negative content
checks that verify the module does NOT contain the lines "from
ai_company.observability import get_logger" and "logger = get_logger(__name__)"
before warning. Concretely, add negative regex_match (or regex_not_match)
predicates that search the file content for those exact symbols (get_logger
import and logger = get_logger(__name__)) so the hook only triggers when both
are missing.
In `@docs/overrides/rest-api.html`:
- Line 31: The CDN script URL is unpinned (src uses `@scalar/api-reference`) so it
will float to newest releases; update the script tag source to a fixed version
(e.g., change "@scalar/api-reference" to a specific release like
"@scalar/api-reference@<major>.<minor>.<patch>" or the exact bundled file URL)
to lock the API reference, then commit the updated src so the docs load a known
stable version; ensure the same pinned version is used wherever
"@scalar/api-reference" appears.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b0dbf579-66cb-46d7-bdfe-f4d11f986618
📒 Files selected for processing (18)
.claude/hookify.function-length.md.claude/hookify.missing-logger.md.claude/hookify.no-future-annotations.md.github/workflows/pages-preview.yml.github/workflows/pages.yml.gitignoreCLAUDE.mdREADME.mddocs/api/index.mddocs/architecture/index.mddocs/design/operations.mddocs/index.mddocs/overrides/.gitkeepdocs/overrides/rest-api.htmldocs/rest-api.mdmkdocs.ymlpyproject.tomlscripts/export_openapi.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Agent
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
All versions in
pyproject.tomlmust use==for pinned versions
Files:
pyproject.toml
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do NOT usefrom __future__ import annotations— Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:(no parentheses) — PEP 758 except syntax enforced by ruff on Python 3.14
Include type hints on all public functions and classes; ensure mypy strict mode compliance
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Never mutate existing objects; create new objects instead. For non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 withBaseModel,model_validator,computed_field, andConfigDict. Use@computed_fieldfor derived values instead of storing redundant fields. UseNotBlankStrfor all identifier/name fields
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over barecreate_task
Keep functions under 50 lines; keep files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)
Files:
scripts/export_openapi.py
🧠 Learnings (5)
📚 Learning: 2026-03-12T08:11:41.492Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T08:11:41.492Z
Learning: ALWAYS read the relevant `docs/design/` page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior
Applied to files:
docs/architecture/index.md
📚 Learning: 2026-03-12T08:11:41.492Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T08:11:41.492Z
Learning: Applies to src/ai_company/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation and internal flow
Applied to files:
.claude/hookify.function-length.md.claude/hookify.missing-logger.md
📚 Learning: 2026-03-12T08:11:41.492Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T08:11:41.492Z
Learning: Applies to **/*.py : Do NOT use `from __future__ import annotations` — Python 3.14 has PEP 649 native lazy annotations
Applied to files:
.claude/hookify.no-future-annotations.md
📚 Learning: 2026-03-12T08:11:41.492Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T08:11:41.492Z
Learning: Applies to src/ai_company/**/*.py : Always use `logger` as the variable name (not `_logger`, not `log`)
Applied to files:
.claude/hookify.missing-logger.md
📚 Learning: 2026-03-12T08:11:41.492Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T08:11:41.492Z
Learning: Applies to src/ai_company/**/*.py : Every module with business logic MUST import and use `from ai_company.observability import get_logger` and create `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()`
Applied to files:
.claude/hookify.missing-logger.md
🧬 Code graph analysis (1)
scripts/export_openapi.py (1)
src/ai_company/api/app.py (1)
create_app(284-401)
🪛 GitHub Actions: Workflow Security
.github/workflows/pages-preview.yml
[warning] 167-167: Secrets referenced without a dedicated environment (CLOUDFLARE_API_TOKEN).
[warning] 168-168: Secrets referenced without a dedicated environment (CLOUDFLARE_ACCOUNT_ID).
[warning] 262-262: Secrets referenced without a dedicated environment (CLOUDFLARE_API_TOKEN).
[warning] 263-263: Secrets referenced without a dedicated environment (CLOUDFLARE_ACCOUNT_ID).
🪛 HTMLHint (1.9.2)
docs/overrides/rest-api.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
🪛 LanguageTool
CLAUDE.md
[style] ~55-~55: A comma is missing here.
Context: ...docs/overrides/(MkDocscustom_dir— e.g.rest-api.html` for the Scalar API page...
(EG_NO_COMMA)
[uncategorized] ~59-~59: The official name of this software platform is spelled with a capital “H”.
Context: ...**: mkdocs.yml at repo root - CI: .github/workflows/pages.yml — exports OpenAPI ...
(GITHUB)
🪛 markdownlint-cli2 (0.21.0)
.claude/hookify.no-future-annotations.md
[warning] 9-9: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (20)
.claude/hookify.function-length.md (1)
8-11: Pattern-based matching looks right here.This keeps the reminder scoped to Python files under
src/ai_company/and only fires when a function definition is part of the change..gitignore (1)
56-58: LGTM!The ignore rule correctly excludes the generated OpenAPI schema directory, preventing CI-generated artifacts from being committed. The comment clearly documents the source script.
pyproject.toml (1)
139-142: LGTM!The per-file ignores are appropriate for CLI scripts:
T20allowsprint()for script outputPLC0415allows deferred imports, which is useful for scripts that need to import heavy modules only when executeddocs/design/operations.md (1)
945-945: LGTM!The documentation accurately reflects the OpenAPI configuration:
/apifor Scalar UI and/docsfor the raw OpenAPI JSON schema. This is consistent with theOpenAPIConfiginsrc/ai_company/api/app.py..claude/hookify.no-future-annotations.md (1)
1-14: LGTM!The hookify rule correctly enforces the project's coding guideline against
from __future__ import annotations. The regex pattern accurately matches the import statement, and the explanation clearly communicates why this import is forbidden (PEP 649 native lazy annotations in Python 3.14).The markdownlint warning about missing top-level heading is a false positive — hookify configuration files use YAML frontmatter as their structure.
docs/overrides/rest-api.html (1)
1-32: Template structure and implementation look good.The HTMLHint doctype warning is a false positive — this is a Jinja2 template that extends
main.html, not a standalone HTML document. The parent template provides the doctype.The CSS correctly provides full viewport width for Scalar while hiding MkDocs navigation, and the relative path to
openapi.jsonwill resolve correctly from the rendered page location..github/workflows/pages-preview.yml (2)
14-14: LGTM!Adding
scripts/**to path triggers ensures preview builds are triggered when the OpenAPI export script changes.
50-52: LGTM!The OpenAPI schema export step is correctly positioned before the MkDocs build, ensuring the generated
openapi.jsonis available when Scalar renders the REST API reference page..github/workflows/pages.yml (2)
13-13: LGTM!Consistent with the preview workflow — script changes will trigger production builds.
48-50: LGTM!The OpenAPI export step mirrors the preview workflow, ensuring consistent behavior between preview and production deployments.
scripts/export_openapi.py (1)
18-30: LGTM! Clean implementation of OpenAPI schema export.The script correctly:
- Defers the heavy import inside
main()(appropriate for a CLI script)- Creates a minimal app instance for schema extraction
- Uses the correct Litestar API (
app.openapi_schema.to_schema())- Ensures the output directory exists
- Writes pretty-printed JSON with proper encoding
Note:
create_app()without arguments will log a warning about missing services, but this is acceptable since only the OpenAPI schema is needed, not runtime functionality.docs/api/index.md (1)
1-1: LGTM!The heading rename from "API Reference" to "Library Reference" correctly reflects the documentation restructure, distinguishing mkdocstrings-generated source code docs from the new Scalar/OpenAPI REST API reference.
docs/architecture/index.md (1)
65-66: LGTM!The new links correctly point to the REST API and Library Reference pages with appropriate relative paths and consistent descriptions.
docs/rest-api.md (1)
1-5: LGTM!Clean setup using MkDocs front matter to specify a custom template. The
rest-api.htmltemplate (indocs/overrides/) will render the Scalar UI with the OpenAPI schema.docs/index.md (1)
124-125: LGTM!The "Further Reading" table correctly splits the previous single API Reference entry into two distinct entries with appropriate relative paths.
README.md (1)
121-122: LGTM!Paths correctly include the
docs/prefix for links from the repository root, and descriptions are consistent with other documentation files.mkdocs.yml (1)
124-140: LGTM!The navigation restructure cleanly separates the REST API reference (Scalar/OpenAPI) from the Library Reference (mkdocstrings). The Library Reference section properly groups all module documentation pages.
CLAUDE.md (3)
41-41: LGTM!Correctly documents the new OpenAPI export command that must run before
mkdocs build.
48-59: LGTM!Comprehensive documentation of the new REST API reference workflow, including:
- REST API reference page with Scalar UI
- Library reference (mkdocstrings)
- Custom templates directory
- Scripts directory with relaxed ruff rules
- CI workflow update for OpenAPI export
183-183: LGTM!CI documentation accurately reflects the updated Pages workflow that now exports the OpenAPI schema and includes
scripts/**in triggers.
| - field: new_text | ||
| operator: regex_match | ||
| value: "^\\s*(?:async\\s+)?(?:def |class )" | ||
| - field: file_content | ||
| operator: not_contains | ||
| value: "get_logger" | ||
| pattern: "^\\s*(?:async\\s+)?(?:def |class )" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n ".claude/hookify.missing-logger.md"Repository: Aureliolo/synthorg
Length of output: 1157
This hook no longer detects a missing logger.
The rule warns on any change to a qualifying src/ai_company/ module that contains a def or class, regardless of whether the required logger setup exists. It needs negative content predicates to verify that from ai_company.observability import get_logger and logger = get_logger(__name__) are actually absent before firing the warning.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/hookify.missing-logger.md around lines 27 - 29, The rule currently
fires on any change where field "new_text" matches the regex pattern for leading
"def" or "class" but doesn't check that the logger import and instantiation are
absent; update the predicate for the hook (the "new_text" regex_match rule) to
include negative content checks that verify the module does NOT contain the
lines "from ai_company.observability import get_logger" and "logger =
get_logger(__name__)" before warning. Concretely, add negative regex_match (or
regex_not_match) predicates that search the file content for those exact symbols
(get_logger import and logger = get_logger(__name__)) so the hook only triggers
when both are missing.
- Restore file_content/get_logger guard in missing-logger hookify rule (was accidentally removed, causing false positives on every file) - Pin Scalar CDN to v1.48.5 for reproducibility - Improve Scalar UI: full-page layout, modern theme, hide footer/sidebar - Add error handling to export_openapi.py for CI failures - Fix endpoint URLs: Scalar UI is at /docs/api, not /api - Complete CLAUDE.md Pages trigger list (add site/**, pyproject.toml, uv.lock) - Remove no-op tabs block and misplaced comment from rest-api.html
- Add `environment:` declarations to jobs using secrets: - ci.yml test job → `ci` environment (CODECOV_TOKEN) - pages-preview.yml deploy/cleanup → `cloudflare-preview` (Cloudflare secrets) - release.yml release-please → `release` (RELEASE_PLEASE_TOKEN) - Add .zizmor.yml config to suppress dependabot-cooldown (daily is intentional) - Add .zizmor.yml and .github/dependabot.yml to zizmor workflow path triggers
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| except Exception as exc: | ||
| print(f"Failed to export OpenAPI schema: {exc}", file=sys.stderr) | ||
| return 1 |
There was a problem hiding this comment.
The exception handler only prints str(exc), which drops the traceback and makes CI failures hard to diagnose. Consider logging/printing the full traceback (e.g., via traceback.print_exception(exc) or logger.exception(...)) before returning a non-zero exit code.
| .md-content__inner { | ||
| min-height: calc(100vh - 3.6rem); | ||
| display: flex; | ||
| flex-direction: column; |
There was a problem hiding this comment.
This loads Scalar from a third-party CDN without Subresource Integrity (SRI) and without a local fallback. For a docs site this is still a supply-chain risk; consider vendoring the asset in the repo (or at least adding an integrity attribute + crossorigin) so the rendered API reference can't be silently altered by a compromised CDN.
| 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. |
There was a problem hiding this comment.
These paths look inconsistent with the Litestar OpenAPI config in ai_company.api.app.create_app() (OpenAPI config path is /docs and Scalar UI is configured at /api). If this section is referring to the running API server, the correct URLs should match the actual routes (e.g., Scalar at /api and schema at something like /docs/openapi.json).
| `/docs/api` (Scalar UI) and `/docs` (OpenAPI JSON) suffice. | |
| `/api` (Scalar UI) and `/docs/openapi.json` (OpenAPI JSON) suffice. |
| 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)}") |
There was a problem hiding this comment.
File write errors produce a raw traceback instead of a clean error message
The OUTPUT_DIR.mkdir(...) and OUTPUT_FILE.write_text(...) calls are outside the try/except block. If either fails (e.g., permission denied, disk full), Python will emit an unhandled exception traceback directly to stderr with no contextual message, and the exit code will be 1 from Python's default handler — which is correct behaviour, but the error output will be harder to interpret in CI logs than the clean message produced for the import/schema failure path.
Consider wrapping the write phase in the same (or a separate) try/except with a diagnostic print(..., file=sys.stderr) so both failure modes produce equally readable CI output:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
try:
OUTPUT_FILE.write_text(
json.dumps(schema_dict, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
except OSError as exc:
print(f"Failed to write OpenAPI schema to {OUTPUT_FILE}: {exc}", file=sys.stderr)
return 1Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/export_openapi.py
Line: 29-34
Comment:
**File write errors produce a raw traceback instead of a clean error message**
The `OUTPUT_DIR.mkdir(...)` and `OUTPUT_FILE.write_text(...)` calls are outside the `try/except` block. If either fails (e.g., permission denied, disk full), Python will emit an unhandled exception traceback directly to stderr with no contextual message, and the exit code will be `1` from Python's default handler — which is correct behaviour, but the error output will be harder to interpret in CI logs than the clean message produced for the import/schema failure path.
Consider wrapping the write phase in the same (or a separate) `try/except` with a diagnostic `print(..., file=sys.stderr)` so both failure modes produce equally readable CI output:
```python
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
try:
OUTPUT_FILE.write_text(
json.dumps(schema_dict, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
except OSError as exc:
print(f"Failed to write OpenAPI schema to {OUTPUT_FILE}: {exc}", file=sys.stderr)
return 1
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.zizmor.yml:
- Around line 4-5: The .zizmor.yml uses an unsupported rule field "action: skip"
for the dependabot-cooldown rule; replace that line with "disable: true" so the
rule becomes disabled (update the dependabot-cooldown entry to use disable: true
instead of action: skip).
In `@docs/overrides/rest-api.html`:
- Around line 31-44: The .rest-api-banner currently uses a container-level
opacity which dims all children; remove the opacity property from
.rest-api-banner and instead lower perceived contrast by adjusting only the
background or border color (e.g., use a semi-transparent background color or
color variable with alpha) while keeping text and inline .rest-api-banner code
at full opacity; ensure .rest-api-banner code retains its own background and
font-color so code chips remain fully legible.
- Around line 47-58: The page relies on the dynamic `@scalar/api-reference` script
(script element with id "api-reference" and
data-url="../_generated/openapi.json") and currently has no non-JS fallback; add
a simple noscript fallback that exposes the bundled OpenAPI JSON (the same
../_generated/openapi.json) as either a direct link and/or inline preformatted
JSON so users without JS or blocked CDN can still access the static schema;
ensure the fallback references the same data-url value and is placed near the
existing api-reference script element.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 11a8fe34-6d91-407a-b01a-a1ba8f6888b2
📒 Files selected for processing (6)
.github/workflows/ci.yml.github/workflows/pages-preview.yml.github/workflows/release.yml.github/workflows/zizmor.yml.zizmor.ymldocs/overrides/rest-api.html
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
🪛 GitHub Actions: Workflow Security
.zizmor.yml
[error] 5-5: Zizmor configuration error: unknown field action, expected one of disable, ignore, config at line 5 column 5 in .zizmor.yml.
🪛 HTMLHint (1.9.2)
docs/overrides/rest-api.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
🔇 Additional comments (7)
.github/workflows/release.yml (1)
13-13: Good hardening for the release job.Scoping this secret-using job to the
releaseenvironment is a solid defense-in-depth change and aligns the workflow with environment-based policy controls..github/workflows/ci.yml (1)
51-51: LGTM! Good security improvement for secrets handling.Adding
environment: cito the test job addresses the zizmorsecrets-outside-envfinding by ensuring thatsecrets.CODECOV_TOKEN(used at lines 83 and 91) is accessed within a declared environment context. This is a security best practice that enables environment protection rules.Ensure the
cienvironment is created in the repository settings (Settings → Environments) if it doesn't already exist, otherwise this job will fail..github/workflows/pages-preview.yml (4)
14-14: LGTM!Adding
scripts/**to the path triggers ensures the workflow runs when the OpenAPI export script or other doc-related scripts are modified, which is necessary since the build now depends onscripts/export_openapi.py.
50-52: LGTM!The OpenAPI export step is correctly positioned after dependency installation and before the MkDocs build, ensuring the schema is available when docs are built. The step uses
uv runconsistently with other Python commands in the workflow.
149-149: LGTM!Adding
environment: cloudflare-previewscopes secrets access to this environment context, addressing the zizmorsecrets-outside-envsecurity finding. This is a best practice for jobs that use sensitive credentials.
227-227: LGTM!Consistent with the
deploy-previewjob, adding the environment declaration here properly scopes the Cloudflare secrets used for deployment cleanup..github/workflows/zizmor.yml (1)
8-9: Explicit config wiring and path filters look good.Re-running on
.zizmor.yml/.github/dependabot.ymlchanges and passingconfig: .zizmor.ymlmakes the workflow react to policy updates and avoids relying on config auto-discovery.Also applies to: 14-15, 36-37
| dependabot-cooldown: | ||
| action: skip |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
zizmor configuration schema rule options disable ignore
💡 Result:
In zizmor.yml, per-rule configuration lives under rules.<id> and you typically have three knobs: disable, ignore, and (sometimes) config. [1]
Disable a rule (turn the audit off entirely)
rules:
template-injection:
disable: trueThis disables that audit completely. [1]
Ignore specific findings (preferred vs disabling)
rules.<id>.ignore is an array of locations in the form:
filename.yml[:line[:column]] (line/column are optional and 1-based). [1]
rules:
template-injection:
ignore:
- ci.yml:100 # ignore any column on line 100
- tests.yml # ignore entire file
- pypi.yml:12:10 # ignore a specific line+columnNote: composite-action findings can’t currently be ignored via zizmor.yml; use inline comments for those. [1]
Ignore via inline comments (one-off suppression)
You can ignore an individual finding inline with:
# zizmor: ignore[rulename]- multiple rules:
# zizmor: ignore[rule1,rule2][2]
Per-rule options (when supported)
Some audits expose additional settings under:
rules:
unpinned-uses:
config: { ... }(Only some rules are configurable; it depends on the audit.) [1]
“Configuration schema”
As of zizmor v1.20.0, zizmor’s configuration has an official JSON schema (intended to be available via SchemaStore). [3]
Sources: [1] [2] [3]
🏁 Script executed:
# Search for zizmor repository and configuration documentation
# First, let's look for any zizmor-related files in the current repository
git ls-files | head -20Repository: Aureliolo/synthorg
Length of output: 684
🌐 Web query:
zizmor dependabot-cooldown rule documentation
💡 Result:
zizmor’s dependabot-cooldown audit is documented on the Audit Rules page under the “dependabot-cooldown” section. It flags Dependabot configs that omit cooldown or set an insufficient cooldown.default-days. [1]
- Docs (direct):
https://docs.zizmor.sh/audits/#dependabot-cooldown[1] - Config knob:
rules.dependabot-cooldown.config.days(minimum acceptablecooldown.default-days; default: 7) [1] - Suggested remediation (example):
[1]
cooldown: default-days: 7
For the underlying Dependabot cooldown syntax/options, see GitHub’s docs section “Setting up a cooldown period for dependency updates.” [2]
Sources: [1] https://docs.zizmor.sh/audits/ , [2] https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/optimizing-pr-creation-version-updates
🏁 Script executed:
# Search for zizmor repository structure to understand rule definitions
curl -s https://api.github.com/repos/nozaq/zizmor/contents | head -50 2>/dev/null || echo "Cannot access GitHub API"Repository: Aureliolo/synthorg
Length of output: 341
Replace the unsupported zizmor rule field.
action: skip is not valid here; zizmor rule configuration only accepts disable, ignore, or config. This causes the workflow to fail parsing .zizmor.yml. Use disable: true to turn off the dependabot-cooldown rule entirely.
🧰 Tools
🪛 GitHub Actions: Workflow Security
[error] 5-5: Zizmor configuration error: unknown field action, expected one of disable, ignore, config at line 5 column 5 in .zizmor.yml.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.zizmor.yml around lines 4 - 5, The .zizmor.yml uses an unsupported rule
field "action: skip" for the dependabot-cooldown rule; replace that line with
"disable: true" so the rule becomes disabled (update the dependabot-cooldown
entry to use disable: true instead of action: skip).
| /* Disclaimer banner */ | ||
| .rest-api-banner { | ||
| padding: 0.75rem 1.5rem; | ||
| font-size: 0.85rem; | ||
| opacity: 0.7; | ||
| border-bottom: 1px solid var(--md-default-fg-color--lightest); | ||
| flex-shrink: 0; | ||
| } | ||
| .rest-api-banner code { | ||
| background: var(--md-code-bg-color); | ||
| padding: 0.1rem 0.4rem; | ||
| border-radius: 0.2rem; | ||
| font-size: 0.8rem; | ||
| } |
There was a problem hiding this comment.
Avoid reducing banner contrast with container opacity.
Applying opacity to the whole banner dims all text and code chips. For a page-level disclaimer, this is easy to miss in low-contrast themes.
Proposed fix
.rest-api-banner {
padding: 0.75rem 1.5rem;
font-size: 0.85rem;
- opacity: 0.7;
+ color: var(--md-default-fg-color--light);
border-bottom: 1px solid var(--md-default-fg-color--lightest);
flex-shrink: 0;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* Disclaimer banner */ | |
| .rest-api-banner { | |
| padding: 0.75rem 1.5rem; | |
| font-size: 0.85rem; | |
| opacity: 0.7; | |
| border-bottom: 1px solid var(--md-default-fg-color--lightest); | |
| flex-shrink: 0; | |
| } | |
| .rest-api-banner code { | |
| background: var(--md-code-bg-color); | |
| padding: 0.1rem 0.4rem; | |
| border-radius: 0.2rem; | |
| font-size: 0.8rem; | |
| } | |
| /* Disclaimer banner */ | |
| .rest-api-banner { | |
| padding: 0.75rem 1.5rem; | |
| font-size: 0.85rem; | |
| color: var(--md-default-fg-color--light); | |
| border-bottom: 1px solid var(--md-default-fg-color--lightest); | |
| flex-shrink: 0; | |
| } | |
| .rest-api-banner code { | |
| background: var(--md-code-bg-color); | |
| padding: 0.1rem 0.4rem; | |
| border-radius: 0.2rem; | |
| font-size: 0.8rem; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/overrides/rest-api.html` around lines 31 - 44, The .rest-api-banner
currently uses a container-level opacity which dims all children; remove the
opacity property from .rest-api-banner and instead lower perceived contrast by
adjusting only the background or border color (e.g., use a semi-transparent
background color or color variable with alpha) while keeping text and inline
.rest-api-banner code at full opacity; ensure .rest-api-banner code retains its
own background and font-color so code chips remain fully legible.
| <div class="rest-api-banner"> | ||
| This is a static snapshot of the OpenAPI schema. | ||
| When the server is running, interactive docs are available at | ||
| <code>/docs/api</code> (Scalar) and <code>/docs</code> (OpenAPI JSON). | ||
| </div> | ||
|
|
||
| <script | ||
| id="api-reference" | ||
| data-url="../_generated/openapi.json" | ||
| data-configuration='{"theme": "default", "layout": "modern"}' | ||
| ></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.48.5"></script> |
There was a problem hiding this comment.
Add a fallback path to the bundled schema.
If JS is disabled or jsDelivr is blocked, this page loses its primary content even though the static openapi.json is present in the build. Expose that file directly so the snapshot stays usable.
Proposed fix
<div class="rest-api-banner">
This is a static snapshot of the OpenAPI schema.
+ Download the bundled snapshot at <a href="../_generated/openapi.json"><code>_generated/openapi.json</code></a>.
When the server is running, interactive docs are available at
<code>/docs/api</code> (Scalar) and <code>/docs</code> (OpenAPI JSON).
</div>
+
+<noscript>
+ <div class="rest-api-banner">
+ JavaScript is required to render Scalar here. You can still use
+ <a href="../_generated/openapi.json"><code>_generated/openapi.json</code></a>.
+ </div>
+</noscript>
<script
id="api-reference"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/overrides/rest-api.html` around lines 47 - 58, The page relies on the
dynamic `@scalar/api-reference` script (script element with id "api-reference" and
data-url="../_generated/openapi.json") and currently has no non-JS fallback; add
a simple noscript fallback that exposes the bundled OpenAPI JSON (the same
../_generated/openapi.json) as either a direct link and/or inline preformatted
JSON so users without JS or blocked CDN can still access the static schema;
ensure the fallback references the same data-url value and is placed near the
existing api-reference script element.
|
Closing to create a fresh PR with the final implementation (standalone Scalar UI page + all review fixes). |
Summary
openapi.jsonschemascripts/export_openapi.pyexports the Litestar app's OpenAPI schema todocs/_generated/openapi.jsonpages.yml,pages-preview.yml) to export OpenAPI schema before MkDocs buildvalue→pattern) and addno-future-annotationshookify rulescripts/**/*.py(allowprintand deferred imports)Test plan
uv run python scripts/export_openapi.pygeneratesdocs/_generated/openapi.jsonuv run mkdocs build --strictsucceeds with the generated schemaReview coverage
Pre-reviewed by 1 agent (docs-consistency), 12 findings addressed (2 CRITICAL, 5 MAJOR, 2 MEDIUM, 1 MINOR, 2 SUGGESTION)