Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/hookify.function-length.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

Expand Down
18 changes: 9 additions & 9 deletions .claude/hookify.missing-logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 )"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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"

Comment on lines 27 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.

- field: file_content
operator: not_contains
value: "get_logger"
pattern: "get_logger"
action: warn
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment on lines 25 to 33
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
---

Expand Down
14 changes: 14 additions & 0 deletions .claude/hookify.no-future-annotations.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
environment: ci
permissions:
contents: read
strategy:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pages-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- "pyproject.toml"
- "uv.lock"
- "src/ai_company/**"
- "scripts/**"
- ".github/workflows/pages-preview.yml"

permissions: {}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- "pyproject.toml"
- "uv.lock"
- "src/ai_company/**"
- "scripts/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
pull-requests: write
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}
Expand All @@ -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' }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
5 changes: 5 additions & 0 deletions .zizmor.yml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +4 to +5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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: true

This 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+column

Note: 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 -20

Repository: 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 acceptable cooldown.default-days; default: 7) [1]
  • Suggested remediation (example):
    cooldown:
      default-days: 7
    [1]

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).

12 changes: 8 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down Expand Up @@ -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-<number>.synthorg-pr-preview.pages.dev`
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
2 changes: 1 addition & 1 deletion docs/api/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# API Reference
# Library Reference

Auto-generated reference documentation from source code docstrings.

Expand Down
3 changes: 2 additions & 1 deletion docs/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion docs/design/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
`/docs/api` (Scalar UI) and `/docs` (OpenAPI JSON) suffice.
`/api` (Scalar UI) and `/docs/openapi.json` (OpenAPI JSON) suffice.

Copilot uses AI. Check for mistakes.

### API Surface

Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

---
Expand Down
Empty file removed docs/overrides/.gitkeep
Empty file.
59 changes: 59 additions & 0 deletions docs/overrides/rest-api.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{% extends "main.html" %}

{% block content %}
<style>
/* Strip ALL MkDocs Material width constraints so Scalar fills the viewport */
.md-container,
.md-main,
.md-main__inner,
.md-content,
.md-content__inner {
max-width: 100%;
width: 100%;
margin: 0;
padding: 0;
}
/* Override the grid layout that constrains content width */
.md-main__inner {
display: block;
}
/* Hide MkDocs sidebar, TOC, and footer on this page */
.md-sidebar,
.md-footer {
display: none;
}
/* Scalar fills remaining viewport height below header */
.md-content__inner {
min-height: calc(100vh - 3.6rem);
display: flex;
flex-direction: column;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
}
/* 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;
}
Comment on lines +31 to +44
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
/* 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.

</style>

<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>
Comment on lines +47 to +58
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

{% endblock %}
5 changes: 5 additions & 0 deletions docs/rest-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
template: rest-api.html
---

# REST API Reference
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
39 changes: 39 additions & 0 deletions scripts/export_openapi.py
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +25 to +27
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

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)}")
Comment on lines +29 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 1
Prompt 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.

return 0


if __name__ == "__main__":
sys.exit(main())
Loading