feat: add PR preview deployments via Cloudflare Pages#302
Conversation
- Add pages-preview.yml workflow that builds site on PRs and deploys to Cloudflare Pages with a "Development Preview" banner injected into all HTML pages. Each PR gets a unique preview URL posted as a comment. - Fix MkDocs strict-mode failures: replace 3 broken relative links in getting_started.md with GitHub URLs, fix docstring indentation in messenger.py (griffe warning). - Document PR Preview workflow in CLAUDE.md.
Pre-reviewed by 7 agents, 7 findings addressed: - Fix expression injection via github.head_ref (use env var) - Fix expression injection via deployment-url output (use process.env) - Add fork PR guard to skip deploy when secrets unavailable - Remove unused artifact-uploaded output - Use lambda in re.sub to prevent backslash misfire - Port inline comments from pages.yml for consistency - Fix CLAUDE.md wording about deploy job behavior
- Add concurrency group to cancel stale preview builds on rapid pushes - Paginate listComments to avoid missing existing preview comment - Use PR head SHA instead of merge commit SHA in preview comment
- Rename project to synthorg-pr-preview - Use PR number in preview URL instead of branch name - Replace wrangler-action with run step (fixes env var expansion) - Remove unused deployments:write permission - Update CLAUDE.md with new project name and concurrency docs
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Files
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a PR Preview GitHub Actions workflow to build MkDocs and Astro sites, merge outputs, inject a dynamic PR banner, upload and deploy previews to Cloudflare Pages, and clean up on PR close. Also adds CI/security workflows and documentation updates (site nav, new user guide, link fixes, minor docstring formatting). Changes
Sequence Diagram(s)sequenceDiagram
participant PR as "Pull Request"
participant GH as "GitHub Actions"
participant Runner as "Actions Runner"
participant MkDocs as "MkDocs (Python)"
participant Astro as "Astro (Node)"
participant Artifact as "Artifact Store"
participant Wrangler as "wrangler"
participant CF as "Cloudflare Pages"
participant PRComment as "GitHub PR Comment"
PR->>GH: open/update PR -> trigger pages-preview.yml
GH->>Runner: run Build Preview
Runner->>MkDocs: setup Python, build mkdocs site
Runner->>Astro: setup Node, build astro site
MkDocs-->>Runner: produced MkDocs output
Astro-->>Runner: produced Astro output
Runner->>Runner: merge outputs (guard against overwrite), inject PR-number banner into HTML
Runner->>Artifact: upload `preview-site` artifact
GH->>Runner: run Deploy Preview (download artifact)
Runner->>Wrangler: deploy artifact to Cloudflare Pages
Wrangler->>CF: create deployment -> returns preview URL
Runner->>PRComment: post or update PR comment with preview URL and short SHA
Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Possibly related issues
Possibly related PRs
🚥 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 introduces a new workflow for generating and deploying preview environments for pull requests using Cloudflare Pages. It also incorporates several security enhancements, bug fixes, and documentation updates to improve the overall development process. 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
|
Preview DeploymentURL: https://pr-302.synthorg-pr-preview.pages.dev Built from commit |
There was a problem hiding this comment.
Code Review
This pull request introduces a new GitHub workflow for deploying PR previews to Cloudflare Pages, along with related documentation updates and minor fixes. The implementation is well-structured and includes security hardening measures. My review includes two suggestions for improving the documentation's readability and maintainability. One suggestion is to format a long descriptive line in CLAUDE.md for better readability. The other is to adjust the linking strategy in getting_started.md to ensure documentation links are version-correct across different branches and tags by making the linked files part of the documentation build.
CLAUDE.md
Outdated
|
|
||
| - **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 | ||
| - **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`. Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets. Build job runs regardless (catches build failures); deploy job skips on fork PRs (no secrets access). Concurrency group cancels stale builds on rapid pushes. |
There was a problem hiding this comment.
This line is very long (over 450 characters), which makes it difficult to read and maintain. For better readability, consider breaking this down into a nested list that details the different aspects of the PR Preview workflow.
| - **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`. Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets. Build job runs regardless (catches build failures); deploy job skips on fork PRs (no secrets access). Concurrency group cancels stale builds on rapid pushes. | |
| - **PR Preview**: `.github/workflows/pages-preview.yml` | |
| - Builds site on PRs (same path triggers as Pages), injects a "Development Preview" banner, and 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`. | |
| - Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets. | |
| - The build job runs on all PRs to catch build failures, while the deploy job skips on fork PRs (no secrets access). | |
| - A concurrency group is used to cancel stale builds on rapid pushes. |
docs/getting_started.md
Outdated
| - [CONTRIBUTING.md](https://github.com/Aureliolo/synthorg/blob/main/.github/CONTRIBUTING.md) — branch, commit, and PR workflow | ||
| - [CLAUDE.md](https://github.com/Aureliolo/synthorg/blob/main/CLAUDE.md) — code conventions and quick command reference | ||
| - [DESIGN_SPEC.md](https://github.com/Aureliolo/synthorg/blob/main/DESIGN_SPEC.md) — full high-level design specification |
There was a problem hiding this comment.
Using absolute URLs pointing to the main branch can be problematic for documentation, as viewers on other branches or tags will be directed to content that may not match their version.
A more robust approach is to make these documentation assets part of the mkdocs build. You can copy these files into the docs directory during your CI build process and then use standard relative links. This ensures the documentation is self-contained and version-correct.
For example, you could add a step in your .github/workflows/pages-preview.yml and .github/workflows/pages.yml workflows:
- name: Copy root markdown files to docs
run: |
cp .github/CONTRIBUTING.md docs/
cp CLAUDE.md docs/
cp DESIGN_SPEC.md docs/With that change, you can update the links in this file to be relative, as suggested.
| - [CONTRIBUTING.md](https://github.com/Aureliolo/synthorg/blob/main/.github/CONTRIBUTING.md) — branch, commit, and PR workflow | |
| - [CLAUDE.md](https://github.com/Aureliolo/synthorg/blob/main/CLAUDE.md) — code conventions and quick command reference | |
| - [DESIGN_SPEC.md](https://github.com/Aureliolo/synthorg/blob/main/DESIGN_SPEC.md) — full high-level design specification | |
| - [CONTRIBUTING.md](CONTRIBUTING.md) — branch, commit, and PR workflow | |
| - [CLAUDE.md](CLAUDE.md) — code conventions and quick command reference | |
| - [DESIGN_SPEC.md](DESIGN_SPEC.md) — full high-level design specification |
Greptile SummaryThis PR adds a complete PR preview deployment pipeline via Cloudflare Pages, including a The workflow demonstrates strong security hygiene: SHA-pinned actions, Key outstanding concerns:
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant GH as GitHub
participant Build as build job
participant Deploy as deploy-preview job
participant Cleanup as cleanup-preview job
participant CF as Cloudflare Pages
participant PR as PR Comment
Note over GH,PR: On opened / synchronize / reopened
GH->>Build: trigger (action != closed)
Build->>Build: checkout @ head.sha
Build->>Build: mkdocs build --strict → _site/docs/
Build->>Build: npm ci + astro build → site/dist/
Build->>Build: merge: cp site/dist/. _site/
Build->>Build: inject preview banner (Python)
Build->>GH: upload-artifact preview-site (v7)
GH->>Deploy: trigger (needs: build, non-fork)
Deploy->>GH: download-artifact preview-site (v8 ⚠️ mismatch)
Deploy->>CF: wrangler pages deploy --branch=pr-N
CF-->>Deploy: deployment live at pr-N.synthorg-pr-preview.pages.dev
Deploy->>PR: create/update preview comment (paginated search)
Note over GH,PR: On closed
GH->>Cleanup: trigger (action == closed, non-fork)
Cleanup->>PR: delete preview comment (paginated search)
Cleanup->>CF: DELETE deployments for branch pr-N (per_page=100)
Note over Cleanup,CF: API errors silently swallowed by || true ⚠️
Last reviewed commit: c05a276 |
| - name: Upload preview artifact | ||
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 |
There was a problem hiding this comment.
Artifact upload/download major-version mismatch
actions/upload-artifact is pinned to # v7 but actions/download-artifact (line 121) is pinned to # v8. GitHub's artifact service uses a different internal format per major version — uploading with v7 and downloading with v8 (or vice versa) will result in the download step failing with "Artifact not found" or a format incompatibility error.
Both actions should reference the same major version. Pin both to the same version (typically the latest: both at v4, or both at whichever major version you intend):
| - name: Upload preview artifact | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 | |
| - name: Upload preview artifact | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 |
Fix: Update the
download-artifactpin on line 121 to the same v7 SHA/tag used here, or update both to a consistent version.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 104-105
Comment:
**Artifact upload/download major-version mismatch**
`actions/upload-artifact` is pinned to `# v7` but `actions/download-artifact` (line 121) is pinned to `# v8`. GitHub's artifact service uses a different internal format per major version — uploading with v7 and downloading with v8 (or vice versa) will result in the download step failing with "Artifact not found" or a format incompatibility error.
Both actions should reference the same major version. Pin both to the same version (typically the latest: both at v4, or both at whichever major version you intend):
```suggestion
- name: Upload preview artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
```
> **Fix:** Update the `download-artifact` pin on line 121 to the same v7 SHA/tag used here, or update both to a consistent version.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
Adds a PR-preview deployment pipeline for the docs + site build, enabling per-PR Cloudflare Pages previews, while also fixing a few documentation/build strictness issues.
Changes:
- Added
.github/workflows/pages-preview.ymlto build MkDocs + Astro on PRs, inject a “Development Preview” banner, deploy to Cloudflare Pages, and comment the preview URL on the PR. - Fixed broken “Next Steps” links in
docs/getting_started.mdto satisfy strict MkDocs builds. - Updated CI documentation in
CLAUDE.mdand adjusted a docstring indentation inmessenger.py.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
.github/workflows/pages-preview.yml |
New PR workflow to build, deploy, and comment Cloudflare Pages preview URLs. |
docs/getting_started.md |
Replaces relative links with absolute GitHub links to avoid MkDocs strict link failures. |
CLAUDE.md |
Documents the new PR Preview workflow and its operational details. |
src/ai_company/communication/messenger.py |
Docstring indentation adjustment related to MkDocstrings/Griffe parsing/rendering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - The bus is shut down while waiting. | ||
| - The subscription is cancelled via :meth:`unsubscribe` | ||
| while a ``receive()`` call is in flight. | ||
| while a ``receive()`` call is in flight. |
There was a problem hiding this comment.
Docstring list continuation is over-indented compared to the corresponding docstring in communication/bus_memory.py (which uses a 2-space continuation under the bullet). With the extra indentation here, some doc renderers treat the continuation as a nested block/code block. Align the while a ``receive()``... line indentation with the bullet text continuation (as in bus_memory.py).
| while a ``receive()`` call is in flight. | |
| while a ``receive()`` call is in flight. |
| if: github.event.pull_request.head.repo.full_name == github.repository | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read |
There was a problem hiding this comment.
Comment preview URL uses github.rest.issues.* endpoints to list/update/create an issue comment, but this job only requests pull-requests: write. Grant issues: write (or replace pull-requests: write with issues: write) so the GITHUB_TOKEN can manage PR comments reliably.
| contents: read | |
| contents: read | |
| issues: write |
.github/workflows/pages-preview.yml
Outdated
| npm i --no-save wrangler@3 > /dev/null 2>&1 | ||
| DEPLOY_OUTPUT=$(npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" 2>&1) | ||
| echo "$DEPLOY_OUTPUT" | ||
| DEPLOY_URL=$(echo "$DEPLOY_OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1) |
There was a problem hiding this comment.
URL extraction uses grep ... | tail -1 under the default bash -eo pipefail runner shell; if grep finds no match, it exits 1 and the whole deploy step fails before the later “skip comment if no URL” logic can run. Make the extraction tolerant (e.g., || true) or avoid parsing by constructing the deterministic Pages URL from PR_NUMBER/project name or using a structured/JSON output flag if available.
| DEPLOY_URL=$(echo "$DEPLOY_OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1) | |
| DEPLOY_URL="https://pr-${PR_NUMBER}.synthorg-pr-preview.pages.dev" |
.github/workflows/pages-preview.yml
Outdated
| - name: Download preview artifact | ||
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | ||
| with: | ||
| name: preview-site | ||
| path: _site | ||
|
|
||
| - name: Deploy to Cloudflare Pages | ||
| id: deploy | ||
| env: | ||
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| npm i --no-save wrangler@3 > /dev/null 2>&1 | ||
| DEPLOY_OUTPUT=$(npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" 2>&1) | ||
| echo "$DEPLOY_OUTPUT" |
There was a problem hiding this comment.
Deploy to Cloudflare Pages uses npm/npx but the deploy-preview job doesn’t set up/pin Node like the build job does. Add an explicit actions/setup-node step (matching the Node version used in build) to keep deployments reproducible and avoid failures if the runner image changes.
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 @.github/workflows/pages-preview.yml:
- Around line 151-157: The current comment body uses the generic heading "##
Preview Deployment" which can clash with other comments; modify the constructed
comment (the const body that contains '## Preview Deployment') to append a
unique, non-visible marker string (e.g., an HTML comment like <!--
preview-deployment:unique-id -->) and then change the logic that
searches/upserts comments to only match existing comments that both include that
marker and are authored by the Actions bot (check comment.body.includes(marker)
&& comment.user?.login === 'github-actions[bot]' or equivalent); this ensures
updates target only the workflow-created comment.
- Around line 136-137: Check if DEPLOY_URL (the variable extracted with grep) is
empty before echoing to GITHUB_OUTPUT and fail the job if it is; update the
workflow step that sets DEPLOY_URL (the lines that assign DEPLOY_URL and echo
"deployment-url=${DEPLOY_URL}" >> "$GITHUB_OUTPUT") to validate the value and
call exit 1 (or use GitHub Actions ::error and exit) when DEPLOY_URL is blank so
the job fails instead of proceeding with an empty deployment URL.
🪄 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: e7198302-bba1-43a1-b02e-c04861af2575
📒 Files selected for processing (4)
.github/workflows/pages-preview.ymlCLAUDE.mddocs/getting_started.mdsrc/ai_company/communication/messenger.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 (3)
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule
Files:
.github/workflows/pages-preview.yml
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:syntax without parentheses (PEP 758) — ruff enforces this on Python 3.14
Type hints required on all public functions; mypy strict mode
Google-style docstrings required on public classes and functions (enforced by ruff D rules)
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Files:
src/ai_company/communication/messenger.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement. Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention andcopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields — including optional and tuple variants — instead of manual whitespace validators.
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task.
Handle errors explicitly, never silently swallow
Validate at system boundaries (user input, external APIs, config files)
Every module with business logic MUST have:from ai_company.observability import get_loggerthenlogger = get_logger(__name__). Never useimport logging/logging.getLogger()/print()in application code.
Variable name for logger must always belogger(not_logger, notlog)
Always use event name constants from domain-specific modules underai_company.observability.events(e.g.PROVIDER_CALL_STARTfromevents.provider,BUDGET_RECORD_ADDEDfromevents.budget, etc.). Import directly:from ai_company.observability.events.<domain> import EVENT_CONSTANT
Always use structured logging kwargs: `logger.info(EVENT, key=va...
Files:
src/ai_company/communication/messenger.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Always read `DESIGN_SPEC.md` before implementing any feature or planning any issue
Applied to files:
docs/getting_started.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Applied to files:
.github/workflows/pages-preview.ymlCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Applied to files:
.github/workflows/pages-preview.ymlCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Applied to files:
.github/workflows/pages-preview.ymlCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Applied to files:
.github/workflows/pages-preview.ymlCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.
Applied to files:
CLAUDE.md
🔇 Additional comments (4)
src/ai_company/communication/messenger.py (1)
294-294: Docstring continuation indentation fix looks correct.This keeps the wrapped bullet item readable and tooling-friendly without changing behavior.
docs/getting_started.md (1)
153-155: Good fix for strict docs builds.Using absolute GitHub URLs here avoids broken relative links to files outside the docs tree.
.github/workflows/pages-preview.yml (1)
13-18: Nice security posture for PR workflows.Least-privilege permissions plus the fork-PR deploy guard are strong defaults for secret safety.
Also applies to: 114-119
CLAUDE.md (1)
176-176: CI documentation update is clear and useful.The new PR Preview description captures trigger behavior, secret requirements, and fork-skip semantics well.
…ewers - Revert messenger.py docstring over-indentation (comment-analyzer, Copilot) - Add pages-preview.yml to DESIGN_SPEC.md file tree (docs-consistency) - Harden deploy URL extraction with || true and explicit validation (Greptile, CodeRabbit, Copilot) - Add HTML comment marker + bot-author check for comment upsert (CodeRabbit) - Pin wrangler to 3.114.17 for reproducibility (Greptile) - Only suppress stdout (not stderr) for npm install (Greptile) - Add Node.js setup step to deploy-preview job (Copilot) - Break long CLAUDE.md CI line into nested list (Gemini)
Griffe expects 4*2=8 spaces relative to docstring body (16 absolute) for bullet continuation lines. The comment-analyzer and Copilot advice to use 14 spaces was incorrect — griffe enforced in MkDocs strict mode.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| persist-credentials: false |
There was a problem hiding this comment.
actions/checkout on pull_request defaults to checking out the PR merge commit (github.sha). In this workflow the PR comment reports pull_request.head.sha, which can diverge from what was actually built/deployed. To keep the deployed artifact and reported SHA consistent, either (a) set ref on the checkout step to github.event.pull_request.head.sha, or (b) keep building the merge commit but update the comment to report context.sha (and/or both SHAs).
| persist-credentials: false | |
| persist-credentials: false | |
| ref: ${{ github.event.pull_request.head.sha }} |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 141-146: Replace the fragile grep-based extraction of DEPLOY_URL
with constructing the stable branch alias directly: remove the DEPLOY_OUTPUT
grep block that sets DEPLOY_URL and instead set
DEPLOY_URL="https://pr-${PR_NUMBER}.synthorg-pr-preview.pages.dev" (ensuring
PR_NUMBER is available from the workflow env or inputs), then write that value
to "$GITHUB_OUTPUT" as before; update any references to DEPLOY_URL in this job
to use the new constructed URL.
🪄 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: 8c180e27-3e7a-4d7d-8cea-9ca47e0437db
📒 Files selected for processing (3)
.github/workflows/pages-preview.ymlCLAUDE.mdDESIGN_SPEC.md
📜 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: Greptile Review
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule
Files:
.github/workflows/pages-preview.yml
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Local docs preview: run `uv run mkdocs serve` (http://127.0.0.1:8000)
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Applied to files:
DESIGN_SPEC.mdCLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Applied to files:
DESIGN_SPEC.mdCLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
Applied to files:
DESIGN_SPEC.mdCLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Applied to files:
DESIGN_SPEC.mdCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule
Applied to files:
DESIGN_SPEC.mdCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Applied to files:
DESIGN_SPEC.mdCLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14
Applied to files:
DESIGN_SPEC.mdCLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Never create a PR directly — `gh pr create` is blocked by hookify. Always use `/pre-pr-review` to create PRs — it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Dependency review: license allow-list (permissive only), PR comment summaries
Applied to files:
.github/workflows/pages-preview.yml
…, cleanup - Checkout PR head SHA instead of merge commit (Copilot #13) - Use deterministic branch alias URL instead of grep-parsed deploy URL (CodeRabbit #14) - Add cleanup job to delete Cloudflare deployments on PR close (Greptile #15) - Skip build/deploy jobs on PR close events - Update CLAUDE.md with new workflow capabilities
Split documentation into two tracks: - User Guide: install, configure, run (for users of the framework) - Developer Setup: clone, test, lint, IDE (for contributors) Both linked side-by-side on the docs home page.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 210-222: The cleanup currently only reads the first page and
swallows errors; update the deployment listing to iterate pages (use a page loop
with a page query param and per_page to collect all matching .result[] ids for
the BRANCH into DEPLOYMENTS), remove the blanket "|| true" on the curl/jq list
so failures are detectable, and when deleting each DEPLOY_ID call the DELETE
endpoint without "|| true" and check the HTTP response body/status: if the
delete fails log/exit with the error details (including response body) so failed
deletions are surfaced; additionally detect and handle the Cloudflare "cannot
delete latest deployment" error case (log it with DEPLOY_ID and suggest skipping
or retrying after creating a new deployment) while continuing to paginate and
attempt other deletions using CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.
🪄 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: e1402680-a146-4c5c-be7d-ea4df6655d4c
📒 Files selected for processing (2)
.github/workflows/pages-preview.ymlCLAUDE.md
📜 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
📓 Path-based instructions (1)
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule
Files:
.github/workflows/pages-preview.yml
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Never create a PR directly — `gh pr create` is blocked by hookify. Always use `/pre-pr-review` to create PRs — it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.
Applied to files:
CLAUDE.md.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Dependency review: license allow-list (permissive only), PR comment summaries
Applied to files:
.github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no 'out of scope' skipping.
Applied to files:
.github/workflows/pages-preview.yml
.github/workflows/pages-preview.yml
Outdated
| DEPLOYMENTS=$(curl -sf \ | ||
| "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments" \ | ||
| -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | ||
| | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id") || true | ||
| if [ -z "$DEPLOYMENTS" ]; then | ||
| echo "No deployments found for branch ${BRANCH}" | ||
| exit 0 | ||
| fi | ||
| for DEPLOY_ID in $DEPLOYMENTS; do | ||
| echo "Deleting deployment ${DEPLOY_ID}..." | ||
| curl -sf -X DELETE \ | ||
| "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments/${DEPLOY_ID}?force=true" \ | ||
| -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" || true |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Cloudflare Pages deployments API pagination behavior documentation
💡 Result:
Cloudflare Pages “Get deployments” (GET /accounts/{account_id}/pages/projects/{project_name}/deployments) uses page-based pagination:
- Request pagination params:
page(which page to fetch) andper_page(items per page). It also supportsenv=production|previewto filter which deployments are listed. [1] - Response pagination metadata is in
result_info, which includes at leastcount,page,per_page, andtotal_count(total deployments matching the query). [1] - This matches Cloudflare’s general API pagination model: use
page=xandper_page=xx, and read the pagination fields fromresult_info. [2]
Sources: [1] Cloudflare API Reference – Pages “Get deployments”; [2] Cloudflare Fundamentals – “Make API calls” (Pagination section.
🌐 Web query:
Cloudflare Pages API delete deployment latest branch restriction
💡 Result:
Cloudflare Pages does have a Delete Deployment endpoint:
DELETE /client/v4/accounts/{account_id}/pages/projects/{project_name}/deployments/{deployment_id}(requires Pages Write) [1]
But there’s an important restriction:
- You cannot delete the latest (most recent) deployment for any given branch. Cloudflare’s own Pages API example for “deleting old deployments” explicitly notes: “The latest deployment for a branch cannot be deleted.” [2]
Implication / workaround:
- If you need to delete what is currently the latest deployment on a branch, you must first make another newer deployment on that same branch (so the one you want to remove is no longer the latest), and then delete it via the endpoint above. [2][1]
Sources:
[1] Cloudflare API – Pages Deployments: Delete Deployment
[2] Cloudflare Pages docs – REST API (example: deleting old deployments; latest-per-branch can’t be deleted)
🏁 Script executed:
# First, let's examine the full workflow file to understand the context
cd .github/workflows && ls -la pages-preview.yml && wc -l pages-preview.ymlRepository: Aureliolo/synthorg
Length of output: 150
🏁 Script executed:
# Let's look at the cleanup job section more carefully, including context around lines 210-222
sed -n '200,230p' .github/workflows/pages-preview.ymlRepository: Aureliolo/synthorg
Length of output: 1376
🏁 Script executed:
# Check if there are any pagination parameters or loop handling in the entire cleanup job
sed -n '180,230p' .github/workflows/pages-preview.ymlRepository: Aureliolo/synthorg
Length of output: 1979
Cleanup cannot guarantee the preview is removed.
Cloudflare's Pages deployments endpoint uses page-based pagination, but the cleanup script never requests pagination parameters and only processes the first page. Additionally, Cloudflare's API explicitly prevents deletion of the latest deployment for any given branch. Since both the list and delete operations suppress errors with || true, failed deletions go unnoticed, leaving closed PRs with orphaned preview deployments while the job reports success.
To address this, surface failed deletions explicitly and implement pagination handling. Longer term, a different strategy is needed to handle the latest-deployment restriction—either by first triggering a new deployment on the branch (making the old one no longer latest) before deletion, or by finding an alternative cleanup approach.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/pages-preview.yml around lines 210 - 222, The cleanup
currently only reads the first page and swallows errors; update the deployment
listing to iterate pages (use a page loop with a page query param and per_page
to collect all matching .result[] ids for the BRANCH into DEPLOYMENTS), remove
the blanket "|| true" on the curl/jq list so failures are detectable, and when
deleting each DEPLOY_ID call the DELETE endpoint without "|| true" and check the
HTTP response body/status: if the delete fails log/exit with the error details
(including response body) so failed deletions are surfaced; additionally detect
and handle the Cloudflare "cannot delete latest deployment" error case (log it
with DEPLOY_ID and suggest skipping or retrying after creating a new deployment)
while continuing to paginate and attempt other deletions using
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.
… dead code - Delete preview comment on PR close before deleting deployments (Greptile #16) - Add pull-requests: write to cleanup job for comment deletion - Add per_page=100 to Cloudflare deployments API call (Greptile #17, CodeRabbit #19) - Surface failed deletions with warnings instead of silent || true - Note Cloudflare restriction: latest deployment per branch cannot be deleted - Remove unreachable if (!url) guard — PREVIEW_URL is always set (Greptile #18)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write |
There was a problem hiding this comment.
deploy-preview uses github.rest.issues.* (listComments/updateComment/createComment), but the job permissions only grant pull-requests: write. With top-level permissions: {}, this can lead to 403s because issue comments typically require issues: write. Add issues: write (and drop pull-requests: write if it’s no longer needed) so the preview URL comment can be created/updated reliably.
| pull-requests: write | |
| issues: write |
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write |
There was a problem hiding this comment.
cleanup-preview deletes the PR comment via github.rest.issues.deleteComment, but the job permissions only grant pull-requests: write. With top-level permissions: {}, this may fail due to missing issues: write. Grant issues: write here as well to ensure cleanup can remove the preview comment.
| pull-requests: write | |
| pull-requests: write | |
| issues: write |
| updated = re.sub(r"(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1) | ||
| if updated != text: | ||
| f.write_text(updated, encoding="utf-8") | ||
| count += 1 | ||
| print(f"Injected preview banner into {count} HTML files") |
There was a problem hiding this comment.
The banner injection step logs how many HTML files were updated, but it never fails the build if count ends up as 0 (e.g., if the <body> regex doesn’t match). Since the banner is a key safety/UX signal for previews, consider failing the job when no files were modified (and/or make the <body> match case-insensitive) so a regression doesn’t silently ship an un-bannered preview.
| updated = re.sub(r"(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1) | |
| if updated != text: | |
| f.write_text(updated, encoding="utf-8") | |
| count += 1 | |
| print(f"Injected preview banner into {count} HTML files") | |
| updated = re.sub(r"(?i)(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1) | |
| if updated != text: | |
| f.write_text(updated, encoding="utf-8") | |
| count += 1 | |
| print(f"Injected preview banner into {count} HTML files") | |
| if count == 0: | |
| raise SystemExit("No HTML files were modified with the preview banner; failing build to avoid un-bannered preview.") |
| - name: Set up Node.js | ||
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | ||
| with: | ||
| node-version: "22" | ||
|
|
There was a problem hiding this comment.
npm dependency cache not configured in either Node.js setup step
Both setup-node calls in the workflow (here in build and again in deploy-preview at line 151) omit the cache input, so npm ci downloads Astro's entire dependency tree fresh on every PR push. For a preview workflow triggered on every synchronize event, this adds avoidable latency and bandwidth.
Adding cache: 'npm' (which keys on package-lock.json) is a one-liner:
| - name: Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: "22" | |
| - name: Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: "22" | |
| cache: 'npm' | |
| cache-dependency-path: site/package-lock.json |
The same change applies to the deploy-preview job's setup-node step at line 151. astral-sh/setup-uv similarly supports an enable-cache option that would cache Python package downloads between runs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 50-54
Comment:
**npm dependency cache not configured in either Node.js setup step**
Both `setup-node` calls in the workflow (here in `build` and again in `deploy-preview` at line 151) omit the `cache` input, so `npm ci` downloads Astro's entire dependency tree fresh on every PR push. For a preview workflow triggered on every `synchronize` event, this adds avoidable latency and bandwidth.
Adding `cache: 'npm'` (which keys on `package-lock.json`) is a one-liner:
```suggestion
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: site/package-lock.json
```
The same change applies to the `deploy-preview` job's `setup-node` step at line 151. `astral-sh/setup-uv` similarly supports an `enable-cache` option that would cache Python package downloads between runs.
How can I resolve this? If you propose a fix, please make it concise.- Truncate build timestamp to milliseconds (Safari rejects microsecond precision in Date constructor, causing "NaN ago" display) - Add timeout-minutes to all three jobs (10m build, 5m deploy, 5m cleanup)
| @@ -0,0 +1,3527 @@ | |||
| # SynthOrg - High-Level Design Specification | |||
There was a problem hiding this comment.
Duplicated design spec creates synchronization risk
docs/design_spec.md is a full 3,527-line copy of the root-level DESIGN_SPEC.md. CLAUDE.md explicitly designates DESIGN_SPEC.md as the authoritative source of truth (with a MANDATORY instruction to always read it before implementing features). Having an independent copy under docs/ means these two files can silently drift apart — a contributor updating the root spec won't necessarily update the docs copy, and vice versa.
A safer approach is to use a MkDocs docs_dir-relative symlink or to reconfigure mkdocs.yml to resolve the file from the repo root:
# mkdocs.yml — resolve the design spec from the repo root
# (requires docs_dir or an explicit file mapping)
nav:
- Design Specification: ../DESIGN_SPEC.mdOr, if MkDocs doesn't support ../ paths out of the box, a pre-build step that copies/symlinks the root file would keep a single source of truth instead of two diverging copies.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/design_spec.md
Line: 1
Comment:
**Duplicated design spec creates synchronization risk**
`docs/design_spec.md` is a full 3,527-line copy of the root-level `DESIGN_SPEC.md`. `CLAUDE.md` explicitly designates `DESIGN_SPEC.md` as the authoritative source of truth (with a `MANDATORY` instruction to always read it before implementing features). Having an independent copy under `docs/` means these two files can silently drift apart — a contributor updating the root spec won't necessarily update the docs copy, and vice versa.
A safer approach is to use a MkDocs `docs_dir`-relative symlink or to reconfigure `mkdocs.yml` to resolve the file from the repo root:
```yaml
# mkdocs.yml — resolve the design spec from the repo root
# (requires docs_dir or an explicit file mapping)
nav:
- Design Specification: ../DESIGN_SPEC.md
```
Or, if MkDocs doesn't support `../` paths out of the box, a pre-build step that copies/symlinks the root file would keep a single source of truth instead of two diverging copies.
How can I resolve this? If you propose a fix, please make it concise.| npm i --no-save wrangler@3.114.17 > /dev/null | ||
| npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" |
There was a problem hiding this comment.
npm install stdout suppression hides progress, consider redirecting to a log
npm i --no-save wrangler@3.114.17 > /dev/null discards stdout entirely, so if Wrangler takes an unexpectedly long time to install (large dependency tree, slow npm mirror, etc.) the step produces no visible output during that wait window. Stderr is still visible so genuine errors will surface, but a hung npm install would produce a silent, blank step log with no clue about what it is doing.
Consider redirecting to a step debug log file instead:
| npm i --no-save wrangler@3.114.17 > /dev/null | |
| npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" | |
| npm i --no-save wrangler@3.114.17 2>&1 | grep -v "^npm warn" || true | |
| npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" |
Or at minimum, only suppress the verbose npm progress lines:
npm i --no-save wrangler@3.114.17 --loglevel=errorThis keeps error output but hides the noisy download progress.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 165-166
Comment:
**`npm install` stdout suppression hides progress, consider redirecting to a log**
`npm i --no-save wrangler@3.114.17 > /dev/null` discards stdout entirely, so if Wrangler takes an unexpectedly long time to install (large dependency tree, slow npm mirror, etc.) the step produces no visible output during that wait window. Stderr is still visible so genuine errors will surface, but a hung `npm install` would produce a silent, blank step log with no clue about what it is doing.
Consider redirecting to a step debug log file instead:
```suggestion
npm i --no-save wrangler@3.114.17 2>&1 | grep -v "^npm warn" || true
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
```
Or at minimum, only suppress the verbose npm progress lines:
```bash
npm i --no-save wrangler@3.114.17 --loglevel=error
```
This keeps error output but hides the noisy download progress.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id") || true | ||
| if [ -z "$DEPLOYMENTS" ]; then | ||
| echo "No deployments found for branch ${BRANCH}" | ||
| exit 0 | ||
| fi | ||
| for DEPLOY_ID in $DEPLOYMENTS; do | ||
| echo "Deleting deployment ${DEPLOY_ID}..." |
There was a problem hiding this comment.
|| true silently swallows Cloudflare API authentication and network errors
The || true on the DEPLOYMENTS=$(curl -sf ... | jq ...) pipeline causes any API-level failure to look identical to "no deployments found." Specifically:
curl -sfexits non-zero if the HTTP status is 4xx or 5xx (wrong token, wrong account ID, project not found, rate-limited) or if the connection fails entirely- When that happens, the pipeline returns non-zero,
|| trueconverts it to success,$DEPLOYMENTSis empty, and the nextif [ -z "$DEPLOYMENTS" ]branch prints"No deployments found for branch pr-N"and exits 0
A Cloudflare API call with invalid credentials would appear as a successful cleanup with no deployments — the stale preview branch would silently remain, and there is no indication in the workflow logs that anything went wrong.
To distinguish "no deployments for this branch" from "API call failed":
RESPONSE=$(curl -sf \
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments?per_page=100" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}") || {
echo "::error::Cloudflare API request failed — check CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
exit 1
}
DEPLOYMENTS=$(echo "$RESPONSE" | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id")Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 265-271
Comment:
**`|| true` silently swallows Cloudflare API authentication and network errors**
The `|| true` on the `DEPLOYMENTS=$(curl -sf ... | jq ...)` pipeline causes any API-level failure to look identical to "no deployments found." Specifically:
- `curl -sf` exits non-zero if the HTTP status is 4xx or 5xx (wrong token, wrong account ID, project not found, rate-limited) **or** if the connection fails entirely
- When that happens, the pipeline returns non-zero, `|| true` converts it to success, `$DEPLOYMENTS` is empty, and the next `if [ -z "$DEPLOYMENTS" ]` branch prints `"No deployments found for branch pr-N"` and exits 0
A Cloudflare API call with invalid credentials would appear as a successful cleanup with no deployments — the stale preview branch would silently remain, and there is no indication in the workflow logs that anything went wrong.
To distinguish "no deployments for this branch" from "API call failed":
```bash
RESPONSE=$(curl -sf \
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments?per_page=100" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}") || {
echo "::error::Cloudflare API request failed — check CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
exit 1
}
DEPLOYMENTS=$(echo "$RESPONSE" | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id")
```
How can I resolve this? If you propose a fix, please make it concise.The deploy-pages action was pinned to a non-existent SHA (typo from PR #298), masked by upstream MkDocs build failures until PR #302 fixed them. Corrected to the actual v4.0.5 SHA. The preview cleanup silently swallowed Cloudflare API errors via curl -sf + || true, causing "No deployments found" even when deployments existed. Now surfaces HTTP errors, logs available branches on miss, and uses jq --arg for safer filtering.
…#304) ## Summary - **Fix deploy-pages SHA**: The `actions/deploy-pages` action in `pages.yml` was pinned to SHA `...53fd0d31` which doesn't exist. Corrected to `...b3c0c03e` (v4.0.5). This typo was introduced in PR #298 but was masked by MkDocs build failures — only surfaced after PR #302 fixed the build. - **Fix preview cleanup**: The Cloudflare deployment cleanup in `pages-preview.yml` silently swallowed API errors (`curl -sf` + `|| true`), causing PR #302's cleanup to report "No deployments found" while the deployment remained live. Now properly surfaces HTTP errors, logs available branches on miss for diagnostics, and uses `jq --arg` for safer filtering. ## Test plan - [ ] GitHub Pages workflow succeeds on merge (deploy step reaches GitHub Pages) - [ ] PR preview cleanup correctly lists and deletes Cloudflare deployments on PR close - [ ] Cleanup logs show diagnostic info if no matching deployments found
🤖 I have created a release *beep* *boop* --- ## [0.1.1](v0.1.0...v0.1.1) (2026-03-11) ### Features * add PR preview deployments via Cloudflare Pages ([#302](#302)) ([b73c45a](b73c45a)) ### Bug Fixes * correct deploy-pages SHA and improve preview cleanup reliability ([#304](#304)) ([584d64a](584d64a)) * harden API key hashing with HMAC-SHA256 and clean up legacy changelog ([#292](#292)) ([5e85353](5e85353)) * upgrade upload-pages-artifact to v4 and add zizmor workflow linting ([#299](#299)) ([2eac571](2eac571)) * use Cloudflare Pages API default per_page for pagination ([#305](#305)) ([9fec245](9fec245)) ### Documentation * remove milestone references and rebrand to SynthOrg ([#289](#289)) ([57a03e0](57a03e0)) * set up documentation site, release CI, and sandbox hardening ([#298](#298)) ([0dec9da](0dec9da)) * split DESIGN_SPEC.md into 7 focused design pages ([#308](#308)) ([9ea0788](9ea0788)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.com>
Summary
.github/workflows/pages-preview.yml— builds MkDocs + Astro on PRs, injects "Development Preview" banner, deploys to Cloudflare Pagespr-<number>.synthorg-pr-preview.pages.devpersist-credentials: false, least-privilege permissionsdocs/getting_started.md(MkDocs--strict), docstring indentation inmessenger.py(griffe warning)Review feedback incorporated
All valid findings from PR #300 reviewers (Gemini, Greptile, Copilot) addressed:
listCommentsto avoid missing existing comment (Copilot)deployments: writepermission (Copilot)Dismissed: absolute links to main in getting_started.md (intentional — files outside docs/ hierarchy)
Test plan
🤖 Generated with Claude Code