Skip to content
Merged
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
22 changes: 22 additions & 0 deletions .claude/skills/babysit-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,28 @@ Render the full triage table only when there's something to fix.
- **Self-comments:** when scanning reviews and inline comments, exclude `synthorg-repo-bot[bot]` and your own GitHub username (resolve via `gh api user --jq .login` once and cache in `state.self_login`).
- **Outside-diff-range comments:** CodeRabbit embeds these in `<details>` blocks at the top of the review body when the affected lines are outside the diff. Parse them as actionable inline comments. They're NOT optional. (Same parser as `/aurelio-review-pr` Phase 4.)

#### Per-reviewer auto-clear behaviour (which reviews to dismiss, which to leave alone)

GitHub keeps every prior `CHANGES_REQUESTED` review attached to a PR until either (a) the reviewer submits a new review with `APPROVED` / `COMMENTED`, or (b) someone calls the dismissal API. Which path to take depends on whether the bot re-reviews on each push:

| Reviewer | Re-reviews on each commit? | Auto-clears its own stale `CHANGES_REQUESTED`? | Action when previous review is now stale |
|---|---|---|---|
| **CodeRabbit** (`coderabbitai[bot]`) | Yes | Yes, by submitting a new review with no actionable items on the new head | **Never call the dismissal API.** Post replies to its inline comments (or `@coderabbitai resolve` on the thread) and let the next CR review auto-clear the prior `CHANGES_REQUESTED`. Manual dismissal is wasted work AND erases reviewer context that humans use to trace the conversation. |
| **Gemini** (`gemini-code-assist[bot]`) | **No.** Only reviews on PR open; subsequent reviews require an explicit `/gemini review` command on the PR | No (without a re-review, its state is frozen on the head it first saw) | If Gemini left `CHANGES_REQUESTED` (rare; Gemini typically uses `COMMENTED` which doesn't block `reviewDecision`), and every cited finding has been addressed in subsequent commits, **dismissal is the right answer**. Gemini will not update on its own. Verify each finding is actually fixed against current code before dismissing. |
| **Other bots** (Copilot, Greptile, Socket Security, ...) | Varies; check the bot's docs or empirical behaviour on the current PR | Varies | Default to "reply, don't dismiss" unless the bot's documented behaviour confirms no auto-update. Err on the side of leaving the review attached so the audit trail is intact. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| **Human reviewers** | n/a | Never auto-clears | Don't dismiss without explicit operator consent. The right path is to address the feedback in a new commit and request a re-review. |

**Default rule:** if you don't know whether the reviewer auto-clears, **reply, don't dismiss**. The cost of an extra `CHANGES_REQUESTED` sitting in `reviewDecision` for a tick or two is low; the cost of dismissing a still-valid finding (or erasing a reviewer thread the next operator was about to read) is high.

**When you do dismiss** (Gemini-style frozen `CHANGES_REQUESTED`), call the API per-review with a `message` body that names (a) the commit SHA the review was attached to, (b) the inline findings it raised, (c) the commit(s) that resolved each one:

```bash
gh api -X PUT "repos/$OWNER_REPO/pulls/$PR/reviews/$REVIEW_ID/dismissals" \
-f message="Stale: review on commit <SHA>; finding(s) <summary> addressed in commit(s) <SHA list>. Dismissing because <bot> does not auto-re-review."
```

Log every dismissal in the round-history entry: `{round, action: "stale_review_dismissed", reviewer, review_id, original_head_sha, addressed_in_sha}`. Dismissals are auditable; never call the API without the entry.

### Mechanics

- **Never `durable: true`** on any cron primitive. Session-only. (`feedback_no_cloud_schedule.md`.)
Expand Down
107 changes: 107 additions & 0 deletions .github/workflows/lychee.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: Link Check (lychee)

# Markdown link-checker for README, every CLAUDE.md tier, and every
# Markdown under docs/. Configured via ``lychee.toml`` at the repo root,
# which is the same source of truth the local pre-push hook consumes.
# Strict preset: any non-200 response fails the run (rate-limit and
# anti-bot hosts are excluded by ``lychee.toml``).
#
# Install path: ``scripts/install_cli_tools.sh lychee`` is the single
# source of truth for the lychee binary (the local pre-push hook and
# this CI job use the byte-identical binary). The previous integration
# via ``lycheeverse/lychee-action`` was dropped because the action's
# release cadence trailed lychee's archive-layout change in v0.24
# (action issue #332 still open at the time of writing), and we already
# maintain a robust nested-archive-aware install in the script. The
# Renovate marker on ``LYCHEE_VERSION`` in that script is the single
# version pin.

on:
pull_request:
branches: [main]
paths:
- "README.md"
- "CLAUDE.md"
- "cli/CLAUDE.md"
- "web/CLAUDE.md"
- "docs/**/*.md"
- ".github/workflows/lychee.yml"
- "lychee.toml"
- "scripts/install_cli_tools.sh"
push:
branches: [main]
paths:
- "README.md"
- "CLAUDE.md"
- "cli/CLAUDE.md"
- "web/CLAUDE.md"
- "docs/**/*.md"
- ".github/workflows/lychee.yml"
- "lychee.toml"
- "scripts/install_cli_tools.sh"
workflow_dispatch:

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lychee:
name: lychee
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: Aureliolo/synthorg/.github/actions/checkout@25921183f274c930bf473dc0339376bda0961eaf
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Restore lychee cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-

- name: Install lychee
shell: bash
# ``runner.temp`` is only available at step-level env; job-level
# env limits context to github/inputs/matrix/needs/secrets/strategy/vars
# (actionlint enforces this). Same install dir is reused by the
# following PATH step.
env:
LYCHEE_INSTALL_DIR: ${{ runner.temp }}/lychee-bin
run: bash scripts/install_cli_tools.sh lychee

- name: Add lychee to PATH
shell: bash
env:
LYCHEE_INSTALL_DIR: ${{ runner.temp }}/lychee-bin
run: echo "${LYCHEE_INSTALL_DIR}" >> "${GITHUB_PATH}"

- name: Run lychee
shell: bash
env:
# Authenticated GitHub requests get a higher rate-limit budget,
# which matters because docs reference many github.com URLs.
GITHUB_TOKEN: ${{ github.token }}
run: |
set -o pipefail
# lychee accepts glob patterns natively (it handles ** without
# needing bash globstar). Markdown output is appended to the
# GitHub job summary so the report shows up in the run UI; the
# same content also lands in the step log via tee.
lychee \
--config lychee.toml \
--no-progress \
--format markdown \
'./README.md' \
'./CLAUDE.md' \
'./cli/CLAUDE.md' \
'./web/CLAUDE.md' \
'./docs/**/*.md' \
| tee -a "${GITHUB_STEP_SUMMARY}"
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ env/
.ruff_cache/
.pytest_cache/
.hypothesis/
.lycheecache

# Coverage
htmlcov/
Expand Down Expand Up @@ -93,8 +94,8 @@ _site/
docs/openapi/reference.html
docs/openapi/openapi.json

# Intermediate i18n catalog built by scripts/extract_web_strings.py
# (hand-off artefact for issue #1417; regenerate locally as needed).
# Intermediate i18n string catalog built by scripts/extract_web_strings.py
# from web/src/. Regenerated on demand; not source-of-truth.
web/src/i18n/_extracted_catalog.json

# Astro / Node.js (landing page)
Expand Down
2 changes: 2 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"_comment_MD036": "no-emphasis-as-heading: intentional use of bold labels inside admonition panels",
"_comment_MD041": "first-line-heading: docs start with MkDocs frontmatter, not an H1",
"_comment_MD046": "code-block-style: MkDocs Material tabbed content requires indented prose that markdownlint misreads as code",
"_comment_MD025": "single-title: pages carry a front-matter `title:` (used by MkDocs nav) AND an in-body `# Heading` (used by direct GitHub rendering). Empty `front_matter_title` tells markdownlint not to count the front-matter title as a top-level heading.",
"default": true,
"MD013": false,
"MD024": false,
"MD025": { "front_matter_title": "" },
"MD030": false,
"MD033": false,
"MD036": false,
Expand Down
26 changes: 18 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ ci:
# (actionlint is NOT skipped: no dedicated CI job covers it, and
# pre-commit.ci is the only enforcement layer for contributors who
# skipped local hooks.)
# - mypy / pytest-unit / go-* / eslint-web:
# - mypy / pytest-unit / go-* / lychee / eslint-web:
# too slow / toolchain-heavy for the cloud runner.
# lychee is also a Rust binary not bundled with pre-commit-uv;
# operators install it via `bash scripts/install_cli_tools.sh`,
# and CI runs it via .github/workflows/lychee.yml.
# - no-em-dashes / no-redundant-timeout / forbidden-literals /
# persistence-boundary / no-new-logger-exception-str-exc /
# orphan-fixtures / boundary-typed / setting-to-startup-trace /
Expand All @@ -38,7 +41,7 @@ ci:
# local-only pre-push hook surface) so pre-commit.ci does not pick
# them up; their CI counterpart is the ``Lint`` job in ``ci.yml``
# which runs the same scripts on every PR.
skip: [commitizen, gitleaks, hadolint-docker, caddy-validate, zizmor, no-em-dashes, no-redundant-timeout, mypy, pytest-unit, golangci-lint, go-vet, go-test, eslint-web, check-push-rebased, check-single-migration-per-pr, check-no-modify-migration, forbidden-literals, persistence-boundary, persistence-protocol-uniformity, dependency-inversion, provider-complete-chokepoint, no-new-logger-exception-str-exc, otlp-span-redaction, orphan-fixtures, doc-drift-counts, boundary-typed, setting-to-startup-trace, long-running-loop-kill-switch, list-pagination, domain-error-hierarchy, dead-api-endpoints, dual-backend-test-parity, schema-drift, no-magic-numbers, convention-gate-inventory, mcp-admin-guardrail, runtime-stats-freshness, dto-types-ts-in-sync, no-stdlib-logging, module-size-budget, no-growth-in-god-modules, no-central-junk-drawer, no-circular-imports, module-depth, protocol-documented, no-module-level-io, state-slice-immutability, strategy-protocol-injection, settings-namespace-complete, deptry, vulture, interrogate, sqlfluff, yamllint, web-knip, web-circular]
skip: [commitizen, gitleaks, hadolint-docker, caddy-validate, zizmor, no-em-dashes, no-redundant-timeout, mypy, pytest-unit, golangci-lint, go-vet, go-test, lychee, eslint-web, check-push-rebased, check-single-migration-per-pr, check-no-modify-migration, forbidden-literals, persistence-boundary, persistence-protocol-uniformity, dependency-inversion, provider-complete-chokepoint, no-new-logger-exception-str-exc, otlp-span-redaction, orphan-fixtures, doc-drift-counts, boundary-typed, setting-to-startup-trace, long-running-loop-kill-switch, list-pagination, domain-error-hierarchy, dead-api-endpoints, dual-backend-test-parity, schema-drift, no-magic-numbers, convention-gate-inventory, mcp-admin-guardrail, runtime-stats-freshness, dto-types-ts-in-sync, no-stdlib-logging, module-size-budget, no-growth-in-god-modules, no-central-junk-drawer, no-circular-imports, module-depth, protocol-documented, no-module-level-io, state-slice-immutability, strategy-protocol-injection, settings-namespace-complete, deptry, vulture, interrogate, sqlfluff, yamllint, web-knip, web-circular]

default_install_hook_types: [pre-commit, commit-msg, pre-push]

Expand Down Expand Up @@ -140,12 +143,6 @@ repos:
# ``stages: [pre-commit]`` (the default) so editors get fast
# feedback on the docs they're touching.

# lychee is a Rust binary not bundled with the pre-commit-hook runtime;
# operators install it via `bash scripts/install_cli_tools.sh`. Until
# the install script is updated to include lychee, run it CI-only via
# a dedicated workflow (lychee-action). Skip in pre-commit.ci too.
# - repo: https://github.com/lycheeverse/lychee

- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
Expand Down Expand Up @@ -415,6 +412,19 @@ repos:
pass_filenames: false
stages: [pre-push]

- id: lychee
name: lychee (Markdown link-checker)
entry: lychee
args: ["--config", "lychee.toml", "--no-progress"]
language: system
# Top-level README, every CLAUDE.md tier, and every Markdown
# under docs/. The CI workflow .github/workflows/lychee.yml
# mirrors this glob set; the local hook is pre-push only
# because the network probes take 8-15s.
files: ^(README|CLAUDE|cli/CLAUDE|web/CLAUDE)\.md$|^docs/.*\.md$
pass_filenames: true
stages: [pre-push]

- id: eslint-web
name: ESLint (web dashboard)
entry: npm --prefix web run lint
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Web: see `web/CLAUDE.md`. CLI: see `cli/CLAUDE.md` (use `go -C cli`, never `cd c
```bash
uv sync # all deps
uv sync --group docs # docs toolchain (zensical + D2)
bash scripts/install_cli_tools.sh # one-time per-machine: golangci-lint only (CI installs separately; install d2 via docs/getting_started.md)
bash scripts/install_cli_tools.sh # one-time per-machine: golangci-lint + lychee (CI installs separately; install d2 via docs/getting_started.md)
uv run ruff check src/ tests/ --fix # lint + auto-fix
uv run ruff format src/ tests/ # format
uv run mypy --num-workers=4 src/ tests/ # strict type-check
Expand All @@ -37,6 +37,7 @@ HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -k properties
HYPOTHESIS_PROFILE=fuzz uv run python -m pytest tests/ -m unit --timeout=0
bash scripts/install_git_hooks.sh # one-time per clone: wire core.hooksPath -> scripts/git-hooks (NOT pre-commit install)
uv run pre-commit run --all-files
uv run pre-commit run lychee --hook-stage pre-push --all-files # local Markdown link-check (lychee, 8-15s)
uv run python scripts/check_schema_drift_revisions.py --backend sqlite # or --backend postgres
PYTHONPATH=. uv run zensical build # docs
```
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ SynthOrg vs [other agent frameworks](https://synthorg.io/compare/) across organi
| [Guides](https://synthorg.io/docs/guides/) | Quickstart, company config, agents, budget, security, MCP tools, deployment, logging, memory |
| [Design Specification](https://synthorg.io/docs/design/) | The designed behaviour of every subsystem (the source of truth; states current wiring status per area) |
| [Architecture](https://synthorg.io/docs/architecture/) | System overview, tech stack, decision log |
| [REST API](https://synthorg.io/docs/rest-api/) | Scalar/OpenAPI reference |
| [REST API](https://synthorg.io/docs/openapi/) | Scalar/OpenAPI reference |
| [Library Reference](https://synthorg.io/docs/api/) | Auto-generated from docstrings |
| [Security](https://synthorg.io/docs/security/) | Application security, container hardening, CI/CD security |
| [Licensing](https://synthorg.io/docs/licensing/) | BUSL 1.1 terms, Additional Use Grant, commercial options |
Expand Down
2 changes: 1 addition & 1 deletion docs/api/layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Litestar REST + WebSocket API: controllers, authentication, guards, and channels
## Errors

The error taxonomy and exception classes live in
[`synthorg.core`](../core/index.md):
[`synthorg.core`](core.md):

- `synthorg.core.error_taxonomy` -- `ErrorCategory`, `ErrorCode`,
RFC 9457 helpers
Expand Down
5 changes: 2 additions & 3 deletions docs/decisions/0006-tiered-module-size-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ feature isolation).

### New docs / SQL / YAML tools

Landed: `markdownlint`, `yamllint`, `sqlfluff`. Deferred (see Exemption
ledger): `lychee` (Markdown link check) and `vale` (Google style +
Landed: `markdownlint`, `yamllint`, `sqlfluff`, `lychee` (Markdown link
check). Deferred (see Exemption ledger): `vale` (Google style +
British dictionary).

## Consequences
Expand Down Expand Up @@ -329,7 +329,6 @@ and closed for the project to reach 100% strict enforcement.
| `sqlfluff` `exclude_rules = RF04` (keywords-as-identifiers) | Same SQL style issue | Trivial |
| Typeguard never landed | Issue #2068: "Wire typeguard after #2048 lands" | Medium |
| Vale prose linter never landed | Issue #2069: "Wire Vale + binary install script" | Small |
| Lychee CI workflow never landed | Issue #2070: "Wire Lychee CI workflow + scripts/install_cli_tools.sh" | Trivial |
| `knip --no-exit-code` (report-only, never blocks) | Issue #2071: "Knip blocking: eliminate unused exports surfaced by knip" | Medium |
| `dpdm --skip-imports` for `stores/auth.ts -> api/client.ts` cycle | Issue #2072: "Fix auth -> client circular dependency" | Small |
| `_module_size_baseline.json` residue: 109 files not covered by PR 3 / PR 4 / #2051 / #2052 (oversized files in `persistence/`, `engine/`, `api/`, `meta/`, etc. that no existing PR addresses) | Issue #2077: "EPIC: Drain residual module-size baseline" | Very large (per-package decomposition program) |
Expand Down
2 changes: 1 addition & 1 deletion docs/design/a2a-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Agent-to-Agent protocol integration. Status, architecture, implemen

# A2A Protocol

The [A2A (Agent-to-Agent) protocol](https://agent-protocol.ai) is a standard for heterogeneous agent communication. SynthOrg exposes an A2A gateway that lets external agent systems discover, invoke, and receive updates from the internal roster, without either side needing to understand the other's internal shape.
The [A2A (Agent-to-Agent) protocol](https://a2a-protocol.org/) is a standard for heterogeneous agent communication. SynthOrg exposes an A2A gateway that lets external agent systems discover, invoke, and receive updates from the internal roster, without either side needing to understand the other's internal shape.

This page is the status-and-architecture reference: what ships today, how it maps onto SynthOrg's internal model, and what's next.

Expand Down
12 changes: 9 additions & 3 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,21 @@ uv sync

## Install external CLI tools (one-time per machine)

Some gates and the docs build rely on external binaries that are not Python packages: `golangci-lint` (Go linter, used by the CLI) and `d2` (architecture diagram renderer).
Some gates and the docs build rely on external binaries that are not Python packages: `golangci-lint` (Go linter, used by the CLI), `lychee` (Markdown link-checker), and `d2` (architecture diagram renderer).

Install `golangci-lint` once per machine:
Install `golangci-lint` and `lychee` once per machine:

```bash
bash scripts/install_cli_tools.sh
```

The script downloads the pinned `golangci-lint` version that matches CI (`.github/workflows/cli.yml`). Re-run only after bumping the pinned version; subsequent `uv sync` invocations do NOT re-run the script. CI uses its own action-based install step, so this is strictly a local-developer convenience.
The script downloads the pinned `golangci-lint` version that matches CI (`.github/workflows/cli.yml`) and the pinned `lychee` version that matches CI (`.github/workflows/lychee.yml`). Re-run only after bumping a pinned version; subsequent `uv sync` invocations do NOT re-run the script. CI uses its own action-based install steps, so this is strictly a local-developer convenience. The `lychee` binary lands in `~/.local/bin/`; if that directory is not already on `PATH`, the script will print the export line you need to add to `~/.bashrc` / `~/.zshrc`.

To run the link-checker locally:

```bash
uv run pre-commit run lychee --hook-stage pre-push --all-files
```

Install `d2` separately (the docs job pins `v0.7.1`). The fastest path is the upstream installer:

Expand Down
Loading
Loading