AGENTS.md · diff
git:20260924.106c80b to git:20260924.110dddc
128 added, 9 removed. Audit A to A.
# Repository Guidelines
Repo: https://github.com/platonai/Browser4
## Architecture
```
browser4-cli (Rust) ──MCP over HTTP──▶ browser4-rest (Kotlin/Spring) ──▶ PulsarWebDriver (Kotlin/CDP)
▲ │
│ ▼
└──── e2e tests ────▶ Fixture HTTP server (Rust test harness)
```
- **CLI:** `cli/browser4-cli/` — Rust binary, MCP tool calls over HTTP
- **Backend:** `browser4-rest/` — Spring Boot, `MCPToolController` dispatches tools
- **Browser driver:** `browser4-core/browser4-browser/` — `PulsarWebDriver` wraps CDP
- **Agent tools:** `browser4-agentic/` — `AgentToolManager` maps MCP tool names → driver methods
### Dispatch chain (CLI → browser)
1. CLI builds MCP tool call: `{tool: "browser_type", arguments: {ref: "#el", text: "hi"}}`
2. `MCPToolController.callTool()` → `dispatchToToolExecutor()`
3. `normalizeFrontendToolCall()` applies `FRONTEND_TOOL_NAME_ALIASES` (e.g., `browser_type` → `fill`)
4. `DefaultArgumentNormalizer` maps `ref` → `selector`, strips `sessionId`, converts snake_case
5. `resolveMcpToolCall()` → `ToolCall("tab", "fill", args)`
6. `AgentToolManager.execute()` → `executor.callFunctionOn(toolCall, driver)` → `PulsarWebDriver.fill()`
### Batch commands
`handleCommandBatch()` → `handleBatchTool()` in `MCPToolController`. CLI's `compile_batch_request()` builds step arrays with `op: "tool"`. `preFocusSelector` only added for `keydown`/`keyup` (not fill/type/press).
### Known CDP pitfalls
- **crbug.com/444929150:** `Input.dispatchMouseEvent` type `mouseWheel` race condition in headless Chrome. Fix: dispatch to `{passive: false}` wheel listener.
- **Cursor positioning:** `DOM.focus()` + `Input.dispatchMouseEvent` (click) may leave cursor at 0. Fix: `setSelectionRange(99999, 99999)` after focus+click.
- **`Input.insertText` racing:** 0ms delay between chars drops `input` events. Fix: use same inter-char delay as `type()` via `randomDelayMillis("type")` (90-240ms).
### Direct CDP methods: mandatory review gates
Any method that implements behavior by calling CDP directly (or by evaluating
JavaScript that synthesizes browser events) — `drag`, `press`, `click`, dialog
handling, screenshots, scroll, storage restore, etc. — must satisfy ALL of the
following before it is considered done. "It works on my machine" is not
sufficient; each gate below has burned this project before.
1. **Anti-bot detection** — the page can observe the automation, and many sites
actively fingerprint it. Consider:
- `event.isTrusted` is `false` for JS-synthesized events; if a trusted path
exists (CDP `Input.*`, `userGesture=true`), prefer it or document why not.
- Fixed inter-event delays and exact element-center coordinates are
fingerprints; randomize delays (via `randomDelayMillis` or an equivalent
bucket) and jitter coordinates like real input would.
- The full input story: real drags/clicks have trusted `mousedown` →
`mousemove` → `mouseup` context; a synthetic event sequence with no
trusted accompaniment is itself detectable.
2. **Negative impact** — check the failure modes, not just the happy path:
- No silent failures: a method must fail loudly rather than dispatch onto
an unrelated element (occlusion, `pointer-events:none`, layout shift,
frames). Validate before dispatching so retries stay idempotent.
- Error messages must surface the real cause to the CLI/user instead of
being swallowed or wrapped by retry machinery.
- Retry safety: blocks inside `invokeOnPage`/`invokeOnElement` must be
idempotent or the deterministic failures must bypass the retry.
- Don't regress error semantics of the upstream method you override.
3. **Cross-platform compatibility** — headless vs GUI Chrome, Docker/CI,
Windows/Linux/macOS all behave differently:
- Scroll commits and layout updates are asynchronous (`scrollIntoViewIfNeeded`
may prefer smooth animation); poll for the observable state instead of
assuming a single read is current.
- Event dispatch timing, renderer scheduling, and viewport geometry differ
under load and in containers; never rely on a single hardcoded delay.
- CDP API availability varies by Chrome version; provide fallbacks or
explicit errors, never a silent no-op.
4. **Real-webpage testing** — unit tests alone are insufficient:
- Add a fixture page (under
`browser4-tests/pulsar-tests-common/src/main/resources/static/b4/`) that
exercises the real behavior: event order, payloads, occluded/disabled/
off-viewport/frame targets, async layout changes.
- Prove it against a real browser through the e2e harness
(`cargo test --test e2e -- --scenario=...`, `requires_browser4: true`),
not only against mocks.
## Project Structure
| Module | Description |
|---|---|
| `browser4-core` | Core engine: sessions, scheduling, DOM, browser control |
| `browser4-dependencies` | BOM and dependency alignment |
| `browser4-tools` | Operational tools and launch helpers |
| `browser4-agentic` | AI agents, MCP, skill registration |
| `browser4-agent-tools` | High-level agent tools: scraping, crawling, stateful page interaction |
| `browser4-rest` | Spring Boot REST layer & command endpoints |
| `cli/browser4-cli` | Rust CLI binary |
| `skills/browser4-cli` | AI agent skill definitions |
| `browser4-apps/browser4-standalone` | Product packaging, unified launcher (`target/Browser4.jar`) |
| `examples/browser4-examples` | Runnable examples |
| `browser4-tests` | E2E, integration, scenario tests |
| `browser4-tests/pulsar-tests-common` | Shared test base classes and utilities |
| `cdp-protocol` | Chrome DevTools Protocol JSON definitions |
| `coworker/` | File-queue automation for task-driven AI workflows |
+ ## Development Environment (Running from Source)
+
+ When developing or running the CLI from the source tree (not an installed
+ binary), the following applies.
+
+ ### Invocation wrappers
+
+ | Shell | Command | Notes |
+ |-------|---------|-------|
+ | PowerShell (Windows) | `./b4w.ps1 <command>` | Primary dev wrapper; builds from source if needed |
+ | Git Bash (Windows) | `./b4w.sh <command>` | Quotes args automatically for pwsh safety |
+ | Git Bash (alt) | `pwsh ./b4w.ps1 <command>` | Direct PowerShell invocation |
+ | Linux / macOS | `./b4w.sh <command>` | Same script works cross-platform |
+ | Any (installed) | `browser4-cli <command>` | After `browser4-cli install` |
+
+ > **Important:** The `$(./b4w.ps1) <command>` syntax shown in some task
+ > instructions does **not** work in bash — `$(…)` is command substitution, not
+ > invocation. Use `pwsh ./b4w.ps1 <command>` or `./b4w.sh <command>` instead.
+
+ ### First-run latency from a source tree
+
+ The first launch builds the runtime bundle via Maven (~1–3 min, before the
+ spinner appears) and then starts the Browser4 backend (Spring Boot + JVM,
+ ~10s). Subsequent commands are instant — the server stays alive between
+ invocations. The spinner shows stage-level progress (JVM → Spring Boot → MCP
+ tools).
+
+ ### Prerequisites for development
+
+ - **[Rust](https://rustup.rs/)** — via `rustup`; needed to compile the CLI binary.
+ - **Java 25+** — required by the Browser4 backend server.
+ - **[Git](https://git-scm.com/)** — for cloning the repository.
+
+ Verify your setup with:
+
+ ```bash
+ cargo --version && java -version
+ ```
+
+ ### Running the CLI from source
+
+ When running from source (not a globally installed binary), use `cargo run`
+ from the CLI directory:
+
+ ```bash
+ cd cli/browser4-cli
+ cargo build # build the binary
+ cargo run -- <command> # run a command (the -- separates cargo args from CLI args)
+ cargo run -- goto "https://example.com"
+ cargo run -- snapshot -v 0
+ ```
+
+ **Note:** All docs use `browser4-cli` as the generic command name. If running
+ from source, substitute `cargo run --` (with the leading
+ `cd cli/browser4-cli &&` if not already in that directory).
+
+ **From repo root (no `cd` required):**
+
+ ```bash
+ cargo run --manifest-path cli/browser4-cli/Cargo.toml -- <command>
+ ```
+
+ This pattern works from any directory — no need to `cd` first.
+
+ ### Output Redirection in Dev Mode
+
+ The working directory during `cargo run` is `cli/browser4-cli/`, so relative
+ file paths must account for this. Use `--quiet` to suppress cargo build output:
+
+ ```bash
+ # From repo root: redirect query results to a file
+ cd cli/browser4-cli && cargo run --quiet -- htmlsnapshot query --sql @../../query.sql --result-only > ../../results.json
+
+ # From cli/browser4-cli/: same pattern with shorter relative paths
+ cargo run --quiet -- htmlsnapshot query --sql @query.sql --result-only > results.json
+ ```
+
+ > **Tip:** `--quiet` passes through to cargo and suppresses the "Finished" /
+ > "Running" build-status lines that would otherwise pollute the output file.
+ > Without `--quiet`, those lines appear on stderr but `2>&1` captures them
+ > along with the data — use `--quiet` instead of `2>&1` for clean output.
+
## Build & Test
### Quick commands
```bash
# Build (skip tests)
./mvnw -DskipTests # Linux/macOS
.\mvnw.cmd -q -D"skipTests" # Windows PowerShell
# Rust unit tests (fast, no backend needed)
cd cli/browser4-cli && cargo test --bin browser4-cli
# Kotlin tests
mvn test -pl browser4-rest -am
mvn test -pl browser4-rest -am -Dtest=MCPToolControllerTest
# E2E tests (needs running backend or mock server)
cargo test --test e2e -- --nocapture
cargo test --test e2e -- --nocapture --scenario=test_e2e_batch_*
# Scoped test runs (Windows)
bin/test.ps1 fast|it|e2e|rest|skills|mcp|cli|browser4|mock-site
```
Maven profile switches in root `pom.xml`: `-DrunITs=true`, `-DrunE2ETests=true`, `-DrunCoreTests=true`, `-DrunRestTests=true`.
### E2E test filtering
```
cargo test --test e2e -- --help # All options
--scenario <pattern> # Glob filter
--group <name> # Group filter (repeatable)
- --level BASIC|EXTENDED|ALL # Test depth
+ --level SMOKE|BASIC|EXTENDED|ALL # Test depth
--fail-fast / --failed # Stop early / rerun failures
+ --max-failures <count> # Tolerated failing scenarios (default 5, 0 = none)
--list / --list-groups # Discover without running
- --enable-install-scenario # Opt into install tests
- --enable-batch-scenario # Opt into batch tests
+ --enable-all # Opt into excluded-by-default tests
--force-rebuild-bundle # Force local Maven + runtime rebuild
--force-remote-bundle # Download pre-built bundle instead
```
### Test locations
| Scope | Path |
|---|---|
| Unit tests | `src/test/kotlin/...` |
- | Integration | `browser4-tests/pulsar-it-tests/` |
- | E2E | `browser4-tests/pulsar-e2e-tests/` |
| REST integration/E2E | `browser4-tests/browser4-rest-tests/` |
| Shared utilities | `browser4-tests/pulsar-tests-common/` |
| Rust E2E | `cli/browser4-cli/tests/e2e/` |
+ > **Note:** The former integration/E2E suites in `pulsar-it-tests` and `pulsar-e2e-tests`
+ > have been migrated into the base library (browser4-core modules and
+ > `pulsar-tests-common`); the placeholder modules have been removed from this repo.
+
## Code Style
### Kotlin
- Immutable `data class`; explicit return types; null-safety (`require`/`check`/`?:`)
- Public APIs require KDoc
- Store AI-generated task docs in `docs-dev/copilot/`
### Logging
```kotlin
logger.info("Task {} finished in {} ms", taskId, cost) // placeholders, never concatenation
```
### Naming
- Test methods: camelCase + `@DisplayName("...")` — **NOT** backtick naming
- Test classes: `<Name>Test.kt` (unit), `<Name>IT.kt` (integration), `<Name>E2ETest.kt` (e2e)
## Testing Guidelines
**Default policy:** Don't run full suites. Compile with tests skipped, run smallest relevant scope. Upgrade scope when risk increases (cross-module, public API/DTO/serialization, Spring wiring, dependency bumps, concurrency/I/O, browser lifecycle).
**Tag-driven scheduling** — use tags from `docs/TESTING.md`:
- Scope: `Unit`, `Integration`, `E2E`, `SDK`
- Speed: `Fast` (<5s), `Slow` (5–30s), `Heavy` (>30s)
- Gates: `Requires*`, `ManualOnly`
**Coverage targets:** Global ≥70%, Core ≥80%, Utilities ≥90%, Controllers ≥85%
**CI has two gates with different Maven test scopes** — pick the tags for the gate you are writing tests for:
| Gate | Workflow | Maven `excluded_groups` | Scope |
|---|---|---|---|
| PR Quality Gate | `.github/workflows/pr.yml` | `ManualOnly,RequiresAI,E2E,E2ETest,Slow,Heavy,HeavyTest,Integration,IntegrationTest,RequiresServer,RequiresBrowser,RequiresDocker,TestInfraCheck` | fast/unit only (`run_pulsar_tests: 'false'`) |
| CI/CD Pipeline (main + release tags) | `.github/workflows/ci.yml` | `ManualOnly,RequiresAI,E2E,E2ETest,Slow,HeavyTest,TestInfraCheck` | adds integration/infra tests that need Chrome, Docker and the started app |
- Both gates pass `-Dsurefire.excludes=**integration**` (class-file pattern, not tags) and both derive success from the surefire XML totals — a test class is skipped by **tag**, never by name. `SDK` is excluded by neither gate (no test carries that tag today); `Heavy` is excluded only by the PR gate, `HeavyTest` by both. `.github/workflows/ci.yml` also builds all-main-modules, starts a Dockerized app on port 8182 and runs `cargo test` in `cli/browser4-cli`.
+ Both gates pass `-Dsurefire.excludes=**integration**` (class-file pattern, not tags) and both derive success from **the Maven exit code plus the surefire XML totals** — the exit code is authoritative (`reconcile-status` in `.github/actions/run-tests/action.yml` refuses to turn a non-zero exit into a pass, and refuses a pass when no XML was produced at all), so a breached JaCoCo floor, a compile error or a dead test fork fails the gate even with zero parsed test failures. A test class is skipped by **tag**, never by name. `SDK` is excluded by neither gate (no test carries that tag today); `Heavy` is excluded only by the PR gate, `HeavyTest` by both. `.github/workflows/ci.yml` also builds all-main-modules, starts a Dockerized app on port 18182 and runs `cargo test` in `cli/browser4-cli`.
+
+ Neither `ci.yml` nor `nightly.yml` fails early any more: `Check Test Status` only records `MAVEN_TESTS_FAILED` in `$GITHUB_ENV`, the Docker build / app startup / CLI e2e stages still run, and a final `Enforce CI Gate` / `Enforce Nightly Gate` step (after `Pipeline Summary`) decides the job outcome from the JVM stage flag plus the CLI e2e outcome. So one round reports both sides instead of hiding the CLI suite behind a single broken JVM test.
+
+ The nightly gate (`.github/workflows/nightly.yml`, 00:00 UTC) is a superset: it adds `Slow`/`HeavyTest`/`TestInfraCheck`, measures JaCoCo in observe-only mode
+ (`-P...,quality-gate -Djacoco.check.skip=true`, no floor), runs `cargo test --bin browser4-cli --lib` (the only place the Rust unit tests run), runs the CLI e2e suite with `--level=EXTENDED --enable-all --max-failures=0`, and defers the job outcome to a final `Enforce Nightly Gate` step so a failing JVM test never skips the CLI e2e stage. See [TESTING.md § CI 门禁实际覆盖](docs/TESTING.md) for the current coverage inventory.
See [CI stabilization notes](docs-dev/copilot/ci-stabilization-4.13.x.md) before changing either list.
## Configuration
- Default port: **18182**
- Config files: `application.properties` → `application-*.properties` → `application-private.properties` (git-ignored, secrets here or env vars)
- Key properties: `openrouter.api.key`, `browser.profile.mode` (DEFAULT|SYSTEM_DEFAULT|SEQUENTIAL|TEMPORARY), `browser.display.mode` (GUI|HEADLESS|SUPERVISED)
- Display mode precedence: session capabilities (`headed` / `displayMode`, e.g. from `open --headed`/`--headless`) override the server-wide `browser.display.mode` default at browser launch (`AbstractPulsarSession.createBoundDriver`); server default applies only when the session has no display preference
- LLM providers configured via env vars: `DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`, `VOLCENGINE_API_KEY`, `OPENAI_API_KEY`
## Development Patterns
+ ### CLI command naming: spaced form preferred
+
+ New CLI commands must use the **spaced name** style (`verb noun`) — e.g.
+ `swarm submit`, `htmlsnapshot get`, `profiles list`, `plugin <domain>` — to
+ stay consistent with every other prefixed command. Internally the CLI is
+ kebab-case (`swarm-submit`); users type the spaced form, which
+ `rewrite_prefixed_command()` (main.rs) rewrites to the internal kebab name.
+
+ When adding a prefixed command:
+
+ 1. `CommandDef.name` stays kebab-case (the internal dispatch name).
+ 2. Register the prefix in `rewrite_prefixed_command()` so `prefix sub` →
+ `prefix-sub`. If the prefix also works standalone (`crawl`, `webdb`,
+ `doctor`, `webminer`, …), gate it with a `known_subs` allowlist so bare
+ usage and positional args pass through untouched.
+ 3. Register the kebab form in `preferred_spaced_command_form()` (and the bare
+ prefix in `preferred_prefixed_group_form()` when the bare prefix is
+ invalid) so users get a "Use 'browser4-cli prefix sub' instead" hint.
+ 4. Single-word commands without subcommands (`goto`, `close`, `eval`, …) stay
+ bare kebab — no prefix, no spaced form.
+ 5. Plugin tool domains are already invoked spaced as `plugin <domain> <method>`
+ (dynamic discovery via `/mcp/tools`, no registration needed).
+ 6. Plugins can declare a **named CLI command** without any CLI change: set
+ `ToolSpec.cliName` (spaced form, e.g. `"profile import"`) on the tool spec.
+ The CLI discovers these from `GET /mcp/tools/specs` at startup and renders
+ them as first-class commands with argument parsing (`browser4-cli profile
+ import --source chrome`). No `CommandDef` needed — the spec's `arguments`
+ define the `--key value` options.
+
### Adding a `browser4-cli` command
1. **`commands.rs`** — Add `CommandDef`: CLI name kebab-case, MCP tool `browser_`-prefixed snake_case, map args in `tool_params_fn`
2. **`MCPToolController.kt`** — Add frontend alias so `browser_my_tool` resolves to internal name `my_tool`
3. **Backend tool** — Reuse existing when possible; if new capability needed add `@MCP` method in `WebDriver.kt`, implement in concrete driver. Add explicit `BrowserTabToolExecutor` case only for non-trivial parameter mapping
4. **`main.rs`** — Update only for: custom dispatch, dynamic tool-name selection, stale-session recovery, `no_snapshot_commands()`, or custom batch handling in `compile_batch_request()`
5. **Docs** — Update `skills/browser4-cli/SKILL.md`; extensive docs → `skills/browser4-cli/references/<topic>.md`
6. **Tests** — `commands.rs` unit tests → controller mapping tests → `e2e.rs` → `MCPToolControllerE2ETest.kt`
7. **Common failures:** missing backend alias, omitted `sessionId`, forgetting `no_snapshot_commands()`/`batch_supported`, element-ref parameter name mismatches, snake_case/camelCase normalization
### REST-based commands (swarm, crawl)
- `tool_name_fn` returns `""`; dispatch entirely in `main.rs` via custom `handle_*`
- HTTP in `http.rs`: `submit_*` → POST `/api/<resource>`, `get_*_result` → GET `/api/<resource>/{id}/result`
- Backend: `@RestController` + `@Service` + `ConcurrentHashMap` task store + `CoroutineScope`
- Return task UUID from POST; CLI polls for completion. No MCP alias needed.
### Snapshot-related commands
Category `Category::Snapshot`:
- `htmlsnapshot get` / `query` — HTML retrieval with pagination (`-limit`, `-offset`)
- `htmlsnapshot grep` — regex search (`-i`, `-v`, `-F`, `-w`, `-A/B/C`, `--selector`)
- `htmlsnapshot inspect` — CSS selector discovery for recurring patterns
- `htmlsnapshot summary` — compressed Web Page Summary Index
- `snapshot` — live a11y snapshot with `--boxes`, `--stdout`, `--limit`
### Modifying install/uninstall/upgrade code
After changing `cli/browser4-cli/src/daemon.rs`, run install-scenario e2e tests:
```bash
- cargo test --test e2e -- --nocapture --level ALL --enable-install-scenario --scenario '*install*'
+ cargo test --test e2e -- --nocapture --level EXTENDED --enable-all --scenario '*install*'
```
## PowerShell Cross-Platform Compatibility
- Use `$IsWindows`, `$IsLinux`, `$IsMacOS` for platform branching
- Avoid Windows-only cmdlets; use `Join-Path` / `[System.IO.Path]::Combine()`
- Prefer `$env:HOME` over `$env:USERPROFILE`
- Shebang: `#!/usr/bin/env pwsh`
- **When fixing one `.ps1`, check siblings for the same issue**
## Test Script Portability
Scripts under `browser4-tests/tests-production/` and `bin/test-production.ps1` test globally-installed `browser4-cli`. They must **never** depend on: git, repo root, source code, Maven/Cargo build outputs. Use `$PSScriptRoot` for sibling references. Repo-awareness must be opt-in with clear error messages when absent.
## Coworker Automation
File-queue system for task-driven AI workflows (`coworker/`). Task files (Markdown with optional `Title:`/`Prompt:` headers) route through state directories: `0draft/` → `1ready/` → `2working/` → `3complete/` (or `3aborted/`, `4review/`, `5approved/`). See [Coworker SKILL.md](coworker/SKILL.md).
## Definition of Done
- [ ] Build and related tests pass
- [ ] No new high-noise logs or warnings
- [ ] New/changed logic has tests (main path + edge case)
- [ ] No secrets or private endpoints committed
- [ ] No arbitrary version changes (follow parent BOM)
- [ ] Documentation updated for public behavior changes
- [ ] Performance impact assessed if >5%
## Common Issues
| Issue | Solution |
|---|---|
| `.ps1` scripts don't run on Linux | `sudo apt-get install -y powershell`, then `pwsh script.ps1` |
| `mvnw` no execute permission | `chmod +x mvnw` |
- | JDK version mismatch | JDK 17+ in `JAVA_HOME` |
+ | JDK version mismatch | JDK 25+ in `JAVA_HOME` |
| Windows parameter escaping | `-D"key.with.dots=value"` |
- | Port 8182 in use | Override `server.port` in root `application.properties` |
+ | Port 18182 in use | Override `server.port` in root `application.properties` |
+ | JaCoCo reports empty / coverage floor never trips | The surefire `argLine` in the root `pom.xml` must use late binding `@{jacocoArgLine}`, never `${jacocoArgLine}`: the property is declared empty, so `${...}` is substituted to `""` while the effective model is built — before `prepare-agent` sets it — and the agent never attaches (`Skipping JaCoCo execution due to missing execution data file`). Verify with `mvn -X -Pquality-gate -pl :browser4-common test` and look for `-javaagent:` on the surefire fork command line |
| BrowserProtocol retry log storms | Use existing retry utilities, lower log level |
## Documentation Update Rule
When a user says **"update documents"** (or "update docs", "refresh documentation"), update ALL of the following that reference the changed feature:
1. **`README.md`** and **`README.zh.md`** — root-level project readmes
2. **`skills/browser4-cli/SKILL.md`** — primary CLI skill document
3. **`skills/browser4-cli/references/*.md`** — reference docs (htmlsnapshot.md, x-sql.md, etc.)
4. **`cli/browser4-cli/README.md`** — CLI-specific README
5. **`cli/browser4-cli/src/help.rs`** — CLI help text generation
6. **`cli/browser4-cli/src/tips.rs`** — CLI tips/hints shown to users
7. **Any other `.md` files** in `docs/`, `skills/`, or project root that reference the changed command or feature
When adding a new CLI option or changing command behavior, always check these locations.
## Documentation References
- [Testing Taxonomy](docs/TESTING.md)
- [Build from Source](docs/build-from-source.md)
- [Configuration Guide](docs/config.md)
- [CLI Skill Guide](skills/browser4-cli/SKILL.md)
- [ARIA Snapshots](docs/aria-snapshots.md)
- [HTML Snapshot](docs/htmlsnapshot-inspect-summary.md)
- [Eval Command Output](docs/eval-command-output.md)
- [Load Options Guide](docs/load-options-guide.md)
- [Crawl Checkpoint & Resume](docs/crawl-checkpoint-resume.md)
- [Mock Site](docs/mocksite.md)
- [QL Functions Guide](docs/ql-functions-guide.md)
- [Coworker Automation](coworker/SKILL.md)
---
*Last updated: 2026-08-25*