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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ jobs:
# parity gate, so this keeps config.ts, the contract, and the docs aligned.
run: bun run check:env-contract

- name: Config-schema guard
# Fails when schema/github-app.schema.json drifts from
# src/repo-config/schema.ts. That file is what editors consume via the
# `# yaml-language-server: $schema=` modeline, so a stale copy would
# advertise a config surface the runtime no longer accepts.
run: bun run check:config-schema

- name: Docs-sync guard (bot workflows, FR-019)
if: github.event_name == 'pull_request'
env:
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ bun.lockb
# Generated/managed assets with intentional non-prettier formatting
.claude/skills/
docs/index.md
schema/github-app.schema.json
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Validate locally with `bun run docs:build` before pushing. If no matching doc ex
**CI-enforced doc gates.** Two project-specific checks run in `.github/workflows/docs.yml` ahead of `mkdocs build --strict` (which only validates internal links and snippet targets, not prose-vs-source agreement):

- Bun version strings in `docs/` **and root-level `README.md` / `CONTRIBUTING.md` / `CLAUDE.md`** are pinned to `.tool-versions` via `bun run scripts/check-docs-versions.ts` (also asserts `package.json` `engines.bun` / `packageManager` and the two `Dockerfile.*` `FROM oven/bun:<ver>` lines agree).
- `src/<file>:<line>` citations in `docs/` **and the same three root-level files** are verified via `bun run scripts/check-docs-citations.ts` (file must exist; cited line / range must be in bounds). Citations may opt in to symbol anchoring with a trailing `#symbol` suffix (e.g. `` `src/core/prompt-builder.ts:155#buildPrompt` ``); when present, the anchor token must physically appear on the cited line range, which closes the silent line-shift hole the bounds-only path can't see (issue #158).
- `src/<file>:<line>` citations in `docs/` **and the same three root-level files** are verified via `bun run scripts/check-docs-citations.ts` (file must exist; cited line / range must be in bounds). Citations may opt in to symbol anchoring with a trailing `#symbol` suffix (e.g. `` `src/core/prompt-builder.ts:179#buildPrompt` ``); when present, the anchor token must physically appear on the cited line range, which closes the silent line-shift hole the bounds-only path can't see (issue #158).

The `docs.yml` `pull_request:` trigger has no `paths:` filter, so these gates run on every PR, code-side bumps that invalidate doc facts (Renovate Bun bump, refactor that shifts cited line numbers) trip the build the same way doc edits do. `Deploy to GitHub Pages` is still gated on `push` / `workflow_dispatch`, so PRs validate but never publish.

Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/build/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ The agent executor (`src/core/executor.ts:208`) supports two prompt-layout strat

The `cacheable` layout splits the prompt by trust:

- **Trusted scaffolding** (`security_directive`, `freshness_directive`, workflow steps, commit / CAPABILITIES boilerplate) → `systemPrompt.append`. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:448#buildPromptParts`. Byte-identical across jobs of the same shape, so the system-prompt prefix becomes a stable cache key.
- **Trusted scaffolding** (`security_directive`, `freshness_directive`, workflow steps, commit / CAPABILITIES boilerplate) → `systemPrompt.append`. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:481#buildPromptParts`. Byte-identical across jobs of the same shape, so the system-prompt prefix becomes a stable cache key.
- **Attacker-influenceable data** (`formatted_context` with title / body / comments, `<untrusted_*>` spotlight blocks with per-call nonce, per-call metadata like delivery ID) → user-role message.
- **Dynamic preset sections** stripped via `excludeDynamicSections: true`.

Expand Down
28 changes: 20 additions & 8 deletions docs/operate/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,24 @@ Controls the internal scheduler that runs prompt-based actions declared in a
repo's `.github-app.yaml`. See [Scheduled actions](../use/scheduled-actions.md)
for the file schema. Server mode only; a daemon process ignores these.

| Variable | Default | Notes |
| ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SCHEDULER_ENABLED` | `false` | Master kill-switch. When false the scheduler never starts. It also will not start without `DATABASE_URL` and a non-empty `ALLOWED_OWNERS`. |
| `SCHEDULER_SCAN_INTERVAL_MS` | `300000` (5 min) | Cadence of the scan that enumerates installations, fetches each `.github-app.yaml`, and enqueues due actions. A value outside `[60000, 3600000]` is rejected at startup. |
| `SCHEDULER_ALLOW_AUTO_MERGE` | `false` | Hard kill-switch for unattended auto-merge. Effective auto-merge requires BOTH this AND a per-action `auto_merge: true`; otherwise no merge tool runs. |
| `SCHEDULER_CONFIG_FILE` | `.github-app.yaml` | Filename read from each installed repo's default-branch root. |
| Variable | Default | Notes |
| ---------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SCHEDULER_ENABLED` | `false` | Master kill-switch. When false the scheduler never starts. It also will not start without `DATABASE_URL` and a non-empty `ALLOWED_OWNERS`. |
| `SCHEDULER_SCAN_INTERVAL_MS` | `300000` (5 min) | Cadence of the scan that enumerates installations, fetches each `.github-app.yaml`, and enqueues due actions. A value outside `[60000, 3600000]` is rejected at startup. |
| `SCHEDULER_ALLOW_AUTO_MERGE` | `false` | Hard kill-switch for unattended auto-merge. Effective auto-merge requires BOTH this AND a per-action `auto_merge: true`; otherwise no merge tool runs. |

## Per-repo config file

Selects the file each installed repo is read from. Unlike the scheduler
variables above this is **not** server-mode only: the same document carries
feature toggles, agent overrides, and trigger filters, so any process that
resolves repo policy reads it. Only the default branch's copy is ever applied.
See [Per-repo configuration](../use/repo-config.md) for the file schema.

| Variable | Default | Notes |
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `REPO_CONFIG_FILE` | `.github-app.yaml` | Filename read from each installed repo's default-branch root. Trimmed at load: a stray space would 404 on every repo and silence the whole surface with nothing logged. |
| `SCHEDULER_CONFIG_FILE` | (unset) | **Deprecated** former name for `REPO_CONFIG_FILE`. Still honoured as a fallback so an upgrade does not silently change which file is read; logs a one-shot boot warning. |

## Review learnings

Expand Down Expand Up @@ -217,9 +229,9 @@ Selects the system/user prompt split the agent executor passes to the Claude Age

**Why this exists.** The SDK's default systemPrompt (`{ type: "preset", preset: "claude_code" }`) embeds dynamic sections (cwd, platform, shell, OS) directly in the system-prompt prefix. Because each delivery clones to a unique `cwd` under `CLONE_BASE_DIR`, the system-prompt prefix is unique per job and the Anthropic prompt cache misses on every invocation, paying the 1-hour TTL `ephemeral_1h_input_tokens` cache-write surcharge (2× base price) with zero compensating reads.

**`legacy` (default).** Single user-role string built by `buildPrompt()` in `src/core/prompt-builder.ts:155#buildPrompt`. SystemPrompt is the unmodified `claude_code` preset. Backwards-compatible; safe rollback target.
**`legacy` (default).** Single user-role string built by `buildPrompt()` in `src/core/prompt-builder.ts:179#buildPrompt`. SystemPrompt is the unmodified `claude_code` preset. Backwards-compatible; safe rollback target.

**`cacheable`.** Static scaffolding (`security_directive`, `freshness_directive`, workflow steps, commit/CAPABILITIES boilerplate) is lifted into `systemPrompt.append`, and `excludeDynamicSections: true` strips cwd / platform / shell / OS from the preset. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:448#buildPromptParts`. The user-role message keeps only the per-call dynamic blocks (`formatted_context`, `untrusted_*` with per-call nonce, per-call metadata). The append is byte-identical across jobs of the same shape (PR vs issue), so the system-prompt prefix becomes a stable cache key.
**`cacheable`.** Static scaffolding (`security_directive`, `freshness_directive`, workflow steps, commit/CAPABILITIES boilerplate) is lifted into `systemPrompt.append`, and `excludeDynamicSections: true` strips cwd / platform / shell / OS from the preset. Built by `buildPromptParts()` in `src/core/prompt-builder.ts:481#buildPromptParts`. The user-role message keeps only the per-call dynamic blocks (`formatted_context`, `untrusted_*` with per-call nonce, per-call metadata). The append is byte-identical across jobs of the same shape (PR vs issue), so the system-prompt prefix becomes a stable cache key.

**Rollout.** Flip the variable to `cacheable`, then verify cache hits by tailing the executor completion log for non-zero `cacheReadInputTokens`:

Expand Down
Loading
Loading