Full Code of zeroclaw-labs/zeroclaw for AI

master b4a2afb04ab1 cached
815 files
10.3 MB
2.7M tokens
11958 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (10,957K chars total). Download the full file to get everything.
Repository: zeroclaw-labs/zeroclaw
Branch: master
Commit: b4a2afb04ab1
Files: 815
Total size: 10.3 MB

Directory structure:
gitextract_ewj5gk9a/

├── .cargo/
│   ├── audit.toml
│   └── config.toml
├── .claude/
│   └── skills/
│       ├── github-issue/
│       │   └── SKILL.md
│       ├── github-pr/
│       │   └── SKILL.md
│       ├── skill-creator/
│       │   ├── LICENSE.txt
│       │   ├── SKILL.md
│       │   ├── agents/
│       │   │   ├── analyzer.md
│       │   │   ├── comparator.md
│       │   │   └── grader.md
│       │   ├── assets/
│       │   │   └── eval_review.html
│       │   ├── eval-viewer/
│       │   │   ├── generate_review.py
│       │   │   └── viewer.html
│       │   ├── references/
│       │   │   └── schemas.md
│       │   └── scripts/
│       │       ├── __init__.py
│       │       ├── aggregate_benchmark.py
│       │       ├── generate_report.py
│       │       ├── improve_description.py
│       │       ├── package_skill.py
│       │       ├── quick_validate.py
│       │       ├── run_eval.py
│       │       ├── run_loop.py
│       │       └── utils.py
│       └── zeroclaw/
│           ├── SKILL.md
│           ├── evals/
│           │   └── evals.json
│           └── references/
│               ├── cli-reference.md
│               └── rest-api.md
├── .coderabbit.yaml
├── .dockerignore
├── .editorconfig
├── .envrc
├── .gemini/
│   └── style-guide.md
├── .gitattributes
├── .githooks/
│   ├── pre-commit
│   └── pre-push
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actionlint.yaml
│   ├── codeql/
│   │   └── codeql-config.yml
│   ├── dependabot.yml
│   ├── label-policy.json
│   ├── labeler.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── README.md
│       ├── checks-on-pr.yml
│       ├── ci-run.yml
│       ├── cross-platform-build-manual.yml
│       ├── master-branch-flow.md
│       ├── pub-aur.yml
│       ├── pub-homebrew-core.yml
│       ├── pub-scoop.yml
│       ├── publish-crates-auto.yml
│       ├── publish-crates.yml
│       ├── release-beta-on-push.yml
│       ├── release-stable-manual.yml
│       └── tweet-release.yml
├── .gitignore
├── .markdownlint-cli2.yaml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile
├── Dockerfile.ci
├── Dockerfile.debian
├── Dockerfile.debian.ci
├── Justfile
├── LICENSE-APACHE
├── LICENSE-MIT
├── NOTICE
├── README.ar.md
├── README.bn.md
├── README.cs.md
├── README.da.md
├── README.de.md
├── README.el.md
├── README.es.md
├── README.fi.md
├── README.fr.md
├── README.he.md
├── README.hi.md
├── README.hu.md
├── README.id.md
├── README.it.md
├── README.ja.md
├── README.ko.md
├── README.md
├── README.nb.md
├── README.nl.md
├── README.pl.md
├── README.pt.md
├── README.ro.md
├── README.ru.md
├── README.sv.md
├── README.th.md
├── README.tl.md
├── README.tr.md
├── README.uk.md
├── README.ur.md
├── README.vi.md
├── README.zh-CN.md
├── SECURITY.md
├── benches/
│   └── agent_benchmarks.rs
├── build.rs
├── clippy.toml
├── crates/
│   └── robot-kit/
│       ├── Cargo.toml
│       ├── PI5_SETUP.md
│       ├── README.md
│       ├── SOUL.md
│       ├── robot.toml
│       └── src/
│           ├── config.rs
│           ├── drive.rs
│           ├── emote.rs
│           ├── lib.rs
│           ├── listen.rs
│           ├── look.rs
│           ├── safety.rs
│           ├── sense.rs
│           ├── speak.rs
│           ├── tests.rs
│           └── traits.rs
├── deny.toml
├── dev/
│   ├── README.md
│   ├── ci/
│   │   └── Dockerfile
│   ├── ci.sh
│   ├── cli.sh
│   ├── config.template.toml
│   ├── docker-compose.ci.yml
│   ├── docker-compose.yml
│   ├── recompute_contributor_tiers.sh
│   ├── sandbox/
│   │   └── Dockerfile
│   └── test-termux-release.sh
├── dist/
│   ├── aur/
│   │   ├── .SRCINFO
│   │   └── PKGBUILD
│   └── scoop/
│       └── zeroclaw.json
├── docker-compose.yml
├── docs/
│   ├── README.ar.md
│   ├── README.bn.md
│   ├── README.cs.md
│   ├── README.da.md
│   ├── README.de.md
│   ├── README.el.md
│   ├── README.es.md
│   ├── README.fi.md
│   ├── README.fr.md
│   ├── README.he.md
│   ├── README.hi.md
│   ├── README.hu.md
│   ├── README.id.md
│   ├── README.it.md
│   ├── README.ja.md
│   ├── README.ko.md
│   ├── README.md
│   ├── README.nb.md
│   ├── README.nl.md
│   ├── README.pl.md
│   ├── README.pt.md
│   ├── README.ro.md
│   ├── README.ru.md
│   ├── README.sv.md
│   ├── README.th.md
│   ├── README.tl.md
│   ├── README.tr.md
│   ├── README.uk.md
│   ├── README.ur.md
│   ├── README.vi.md
│   ├── README.zh-CN.md
│   ├── SUMMARY.ar.md
│   ├── SUMMARY.bn.md
│   ├── SUMMARY.cs.md
│   ├── SUMMARY.da.md
│   ├── SUMMARY.de.md
│   ├── SUMMARY.el.md
│   ├── SUMMARY.es.md
│   ├── SUMMARY.fi.md
│   ├── SUMMARY.fr.md
│   ├── SUMMARY.he.md
│   ├── SUMMARY.hi.md
│   ├── SUMMARY.hu.md
│   ├── SUMMARY.id.md
│   ├── SUMMARY.it.md
│   ├── SUMMARY.ja.md
│   ├── SUMMARY.ko.md
│   ├── SUMMARY.md
│   ├── SUMMARY.nb.md
│   ├── SUMMARY.nl.md
│   ├── SUMMARY.pl.md
│   ├── SUMMARY.pt.md
│   ├── SUMMARY.ro.md
│   ├── SUMMARY.ru.md
│   ├── SUMMARY.sv.md
│   ├── SUMMARY.th.md
│   ├── SUMMARY.tl.md
│   ├── SUMMARY.tr.md
│   ├── SUMMARY.uk.md
│   ├── SUMMARY.ur.md
│   ├── SUMMARY.vi.md
│   ├── SUMMARY.zh-CN.md
│   ├── assets/
│   │   └── architecture-diagrams.md
│   ├── contributing/
│   │   ├── README.md
│   │   ├── actions-source-policy.md
│   │   ├── adding-boards-and-tools.md
│   │   ├── cargo-slicer-speedup.md
│   │   ├── change-playbooks.md
│   │   ├── ci-map.md
│   │   ├── cla.md
│   │   ├── custom-providers.md
│   │   ├── doc-template.md
│   │   ├── docs-contract.md
│   │   ├── extension-examples.md
│   │   ├── langgraph-integration.md
│   │   ├── pr-discipline.md
│   │   ├── pr-workflow.md
│   │   ├── release-process.md
│   │   ├── reviewer-playbook.md
│   │   ├── testing-telegram.md
│   │   └── testing.md
│   ├── hardware/
│   │   ├── README.md
│   │   ├── android-setup.md
│   │   ├── arduino-uno-q-setup.md
│   │   ├── datasheets/
│   │   │   ├── arduino-uno.md
│   │   │   ├── esp32.md
│   │   │   └── nucleo-f401re.md
│   │   ├── hardware-peripherals-design.md
│   │   └── nucleo-setup.md
│   ├── i18n/
│   │   ├── README.md
│   │   └── zh-CN/
│   │       ├── contributing/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── actions-source-policy.zh-CN.md
│   │       │   ├── adding-boards-and-tools.zh-CN.md
│   │       │   ├── cargo-slicer-speedup.zh-CN.md
│   │       │   ├── change-playbooks.zh-CN.md
│   │       │   ├── ci-map.zh-CN.md
│   │       │   ├── cla.zh-CN.md
│   │       │   ├── custom-providers.zh-CN.md
│   │       │   ├── doc-template.zh-CN.md
│   │       │   ├── docs-contract.zh-CN.md
│   │       │   ├── extension-examples.zh-CN.md
│   │       │   ├── langgraph-integration.zh-CN.md
│   │       │   ├── pr-discipline.zh-CN.md
│   │       │   ├── pr-workflow.zh-CN.md
│   │       │   ├── release-process.zh-CN.md
│   │       │   ├── reviewer-playbook.zh-CN.md
│   │       │   ├── testing-telegram.zh-CN.md
│   │       │   └── testing.zh-CN.md
│   │       ├── hardware/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── android-setup.zh-CN.md
│   │       │   ├── arduino-uno-q-setup.zh-CN.md
│   │       │   ├── datasheets/
│   │       │   │   ├── arduino-uno.zh-CN.md
│   │       │   │   ├── esp32.zh-CN.md
│   │       │   │   └── nucleo-f401re.zh-CN.md
│   │       │   ├── hardware-peripherals-design.zh-CN.md
│   │       │   └── nucleo-setup.zh-CN.md
│   │       ├── maintainers/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── docs-inventory.zh-CN.md
│   │       │   ├── i18n-coverage.zh-CN.md
│   │       │   ├── project-triage-snapshot-2026-02-18.zh-CN.md
│   │       │   ├── refactor-candidates.zh-CN.md
│   │       │   ├── repo-map.zh-CN.md
│   │       │   ├── structure-README.zh-CN.md
│   │       │   └── trademark.zh-CN.md
│   │       ├── ops/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── network-deployment.zh-CN.md
│   │       │   ├── operations-runbook.zh-CN.md
│   │       │   ├── proxy-agent-playbook.zh-CN.md
│   │       │   ├── resource-limits.zh-CN.md
│   │       │   └── troubleshooting.zh-CN.md
│   │       ├── reference/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── api/
│   │       │   │   ├── channels-reference.zh-CN.md
│   │       │   │   ├── config-reference.zh-CN.md
│   │       │   │   └── providers-reference.zh-CN.md
│   │       │   ├── cli/
│   │       │   │   └── commands-reference.zh-CN.md
│   │       │   └── sop/
│   │       │       ├── README.zh-CN.md
│   │       │       ├── connectivity.zh-CN.md
│   │       │       ├── cookbook.zh-CN.md
│   │       │       ├── observability.zh-CN.md
│   │       │       └── syntax.zh-CN.md
│   │       ├── security/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── agnostic-security.zh-CN.md
│   │       │   ├── audit-logging.zh-CN.md
│   │       │   ├── frictionless-security.zh-CN.md
│   │       │   ├── matrix-e2ee-guide.zh-CN.md
│   │       │   ├── sandboxing.zh-CN.md
│   │       │   └── security-roadmap.zh-CN.md
│   │       └── setup-guides/
│   │           ├── README.zh-CN.md
│   │           ├── macos-update-uninstall.zh-CN.md
│   │           ├── mattermost-setup.zh-CN.md
│   │           ├── nextcloud-talk-setup.zh-CN.md
│   │           ├── one-click-bootstrap.zh-CN.md
│   │           └── zai-glm-setup.zh-CN.md
│   ├── maintainers/
│   │   ├── README.md
│   │   ├── docs-inventory.md
│   │   ├── i18n-coverage.md
│   │   ├── project-triage-snapshot-2026-02-18.md
│   │   ├── refactor-candidates.md
│   │   ├── repo-map.md
│   │   ├── structure-README.md
│   │   └── trademark.md
│   ├── openai-temperature-compatibility.md
│   ├── ops/
│   │   ├── README.md
│   │   ├── network-deployment.md
│   │   ├── operations-runbook.md
│   │   ├── proxy-agent-playbook.md
│   │   ├── resource-limits.md
│   │   ├── troubleshooting.md
│   │   └── troubleshooting.vi.md
│   ├── reference/
│   │   ├── README.md
│   │   ├── api/
│   │   │   ├── channels-reference.md
│   │   │   ├── config-reference.md
│   │   │   ├── config-reference.vi.md
│   │   │   └── providers-reference.md
│   │   ├── cli/
│   │   │   ├── commands-reference.md
│   │   │   └── commands-reference.vi.md
│   │   └── sop/
│   │       ├── README.md
│   │       ├── connectivity.md
│   │       ├── cookbook.md
│   │       ├── observability.md
│   │       └── syntax.md
│   ├── security/
│   │   ├── README.md
│   │   ├── agnostic-security.md
│   │   ├── audit-logging.md
│   │   ├── frictionless-security.md
│   │   ├── matrix-e2ee-guide.md
│   │   ├── sandboxing.md
│   │   └── security-roadmap.md
│   ├── setup-guides/
│   │   ├── README.md
│   │   ├── README.vi.md
│   │   ├── macos-update-uninstall.md
│   │   ├── mattermost-setup.md
│   │   ├── nextcloud-talk-setup.md
│   │   ├── one-click-bootstrap.md
│   │   ├── one-click-bootstrap.vi.md
│   │   └── zai-glm-setup.md
│   ├── superpowers/
│   │   └── specs/
│   │       └── 2026-03-13-linkedin-tool-design.md
│   └── vi/
│       ├── README.md
│       ├── actions-source-policy.md
│       ├── adding-boards-and-tools.md
│       ├── agnostic-security.md
│       ├── arduino-uno-q-setup.md
│       ├── audit-logging.md
│       ├── channels-reference.md
│       ├── ci-map.md
│       ├── commands-reference.md
│       ├── config-reference.md
│       ├── contributing/
│       │   └── README.md
│       ├── custom-providers.md
│       ├── datasheets/
│       │   ├── arduino-uno.md
│       │   ├── esp32.md
│       │   └── nucleo-f401re.md
│       ├── frictionless-security.md
│       ├── getting-started/
│       │   └── README.md
│       ├── hardware/
│       │   └── README.md
│       ├── hardware-peripherals-design.md
│       ├── langgraph-integration.md
│       ├── matrix-e2ee-guide.md
│       ├── mattermost-setup.md
│       ├── network-deployment.md
│       ├── nucleo-setup.md
│       ├── one-click-bootstrap.md
│       ├── operations/
│       │   └── README.md
│       ├── operations-runbook.md
│       ├── pr-workflow.md
│       ├── project/
│       │   └── README.md
│       ├── providers-reference.md
│       ├── proxy-agent-playbook.md
│       ├── reference/
│       │   └── README.md
│       ├── release-process.md
│       ├── resource-limits.md
│       ├── reviewer-playbook.md
│       ├── sandboxing.md
│       ├── security/
│       │   └── README.md
│       ├── security-roadmap.md
│       ├── troubleshooting.md
│       └── zai-glm-setup.md
├── example-plugin/
│   ├── Cargo.toml
│   ├── manifest.toml
│   └── src/
│       └── lib.rs
├── examples/
│   └── config.example.toml
├── firmware/
│   ├── arduino/
│   │   └── arduino.ino
│   ├── esp32/
│   │   ├── .cargo/
│   │   │   └── config.toml
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── SETUP.md
│   │   ├── build.rs
│   │   ├── rust-toolchain.toml
│   │   └── src/
│   │       └── main.rs
│   ├── esp32-ui/
│   │   ├── .cargo/
│   │   │   └── config.toml
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── build.rs
│   │   ├── src/
│   │   │   └── main.rs
│   │   └── ui/
│   │       └── main.slint
│   ├── nucleo/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   └── uno-q-bridge/
│       ├── app.yaml
│       ├── python/
│       │   ├── main.py
│       │   └── requirements.txt
│       └── sketch/
│           ├── sketch.ino
│           └── sketch.yaml
├── flake.nix
├── fuzz/
│   ├── Cargo.toml
│   └── fuzz_targets/
│       ├── fuzz_command_validation.rs
│       ├── fuzz_config_parse.rs
│       ├── fuzz_provider_response.rs
│       ├── fuzz_tool_params.rs
│       └── fuzz_webhook_payload.rs
├── install.sh
├── python/
│   ├── README.md
│   ├── README.vi.md
│   ├── pyproject.toml
│   ├── tests/
│   │   ├── __init__.py
│   │   └── test_tools.py
│   └── zeroclaw_tools/
│       ├── __init__.py
│       ├── __main__.py
│       ├── agent.py
│       ├── integrations/
│       │   ├── __init__.py
│       │   └── discord_bot.py
│       └── tools/
│           ├── __init__.py
│           ├── base.py
│           ├── file.py
│           ├── memory.py
│           ├── shell.py
│           └── web.py
├── rustfmt.toml
├── scripts/
│   ├── ci/
│   │   ├── check_binary_size.sh
│   │   ├── collect_changed_links.py
│   │   ├── detect_change_scope.sh
│   │   ├── docs_links_gate.sh
│   │   ├── docs_quality_gate.sh
│   │   ├── fetch_actions_data.py
│   │   ├── rust_quality_gate.sh
│   │   └── rust_strict_delta_gate.sh
│   └── release/
│       └── cut_release_tag.sh
├── src/
│   ├── agent/
│   │   ├── agent.rs
│   │   ├── classifier.rs
│   │   ├── dispatcher.rs
│   │   ├── loop_.rs
│   │   ├── memory_loader.rs
│   │   ├── mod.rs
│   │   ├── prompt.rs
│   │   └── tests.rs
│   ├── approval/
│   │   └── mod.rs
│   ├── auth/
│   │   ├── anthropic_token.rs
│   │   ├── gemini_oauth.rs
│   │   ├── mod.rs
│   │   ├── oauth_common.rs
│   │   ├── openai_oauth.rs
│   │   └── profiles.rs
│   ├── channels/
│   │   ├── bluesky.rs
│   │   ├── clawdtalk.rs
│   │   ├── cli.rs
│   │   ├── dingtalk.rs
│   │   ├── discord.rs
│   │   ├── email_channel.rs
│   │   ├── imessage.rs
│   │   ├── irc.rs
│   │   ├── lark.rs
│   │   ├── linq.rs
│   │   ├── matrix.rs
│   │   ├── mattermost.rs
│   │   ├── mochat.rs
│   │   ├── mod.rs
│   │   ├── mqtt.rs
│   │   ├── nextcloud_talk.rs
│   │   ├── nostr.rs
│   │   ├── notion.rs
│   │   ├── qq.rs
│   │   ├── reddit.rs
│   │   ├── session_backend.rs
│   │   ├── session_sqlite.rs
│   │   ├── session_store.rs
│   │   ├── signal.rs
│   │   ├── slack.rs
│   │   ├── telegram.rs
│   │   ├── traits.rs
│   │   ├── transcription.rs
│   │   ├── tts.rs
│   │   ├── twitter.rs
│   │   ├── wati.rs
│   │   ├── webhook.rs
│   │   ├── wecom.rs
│   │   ├── whatsapp.rs
│   │   ├── whatsapp_storage.rs
│   │   └── whatsapp_web.rs
│   ├── commands/
│   │   ├── mod.rs
│   │   ├── self_test.rs
│   │   └── update.rs
│   ├── config/
│   │   ├── mod.rs
│   │   ├── schema.rs
│   │   ├── traits.rs
│   │   └── workspace.rs
│   ├── cost/
│   │   ├── mod.rs
│   │   ├── tracker.rs
│   │   └── types.rs
│   ├── cron/
│   │   ├── mod.rs
│   │   ├── schedule.rs
│   │   ├── scheduler.rs
│   │   ├── store.rs
│   │   └── types.rs
│   ├── daemon/
│   │   └── mod.rs
│   ├── doctor/
│   │   └── mod.rs
│   ├── gateway/
│   │   ├── api.rs
│   │   ├── api_pairing.rs
│   │   ├── api_plugins.rs
│   │   ├── mod.rs
│   │   ├── nodes.rs
│   │   ├── sse.rs
│   │   ├── static_files.rs
│   │   └── ws.rs
│   ├── hands/
│   │   ├── mod.rs
│   │   └── types.rs
│   ├── hardware/
│   │   ├── discover.rs
│   │   ├── introspect.rs
│   │   ├── mod.rs
│   │   └── registry.rs
│   ├── health/
│   │   └── mod.rs
│   ├── heartbeat/
│   │   ├── engine.rs
│   │   ├── mod.rs
│   │   └── store.rs
│   ├── hooks/
│   │   ├── builtin/
│   │   │   ├── command_logger.rs
│   │   │   ├── mod.rs
│   │   │   └── webhook_audit.rs
│   │   ├── mod.rs
│   │   ├── runner.rs
│   │   └── traits.rs
│   ├── i18n.rs
│   ├── identity.rs
│   ├── integrations/
│   │   ├── mod.rs
│   │   └── registry.rs
│   ├── lib.rs
│   ├── main.rs
│   ├── memory/
│   │   ├── backend.rs
│   │   ├── chunker.rs
│   │   ├── cli.rs
│   │   ├── consolidation.rs
│   │   ├── embeddings.rs
│   │   ├── hygiene.rs
│   │   ├── knowledge_graph.rs
│   │   ├── lucid.rs
│   │   ├── markdown.rs
│   │   ├── mod.rs
│   │   ├── none.rs
│   │   ├── postgres.rs
│   │   ├── qdrant.rs
│   │   ├── response_cache.rs
│   │   ├── snapshot.rs
│   │   ├── sqlite.rs
│   │   ├── traits.rs
│   │   └── vector.rs
│   ├── migration.rs
│   ├── multimodal.rs
│   ├── nodes/
│   │   ├── mod.rs
│   │   └── transport.rs
│   ├── observability/
│   │   ├── log.rs
│   │   ├── mod.rs
│   │   ├── multi.rs
│   │   ├── noop.rs
│   │   ├── otel.rs
│   │   ├── prometheus.rs
│   │   ├── runtime_trace.rs
│   │   ├── traits.rs
│   │   └── verbose.rs
│   ├── onboard/
│   │   ├── mod.rs
│   │   └── wizard.rs
│   ├── peripherals/
│   │   ├── arduino_flash.rs
│   │   ├── arduino_upload.rs
│   │   ├── capabilities_tool.rs
│   │   ├── mod.rs
│   │   ├── nucleo_flash.rs
│   │   ├── rpi.rs
│   │   ├── serial.rs
│   │   ├── traits.rs
│   │   ├── uno_q_bridge.rs
│   │   └── uno_q_setup.rs
│   ├── plugins/
│   │   ├── error.rs
│   │   ├── host.rs
│   │   ├── mod.rs
│   │   ├── wasm_channel.rs
│   │   └── wasm_tool.rs
│   ├── providers/
│   │   ├── anthropic.rs
│   │   ├── azure_openai.rs
│   │   ├── bedrock.rs
│   │   ├── claude_code.rs
│   │   ├── compatible.rs
│   │   ├── copilot.rs
│   │   ├── gemini.rs
│   │   ├── gemini_cli.rs
│   │   ├── glm.rs
│   │   ├── kilocli.rs
│   │   ├── mod.rs
│   │   ├── ollama.rs
│   │   ├── openai.rs
│   │   ├── openai_codex.rs
│   │   ├── openrouter.rs
│   │   ├── reliable.rs
│   │   ├── router.rs
│   │   ├── telnyx.rs
│   │   └── traits.rs
│   ├── rag/
│   │   └── mod.rs
│   ├── runtime/
│   │   ├── docker.rs
│   │   ├── mod.rs
│   │   ├── native.rs
│   │   ├── traits.rs
│   │   └── wasm.rs
│   ├── security/
│   │   ├── audit.rs
│   │   ├── bubblewrap.rs
│   │   ├── detect.rs
│   │   ├── docker.rs
│   │   ├── domain_matcher.rs
│   │   ├── estop.rs
│   │   ├── firejail.rs
│   │   ├── iam_policy.rs
│   │   ├── landlock.rs
│   │   ├── leak_detector.rs
│   │   ├── mod.rs
│   │   ├── nevis.rs
│   │   ├── otp.rs
│   │   ├── pairing.rs
│   │   ├── playbook.rs
│   │   ├── policy.rs
│   │   ├── prompt_guard.rs
│   │   ├── secrets.rs
│   │   ├── traits.rs
│   │   ├── vulnerability.rs
│   │   └── workspace_boundary.rs
│   ├── service/
│   │   └── mod.rs
│   ├── skillforge/
│   │   ├── evaluate.rs
│   │   ├── integrate.rs
│   │   ├── mod.rs
│   │   └── scout.rs
│   ├── skills/
│   │   ├── audit.rs
│   │   ├── creator.rs
│   │   ├── mod.rs
│   │   └── symlink_tests.rs
│   ├── sop/
│   │   ├── audit.rs
│   │   ├── condition.rs
│   │   ├── dispatch.rs
│   │   ├── engine.rs
│   │   ├── gates.rs
│   │   ├── metrics.rs
│   │   ├── mod.rs
│   │   └── types.rs
│   ├── tools/
│   │   ├── backup_tool.rs
│   │   ├── browser.rs
│   │   ├── browser_delegate.rs
│   │   ├── browser_open.rs
│   │   ├── calculator.rs
│   │   ├── cli_discovery.rs
│   │   ├── cloud_ops.rs
│   │   ├── cloud_patterns.rs
│   │   ├── composio.rs
│   │   ├── content_search.rs
│   │   ├── cron_add.rs
│   │   ├── cron_list.rs
│   │   ├── cron_remove.rs
│   │   ├── cron_run.rs
│   │   ├── cron_runs.rs
│   │   ├── cron_update.rs
│   │   ├── data_management.rs
│   │   ├── delegate.rs
│   │   ├── file_edit.rs
│   │   ├── file_read.rs
│   │   ├── file_write.rs
│   │   ├── git_operations.rs
│   │   ├── glob_search.rs
│   │   ├── google_workspace.rs
│   │   ├── hardware_board_info.rs
│   │   ├── hardware_memory_map.rs
│   │   ├── hardware_memory_read.rs
│   │   ├── http_request.rs
│   │   ├── image_info.rs
│   │   ├── jira_tool.rs
│   │   ├── knowledge_tool.rs
│   │   ├── linkedin.rs
│   │   ├── linkedin_client.rs
│   │   ├── mcp_client.rs
│   │   ├── mcp_deferred.rs
│   │   ├── mcp_protocol.rs
│   │   ├── mcp_tool.rs
│   │   ├── mcp_transport.rs
│   │   ├── memory_forget.rs
│   │   ├── memory_recall.rs
│   │   ├── memory_store.rs
│   │   ├── microsoft365/
│   │   │   ├── auth.rs
│   │   │   ├── graph_client.rs
│   │   │   ├── mod.rs
│   │   │   └── types.rs
│   │   ├── mod.rs
│   │   ├── model_routing_config.rs
│   │   ├── model_switch.rs
│   │   ├── node_tool.rs
│   │   ├── notion_tool.rs
│   │   ├── pdf_read.rs
│   │   ├── project_intel.rs
│   │   ├── proxy_config.rs
│   │   ├── pushover.rs
│   │   ├── read_skill.rs
│   │   ├── report_templates.rs
│   │   ├── schedule.rs
│   │   ├── schema.rs
│   │   ├── screenshot.rs
│   │   ├── security_ops.rs
│   │   ├── shell.rs
│   │   ├── sop_advance.rs
│   │   ├── sop_approve.rs
│   │   ├── sop_execute.rs
│   │   ├── sop_list.rs
│   │   ├── sop_status.rs
│   │   ├── swarm.rs
│   │   ├── text_browser.rs
│   │   ├── tool_search.rs
│   │   ├── traits.rs
│   │   ├── web_fetch.rs
│   │   ├── web_search_tool.rs
│   │   └── workspace_tool.rs
│   ├── tunnel/
│   │   ├── cloudflare.rs
│   │   ├── custom.rs
│   │   ├── mod.rs
│   │   ├── ngrok.rs
│   │   ├── none.rs
│   │   ├── openvpn.rs
│   │   └── tailscale.rs
│   └── util.rs
├── taplo.toml
├── tests/
│   ├── component/
│   │   ├── config_persistence.rs
│   │   ├── config_schema.rs
│   │   ├── dockerignore_test.rs
│   │   ├── gateway.rs
│   │   ├── mod.rs
│   │   ├── otel_dependency_feature_regression.rs
│   │   ├── provider_resolution.rs
│   │   ├── provider_schema.rs
│   │   ├── reply_target_field_regression.rs
│   │   ├── security.rs
│   │   └── whatsapp_webhook_security.rs
│   ├── fixtures/
│   │   └── traces/
│   │       ├── multi_tool_chain.json
│   │       ├── single_tool_echo.json
│   │       └── smoke_greeting.json
│   ├── integration/
│   │   ├── agent.rs
│   │   ├── agent_robustness.rs
│   │   ├── channel_matrix.rs
│   │   ├── channel_routing.rs
│   │   ├── hooks.rs
│   │   ├── memory_comparison.rs
│   │   ├── memory_restart.rs
│   │   ├── mod.rs
│   │   ├── telegram_attachment_fallback.rs
│   │   └── telegram_finalize_draft.rs
│   ├── live/
│   │   ├── gemini_fallback_oauth_refresh.rs
│   │   ├── mod.rs
│   │   ├── openai_codex_vision_e2e.rs
│   │   └── providers.rs
│   ├── manual/
│   │   ├── telegram/
│   │   │   ├── generate_test_messages.py
│   │   │   ├── quick_test.sh
│   │   │   ├── test_telegram_integration.sh
│   │   │   └── testing-telegram.md
│   │   └── test_dockerignore.sh
│   ├── support/
│   │   ├── assertions.rs
│   │   ├── helpers.rs
│   │   ├── mock_channel.rs
│   │   ├── mock_provider.rs
│   │   ├── mock_tools.rs
│   │   ├── mod.rs
│   │   └── trace.rs
│   ├── system/
│   │   ├── full_stack.rs
│   │   └── mod.rs
│   ├── test_component.rs
│   ├── test_integration.rs
│   ├── test_live.rs
│   └── test_system.rs
├── tool_descriptions/
│   ├── en.toml
│   └── zh-CN.toml
└── web/
    ├── .gitignore
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   └── layout/
    │   │       ├── Header.tsx
    │   │       ├── Layout.tsx
    │   │       └── Sidebar.tsx
    │   ├── hooks/
    │   │   ├── useApi.ts
    │   │   ├── useAuth.ts
    │   │   ├── useDevices.ts
    │   │   ├── useDraft.ts
    │   │   ├── useSSE.ts
    │   │   └── useWebSocket.ts
    │   ├── index.css
    │   ├── lib/
    │   │   ├── api.ts
    │   │   ├── auth.ts
    │   │   ├── i18n.ts
    │   │   ├── sse.ts
    │   │   ├── uuid.ts
    │   │   └── ws.ts
    │   ├── main.tsx
    │   ├── pages/
    │   │   ├── AgentChat.tsx
    │   │   ├── Config.tsx
    │   │   ├── Cost.tsx
    │   │   ├── Cron.tsx
    │   │   ├── Dashboard.tsx
    │   │   ├── Doctor.tsx
    │   │   ├── Integrations.tsx
    │   │   ├── Logs.tsx
    │   │   ├── Memory.tsx
    │   │   ├── Pairing.tsx
    │   │   └── Tools.tsx
    │   ├── types/
    │   │   └── api.ts
    │   └── vite-env.d.ts
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .cargo/audit.toml
================================================
# cargo-audit configuration
# https://rustsec.org/

[advisories]
ignore = [
    # wasmtime vulns via extism 1.13.0 — no upstream fix; plugins feature-gated
    "RUSTSEC-2026-0006",  # wasmtime f64.copysign segfault on x86-64
    "RUSTSEC-2026-0020",  # WASI guest-controlled resource exhaustion
    "RUSTSEC-2026-0021",  # WASI http fields panic
]


================================================
FILE: .cargo/config.toml
================================================
[target.x86_64-unknown-linux-musl]
rustflags = ["-C", "link-arg=-static"]

[target.aarch64-unknown-linux-musl]
rustflags = ["-C", "link-arg=-static"]

# Android targets (NDK toolchain)
[target.armv7-linux-androideabi]
linker = "armv7a-linux-androideabi21-clang"

[target.aarch64-linux-android]
linker = "aarch64-linux-android21-clang"


================================================
FILE: .claude/skills/github-issue/SKILL.md
================================================
# Skill: github-issue

File a structured GitHub issue (bug report or feature request) for ZeroClaw interactively from Claude Code.

## When to Use

Trigger when the user wants to file a GitHub issue, report a bug, or request a feature for ZeroClaw. Keywords: "file issue", "report bug", "feature request", "open issue", "create issue", "github issue".

## Instructions

You are filing a GitHub issue against the ZeroClaw repository using structured issue forms. Follow this workflow exactly.

### Step 1: Detect Issue Type and Read the Template

Determine from the user's message whether this is a **bug report** or **feature request**.
- If unclear, use AskUserQuestion to ask: "Is this a bug report or a feature request?"

Then read the corresponding issue template to understand the required fields:

- Bug report: `.github/ISSUE_TEMPLATE/bug_report.yml`
- Feature request: `.github/ISSUE_TEMPLATE/feature_request.yml`

Parse the YAML to extract:
- The `title` prefix (e.g. `[Bug]: `, `[Feature]: `)
- The `labels` array
- Each field in the `body` array: its `type` (dropdown, textarea, input, checkboxes, markdown), `id`, `attributes.label`, `attributes.options` (for dropdowns), `attributes.description`, `attributes.placeholder`, and `validations.required`

This is the source of truth for what fields exist, what they're called, what options are available, and which are required. Do not assume or hardcode any field names or options — always derive them from the template file.

### Step 2: Auto-Gather Context

Before asking the user anything, silently gather environment and repo context:

```bash
# Git context
git log --oneline -5
git status --short
git diff --stat HEAD~1 2>/dev/null

# For bug reports — environment detection
uname -s -r -m                          # OS info
sw_vers 2>/dev/null                     # macOS version
rustc --version 2>/dev/null             # Rust version
cargo metadata --format-version=1 --no-deps 2>/dev/null | jq -r '.packages[] | select(.name=="zeroclaw") | .version' 2>/dev/null   # ZeroClaw version
git rev-parse --short HEAD              # commit SHA fallback
```

Also read recently changed files to infer the affected component and architecture impact.

### Step 3: Pre-Fill and Present the Form

Using the parsed template fields and gathered context, draft values for ALL fields from the template:

- **dropdown** fields: select the most likely option from `attributes.options` based on context. For dropdowns where you're uncertain, note your best guess and flag it for the user.
- **textarea** fields: draft content based on the user's description, git context, and the field's `attributes.description`/`attributes.placeholder` for guidance on what's expected.
- **input** fields: fill with auto-detected values (versions, OS) or draft from user context.
- **checkboxes** fields: auto-check all items (the skill itself ensures compliance with the stated checks).
- **markdown** fields: skip these — they're informational headers, not form inputs.
- **optional fields** (where `validations.required` is false): fill if there's enough context, otherwise note "(optional — not enough context to fill)".

Present the complete draft to the user in a clean readable format:

```
## Issue Draft: [Bug]: <title> / [Feature]: <title>
**Labels**: <from template>

### <Field Label>
<proposed value or selection>

### <Field Label>
<proposed value>
...
```

Use AskUserQuestion to ask the user to review:
- "Here's the pre-filled issue. Please review and let me know what to change, or say 'submit' to file it."

If the user requests changes, update the draft and re-present. Iterate until the user approves.

### Step 4: Scope Guard

Before final submission, analyze the collected content for scope creep:
- Does the bug report describe multiple independent defects?
- Does the feature request bundle unrelated changes?

If multi-concept issues are detected:
1. Inform the user: "This issue appears to cover multiple distinct topics. Focused, single-concept issues are strongly preferred and more likely to be accepted."
2. Break down the distinct groups found.
3. Offer to file separate issues for each group, reusing shared context (environment, etc.).
4. Let the user decide: proceed as-is or split.

### Step 5: Construct Issue Body

Build the issue body as markdown sections matching GitHub's form-field rendering format. GitHub renders form-submitted issues with `### <Field Label>` sections, so use that exact structure.

For each non-markdown field from the template, in order:

```markdown
### <attributes.label>

<value>
```

For optional fields with no content, use `_No response_` as the value (this matches GitHub's native rendering for empty optional fields).

For checkbox fields, render each option as:
```markdown
- [X] <option label text>
```

### Step 6: Final Preview and Submit

Show the final constructed issue (title + labels + full body) for one last confirmation.

Then submit using a HEREDOC for the body to preserve formatting:

```bash
gh issue create --title "<title prefix><user title>" --label "<label1>,<label2>" --body "$(cat <<'ISSUE_EOF'
<body content>
ISSUE_EOF
)"
```

Return the resulting issue URL to the user.

### Important Rules

- **Always read the template file** — never assume field names, options, or structure. The templates are the source of truth and may change over time.
- **Never include personal/sensitive data** in the issue. Redact secrets, tokens, emails, real names.
- **Use neutral project-scoped placeholders** per ZeroClaw's privacy contract.
- **One concept per issue** — enforce the scope guard.
- **Auto-detect, don't guess** — use real command output for environment fields.
- **Match GitHub's rendering** — use `### Field Label` sections so issues look consistent whether filed via web UI or this skill.


================================================
FILE: .claude/skills/github-pr/SKILL.md
================================================
# Skill: github-pr

Open or update a GitHub Pull Request for ZeroClaw. Handles creating new PRs with a fully filled-out template body, and updating existing PRs (title, body sections, labels, comments). Use this skill whenever the user wants to open a PR, create a pull request, update a PR, edit PR description, add labels to a PR, or sync a PR after new commits — even if they don't say "PR" explicitly (e.g., "submit this for review", "push and open for merge").

## Instructions

This skill supports two modes: **Open** (create a new PR) and **Update** (edit an existing PR). Detect the mode from context — if there's already an open PR for the current branch and the user didn't say "open a new PR", default to update mode.

The PR template at `.github/pull_request_template.md` is the source of truth for the PR body structure. Read it every time — never assume or hardcode section names, fields, or their order. The template may change over time and the skill should always reflect its current state.

---

## Shared: Read the PR Template

Before opening or updating a PR body, read `.github/pull_request_template.md` and parse it to understand:

- The `## ` section headers (these are the top-level sections of the PR body)
- The bullet points, fields, and prompts within each section
- Which sections are marked `(required)` vs optional/recommended
- Any inline formatting conventions (backtick options, Yes/No fields, etc.)

This parsed structure drives how you fill, present, and edit the PR body.

---

## Mode: Open a New PR

### Step 1: Gather Context

Collect information to pre-fill the PR body. Run these in parallel:

```bash
# Branch and commit context
git branch --show-current
git log master..HEAD --oneline
git diff master...HEAD --stat

# Check if branch is pushed
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null

# Environment (for validation evidence)
rustc --version 2>/dev/null
```

Also review the changed files and commit messages to understand the nature of the change (bug fix, feature, refactor, docs, chore, etc.) and which subsystems are affected.

### Step 2: Pre-Fill the Template

Using the parsed template structure and gathered context, draft a complete PR body:

- For each `## ` section from the template, fill in the bullet points and fields based on context from the commits, diff, and changed files.
- Use the field descriptions and placeholder text in the template as guidance for what each field expects.
- For Yes/No fields, infer from the diff (e.g., if no files in `src/security/` changed, security impact is likely all No).
- For required sections, always provide a substantive answer. For optional sections, fill if there's enough context, otherwise leave the template prompts in place.
- Draft a conventional commit-style PR title based on the changes (e.g., `feat(provider): add retry budget override`, `fix(channel): handle disconnect gracefully`, `chore(ci): update workflow targets`).

### Step 3: Present Draft for Review

Show the user the complete draft:

```
## PR Draft: <title>
**Branch**: <head> -> master
**Labels**: <suggested labels>

<full body with all sections filled>
```

Ask the user to review: "Here's the pre-filled PR. Review and let me know what to change, or say 'submit' to open it."

Iterate on changes until the user approves.

### Step 4: Push and Create

1. If the branch isn't pushed yet, push it:
   ```bash
   git push -u origin <branch>
   ```

2. Create the PR using a HEREDOC for the body:
   ```bash
   gh pr create --title "<title>" --base master --body "$(cat <<'PR_BODY_EOF'
   <full body>
   PR_BODY_EOF
   )"
   ```

3. If labels were agreed on, add them:
   ```bash
   gh pr edit <number> --add-label "<label1>,<label2>"
   ```

4. Return the PR URL to the user.

---

## Mode: Update an Existing PR

### Step 1: Identify the PR

1. **If a PR number or URL is given**: use that directly.
2. **If on a branch with an open PR**: auto-detect:
   ```bash
   gh pr view --json number,title,body,labels,state,author,url,headRefName 2>/dev/null
   ```
3. **If neither**: ask the user for the PR number.

Verify the current user is the PR author:
```bash
CURRENT_USER=$(gh api user --jq '.login')
PR_AUTHOR=$(gh pr view <number> --json author --jq '.author.login')
```
If not the author, stop and inform the user.

### Step 2: Fetch Current State

```bash
gh pr view <number> --json number,title,body,labels,state,baseRefName,headRefName,url,author,reviewDecision,statusCheckRollup,commits
```

Display a summary:
```
## PR #<number>: <title>
**State**: <open/closed/merged>
**Branch**: <head> -> <base>
**Labels**: <label list>
**Checks**: <pass/fail/pending>
**URL**: <url>
```

### Step 3: Determine What to Update

Support these operations:

| Operation | How |
|---|---|
| **Edit title** | `gh pr edit <number> --title "<new title>"` |
| **Edit full body** | `gh pr edit <number> --body "<new body>"` |
| **Add labels** | `gh pr edit <number> --add-label "<label1>,<label2>"` |
| **Remove labels** | `gh pr edit <number> --remove-label "<label1>"` |
| **Edit specific section** | Parse body by `## ` headers, modify target section, re-submit full body |
| **Add a comment** | `gh pr comment <number> --body "<comment>"` |
| **Link an issue** | Edit the linked-issue section in the body |
| **Smart update after new commits** | Re-analyze and suggest section updates |

### Step 4: Handle Body Section Edits

When editing a specific section:

1. Parse the current PR body into sections by `## ` headers
2. Match the user's request to the corresponding section from the template
3. Show the current content of that section and the proposed replacement
4. On confirmation, modify only that section, reconstruct the full body, and submit

### Step 5: Smart Update After New Commits

When the user wants to sync the PR description after pushing new changes:

1. Identify new commits:
   ```bash
   gh pr view <number> --json commits --jq '.commits[].messageHeadline'
   git log <base>..<head> --oneline
   git diff <base>...<head> --stat
   ```

2. Re-read the PR template. Analyze which sections are now stale based on the new changes — use the template's section names and field descriptions to identify what needs updating rather than relying on hardcoded assumptions.

3. Present proposed updates section-by-section and confirm before applying.

### Step 6: Apply Updates

For title/label changes, use direct `gh pr edit` flags.

For body edits, use a HEREDOC:
```bash
gh pr edit <number> --body "$(cat <<'PR_BODY_EOF'
<full updated body>
PR_BODY_EOF
)"
```

For comments:
```bash
gh pr comment <number> --body "$(cat <<'COMMENT_EOF'
<comment text>
COMMENT_EOF
)"
```

### Step 7: Confirm

Fetch and display the updated state:
```bash
gh pr view <number> --json number,title,labels,url
```

Return the PR URL.

---

## Important Rules

- **Always read `.github/pull_request_template.md`** before filling or editing a PR body. Never assume section names, fields, or structure — derive everything from the template. It's the source of truth and may change.
- **For updates, only modify requested sections.** Preserve everything else exactly as-is.
- **Always show diffs before applying body edits.** Present current vs proposed for each changed section.
- **Never include personal/sensitive data** in PR content per ZeroClaw's privacy contract.
- **For label changes**, only use labels that exist in the repository. Check with `gh label list` if unsure.
- **Fetch the latest body before editing** to avoid clobbering concurrent changes.
- **For new PRs**, push the branch before creating (with `-u` to set upstream tracking).


================================================
FILE: .claude/skills/skill-creator/LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: .claude/skills/skill-creator/SKILL.md
================================================
---
name: skill-creator
description: Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
---

# Skill Creator

A skill for creating new skills and iteratively improving them.

At a high level, the process of creating a skill goes like this:

- Decide what you want the skill to do and roughly how it should do it
- Write a draft of the skill
- Create a few test prompts and run claude-with-access-to-the-skill on them
- Help the user evaluate the results both qualitatively and quantitatively
  - While the runs happen in the background, draft some quantitative evals if there aren't any (if there are some, you can either use as is or modify if you feel something needs to change about them). Then explain them to the user (or if they already existed, explain the ones that already exist)
  - Use the `eval-viewer/generate_review.py` script to show the user the results for them to look at, and also let them look at the quantitative metrics
- Rewrite the skill based on feedback from the user's evaluation of the results (and also if there are any glaring flaws that become apparent from the quantitative benchmarks)
- Repeat until you're satisfied
- Expand the test set and try again at larger scale

Your job when using this skill is to figure out where the user is in this process and then jump in and help them progress through these stages. So for instance, maybe they're like "I want to make a skill for X". You can help narrow down what they mean, write a draft, write the test cases, figure out how they want to evaluate, run all the prompts, and repeat.

On the other hand, maybe they already have a draft of the skill. In this case you can go straight to the eval/iterate part of the loop.

Of course, you should always be flexible and if the user is like "I don't need to run a bunch of evaluations, just vibe with me", you can do that instead.

Then after the skill is done (but again, the order is flexible), you can also run the skill description improver, which we have a whole separate script for, to optimize the triggering of the skill.

Cool? Cool.

## Communicating with the user

The skill creator is liable to be used by people across a wide range of familiarity with coding jargon. If you haven't heard (and how could you, it's only very recently that it started), there's a trend now where the power of Claude is inspiring plumbers to open up their terminals, parents and grandparents to google "how to install npm". On the other hand, the bulk of users are probably fairly computer-literate.

So please pay attention to context cues to understand how to phrase your communication! In the default case, just to give you some idea:

- "evaluation" and "benchmark" are borderline, but OK
- for "JSON" and "assertion" you want to see serious cues from the user that they know what those things are before using them without explaining them

It's OK to briefly explain terms if you're in doubt, and feel free to clarify terms with a short definition if you're unsure if the user will get it.

---

## Creating a skill

### Capture Intent

Start by understanding the user's intent. The current conversation might already contain a workflow the user wants to capture (e.g., they say "turn this into a skill"). If so, extract answers from the conversation history first — the tools used, the sequence of steps, corrections the user made, input/output formats observed. The user may need to fill the gaps, and should confirm before proceeding to the next step.

1. What should this skill enable Claude to do?
2. When should this skill trigger? (what user phrases/contexts)
3. What's the expected output format?
4. Should we set up test cases to verify the skill works? Skills with objectively verifiable outputs (file transforms, data extraction, code generation, fixed workflow steps) benefit from test cases. Skills with subjective outputs (writing style, art) often don't need them. Suggest the appropriate default based on the skill type, but let the user decide.

### Interview and Research

Proactively ask questions about edge cases, input/output formats, example files, success criteria, and dependencies. Wait to write test prompts until you've got this part ironed out.

Check available MCPs - if useful for research (searching docs, finding similar skills, looking up best practices), research in parallel via subagents if available, otherwise inline. Come prepared with context to reduce burden on the user.

### Write the SKILL.md

Based on the user interview, fill in these components:

- **name**: Skill identifier
- **description**: When to trigger, what it does. This is the primary triggering mechanism - include both what the skill does AND specific contexts for when to use it. All "when to use" info goes here, not in the body. Note: currently Claude has a tendency to "undertrigger" skills -- to not use them when they'd be useful. To combat this, please make the skill descriptions a little bit "pushy". So for instance, instead of "How to build a simple fast dashboard to display internal Anthropic data.", you might write "How to build a simple fast dashboard to display internal Anthropic data. Make sure to use this skill whenever the user mentions dashboards, data visualization, internal metrics, or wants to display any kind of company data, even if they don't explicitly ask for a 'dashboard.'"
- **compatibility**: Required tools, dependencies (optional, rarely needed)
- **the rest of the skill :)**

### Skill Writing Guide

#### Anatomy of a Skill

```
skill-name/
├── SKILL.md (required)
│   ├── YAML frontmatter (name, description required)
│   └── Markdown instructions
└── Bundled Resources (optional)
    ├── scripts/    - Executable code for deterministic/repetitive tasks
    ├── references/ - Docs loaded into context as needed
    └── assets/     - Files used in output (templates, icons, fonts)
```

#### Progressive Disclosure

Skills use a three-level loading system:
1. **Metadata** (name + description) - Always in context (~100 words)
2. **SKILL.md body** - In context whenever skill triggers (<500 lines ideal)
3. **Bundled resources** - As needed (unlimited, scripts can execute without loading)

These word counts are approximate and you can feel free to go longer if needed.

**Key patterns:**
- Keep SKILL.md under 500 lines; if you're approaching this limit, add an additional layer of hierarchy along with clear pointers about where the model using the skill should go next to follow up.
- Reference files clearly from SKILL.md with guidance on when to read them
- For large reference files (>300 lines), include a table of contents

**Domain organization**: When a skill supports multiple domains/frameworks, organize by variant:
```
cloud-deploy/
├── SKILL.md (workflow + selection)
└── references/
    ├── aws.md
    ├── gcp.md
    └── azure.md
```
Claude reads only the relevant reference file.

#### Principle of Lack of Surprise

This goes without saying, but skills must not contain malware, exploit code, or any content that could compromise system security. A skill's contents should not surprise the user in their intent if described. Don't go along with requests to create misleading skills or skills designed to facilitate unauthorized access, data exfiltration, or other malicious activities. Things like a "roleplay as an XYZ" are OK though.

#### Writing Patterns

Prefer using the imperative form in instructions.

**Defining output formats** - You can do it like this:
```markdown
## Report structure
ALWAYS use this exact template:
# [Title]
## Executive summary
## Key findings
## Recommendations
```

**Examples pattern** - It's useful to include examples. You can format them like this (but if "Input" and "Output" are in the examples you might want to deviate a little):
```markdown
## Commit message format
**Example 1:**
Input: Added user authentication with JWT tokens
Output: feat(auth): implement JWT-based authentication
```

### Writing Style

Try to explain to the model why things are important in lieu of heavy-handed musty MUSTs. Use theory of mind and try to make the skill general and not super-narrow to specific examples. Start by writing a draft and then look at it with fresh eyes and improve it.

### Test Cases

After writing the skill draft, come up with 2-3 realistic test prompts — the kind of thing a real user would actually say. Share them with the user: [you don't have to use this exact language] "Here are a few test cases I'd like to try. Do these look right, or do you want to add more?" Then run them.

Save test cases to `evals/evals.json`. Don't write assertions yet — just the prompts. You'll draft assertions in the next step while the runs are in progress.

```json
{
  "skill_name": "example-skill",
  "evals": [
    {
      "id": 1,
      "prompt": "User's task prompt",
      "expected_output": "Description of expected result",
      "files": []
    }
  ]
}
```

See `references/schemas.md` for the full schema (including the `assertions` field, which you'll add later).

## Running and evaluating test cases

This section is one continuous sequence — don't stop partway through. Do NOT use `/skill-test` or any other testing skill.

Put results in `<skill-name>-workspace/` as a sibling to the skill directory. Within the workspace, organize results by iteration (`iteration-1/`, `iteration-2/`, etc.) and within that, each test case gets a directory (`eval-0/`, `eval-1/`, etc.). Don't create all of this upfront — just create directories as you go.

### Step 1: Spawn all runs (with-skill AND baseline) in the same turn

For each test case, spawn two subagents in the same turn — one with the skill, one without. This is important: don't spawn the with-skill runs first and then come back for baselines later. Launch everything at once so it all finishes around the same time.

**With-skill run:**

```
Execute this task:
- Skill path: <path-to-skill>
- Task: <eval prompt>
- Input files: <eval files if any, or "none">
- Save outputs to: <workspace>/iteration-<N>/eval-<ID>/with_skill/outputs/
- Outputs to save: <what the user cares about — e.g., "the .docx file", "the final CSV">
```

**Baseline run** (same prompt, but the baseline depends on context):
- **Creating a new skill**: no skill at all. Same prompt, no skill path, save to `without_skill/outputs/`.
- **Improving an existing skill**: the old version. Before editing, snapshot the skill (`cp -r <skill-path> <workspace>/skill-snapshot/`), then point the baseline subagent at the snapshot. Save to `old_skill/outputs/`.

Write an `eval_metadata.json` for each test case (assertions can be empty for now). Give each eval a descriptive name based on what it's testing — not just "eval-0". Use this name for the directory too. If this iteration uses new or modified eval prompts, create these files for each new eval directory — don't assume they carry over from previous iterations.

```json
{
  "eval_id": 0,
  "eval_name": "descriptive-name-here",
  "prompt": "The user's task prompt",
  "assertions": []
}
```

### Step 2: While runs are in progress, draft assertions

Don't just wait for the runs to finish — you can use this time productively. Draft quantitative assertions for each test case and explain them to the user. If assertions already exist in `evals/evals.json`, review them and explain what they check.

Good assertions are objectively verifiable and have descriptive names — they should read clearly in the benchmark viewer so someone glancing at the results immediately understands what each one checks. Subjective skills (writing style, design quality) are better evaluated qualitatively — don't force assertions onto things that need human judgment.

Update the `eval_metadata.json` files and `evals/evals.json` with the assertions once drafted. Also explain to the user what they'll see in the viewer — both the qualitative outputs and the quantitative benchmark.

### Step 3: As runs complete, capture timing data

When each subagent task completes, you receive a notification containing `total_tokens` and `duration_ms`. Save this data immediately to `timing.json` in the run directory:

```json
{
  "total_tokens": 84852,
  "duration_ms": 23332,
  "total_duration_seconds": 23.3
}
```

This is the only opportunity to capture this data — it comes through the task notification and isn't persisted elsewhere. Process each notification as it arrives rather than trying to batch them.

### Step 4: Grade, aggregate, and launch the viewer

Once all runs are done:

1. **Grade each run** — spawn a grader subagent (or grade inline) that reads `agents/grader.md` and evaluates each assertion against the outputs. Save results to `grading.json` in each run directory. The grading.json expectations array must use the fields `text`, `passed`, and `evidence` (not `name`/`met`/`details` or other variants) — the viewer depends on these exact field names. For assertions that can be checked programmatically, write and run a script rather than eyeballing it — scripts are faster, more reliable, and can be reused across iterations.

2. **Aggregate into benchmark** — run the aggregation script from the skill-creator directory:
   ```bash
   python -m scripts.aggregate_benchmark <workspace>/iteration-N --skill-name <name>
   ```
   This produces `benchmark.json` and `benchmark.md` with pass_rate, time, and tokens for each configuration, with mean ± stddev and the delta. If generating benchmark.json manually, see `references/schemas.md` for the exact schema the viewer expects.
Put each with_skill version before its baseline counterpart.

3. **Do an analyst pass** — read the benchmark data and surface patterns the aggregate stats might hide. See `agents/analyzer.md` (the "Analyzing Benchmark Results" section) for what to look for — things like assertions that always pass regardless of skill (non-discriminating), high-variance evals (possibly flaky), and time/token tradeoffs.

4. **Launch the viewer** with both qualitative outputs and quantitative data:
   ```bash
   nohup python <skill-creator-path>/eval-viewer/generate_review.py \
     <workspace>/iteration-N \
     --skill-name "my-skill" \
     --benchmark <workspace>/iteration-N/benchmark.json \
     > /dev/null 2>&1 &
   VIEWER_PID=$!
   ```
   For iteration 2+, also pass `--previous-workspace <workspace>/iteration-<N-1>`.

   **Cowork / headless environments:** If `webbrowser.open()` is not available or the environment has no display, use `--static <output_path>` to write a standalone HTML file instead of starting a server. Feedback will be downloaded as a `feedback.json` file when the user clicks "Submit All Reviews". After download, copy `feedback.json` into the workspace directory for the next iteration to pick up.

Note: please use generate_review.py to create the viewer; there's no need to write custom HTML.

5. **Tell the user** something like: "I've opened the results in your browser. There are two tabs — 'Outputs' lets you click through each test case and leave feedback, 'Benchmark' shows the quantitative comparison. When you're done, come back here and let me know."

### What the user sees in the viewer

The "Outputs" tab shows one test case at a time:
- **Prompt**: the task that was given
- **Output**: the files the skill produced, rendered inline where possible
- **Previous Output** (iteration 2+): collapsed section showing last iteration's output
- **Formal Grades** (if grading was run): collapsed section showing assertion pass/fail
- **Feedback**: a textbox that auto-saves as they type
- **Previous Feedback** (iteration 2+): their comments from last time, shown below the textbox

The "Benchmark" tab shows the stats summary: pass rates, timing, and token usage for each configuration, with per-eval breakdowns and analyst observations.

Navigation is via prev/next buttons or arrow keys. When done, they click "Submit All Reviews" which saves all feedback to `feedback.json`.

### Step 5: Read the feedback

When the user tells you they're done, read `feedback.json`:

```json
{
  "reviews": [
    {"run_id": "eval-0-with_skill", "feedback": "the chart is missing axis labels", "timestamp": "..."},
    {"run_id": "eval-1-with_skill", "feedback": "", "timestamp": "..."},
    {"run_id": "eval-2-with_skill", "feedback": "perfect, love this", "timestamp": "..."}
  ],
  "status": "complete"
}
```

Empty feedback means the user thought it was fine. Focus your improvements on the test cases where the user had specific complaints.

Kill the viewer server when you're done with it:

```bash
kill $VIEWER_PID 2>/dev/null
```

---

## Improving the skill

This is the heart of the loop. You've run the test cases, the user has reviewed the results, and now you need to make the skill better based on their feedback.

### How to think about improvements

1. **Generalize from the feedback.** The big picture thing that's happening here is that we're trying to create skills that can be used a million times (maybe literally, maybe even more who knows) across many different prompts. Here you and the user are iterating on only a few examples over and over again because it helps move faster. The user knows these examples in and out and it's quick for them to assess new outputs. But if the skill you and the user are codeveloping works only for those examples, it's useless. Rather than put in fiddly overfitty changes, or oppressively constrictive MUSTs, if there's some stubborn issue, you might try branching out and using different metaphors, or recommending different patterns of working. It's relatively cheap to try and maybe you'll land on something great.

2. **Keep the prompt lean.** Remove things that aren't pulling their weight. Make sure to read the transcripts, not just the final outputs — if it looks like the skill is making the model waste a bunch of time doing things that are unproductive, you can try getting rid of the parts of the skill that are making it do that and seeing what happens.

3. **Explain the why.** Try hard to explain the **why** behind everything you're asking the model to do. Today's LLMs are *smart*. They have good theory of mind and when given a good harness can go beyond rote instructions and really make things happen. Even if the feedback from the user is terse or frustrated, try to actually understand the task and why the user is writing what they wrote, and what they actually wrote, and then transmit this understanding into the instructions. If you find yourself writing ALWAYS or NEVER in all caps, or using super rigid structures, that's a yellow flag — if possible, reframe and explain the reasoning so that the model understands why the thing you're asking for is important. That's a more humane, powerful, and effective approach.

4. **Look for repeated work across test cases.** Read the transcripts from the test runs and notice if the subagents all independently wrote similar helper scripts or took the same multi-step approach to something. If all 3 test cases resulted in the subagent writing a `create_docx.py` or a `build_chart.py`, that's a strong signal the skill should bundle that script. Write it once, put it in `scripts/`, and tell the skill to use it. This saves every future invocation from reinventing the wheel.

This task is pretty important (we are trying to create billions a year in economic value here!) and your thinking time is not the blocker; take your time and really mull things over. I'd suggest writing a draft revision and then looking at it anew and making improvements. Really do your best to get into the head of the user and understand what they want and need.

### The iteration loop

After improving the skill:

1. Apply your improvements to the skill
2. Rerun all test cases into a new `iteration-<N+1>/` directory, including baseline runs. If you're creating a new skill, the baseline is always `without_skill` (no skill) — that stays the same across iterations. If you're improving an existing skill, use your judgment on what makes sense as the baseline: the original version the user came in with, or the previous iteration.
3. Launch the reviewer with `--previous-workspace` pointing at the previous iteration
4. Wait for the user to review and tell you they're done
5. Read the new feedback, improve again, repeat

Keep going until:
- The user says they're happy
- The feedback is all empty (everything looks good)
- You're not making meaningful progress

---

## Advanced: Blind comparison

For situations where you want a more rigorous comparison between two versions of a skill (e.g., the user asks "is the new version actually better?"), there's a blind comparison system. Read `agents/comparator.md` and `agents/analyzer.md` for the details. The basic idea is: give two outputs to an independent agent without telling it which is which, and let it judge quality. Then analyze why the winner won.

This is optional, requires subagents, and most users won't need it. The human review loop is usually sufficient.

---

## Description Optimization

The description field in SKILL.md frontmatter is the primary mechanism that determines whether Claude invokes a skill. After creating or improving a skill, offer to optimize the description for better triggering accuracy.

### Step 1: Generate trigger eval queries

Create 20 eval queries — a mix of should-trigger and should-not-trigger. Save as JSON:

```json
[
  {"query": "the user prompt", "should_trigger": true},
  {"query": "another prompt", "should_trigger": false}
]
```

The queries must be realistic and something a Claude Code or Claude.ai user would actually type. Not abstract requests, but requests that are concrete and specific and have a good amount of detail. For instance, file paths, personal context about the user's job or situation, column names and values, company names, URLs. A little bit of backstory. Some might be in lowercase or contain abbreviations or typos or casual speech. Use a mix of different lengths, and focus on edge cases rather than making them clear-cut (the user will get a chance to sign off on them).

Bad: `"Format this data"`, `"Extract text from PDF"`, `"Create a chart"`

Good: `"ok so my boss just sent me this xlsx file (its in my downloads, called something like 'Q4 sales final FINAL v2.xlsx') and she wants me to add a column that shows the profit margin as a percentage. The revenue is in column C and costs are in column D i think"`

For the **should-trigger** queries (8-10), think about coverage. You want different phrasings of the same intent — some formal, some casual. Include cases where the user doesn't explicitly name the skill or file type but clearly needs it. Throw in some uncommon use cases and cases where this skill competes with another but should win.

For the **should-not-trigger** queries (8-10), the most valuable ones are the near-misses — queries that share keywords or concepts with the skill but actually need something different. Think adjacent domains, ambiguous phrasing where a naive keyword match would trigger but shouldn't, and cases where the query touches on something the skill does but in a context where another tool is more appropriate.

The key thing to avoid: don't make should-not-trigger queries obviously irrelevant. "Write a fibonacci function" as a negative test for a PDF skill is too easy — it doesn't test anything. The negative cases should be genuinely tricky.

### Step 2: Review with user

Present the eval set to the user for review using the HTML template:

1. Read the template from `assets/eval_review.html`
2. Replace the placeholders:
   - `__EVAL_DATA_PLACEHOLDER__` → the JSON array of eval items (no quotes around it — it's a JS variable assignment)
   - `__SKILL_NAME_PLACEHOLDER__` → the skill's name
   - `__SKILL_DESCRIPTION_PLACEHOLDER__` → the skill's current description
3. Write to a temp file (e.g., `/tmp/eval_review_<skill-name>.html`) and open it: `open /tmp/eval_review_<skill-name>.html`
4. The user can edit queries, toggle should-trigger, add/remove entries, then click "Export Eval Set"
5. The file downloads to `~/Downloads/eval_set.json` — check the Downloads folder for the most recent version in case there are multiple (e.g., `eval_set (1).json`)

This step matters — bad eval queries lead to bad descriptions.

### Step 3: Run the optimization loop

Tell the user: "This will take some time — I'll run the optimization loop in the background and check on it periodically."

Save the eval set to the workspace, then run in the background:

```bash
python -m scripts.run_loop \
  --eval-set <path-to-trigger-eval.json> \
  --skill-path <path-to-skill> \
  --model <model-id-powering-this-session> \
  --max-iterations 5 \
  --verbose
```

Use the model ID from your system prompt (the one powering the current session) so the triggering test matches what the user actually experiences.

While it runs, periodically tail the output to give the user updates on which iteration it's on and what the scores look like.

This handles the full optimization loop automatically. It splits the eval set into 60% train and 40% held-out test, evaluates the current description (running each query 3 times to get a reliable trigger rate), then calls Claude to propose improvements based on what failed. It re-evaluates each new description on both train and test, iterating up to 5 times. When it's done, it opens an HTML report in the browser showing the results per iteration and returns JSON with `best_description` — selected by test score rather than train score to avoid overfitting.

### How skill triggering works

Understanding the triggering mechanism helps design better eval queries. Skills appear in Claude's `available_skills` list with their name + description, and Claude decides whether to consult a skill based on that description. The important thing to know is that Claude only consults skills for tasks it can't easily handle on its own — simple, one-step queries like "read this PDF" may not trigger a skill even if the description matches perfectly, because Claude can handle them directly with basic tools. Complex, multi-step, or specialized queries reliably trigger skills when the description matches.

This means your eval queries should be substantive enough that Claude would actually benefit from consulting a skill. Simple queries like "read file X" are poor test cases — they won't trigger skills regardless of description quality.

### Step 4: Apply the result

Take `best_description` from the JSON output and update the skill's SKILL.md frontmatter. Show the user before/after and report the scores.

---

### Package and Present (only if `present_files` tool is available)

Check whether you have access to the `present_files` tool. If you don't, skip this step. If you do, package the skill and present the .skill file to the user:

```bash
python -m scripts.package_skill <path/to/skill-folder>
```

After packaging, direct the user to the resulting `.skill` file path so they can install it.

---

## Claude.ai-specific instructions

In Claude.ai, the core workflow is the same (draft → test → review → improve → repeat), but because Claude.ai doesn't have subagents, some mechanics change. Here's what to adapt:

**Running test cases**: No subagents means no parallel execution. For each test case, read the skill's SKILL.md, then follow its instructions to accomplish the test prompt yourself. Do them one at a time. This is less rigorous than independent subagents (you wrote the skill and you're also running it, so you have full context), but it's a useful sanity check — and the human review step compensates. Skip the baseline runs — just use the skill to complete the task as requested.

**Reviewing results**: If you can't open a browser (e.g., Claude.ai's VM has no display, or you're on a remote server), skip the browser reviewer entirely. Instead, present results directly in the conversation. For each test case, show the prompt and the output. If the output is a file the user needs to see (like a .docx or .xlsx), save it to the filesystem and tell them where it is so they can download and inspect it. Ask for feedback inline: "How does this look? Anything you'd change?"

**Benchmarking**: Skip the quantitative benchmarking — it relies on baseline comparisons which aren't meaningful without subagents. Focus on qualitative feedback from the user.

**The iteration loop**: Same as before — improve the skill, rerun the test cases, ask for feedback — just without the browser reviewer in the middle. You can still organize results into iteration directories on the filesystem if you have one.

**Description optimization**: This section requires the `claude` CLI tool (specifically `claude -p`) which is only available in Claude Code. Skip it if you're on Claude.ai.

**Blind comparison**: Requires subagents. Skip it.

**Packaging**: The `package_skill.py` script works anywhere with Python and a filesystem. On Claude.ai, you can run it and the user can download the resulting `.skill` file.

**Updating an existing skill**: The user might be asking you to update an existing skill, not create a new one. In this case:
- **Preserve the original name.** Note the skill's directory name and `name` frontmatter field -- use them unchanged. E.g., if the installed skill is `research-helper`, output `research-helper.skill` (not `research-helper-v2`).
- **Copy to a writeable location before editing.** The installed skill path may be read-only. Copy to `/tmp/skill-name/`, edit there, and package from the copy.
- **If packaging manually, stage in `/tmp/` first**, then copy to the output directory -- direct writes may fail due to permissions.

---

## Cowork-Specific Instructions

If you're in Cowork, the main things to know are:

- You have subagents, so the main workflow (spawn test cases in parallel, run baselines, grade, etc.) all works. (However, if you run into severe problems with timeouts, it's OK to run the test prompts in series rather than parallel.)
- You don't have a browser or display, so when generating the eval viewer, use `--static <output_path>` to write a standalone HTML file instead of starting a server. Then proffer a link that the user can click to open the HTML in their browser.
- For whatever reason, the Cowork setup seems to disincline Claude from generating the eval viewer after running the tests, so just to reiterate: whether you're in Cowork or in Claude Code, after running tests, you should always generate the eval viewer for the human to look at examples before revising the skill yourself and trying to make corrections, using `generate_review.py` (not writing your own boutique html code). Sorry in advance but I'm gonna go all caps here: GENERATE THE EVAL VIEWER *BEFORE* evaluating inputs yourself. You want to get them in front of the human ASAP!
- Feedback works differently: since there's no running server, the viewer's "Submit All Reviews" button will download `feedback.json` as a file. You can then read it from there (you may have to request access first).
- Packaging works — `package_skill.py` just needs Python and a filesystem.
- Description optimization (`run_loop.py` / `run_eval.py`) should work in Cowork just fine since it uses `claude -p` via subprocess, not a browser, but please save it until you've fully finished making the skill and the user agrees it's in good shape.
- **Updating an existing skill**: The user might be asking you to update an existing skill, not create a new one. Follow the update guidance in the claude.ai section above.

---

## Reference files

The agents/ directory contains instructions for specialized subagents. Read them when you need to spawn the relevant subagent.

- `agents/grader.md` — How to evaluate assertions against outputs
- `agents/comparator.md` — How to do blind A/B comparison between two outputs
- `agents/analyzer.md` — How to analyze why one version beat another

The references/ directory has additional documentation:
- `references/schemas.md` — JSON structures for evals.json, grading.json, etc.

---

Repeating one more time the core loop here for emphasis:

- Figure out what the skill is about
- Draft or edit the skill
- Run claude-with-access-to-the-skill on test prompts
- With the user, evaluate the outputs:
  - Create benchmark.json and run `eval-viewer/generate_review.py` to help the user review them
  - Run quantitative evals
- Repeat until you and the user are satisfied
- Package the final skill and return it to the user.

Please add steps to your TodoList, if you have such a thing, to make sure you don't forget. If you're in Cowork, please specifically put "Create evals JSON and run `eval-viewer/generate_review.py` so human can review test cases" in your TodoList to make sure it happens.

Good luck!


================================================
FILE: .claude/skills/skill-creator/agents/analyzer.md
================================================
# Post-hoc Analyzer Agent

Analyze blind comparison results to understand WHY the winner won and generate improvement suggestions.

## Role

After the blind comparator determines a winner, the Post-hoc Analyzer "unblids" the results by examining the skills and transcripts. The goal is to extract actionable insights: what made the winner better, and how can the loser be improved?

## Inputs

You receive these parameters in your prompt:

- **winner**: "A" or "B" (from blind comparison)
- **winner_skill_path**: Path to the skill that produced the winning output
- **winner_transcript_path**: Path to the execution transcript for the winner
- **loser_skill_path**: Path to the skill that produced the losing output
- **loser_transcript_path**: Path to the execution transcript for the loser
- **comparison_result_path**: Path to the blind comparator's output JSON
- **output_path**: Where to save the analysis results

## Process

### Step 1: Read Comparison Result

1. Read the blind comparator's output at comparison_result_path
2. Note the winning side (A or B), the reasoning, and any scores
3. Understand what the comparator valued in the winning output

### Step 2: Read Both Skills

1. Read the winner skill's SKILL.md and key referenced files
2. Read the loser skill's SKILL.md and key referenced files
3. Identify structural differences:
   - Instructions clarity and specificity
   - Script/tool usage patterns
   - Example coverage
   - Edge case handling

### Step 3: Read Both Transcripts

1. Read the winner's transcript
2. Read the loser's transcript
3. Compare execution patterns:
   - How closely did each follow their skill's instructions?
   - What tools were used differently?
   - Where did the loser diverge from optimal behavior?
   - Did either encounter errors or make recovery attempts?

### Step 4: Analyze Instruction Following

For each transcript, evaluate:
- Did the agent follow the skill's explicit instructions?
- Did the agent use the skill's provided tools/scripts?
- Were there missed opportunities to leverage skill content?
- Did the agent add unnecessary steps not in the skill?

Score instruction following 1-10 and note specific issues.

### Step 5: Identify Winner Strengths

Determine what made the winner better:
- Clearer instructions that led to better behavior?
- Better scripts/tools that produced better output?
- More comprehensive examples that guided edge cases?
- Better error handling guidance?

Be specific. Quote from skills/transcripts where relevant.

### Step 6: Identify Loser Weaknesses

Determine what held the loser back:
- Ambiguous instructions that led to suboptimal choices?
- Missing tools/scripts that forced workarounds?
- Gaps in edge case coverage?
- Poor error handling that caused failures?

### Step 7: Generate Improvement Suggestions

Based on the analysis, produce actionable suggestions for improving the loser skill:
- Specific instruction changes to make
- Tools/scripts to add or modify
- Examples to include
- Edge cases to address

Prioritize by impact. Focus on changes that would have changed the outcome.

### Step 8: Write Analysis Results

Save structured analysis to `{output_path}`.

## Output Format

Write a JSON file with this structure:

```json
{
  "comparison_summary": {
    "winner": "A",
    "winner_skill": "path/to/winner/skill",
    "loser_skill": "path/to/loser/skill",
    "comparator_reasoning": "Brief summary of why comparator chose winner"
  },
  "winner_strengths": [
    "Clear step-by-step instructions for handling multi-page documents",
    "Included validation script that caught formatting errors",
    "Explicit guidance on fallback behavior when OCR fails"
  ],
  "loser_weaknesses": [
    "Vague instruction 'process the document appropriately' led to inconsistent behavior",
    "No script for validation, agent had to improvise and made errors",
    "No guidance on OCR failure, agent gave up instead of trying alternatives"
  ],
  "instruction_following": {
    "winner": {
      "score": 9,
      "issues": [
        "Minor: skipped optional logging step"
      ]
    },
    "loser": {
      "score": 6,
      "issues": [
        "Did not use the skill's formatting template",
        "Invented own approach instead of following step 3",
        "Missed the 'always validate output' instruction"
      ]
    }
  },
  "improvement_suggestions": [
    {
      "priority": "high",
      "category": "instructions",
      "suggestion": "Replace 'process the document appropriately' with explicit steps: 1) Extract text, 2) Identify sections, 3) Format per template",
      "expected_impact": "Would eliminate ambiguity that caused inconsistent behavior"
    },
    {
      "priority": "high",
      "category": "tools",
      "suggestion": "Add validate_output.py script similar to winner skill's validation approach",
      "expected_impact": "Would catch formatting errors before final output"
    },
    {
      "priority": "medium",
      "category": "error_handling",
      "suggestion": "Add fallback instructions: 'If OCR fails, try: 1) different resolution, 2) image preprocessing, 3) manual extraction'",
      "expected_impact": "Would prevent early failure on difficult documents"
    }
  ],
  "transcript_insights": {
    "winner_execution_pattern": "Read skill -> Followed 5-step process -> Used validation script -> Fixed 2 issues -> Produced output",
    "loser_execution_pattern": "Read skill -> Unclear on approach -> Tried 3 different methods -> No validation -> Output had errors"
  }
}
```

## Guidelines

- **Be specific**: Quote from skills and transcripts, don't just say "instructions were unclear"
- **Be actionable**: Suggestions should be concrete changes, not vague advice
- **Focus on skill improvements**: The goal is to improve the losing skill, not critique the agent
- **Prioritize by impact**: Which changes would most likely have changed the outcome?
- **Consider causation**: Did the skill weakness actually cause the worse output, or is it incidental?
- **Stay objective**: Analyze what happened, don't editorialize
- **Think about generalization**: Would this improvement help on other evals too?

## Categories for Suggestions

Use these categories to organize improvement suggestions:

| Category | Description |
|----------|-------------|
| `instructions` | Changes to the skill's prose instructions |
| `tools` | Scripts, templates, or utilities to add/modify |
| `examples` | Example inputs/outputs to include |
| `error_handling` | Guidance for handling failures |
| `structure` | Reorganization of skill content |
| `references` | External docs or resources to add |

## Priority Levels

- **high**: Would likely change the outcome of this comparison
- **medium**: Would improve quality but may not change win/loss
- **low**: Nice to have, marginal improvement

---

# Analyzing Benchmark Results

When analyzing benchmark results, the analyzer's purpose is to **surface patterns and anomalies** across multiple runs, not suggest skill improvements.

## Role

Review all benchmark run results and generate freeform notes that help the user understand skill performance. Focus on patterns that wouldn't be visible from aggregate metrics alone.

## Inputs

You receive these parameters in your prompt:

- **benchmark_data_path**: Path to the in-progress benchmark.json with all run results
- **skill_path**: Path to the skill being benchmarked
- **output_path**: Where to save the notes (as JSON array of strings)

## Process

### Step 1: Read Benchmark Data

1. Read the benchmark.json containing all run results
2. Note the configurations tested (with_skill, without_skill)
3. Understand the run_summary aggregates already calculated

### Step 2: Analyze Per-Assertion Patterns

For each expectation across all runs:
- Does it **always pass** in both configurations? (may not differentiate skill value)
- Does it **always fail** in both configurations? (may be broken or beyond capability)
- Does it **always pass with skill but fail without**? (skill clearly adds value here)
- Does it **always fail with skill but pass without**? (skill may be hurting)
- Is it **highly variable**? (flaky expectation or non-deterministic behavior)

### Step 3: Analyze Cross-Eval Patterns

Look for patterns across evals:
- Are certain eval types consistently harder/easier?
- Do some evals show high variance while others are stable?
- Are there surprising results that contradict expectations?

### Step 4: Analyze Metrics Patterns

Look at time_seconds, tokens, tool_calls:
- Does the skill significantly increase execution time?
- Is there high variance in resource usage?
- Are there outlier runs that skew the aggregates?

### Step 5: Generate Notes

Write freeform observations as a list of strings. Each note should:
- State a specific observation
- Be grounded in the data (not speculation)
- Help the user understand something the aggregate metrics don't show

Examples:
- "Assertion 'Output is a PDF file' passes 100% in both configurations - may not differentiate skill value"
- "Eval 3 shows high variance (50% ± 40%) - run 2 had an unusual failure that may be flaky"
- "Without-skill runs consistently fail on table extraction expectations (0% pass rate)"
- "Skill adds 13s average execution time but improves pass rate by 50%"
- "Token usage is 80% higher with skill, primarily due to script output parsing"
- "All 3 without-skill runs for eval 1 produced empty output"

### Step 6: Write Notes

Save notes to `{output_path}` as a JSON array of strings:

```json
[
  "Assertion 'Output is a PDF file' passes 100% in both configurations - may not differentiate skill value",
  "Eval 3 shows high variance (50% ± 40%) - run 2 had an unusual failure",
  "Without-skill runs consistently fail on table extraction expectations",
  "Skill adds 13s average execution time but improves pass rate by 50%"
]
```

## Guidelines

**DO:**
- Report what you observe in the data
- Be specific about which evals, expectations, or runs you're referring to
- Note patterns that aggregate metrics would hide
- Provide context that helps interpret the numbers

**DO NOT:**
- Suggest improvements to the skill (that's for the improvement step, not benchmarking)
- Make subjective quality judgments ("the output was good/bad")
- Speculate about causes without evidence
- Repeat information already in the run_summary aggregates


================================================
FILE: .claude/skills/skill-creator/agents/comparator.md
================================================
# Blind Comparator Agent

Compare two outputs WITHOUT knowing which skill produced them.

## Role

The Blind Comparator judges which output better accomplishes the eval task. You receive two outputs labeled A and B, but you do NOT know which skill produced which. This prevents bias toward a particular skill or approach.

Your judgment is based purely on output quality and task completion.

## Inputs

You receive these parameters in your prompt:

- **output_a_path**: Path to the first output file or directory
- **output_b_path**: Path to the second output file or directory
- **eval_prompt**: The original task/prompt that was executed
- **expectations**: List of expectations to check (optional - may be empty)

## Process

### Step 1: Read Both Outputs

1. Examine output A (file or directory)
2. Examine output B (file or directory)
3. Note the type, structure, and content of each
4. If outputs are directories, examine all relevant files inside

### Step 2: Understand the Task

1. Read the eval_prompt carefully
2. Identify what the task requires:
   - What should be produced?
   - What qualities matter (accuracy, completeness, format)?
   - What would distinguish a good output from a poor one?

### Step 3: Generate Evaluation Rubric

Based on the task, generate a rubric with two dimensions:

**Content Rubric** (what the output contains):
| Criterion | 1 (Poor) | 3 (Acceptable) | 5 (Excellent) |
|-----------|----------|----------------|---------------|
| Correctness | Major errors | Minor errors | Fully correct |
| Completeness | Missing key elements | Mostly complete | All elements present |
| Accuracy | Significant inaccuracies | Minor inaccuracies | Accurate throughout |

**Structure Rubric** (how the output is organized):
| Criterion | 1 (Poor) | 3 (Acceptable) | 5 (Excellent) |
|-----------|----------|----------------|---------------|
| Organization | Disorganized | Reasonably organized | Clear, logical structure |
| Formatting | Inconsistent/broken | Mostly consistent | Professional, polished |
| Usability | Difficult to use | Usable with effort | Easy to use |

Adapt criteria to the specific task. For example:
- PDF form → "Field alignment", "Text readability", "Data placement"
- Document → "Section structure", "Heading hierarchy", "Paragraph flow"
- Data output → "Schema correctness", "Data types", "Completeness"

### Step 4: Evaluate Each Output Against the Rubric

For each output (A and B):

1. **Score each criterion** on the rubric (1-5 scale)
2. **Calculate dimension totals**: Content score, Structure score
3. **Calculate overall score**: Average of dimension scores, scaled to 1-10

### Step 5: Check Assertions (if provided)

If expectations are provided:

1. Check each expectation against output A
2. Check each expectation against output B
3. Count pass rates for each output
4. Use expectation scores as secondary evidence (not the primary decision factor)

### Step 6: Determine the Winner

Compare A and B based on (in priority order):

1. **Primary**: Overall rubric score (content + structure)
2. **Secondary**: Assertion pass rates (if applicable)
3. **Tiebreaker**: If truly equal, declare a TIE

Be decisive - ties should be rare. One output is usually better, even if marginally.

### Step 7: Write Comparison Results

Save results to a JSON file at the path specified (or `comparison.json` if not specified).

## Output Format

Write a JSON file with this structure:

```json
{
  "winner": "A",
  "reasoning": "Output A provides a complete solution with proper formatting and all required fields. Output B is missing the date field and has formatting inconsistencies.",
  "rubric": {
    "A": {
      "content": {
        "correctness": 5,
        "completeness": 5,
        "accuracy": 4
      },
      "structure": {
        "organization": 4,
        "formatting": 5,
        "usability": 4
      },
      "content_score": 4.7,
      "structure_score": 4.3,
      "overall_score": 9.0
    },
    "B": {
      "content": {
        "correctness": 3,
        "completeness": 2,
        "accuracy": 3
      },
      "structure": {
        "organization": 3,
        "formatting": 2,
        "usability": 3
      },
      "content_score": 2.7,
      "structure_score": 2.7,
      "overall_score": 5.4
    }
  },
  "output_quality": {
    "A": {
      "score": 9,
      "strengths": ["Complete solution", "Well-formatted", "All fields present"],
      "weaknesses": ["Minor style inconsistency in header"]
    },
    "B": {
      "score": 5,
      "strengths": ["Readable output", "Correct basic structure"],
      "weaknesses": ["Missing date field", "Formatting inconsistencies", "Partial data extraction"]
    }
  },
  "expectation_results": {
    "A": {
      "passed": 4,
      "total": 5,
      "pass_rate": 0.80,
      "details": [
        {"text": "Output includes name", "passed": true},
        {"text": "Output includes date", "passed": true},
        {"text": "Format is PDF", "passed": true},
        {"text": "Contains signature", "passed": false},
        {"text": "Readable text", "passed": true}
      ]
    },
    "B": {
      "passed": 3,
      "total": 5,
      "pass_rate": 0.60,
      "details": [
        {"text": "Output includes name", "passed": true},
        {"text": "Output includes date", "passed": false},
        {"text": "Format is PDF", "passed": true},
        {"text": "Contains signature", "passed": false},
        {"text": "Readable text", "passed": true}
      ]
    }
  }
}
```

If no expectations were provided, omit the `expectation_results` field entirely.

## Field Descriptions

- **winner**: "A", "B", or "TIE"
- **reasoning**: Clear explanation of why the winner was chosen (or why it's a tie)
- **rubric**: Structured rubric evaluation for each output
  - **content**: Scores for content criteria (correctness, completeness, accuracy)
  - **structure**: Scores for structure criteria (organization, formatting, usability)
  - **content_score**: Average of content criteria (1-5)
  - **structure_score**: Average of structure criteria (1-5)
  - **overall_score**: Combined score scaled to 1-10
- **output_quality**: Summary quality assessment
  - **score**: 1-10 rating (should match rubric overall_score)
  - **strengths**: List of positive aspects
  - **weaknesses**: List of issues or shortcomings
- **expectation_results**: (Only if expectations provided)
  - **passed**: Number of expectations that passed
  - **total**: Total number of expectations
  - **pass_rate**: Fraction passed (0.0 to 1.0)
  - **details**: Individual expectation results

## Guidelines

- **Stay blind**: DO NOT try to infer which skill produced which output. Judge purely on output quality.
- **Be specific**: Cite specific examples when explaining strengths and weaknesses.
- **Be decisive**: Choose a winner unless outputs are genuinely equivalent.
- **Output quality first**: Assertion scores are secondary to overall task completion.
- **Be objective**: Don't favor outputs based on style preferences; focus on correctness and completeness.
- **Explain your reasoning**: The reasoning field should make it clear why you chose the winner.
- **Handle edge cases**: If both outputs fail, pick the one that fails less badly. If both are excellent, pick the one that's marginally better.


================================================
FILE: .claude/skills/skill-creator/agents/grader.md
================================================
# Grader Agent

Evaluate expectations against an execution transcript and outputs.

## Role

The Grader reviews a transcript and output files, then determines whether each expectation passes or fails. Provide clear evidence for each judgment.

You have two jobs: grade the outputs, and critique the evals themselves. A passing grade on a weak assertion is worse than useless — it creates false confidence. When you notice an assertion that's trivially satisfied, or an important outcome that no assertion checks, say so.

## Inputs

You receive these parameters in your prompt:

- **expectations**: List of expectations to evaluate (strings)
- **transcript_path**: Path to the execution transcript (markdown file)
- **outputs_dir**: Directory containing output files from execution

## Process

### Step 1: Read the Transcript

1. Read the transcript file completely
2. Note the eval prompt, execution steps, and final result
3. Identify any issues or errors documented

### Step 2: Examine Output Files

1. List files in outputs_dir
2. Read/examine each file relevant to the expectations. If outputs aren't plain text, use the inspection tools provided in your prompt — don't rely solely on what the transcript says the executor produced.
3. Note contents, structure, and quality

### Step 3: Evaluate Each Assertion

For each expectation:

1. **Search for evidence** in the transcript and outputs
2. **Determine verdict**:
   - **PASS**: Clear evidence the expectation is true AND the evidence reflects genuine task completion, not just surface-level compliance
   - **FAIL**: No evidence, or evidence contradicts the expectation, or the evidence is superficial (e.g., correct filename but empty/wrong content)
3. **Cite the evidence**: Quote the specific text or describe what you found

### Step 4: Extract and Verify Claims

Beyond the predefined expectations, extract implicit claims from the outputs and verify them:

1. **Extract claims** from the transcript and outputs:
   - Factual statements ("The form has 12 fields")
   - Process claims ("Used pypdf to fill the form")
   - Quality claims ("All fields were filled correctly")

2. **Verify each claim**:
   - **Factual claims**: Can be checked against the outputs or external sources
   - **Process claims**: Can be verified from the transcript
   - **Quality claims**: Evaluate whether the claim is justified

3. **Flag unverifiable claims**: Note claims that cannot be verified with available information

This catches issues that predefined expectations might miss.

### Step 5: Read User Notes

If `{outputs_dir}/user_notes.md` exists:
1. Read it and note any uncertainties or issues flagged by the executor
2. Include relevant concerns in the grading output
3. These may reveal problems even when expectations pass

### Step 6: Critique the Evals

After grading, consider whether the evals themselves could be improved. Only surface suggestions when there's a clear gap.

Good suggestions test meaningful outcomes — assertions that are hard to satisfy without actually doing the work correctly. Think about what makes an assertion *discriminating*: it passes when the skill genuinely succeeds and fails when it doesn't.

Suggestions worth raising:
- An assertion that passed but would also pass for a clearly wrong output (e.g., checking filename existence but not file content)
- An important outcome you observed — good or bad — that no assertion covers at all
- An assertion that can't actually be verified from the available outputs

Keep the bar high. The goal is to flag things the eval author would say "good catch" about, not to nitpick every assertion.

### Step 7: Write Grading Results

Save results to `{outputs_dir}/../grading.json` (sibling to outputs_dir).

## Grading Criteria

**PASS when**:
- The transcript or outputs clearly demonstrate the expectation is true
- Specific evidence can be cited
- The evidence reflects genuine substance, not just surface compliance (e.g., a file exists AND contains correct content, not just the right filename)

**FAIL when**:
- No evidence found for the expectation
- Evidence contradicts the expectation
- The expectation cannot be verified from available information
- The evidence is superficial — the assertion is technically satisfied but the underlying task outcome is wrong or incomplete
- The output appears to meet the assertion by coincidence rather than by actually doing the work

**When uncertain**: The burden of proof to pass is on the expectation.

### Step 8: Read Executor Metrics and Timing

1. If `{outputs_dir}/metrics.json` exists, read it and include in grading output
2. If `{outputs_dir}/../timing.json` exists, read it and include timing data

## Output Format

Write a JSON file with this structure:

```json
{
  "expectations": [
    {
      "text": "The output includes the name 'John Smith'",
      "passed": true,
      "evidence": "Found in transcript Step 3: 'Extracted names: John Smith, Sarah Johnson'"
    },
    {
      "text": "The spreadsheet has a SUM formula in cell B10",
      "passed": false,
      "evidence": "No spreadsheet was created. The output was a text file."
    },
    {
      "text": "The assistant used the skill's OCR script",
      "passed": true,
      "evidence": "Transcript Step 2 shows: 'Tool: Bash - python ocr_script.py image.png'"
    }
  ],
  "summary": {
    "passed": 2,
    "failed": 1,
    "total": 3,
    "pass_rate": 0.67
  },
  "execution_metrics": {
    "tool_calls": {
      "Read": 5,
      "Write": 2,
      "Bash": 8
    },
    "total_tool_calls": 15,
    "total_steps": 6,
    "errors_encountered": 0,
    "output_chars": 12450,
    "transcript_chars": 3200
  },
  "timing": {
    "executor_duration_seconds": 165.0,
    "grader_duration_seconds": 26.0,
    "total_duration_seconds": 191.0
  },
  "claims": [
    {
      "claim": "The form has 12 fillable fields",
      "type": "factual",
      "verified": true,
      "evidence": "Counted 12 fields in field_info.json"
    },
    {
      "claim": "All required fields were populated",
      "type": "quality",
      "verified": false,
      "evidence": "Reference section was left blank despite data being available"
    }
  ],
  "user_notes_summary": {
    "uncertainties": ["Used 2023 data, may be stale"],
    "needs_review": [],
    "workarounds": ["Fell back to text overlay for non-fillable fields"]
  },
  "eval_feedback": {
    "suggestions": [
      {
        "assertion": "The output includes the name 'John Smith'",
        "reason": "A hallucinated document that mentions the name would also pass — consider checking it appears as the primary contact with matching phone and email from the input"
      },
      {
        "reason": "No assertion checks whether the extracted phone numbers match the input — I observed incorrect numbers in the output that went uncaught"
      }
    ],
    "overall": "Assertions check presence but not correctness. Consider adding content verification."
  }
}
```

## Field Descriptions

- **expectations**: Array of graded expectations
  - **text**: The original expectation text
  - **passed**: Boolean - true if expectation passes
  - **evidence**: Specific quote or description supporting the verdict
- **summary**: Aggregate statistics
  - **passed**: Count of passed expectations
  - **failed**: Count of failed expectations
  - **total**: Total expectations evaluated
  - **pass_rate**: Fraction passed (0.0 to 1.0)
- **execution_metrics**: Copied from executor's metrics.json (if available)
  - **output_chars**: Total character count of output files (proxy for tokens)
  - **transcript_chars**: Character count of transcript
- **timing**: Wall clock timing from timing.json (if available)
  - **executor_duration_seconds**: Time spent in executor subagent
  - **total_duration_seconds**: Total elapsed time for the run
- **claims**: Extracted and verified claims from the output
  - **claim**: The statement being verified
  - **type**: "factual", "process", or "quality"
  - **verified**: Boolean - whether the claim holds
  - **evidence**: Supporting or contradicting evidence
- **user_notes_summary**: Issues flagged by the executor
  - **uncertainties**: Things the executor wasn't sure about
  - **needs_review**: Items requiring human attention
  - **workarounds**: Places where the skill didn't work as expected
- **eval_feedback**: Improvement suggestions for the evals (only when warranted)
  - **suggestions**: List of concrete suggestions, each with a `reason` and optionally an `assertion` it relates to
  - **overall**: Brief assessment — can be "No suggestions, evals look solid" if nothing to flag

## Guidelines

- **Be objective**: Base verdicts on evidence, not assumptions
- **Be specific**: Quote the exact text that supports your verdict
- **Be thorough**: Check both transcript and output files
- **Be consistent**: Apply the same standard to each expectation
- **Explain failures**: Make it clear why evidence was insufficient
- **No partial credit**: Each expectation is pass or fail, not partial


================================================
FILE: .claude/skills/skill-creator/assets/eval_review.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Eval Set Review - __SKILL_NAME_PLACEHOLDER__</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: 'Lora', Georgia, serif; background: #faf9f5; padding: 2rem; color: #141413; }
    h1 { font-family: 'Poppins', sans-serif; margin-bottom: 0.5rem; font-size: 1.5rem; }
    .description { color: #b0aea5; margin-bottom: 1.5rem; font-style: italic; max-width: 900px; }
    .controls { margin-bottom: 1rem; display: flex; gap: 0.5rem; }
    .btn { font-family: 'Poppins', sans-serif; padding: 0.5rem 1rem; border: none; border-radius: 6px; cursor: pointer; font-size: 0.875rem; font-weight: 500; }
    .btn-add { background: #6a9bcc; color: white; }
    .btn-add:hover { background: #5889b8; }
    .btn-export { background: #d97757; color: white; }
    .btn-export:hover { background: #c4613f; }
    table { width: 100%; max-width: 1100px; border-collapse: collapse; background: white; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
    th { font-family: 'Poppins', sans-serif; background: #141413; color: #faf9f5; padding: 0.75rem 1rem; text-align: left; font-size: 0.875rem; }
    td { padding: 0.75rem 1rem; border-bottom: 1px solid #e8e6dc; vertical-align: top; }
    tr:nth-child(even) td { background: #faf9f5; }
    tr:hover td { background: #f3f1ea; }
    .section-header td { background: #e8e6dc; font-family: 'Poppins', sans-serif; font-weight: 500; font-size: 0.8rem; color: #141413; text-transform: uppercase; letter-spacing: 0.05em; }
    .query-input { width: 100%; padding: 0.4rem; border: 1px solid #e8e6dc; border-radius: 4px; font-size: 0.875rem; font-family: 'Lora', Georgia, serif; resize: vertical; min-height: 60px; }
    .query-input:focus { outline: none; border-color: #d97757; box-shadow: 0 0 0 2px rgba(217,119,87,0.15); }
    .toggle { position: relative; display: inline-block; width: 44px; height: 24px; }
    .toggle input { opacity: 0; width: 0; height: 0; }
    .toggle .slider { position: absolute; inset: 0; background: #b0aea5; border-radius: 24px; cursor: pointer; transition: 0.2s; }
    .toggle .slider::before { content: ""; position: absolute; width: 18px; height: 18px; left: 3px; bottom: 3px; background: white; border-radius: 50%; transition: 0.2s; }
    .toggle input:checked + .slider { background: #d97757; }
    .toggle input:checked + .slider::before { transform: translateX(20px); }
    .btn-delete { background: #c44; color: white; padding: 0.3rem 0.6rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.75rem; font-family: 'Poppins', sans-serif; }
    .btn-delete:hover { background: #a33; }
    .summary { margin-top: 1rem; color: #b0aea5; font-size: 0.875rem; }
  </style>
</head>
<body>
  <h1>Eval Set Review: <span id="skill-name">__SKILL_NAME_PLACEHOLDER__</span></h1>
  <p class="description">Current description: <span id="skill-desc">__SKILL_DESCRIPTION_PLACEHOLDER__</span></p>

  <div class="controls">
    <button class="btn btn-add" onclick="addRow()">+ Add Query</button>
    <button class="btn btn-export" onclick="exportEvalSet()">Export Eval Set</button>
  </div>

  <table>
    <thead>
      <tr>
        <th style="width:65%">Query</th>
        <th style="width:18%">Should Trigger</th>
        <th style="width:10%">Actions</th>
      </tr>
    </thead>
    <tbody id="eval-body"></tbody>
  </table>

  <p class="summary" id="summary"></p>

  <script>
    const EVAL_DATA = __EVAL_DATA_PLACEHOLDER__;

    let evalItems = [...EVAL_DATA];

    function render() {
      const tbody = document.getElementById('eval-body');
      tbody.innerHTML = '';

      // Sort: should-trigger first, then should-not-trigger
      const sorted = evalItems
        .map((item, origIdx) => ({ ...item, origIdx }))
        .sort((a, b) => (b.should_trigger ? 1 : 0) - (a.should_trigger ? 1 : 0));

      let lastGroup = null;
      sorted.forEach(item => {
        const group = item.should_trigger ? 'trigger' : 'no-trigger';
        if (group !== lastGroup) {
          const headerRow = document.createElement('tr');
          headerRow.className = 'section-header';
          headerRow.innerHTML = `<td colspan="3">${item.should_trigger ? 'Should Trigger' : 'Should NOT Trigger'}</td>`;
          tbody.appendChild(headerRow);
          lastGroup = group;
        }

        const idx = item.origIdx;
        const tr = document.createElement('tr');
        tr.innerHTML = `
          <td><textarea class="query-input" onchange="updateQuery(${idx}, this.value)">${escapeHtml(item.query)}</textarea></td>
          <td>
            <label class="toggle">
              <input type="checkbox" ${item.should_trigger ? 'checked' : ''} onchange="updateTrigger(${idx}, this.checked)">
              <span class="slider"></span>
            </label>
            <span style="margin-left:8px;font-size:0.8rem;color:#b0aea5">${item.should_trigger ? 'Yes' : 'No'}</span>
          </td>
          <td><button class="btn-delete" onclick="deleteRow(${idx})">Delete</button></td>
        `;
        tbody.appendChild(tr);
      });
      updateSummary();
    }

    function escapeHtml(text) {
      const div = document.createElement('div');
      div.textContent = text;
      return div.innerHTML;
    }

    function updateQuery(idx, value) { evalItems[idx].query = value; updateSummary(); }
    function updateTrigger(idx, value) { evalItems[idx].should_trigger = value; render(); }
    function deleteRow(idx) { evalItems.splice(idx, 1); render(); }

    function addRow() {
      evalItems.push({ query: '', should_trigger: true });
      render();
      const inputs = document.querySelectorAll('.query-input');
      inputs[inputs.length - 1].focus();
    }

    function updateSummary() {
      const trigger = evalItems.filter(i => i.should_trigger).length;
      const noTrigger = evalItems.filter(i => !i.should_trigger).length;
      document.getElementById('summary').textContent =
        `${evalItems.length} queries total: ${trigger} should trigger, ${noTrigger} should not trigger`;
    }

    function exportEvalSet() {
      const valid = evalItems.filter(i => i.query.trim() !== '');
      const data = valid.map(i => ({ query: i.query.trim(), should_trigger: i.should_trigger }));
      const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'eval_set.json';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }

    render();
  </script>
</body>
</html>


================================================
FILE: .claude/skills/skill-creator/eval-viewer/generate_review.py
================================================
#!/usr/bin/env python3
"""Generate and serve a review page for eval results.

Reads the workspace directory, discovers runs (directories with outputs/),
embeds all output data into a self-contained HTML page, and serves it via
a tiny HTTP server. Feedback auto-saves to feedback.json in the workspace.

Usage:
    python generate_review.py <workspace-path> [--port PORT] [--skill-name NAME]
    python generate_review.py <workspace-path> --previous-feedback /path/to/old/feedback.json

No dependencies beyond the Python stdlib are required.
"""

import argparse
import base64
import json
import mimetypes
import os
import re
import signal
import subprocess
import sys
import time
import webbrowser
from functools import partial
from http.server import HTTPServer, BaseHTTPRequestHandler
from pathlib import Path

# Files to exclude from output listings
METADATA_FILES = {"transcript.md", "user_notes.md", "metrics.json"}

# Extensions we render as inline text
TEXT_EXTENSIONS = {
    ".txt", ".md", ".json", ".csv", ".py", ".js", ".ts", ".tsx", ".jsx",
    ".yaml", ".yml", ".xml", ".html", ".css", ".sh", ".rb", ".go", ".rs",
    ".java", ".c", ".cpp", ".h", ".hpp", ".sql", ".r", ".toml",
}

# Extensions we render as inline images
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"}

# MIME type overrides for common types
MIME_OVERRIDES = {
    ".svg": "image/svg+xml",
    ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
}


def get_mime_type(path: Path) -> str:
    ext = path.suffix.lower()
    if ext in MIME_OVERRIDES:
        return MIME_OVERRIDES[ext]
    mime, _ = mimetypes.guess_type(str(path))
    return mime or "application/octet-stream"


def find_runs(workspace: Path) -> list[dict]:
    """Recursively find directories that contain an outputs/ subdirectory."""
    runs: list[dict] = []
    _find_runs_recursive(workspace, workspace, runs)
    runs.sort(key=lambda r: (r.get("eval_id", float("inf")), r["id"]))
    return runs


def _find_runs_recursive(root: Path, current: Path, runs: list[dict]) -> None:
    if not current.is_dir():
        return

    outputs_dir = current / "outputs"
    if outputs_dir.is_dir():
        run = build_run(root, current)
        if run:
            runs.append(run)
        return

    skip = {"node_modules", ".git", "__pycache__", "skill", "inputs"}
    for child in sorted(current.iterdir()):
        if child.is_dir() and child.name not in skip:
            _find_runs_recursive(root, child, runs)


def build_run(root: Path, run_dir: Path) -> dict | None:
    """Build a run dict with prompt, outputs, and grading data."""
    prompt = ""
    eval_id = None

    # Try eval_metadata.json
    for candidate in [run_dir / "eval_metadata.json", run_dir.parent / "eval_metadata.json"]:
        if candidate.exists():
            try:
                metadata = json.loads(candidate.read_text())
                prompt = metadata.get("prompt", "")
                eval_id = metadata.get("eval_id")
            except (json.JSONDecodeError, OSError):
                pass
            if prompt:
                break

    # Fall back to transcript.md
    if not prompt:
        for candidate in [run_dir / "transcript.md", run_dir / "outputs" / "transcript.md"]:
            if candidate.exists():
                try:
                    text = candidate.read_text()
                    match = re.search(r"## Eval Prompt\n\n([\s\S]*?)(?=\n##|$)", text)
                    if match:
                        prompt = match.group(1).strip()
                except OSError:
                    pass
                if prompt:
                    break

    if not prompt:
        prompt = "(No prompt found)"

    run_id = str(run_dir.relative_to(root)).replace("/", "-").replace("\\", "-")

    # Collect output files
    outputs_dir = run_dir / "outputs"
    output_files: list[dict] = []
    if outputs_dir.is_dir():
        for f in sorted(outputs_dir.iterdir()):
            if f.is_file() and f.name not in METADATA_FILES:
                output_files.append(embed_file(f))

    # Load grading if present
    grading = None
    for candidate in [run_dir / "grading.json", run_dir.parent / "grading.json"]:
        if candidate.exists():
            try:
                grading = json.loads(candidate.read_text())
            except (json.JSONDecodeError, OSError):
                pass
            if grading:
                break

    return {
        "id": run_id,
        "prompt": prompt,
        "eval_id": eval_id,
        "outputs": output_files,
        "grading": grading,
    }


def embed_file(path: Path) -> dict:
    """Read a file and return an embedded representation."""
    ext = path.suffix.lower()
    mime = get_mime_type(path)

    if ext in TEXT_EXTENSIONS:
        try:
            content = path.read_text(errors="replace")
        except OSError:
            content = "(Error reading file)"
        return {
            "name": path.name,
            "type": "text",
            "content": content,
        }
    elif ext in IMAGE_EXTENSIONS:
        try:
            raw = path.read_bytes()
            b64 = base64.b64encode(raw).decode("ascii")
        except OSError:
            return {"name": path.name, "type": "error", "content": "(Error reading file)"}
        return {
            "name": path.name,
            "type": "image",
            "mime": mime,
            "data_uri": f"data:{mime};base64,{b64}",
        }
    elif ext == ".pdf":
        try:
            raw = path.read_bytes()
            b64 = base64.b64encode(raw).decode("ascii")
        except OSError:
            return {"name": path.name, "type": "error", "content": "(Error reading file)"}
        return {
            "name": path.name,
            "type": "pdf",
            "data_uri": f"data:{mime};base64,{b64}",
        }
    elif ext == ".xlsx":
        try:
            raw = path.read_bytes()
            b64 = base64.b64encode(raw).decode("ascii")
        except OSError:
            return {"name": path.name, "type": "error", "content": "(Error reading file)"}
        return {
            "name": path.name,
            "type": "xlsx",
            "data_b64": b64,
        }
    else:
        # Binary / unknown — base64 download link
        try:
            raw = path.read_bytes()
            b64 = base64.b64encode(raw).decode("ascii")
        except OSError:
            return {"name": path.name, "type": "error", "content": "(Error reading file)"}
        return {
            "name": path.name,
            "type": "binary",
            "mime": mime,
            "data_uri": f"data:{mime};base64,{b64}",
        }


def load_previous_iteration(workspace: Path) -> dict[str, dict]:
    """Load previous iteration's feedback and outputs.

    Returns a map of run_id -> {"feedback": str, "outputs": list[dict]}.
    """
    result: dict[str, dict] = {}

    # Load feedback
    feedback_map: dict[str, str] = {}
    feedback_path = workspace / "feedback.json"
    if feedback_path.exists():
        try:
            data = json.loads(feedback_path.read_text())
            feedback_map = {
                r["run_id"]: r["feedback"]
                for r in data.get("reviews", [])
                if r.get("feedback", "").strip()
            }
        except (json.JSONDecodeError, OSError, KeyError):
            pass

    # Load runs (to get outputs)
    prev_runs = find_runs(workspace)
    for run in prev_runs:
        result[run["id"]] = {
            "feedback": feedback_map.get(run["id"], ""),
            "outputs": run.get("outputs", []),
        }

    # Also add feedback for run_ids that had feedback but no matching run
    for run_id, fb in feedback_map.items():
        if run_id not in result:
            result[run_id] = {"feedback": fb, "outputs": []}

    return result


def generate_html(
    runs: list[dict],
    skill_name: str,
    previous: dict[str, dict] | None = None,
    benchmark: dict | None = None,
) -> str:
    """Generate the complete standalone HTML page with embedded data."""
    template_path = Path(__file__).parent / "viewer.html"
    template = template_path.read_text()

    # Build previous_feedback and previous_outputs maps for the template
    previous_feedback: dict[str, str] = {}
    previous_outputs: dict[str, list[dict]] = {}
    if previous:
        for run_id, data in previous.items():
            if data.get("feedback"):
                previous_feedback[run_id] = data["feedback"]
            if data.get("outputs"):
                previous_outputs[run_id] = data["outputs"]

    embedded = {
        "skill_name": skill_name,
        "runs": runs,
        "previous_feedback": previous_feedback,
        "previous_outputs": previous_outputs,
    }
    if benchmark:
        embedded["benchmark"] = benchmark

    data_json = json.dumps(embedded)

    return template.replace("/*__EMBEDDED_DATA__*/", f"const EMBEDDED_DATA = {data_json};")


# ---------------------------------------------------------------------------
# HTTP server (stdlib only, zero dependencies)
# ---------------------------------------------------------------------------

def _kill_port(port: int) -> None:
    """Kill any process listening on the given port."""
    try:
        result = subprocess.run(
            ["lsof", "-ti", f":{port}"],
            capture_output=True, text=True, timeout=5,
        )
        for pid_str in result.stdout.strip().split("\n"):
            if pid_str.strip():
                try:
                    os.kill(int(pid_str.strip()), signal.SIGTERM)
                except (ProcessLookupError, ValueError):
                    pass
        if result.stdout.strip():
            time.sleep(0.5)
    except subprocess.TimeoutExpired:
        pass
    except FileNotFoundError:
        print("Note: lsof not found, cannot check if port is in use", file=sys.stderr)

class ReviewHandler(BaseHTTPRequestHandler):
    """Serves the review HTML and handles feedback saves.

    Regenerates the HTML on each page load so that refreshing the browser
    picks up new eval outputs without restarting the server.
    """

    def __init__(
        self,
        workspace: Path,
        skill_name: str,
        feedback_path: Path,
        previous: dict[str, dict],
        benchmark_path: Path | None,
        *args,
        **kwargs,
    ):
        self.workspace = workspace
        self.skill_name = skill_name
        self.feedback_path = feedback_path
        self.previous = previous
        self.benchmark_path = benchmark_path
        super().__init__(*args, **kwargs)

    def do_GET(self) -> None:
        if self.path == "/" or self.path == "/index.html":
            # Regenerate HTML on each request (re-scans workspace for new outputs)
            runs = find_runs(self.workspace)
            benchmark = None
            if self.benchmark_path and self.benchmark_path.exists():
                try:
                    benchmark = json.loads(self.benchmark_path.read_text())
                except (json.JSONDecodeError, OSError):
                    pass
            html = generate_html(runs, self.skill_name, self.previous, benchmark)
            content = html.encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(content)))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == "/api/feedback":
            data = b"{}"
            if self.feedback_path.exists():
                data = self.feedback_path.read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
        else:
            self.send_error(404)

    def do_POST(self) -> None:
        if self.path == "/api/feedback":
            length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(length)
            try:
                data = json.loads(body)
                if not isinstance(data, dict) or "reviews" not in data:
                    raise ValueError("Expected JSON object with 'reviews' key")
                self.feedback_path.write_text(json.dumps(data, indent=2) + "\n")
                resp = b'{"ok":true}'
                self.send_response(200)
            except (json.JSONDecodeError, OSError, ValueError) as e:
                resp = json.dumps({"error": str(e)}).encode()
                self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(resp)))
            self.end_headers()
            self.wfile.write(resp)
        else:
            self.send_error(404)

    def log_message(self, format: str, *args: object) -> None:
        # Suppress request logging to keep terminal clean
        pass


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate and serve eval review")
    parser.add_argument("workspace", type=Path, help="Path to workspace directory")
    parser.add_argument("--port", "-p", type=int, default=3117, help="Server port (default: 3117)")
    parser.add_argument("--skill-name", "-n", type=str, default=None, help="Skill name for header")
    parser.add_argument(
        "--previous-workspace", type=Path, default=None,
        help="Path to previous iteration's workspace (shows old outputs and feedback as context)",
    )
    parser.add_argument(
        "--benchmark", type=Path, default=None,
        help="Path to benchmark.json to show in the Benchmark tab",
    )
    parser.add_argument(
        "--static", "-s", type=Path, default=None,
        help="Write standalone HTML to this path instead of starting a server",
    )
    args = parser.parse_args()

    workspace = args.workspace.resolve()
    if not workspace.is_dir():
        print(f"Error: {workspace} is not a directory", file=sys.stderr)
        sys.exit(1)

    runs = find_runs(workspace)
    if not runs:
        print(f"No runs found in {workspace}", file=sys.stderr)
        sys.exit(1)

    skill_name = args.skill_name or workspace.name.replace("-workspace", "")
    feedback_path = workspace / "feedback.json"

    previous: dict[str, dict] = {}
    if args.previous_workspace:
        previous = load_previous_iteration(args.previous_workspace.resolve())

    benchmark_path = args.benchmark.resolve() if args.benchmark else None
    benchmark = None
    if benchmark_path and benchmark_path.exists():
        try:
            benchmark = json.loads(benchmark_path.read_text())
        except (json.JSONDecodeError, OSError):
            pass

    if args.static:
        html = generate_html(runs, skill_name, previous, benchmark)
        args.static.parent.mkdir(parents=True, exist_ok=True)
        args.static.write_text(html)
        print(f"\n  Static viewer written to: {args.static}\n")
        sys.exit(0)

    # Kill any existing process on the target port
    port = args.port
    _kill_port(port)
    handler = partial(ReviewHandler, workspace, skill_name, feedback_path, previous, benchmark_path)
    try:
        server = HTTPServer(("127.0.0.1", port), handler)
    except OSError:
        # Port still in use after kill attempt — find a free one
        server = HTTPServer(("127.0.0.1", 0), handler)
        port = server.server_address[1]

    url = f"http://localhost:{port}"
    print(f"\n  Eval Viewer")
    print(f"  ─────────────────────────────────")
    print(f"  URL:       {url}")
    print(f"  Workspace: {workspace}")
    print(f"  Feedback:  {feedback_path}")
    if previous:
        print(f"  Previous:  {args.previous_workspace} ({len(previous)} runs)")
    if benchmark_path:
        print(f"  Benchmark: {benchmark_path}")
    print(f"\n  Press Ctrl+C to stop.\n")

    webbrowser.open(url)

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nStopped.")
        server.server_close()


if __name__ == "__main__":
    main()


================================================
FILE: .claude/skills/skill-creator/eval-viewer/viewer.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Eval Review</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
  <script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script>
  <style>
    :root {
      --bg: #faf9f5;
      --surface: #ffffff;
      --border: #e8e6dc;
      --text: #141413;
      --text-muted: #b0aea5;
      --accent: #d97757;
      --accent-hover: #c4613f;
      --green: #788c5d;
      --green-bg: #eef2e8;
      --red: #c44;
      --red-bg: #fceaea;
      --header-bg: #141413;
      --header-text: #faf9f5;
      --radius: 6px;
    }

    * { box-sizing: border-box; margin: 0; padding: 0; }

    body {
      font-family: 'Lora', Georgia, serif;
      background: var(--bg);
      color: var(--text);
      height: 100vh;
      display: flex;
      flex-direction: column;
    }

    /* ---- Header ---- */
    .header {
      background: var(--header-bg);
      color: var(--header-text);
      padding: 1rem 2rem;
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-shrink: 0;
    }
    .header h1 {
      font-family: 'Poppins', sans-serif;
      font-size: 1.25rem;
      font-weight: 600;
    }
    .header .instructions {
      font-size: 0.8rem;
      opacity: 0.7;
      margin-top: 0.25rem;
    }
    .header .progress {
      font-size: 0.875rem;
      opacity: 0.8;
      text-align: right;
    }

    /* ---- Main content ---- */
    .main {
      flex: 1;
      overflow-y: auto;
      padding: 1.5rem 2rem;
      display: flex;
      flex-direction: column;
      gap: 1.25rem;
    }

    /* ---- Sections ---- */
    .section {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      flex-shrink: 0;
    }
    .section-header {
      font-family: 'Poppins', sans-serif;
      padding: 0.75rem 1rem;
      font-size: 0.75rem;
      font-weight: 500;
      text-transform: uppercase;
      letter-spacing: 0.05em;
      color: var(--text-muted);
      border-bottom: 1px solid var(--border);
      background: var(--bg);
    }
    .section-body {
      padding: 1rem;
    }

    /* ---- Config badge ---- */
    .config-badge {
      display: inline-block;
      padding: 0.2rem 0.625rem;
      border-radius: 9999px;
      font-family: 'Poppins', sans-serif;
      font-size: 0.6875rem;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.03em;
      margin-left: 0.75rem;
      vertical-align: middle;
    }
    .config-badge.config-primary {
      background: rgba(33, 150, 243, 0.12);
      color: #1976d2;
    }
    .config-badge.config-baseline {
      background: rgba(255, 193, 7, 0.15);
      color: #f57f17;
    }

    /* ---- Prompt ---- */
    .prompt-text {
      white-space: pre-wrap;
      font-size: 0.9375rem;
      line-height: 1.6;
    }

    /* ---- Outputs ---- */
    .output-file {
      border: 1px solid var(--border);
      border-radius: var(--radius);
      overflow: hidden;
    }
    .output-file + .output-file {
      margin-top: 1rem;
    }
    .output-file-header {
      padding: 0.5rem 0.75rem;
      font-size: 0.8rem;
      font-weight: 600;
      color: var(--text-muted);
      background: var(--bg);
      border-bottom: 1px solid var(--border);
      font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .output-file-header .dl-btn {
      font-size: 0.7rem;
      color: var(--accent);
      text-decoration: none;
      cursor: pointer;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      font-weight: 500;
      opacity: 0.8;
    }
    .output-file-header .dl-btn:hover {
      opacity: 1;
      text-decoration: underline;
    }
    .output-file-content {
      padding: 0.75rem;
      overflow-x: auto;
    }
    .output-file-content pre {
      font-size: 0.8125rem;
      line-height: 1.5;
      white-space: pre-wrap;
      word-break: break-word;
      font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
    }
    .output-file-content img {
      max-width: 100%;
      height: auto;
      border-radius: 4px;
    }
    .output-file-content iframe {
      width: 100%;
      height: 600px;
      border: none;
    }
    .output-file-content table {
      border-collapse: collapse;
      font-size: 0.8125rem;
      width: 100%;
    }
    .output-file-content table td,
    .output-file-content table th {
      border: 1px solid var(--border);
      padding: 0.375rem 0.5rem;
      text-align: left;
    }
    .output-file-content table th {
      background: var(--bg);
      font-weight: 600;
    }
    .output-file-content .download-link {
      display: inline-flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.5rem 1rem;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 4px;
      color: var(--accent);
      text-decoration: none;
      font-size: 0.875rem;
      cursor: pointer;
    }
    .output-file-content .download-link:hover {
      background: var(--border);
    }
    .empty-state {
      color: var(--text-muted);
      font-style: italic;
      padding: 2rem;
      text-align: center;
    }

    /* ---- Feedback ---- */
    .prev-feedback {
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 4px;
      padding: 0.625rem 0.75rem;
      margin-top: 0.75rem;
      font-size: 0.8125rem;
      color: var(--text-muted);
      line-height: 1.5;
    }
    .prev-feedback-label {
      font-size: 0.7rem;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.04em;
      margin-bottom: 0.25rem;
      color: var(--text-muted);
    }
    .feedback-textarea {
      width: 100%;
      min-height: 100px;
      padding: 0.75rem;
      border: 1px solid var(--border);
      border-radius: 4px;
      font-family: inherit;
      font-size: 0.9375rem;
      line-height: 1.5;
      resize: vertical;
      color: var(--text);
    }
    .feedback-textarea:focus {
      outline: none;
      border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
    }
    .feedback-status {
      font-size: 0.75rem;
      color: var(--text-muted);
      margin-top: 0.5rem;
      min-height: 1.1em;
    }

    /* ---- Grades (collapsible) ---- */
    .grades-toggle {
      display: flex;
      align-items: center;
      cursor: pointer;
      user-select: none;
    }
    .grades-toggle:hover {
      color: var(--accent);
    }
    .grades-toggle .arrow {
      margin-right: 0.5rem;
      transition: transform 0.15s;
      font-size: 0.75rem;
    }
    .grades-toggle .arrow.open {
      transform: rotate(90deg);
    }
    .grades-content {
      display: none;
      margin-top: 0.75rem;
    }
    .grades-content.open {
      display: block;
    }
    .grades-summary {
      font-size: 0.875rem;
      margin-bottom: 0.75rem;
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }
    .grade-badge {
      display: inline-block;
      padding: 0.125rem 0.5rem;
      border-radius: 9999px;
      font-size: 0.75rem;
      font-weight: 600;
    }
    .grade-pass { background: var(--green-bg); color: var(--green); }
    .grade-fail { background: var(--red-bg); color: var(--red); }
    .assertion-list {
      list-style: none;
    }
    .assertion-item {
      padding: 0.625rem 0;
      border-bottom: 1px solid var(--border);
      font-size: 0.8125rem;
    }
    .assertion-item:last-child { border-bottom: none; }
    .assertion-status {
      font-weight: 600;
      margin-right: 0.5rem;
    }
    .assertion-status.pass { color: var(--green); }
    .assertion-status.fail { color: var(--red); }
    .assertion-evidence {
      color: var(--text-muted);
      font-size: 0.75rem;
      margin-top: 0.25rem;
      padding-left: 1.5rem;
    }

    /* ---- View tabs ---- */
    .view-tabs {
      display: flex;
      gap: 0;
      padding: 0 2rem;
      background: var(--bg);
      border-bottom: 1px solid var(--border);
      flex-shrink: 0;
    }
    .view-tab {
      font-family: 'Poppins', sans-serif;
      padding: 0.625rem 1.25rem;
      font-size: 0.8125rem;
      font-weight: 500;
      cursor: pointer;
      border: none;
      background: none;
      color: var(--text-muted);
      border-bottom: 2px solid transparent;
      transition: all 0.15s;
    }
    .view-tab:hover { color: var(--text); }
    .view-tab.active {
      color: var(--accent);
      border-bottom-color: var(--accent);
    }
    .view-panel { display: none; }
    .view-panel.active { display: flex; flex-direction: column; flex: 1; overflow: hidden; }

    /* ---- Benchmark view ---- */
    .benchmark-view {
      padding: 1.5rem 2rem;
      overflow-y: auto;
      flex: 1;
    }
    .benchmark-table {
      border-collapse: collapse;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      font-size: 0.8125rem;
      width: 100%;
      margin-bottom: 1.5rem;
    }
    .benchmark-table th, .benchmark-table td {
      padding: 0.625rem 0.75rem;
      text-align: left;
      border: 1px solid var(--border);
    }
    .benchmark-table th {
      font-family: 'Poppins', sans-serif;
      background: var(--header-bg);
      color: var(--header-text);
      font-weight: 500;
      font-size: 0.75rem;
      text-transform: uppercase;
      letter-spacing: 0.04em;
    }
    .benchmark-table tr:hover { background: var(--bg); }
    .benchmark-table tr.benchmark-row-with { background: rgba(33, 150, 243, 0.06); }
    .benchmark-table tr.benchmark-row-without { background: rgba(255, 193, 7, 0.06); }
    .benchmark-table tr.benchmark-row-with:hover { background: rgba(33, 150, 243, 0.12); }
    .benchmark-table tr.benchmark-row-without:hover { background: rgba(255, 193, 7, 0.12); }
    .benchmark-table tr.benchmark-row-avg { font-weight: 600; border-top: 2px solid var(--border); }
    .benchmark-table tr.benchmark-row-avg.benchmark-row-with { background: rgba(33, 150, 243, 0.12); }
    .benchmark-table tr.benchmark-row-avg.benchmark-row-without { background: rgba(255, 193, 7, 0.12); }
    .benchmark-delta-positive { color: var(--green); font-weight: 600; }
    .benchmark-delta-negative { color: var(--red); font-weight: 600; }
    .benchmark-notes {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      padding: 1rem;
    }
    .benchmark-notes h3 {
      font-family: 'Poppins', sans-serif;
      font-size: 0.875rem;
      margin-bottom: 0.75rem;
    }
    .benchmark-notes ul {
      list-style: disc;
      padding-left: 1.25rem;
    }
    .benchmark-notes li {
      font-size: 0.8125rem;
      line-height: 1.6;
      margin-bottom: 0.375rem;
    }
    .benchmark-empty {
      color: var(--text-muted);
      font-style: italic;
      text-align: center;
      padding: 3rem;
    }

    /* ---- Navigation ---- */
    .nav {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1rem 2rem;
      border-top: 1px solid var(--border);
      background: var(--surface);
      flex-shrink: 0;
    }
    .nav-btn {
      font-family: 'Poppins', sans-serif;
      padding: 0.5rem 1.25rem;
      border: 1px solid var(--border);
      border-radius: var(--radius);
      background: var(--surface);
      cursor: pointer;
      font-size: 0.875rem;
      font-weight: 500;
      color: var(--text);
      transition: all 0.15s;
    }
    .nav-btn:hover:not(:disabled) {
      background: var(--bg);
      border-color: var(--text-muted);
    }
    .nav-btn:disabled {
      opacity: 0.4;
      cursor: not-allowed;
    }
    .done-btn {
      font-family: 'Poppins', sans-serif;
      padding: 0.5rem 1.5rem;
      border: 1px solid var(--border);
      border-radius: var(--radius);
      background: var(--surface);
      color: var(--text);
      cursor: pointer;
      font-size: 0.875rem;
      font-weight: 500;
      transition: all 0.15s;
    }
    .done-btn:hover {
      background: var(--bg);
      border-color: var(--text-muted);
    }
    .done-btn.ready {
      border: none;
      background: var(--accent);
      color: white;
      font-weight: 600;
    }
    .done-btn.ready:hover {
      background: var(--accent-hover);
    }
    /* ---- Done overlay ---- */
    .done-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.5);
      z-index: 100;
      justify-content: center;
      align-items: center;
    }
    .done-overlay.visible {
      display: flex;
    }
    .done-card {
      background: var(--surface);
      border-radius: 12px;
      padding: 2rem 3rem;
      text-align: center;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      max-width: 500px;
    }
    .done-card h2 {
      font-size: 1.5rem;
      margin-bottom: 0.5rem;
    }
    .done-card p {
      color: var(--text-muted);
      margin-bottom: 1.5rem;
      line-height: 1.5;
    }
    .done-card .btn-row {
      display: flex;
      gap: 0.5rem;
      justify-content: center;
    }
    .done-card button {
      padding: 0.5rem 1.25rem;
      border: 1px solid var(--border);
      border-radius: var(--radius);
      background: var(--surface);
      cursor: pointer;
      font-size: 0.875rem;
    }
    .done-card button:hover {
      background: var(--bg);
    }
    /* ---- Toast ---- */
    .toast {
      position: fixed;
      bottom: 5rem;
      left: 50%;
      transform: translateX(-50%);
      background: var(--header-bg);
      color: var(--header-text);
      padding: 0.625rem 1.25rem;
      border-radius: var(--radius);
      font-size: 0.875rem;
      opacity: 0;
      transition: opacity 0.3s;
      pointer-events: none;
      z-index: 200;
    }
    .toast.visible {
      opacity: 1;
    }
  </style>
</head>
<body>
  <div id="app" style="height:100vh; display:flex; flex-direction:column;">
    <div class="header">
      <div>
        <h1>Eval Review: <span id="skill-name"></span></h1>
        <div class="instructions">Review each output and leave feedback below. Navigate with arrow keys or buttons. When done, copy feedback and paste into Claude Code.</div>
      </div>
      <div class="progress" id="progress"></div>
    </div>

    <!-- View tabs (only shown when benchmark data exists) -->
    <div class="view-tabs" id="view-tabs" style="display:none;">
      <button class="view-tab active" onclick="switchView('outputs')">Outputs</button>
      <button class="view-tab" onclick="switchView('benchmark')">Benchmark</button>
    </div>

    <!-- Outputs panel (qualitative review) -->
    <div class="view-panel active" id="panel-outputs">
    <div class="main">
      <!-- Prompt -->
      <div class="section">
        <div class="section-header">Prompt <span class="config-badge" id="config-badge" style="display:none;"></span></div>
        <div class="section-body">
          <div class="prompt-text" id="prompt-text"></div>
        </div>
      </div>

      <!-- Outputs -->
      <div class="section">
        <div class="section-header">Output</div>
        <div class="section-body" id="outputs-body">
          <div class="empty-state">No output files found</div>
        </div>
      </div>

      <!-- Previous Output (collapsible) -->
      <div class="section" id="prev-outputs-section" style="display:none;">
        <div class="section-header">
          <div class="grades-toggle" onclick="togglePrevOutputs()">
            <span class="arrow" id="prev-outputs-arrow">&#9654;</span>
            Previous Output
          </div>
        </div>
        <div class="grades-content" id="prev-outputs-content"></div>
      </div>

      <!-- Grades (collapsible) -->
      <div class="section" id="grades-section" style="display:none;">
        <div class="section-header">
          <div class="grades-toggle" onclick="toggleGrades()">
            <span class="arrow" id="grades-arrow">&#9654;</span>
            Formal Grades
          </div>
        </div>
        <div class="grades-content" id="grades-content"></div>
      </div>

      <!-- Feedback -->
      <div class="section">
        <div class="section-header">Your Feedback</div>
        <div class="section-body">
          <textarea
            class="feedback-textarea"
            id="feedback"
            placeholder="What do you think of this output? Any issues, suggestions, or things that look great?"
          ></textarea>
          <div class="feedback-status" id="feedback-status"></div>
          <div class="prev-feedback" id="prev-feedback" style="display:none;">
            <div class="prev-feedback-label">Previous feedback</div>
            <div id="prev-feedback-text"></div>
          </div>
        </div>
      </div>
    </div>

    <div class="nav" id="outputs-nav">
      <button class="nav-btn" id="prev-btn" onclick="navigate(-1)">&#8592; Previous</button>
      <button class="done-btn" id="done-btn" onclick="showDoneDialog()">Submit All Reviews</button>
      <button class="nav-btn" id="next-btn" onclick="navigate(1)">Next &#8594;</button>
    </div>
    </div><!-- end panel-outputs -->

    <!-- Benchmark panel (quantitative stats) -->
    <div class="view-panel" id="panel-benchmark">
      <div class="benchmark-view" id="benchmark-content">
        <div class="benchmark-empty">No benchmark data available. Run a benchmark to see quantitative results here.</div>
      </div>
    </div>
  </div>

  <!-- Done overlay -->
  <div class="done-overlay" id="done-overlay">
    <div class="done-card">
      <h2>Review Complete</h2>
      <p>Your feedback has been saved. Go back to your Claude Code session and tell Claude you're done reviewing.</p>
      <div class="btn-row">
        <button onclick="closeDoneDialog()">OK</button>
      </div>
    </div>
  </div>

  <!-- Toast -->
  <div class="toast" id="toast"></div>

  <script>
    // ---- Embedded data (injected by generate_review.py) ----
    /*__EMBEDDED_DATA__*/

    // ---- State ----
    let feedbackMap = {};  // run_id -> feedback text
    let currentIndex = 0;
    let visitedRuns = new Set();

    // ---- Init ----
    async function init() {
      // Load saved feedback from server — but only if this isn't a fresh
      // iteration (indicated by previous_feedback being present). When
      // previous feedback exists, the feedback.json on disk is stale from
      // the prior iteration and should not pre-fill the textareas.
      const hasPrevious = Object.keys(EMBEDDED_DATA.previous_feedback || {}).length > 0
        || Object.keys(EMBEDDED_DATA.previous_outputs || {}).length > 0;
      if (!hasPrevious) {
        try {
          const resp = await fetch("/api/feedback");
          const data = await resp.json();
          if (data.reviews) {
            for (const r of data.reviews) feedbackMap[r.run_id] = r.feedback;
          }
        } catch { /* first run, no feedback yet */ }
      }

      document.getElementById("skill-name").textContent = EMBEDDED_DATA.skill_name;
      showRun(0);

      // Wire up feedback auto-save
      const textarea = document.getElementById("feedback");
      let saveTimeout = null;
      textarea.addEventListener("input", () => {
        clearTimeout(saveTimeout);
        document.getElementById("feedback-status").textContent = "";
        saveTimeout = setTimeout(() => saveCurrentFeedback(), 800);
      });
    }

    // ---- Navigation ----
    function navigate(delta) {
      const newIndex = currentIndex + delta;
      if (newIndex >= 0 && newIndex < EMBEDDED_DATA.runs.length) {
        saveCurrentFeedback();
        showRun(newIndex);
      }
    }

    function updateNavButtons() {
      document.getElementById("prev-btn").disabled = currentIndex === 0;
      document.getElementById("next-btn").disabled =
        currentIndex === EMBEDDED_DATA.runs.length - 1;
    }

    // ---- Show a run ----
    function showRun(index) {
      currentIndex = index;
      const run = EMBEDDED_DATA.runs[index];

      // Progress
      document.getElementById("progress").textContent =
        `${index + 1} of ${EMBEDDED_DATA.runs.length}`;

      // Prompt
      document.getElementById("prompt-text").textContent = run.prompt;

      // Config badge
      const badge = document.getElementById("config-badge");
      const configMatch = run.id.match(/(with_skill|without_skill|new_skill|old_skill)/);
      if (configMatch) {
        const config = configMatch[1];
        const isBaseline = config === "without_skill" || config === "old_skill";
        badge.textContent = config.replace(/_/g, " ");
        badge.className = "config-badge " + (isBaseline ? "config-baseline" : "config-primary");
        badge.style.display = "inline-block";
      } else {
        badge.style.display = "none";
      }

      // Outputs
      renderOutputs(run);

      // Previous outputs
      renderPrevOutputs(run);

      // Grades
      renderGrades(run);

      // Previous feedback
      const prevFb = (EMBEDDED_DATA.previous_feedback || {})[run.id];
      const prevEl = document.getElementById("prev-feedback");
      if (prevFb) {
        document.getElementById("prev-feedback-text").textContent = prevFb;
        prevEl.style.display = "block";
      } else {
        prevEl.style.display = "none";
      }

      // Feedback
      document.getElementById("feedback").value = feedbackMap[run.id] || "";
      document.getElementById("feedback-status").textContent = "";

      updateNavButtons();

      // Track visited runs and promote done button when all visited
      visitedRuns.add(index);
      const doneBtn = document.getElementById("done-btn");
      if (visitedRuns.size >= EMBEDDED_DATA.runs.length) {
        doneBtn.classList.add("ready");
      }

      // Scroll main content to top
      document.querySelector(".main").scrollTop = 0;
    }

    // ---- Render outputs ----
    function renderOutputs(run) {
      const container = document.getElementById("outputs-body");
      container.innerHTML = "";

      const outputs = run.outputs || [];
      if (outputs.length === 0) {
        container.innerHTML = '<div class="empty-state">No output files</div>';
        return;
      }

      for (const file of outputs) {
        const fileDiv = document.createElement("div");
        fileDiv.className = "output-file";

        // Always show file header with download link
        const header = document.createElement("div");
        header.className = "output-file-header";
        const nameSpan = document.createElement("span");
        nameSpan.textContent = file.name;
        header.appendChild(nameSpan);
        const dlBtn = document.createElement("a");
        dlBtn.className = "dl-btn";
        dlBtn.textContent = "Download";
        dlBtn.download = file.name;
        dlBtn.href = getDownloadUri(file);
        header.appendChild(dlBtn);
        fileDiv.appendChild(header);

        const content = document.createElement("div");
        content.className = "output-file-content";

        if (file.type === "text") {
          const pre = document.createElement("pre");
          pre.textContent = file.content;
          content.appendChild(pre);
        } else if (file.type === "image") {
          const img = document.createElement("img");
          img.src = file.data_uri;
          img.alt = file.name;
          content.appendChild(img);
        } else if (file.type === "pdf") {
          const iframe = document.createElement("iframe");
          iframe.src = file.data_uri;
          content.appendChild(iframe);
        } else if (file.type === "xlsx") {
          renderXlsx(content, file.data_b64);
        } else if (file.type === "binary") {
          const a = document.createElement("a");
          a.className = "download-link";
          a.href = file.data_uri;
          a.download = file.name;
          a.textContent = "Download " + file.name;
          content.appendChild(a);
        } else if (file.type === "error") {
          const pre = document.createElement("pre");
          pre.textContent = file.content;
          pre.style.color = "var(--red)";
          content.appendChild(pre);
        }

        fileDiv.appendChild(content);
        container.appendChild(fileDiv);
      }
    }

    // ---- XLSX rendering via SheetJS ----
    function renderXlsx(container, b64Data) {
      try {
        const raw = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
        const wb = XLSX.read(raw, { type: "array" });

        for (let i = 0; i < wb.SheetNames.length; i++) {
          const sheetName = wb.SheetNames[i];
          const ws = wb.Sheets[sheetName];

          if (wb.SheetNames.length > 1) {
            const sheetLabel = document.createElement("div");
            sheetLabel.style.cssText =
              "font-weight:600; font-size:0.8rem; color:#b0aea5; margin-top:0.5rem; margin-bottom:0.25rem;";
            sheetLabel.textContent = "Sheet: " + sheetName;
            container.appendChild(sheetLabel);
          }

          const htmlStr = XLSX.utils.sheet_to_html(ws, { editable: false });
          const wrapper = document.createElement("div");
          wrapper.innerHTML = htmlStr;
          container.appendChild(wrapper);
        }
      } catch (err) {
        container.textContent = "Error rendering spreadsheet: " + err.message;
      }
    }

    // ---- Grades ----
    function renderGrades(run) {
      const section = document.getElementById("grades-section");
      const content = document.getElementById("grades-content");

      if (!run.grading) {
        section.style.display = "none";
        return;
      }

      const grading = run.grading;
      section.style.display = "block";
      // Reset to collapsed
      content.classList.remove("open");
      document.getElementById("grades-arrow").classList.remove("open");

      const summary = grading.summary || {};
      const expectations = grading.expectations || [];

      let html = '<div style="padding: 1rem;">';

      // Summary line
      const passRate = summary.pass_rate != null
        ? Math.round(summary.pass_rate * 100) + "%"
        : "?";
      const badgeClass = summary.pass_rate >= 0.8 ? "grade-pass" : summary.pass_rate >= 0.5 ? "" : "grade-fail";
      html += '<div class="grades-summary">';
      html += '<span class="grade-badge ' + badgeClass + '">' + passRate + '</span>';
      html += '<span>' + (summary.passed || 0) + ' passed, ' + (summary.failed || 0) + ' failed of ' + (summary.total || 0) + '</span>';
      html += '</div>';

      // Assertions list
      html += '<ul class="assertion-list">';
      for (const exp of expectations) {
        const statusClass = exp.passed ? "pass" : "fail";
        const statusIcon = exp.passed ? "\u2713" : "\u2717";
        html += '<li class="assertion-item">';
        html += '<span class="assertion-status ' + statusClass + '">' + statusIcon + '</span>';
        html += '<span>' + escapeHtml(exp.text) + '</span>';
        if (exp.evidence) {
          html += '<div class="assertion-evidence">' + escapeHtml(exp.evidence) + '</div>';
        }
        html += '</li>';
      }
      html += '</ul>';

      html += '</div>';
      content.innerHTML = html;
    }

    function toggleGrades() {
      const content = document.getElementById("grades-content");
      const arrow = document.getElementById("grades-arrow");
      content.classList.toggle("open");
      arrow.classList.toggle("open");
    }

    // ---- Previous outputs (collapsible) ----
    function renderPrevOutputs(run) {
      const section = document.getElementById("prev-outputs-section");
      const content = document.getElementById("prev-outputs-content");
      const prevOutputs = (EMBEDDED_DATA.previous_outputs || {})[run.id];

      if (!prevOutputs || prevOutputs.length === 0) {
        section.style.display = "none";
        return;
      }

      section.style.display = "block";
      // Reset to collapsed
      content.classList.remove("open");
      document.getElementById("prev-outputs-arrow").classList.remove("open");

      // Render the files into the content area
      content.innerHTML = "";
      const wrapper = document.createElement("div");
      wrapper.style.padding = "1rem";

      for (const file of prevOutputs) {
        const fileDiv = document.createElement("div");
        fileDiv.className = "output-file";

        const header = document.createElement("div");
        header.className = "output-file-header";
        const nameSpan = document.createElement("span");
        nameSpan.textContent = file.name;
        header.appendChild(nameSpan);
        const dlBtn = document.createElement("a");
        dlBtn.className = "dl-btn";
        dlBtn.textContent = "Download";
        dlBtn.download = file.name;
        dlBtn.href = getDownloadUri(file);
        header.appendChild(dlBtn);
        fileDiv.appendChild(header);

        const fc = document.createElement("div");
        fc.className = "output-file-content";

        if (file.type === "text") {
          const pre = document.createElement("pre");
          pre.textContent = file.content;
          fc.appendChild(pre);
        } else if (file.type === "image") {
          const img = document.createElement("img");
          img.src = file.data_uri;
          img.alt = file.name;
          fc.appendChild(img);
        } else if (file.type === "pdf") {
          const iframe = document.createElement("iframe");
          iframe.src = file.data_uri;
          fc.appendChild(iframe);
        } else if (file.type === "xlsx") {
          renderXlsx(fc, file.data_b64);
        } else if (file.type === "binary") {
          const a = document.createElement("a");
          a.className = "download-link";
          a.href = file.data_uri;
          a.download = file.name;
          a.textContent = "Download " + file.name;
          fc.appendChild(a);
        }

        fileDiv.appendChild(fc);
        wrapper.appendChild(fileDiv);
      }

      content.appendChild(wrapper);
    }

    function togglePrevOutputs() {
      const content = document.getElementById("prev-outputs-content");
      const arrow = document.getElementById("prev-outputs-arrow");
      content.classList.toggle("open");
      arrow.classList.toggle("open");
    }

    // ---- Feedback (saved to server -> feedback.json) ----
    function saveCurrentFeedback() {
      const run = EMBEDDED_DATA.runs[currentIndex];
      const text = document.getElementById("feedback").value;

      if (text.trim() === "") {
        delete feedbackMap[run.id];
      } else {
        feedbackMap[run.id] = text;
      }

      // Build reviews array from map
      const reviews = [];
      for (const [run_id, feedback] of Object.entries(feedbackMap)) {
        if (feedback.trim()) {
          reviews.push({ run_id, feedback, timestamp: new Date().toISOString() });
        }
      }

      fetch("/api/feedback", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ reviews, status: "in_progress" }),
      }).then(() => {
        document.getElementById("feedback-status").textContent = "Saved";
      }).catch(() => {
        // Static mode or server unavailable — no-op on auto-save,
        // feedback will be downloaded on final submit
        document.getElementById("feedback-status").textContent = "Will download on submit";
      });
    }

    // ---- Done ----
    function showDoneDialog() {
      // Save current textarea to feedbackMap (but don't POST yet)
      const run = EMBEDDED_DATA.runs[currentIndex];
      const text = document.getElementById("feedback").value;
      if (text.trim() === "") {
        delete feedbackMap[run.id];
      } else {
        feedbackMap[run.id] = text;
      }

      // POST once with status: complete — include ALL runs so the model
      // can distinguish "no feedback" (looks good) from "not reviewed"
      const reviews = [];
      const ts = new Date().toISOString();
      for (const r of EMBEDDED_DATA.runs) {
        reviews.push({ run_id: r.id, feedback: feedbackMap[r.id] || "", timestamp: ts });
      }
      const payload = JSON.stringify({ reviews, status: "complete" }, null, 2);
      fetch("/api/feedback", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: payload,
      }).then(() => {
        document.getElementById("done-overlay").classList.add("visible");
      }).catch(() => {
        // Server not available (static mode) — download as file
        const blob = new Blob([payload], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = "feedback.json";
        a.click();
        URL.revokeObjectURL(url);
        document.getElementById("done-overlay").classList.add("visible");
      });
    }

    function closeDoneDialog() {
      // Reset status back to in_progress
      saveCurrentFeedback();
      document.getElementById("done-overlay").classList.remove("visible");
    }

    // ---- Toast ----
    function showToast(message) {
      const toast = document.getElementById("toast");
      toast.textContent = message;
      toast.classList.add("visible");
      setTimeout(() => toast.classList.remove("visible"), 2000);
    }

    // ---- Keyboard nav ----
    document.addEventListener("keydown", (e) => {
      // Don't capture when typing in textarea
      if (e.target.tagName === "TEXTAREA") return;

      if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
        e.preventDefault();
        navigate(-1);
      } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
        e.preventDefault();
        navigate(1);
      }
    });

    // ---- Util ----
    function getDownloadUri(file) {
      if (file.data_uri) return file.data_uri;
      if (file.data_b64) return "data:application/octet-stream;base64," + file.data_b64;
      if (file.type === "text") return "data:text/plain;charset=utf-8," + encodeURIComponent(file.content);
      return "#";
    }

    function escapeHtml(text) {
      const div = document.createElement("div");
      div.textContent = text;
      return div.innerHTML;
    }

    // ---- View switching ----
    function switchView(view) {
      document.querySelectorAll(".view-tab").forEach(t => t.classList.remove("active"));
      document.querySelectorAll(".view-panel").forEach(p => p.classList.remove("active"));
      document.querySelector(`[onclick="switchView('${view}')"]`).classList.add("active");
      document.getElementById("panel-" + view).classList.add("active");
    }

    // ---- Benchmark rendering ----
    function renderBenchmark() {
      const data = EMBEDDED_DATA.benchmark;
      if (!data) return;

      // Show the tabs
      document.getElementById("view-tabs").style.display = "flex";

      const container = document.getElementById("benchmark-content");
      const summary = data.run_summary || {};
      const metadata = data.metadata || {};
      const notes = data.notes || [];

      let html = "";

      // Header
      html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
      html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
      if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> &mdash; ";
      if (metadata.timestamp) html += metadata.timestamp + " &mdash; ";
      if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " &mdash; ";
      html += (metadata.runs_per_configuration || "?") + " runs per configuration";
      html += "</p>";

      // Summary table
      html += '<table class="benchmark-table">';

      function fmtStat(stat, pct) {
        if (!stat) return "—";
        const suffix = pct ? "%" : "";
        const m = pct ? (stat.mean * 100).toFixed(0) : stat.mean.toFixed(1);
        const s = pct ? (stat.stddev * 100).toFixed(0) : stat.stddev.toFixed(1);
        return m + suffix + " ± " + s + suffix;
      }

      function deltaClass(val) {
        if (!val) return "";
        const n = parseFloat(val);
        if (n > 0) return "benchmark-delta-positive";
        if (n < 0) return "benchmark-delta-negative";
        return "";
      }

      // Discover config names dynamically (everything except "delta")
      const configs = Object.keys(summary).filter(k => k !== "delta");
      const configA = configs[0] || "config_a";
      const configB = configs[1] || "config_b";
      const labelA = configA.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
      const labelB = configB.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
      const a = summary[configA] || {};
      const b = summary[configB] || {};
      const delta = summary.delta || {};

      html += "<thead><tr><th>Metric</th><th>" + escapeHtml(labelA) + "</th><th>" + escapeHtml(labelB) + "</th><th>Delta</th></tr></thead>";
      html += "<tbody>";

      html += "<tr><td><strong>Pass Rate</strong></td>";
      html += "<td>" + fmtStat(a.pass_rate, true) + "</td>";
      html += "<td>" + fmtStat(b.pass_rate, true) + "</td>";
      html += '<td class="' + deltaClass(delta.pass_rate) + '">' + (delta.pass_rate || "—") + "</td></tr>";

      // Time (only show row if data exists)
      if (a.time_seconds || b.time_seconds) {
        html += "<tr><td><strong>Time (s)</strong></td>";
        html += "<td>" + fmtStat(a.time_seconds, false) + "</td>";
        html += "<td>" + fmtStat(b.time_seconds, false) + "</td>";
        html += '<td class="' + deltaClass(delta.time_seconds) + '">' + (delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
      }

      // Tokens (only show row if data exists)
      if (a.tokens || b.tokens) {
        html += "<tr><td><strong>Tokens</strong></td>";
        html += "<td>" + fmtStat(a.tokens, false) + "</td>";
        html += "<td>" + fmtStat(b.tokens, false) + "</td>";
        html += '<td class="' + deltaClass(delta.tokens) + '">' + (delta.tokens || "—") + "</td></tr>";
      }

      html += "</tbody></table>";

      // Per-eval breakdown (if runs data available)
      const runs = data.runs || [];
      if (runs.length > 0) {
        const evalIds = [...new Set(runs.map(r => r.eval_id))].sort((a, b) => a - b);

        html += "<h3 style='font-family: Poppins, sans-serif; margin-bottom: 0.75rem;'>Per-Eval Breakdown</h3>";

        const hasTime = runs.some(r => r.result && r.result.time_seconds != null);
        const hasErrors = runs.some(r => r.result && r.result.errors > 0);

        for (const evalId of evalIds) {
          const evalRuns = runs.filter(r => r.eval_id === evalId);
          const evalName = evalRuns[0] && evalRuns[0].eval_name ? evalRuns[0].eval_name : "Eval " + evalId;

          html += "<h4 style='font-family: Poppins, sans-serif; margin: 1rem 0 0.5rem; color: var(--text);'>" + escapeHtml(evalName) + "</h4>";
          html += '<table class="benchmark-table">';
          html += "<thead><tr><th>Config</th><th>Run</th><th>Pass Rate</th>";
          if (hasTime) html += "<th>Time (s)</th>";
          if (hasErrors) html += "<th>Crashes During Execution</th>";
          html += "</tr></thead>";
          html += "<tbody>";

          // Group by config and render with average rows
          const configGroups = [...new Set(evalRuns.map(r => r.configuration))];
          for (let ci = 0; ci < configGroups.length; ci++) {
            const config = configGroups[ci];
            const configRuns = evalRuns.filter(r => r.configuration === config);
            if (configRuns.length === 0) continue;

            const rowClass = ci === 0 ? "benchmark-row-with" : "benchmark-row-without";
            const configLabel = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());

            for (const run of configRuns) {
              const r = run.result || {};
              const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
              html += '<tr class="' + rowClass + '">';
              html += "<td>" + configLabel + "</td>";
              html += "<td>" + run.run_number + "</td>";
              html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
              if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
              if (hasErrors) html += "<td>" + (r.errors || 0) + "</td>";
              html += "</tr>";
            }

            // Average row
            const rates = configRuns.map(r => (r.result || {}).pass_rate || 0);
            const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
            const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
            html += '<tr class="benchmark-row-avg ' + rowClass + '">';
            html += "<td>" + configLabel + "</td>";
            html += "<td>Avg</td>";
            html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
            if (hasTime) {
              const times = configRuns.map(r => (r.result || {}).time_seconds).filter(t => t != null);
              html += "<td>" + (times.length ? (times.reduce((a, b) => a + b, 0) / times.length).toFixed(1) : "—") + "</td>";
            }
            if (hasErrors) html += "<td></td>";
            html += "</tr>";
          }
          html += "</tbody></table>";

          // Per-assertion detail for this eval
          const runsWithExpectations = {};
          for (const config of configGroups) {
            runsWithExpectations[config] = evalRuns.filter(r => r.configuration === config && r.expectations && r.expectations.length > 0);
          }
          const hasAnyExpectations = Object.values(runsWithExpectations).some(runs => runs.length > 0);
          if (hasAnyExpectations) {
            // Collect all unique assertion texts across all configs
            const allAssertions = [];
            const seen = new Set();
            for (const config of configGroups) {
              for (const run of runsWithExpectations[config]) {
                for (const exp of (run.expectations || [])) {
                  if (!seen.has(exp.text)) {
                    seen.add(exp.text);
                    allAssertions.push(exp.text);
                  }
                }
              }
            }

            html += '<table class="benchmark-table" style="margin-top: 0.5rem;">';
            html += "<thead><tr><th>Assertion</th>";
            for (const config of configGroups) {
              const label = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
              html += "<th>" + escapeHtml(label) + "</th>";
            }
            html += "</tr></thead><tbody>";

            for (const assertionText of allAssertions) {
              html += "<tr><td>" + escapeHtml(assertionText) + "</td>";

              for (const config of configGroups) {
                html += "<td>";
                for (const run of runsWithExpectations[config]) {
                  const exp = (run.expectations || []).find(e => e.text === assertionText);
                  if (exp) {
                    const cls = exp.passed ? "benchmark-delta-positive" : "benchmark-delta-negative";
                    const icon = exp.passed ? "\u2713" : "\u2717";
                    html += '<span class="' + cls + '" title="Run ' + run.run_number + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
                  } else {
                    html += "— ";
                  }
                }
                html += "</td>";
              }
              html += "</tr>";
            }
            html += "</tbody></table>";
          }
        }
      }

      // Notes
      if (notes.length > 0) {
        html += '<div class="benchmark-notes">';
        html += "<h3>Analysis Notes</h3>";
        html += "<ul>";
        for (const note of notes) {
          html += "<li>" + escapeHtml(note) + "</li>";
        }
        html += "</ul></div>";
      }

      container.innerHTML = html;
    }

    // ---- Start ----
    init();
    renderBenchmark();
  </script>
</body>
</html>


================================================
FILE: .claude/skills/skill-creator/references/schemas.md
================================================
# JSON Schemas

This document defines the JSON schemas used by skill-creator.

---

## evals.json

Defines the evals for a skill. Located at `evals/evals.json` within the skill directory.

```json
{
  "skill_name": "example-skill",
  "evals": [
    {
      "id": 1,
      "prompt": "User's example prompt",
      "expected_output": "Description of expected result",
      "files": ["evals/files/sample1.pdf"],
      "expectations": [
        "The output includes X",
        "The skill used script Y"
      ]
    }
  ]
}
```

**Fields:**
- `skill_name`: Name matching the skill's frontmatter
- `evals[].id`: Unique integer identifier
- `evals[].prompt`: The task to execute
- `evals[].expected_output`: Human-readable description of success
- `evals[].files`: Optional list of input file paths (relative to skill root)
- `evals[].expectations`: List of verifiable statements

---

## history.json

Tracks version progression in Improve mode. Located at workspace root.

```json
{
  "started_at": "2026-01-15T10:30:00Z",
  "skill_name": "pdf",
  "current_best": "v2",
  "iterations": [
    {
      "version": "v0",
      "parent": null,
      "expectation_pass_rate": 0.65,
      "grading_result": "baseline",
      "is_current_best": false
    },
    {
      "version": "v1",
      "parent": "v0",
      "expectation_pass_rate": 0.75,
      "grading_result": "won",
      "is_current_best": false
    },
    {
      "version": "v2",
      "parent": "v1",
      "expectation_pass_rate": 0.85,
      "grading_result": "won",
      "is_current_best": true
    }
  ]
}
```

**Fields:**
- `started_at`: ISO timestamp of when improvement started
- `skill_name`: Name of the skill being improved
- `current_best`: Version identifier of the best performer
- `iterations[].version`: Version identifier (v0, v1, ...)
- `iterations[].parent`: Parent version this was derived from
- `iterations[].expectation_pass_rate`: Pass rate from grading
- `iterations[].grading_result`: "baseline", "won", "lost", or "tie"
- `iterations[].is_current_best`: Whether this is the current best version

---

## grading.json

Output from the grader agent. Located at `<run-dir>/grading.json`.

```json
{
  "expectations": [
    {
      "text": "The output includes the name 'John Smith'",
      "passed": true,
      "evidence": "Found in transcript Step 3: 'Extracted names: John Smith, Sarah Johnson'"
    },
    {
      "text": "The spreadsheet has a SUM formula in cell B10",
      "passed": false,
      "evidence": "No spreadsheet was created. The output was a text file."
    }
  ],
  "summary": {
    "passed": 2,
    "failed": 1,
    "total": 3,
    "pass_rate": 0.67
  },
  "execution_metrics": {
    "tool_calls": {
      "Read": 5,
      "Write": 2,
      "Bash": 8
    },
    "total_tool_calls": 15,
    "total_steps": 6,
    "errors_encountered": 0,
    "output_chars": 12450,
    "transcript_chars": 3200
  },
  "timing": {
    "executor_duration_seconds": 165.0,
    "grader_duration_seconds": 26.0,
    "total_duration_seconds": 191.0
  },
  "claims": [
    {
      "claim": "The form has 12 fillable fields",
      "type": "factual",
      "verified": true,
      "evidence": "Counted 12 fields in field_info.json"
    }
  ],
  "user_notes_summary": {
    "uncertainties": ["Used 2023 data, may be stale"],
    "needs_review": [],
    "workarounds": ["Fell back to text overlay for non-fillable fields"]
  },
  "eval_feedback": {
    "suggestions": [
      {
        "assertion": "The output includes the name 'John Smith'",
        "reason": "A hallucinated document that mentions the name would also pass"
      }
    ],
    "overall": "Assertions check presence but not correctness."
  }
}
```

**Fields:**
- `expectations[]`: Graded expectations with evidence
- `summary`: Aggregate pass/fail counts
- `execution_metrics`: Tool usage and output size (from executor's metrics.json)
- `timing`: Wall clock timing (from timing.json)
- `claims`: Extracted and verified claims from the output
- `user_notes_summary`: Issues flagged by the executor
- `eval_feedback`: (optional) Improvement suggestions for the evals, only present when the grader identifies issues worth raising

---

## metrics.json

Output from the executor agent. Located at `<run-dir>/outputs/metrics.json`.

```json
{
  "tool_calls": {
    "Read": 5,
    "Write": 2,
    "Bash": 8,
    "Edit": 1,
    "Glob": 2,
    "Grep": 0
  },
  "total_tool_calls": 18,
  "total_steps": 6,
  "files_created": ["filled_form.pdf", "field_values.json"],
  "errors_encountered": 0,
  "output_chars": 12450,
  "transcript_chars": 3200
}
```

**Fields:**
- `tool_calls`: Count per tool type
- `total_tool_calls`: Sum of all tool calls
- `total_steps`: Number of major execution steps
- `files_created`: List of output files created
- `errors_encountered`: Number of errors during execution
- `output_chars`: Total character count of output files
- `transcript_chars`: Character count of transcript

---

## timing.json

Wall clock timing for a run. Located at `<run-dir>/timing.json`.

**How to capture:** When a subagent task completes, the task notification includes `total_tokens` and `duration_ms`. Save these immediately — they are not persisted anywhere else and cannot be recovered after the fact.

```json
{
  "total_tokens": 84852,
  "duration_ms": 23332,
  "total_duration_seconds": 23.3,
  "executor_start": "2026-01-15T10:30:00Z",
  "executor_end": "2026-01-15T10:32:45Z",
  "executor_duration_seconds": 165.0,
  "grader_start": "2026-01-15T10:32:46Z",
  "grader_end": "2026-01-15T10:33:12Z",
  "grader_duration_seconds": 26.0
}
```

---

## benchmark.json

Output from Benchmark mode. Located at `benchmarks/<timestamp>/benchmark.json`.

```json
{
  "metadata": {
    "skill_name": "pdf",
    "skill_path": "/path/to/pdf",
    "executor_model": "claude-sonnet-4-20250514",
    "analyzer_model": "most-capable-model",
    "timestamp": "2026-01-15T10:30:00Z",
    "evals_run": [1, 2, 3],
    "runs_per_configuration": 3
  },

  "runs": [
    {
      "eval_id": 1,
      "eval_name": "Ocean",
      "configuration": "with_skill",
      "run_number": 1,
      "result": {
        "pass_rate": 0.85,
        "passed": 6,
        "failed": 1,
        "total": 7,
        "time_seconds": 42.5,
        "tokens": 3800,
        "tool_calls": 18,
        "errors": 0
      },
      "expectations": [
        {"text": "...", "passed": true, "evidence": "..."}
      ],
      "notes": [
        "Used 2023 data, may be stale",
        "Fell back to text overlay for non-fillable fields"
      ]
    }
  ],

  "run_summary": {
    "with_skill": {
      "pass_rate": {"mean": 0.85, "stddev": 0.05, "min": 0.80, "max": 0.90},
      "time_seconds": {"mean": 45.0, "stddev": 12.0, "min": 32.0, "max": 58.0},
      "tokens": {"mean": 3800, "stddev": 400, "min": 3200, "max": 4100}
    },
    "without_skill": {
      "pass_rate": {"mean": 0.35, "stddev": 0.08, "min": 0.28, "max": 0.45},
      "time_seconds": {"mean": 32.0, "stddev": 8.0, "min": 24.0, "max": 42.0},
      "tokens": {"mean": 2100, "stddev": 300, "min": 1800, "max": 2500}
    },
    "delta": {
      "pass_rate": "+0.50",
      "time_seconds": "+13.0",
      "tokens": "+1700"
    }
  },

  "notes": [
    "Assertion 'Output is a PDF file' passes 100% in both configurations - may not differentiate skill value",
    "Eval 3 shows high variance (50% ± 40%) - may be flaky or model-dependent",
    "Without-skill runs consistently fail on table extraction expectations",
    "Skill adds 13s average execution time but improves pass rate by 50%"
  ]
}
```

**Fields:**
- `metadata`: Information about the benchmark run
  - `skill_name`: Name of the skill
  - `timestamp`: When the benchmark was run
  - `evals_run`: List of eval names or IDs
  - `runs_per_configuration`: Number of runs per config (e.g. 3)
- `runs[]`: Individual run results
  - `eval_id`: Numeric eval identifier
  - `eval_name`: Human-readable eval name (used as section header in the viewer)
  - `configuration`: Must be `"with_skill"` or `"without_skill"` (the viewer uses this exact string for grouping and color coding)
  - `run_number`: Integer run number (1, 2, 3...)
  - `result`: Nested object with `pass_rate`, `passed`, `total`, `time_seconds`, `tokens`, `errors`
- `run_summary`: Statistical aggregates per configuration
  - `with_skill` / `without_skill`: Each contains `pass_rate`, `time_seconds`, `tokens` objects with `mean` and `stddev` fields
  - `delta`: Difference strings like `"+0.50"`, `"+13.0"`, `"+1700"`
- `notes`: Freeform observations from the analyzer

**Important:** The viewer reads these field names exactly. Using `config` instead of `configuration`, or putting `pass_rate` at the top level of a run instead of nested under `result`, will cause the viewer to show empty/zero values. Always reference this schema when generating benchmark.json manually.

---

## comparison.json

Output from blind comparator. Located at `<grading-dir>/comparison-N.json`.

```json
{
  "winner": "A",
  "reasoning": "Output A provides a complete solution with proper formatting and all required fields. Output B is missing the date field and has formatting inconsistencies.",
  "rubric": {
    "A": {
      "content": {
        "correctness": 5,
        "completeness": 5,
        "accuracy": 4
      },
      "structure": {
        "organization": 4,
        "formatting": 5,
        "usability": 4
      },
      "content_score": 4.7,
      "structure_score": 4.3,
      "overall_score": 9.0
    },
    "B": {
      "content": {
        "correctness": 3,
        "completeness": 2,
        "accuracy": 3
      },
      "structure": {
        "organization": 3,
        "formatting": 2,
        "usability": 3
      },
      "content_score": 2.7,
      "structure_score": 2.7,
      "overall_score": 5.4
    }
  },
  "output_quality": {
    "A": {
      "score": 9,
      "strengths": ["Complete solution", "Well-formatted", "All fields present"],
      "weaknesses": ["Minor style inconsistency in header"]
    },
    "B": {
      "score": 5,
      "strengths": ["Readable output", "Correct basic structure"],
      "weaknesses": ["Missing date field", "Formatting inconsistencies", "Partial data extraction"]
    }
  },
  "expectation_results": {
    "A": {
      "passed": 4,
      "total": 5,
      "pass_rate": 0.80,
      "details": [
        {"text": "Output includes name", "passed": true}
      ]
    },
    "B": {
      "passed": 3,
      "total": 5,
      "pass_rate": 0.60,
      "details": [
        {"text": "Output includes name", "passed": true}
      ]
    }
  }
}
```

---

## analysis.json

Output from post-hoc analyzer. Located at `<grading-dir>/analysis.json`.

```json
{
  "comparison_summary": {
    "winner": "A",
    "winner_skill": "path/to/winner/skill",
    "loser_skill": "path/to/loser/skill",
    "comparator_reasoning": "Brief summary of why comparator chose winner"
  },
  "winner_strengths": [
    "Clear step-by-step instructions for handling multi-page documents",
    "Included validation script that caught formatting errors"
  ],
  "loser_weaknesses": [
    "Vague instruction 'process the document appropriately' led to inconsistent behavior",
    "No script for validation, agent had to improvise"
  ],
  "instruction_following": {
    "winner": {
      "score": 9,
      "issues": ["Minor: skipped optional logging step"]
    },
    "loser": {
      "score": 6,
      "issues": [
        "Did not use the skill's formatting template",
        "Invented own approach instead of following step 3"
      ]
    }
  },
  "improvement_suggestions": [
    {
      "priority": "high",
      "category": "instructions",
      "suggestion": "Replace 'process the document appropriately' with explicit steps",
      "expected_impact": "Would eliminate ambiguity that caused inconsistent behavior"
    }
  ],
  "transcript_insights": {
    "winner_execution_pattern": "Read skill -> Followed 5-step process -> Used validation script",
    "loser_execution_pattern": "Read skill -> Unclear on approach -> Tried 3 different methods"
  }
}
```


================================================
FILE: .claude/skills/skill-creator/scripts/__init__.py
================================================


================================================
FILE: .claude/skills/skill-creator/scripts/aggregate_benchmark.py
================================================
#!/usr/bin/env python3
"""
Aggregate individual run results into benchmark summary statistics.

Reads grading.json files from run directories and produces:
- run_summary with mean, stddev, min, max for each metric
- delta between with_skill and without_skill configurations

Usage:
    python aggregate_benchmark.py <benchmark_dir>

Example:
    python aggregate_benchmark.py benchmarks/2026-01-15T10-30-00/

The script supports two directory layouts:

    Workspace layout (from skill-creator iterations):
    <benchmark_dir>/
    └── eval-N/
        ├── with_skill/
        │   ├── run-1/grading.json
        │   └── run-2/grading.json
        └── without_skill/
            ├── run-1/grading.json
            └── run-2/grading.json

    Legacy layout (with runs/ subdirectory):
    <benchmark_dir>/
    └── runs/
        └── eval-N/
            ├── with_skill/
            │   └── run-1/grading.json
            └── without_skill/
                └── run-1/grading.json
"""

import argparse
import json
import math
import sys
from datetime import datetime, timezone
from pathlib import Path


def calculate_stats(values: list[float]) -> dict:
    """Calculate mean, stddev, min, max for a list of values."""
    if not values:
        return {"mean": 0.0, "stddev": 0.0, "min": 0.0, "max": 0.0}

    n = len(values)
    mean = sum(values) / n

    if n > 1:
        variance = sum((x - mean) ** 2 for x in values) / (n - 1)
        stddev = math.sqrt(variance)
    else:
        stddev = 0.0

    return {
        "mean": round(mean, 4),
        "stddev": round(stddev, 4),
        "min": round(min(values), 4),
        "max": round(max(values), 4)
    }


def load_run_results(benchmark_dir: Path) -> dict:
    """
    Load all run results from a benchmark directory.

    Returns dict keyed by config name (e.g. "with_skill"/"without_skill",
    or "new_skill"/"old_skill"), each containing a list of run results.
    """
    # Support both layouts: eval dirs directly under benchmark_dir, or under runs/
    runs_dir = benchmark_dir / "runs"
    if runs_dir.exists():
        search_dir = runs_dir
    elif list(benchmark_dir.glob("eval-*")):
        search_dir = benchmark_dir
    else:
        print(f"No eval directories found in {benchmark_dir} or {benchmark_dir / 'runs'}")
        return {}

    results: dict[str, list] = {}

    for eval_idx, eval_dir in enumerate(sorted(search_dir.glob("eval-*"))):
        metadata_path = eval_dir / "eval_metadata.json"
        if metadata_path.exists():
            try:
                with open(metadata_path) as mf:
                    eval_id = json.load(mf).get("eval_id", eval_idx)
            except (json.JSONDecodeError, OSError):
                eval_id = eval_idx
        else:
            try:
                eval_id = int(eval_dir.name.split("-")[1])
            except ValueError:
                eval_id = eval_idx

        # Discover config directories dynamically rather than hardcoding names
        for config_dir in sorted(eval_dir.iterdir()):
            if not config_dir.is_dir():
                continue
            # Skip non-config directories (inputs, outputs, etc.)
            if not list(config_dir.glob("run-*")):
                continue
            config = config_dir.name
            if config not in results:
                results[config] = []

            for run_dir in sorted(config_dir.glob("run-*")):
                run_number = int(run_dir.name.split("-")[1])
                grading_file = run_dir / "grading.json"

                if not grading_file.exists():
                    print(f"Warning: grading.json not found in {run_dir}")
                    continue

                try:
                    with open(grading_file) as f:
                        grading = json.load(f)
                except json.JSONDecodeError as e:
                    print(f"Warning: Invalid JSON in {grading_file}: {e}")
                    continue

                # Extract metrics
                result = {
                    "eval_id": eval_id,
                    "run_number": run_number,
                    "pass_rate": grading.get("summary", {}).get("pass_rate", 0.0),
                    "passed": grading.get("summary", {}).get("passed", 0),
                    "failed": grading.get("summary", {}).get("failed", 0),
                    "total": grading.get("summary", {}).get("total", 0),
                }

                # Extract timing — check grading.json first, then sibling timing.json
                timing = grading.get("timing", {})
                result["time_seconds"] = timing.get("total_duration_seconds", 0.0)
                timing_file = run_dir / "timing.json"
                if result["time_seconds"] == 0.0 and timing_file.exists():
                    try:
                        with open(timing_file) as tf:
                            timing_data = json.load(tf)
                        result["time_seconds"] = timing_data.get("total_duration_seconds", 0.0)
                        result["tokens"] = timing_data.get("total_tokens", 0)
                    except json.JSONDecodeError:
                        pass

                # Extract metrics if available
                metrics = grading.get("execution_metrics", {})
                result["tool_calls"] = metrics.get("total_tool_calls", 0)
                if not result.get("tokens"):
                    result["tokens"] = metrics.get("output_chars", 0)
                result["errors"] = metrics.get("errors_encountered", 0)

                # Extract expectations — viewer requires fields: text, passed, evidence
                raw_expectations = grading.get("expectations", [])
                for exp in raw_expectations:
                    if "text" not in exp or "passed" not in exp:
                        print(f"Warning: expectation in {grading_file} missing required fields (text, passed, evidence): {exp}")
                result["expectations"] = raw_expectations

                # Extract notes from user_notes_summary
                notes_summary = grading.get("user_notes_summary", {})
                notes = []
                notes.extend(notes_summary.get("uncertainties", []))
                notes.extend(notes_summary.get("needs_review", []))
                notes.extend(notes_summary.get("workarounds", []))
                result["notes"] = notes

                results[config].append(result)

    return results


def aggregate_results(results: dict) -> dict:
    """
    Aggregate run results into summary statistics.

    Returns run_summary with stats for each configuration and delta.
    """
    run_summary = {}
    configs = list(results.keys())

    for config in configs:
        runs = results.get(config, [])

        if not runs:
            run_summary[config] = {
                "pass_rate": {"mean": 0.0, "stddev": 0.0, "min": 0.0, "max": 0.0},
                "time_seconds": {"mean": 0.0, "stddev": 0.0, "min": 0.0, "max": 0.0},
                "tokens": {"mean": 0, "stddev": 0, "min": 0, "max": 0}
            }
            continue

        pass_rates = [r["pass_rate"] for r in runs]
        times = [r["time_seconds"] for r in runs]
        tokens = [r.get("tokens", 0) for r in runs]

        run_summary[config] = {
            "pass_rate": calculate_stats(pass_rates),
            "time_seconds": calculate_stats(times),
            "tokens": calculate_stats(tokens)
        }

    # Calculate delta between the first two configs (if two exist)
    if len(configs) >= 2:
        primary = run_summary.get(configs[0], {})
        baseline = run_summary.get(configs[1], {})
    else:
        primary = run_summary.get(configs[0], {}) if configs else {}
        baseline = {}

    delta_pass_rate = primary.get("pass_rate", {}).get("mean", 0) - baseline.get("pass_rate", {}).get("mean", 0)
    delta_time = primary.get("time_seconds", {}).get("mean", 0) - baseline.get("time_seconds", {}).get("mean", 0)
    delta_tokens = primary.get("tokens", {}).get("mean", 0) - baseline.get("tokens", {}).get("mean", 0)

    run_summary["delta"] = {
        "pass_rate": f"{delta_pass_rate:+.2f}",
        "time_seconds": f"{delta_time:+.1f}",
        "tokens": f"{delta_tokens:+.0f}"
    }

    return run_summary


def generate_benchmark(benchmark_dir: Path, skill_name: str = "", skill_path: str = "") -> dict:
    """
    Generate complete benchmark.json from run results.
    """
    results = load_run_results(benchmark_dir)
    run_summary = aggregate_results(results)

    # Build runs array for benchmark.json
    runs = []
    for config in results:
        for result in results[config]:
            runs.append({
                "eval_id": result["eval_id"],
                "configuration": config,
                "run_number": result["run_number"],
                "result": {
                    "pass_rate": result["pass_rate"],
                    "passed": result["passed"],
                    "failed": result["failed"],
                    "total": result["total"],
                    "time_seconds": result["time_seconds"],
                    "tokens": result.get("tokens", 0),
                    "tool_calls": result.get("tool_calls", 0),
                    "errors": result.get("errors", 0)
                },
                "expectations": result["expectations"],
                "notes": result["notes"]
            })

    # Determine eval IDs from results
    eval_ids = sorted(set(
        r["eval_id"]
        for config in results.values()
        for r in config
    ))

    benchmark = {
        "metadata": {
            "skill_name": skill_name or "<skill-name>",
            "skill_path": skill_path or "<path/to/skill>",
            "executor_model": "<model-name>",
            "analyzer_model": "<model-name>",
            "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
            "evals_run": eval_ids,
            "runs_per_configuration": 3
        },
        "runs": runs,
        "run_summary": run_summary,
        "notes": []  # To be filled by analyzer
    }

    return benchmark


def generate_markdown(benchmark: dict) -> str:
    """Generate human-readable benchmark.md from benchmark data."""
    metadata = benchmark["metadata"]
    run_summary = benchmark["run_summary"]

    # Determine config names (excluding "delta")
    configs = [k for k in run_summary if k != "delta"]
    config_a = configs[0] if len(configs) >= 1 else "config_a"
    config_b = configs[1] if len(configs) >= 2 else "config_b"
    label_a = config_a.replace("_", " ").title()
    label_b = config_b.replace("_", " ").title()

    lines = [
        f"# Skill Benchmark: {metadata['skill_name']}",
        "",
        f"**Model**: {metadata['executor_model']}",
        f"**Date**: {metadata['timestamp']}",
        f"**Evals**: {', '.join(map(str, metadata['evals_run']))} ({metadata['runs_per_configuration']} runs each per configuration)",
        "",
        "## Summary",
        "",
        f"| Metric | {label_a} | {label_b} | Delta |",
        "|--------|------------|---------------|-------|",
    ]

    a_summary = run_summary.get(config_a, {})
    b_summary = run_summary.get(config_b, {})
    delta = run_summary.get("delta", {})

    # Format pass rate
    a_pr = a_summary.get("pass_rate", {})
    b_pr = b_summary.get("pass_rate", {})
    lines.append(f"| Pass Rate | {a_pr.get('mean', 0)*100:.0f}% ± {a_pr.get('stddev', 0)*100:.0f}% | {b_pr.get('mean', 0)*100:.0f}% ± {b_pr.get('stddev', 0)*100:.0f}% | {delta.get('pass_rate', '—')} |")

    # Format time
    a_time = a_summary.get("time_seconds", {})
    b_time = b_summary.get("time_seconds", {})
    lines.append(f"| Time | {a_time.get('mean', 0):.1f}s ± {a_time.get('stddev', 0):.1f}s | {b_time.get('mean', 0):.1f}s ± {b_time.get('stddev', 0):.1f}s | {delta.get('time_seconds', '—')}s |")

    # Format tokens
    a_tokens = a_summary.get("tokens", {})
    b_tokens = b_summary.get("tokens", {})
    lines.append(f"| Tokens | {a_tokens.get('mean', 0):.0f} ± {a_tokens.get('stddev', 0):.0f} | {b_tokens.get('mean', 0):.0f} ± {b_tokens.get('stddev', 0):.0f} | {delta.get('tokens', '—')} |")

    # Notes section
    if benchmark.get("notes"):
        lines.extend([
            "",
            "## Notes",
            ""
        ])
        for note in benchmark["notes"]:
            lines.append(f"- {note}")

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(
        description="Aggregate benchmark run results into summary statistics"
    )
    parser.add_argument(
        "benchmark_dir",
        type=Path,
        help="Path to the benchmark directory"
    )
    parser.add_argument(
        "--skill-name",
        default="",
        help="Name of the skill being benchmarked"
    )
    parser.add_argument(
        "--skill-path",
        default="",
        help="Path to the skill being benchmarked"
    )
    parser.add_argument(
        "--output", "-o",
        type=Path,
        help="Output path for benchmark.json (default: <benchmark_dir>/benchmark.json)"
    )

    args = parser.parse_args()

    if not args.benchmark_dir.exists():
        print(f"Directory not found: {args.benchmark_dir}")
        sys.exit(1)

    # Generate benchmark
    benchmark = generate_benchmark(args.benchmark_dir, args.skill_name, args.skill_path)

    # Determine output paths
    output_json = args.output or (args.benchmark_dir / "benchmark.json")
    output_md = output_json.with_suffix(".md")

    # Write benchmark.json
    with open(output_json, "w") as f:
        json.dump(benchmark, f, indent=2)
    print(f"Generated: {output_json}")

    # Write benchmark.md
    markdown = generate_markdown(benchmark)
    with open(output_md, "w") as f:
        f.write(markdown)
    print(f"Generated: {output_md}")

    # Print summary
    run_summary = benchmark["run_summary"]
    configs = [k for k in run_summary if k != "delta"]
    delta = run_summary.get("delta", {})

    print(f"\nSummary:")
    for config in configs:
        pr = run_summary[config]["pass_rate"]["mean"]
        label = config.replace("_", " ").title()
        print(f"  {label}: {pr*100:.1f}% pass rate")
    print(f"  Delta:         {delta.get('pass_rate', '—')}")


if __name__ == "__main__":
    main()


================================================
FILE: .claude/skills/skill-creator/scripts/generate_report.py
================================================
#!/usr/bin/env python3
"""Generate an HTML report from run_loop.py output.

Takes the JSON output from run_loop.py and generates a visual HTML report
showing each description attempt with check/x for each test case.
Distinguishes between train and test queries.
"""

import argparse
import html
import json
import sys
from pathlib import Path


def generate_html(data: dict, auto_refresh: bool = False, skill_name: str = "") -> str:
    """Generate HTML report from loop output data. If auto_refresh is True, adds a meta refresh tag."""
    history = data.get("history", [])
    holdout = data.get("holdout", 0)
    title_prefix = html.escape(skill_name + " \u2014 ") if skill_name else ""

    # Get all unique queries from train and test sets, with should_trigger info
    train_queries: list[dict] = []
    test_queries: list[dict] = []
    if history:
        for r in history[0].get("train_results", history[0].get("results", [])):
            train_queries.append({"query": r["query"], "should_trigger": r.get("should_trigger", True)})
        if history[0].get("test_results"):
            for r in history[0].get("test_results", []):
                test_queries.append({"query": r["query"], "should_trigger": r.get("should_trigger", True)})

    refresh_tag = '    <meta http-equiv="refresh" content="5">\n' if auto_refresh else ""

    html_parts = ["""<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
""" + refresh_tag + """    <title>""" + title_prefix + """Skill Description Optimization</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Lora', Georgia, serif;
            max-width: 100%;
            margin: 0 auto;
            padding: 20px;
            background: #faf9f5;
            color: #141413;
        }
        h1 { font-family: 'Poppins', sans-serif; color: #141413; }
        .explainer {
            background: white;
            padding: 15px;
            border-radius: 6px;
            margin-bottom: 20px;
            border: 1px solid #e8e6dc;
            color: #b0aea5;
            font-size: 0.875rem;
            line-height: 1.6;
        }
        .summary {
            background: white;
            padding: 15px;
            border-radius: 6px;
            margin-bottom: 20px;
            border: 1px solid #e8e6dc;
        }
        .summary p { margin: 5px 0; }
        .best { color: #788c5d; font-weight: bold; }
        .table-container {
            overflow-x: auto;
            width: 100%;
        }
        table {
            border-collapse: collapse;
            background: white;
            border: 1px solid #e8e6dc;
            border-radius: 6px;
            font-size: 12px;
            min-width: 100%;
        }
        th, td {
            padding: 8px;
            text-align: left;
            border: 1px solid #e8e6dc;
            white-space: normal;
            word-wrap: break-word;
        }
        th {
            font-family: 'Poppins', sans-serif;
            background: #141413;
            color: #faf9f5;
            font-weight: 500;
        }
        th.test-col {
            background: #6a9bcc;
        }
        th.query-col { min-width: 200px; }
        td.description {
            font-family: monospace;
            font-size: 11px;
            word-wrap: break-word;
            max-width: 400px;
        }
        td.result {
            text-align: center;
            font-size: 16px;
            min-width: 40px;
        }
        td.test-result {
            background: #f0f6fc;
        }
        .pass { color: #788c5d; }
        .fail { color: #c44; }
        .rate {
            font-size: 9px;
            color: #b0aea5;
            display: block;
        }
        tr:hover { background: #faf9f5; }
        .score {
            display: inline-block;
            padding: 2px 6px;
            border-radius: 4px;
            font-weight: bold;
            font-size: 11px;
        }
        .score-good { background: #eef2e8; color: #788c5d; }
        .score-ok { background: #fef3c7; color: #d97706; }
        .score-bad { background: #fceaea; color: #c44; }
        .train-label { color: #b0aea5; font-size: 10px; }
        .test-label { color: #6a9bcc; font-size: 10px; font-weight: bold; }
        .best-row { background: #f5f8f2; }
        th.positive-col { border-bottom: 3px solid #788c5d; }
        th.negative-col { border-bottom: 3px solid #c44; }
        th.test-col.positive-col { border-bottom: 3px solid #788c5d; }
        th.test-col.negative-col { border-bottom: 3px solid #c44; }
        .legend { font-family: 'Poppins', sans-serif; display: flex; gap: 20px; margin-bottom: 10px; font-size: 13px; align-items: center; }
        .legend-item { display: flex; align-items: center; gap: 6px; }
        .legend-swatch { width: 16px; height: 16px; border-radius: 3px; display: inline-block; }
        .swatch-positive { background: #141413; border-bottom: 3px solid #788c5d; }
        .swatch-negative { background: #141413; border-bottom: 3px solid #c44; }
        .swatch-test { background: #6a9bcc; }
        .swatch-train { background: #141413; }
    </style>
</head>
<body>
    <h1>""" + title_prefix + """Skill Description Optimization</h1>
    <div class="explainer">
        <strong>Optimizing your skill's description.</strong> This page updates automatically as Claude tests different versions of your skill's description. Each row is an iteration — a new description attempt. The columns show test queries: green checkmarks mean the skill triggered correctly (or correctly didn't trigger), red crosses mean it got it wrong. The "Train" score shows performance on queries used to improve the description; the "Test" score shows performance on held-out queries the optimizer hasn't seen. When it's done, Claude will apply the best-performing description to your skill.
    </div>
"""]

    # Summary section
    best_test_score = data.get('best_test_score')
    best_train_score = data.get('best_train_score')
    html_parts.append(f"""
    <div class="summary">
        <p><strong>Original:</strong> {html.escape(data.get('original_description', 'N/A'))}</p>
        <p class="best"><strong>Best:</strong> {html.escape(data.get('best_description', 'N/A'))}</p>
        <p><strong>Best Score:</strong> {data.get('best_score', 'N/A')} {'(test)' if best_test_score else '(train)'}</p>
        <p><strong>Iterations:</strong> {data.get('iterations_run', 0)} | <strong>Train:</strong> {data.get('train_size', '?')} | <strong>Test:</strong> {data.get('test_size', '?')}</p>
    </div>
""")

    # Legend
    html_parts.append("""
    <div class="legend">
        <span style="font-weight:600">Query columns:</span>
        <span class="legend-item"><span class="legend-swatch swatch-positive"></span> Should trigger</span>
        <span class="legend-item"><span class="legend-swatch swatch-negative"></span> Should NOT trigger</span>
        <span class="legend-item"><span class="legend-swatch swatch-train"></span> Train</span>
        <span class="legend-item"><span class="legend-swatch swatch-test"></span> Test</span>
    </div>
""")

    # Table header
    html_parts.append("""
    <div class="table-container">
    <table>
        <thead>
            <tr>
                <th>Iter</th>
                <th>Train</th>
                <th>Test</th>
                <th class="query-col">Description</th>
""")

    # Add column headers for train queries
    for qinfo in train_queries:
        polarity = "positive-col" if qinfo["should_trigger"] else "negative-col"
        html_parts.append(f'                <th class="{polarity}">{html.escape(qinfo["query"])}</th>\n')

    # Add column headers for test queries (different color)
    for qinfo in test_queries:
        polarity = "positive-col" if qinfo["should_trigger"] else "negative-col"
        html_parts.append(f'                <th class="test-col {polarity}">{html.escape(qinfo["query"])}</th>\n')

    html_parts.append("""            </tr>
        </thead>
        <tbody>
""")

    # Find best iteration for highlighting
    if test_queries:
        best_iter = max(history, key=lambda h: h.get("test_passed") or 0).get("iteration")
    else:
        best_iter = max(history, key=lambda h: h.get("train_passed", h.get("passed", 0))).get("iteration")

    # Add rows for each iteration
    for h in history:
        iteration = h.get("iteration", "?")
        train_passed = h.get("train_passed", h.get("passed", 0))
        train_total = h.get("train_total", h.get("total", 0))
        test_passed = h.get("test_passed")
        test_total = h.get("test_total")
        description = h.get("description", "")
        train_results = h.get("train_results", h.get("results", []))
        test_results = h.get("test_results", [])

        # Create lookups for results by query
        train_by_query = {r["query"]: r for r in train_results}
        test_by_query = {r["query"]: r for r in test_results} if test_results else {}

        # Compute aggregate correct/total runs across all retries
        def aggregate_runs(results: list[dict]) -> tuple[int, int]:
            correct = 0
            total = 0
            for r in results:
                runs = r.get("runs", 0)
                triggers = r.get("triggers", 0)
                total += runs
                if r.get("should_trigger", True):
                    correct += triggers
                else:
                    correct += runs - triggers
            return correct, total

        train_correct, train_runs = aggregate_runs(train_results)
        test_correct, test_runs = aggregate_runs(test_results)

        # Determine score classes
        def score_class(correct: int, total: int) -> str:
            if total > 0:
                ratio = correct / total
                if ratio >= 0.8:
                    return "score-good"
                elif ratio >= 0.5:
                    return "score-ok"
            return "score-bad"

        train_class = score_class(train_correct, train_runs)
        test_class = score_class(test_correct, test_runs)

        row_class = "best-row" if iteration == best_iter else ""

        html_parts.append(f"""            <tr class="{row_class}">
                <td>{iteration}</td>
                <td><span class="score {train_class}">{train_correct}/{train_runs}</span></td>
                <td><span class="score {test_class}">{test_correct}/{test_runs}</span></td>
                <td class="description">{html.escape(description)}</td>
""")

        # Add result for each train query
        for qinfo in train_queries:
            r = train_by_query.get(qinfo["query"], {})
            did_pass = r.get("pass", False)
            triggers = r.get("triggers", 0)
            runs = r.get("runs", 0)

            icon = "✓" if did_pass else "✗"
            css_class = "pass" if did_pass else "fail"

            html_parts.append(f'                <td class="result {css_class}">{icon}<span class="rate">{triggers}/{runs}</span></td>\n')

        # Add result for each test query (with different background)
        for qinfo in test_queries:
            r = test_by_query.get(qinfo["query"], {})
            did_pass = r.get("pass", False)
            triggers = r.get("triggers", 0)
            runs = r.get("runs", 0)

            icon = "✓" if did_pass else "✗"
            css_class = "pass" if did_pass else "fail"

            html_parts.append(f'                <td class="result test-result {css_class}">{icon}<span class="rate">{triggers}/{runs}</span></td>\n')

        html_parts.append("            </tr>\n")

    html_parts.append("""        </tbody>
    </table>
    </div>
""")

    html_parts.append("""
</body>
</html>
""")

    return "".join(html_parts)


def main():
    parser = argparse.ArgumentParser(description="Generate HTML report from run_loop output")
    parser.add_argument("input", help="Path to JSON output from run_loop.py (or - for stdin)")
    parser.add_argument("-o", "--output", default=None, help="Output HTML file (default: stdout)")
    parser.add_argument("--skill-name", default="", help="Skill name to include in the report title")
    args = parser.parse_args()

    if args.input == "-":
        data = json.load(sys.stdin)
    else:
        data = json.loads(Path(args.input).read_text())

    html_output = generate_html(data, skill_name=args.skill_name)

    if args.output:
        Path(args.output).write_text(html_output)
        print(f"Report written to {args.output}", file=sys.stderr)
    else:
        print(html_output)


if __name__ == "__main__":
    main()


================================================
FILE: .claude/skills/skill-creator/scripts/improve_description.py
================================================
#!/usr/bin/env python3
"""Improve a skill description based on eval results.

Takes eval results (from run_eval.py) and generates an improved description
by calling `claude -p` as a subprocess (same auth pattern as run_eval.py —
uses the session's Claude Code auth, no separate ANTHROPIC_API_KEY needed).
"""

import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path

from scripts.utils import parse_skill_md


def _call_claude(prompt: str, model: str | None, timeout: int = 300) -> str:
    """Run `claude -p` with the prompt on stdin and return the text response.

    Prompt goes over stdin (not argv) because it embeds the full SKILL.md
    body and can easily exceed comfortable argv length.
    """
    cmd = ["claude", "-p", "--output-format", "text"]
    if model:
        cmd.extend(["--model", model])

    # Remove CLAUDECODE env var to allow nesting claude -p inside a
    # Claude Code session. The guard is for interactive terminal conflicts;
    # programmatic subprocess usage is safe. Same pattern as run_eval.py.
    env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}

    result = subprocess.run(
        cmd,
        input=prompt,
        capture_output=True,
        text=True,
        env=env,
        timeout=timeout,
    )
    if result.returncode != 0:
        raise RuntimeError(
            f"claude -p exited {result.returncode}\nstderr: {result.stderr}"
        )
    return result.stdout


def improve_description(
    skill_name: str,
    skill_content: str,
    current_description: str,
    eval_results: dict,
    history: list[dict],
    model: str,
    test_results: dict | None = None,
    log_dir: Path | None = None,
    iteration: int | None = None,
) -> str:
    """Call Claude to improve the description based on eval results."""
    failed_triggers = [
        r for r in eval_results["results"]
        if r["should_trigger"] and not r["pass"]
    ]
    false_triggers = [
        r for r in eval_results["results"]
        if not r["should_trigger"] and not r["pass"]
    ]

    # Build scores summary
    train_score = f"{eval_results['summary']['passed']}/{eval_results['summary']['total']}"
    if test_results:
        test_score = f"{test_results['summary']['passed']}/{test_results['summary']['total']}"
        scores_summary = f"Train: {train_score}, Test: {test_score}"
    else:
        scores_summary = f"Train: {train_score}"

    prompt = f"""You are optimizing a skill description for a Claude Code skill called "{skill_name}". A "skill" is sort of like a prompt, but with progressive disclosure -- there's a title and description that Claude sees when deciding whether to use the skill, and then if it does use the skill, it reads the .md file which has lots more details and potentially links to other resources in the skill folder like helper files and scripts and additional documentation or examples.

The description appears in Claude's "available_skills" list. When a user sends a query, Claude decides whether to invoke the skill based solely on the title and on this description. Your goal is to write a description that triggers for relevant queries, and doesn't trigger for irrelevant ones.

Here's the current description:
<current_description>
"{current_description}"
</current_description>

Current scores ({scores_summary}):
<scores_summary>
"""
    if failed_triggers:
        prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n"
        for r in failed_triggers:
            prompt += f'  - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
        prompt += "\n"

    if false_triggers:
        prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n"
        for r in false_triggers:
            prompt += f'  - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
        prompt += "\n"

    if history:
        prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n"
        for h in history:
            train_s = f"{h.get('train_passed', h.get('passed', 0))}/{h.get('train_total', h.get('total', 0))}"
            test_s = f"{h.get('test_passed', '?')}/{h.get('test_total', '?')}" if h.get('test_passed') is not None else None
            score_str = f"train={train_s}" + (f", test={test_s}" if test_s else "")
            prompt += f'<attempt {score_str}>\n'
            prompt += f'Description: "{h["description"]}"\n'
            if "results" in h:
                prompt += "Train results:\n"
                for r in h["results"]:
                    status = "PASS" if r["pass"] else "FAIL"
                    prompt += f'  [{status}] "{r["query"][:80]}" (triggered {r["triggers"]}/{r["runs"]})\n'
            if h.get("note"
Download .txt
gitextract_ewj5gk9a/

├── .cargo/
│   ├── audit.toml
│   └── config.toml
├── .claude/
│   └── skills/
│       ├── github-issue/
│       │   └── SKILL.md
│       ├── github-pr/
│       │   └── SKILL.md
│       ├── skill-creator/
│       │   ├── LICENSE.txt
│       │   ├── SKILL.md
│       │   ├── agents/
│       │   │   ├── analyzer.md
│       │   │   ├── comparator.md
│       │   │   └── grader.md
│       │   ├── assets/
│       │   │   └── eval_review.html
│       │   ├── eval-viewer/
│       │   │   ├── generate_review.py
│       │   │   └── viewer.html
│       │   ├── references/
│       │   │   └── schemas.md
│       │   └── scripts/
│       │       ├── __init__.py
│       │       ├── aggregate_benchmark.py
│       │       ├── generate_report.py
│       │       ├── improve_description.py
│       │       ├── package_skill.py
│       │       ├── quick_validate.py
│       │       ├── run_eval.py
│       │       ├── run_loop.py
│       │       └── utils.py
│       └── zeroclaw/
│           ├── SKILL.md
│           ├── evals/
│           │   └── evals.json
│           └── references/
│               ├── cli-reference.md
│               └── rest-api.md
├── .coderabbit.yaml
├── .dockerignore
├── .editorconfig
├── .envrc
├── .gemini/
│   └── style-guide.md
├── .gitattributes
├── .githooks/
│   ├── pre-commit
│   └── pre-push
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actionlint.yaml
│   ├── codeql/
│   │   └── codeql-config.yml
│   ├── dependabot.yml
│   ├── label-policy.json
│   ├── labeler.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── README.md
│       ├── checks-on-pr.yml
│       ├── ci-run.yml
│       ├── cross-platform-build-manual.yml
│       ├── master-branch-flow.md
│       ├── pub-aur.yml
│       ├── pub-homebrew-core.yml
│       ├── pub-scoop.yml
│       ├── publish-crates-auto.yml
│       ├── publish-crates.yml
│       ├── release-beta-on-push.yml
│       ├── release-stable-manual.yml
│       └── tweet-release.yml
├── .gitignore
├── .markdownlint-cli2.yaml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile
├── Dockerfile.ci
├── Dockerfile.debian
├── Dockerfile.debian.ci
├── Justfile
├── LICENSE-APACHE
├── LICENSE-MIT
├── NOTICE
├── README.ar.md
├── README.bn.md
├── README.cs.md
├── README.da.md
├── README.de.md
├── README.el.md
├── README.es.md
├── README.fi.md
├── README.fr.md
├── README.he.md
├── README.hi.md
├── README.hu.md
├── README.id.md
├── README.it.md
├── README.ja.md
├── README.ko.md
├── README.md
├── README.nb.md
├── README.nl.md
├── README.pl.md
├── README.pt.md
├── README.ro.md
├── README.ru.md
├── README.sv.md
├── README.th.md
├── README.tl.md
├── README.tr.md
├── README.uk.md
├── README.ur.md
├── README.vi.md
├── README.zh-CN.md
├── SECURITY.md
├── benches/
│   └── agent_benchmarks.rs
├── build.rs
├── clippy.toml
├── crates/
│   └── robot-kit/
│       ├── Cargo.toml
│       ├── PI5_SETUP.md
│       ├── README.md
│       ├── SOUL.md
│       ├── robot.toml
│       └── src/
│           ├── config.rs
│           ├── drive.rs
│           ├── emote.rs
│           ├── lib.rs
│           ├── listen.rs
│           ├── look.rs
│           ├── safety.rs
│           ├── sense.rs
│           ├── speak.rs
│           ├── tests.rs
│           └── traits.rs
├── deny.toml
├── dev/
│   ├── README.md
│   ├── ci/
│   │   └── Dockerfile
│   ├── ci.sh
│   ├── cli.sh
│   ├── config.template.toml
│   ├── docker-compose.ci.yml
│   ├── docker-compose.yml
│   ├── recompute_contributor_tiers.sh
│   ├── sandbox/
│   │   └── Dockerfile
│   └── test-termux-release.sh
├── dist/
│   ├── aur/
│   │   ├── .SRCINFO
│   │   └── PKGBUILD
│   └── scoop/
│       └── zeroclaw.json
├── docker-compose.yml
├── docs/
│   ├── README.ar.md
│   ├── README.bn.md
│   ├── README.cs.md
│   ├── README.da.md
│   ├── README.de.md
│   ├── README.el.md
│   ├── README.es.md
│   ├── README.fi.md
│   ├── README.fr.md
│   ├── README.he.md
│   ├── README.hi.md
│   ├── README.hu.md
│   ├── README.id.md
│   ├── README.it.md
│   ├── README.ja.md
│   ├── README.ko.md
│   ├── README.md
│   ├── README.nb.md
│   ├── README.nl.md
│   ├── README.pl.md
│   ├── README.pt.md
│   ├── README.ro.md
│   ├── README.ru.md
│   ├── README.sv.md
│   ├── README.th.md
│   ├── README.tl.md
│   ├── README.tr.md
│   ├── README.uk.md
│   ├── README.ur.md
│   ├── README.vi.md
│   ├── README.zh-CN.md
│   ├── SUMMARY.ar.md
│   ├── SUMMARY.bn.md
│   ├── SUMMARY.cs.md
│   ├── SUMMARY.da.md
│   ├── SUMMARY.de.md
│   ├── SUMMARY.el.md
│   ├── SUMMARY.es.md
│   ├── SUMMARY.fi.md
│   ├── SUMMARY.fr.md
│   ├── SUMMARY.he.md
│   ├── SUMMARY.hi.md
│   ├── SUMMARY.hu.md
│   ├── SUMMARY.id.md
│   ├── SUMMARY.it.md
│   ├── SUMMARY.ja.md
│   ├── SUMMARY.ko.md
│   ├── SUMMARY.md
│   ├── SUMMARY.nb.md
│   ├── SUMMARY.nl.md
│   ├── SUMMARY.pl.md
│   ├── SUMMARY.pt.md
│   ├── SUMMARY.ro.md
│   ├── SUMMARY.ru.md
│   ├── SUMMARY.sv.md
│   ├── SUMMARY.th.md
│   ├── SUMMARY.tl.md
│   ├── SUMMARY.tr.md
│   ├── SUMMARY.uk.md
│   ├── SUMMARY.ur.md
│   ├── SUMMARY.vi.md
│   ├── SUMMARY.zh-CN.md
│   ├── assets/
│   │   └── architecture-diagrams.md
│   ├── contributing/
│   │   ├── README.md
│   │   ├── actions-source-policy.md
│   │   ├── adding-boards-and-tools.md
│   │   ├── cargo-slicer-speedup.md
│   │   ├── change-playbooks.md
│   │   ├── ci-map.md
│   │   ├── cla.md
│   │   ├── custom-providers.md
│   │   ├── doc-template.md
│   │   ├── docs-contract.md
│   │   ├── extension-examples.md
│   │   ├── langgraph-integration.md
│   │   ├── pr-discipline.md
│   │   ├── pr-workflow.md
│   │   ├── release-process.md
│   │   ├── reviewer-playbook.md
│   │   ├── testing-telegram.md
│   │   └── testing.md
│   ├── hardware/
│   │   ├── README.md
│   │   ├── android-setup.md
│   │   ├── arduino-uno-q-setup.md
│   │   ├── datasheets/
│   │   │   ├── arduino-uno.md
│   │   │   ├── esp32.md
│   │   │   └── nucleo-f401re.md
│   │   ├── hardware-peripherals-design.md
│   │   └── nucleo-setup.md
│   ├── i18n/
│   │   ├── README.md
│   │   └── zh-CN/
│   │       ├── contributing/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── actions-source-policy.zh-CN.md
│   │       │   ├── adding-boards-and-tools.zh-CN.md
│   │       │   ├── cargo-slicer-speedup.zh-CN.md
│   │       │   ├── change-playbooks.zh-CN.md
│   │       │   ├── ci-map.zh-CN.md
│   │       │   ├── cla.zh-CN.md
│   │       │   ├── custom-providers.zh-CN.md
│   │       │   ├── doc-template.zh-CN.md
│   │       │   ├── docs-contract.zh-CN.md
│   │       │   ├── extension-examples.zh-CN.md
│   │       │   ├── langgraph-integration.zh-CN.md
│   │       │   ├── pr-discipline.zh-CN.md
│   │       │   ├── pr-workflow.zh-CN.md
│   │       │   ├── release-process.zh-CN.md
│   │       │   ├── reviewer-playbook.zh-CN.md
│   │       │   ├── testing-telegram.zh-CN.md
│   │       │   └── testing.zh-CN.md
│   │       ├── hardware/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── android-setup.zh-CN.md
│   │       │   ├── arduino-uno-q-setup.zh-CN.md
│   │       │   ├── datasheets/
│   │       │   │   ├── arduino-uno.zh-CN.md
│   │       │   │   ├── esp32.zh-CN.md
│   │       │   │   └── nucleo-f401re.zh-CN.md
│   │       │   ├── hardware-peripherals-design.zh-CN.md
│   │       │   └── nucleo-setup.zh-CN.md
│   │       ├── maintainers/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── docs-inventory.zh-CN.md
│   │       │   ├── i18n-coverage.zh-CN.md
│   │       │   ├── project-triage-snapshot-2026-02-18.zh-CN.md
│   │       │   ├── refactor-candidates.zh-CN.md
│   │       │   ├── repo-map.zh-CN.md
│   │       │   ├── structure-README.zh-CN.md
│   │       │   └── trademark.zh-CN.md
│   │       ├── ops/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── network-deployment.zh-CN.md
│   │       │   ├── operations-runbook.zh-CN.md
│   │       │   ├── proxy-agent-playbook.zh-CN.md
│   │       │   ├── resource-limits.zh-CN.md
│   │       │   └── troubleshooting.zh-CN.md
│   │       ├── reference/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── api/
│   │       │   │   ├── channels-reference.zh-CN.md
│   │       │   │   ├── config-reference.zh-CN.md
│   │       │   │   └── providers-reference.zh-CN.md
│   │       │   ├── cli/
│   │       │   │   └── commands-reference.zh-CN.md
│   │       │   └── sop/
│   │       │       ├── README.zh-CN.md
│   │       │       ├── connectivity.zh-CN.md
│   │       │       ├── cookbook.zh-CN.md
│   │       │       ├── observability.zh-CN.md
│   │       │       └── syntax.zh-CN.md
│   │       ├── security/
│   │       │   ├── README.zh-CN.md
│   │       │   ├── agnostic-security.zh-CN.md
│   │       │   ├── audit-logging.zh-CN.md
│   │       │   ├── frictionless-security.zh-CN.md
│   │       │   ├── matrix-e2ee-guide.zh-CN.md
│   │       │   ├── sandboxing.zh-CN.md
│   │       │   └── security-roadmap.zh-CN.md
│   │       └── setup-guides/
│   │           ├── README.zh-CN.md
│   │           ├── macos-update-uninstall.zh-CN.md
│   │           ├── mattermost-setup.zh-CN.md
│   │           ├── nextcloud-talk-setup.zh-CN.md
│   │           ├── one-click-bootstrap.zh-CN.md
│   │           └── zai-glm-setup.zh-CN.md
│   ├── maintainers/
│   │   ├── README.md
│   │   ├── docs-inventory.md
│   │   ├── i18n-coverage.md
│   │   ├── project-triage-snapshot-2026-02-18.md
│   │   ├── refactor-candidates.md
│   │   ├── repo-map.md
│   │   ├── structure-README.md
│   │   └── trademark.md
│   ├── openai-temperature-compatibility.md
│   ├── ops/
│   │   ├── README.md
│   │   ├── network-deployment.md
│   │   ├── operations-runbook.md
│   │   ├── proxy-agent-playbook.md
│   │   ├── resource-limits.md
│   │   ├── troubleshooting.md
│   │   └── troubleshooting.vi.md
│   ├── reference/
│   │   ├── README.md
│   │   ├── api/
│   │   │   ├── channels-reference.md
│   │   │   ├── config-reference.md
│   │   │   ├── config-reference.vi.md
│   │   │   └── providers-reference.md
│   │   ├── cli/
│   │   │   ├── commands-reference.md
│   │   │   └── commands-reference.vi.md
│   │   └── sop/
│   │       ├── README.md
│   │       ├── connectivity.md
│   │       ├── cookbook.md
│   │       ├── observability.md
│   │       └── syntax.md
│   ├── security/
│   │   ├── README.md
│   │   ├── agnostic-security.md
│   │   ├── audit-logging.md
│   │   ├── frictionless-security.md
│   │   ├── matrix-e2ee-guide.md
│   │   ├── sandboxing.md
│   │   └── security-roadmap.md
│   ├── setup-guides/
│   │   ├── README.md
│   │   ├── README.vi.md
│   │   ├── macos-update-uninstall.md
│   │   ├── mattermost-setup.md
│   │   ├── nextcloud-talk-setup.md
│   │   ├── one-click-bootstrap.md
│   │   ├── one-click-bootstrap.vi.md
│   │   └── zai-glm-setup.md
│   ├── superpowers/
│   │   └── specs/
│   │       └── 2026-03-13-linkedin-tool-design.md
│   └── vi/
│       ├── README.md
│       ├── actions-source-policy.md
│       ├── adding-boards-and-tools.md
│       ├── agnostic-security.md
│       ├── arduino-uno-q-setup.md
│       ├── audit-logging.md
│       ├── channels-reference.md
│       ├── ci-map.md
│       ├── commands-reference.md
│       ├── config-reference.md
│       ├── contributing/
│       │   └── README.md
│       ├── custom-providers.md
│       ├── datasheets/
│       │   ├── arduino-uno.md
│       │   ├── esp32.md
│       │   └── nucleo-f401re.md
│       ├── frictionless-security.md
│       ├── getting-started/
│       │   └── README.md
│       ├── hardware/
│       │   └── README.md
│       ├── hardware-peripherals-design.md
│       ├── langgraph-integration.md
│       ├── matrix-e2ee-guide.md
│       ├── mattermost-setup.md
│       ├── network-deployment.md
│       ├── nucleo-setup.md
│       ├── one-click-bootstrap.md
│       ├── operations/
│       │   └── README.md
│       ├── operations-runbook.md
│       ├── pr-workflow.md
│       ├── project/
│       │   └── README.md
│       ├── providers-reference.md
│       ├── proxy-agent-playbook.md
│       ├── reference/
│       │   └── README.md
│       ├── release-process.md
│       ├── resource-limits.md
│       ├── reviewer-playbook.md
│       ├── sandboxing.md
│       ├── security/
│       │   └── README.md
│       ├── security-roadmap.md
│       ├── troubleshooting.md
│       └── zai-glm-setup.md
├── example-plugin/
│   ├── Cargo.toml
│   ├── manifest.toml
│   └── src/
│       └── lib.rs
├── examples/
│   └── config.example.toml
├── firmware/
│   ├── arduino/
│   │   └── arduino.ino
│   ├── esp32/
│   │   ├── .cargo/
│   │   │   └── config.toml
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── SETUP.md
│   │   ├── build.rs
│   │   ├── rust-toolchain.toml
│   │   └── src/
│   │       └── main.rs
│   ├── esp32-ui/
│   │   ├── .cargo/
│   │   │   └── config.toml
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   ├── build.rs
│   │   ├── src/
│   │   │   └── main.rs
│   │   └── ui/
│   │       └── main.slint
│   ├── nucleo/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   └── uno-q-bridge/
│       ├── app.yaml
│       ├── python/
│       │   ├── main.py
│       │   └── requirements.txt
│       └── sketch/
│           ├── sketch.ino
│           └── sketch.yaml
├── flake.nix
├── fuzz/
│   ├── Cargo.toml
│   └── fuzz_targets/
│       ├── fuzz_command_validation.rs
│       ├── fuzz_config_parse.rs
│       ├── fuzz_provider_response.rs
│       ├── fuzz_tool_params.rs
│       └── fuzz_webhook_payload.rs
├── install.sh
├── python/
│   ├── README.md
│   ├── README.vi.md
│   ├── pyproject.toml
│   ├── tests/
│   │   ├── __init__.py
│   │   └── test_tools.py
│   └── zeroclaw_tools/
│       ├── __init__.py
│       ├── __main__.py
│       ├── agent.py
│       ├── integrations/
│       │   ├── __init__.py
│       │   └── discord_bot.py
│       └── tools/
│           ├── __init__.py
│           ├── base.py
│           ├── file.py
│           ├── memory.py
│           ├── shell.py
│           └── web.py
├── rustfmt.toml
├── scripts/
│   ├── ci/
│   │   ├── check_binary_size.sh
│   │   ├── collect_changed_links.py
│   │   ├── detect_change_scope.sh
│   │   ├── docs_links_gate.sh
│   │   ├── docs_quality_gate.sh
│   │   ├── fetch_actions_data.py
│   │   ├── rust_quality_gate.sh
│   │   └── rust_strict_delta_gate.sh
│   └── release/
│       └── cut_release_tag.sh
├── src/
│   ├── agent/
│   │   ├── agent.rs
│   │   ├── classifier.rs
│   │   ├── dispatcher.rs
│   │   ├── loop_.rs
│   │   ├── memory_loader.rs
│   │   ├── mod.rs
│   │   ├── prompt.rs
│   │   └── tests.rs
│   ├── approval/
│   │   └── mod.rs
│   ├── auth/
│   │   ├── anthropic_token.rs
│   │   ├── gemini_oauth.rs
│   │   ├── mod.rs
│   │   ├── oauth_common.rs
│   │   ├── openai_oauth.rs
│   │   └── profiles.rs
│   ├── channels/
│   │   ├── bluesky.rs
│   │   ├── clawdtalk.rs
│   │   ├── cli.rs
│   │   ├── dingtalk.rs
│   │   ├── discord.rs
│   │   ├── email_channel.rs
│   │   ├── imessage.rs
│   │   ├── irc.rs
│   │   ├── lark.rs
│   │   ├── linq.rs
│   │   ├── matrix.rs
│   │   ├── mattermost.rs
│   │   ├── mochat.rs
│   │   ├── mod.rs
│   │   ├── mqtt.rs
│   │   ├── nextcloud_talk.rs
│   │   ├── nostr.rs
│   │   ├── notion.rs
│   │   ├── qq.rs
│   │   ├── reddit.rs
│   │   ├── session_backend.rs
│   │   ├── session_sqlite.rs
│   │   ├── session_store.rs
│   │   ├── signal.rs
│   │   ├── slack.rs
│   │   ├── telegram.rs
│   │   ├── traits.rs
│   │   ├── transcription.rs
│   │   ├── tts.rs
│   │   ├── twitter.rs
│   │   ├── wati.rs
│   │   ├── webhook.rs
│   │   ├── wecom.rs
│   │   ├── whatsapp.rs
│   │   ├── whatsapp_storage.rs
│   │   └── whatsapp_web.rs
│   ├── commands/
│   │   ├── mod.rs
│   │   ├── self_test.rs
│   │   └── update.rs
│   ├── config/
│   │   ├── mod.rs
│   │   ├── schema.rs
│   │   ├── traits.rs
│   │   └── workspace.rs
│   ├── cost/
│   │   ├── mod.rs
│   │   ├── tracker.rs
│   │   └── types.rs
│   ├── cron/
│   │   ├── mod.rs
│   │   ├── schedule.rs
│   │   ├── scheduler.rs
│   │   ├── store.rs
│   │   └── types.rs
│   ├── daemon/
│   │   └── mod.rs
│   ├── doctor/
│   │   └── mod.rs
│   ├── gateway/
│   │   ├── api.rs
│   │   ├── api_pairing.rs
│   │   ├── api_plugins.rs
│   │   ├── mod.rs
│   │   ├── nodes.rs
│   │   ├── sse.rs
│   │   ├── static_files.rs
│   │   └── ws.rs
│   ├── hands/
│   │   ├── mod.rs
│   │   └── types.rs
│   ├── hardware/
│   │   ├── discover.rs
│   │   ├── introspect.rs
│   │   ├── mod.rs
│   │   └── registry.rs
│   ├── health/
│   │   └── mod.rs
│   ├── heartbeat/
│   │   ├── engine.rs
│   │   ├── mod.rs
│   │   └── store.rs
│   ├── hooks/
│   │   ├── builtin/
│   │   │   ├── command_logger.rs
│   │   │   ├── mod.rs
│   │   │   └── webhook_audit.rs
│   │   ├── mod.rs
│   │   ├── runner.rs
│   │   └── traits.rs
│   ├── i18n.rs
│   ├── identity.rs
│   ├── integrations/
│   │   ├── mod.rs
│   │   └── registry.rs
│   ├── lib.rs
│   ├── main.rs
│   ├── memory/
│   │   ├── backend.rs
│   │   ├── chunker.rs
│   │   ├── cli.rs
│   │   ├── consolidation.rs
│   │   ├── embeddings.rs
│   │   ├── hygiene.rs
│   │   ├── knowledge_graph.rs
│   │   ├── lucid.rs
│   │   ├── markdown.rs
│   │   ├── mod.rs
│   │   ├── none.rs
│   │   ├── postgres.rs
│   │   ├── qdrant.rs
│   │   ├── response_cache.rs
│   │   ├── snapshot.rs
│   │   ├── sqlite.rs
│   │   ├── traits.rs
│   │   └── vector.rs
│   ├── migration.rs
│   ├── multimodal.rs
│   ├── nodes/
│   │   ├── mod.rs
│   │   └── transport.rs
│   ├── observability/
│   │   ├── log.rs
│   │   ├── mod.rs
│   │   ├── multi.rs
│   │   ├── noop.rs
│   │   ├── otel.rs
│   │   ├── prometheus.rs
│   │   ├── runtime_trace.rs
│   │   ├── traits.rs
│   │   └── verbose.rs
│   ├── onboard/
│   │   ├── mod.rs
│   │   └── wizard.rs
│   ├── peripherals/
│   │   ├── arduino_flash.rs
│   │   ├── arduino_upload.rs
│   │   ├── capabilities_tool.rs
│   │   ├── mod.rs
│   │   ├── nucleo_flash.rs
│   │   ├── rpi.rs
│   │   ├── serial.rs
│   │   ├── traits.rs
│   │   ├── uno_q_bridge.rs
│   │   └── uno_q_setup.rs
│   ├── plugins/
│   │   ├── error.rs
│   │   ├── host.rs
│   │   ├── mod.rs
│   │   ├── wasm_channel.rs
│   │   └── wasm_tool.rs
│   ├── providers/
│   │   ├── anthropic.rs
│   │   ├── azure_openai.rs
│   │   ├── bedrock.rs
│   │   ├── claude_code.rs
│   │   ├── compatible.rs
│   │   ├── copilot.rs
│   │   ├── gemini.rs
│   │   ├── gemini_cli.rs
│   │   ├── glm.rs
│   │   ├── kilocli.rs
│   │   ├── mod.rs
│   │   ├── ollama.rs
│   │   ├── openai.rs
│   │   ├── openai_codex.rs
│   │   ├── openrouter.rs
│   │   ├── reliable.rs
│   │   ├── router.rs
│   │   ├── telnyx.rs
│   │   └── traits.rs
│   ├── rag/
│   │   └── mod.rs
│   ├── runtime/
│   │   ├── docker.rs
│   │   ├── mod.rs
│   │   ├── native.rs
│   │   ├── traits.rs
│   │   └── wasm.rs
│   ├── security/
│   │   ├── audit.rs
│   │   ├── bubblewrap.rs
│   │   ├── detect.rs
│   │   ├── docker.rs
│   │   ├── domain_matcher.rs
│   │   ├── estop.rs
│   │   ├── firejail.rs
│   │   ├── iam_policy.rs
│   │   ├── landlock.rs
│   │   ├── leak_detector.rs
│   │   ├── mod.rs
│   │   ├── nevis.rs
│   │   ├── otp.rs
│   │   ├── pairing.rs
│   │   ├── playbook.rs
│   │   ├── policy.rs
│   │   ├── prompt_guard.rs
│   │   ├── secrets.rs
│   │   ├── traits.rs
│   │   ├── vulnerability.rs
│   │   └── workspace_boundary.rs
│   ├── service/
│   │   └── mod.rs
│   ├── skillforge/
│   │   ├── evaluate.rs
│   │   ├── integrate.rs
│   │   ├── mod.rs
│   │   └── scout.rs
│   ├── skills/
│   │   ├── audit.rs
│   │   ├── creator.rs
│   │   ├── mod.rs
│   │   └── symlink_tests.rs
│   ├── sop/
│   │   ├── audit.rs
│   │   ├── condition.rs
│   │   ├── dispatch.rs
│   │   ├── engine.rs
│   │   ├── gates.rs
│   │   ├── metrics.rs
│   │   ├── mod.rs
│   │   └── types.rs
│   ├── tools/
│   │   ├── backup_tool.rs
│   │   ├── browser.rs
│   │   ├── browser_delegate.rs
│   │   ├── browser_open.rs
│   │   ├── calculator.rs
│   │   ├── cli_discovery.rs
│   │   ├── cloud_ops.rs
│   │   ├── cloud_patterns.rs
│   │   ├── composio.rs
│   │   ├── content_search.rs
│   │   ├── cron_add.rs
│   │   ├── cron_list.rs
│   │   ├── cron_remove.rs
│   │   ├── cron_run.rs
│   │   ├── cron_runs.rs
│   │   ├── cron_update.rs
│   │   ├── data_management.rs
│   │   ├── delegate.rs
│   │   ├── file_edit.rs
│   │   ├── file_read.rs
│   │   ├── file_write.rs
│   │   ├── git_operations.rs
│   │   ├── glob_search.rs
│   │   ├── google_workspace.rs
│   │   ├── hardware_board_info.rs
│   │   ├── hardware_memory_map.rs
│   │   ├── hardware_memory_read.rs
│   │   ├── http_request.rs
│   │   ├── image_info.rs
│   │   ├── jira_tool.rs
│   │   ├── knowledge_tool.rs
│   │   ├── linkedin.rs
│   │   ├── linkedin_client.rs
│   │   ├── mcp_client.rs
│   │   ├── mcp_deferred.rs
│   │   ├── mcp_protocol.rs
│   │   ├── mcp_tool.rs
│   │   ├── mcp_transport.rs
│   │   ├── memory_forget.rs
│   │   ├── memory_recall.rs
│   │   ├── memory_store.rs
│   │   ├── microsoft365/
│   │   │   ├── auth.rs
│   │   │   ├── graph_client.rs
│   │   │   ├── mod.rs
│   │   │   └── types.rs
│   │   ├── mod.rs
│   │   ├── model_routing_config.rs
│   │   ├── model_switch.rs
│   │   ├── node_tool.rs
│   │   ├── notion_tool.rs
│   │   ├── pdf_read.rs
│   │   ├── project_intel.rs
│   │   ├── proxy_config.rs
│   │   ├── pushover.rs
│   │   ├── read_skill.rs
│   │   ├── report_templates.rs
│   │   ├── schedule.rs
│   │   ├── schema.rs
│   │   ├── screenshot.rs
│   │   ├── security_ops.rs
│   │   ├── shell.rs
│   │   ├── sop_advance.rs
│   │   ├── sop_approve.rs
│   │   ├── sop_execute.rs
│   │   ├── sop_list.rs
│   │   ├── sop_status.rs
│   │   ├── swarm.rs
│   │   ├── text_browser.rs
│   │   ├── tool_search.rs
│   │   ├── traits.rs
│   │   ├── web_fetch.rs
│   │   ├── web_search_tool.rs
│   │   └── workspace_tool.rs
│   ├── tunnel/
│   │   ├── cloudflare.rs
│   │   ├── custom.rs
│   │   ├── mod.rs
│   │   ├── ngrok.rs
│   │   ├── none.rs
│   │   ├── openvpn.rs
│   │   └── tailscale.rs
│   └── util.rs
├── taplo.toml
├── tests/
│   ├── component/
│   │   ├── config_persistence.rs
│   │   ├── config_schema.rs
│   │   ├── dockerignore_test.rs
│   │   ├── gateway.rs
│   │   ├── mod.rs
│   │   ├── otel_dependency_feature_regression.rs
│   │   ├── provider_resolution.rs
│   │   ├── provider_schema.rs
│   │   ├── reply_target_field_regression.rs
│   │   ├── security.rs
│   │   └── whatsapp_webhook_security.rs
│   ├── fixtures/
│   │   └── traces/
│   │       ├── multi_tool_chain.json
│   │       ├── single_tool_echo.json
│   │       └── smoke_greeting.json
│   ├── integration/
│   │   ├── agent.rs
│   │   ├── agent_robustness.rs
│   │   ├── channel_matrix.rs
│   │   ├── channel_routing.rs
│   │   ├── hooks.rs
│   │   ├── memory_comparison.rs
│   │   ├── memory_restart.rs
│   │   ├── mod.rs
│   │   ├── telegram_attachment_fallback.rs
│   │   └── telegram_finalize_draft.rs
│   ├── live/
│   │   ├── gemini_fallback_oauth_refresh.rs
│   │   ├── mod.rs
│   │   ├── openai_codex_vision_e2e.rs
│   │   └── providers.rs
│   ├── manual/
│   │   ├── telegram/
│   │   │   ├── generate_test_messages.py
│   │   │   ├── quick_test.sh
│   │   │   ├── test_telegram_integration.sh
│   │   │   └── testing-telegram.md
│   │   └── test_dockerignore.sh
│   ├── support/
│   │   ├── assertions.rs
│   │   ├── helpers.rs
│   │   ├── mock_channel.rs
│   │   ├── mock_provider.rs
│   │   ├── mock_tools.rs
│   │   ├── mod.rs
│   │   └── trace.rs
│   ├── system/
│   │   ├── full_stack.rs
│   │   └── mod.rs
│   ├── test_component.rs
│   ├── test_integration.rs
│   ├── test_live.rs
│   └── test_system.rs
├── tool_descriptions/
│   ├── en.toml
│   └── zh-CN.toml
└── web/
    ├── .gitignore
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   └── layout/
    │   │       ├── Header.tsx
    │   │       ├── Layout.tsx
    │   │       └── Sidebar.tsx
    │   ├── hooks/
    │   │   ├── useApi.ts
    │   │   ├── useAuth.ts
    │   │   ├── useDevices.ts
    │   │   ├── useDraft.ts
    │   │   ├── useSSE.ts
    │   │   └── useWebSocket.ts
    │   ├── index.css
    │   ├── lib/
    │   │   ├── api.ts
    │   │   ├── auth.ts
    │   │   ├── i18n.ts
    │   │   ├── sse.ts
    │   │   ├── uuid.ts
    │   │   └── ws.ts
    │   ├── main.tsx
    │   ├── pages/
    │   │   ├── AgentChat.tsx
    │   │   ├── Config.tsx
    │   │   ├── Cost.tsx
    │   │   ├── Cron.tsx
    │   │   ├── Dashboard.tsx
    │   │   ├── Doctor.tsx
    │   │   ├── Integrations.tsx
    │   │   ├── Logs.tsx
    │   │   ├── Memory.tsx
    │   │   ├── Pairing.tsx
    │   │   └── Tools.tsx
    │   ├── types/
    │   │   └── api.ts
    │   └── vite-env.d.ts
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts
Download .txt
Showing preview only (1,128K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (11958 symbols across 382 files)

FILE: .claude/skills/skill-creator/eval-viewer/generate_review.py
  function get_mime_type (line 52) | def get_mime_type(path: Path) -> str:
  function find_runs (line 60) | def find_runs(workspace: Path) -> list[dict]:
  function _find_runs_recursive (line 68) | def _find_runs_recursive(root: Path, current: Path, runs: list[dict]) ->...
  function build_run (line 85) | def build_run(root: Path, run_dir: Path) -> dict | None:
  function embed_file (line 149) | def embed_file(path: Path) -> dict:
  function load_previous_iteration (line 213) | def load_previous_iteration(workspace: Path) -> dict[str, dict]:
  function generate_html (line 250) | def generate_html(
  function _kill_port (line 288) | def _kill_port(port: int) -> None:
  class ReviewHandler (line 308) | class ReviewHandler(BaseHTTPRequestHandler):
    method __init__ (line 315) | def __init__(
    method do_GET (line 332) | def do_GET(self) -> None:
    method do_POST (line 361) | def do_POST(self) -> None:
    method log_message (line 382) | def log_message(self, format: str, *args: object) -> None:
  function main (line 387) | def main() -> None:

FILE: .claude/skills/skill-creator/scripts/aggregate_benchmark.py
  function calculate_stats (line 45) | def calculate_stats(values: list[float]) -> dict:
  function load_run_results (line 67) | def load_run_results(benchmark_dir: Path) -> dict:
  function aggregate_results (line 176) | def aggregate_results(results: dict) -> dict:
  function generate_benchmark (line 227) | def generate_benchmark(benchmark_dir: Path, skill_name: str = "", skill_...
  function generate_markdown (line 281) | def generate_markdown(benchmark: dict) -> str:
  function main (line 338) | def main():

FILE: .claude/skills/skill-creator/scripts/generate_report.py
  function generate_html (line 16) | def generate_html(data: dict, auto_refresh: bool = False, skill_name: st...
  function main (line 304) | def main():

FILE: .claude/skills/skill-creator/scripts/improve_description.py
  function _call_claude (line 20) | def _call_claude(prompt: str, model: str | None, timeout: int = 300) -> ...
  function improve_description (line 50) | def improve_description(
  function main (line 194) | def main():

FILE: .claude/skills/skill-creator/scripts/package_skill.py
  function should_exclude (line 27) | def should_exclude(rel_path: Path) -> bool:
  function package_skill (line 42) | def package_skill(skill_path, output_dir=None):
  function main (line 111) | def main():

FILE: .claude/skills/skill-creator/scripts/quick_validate.py
  function validate_skill (line 12) | def validate_skill(skill_path):

FILE: .claude/skills/skill-creator/scripts/run_eval.py
  function find_project_root (line 22) | def find_project_root() -> Path:
  function run_single_query (line 35) | def run_single_query(
  function run_eval (line 184) | def run_eval(
  function main (line 259) | def main():

FILE: .claude/skills/skill-creator/scripts/run_loop.py
  function split_eval_set (line 24) | def split_eval_set(eval_set: list[dict], holdout: float, seed: int = 42)...
  function run_loop (line 47) | def run_loop(
  function main (line 244) | def main():

FILE: .claude/skills/skill-creator/scripts/utils.py
  function parse_skill_md (line 7) | def parse_skill_md(skill_path: Path) -> tuple[str, str, str]:

FILE: benches/agent_benchmarks.rs
  type BenchProvider (line 32) | struct BenchProvider {
    method text_only (line 37) | fn text_only(text: &str) -> Self {
    method with_tool_then_text (line 48) | fn with_tool_then_text() -> Self {
  method chat_with_system (line 74) | async fn chat_with_system(
  method chat (line 84) | async fn chat(
  type NoopTool (line 103) | struct NoopTool;
  method name (line 107) | fn name(&self) -> &str {
  method description (line 110) | fn description(&self) -> &str {
  method parameters_schema (line 113) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 116) | async fn execute(&self, _args: serde_json::Value) -> Result<ToolResult> {
  function make_memory (line 125) | fn make_memory() -> Arc<dyn Memory> {
  function make_sqlite_memory (line 133) | fn make_sqlite_memory(dir: &std::path::Path) -> Arc<dyn Memory> {
  function make_observer (line 141) | fn make_observer() -> Arc<dyn Observer> {
  function bench_xml_parsing (line 149) | fn bench_xml_parsing(c: &mut Criterion) {
  function bench_native_parsing (line 197) | fn bench_native_parsing(c: &mut Criterion) {
  function bench_memory_operations (line 227) | fn bench_memory_operations(c: &mut Criterion) {
  function bench_agent_turn (line 282) | fn bench_agent_turn(c: &mut Criterion) {

FILE: build.rs
  function main (line 6) | fn main() {
  function web_build_required (line 90) | fn web_build_required(web_dir: &Path, dist_dir: &Path) -> bool {
  function latest_modified (line 111) | fn latest_modified(path: &Path) -> Option<SystemTime> {
  function ensure_dist_dir (line 135) | fn ensure_dist_dir(dist_dir: &Path) {
  function ensure_dashboard_assets (line 141) | fn ensure_dashboard_assets(dist_dir: &Path) {
  function which_npm (line 160) | fn which_npm() -> Result<String, ()> {

FILE: crates/robot-kit/src/config.rs
  type RobotConfig (line 8) | pub struct RobotConfig {
    method load (line 206) | pub fn load(path: &std::path::Path) -> anyhow::Result<Self> {
    method save (line 212) | pub fn save(&self, path: &std::path::Path) -> anyhow::Result<()> {
  type DriveConfig (line 26) | pub struct DriveConfig {
  type CameraConfig (line 44) | pub struct CameraConfig {
  type AudioConfig (line 60) | pub struct AudioConfig {
  type SensorConfig (line 81) | pub struct SensorConfig {
  type SafetyConfig (line 96) | pub struct SafetyConfig {
  method default (line 157) | fn default() -> Self {

FILE: crates/robot-kit/src/drive.rs
  type DriveBackend (line 20) | trait DriveBackend: Send + Sync {
    method move_robot (line 21) | async fn move_robot(
    method stop (line 28) | async fn stop(&self) -> Result<()>;
    method get_odometry (line 30) | async fn get_odometry(&self) -> Result<(f64, f64, f64)>;
    method move_robot (line 38) | async fn move_robot(
    method stop (line 56) | async fn stop(&self) -> Result<()> {
    method get_odometry (line 61) | async fn get_odometry(&self) -> Result<(f64, f64, f64)> {
    method move_robot (line 73) | async fn move_robot(
    method stop (line 113) | async fn stop(&self) -> Result<()> {
    method get_odometry (line 129) | async fn get_odometry(&self) -> Result<(f64, f64, f64)> {
    method move_robot (line 142) | async fn move_robot(
    method stop (line 171) | async fn stop(&self) -> Result<()> {
    method get_odometry (line 175) | async fn get_odometry(&self) -> Result<(f64, f64, f64)> {
  type MockDrive (line 34) | struct MockDrive;
  type Ros2Drive (line 67) | struct Ros2Drive {
  type SerialDrive (line 136) | struct SerialDrive {
  type DriveTool (line 181) | pub struct DriveTool {
    method new (line 188) | pub fn new(config: RobotConfig) -> Self {
  method name (line 210) | fn name(&self) -> &str {
  method description (line 214) | fn description(&self) -> &str {
  method parameters_schema (line 219) | fn parameters_schema(&self) -> Value {
  method execute (line 253) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function drive_tool_name (line 391) | fn drive_tool_name() {
  function drive_tool_schema_has_action (line 397) | fn drive_tool_schema_has_action() {
  function drive_forward_mock (line 404) | async fn drive_forward_mock() {
  function drive_stop (line 415) | async fn drive_stop() {
  function drive_unknown_action (line 423) | async fn drive_unknown_action() {

FILE: crates/robot-kit/src/emote.rs
  type Expression (line 15) | pub enum Expression {
    method from_str (line 29) | fn from_str(s: &str) -> Option<Self> {
    method pattern (line 47) | fn pattern(&self) -> Vec<(u8, u8, u8)> {
  type EmoteTool (line 117) | pub struct EmoteTool {
    method new (line 124) | pub fn new(config: RobotConfig) -> Self {
    method set_expression (line 133) | async fn set_expression(&self, expr: Expression) -> Result<()> {
    method play_emotion_sound (line 164) | async fn play_emotion_sound(&self, emotion: &str) -> Result<()> {
    method animate (line 181) | async fn animate(&self, animation: &str) -> Result<()> {
  method name (line 217) | fn name(&self) -> &str {
  method description (line 221) | fn description(&self) -> &str {
  method parameters_schema (line 227) | fn parameters_schema(&self) -> Value {
  method execute (line 254) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function emote_tool_name (line 296) | fn emote_tool_name() {
  function expression_parsing (line 302) | fn expression_parsing() {
  function expression_pattern_size (line 309) | fn expression_pattern_size() {
  function emote_happy (line 315) | async fn emote_happy() {

FILE: crates/robot-kit/src/lib.rs
  constant VERSION (line 121) | pub const VERSION: &str = env!("CARGO_PKG_VERSION");
  function create_tools (line 126) | pub fn create_tools(config: &RobotConfig) -> Vec<Box<dyn Tool>> {
  function create_safe_tools (line 139) | pub fn create_safe_tools(

FILE: crates/robot-kit/src/listen.rs
  type ListenTool (line 13) | pub struct ListenTool {
    method new (line 19) | pub fn new(config: RobotConfig) -> Self {
    method record_audio (line 33) | async fn record_audio(&self, duration_secs: u64) -> Result<PathBuf> {
    method transcribe (line 70) | async fn transcribe(&self, audio_path: &Path) -> Result<String> {
  method name (line 119) | fn name(&self) -> &str {
  method description (line 123) | fn description(&self) -> &str {
  method parameters_schema (line 128) | fn parameters_schema(&self) -> Value {
  method execute (line 146) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function listen_tool_name (line 197) | fn listen_tool_name() {
  function listen_tool_schema (line 203) | fn listen_tool_schema() {

FILE: crates/robot-kit/src/look.rs
  type LookTool (line 13) | pub struct LookTool {
    method new (line 19) | pub fn new(config: RobotConfig) -> Self {
    method capture_image (line 34) | async fn capture_image(&self) -> Result<PathBuf> {
    method describe_image (line 88) | async fn describe_image(&self, image_path: &PathBuf, prompt: &str) -> ...
  method name (line 129) | fn name(&self) -> &str {
  method description (line 133) | fn description(&self) -> &str {
  method parameters_schema (line 138) | fn parameters_schema(&self) -> Value {
  method execute (line 156) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function look_tool_name (line 239) | fn look_tool_name() {
  function look_tool_schema (line 245) | fn look_tool_schema() {

FILE: crates/robot-kit/src/safety.rs
  type SafetyEvent (line 30) | pub enum SafetyEvent {
  type SafetyState (line 48) | pub struct SafetyState {
  method default (line 64) | fn default() -> Self {
  type SafetyMonitor (line 77) | pub struct SafetyMonitor {
    method new (line 85) | pub fn new(config: SafetyConfig) -> (Self, broadcast::Receiver<SafetyE...
    method state (line 96) | pub fn state(&self) -> Arc<SafetyState> {
    method subscribe (line 100) | pub fn subscribe(&self) -> broadcast::Receiver<SafetyEvent> {
    method can_move (line 105) | pub async fn can_move(&self) -> bool {
    method speed_limit (line 113) | pub async fn speed_limit(&self) -> f64 {
    method request_movement (line 118) | pub async fn request_movement(&self, direction: &str, distance: f64) -...
    method calculate_speed_limit (line 178) | async fn calculate_speed_limit(&self, obstacle_distance: f64) -> f64 {
    method emergency_stop (line 196) | pub async fn emergency_stop(&self, reason: &str) {
    method reset_estop (line 208) | pub async fn reset_estop(&self) {
    method update_obstacle_distance (line 218) | pub async fn update_obstacle_distance(&self, distance: f64, angle: u16) {
    method bump_detected (line 246) | pub async fn bump_detected(&self, sensor: &str) {
    method shutdown (line 273) | pub fn shutdown(&self) {
    method run (line 278) | pub async fn run(&self, mut sensor_rx: tokio::sync::mpsc::Receiver<Sen...
  type SensorReading (line 335) | pub enum SensorReading {
  type SafeDrive (line 343) | pub struct SafeDrive {
    method new (line 349) | pub fn new(drive: Arc<dyn crate::traits::Tool>, safety: Arc<SafetyMoni...
    method name (line 359) | fn name(&self) -> &str {
    method description (line 363) | fn description(&self) -> &str {
    method parameters_schema (line 367) | fn parameters_schema(&self) -> serde_json::Value {
    method execute (line 371) | async fn execute(&self, args: serde_json::Value) -> Result<ToolResult> {
  function preflight_check (line 409) | pub async fn preflight_check(config: &RobotConfig) -> Result<Vec<String>> {
  function safety_state_defaults (line 445) | async fn safety_state_defaults() {
  function safety_monitor_blocks_on_obstacle (line 452) | async fn safety_monitor_blocks_on_obstacle() {
  function safety_monitor_estop (line 468) | async fn safety_monitor_estop() {
  function speed_limit_calculation (line 483) | async fn speed_limit_calculation() {
  function request_movement_blocked (line 505) | async fn request_movement_blocked() {
  method default (line 521) | fn default() -> Self {

FILE: crates/robot-kit/src/sense.rs
  type LidarScan (line 16) | pub struct LidarScan {
  type MotionResult (line 27) | pub struct MotionResult {
  type SenseTool (line 32) | pub struct SenseTool {
    method new (line 38) | pub fn new(config: RobotConfig) -> Self {
    method scan_lidar (line 46) | async fn scan_lidar(&self) -> Result<LidarScan> {
    method scan_mock (line 55) | async fn scan_mock(&self) -> Result<LidarScan> {
    method scan_rplidar (line 92) | async fn scan_rplidar(&self) -> Result<LidarScan> {
    method scan_ros2 (line 143) | async fn scan_ros2(&self) -> Result<LidarScan> {
    method check_motion (line 183) | async fn check_motion(&self) -> Result<MotionResult> {
    method check_distance (line 207) | async fn check_distance(&self) -> Result<f64> {
  method name (line 239) | fn name(&self) -> &str {
  method description (line 243) | fn description(&self) -> &str {
  method parameters_schema (line 249) | fn parameters_schema(&self) -> Value {
  method execute (line 268) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function sense_tool_name (line 432) | fn sense_tool_name() {
  function sense_scan_mock (line 438) | async fn sense_scan_mock() {
  function sense_clear_ahead (line 449) | async fn sense_clear_ahead() {

FILE: crates/robot-kit/src/speak.rs
  type SpeakTool (line 13) | pub struct SpeakTool {
    method new (line 19) | pub fn new(config: RobotConfig) -> Self {
    method speak (line 30) | async fn speak(&self, text: &str, emotion: &str) -> Result<()> {
    method play_sound (line 101) | async fn play_sound(&self, sound: &str) -> Result<()> {
  method name (line 128) | fn name(&self) -> &str {
  method description (line 132) | fn description(&self) -> &str {
  method parameters_schema (line 137) | fn parameters_schema(&self) -> Value {
  method execute (line 158) | async fn execute(&self, args: Value) -> Result<ToolResult> {
  function speak_tool_name (line 219) | fn speak_tool_name() {
  function speak_tool_schema (line 225) | fn speak_tool_schema() {

FILE: crates/robot-kit/src/tests.rs
  function all_tools_have_valid_names (line 20) | fn all_tools_have_valid_names() {
  function all_tools_have_descriptions (line 42) | fn all_tools_have_descriptions() {
  function all_tools_have_valid_schemas (line 63) | fn all_tools_have_valid_schemas() {
  function drive_forward_mock (line 94) | async fn drive_forward_mock() {
  function drive_stop_always_succeeds (line 108) | async fn drive_stop_always_succeeds() {
  function drive_strafe_left (line 119) | async fn drive_strafe_left() {
  function drive_rotate (line 132) | async fn drive_rotate() {
  function drive_invalid_action_fails (line 145) | async fn drive_invalid_action_fails() {
  function drive_missing_action_fails (line 156) | async fn drive_missing_action_fails() {
  function drive_speed_clamped (line 166) | async fn drive_speed_clamped() {
  function sense_scan_returns_distances (line 184) | async fn sense_scan_returns_distances() {
  function sense_clear_ahead_check (line 200) | async fn sense_clear_ahead_check() {
  function sense_motion_detection (line 215) | async fn sense_motion_detection() {
  function emote_happy (line 229) | async fn emote_happy() {
  function emote_all_expressions_valid (line 242) | async fn emote_all_expressions_valid() {
  function emote_invalid_expression_fails (line 270) | async fn emote_invalid_expression_fails() {
  function config_default_is_safe (line 284) | fn config_default_is_safe() {
  function config_serializes_to_toml (line 295) | fn config_serializes_to_toml() {
  function config_roundtrips (line 303) | fn config_roundtrips() {
  function test_safety_config (line 323) | fn test_safety_config() -> SafetyConfig {
  function safety_initially_allows_movement (line 340) | async fn safety_initially_allows_movement() {
  function safety_blocks_on_close_obstacle (line 348) | async fn safety_blocks_on_close_obstacle() {
  function safety_allows_after_obstacle_clears (line 359) | async fn safety_allows_after_obstacle_clears() {
  function safety_estop_blocks_everything (line 373) | async fn safety_estop_blocks_everything() {
  function safety_estop_reset (line 388) | async fn safety_estop_reset() {
  function safety_speed_limit_far (line 400) | async fn safety_speed_limit_far() {
  function safety_speed_limit_approaching (line 412) | async fn safety_speed_limit_approaching() {
  function safety_movement_request_approved (line 425) | async fn safety_movement_request_approved() {
  function safety_movement_request_denied_close (line 437) | async fn safety_movement_request_denied_close() {
  function safety_bump_triggers_stop (line 449) | async fn safety_bump_triggers_stop() {
  function drive_then_sense_workflow (line 470) | async fn drive_then_sense_workflow() {
  function create_tools_returns_all_tools (line 500) | async fn create_tools_returns_all_tools() {
  function safe_drive_blocks_on_obstacle (line 517) | async fn safe_drive_blocks_on_obstacle() {

FILE: crates/robot-kit/src/traits.rs
  type ToolResult (line 12) | pub struct ToolResult {
    method success (line 23) | pub fn success(output: impl Into<String>) -> Self {
    method error (line 32) | pub fn error(error: impl Into<String>) -> Self {
    method partial (line 41) | pub fn partial(output: impl Into<String>, error: impl Into<String>) ->...
  type ToolSpec (line 52) | pub struct ToolSpec {
  type Tool (line 98) | pub trait Tool: Send + Sync {
    method name (line 100) | fn name(&self) -> &str;
    method description (line 103) | fn description(&self) -> &str;
    method parameters_schema (line 108) | fn parameters_schema(&self) -> Value;
    method execute (line 113) | async fn execute(&self, args: Value) -> anyhow::Result<ToolResult>;
    method spec (line 116) | fn spec(&self) -> ToolSpec {

FILE: example-plugin/src/lib.rs
  type WeatherInput (line 10) | struct WeatherInput {
  type WeatherOutput (line 15) | struct WeatherOutput {
  function get_weather (line 25) | pub fn get_weather(input: String) -> FnResult<String> {

FILE: firmware/esp32-ui/build.rs
  function main (line 3) | fn main() {

FILE: firmware/esp32-ui/src/main.rs
  function main (line 12) | fn main() -> anyhow::Result<()> {

FILE: firmware/esp32/build.rs
  function main (line 1) | fn main() {

FILE: firmware/esp32/src/main.rs
  type Request (line 17) | struct Request {
  type Response (line 25) | struct Response {
  function main (line 33) | fn main() -> anyhow::Result<()> {
  function handle_request (line 89) | fn handle_request<G2, G13>(
  function gpio_read (line 140) | fn gpio_read(_pin: i32) -> anyhow::Result<u8> {
  function gpio_write (line 145) | fn gpio_write<G2, G13>(

FILE: firmware/nucleo/src/main.rs
  constant LED_PIN (line 21) | const LED_PIN: u8 = 13;
  function parse_arg (line 24) | fn parse_arg(line: &[u8], key: &[u8]) -> Option<i32> {
  function has_cmd (line 65) | fn has_cmd(line: &[u8], cmd: &[u8]) -> bool {
  function copy_id (line 86) | fn copy_id(line: &[u8], out: &mut [u8]) -> usize {
  function main (line 108) | async fn main(_spawner: Spawner) {

FILE: firmware/uno-q-bridge/python/main.py
  function handle_client (line 10) | def handle_client(conn):
  function accept_loop (line 41) | def accept_loop(server):
  function loop (line 51) | def loop():
  function main (line 54) | def main():

FILE: python/tests/test_tools.py
  function test_import_main (line 8) | def test_import_main():
  function test_import_tool_decorator (line 18) | def test_import_tool_decorator():
  function test_tool_decorator_custom_metadata (line 30) | def test_tool_decorator_custom_metadata():
  function test_agent_creation (line 42) | def test_agent_creation():
  function test_cli_allows_interactive_without_message (line 54) | def test_cli_allows_interactive_without_message():
  function test_cli_requires_message_when_not_interactive (line 64) | def test_cli_requires_message_when_not_interactive():
  function test_invoke_in_event_loop_raises (line 73) | async def test_invoke_in_event_loop_raises():
  function test_shell_tool (line 84) | async def test_shell_tool():
  function test_file_tools (line 93) | async def test_file_tools(tmp_path):

FILE: python/zeroclaw_tools/__main__.py
  function chat (line 29) | async def chat(message: str, api_key: str, base_url: Optional[str], mode...
  function _build_parser (line 43) | def _build_parser() -> argparse.ArgumentParser:
  function parse_args (line 60) | def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
  function main (line 71) | def main(argv: list[str] | None = None):

FILE: python/zeroclaw_tools/agent.py
  class ZeroclawAgent (line 20) | class ZeroclawAgent:
    method __init__ (line 29) | def __init__(
    method _build_graph (line 63) | def _build_graph(self) -> StateGraph:
    method ainvoke (line 87) | async def ainvoke(self, input: dict[str, Any], config: Optional[dict] ...
    method invoke (line 106) | def invoke(self, input: dict[str, Any], config: Optional[dict] = None)...
  function create_agent (line 123) | def create_agent(

FILE: python/zeroclaw_tools/integrations/discord_bot.py
  class DiscordBot (line 22) | class DiscordBot:
    method __init__ (line 42) | def __init__(
    method _setup_events (line 88) | def _setup_events(self):
    method _process_message (line 128) | async def _process_message(self, content: str, user_id: str) -> str:
    method _split_message (line 153) | def _split_message(text: str, max_len: int = 1900) -> list[str]:
    method run (line 175) | def run(self):

FILE: python/zeroclaw_tools/tools/base.py
  function tool (line 10) | def tool(

FILE: python/zeroclaw_tools/tools/file.py
  function file_read (line 14) | def file_read(path: str) -> str:
  function file_write (line 39) | def file_write(path: str, content: str) -> str:

FILE: python/zeroclaw_tools/tools/memory.py
  function _get_memory_path (line 11) | def _get_memory_path() -> Path:
  function _load_memory (line 16) | def _load_memory() -> dict:
  function _save_memory (line 28) | def _save_memory(data: dict) -> None:
  function memory_store (line 37) | def memory_store(key: str, value: str) -> str:
  function memory_recall (line 58) | def memory_recall(query: str) -> str:

FILE: python/zeroclaw_tools/tools/shell.py
  function shell (line 11) | def shell(command: str) -> str:

FILE: python/zeroclaw_tools/tools/web.py
  function http_request (line 15) | def http_request(url: str, method: str = "GET", headers: str = "", body:...
  function web_search (line 50) | def web_search(query: str) -> str:

FILE: scripts/ci/collect_changed_links.py
  function run_git (line 20) | def run_git(args: list[str]) -> subprocess.CompletedProcess[str]:
  function commit_exists (line 24) | def commit_exists(rev: str) -> bool:
  function normalize_docs_files (line 30) | def normalize_docs_files(raw: str) -> list[str]:
  function infer_base_sha (line 41) | def infer_base_sha(provided: str) -> str:
  function infer_docs_files (line 51) | def infer_docs_files(base_sha: str, provided: list[str]) -> list[str]:
  function normalize_link_target (line 67) | def normalize_link_target(raw_target: str, source_path: str) -> str | None:
  function extract_links (line 105) | def extract_links(text: str, source_path: str) -> list[str]:
  function added_lines_for_file (line 126) | def added_lines_for_file(base_sha: str, path: str) -> list[str]:
  function main (line 143) | def main() -> int:

FILE: scripts/ci/fetch_actions_data.py
  function parse_args (line 22) | def parse_args():
  function fetch_runs (line 47) | def fetch_runs(repo, date_str, page=1, per_page=100):
  function fetch_jobs (line 60) | def fetch_jobs(repo, run_id):
  function parse_duration (line 70) | def parse_duration(started, completed):
  function main (line 82) | def main():

FILE: src/agent/agent.rs
  type Agent (line 20) | pub struct Agent {
    method builder (line 286) | pub fn builder() -> AgentBuilder {
    method history (line 290) | pub fn history(&self) -> &[ConversationMessage] {
    method clear_history (line 294) | pub fn clear_history(&mut self) {
    method set_memory_session_id (line 298) | pub fn set_memory_session_id(&mut self, session_id: Option<String>) {
    method seed_history (line 307) | pub fn seed_history(&mut self, messages: &[ChatMessage]) {
    method from_config (line 321) | pub fn from_config(config: &Config) -> Result<Self> {
    method trim_history (line 444) | fn trim_history(&mut self) {
    method build_system_prompt (line 471) | fn build_system_prompt(&self) -> Result<String> {
    method execute_tool_call (line 487) | async fn execute_tool_call(&self, call: &ParsedToolCall) -> ToolExecut...
    method execute_tools (line 525) | async fn execute_tools(&self, calls: &[ParsedToolCall]) -> Vec<ToolExe...
    method classify_model (line 541) | fn classify_model(&self, user_message: &str) -> String {
    method turn (line 565) | pub async fn turn(&mut self, user_message: &str) -> Result<String> {
    method run_single (line 726) | pub async fn run_single(&mut self, message: &str) -> Result<String> {
    method run_interactive (line 730) | pub async fn run_interactive(&mut self) -> Result<()> {
  type AgentBuilder (line 50) | pub struct AgentBuilder {
    method new (line 77) | pub fn new() -> Self {
    method provider (line 105) | pub fn provider(mut self, provider: Box<dyn Provider>) -> Self {
    method tools (line 110) | pub fn tools(mut self, tools: Vec<Box<dyn Tool>>) -> Self {
    method memory (line 115) | pub fn memory(mut self, memory: Arc<dyn Memory>) -> Self {
    method observer (line 120) | pub fn observer(mut self, observer: Arc<dyn Observer>) -> Self {
    method prompt_builder (line 125) | pub fn prompt_builder(mut self, prompt_builder: SystemPromptBuilder) -...
    method tool_dispatcher (line 130) | pub fn tool_dispatcher(mut self, tool_dispatcher: Box<dyn ToolDispatch...
    method memory_loader (line 135) | pub fn memory_loader(mut self, memory_loader: Box<dyn MemoryLoader>) -...
    method config (line 140) | pub fn config(mut self, config: crate::config::AgentConfig) -> Self {
    method model_name (line 145) | pub fn model_name(mut self, model_name: String) -> Self {
    method temperature (line 150) | pub fn temperature(mut self, temperature: f64) -> Self {
    method workspace_dir (line 155) | pub fn workspace_dir(mut self, workspace_dir: std::path::PathBuf) -> S...
    method identity_config (line 160) | pub fn identity_config(mut self, identity_config: crate::config::Ident...
    method skills (line 165) | pub fn skills(mut self, skills: Vec<crate::skills::Skill>) -> Self {
    method skills_prompt_mode (line 170) | pub fn skills_prompt_mode(
    method auto_save (line 178) | pub fn auto_save(mut self, auto_save: bool) -> Self {
    method memory_session_id (line 183) | pub fn memory_session_id(mut self, memory_session_id: Option<String>) ...
    method classification_config (line 188) | pub fn classification_config(
    method available_hints (line 196) | pub fn available_hints(mut self, available_hints: Vec<String>) -> Self {
    method route_model_by_hint (line 201) | pub fn route_model_by_hint(mut self, route_model_by_hint: HashMap<Stri...
    method allowed_tools (line 206) | pub fn allowed_tools(mut self, allowed_tools: Option<Vec<String>>) -> ...
    method response_cache (line 211) | pub fn response_cache(
    method tool_descriptions (line 219) | pub fn tool_descriptions(mut self, tool_descriptions: Option<ToolDescr...
    method security_summary (line 224) | pub fn security_summary(mut self, summary: Option<String>) -> Self {
    method build (line 229) | pub fn build(self) -> Result<Agent> {
  function run (line 757) | pub async fn run(
  type MockProvider (line 818) | struct MockProvider {
  method chat_with_system (line 824) | async fn chat_with_system(
  method chat (line 834) | async fn chat(
  type ModelCaptureProvider (line 853) | struct ModelCaptureProvider {
  method chat_with_system (line 860) | async fn chat_with_system(
  method chat (line 870) | async fn chat(
  type MockTool (line 890) | struct MockTool;
  method name (line 894) | fn name(&self) -> &str {
  method description (line 898) | fn description(&self) -> &str {
  method parameters_schema (line 902) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 906) | async fn execute(&self, _args: serde_json::Value) -> Result<crate::tools...
  function turn_without_tools_returns_text (line 916) | async fn turn_without_tools_returns_text() {
  function turn_with_native_dispatcher_handles_tool_results_variant (line 951) | async fn turn_with_native_dispatcher_handles_tool_results_variant() {
  function turn_routes_with_hint_when_query_classification_matches (line 1002) | async fn turn_routes_with_hint_when_query_classification_matches() {
  function from_config_passes_extra_headers_to_custom_provider (line 1056) | async fn from_config_passes_extra_headers_to_custom_provider() {
  function builder_allowed_tools_none_keeps_all_tools (line 1142) | fn builder_allowed_tools_none_keeps_all_tools() {
  function builder_allowed_tools_some_filters_tools (line 1173) | fn builder_allowed_tools_some_filters_tools() {
  function seed_history_prepends_system_and_skips_system_from_seed (line 1206) | fn seed_history_prepends_system_and_skips_system_from_seed() {

FILE: src/agent/classifier.rs
  type ClassificationDecision (line 4) | pub struct ClassificationDecision {
  function classify (line 14) | pub fn classify(config: &QueryClassificationConfig, message: &str) -> Op...
  function classify_with_decision (line 20) | pub fn classify_with_decision(
  function make_config (line 73) | fn make_config(enabled: bool, rules: Vec<ClassificationRule>) -> QueryCl...
  function disabled_returns_none (line 78) | fn disabled_returns_none() {
  function empty_rules_returns_none (line 91) | fn empty_rules_returns_none() {
  function keyword_match_case_insensitive (line 97) | fn keyword_match_case_insensitive() {
  function pattern_match_case_sensitive (line 110) | fn pattern_match_case_sensitive() {
  function length_constraints (line 124) | fn length_constraints() {
  function priority_ordering (line 157) | fn priority_ordering() {
  function no_match_returns_none (line 179) | fn no_match_returns_none() {
  function classify_with_decision_exposes_priority_of_matched_rule (line 192) | fn classify_with_decision_exposes_priority_of_matched_rule() {

FILE: src/agent/dispatcher.rs
  type ParsedToolCall (line 7) | pub struct ParsedToolCall {
  type ToolExecutionResult (line 14) | pub struct ToolExecutionResult {
  type ToolDispatcher (line 21) | pub trait ToolDispatcher: Send + Sync {
    method parse_response (line 22) | fn parse_response(&self, response: &ChatResponse) -> (String, Vec<Pars...
    method format_results (line 23) | fn format_results(&self, results: &[ToolExecutionResult]) -> Conversat...
    method prompt_instructions (line 24) | fn prompt_instructions(&self, tools: &[Box<dyn Tool>]) -> String;
    method to_provider_messages (line 25) | fn to_provider_messages(&self, history: &[ConversationMessage]) -> Vec...
    method should_send_tool_specs (line 26) | fn should_send_tool_specs(&self) -> bool;
    method parse_response (line 113) | fn parse_response(&self, response: &ChatResponse) -> (String, Vec<Pars...
    method format_results (line 118) | fn format_results(&self, results: &[ToolExecutionResult]) -> Conversat...
    method prompt_instructions (line 131) | fn prompt_instructions(&self, _tools: &[Box<dyn Tool>]) -> String {
    method to_provider_messages (line 143) | fn to_provider_messages(&self, history: &[ConversationMessage]) -> Vec...
    method should_send_tool_specs (line 166) | fn should_send_tool_specs(&self) -> bool {
    method parse_response (line 174) | fn parse_response(&self, response: &ChatResponse) -> (String, Vec<Pars...
    method format_results (line 195) | fn format_results(&self, results: &[ToolExecutionResult]) -> Conversat...
    method prompt_instructions (line 209) | fn prompt_instructions(&self, _tools: &[Box<dyn Tool>]) -> String {
    method to_provider_messages (line 213) | fn to_provider_messages(&self, history: &[ConversationMessage]) -> Vec...
    method should_send_tool_specs (line 248) | fn should_send_tool_specs(&self) -> bool {
  type XmlToolDispatcher (line 30) | pub struct XmlToolDispatcher;
    method parse_xml_tool_calls (line 33) | fn parse_xml_tool_calls(response: &str) -> (String, Vec<ParsedToolCall...
    method strip_think_tags (line 88) | fn strip_think_tags(s: &str) -> String {
    method tool_specs (line 107) | pub fn tool_specs(tools: &[Box<dyn Tool>]) -> Vec<ToolSpec> {
  type NativeToolDispatcher (line 171) | pub struct NativeToolDispatcher;
  function xml_dispatcher_parses_tool_calls (line 258) | fn xml_dispatcher_parses_tool_calls() {
  function xml_dispatcher_strips_think_before_tool_call (line 275) | fn xml_dispatcher_strips_think_before_tool_call() {
  function xml_dispatcher_think_only_returns_no_calls (line 296) | fn xml_dispatcher_think_only_returns_no_calls() {
  function native_dispatcher_roundtrip (line 309) | fn native_dispatcher_roundtrip() {
  function xml_format_results_contains_tool_result_tags (line 341) | fn xml_format_results_contains_tool_result_tags() {
  function native_format_results_keeps_tool_call_id (line 358) | fn native_format_results_keeps_tool_call_id() {
  function native_to_provider_messages_includes_reasoning_content (line 381) | fn native_to_provider_messages_includes_reasoning_content() {
  function native_to_provider_messages_omits_reasoning_content_when_none (line 404) | fn native_to_provider_messages_omits_reasoning_content_when_none() {
  function xml_to_provider_messages_ignores_reasoning_content (line 424) | fn xml_to_provider_messages_ignores_reasoning_content() {

FILE: src/agent/loop_.rs
  constant STREAM_CHUNK_MIN_CHARS (line 27) | const STREAM_CHUNK_MIN_CHARS: usize = 80;
  constant DEFAULT_MAX_TOOL_ITERATIONS (line 31) | const DEFAULT_MAX_TOOL_ITERATIONS: usize = 10;
  constant AUTOSAVE_MIN_MESSAGE_CHARS (line 35) | const AUTOSAVE_MIN_MESSAGE_CHARS: usize = 20;
  type ModelSwitchCallback (line 39) | pub type ModelSwitchCallback = Arc<Mutex<Option<(String, String)>>>;
  function get_model_switch_state (line 48) | pub fn get_model_switch_state() -> ModelSwitchCallback {
  function clear_model_switch_request (line 53) | pub fn clear_model_switch_request() {
  function glob_match (line 60) | fn glob_match(pattern: &str, name: &str) -> bool {
  function filter_tool_specs_for_turn (line 82) | pub(crate) fn filter_tool_specs_for_turn(
  function filter_by_allowed_tools (line 125) | pub(crate) fn filter_by_allowed_tools(
  function compute_excluded_mcp_tools (line 142) | fn compute_excluded_mcp_tools(
  function scrub_credentials (line 183) | pub(crate) fn scrub_credentials(input: &str) -> String {
  constant DEFAULT_MAX_HISTORY_MESSAGES (line 229) | const DEFAULT_MAX_HISTORY_MESSAGES: usize = 50;
  constant COMPACTION_KEEP_RECENT_MESSAGES (line 232) | const COMPACTION_KEEP_RECENT_MESSAGES: usize = 20;
  constant COMPACTION_MAX_SOURCE_CHARS (line 235) | const COMPACTION_MAX_SOURCE_CHARS: usize = 12_000;
  constant COMPACTION_MAX_SUMMARY_CHARS (line 238) | const COMPACTION_MAX_SUMMARY_CHARS: usize = 2_000;
  function estimate_history_tokens (line 242) | fn estimate_history_tokens(history: &[ChatMessage]) -> usize {
  constant PROGRESS_MIN_INTERVAL_MS (line 253) | pub(crate) const PROGRESS_MIN_INTERVAL_MS: u64 = 500;
  constant DRAFT_CLEAR_SENTINEL (line 257) | pub(crate) const DRAFT_CLEAR_SENTINEL: &str = "\x00CLEAR\x00";
  function truncate_tool_args_for_progress (line 260) | fn truncate_tool_args_for_progress(name: &str, args: &serde_json::Value,...
  function tools_to_openai_format (line 276) | fn tools_to_openai_format(tools_registry: &[Box<dyn Tool>]) -> Vec<serde...
  function autosave_memory_key (line 292) | fn autosave_memory_key(prefix: &str) -> String {
  function memory_session_id_from_state_file (line 296) | fn memory_session_id_from_state_file(path: &Path) -> Option<String> {
  function trim_history (line 307) | fn trim_history(history: &mut Vec<ChatMessage>, max_history: usize) {
  function build_compaction_transcript (line 325) | fn build_compaction_transcript(messages: &[ChatMessage]) -> String {
  function apply_compaction_summary (line 339) | fn apply_compaction_summary(
  function auto_compact_history (line 349) | async fn auto_compact_history(
  type InteractiveSessionState (line 412) | struct InteractiveSessionState {
    method from_history (line 418) | fn from_history(history: &[ChatMessage]) -> Self {
  function load_interactive_session_history (line 426) | fn load_interactive_session_history(path: &Path, system_prompt: &str) ->...
  function save_interactive_session_history (line 442) | fn save_interactive_session_history(path: &Path, history: &[ChatMessage]...
  function build_context (line 455) | async fn build_context(
  function build_hardware_context (line 503) | fn build_hardware_context(
  function find_tool (line 542) | fn find_tool<'a>(tools: &'a [Box<dyn Tool>], name: &str) -> Option<&'a d...
  function parse_arguments_value (line 546) | fn parse_arguments_value(raw: Option<&serde_json::Value>) -> serde_json:...
  function parse_tool_call_id (line 555) | fn parse_tool_call_id(
  function canonicalize_json_for_tool_signature (line 570) | fn canonicalize_json_for_tool_signature(value: &serde_json::Value) -> se...
  function tool_call_signature (line 593) | fn tool_call_signature(name: &str, arguments: &serde_json::Value) -> (St...
  function parse_tool_call_value (line 599) | fn parse_tool_call_value(value: &serde_json::Value) -> Option<ParsedTool...
  function parse_tool_calls_from_json_value (line 643) | fn parse_tool_calls_from_json_value(value: &serde_json::Value) -> Vec<Pa...
  function is_xml_meta_tag (line 674) | fn is_xml_meta_tag(tag: &str) -> bool {
  function extract_xml_pairs (line 710) | fn extract_xml_pairs(input: &str) -> Vec<(&str, &str)> {
  function parse_xml_tool_calls (line 734) | fn parse_xml_tool_calls(xml_content: &str) -> Option<Vec<ParsedToolCall>> {
  function parse_minimax_invoke_calls (line 797) | fn parse_minimax_invoke_calls(response: &str) -> Option<(String, Vec<Par...
  constant TOOL_CALL_OPEN_TAGS (line 893) | const TOOL_CALL_OPEN_TAGS: [&str; 6] = [
  constant TOOL_CALL_CLOSE_TAGS (line 902) | const TOOL_CALL_CLOSE_TAGS: [&str; 6] = [
  function find_first_tag (line 911) | fn find_first_tag<'a>(haystack: &str, tags: &'a [&'a str]) -> Option<(us...
  function matching_tool_call_close_tag (line 917) | fn matching_tool_call_close_tag(open_tag: &str) -> Option<&'static str> {
  function extract_first_json_value_with_end (line 929) | fn extract_first_json_value_with_end(input: &str) -> Option<(serde_json:...
  function strip_leading_close_tags (line 951) | fn strip_leading_close_tags(mut input: &str) -> &str {
  function extract_json_values (line 974) | fn extract_json_values(input: &str) -> Vec<serde_json::Value> {
  function find_json_end (line 1013) | fn find_json_end(input: &str) -> Option<usize> {
  function parse_xml_attribute_tool_calls (line 1057) | fn parse_xml_attribute_tool_calls(response: &str) -> Vec<ParsedToolCall> {
  function parse_perl_style_tool_calls (line 1114) | fn parse_perl_style_tool_calls(response: &str) -> Vec<ParsedToolCall> {
  function parse_function_call_tool_calls (line 1188) | fn parse_function_call_tool_calls(response: &str) -> Vec<ParsedToolCall> {
  function map_tool_name_alias (line 1235) | fn map_tool_name_alias(tool_name: &str) -> &str {
  function build_curl_command (line 1256) | fn build_curl_command(url: &str) -> Option<String> {
  function parse_glm_style_tool_calls (line 1269) | fn parse_glm_style_tool_calls(text: &str) -> Vec<(String, serde_json::Va...
  function default_param_for_tool (line 1335) | fn default_param_for_tool(tool: &str) -> &'static str {
  function parse_glm_shortened_body (line 1364) | fn parse_glm_shortened_body(body: &str) -> Option<ParsedToolCall> {
  function parse_tool_calls (line 1532) | fn parse_tool_calls(response: &str) -> (String, Vec<ParsedToolCall>) {
  function strip_think_tags (line 1926) | fn strip_think_tags(s: &str) -> String {
  function strip_tool_result_blocks (line 1948) | fn strip_tool_result_blocks(text: &str) -> String {
  function detect_tool_call_parse_issue (line 1969) | fn detect_tool_call_parse_issue(response: &str, parsed_calls: &[ParsedTo...
  function parse_structured_tool_calls (line 2001) | fn parse_structured_tool_calls(tool_calls: &[ToolCall]) -> Vec<ParsedToo...
  function build_native_assistant_history (line 2016) | fn build_native_assistant_history(
  function build_native_assistant_history_from_parsed_calls (line 2053) | fn build_native_assistant_history_from_parsed_calls(
  function build_assistant_history_with_tool_calls (line 2090) | fn build_assistant_history_with_tool_calls(text: &str, tool_calls: &[Too...
  function resolve_display_text (line 2111) | fn resolve_display_text(
  type ParsedToolCall (line 2135) | struct ParsedToolCall {
  type ToolLoopCancelled (line 2142) | pub(crate) struct ToolLoopCancelled;
    method fmt (line 2145) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function is_tool_loop_cancelled (line 2152) | pub(crate) fn is_tool_loop_cancelled(err: &anyhow::Error) -> bool {
  type ModelSwitchRequested (line 2157) | pub(crate) struct ModelSwitchRequested {
    method fmt (line 2163) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function is_model_switch_requested (line 2174) | pub(crate) fn is_model_switch_requested(err: &anyhow::Error) -> Option<(...
  function agent_turn (line 2185) | pub(crate) async fn agent_turn(
  function maybe_inject_channel_delivery_defaults (line 2229) | fn maybe_inject_channel_delivery_defaults(
  function execute_one_tool (line 2323) | async fn execute_one_tool(
  type ToolExecutionOutcome (line 2413) | struct ToolExecutionOutcome {
  function should_execute_tools_in_parallel (line 2420) | fn should_execute_tools_in_parallel(
  function execute_tools_parallel (line 2439) | async fn execute_tools_parallel(
  function execute_tools_sequential (line 2464) | async fn execute_tools_sequential(
  function run_tool_call_loop (line 2505) | pub(crate) async fn run_tool_call_loop(
  function build_tool_instructions (line 3210) | pub(crate) fn build_tool_instructions(
  function run (line 3251) | pub async fn run(
  function process_message (line 4024) | pub async fn process_message(
  function interactive_session_state_round_trips_history (line 4335) | fn interactive_session_state_round_trips_history() {
  function interactive_session_state_adds_missing_system_prompt (line 4354) | fn interactive_session_state_adds_missing_system_prompt() {
  function scrub_credentials_redacts_bearer_token (line 4380) | fn scrub_credentials_redacts_bearer_token() {
  function scrub_credentials_redacts_json_api_key (line 4391) | fn scrub_credentials_redacts_json_api_key() {
  function execute_one_tool_does_not_panic_on_utf8_boundary (line 4399) | async fn execute_one_tool_does_not_panic_on_utf8_boundary() {
  function execute_one_tool_resolves_unique_activated_tool_suffix (line 4419) | async fn execute_one_tool_resolves_unique_activated_tool_suffix() {
  type NonVisionProvider (line 4454) | struct NonVisionProvider {
  method chat_with_system (line 4460) | async fn chat_with_system(
  type VisionProvider (line 4472) | struct VisionProvider {
  method capabilities (line 4478) | fn capabilities(&self) -> ProviderCapabilities {
  method chat_with_system (line 4486) | async fn chat_with_system(
  method chat (line 4497) | async fn chat(
  type ScriptedProvider (line 4522) | struct ScriptedProvider {
    method from_text_responses (line 4528) | fn from_text_responses(responses: Vec<&str>) -> Self {
    method with_native_tool_support (line 4544) | fn with_native_tool_support(mut self) -> Self {
  method capabilities (line 4552) | fn capabilities(&self) -> ProviderCapabilities {
  method chat_with_system (line 4556) | async fn chat_with_system(
  method chat (line 4566) | async fn chat(
  type CountingTool (line 4582) | struct CountingTool {
    method new (line 4588) | fn new(name: &str, invocations: Arc<AtomicUsize>) -> Self {
  method name (line 4598) | fn name(&self) -> &str {
  method description (line 4602) | fn description(&self) -> &str {
  method parameters_schema (line 4606) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 4615) | async fn execute(
  type RecordingArgsTool (line 4632) | struct RecordingArgsTool {
    method new (line 4638) | fn new(name: &str, recorded_args: Arc<Mutex<Vec<serde_json::Value>>>) ...
  method name (line 4648) | fn name(&self) -> &str {
  method description (line 4652) | fn description(&self) -> &str {
  method parameters_schema (line 4656) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 4667) | async fn execute(
  type DelayTool (line 4683) | struct DelayTool {
    method new (line 4691) | fn new(
  method name (line 4708) | fn name(&self) -> &str {
  method description (line 4712) | fn description(&self) -> &str {
  method parameters_schema (line 4716) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 4726) | async fn execute(
  type FailingTool (line 4752) | struct FailingTool {
    method new (line 4758) | fn new(name: &str, error_reason: &str) -> Self {
  method name (line 4768) | fn name(&self) -> &str {
  method description (line 4772) | fn description(&self) -> &str {
  method parameters_schema (line 4776) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 4785) | async fn execute(
  function run_tool_call_loop_returns_structured_error_for_non_vision_provider (line 4798) | async fn run_tool_call_loop_returns_structured_error_for_non_vision_prov...
  function run_tool_call_loop_rejects_oversized_image_payload (line 4841) | async fn run_tool_call_loop_rejects_oversized_image_payload() {
  function run_tool_call_loop_accepts_valid_multimodal_request_flow (line 4892) | async fn run_tool_call_loop_accepts_valid_multimodal_request_flow() {
  function should_execute_tools_in_parallel_returns_false_for_single_call (line 4934) | fn should_execute_tools_in_parallel_returns_false_for_single_call() {
  function should_execute_tools_in_parallel_returns_false_when_approval_is_required (line 4945) | fn should_execute_tools_in_parallel_returns_false_when_approval_is_requi...
  function should_execute_tools_in_parallel_returns_true_when_cli_has_no_interactive_approvals (line 4968) | fn should_execute_tools_in_parallel_returns_true_when_cli_has_no_interac...
  function run_tool_call_loop_executes_multiple_tools_with_ordered_results (line 4994) | async fn run_tool_call_loop_executes_multiple_tools_with_ordered_results...
  function run_tool_call_loop_injects_channel_delivery_defaults_for_cron_add (line 5084) | async fn run_tool_call_loop_injects_channel_delivery_defaults_for_cron_a...
  function run_tool_call_loop_preserves_explicit_cron_delivery_none (line 5146) | async fn run_tool_call_loop_preserves_explicit_cron_delivery_none() {
  function run_tool_call_loop_deduplicates_repeated_tool_calls (line 5200) | async fn run_tool_call_loop_deduplicates_repeated_tool_calls() {
  function run_tool_call_loop_allows_low_risk_shell_in_non_interactive_mode (line 5264) | async fn run_tool_call_loop_allows_low_risk_shell_in_non_interactive_mod...
  function run_tool_call_loop_dedup_exempt_allows_repeated_calls (line 5328) | async fn run_tool_call_loop_dedup_exempt_allows_repeated_calls() {
  function run_tool_call_loop_dedup_exempt_only_affects_listed_tools (line 5395) | async fn run_tool_call_loop_dedup_exempt_only_affects_listed_tools() {
  function run_tool_call_loop_native_mode_preserves_fallback_tool_call_ids (line 5470) | async fn run_tool_call_loop_native_mode_preserves_fallback_tool_call_ids...
  function run_tool_call_loop_relays_native_tool_call_text_via_on_delta (line 5531) | async fn run_tool_call_loop_relays_native_tool_call_text_via_on_delta() {
  function agent_turn_executes_activated_tool_from_wrapper (line 5624) | fn agent_turn_executes_activated_tool_from_wrapper() {
  function resolve_display_text_hides_raw_payload_for_tool_only_turns (line 5684) | fn resolve_display_text_hides_raw_payload_for_tool_only_turns() {
  function resolve_display_text_keeps_plain_text_for_tool_turns (line 5695) | fn resolve_display_text_keeps_plain_text_for_tool_turns() {
  function resolve_display_text_uses_response_text_for_native_tool_turns (line 5706) | fn resolve_display_text_uses_response_text_for_native_tool_turns() {
  function resolve_display_text_uses_response_text_for_final_turns (line 5712) | fn resolve_display_text_uses_response_text_for_final_turns() {
  function parse_tool_calls_extracts_single_call (line 5718) | fn parse_tool_calls_extracts_single_call() {
  function parse_tool_calls_extracts_multiple_calls (line 5735) | fn parse_tool_calls_extracts_multiple_calls() {
  function parse_tool_calls_returns_text_only_when_no_calls (line 5750) | fn parse_tool_calls_returns_text_only_when_no_calls() {
  function parse_tool_calls_handles_malformed_json (line 5758) | fn parse_tool_calls_handles_malformed_json() {
  function parse_tool_calls_text_before_and_after (line 5770) | fn parse_tool_calls_text_before_and_after() {
  function parse_tool_calls_handles_openai_format (line 5784) | fn parse_tool_calls_handles_openai_format() {
  function parse_tool_calls_handles_openai_format_multiple_calls (line 5799) | fn parse_tool_calls_handles_openai_format_multiple_calls() {
  function parse_tool_calls_openai_format_without_content (line 5809) | fn parse_tool_calls_openai_format_without_content() {
  function parse_tool_calls_preserves_openai_tool_call_ids (line 5820) | fn parse_tool_calls_preserves_openai_tool_call_ids() {
  function parse_tool_calls_handles_markdown_json_inside_tool_call_tag (line 5828) | fn parse_tool_calls_handles_markdown_json_inside_tool_call_tag() {
  function parse_tool_calls_handles_noisy_tool_call_tag_body (line 5846) | fn parse_tool_calls_handles_noisy_tool_call_tag_body() {
  function parse_tool_calls_handles_tool_call_inline_attributes_with_send_message_alias (line 5863) | fn parse_tool_calls_handles_tool_call_inline_attributes_with_send_messag...
  function parse_tool_calls_handles_tool_call_function_style_arguments (line 5881) | fn parse_tool_calls_handles_tool_call_function_style_arguments() {
  function parse_tool_calls_handles_xml_nested_tool_payload (line 5899) | fn parse_tool_calls_handles_xml_nested_tool_payload() {
  function parse_tool_calls_ignores_xml_thinking_wrapper (line 5917) | fn parse_tool_calls_ignores_xml_thinking_wrapper() {
  function parse_tool_calls_handles_xml_with_json_arguments (line 5936) | fn parse_tool_calls_handles_xml_with_json_arguments() {
  function parse_tool_calls_handles_markdown_tool_call_fence (line 5952) | fn parse_tool_calls_handles_markdown_tool_call_fence() {
  function parse_tool_calls_handles_markdown_tool_call_hybrid_close_tag (line 5972) | fn parse_tool_calls_handles_markdown_tool_call_hybrid_close_tag() {
  function parse_tool_calls_handles_markdown_invoke_fence (line 5992) | fn parse_tool_calls_handles_markdown_invoke_fence() {
  function parse_tool_calls_handles_tool_name_fence_format (line 6011) | fn parse_tool_calls_handles_tool_name_fence_format() {
  function parse_tool_calls_handles_tool_name_fence_shell (line 6031) | fn parse_tool_calls_handles_tool_name_fence_shell() {
  function parse_tool_calls_handles_multiple_tool_name_fences (line 6047) | fn parse_tool_calls_handles_multiple_tool_name_fences() {
  function parse_tool_calls_handles_toolcall_tag_alias (line 6069) | fn parse_tool_calls_handles_toolcall_tag_alias() {
  function parse_tool_calls_handles_tool_dash_call_tag_alias (line 6085) | fn parse_tool_calls_handles_tool_dash_call_tag_alias() {
  function parse_tool_calls_handles_invoke_tag_alias (line 6101) | fn parse_tool_calls_handles_invoke_tag_alias() {
  function parse_tool_calls_handles_minimax_invoke_parameter_format (line 6117) | fn parse_tool_calls_handles_minimax_invoke_parameter_format() {
  function parse_tool_calls_handles_minimax_invoke_with_surrounding_text (line 6135) | fn parse_tool_calls_handles_minimax_invoke_with_surrounding_text() {
  function parse_tool_calls_handles_minimax_toolcall_alias_and_cross_close_tag (line 6161) | fn parse_tool_calls_handles_minimax_toolcall_alias_and_cross_close_tag() {
  function parse_tool_calls_handles_perl_style_tool_call_blocks (line 6177) | fn parse_tool_calls_handles_perl_style_tool_call_blocks() {
  function parse_tool_calls_recovers_unclosed_tool_call_with_json (line 6192) | fn parse_tool_calls_recovers_unclosed_tool_call_with_json() {
  function parse_tool_calls_recovers_mismatched_close_tag (line 6208) | fn parse_tool_calls_recovers_mismatched_close_tag() {
  function parse_tool_calls_recovers_cross_alias_closing_tags (line 6224) | fn parse_tool_calls_recovers_cross_alias_closing_tags() {
  function parse_tool_calls_rejects_raw_tool_json_without_tags (line 6236) | fn parse_tool_calls_rejects_raw_tool_json_without_tags() {
  function build_tool_instructions_includes_all_tools (line 6253) | fn build_tool_instructions_includes_all_tools() {
  function tools_to_openai_format_produces_valid_schema (line 6270) | fn tools_to_openai_format_produces_valid_schema() {
  function trim_history_preserves_system_prompt (line 6296) | fn trim_history_preserves_system_prompt() {
  function trim_history_noop_when_within_limit (line 6320) | fn trim_history_noop_when_within_limit() {
  function build_compaction_transcript_formats_roles (line 6331) | fn build_compaction_transcript_formats_roles() {
  function apply_compaction_summary_replaces_old_segment (line 6342) | fn apply_compaction_summary_replaces_old_segment() {
  function autosave_memory_key_has_prefix_and_uniqueness (line 6360) | fn autosave_memory_key_has_prefix_and_uniqueness() {
  function autosave_memory_keys_preserve_multiple_turns (line 6370) | async fn autosave_memory_keys_preserve_multiple_turns() {
  function build_context_ignores_legacy_assistant_autosave_entries (line 6391) | async fn build_context_ignores_legacy_assistant_autosave_entries() {
  function parse_tool_calls_handles_empty_tool_result (line 6422) | fn parse_tool_calls_handles_empty_tool_result() {
  function strip_tool_result_blocks_removes_single_block (line 6435) | fn strip_tool_result_blocks_removes_single_block() {
  function strip_tool_result_blocks_removes_multiple_blocks (line 6444) | fn strip_tool_result_blocks_removes_multiple_blocks() {
  function strip_tool_result_blocks_removes_prefix (line 6456) | fn strip_tool_result_blocks_removes_prefix() {
  function strip_tool_result_blocks_removes_thinking (line 6463) | fn strip_tool_result_blocks_removes_thinking() {
  function strip_tool_result_blocks_removes_think_tags (line 6469) | fn strip_tool_result_blocks_removes_think_tags() {
  function strip_think_tags_removes_single_block (line 6475) | fn strip_think_tags_removes_single_block() {
  function strip_think_tags_removes_multiple_blocks (line 6480) | fn strip_think_tags_removes_multiple_blocks() {
  function strip_think_tags_handles_unclosed_block (line 6485) | fn strip_think_tags_handles_unclosed_block() {
  function strip_think_tags_preserves_text_without_tags (line 6490) | fn strip_think_tags_preserves_text_without_tags() {
  function parse_tool_calls_strips_think_before_tool_call (line 6495) | fn parse_tool_calls_strips_think_before_tool_call() {
  function parse_tool_calls_strips_think_only_returns_empty (line 6514) | fn parse_tool_calls_strips_think_only_returns_empty() {
  function parse_tool_calls_handles_qwen_think_with_multiple_tool_calls (line 6524) | fn parse_tool_calls_handles_qwen_think_with_multiple_tool_calls() {
  function strip_tool_result_blocks_preserves_clean_text (line 6539) | fn strip_tool_result_blocks_preserves_clean_text() {
  function strip_tool_result_blocks_returns_empty_for_only_tags (line 6545) | fn strip_tool_result_blocks_returns_empty_for_only_tags() {
  function parse_arguments_value_handles_null (line 6551) | fn parse_arguments_value_handles_null() {
  function parse_tool_calls_handles_empty_tool_calls_array (line 6559) | fn parse_tool_calls_handles_empty_tool_calls_array() {
  function detect_tool_call_parse_issue_flags_malformed_payloads (line 6569) | fn detect_tool_call_parse_issue_flags_malformed_payloads() {
  function detect_tool_call_parse_issue_ignores_normal_text (line 6580) | fn detect_tool_call_parse_issue_ignores_normal_text() {
  function parse_tool_calls_handles_whitespace_only_name (line 6586) | fn parse_tool_calls_handles_whitespace_only_name() {
  function parse_tool_calls_handles_empty_string_arguments (line 6594) | fn parse_tool_calls_handles_empty_string_arguments() {
  function trim_history_with_no_system_prompt (line 6607) | fn trim_history_with_no_system_prompt() {
  function trim_history_preserves_role_ordering (line 6618) | fn trim_history_preserves_role_ordering() {
  function trim_history_with_only_system_prompt (line 6631) | fn trim_history_with_only_system_prompt() {
  function parse_arguments_value_handles_invalid_json_string (line 6643) | fn parse_arguments_value_handles_invalid_json_string() {
  function parse_arguments_value_handles_none (line 6652) | fn parse_arguments_value_handles_none() {
  function extract_json_values_handles_empty_string (line 6664) | fn extract_json_values_handles_empty_string() {
  function extract_json_values_handles_whitespace_only (line 6671) | fn extract_json_values_handles_whitespace_only() {
  function extract_json_values_handles_multiple_objects (line 6678) | fn extract_json_values_handles_multiple_objects() {
  function extract_json_values_handles_arrays (line 6686) | fn extract_json_values_handles_arrays() {
  constant _ (line 6697) | const _: () = {
  function constants_bounds_are_compile_time_checked (line 6705) | fn constants_bounds_are_compile_time_checked() {
  function parse_tool_call_value_handles_missing_name_field (line 6714) | fn parse_tool_call_value_handles_missing_name_field() {
  function parse_tool_call_value_handles_top_level_name (line 6722) | fn parse_tool_call_value_handles_top_level_name() {
  function parse_tool_call_value_accepts_top_level_parameters_alias (line 6731) | fn parse_tool_call_value_accepts_top_level_parameters_alias() {
  function parse_tool_call_value_accepts_function_parameters_alias (line 6745) | fn parse_tool_call_value_accepts_function_parameters_alias() {
  function parse_tool_call_value_preserves_tool_call_id_aliases (line 6761) | fn parse_tool_call_value_preserves_tool_call_id_aliases() {
  function parse_tool_calls_from_json_value_handles_empty_array (line 6774) | fn parse_tool_calls_from_json_value_handles_empty_array() {
  function parse_tool_calls_from_json_value_handles_missing_tool_calls (line 6782) | fn parse_tool_calls_from_json_value_handles_missing_tool_calls() {
  function parse_tool_calls_from_json_value_handles_top_level_array (line 6790) | fn parse_tool_calls_from_json_value_handles_top_level_array() {
  function parse_glm_style_browser_open_url (line 6805) | fn parse_glm_style_browser_open_url() {
  function parse_glm_style_shell_command (line 6818) | fn parse_glm_style_shell_command() {
  function parse_glm_style_http_request (line 6827) | fn parse_glm_style_http_request() {
  function parse_glm_style_ignores_plain_url (line 6837) | fn parse_glm_style_ignores_plain_url() {
  function parse_glm_style_json_args (line 6849) | fn parse_glm_style_json_args() {
  function parse_glm_style_multiple_calls (line 6858) | fn parse_glm_style_multiple_calls() {
  function parse_glm_style_tool_call_integration (line 6866) | fn parse_glm_style_tool_call_integration() {
  function parse_glm_style_rejects_non_http_url_param (line 6877) | fn parse_glm_style_rejects_non_http_url_param() {
  function parse_tool_calls_handles_unclosed_tool_call_tag (line 6884) | fn parse_tool_calls_handles_unclosed_tool_call_tag() {
  function parse_tool_calls_empty_input_returns_empty (line 6899) | fn parse_tool_calls_empty_input_returns_empty() {
  function parse_tool_calls_whitespace_only_returns_empty_calls (line 6906) | fn parse_tool_calls_whitespace_only_returns_empty_calls() {
  function parse_tool_calls_nested_xml_tags_handled (line 6913) | fn parse_tool_calls_nested_xml_tags_handled() {
  function parse_tool_calls_truncated_json_no_panic (line 6925) | fn parse_tool_calls_truncated_json_no_panic() {
  function parse_tool_calls_empty_json_object_in_tag (line 6933) | fn parse_tool_calls_empty_json_object_in_tag() {
  function parse_tool_calls_closing_tag_only_returns_text (line 6944) | fn parse_tool_calls_closing_tag_only_returns_text() {
  function parse_tool_calls_very_large_arguments_no_panic (line 6958) | fn parse_tool_calls_very_large_arguments_no_panic() {
  function parse_tool_calls_special_characters_in_arguments (line 6970) | fn parse_tool_calls_special_characters_in_arguments() {
  function parse_tool_calls_text_with_embedded_json_not_extracted (line 6978) | fn parse_tool_calls_text_with_embedded_json_not_extracted() {
  function parse_tool_calls_multiple_formats_mixed (line 6989) | fn parse_tool_calls_multiple_formats_mixed() {
  function scrub_credentials_empty_input (line 7016) | fn scrub_credentials_empty_input() {
  function scrub_credentials_no_sensitive_data (line 7022) | fn scrub_credentials_no_sensitive_data() {
  function scrub_credentials_multibyte_chars_no_panic (line 7032) | fn scrub_credentials_multibyte_chars_no_panic() {
  function scrub_credentials_short_values_not_redacted (line 7046) | fn scrub_credentials_short_values_not_redacted() {
  function trim_history_empty_history (line 7058) | fn trim_history_empty_history() {
  function trim_history_system_only (line 7065) | fn trim_history_system_only() {
  function trim_history_exactly_at_limit (line 7073) | fn trim_history_exactly_at_limit() {
  function trim_history_removes_oldest_non_system (line 7084) | fn trim_history_removes_oldest_non_system() {
  function native_tools_system_prompt_contains_zero_xml (line 7103) | fn native_tools_system_prompt_contains_zero_xml() {
  function parse_tool_calls_cross_alias_close_tag_with_json (line 7159) | fn parse_tool_calls_cross_alias_close_tag_with_json() {
  function parse_tool_calls_cross_alias_close_tag_with_glm_shortened (line 7170) | fn parse_tool_calls_cross_alias_close_tag_with_glm_shortened() {
  function parse_tool_calls_glm_shortened_body_in_matched_tags (line 7181) | fn parse_tool_calls_glm_shortened_body_in_matched_tags() {
  function parse_tool_calls_glm_yaml_style_in_tags (line 7192) | fn parse_tool_calls_glm_yaml_style_in_tags() {
  function parse_tool_calls_attribute_style_in_tags (line 7204) | fn parse_tool_calls_attribute_style_in_tags() {
  function parse_tool_calls_file_read_shortened_in_cross_alias (line 7215) | fn parse_tool_calls_file_read_shortened_in_cross_alias() {
  function parse_tool_calls_unclosed_glm_shortened_no_close_tag (line 7226) | fn parse_tool_calls_unclosed_glm_shortened_no_close_tag() {
  function parse_tool_calls_text_before_cross_alias (line 7237) | fn parse_tool_calls_text_before_cross_alias() {
  function parse_glm_shortened_body_url_to_curl (line 7249) | fn parse_glm_shortened_body_url_to_curl() {
  function parse_glm_shortened_body_browser_open_maps_to_shell_command (line 7259) | fn parse_glm_shortened_body_browser_open_maps_to_shell_command() {
  function parse_glm_shortened_body_memory_recall (line 7270) | fn parse_glm_shortened_body_memory_recall() {
  function parse_glm_shortened_body_function_style_alias_maps_to_message_send (line 7278) | fn parse_glm_shortened_body_function_style_alias_maps_to_message_send() {
  function map_tool_name_alias_direct_coverage (line 7287) | fn map_tool_name_alias_direct_coverage() {
  function default_param_for_tool_coverage (line 7300) | fn default_param_for_tool_coverage() {
  function parse_glm_shortened_body_rejects_empty (line 7312) | fn parse_glm_shortened_body_rejects_empty() {
  function parse_glm_shortened_body_rejects_invalid_tool_name (line 7318) | fn parse_glm_shortened_body_rejects_invalid_tool_name() {
  function build_native_assistant_history_includes_reasoning_content (line 7329) | fn build_native_assistant_history_includes_reasoning_content() {
  function build_native_assistant_history_omits_reasoning_content_when_none (line 7343) | fn build_native_assistant_history_omits_reasoning_content_when_none() {
  function build_native_assistant_history_from_parsed_calls_includes_reasoning_content (line 7356) | fn build_native_assistant_history_from_parsed_calls_includes_reasoning_c...
  function build_native_assistant_history_from_parsed_calls_omits_reasoning_content_when_none (line 7375) | fn build_native_assistant_history_from_parsed_calls_omits_reasoning_cont...
  function glob_match_exact_no_wildcard (line 7391) | fn glob_match_exact_no_wildcard() {
  function glob_match_prefix_wildcard (line 7397) | fn glob_match_prefix_wildcard() {
  function glob_match_star_matches_everything (line 7413) | fn glob_match_star_matches_everything() {
  function make_spec (line 7420) | fn make_spec(name: &str) -> crate::tools::ToolSpec {
  function filter_tool_specs_no_groups_returns_all (line 7429) | fn filter_tool_specs_no_groups_returns_all() {
  function filter_tool_specs_always_group_includes_matching_mcp_tool (line 7440) | fn filter_tool_specs_always_group_includes_matching_mcp_tool() {
  function filter_tool_specs_dynamic_group_included_on_keyword_match (line 7462) | fn filter_tool_specs_dynamic_group_included_on_keyword_match() {
  function filter_tool_specs_dynamic_group_excluded_on_no_keyword_match (line 7478) | fn filter_tool_specs_dynamic_group_excluded_on_no_keyword_match() {
  function filter_tool_specs_dynamic_keyword_match_is_case_insensitive (line 7494) | fn filter_tool_specs_dynamic_keyword_match_is_case_insensitive() {
  function estimate_history_tokens_empty (line 7510) | fn estimate_history_tokens_empty() {
  function estimate_history_tokens_single_message (line 7515) | fn estimate_history_tokens_single_message() {
  function estimate_history_tokens_multiple_messages (line 7523) | fn estimate_history_tokens_multiple_messages() {
  function run_tool_call_loop_surfaces_tool_failure_reason_in_on_delta (line 7534) | async fn run_tool_call_loop_surfaces_tool_failure_reason_in_on_delta() {
  function filter_by_allowed_tools_none_passes_all (line 7606) | fn filter_by_allowed_tools_none_passes_all() {
  function filter_by_allowed_tools_some_restricts_to_listed (line 7617) | fn filter_by_allowed_tools_some_restricts_to_listed() {
  function filter_by_allowed_tools_unknown_names_silently_ignored (line 7633) | fn filter_by_allowed_tools_unknown_names_silently_ignored() {
  function filter_by_allowed_tools_empty_list_excludes_all (line 7647) | fn filter_by_allowed_tools_empty_list_excludes_all() {

FILE: src/agent/memory_loader.rs
  type MemoryLoader (line 6) | pub trait MemoryLoader: Send + Sync {
    method load_context (line 7) | async fn load_context(
    method load_context (line 40) | async fn load_context(
  type DefaultMemoryLoader (line 15) | pub struct DefaultMemoryLoader {
    method new (line 30) | pub fn new(limit: usize, min_relevance_score: f64) -> Self {
  method default (line 21) | fn default() -> Self {
  type MockMemory (line 83) | struct MockMemory;
  type MockMemoryWithEntries (line 84) | struct MockMemoryWithEntries {
  method store (line 90) | async fn store(
  method recall (line 100) | async fn recall(
  method get (line 120) | async fn get(&self, _key: &str) -> anyhow::Result<Option<MemoryEntry>> {
  method list (line 124) | async fn list(
  method forget (line 132) | async fn forget(&self, _key: &str) -> anyhow::Result<bool> {
  method count (line 136) | async fn count(&self) -> anyhow::Result<usize> {
  method health_check (line 140) | async fn health_check(&self) -> bool {
  method name (line 144) | fn name(&self) -> &str {
  method store (line 151) | async fn store(
  method recall (line 161) | async fn recall(
  method get (line 170) | async fn get(&self, _key: &str) -> anyhow::Result<Option<MemoryEntry>> {
  method list (line 174) | async fn list(
  method forget (line 182) | async fn forget(&self, _key: &str) -> anyhow::Result<bool> {
  method count (line 186) | async fn count(&self) -> anyhow::Result<usize> {
  method health_check (line 190) | async fn health_check(&self) -> bool {
  method name (line 194) | fn name(&self) -> &str {
  function default_loader_formats_context (line 200) | async fn default_loader_formats_context() {
  function default_loader_skips_legacy_assistant_autosave_entries (line 211) | async fn default_loader_skips_legacy_assistant_autosave_entries() {

FILE: src/agent/prompt.rs
  constant BOOTSTRAP_MAX_CHARS (line 11) | const BOOTSTRAP_MAX_CHARS: usize = 20_000;
  type PromptContext (line 13) | pub struct PromptContext<'a> {
  type PromptSection (line 31) | pub trait PromptSection: Send + Sync {
    method name (line 32) | fn name(&self) -> &str;
    method build (line 33) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String>;
    method name (line 88) | fn name(&self) -> &str {
    method build (line 92) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 131) | fn name(&self) -> &str {
    method build (line 135) | fn build(&self, _ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 147) | fn name(&self) -> &str {
    method build (line 151) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 175) | fn name(&self) -> &str {
    method build (line 179) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 202) | fn name(&self) -> &str {
    method build (line 206) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 216) | fn name(&self) -> &str {
    method build (line 220) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 229) | fn name(&self) -> &str {
    method build (line 233) | fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 245) | fn name(&self) -> &str {
    method build (line 249) | fn build(&self, _ctx: &PromptContext<'_>) -> Result<String> {
    method name (line 260) | fn name(&self) -> &str {
    method build (line 264) | fn build(&self, _ctx: &PromptContext<'_>) -> Result<String> {
  type SystemPromptBuilder (line 37) | pub struct SystemPromptBuilder {
    method with_defaults (line 42) | pub fn with_defaults() -> Self {
    method add_section (line 58) | pub fn add_section(mut self, section: Box<dyn PromptSection>) -> Self {
    method build (line 63) | pub fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
  type IdentitySection (line 77) | pub struct IdentitySection;
  type ToolHonestySection (line 78) | pub struct ToolHonestySection;
  type ToolsSection (line 79) | pub struct ToolsSection;
  type SafetySection (line 80) | pub struct SafetySection;
  type SkillsSection (line 81) | pub struct SkillsSection;
  type WorkspaceSection (line 82) | pub struct WorkspaceSection;
  type RuntimeSection (line 83) | pub struct RuntimeSection;
  type DateTimeSection (line 84) | pub struct DateTimeSection;
  type ChannelMediaSection (line 85) | pub struct ChannelMediaSection;
  function inject_workspace_file (line 274) | fn inject_workspace_file(prompt: &mut String, workspace_dir: &Path, file...
  type TestTool (line 314) | struct TestTool;
  method name (line 318) | fn name(&self) -> &str {
  method description (line 322) | fn description(&self) -> &str {
  method parameters_schema (line 326) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 330) | async fn execute(
  function identity_section_with_aieos_includes_workspace_files (line 343) | fn identity_section_with_aieos_includes_workspace_files() {
  function prompt_builder_assembles_sections (line 388) | fn prompt_builder_assembles_sections() {
  function skills_section_includes_instructions_and_tools (line 408) | fn skills_section_includes_instructions_and_tools() {
  function skills_section_compact_mode_omits_instructions_but_keeps_tools (line 448) | fn skills_section_compact_mode_omits_instructions_but_keeps_tools() {
  function datetime_section_includes_timestamp_and_timezone (line 492) | fn datetime_section_includes_timestamp_and_timezone() {
  function prompt_builder_inlines_and_escapes_skills (line 516) | fn prompt_builder_inlines_and_escapes_skills() {
  function safety_section_includes_security_summary_when_present (line 562) | fn safety_section_includes_security_summary_when_present() {
  function safety_section_omits_security_policy_when_none (line 599) | fn safety_section_omits_security_policy_when_none() {

FILE: src/agent/tests.rs
  type ScriptedProvider (line 49) | struct ScriptedProvider {
    method new (line 56) | fn new(responses: Vec<ChatResponse>) -> Self {
    method request_count (line 63) | fn request_count(&self) -> usize {
  method chat_with_system (line 70) | async fn chat_with_system(
  method chat (line 80) | async fn chat(
  type FailingProvider (line 105) | struct FailingProvider;
  method chat_with_system (line 109) | async fn chat_with_system(
  method chat (line 119) | async fn chat(
  type EchoTool (line 130) | struct EchoTool;
  method name (line 134) | fn name(&self) -> &str {
  method description (line 138) | fn description(&self) -> &str {
  method parameters_schema (line 142) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 151) | async fn execute(&self, args: serde_json::Value) -> Result<ToolResult> {
  type FailingTool (line 166) | struct FailingTool;
  method name (line 170) | fn name(&self) -> &str {
  method description (line 174) | fn description(&self) -> &str {
  method parameters_schema (line 178) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 182) | async fn execute(&self, _args: serde_json::Value) -> Result<ToolResult> {
  type PanickingTool (line 192) | struct PanickingTool;
  method name (line 196) | fn name(&self) -> &str {
  method description (line 200) | fn description(&self) -> &str {
  method parameters_schema (line 204) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 208) | async fn execute(&self, _args: serde_json::Value) -> Result<ToolResult> {
  type CountingTool (line 214) | struct CountingTool {
    method new (line 219) | fn new() -> (Self, Arc<Mutex<usize>>) {
  method name (line 232) | fn name(&self) -> &str {
  method description (line 236) | fn description(&self) -> &str {
  method parameters_schema (line 240) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 244) | async fn execute(&self, _args: serde_json::Value) -> Result<ToolResult> {
  function make_memory (line 255) | fn make_memory() -> Arc<dyn Memory> {
  function make_sqlite_memory (line 263) | fn make_sqlite_memory() -> (Arc<dyn Memory>, tempfile::TempDir) {
  function make_observer (line 273) | fn make_observer() -> Arc<dyn Observer> {
  function build_agent_with (line 277) | fn build_agent_with(
  function build_agent_with_memory (line 293) | fn build_agent_with_memory(
  function build_agent_with_config (line 311) | fn build_agent_with_config(
  function tool_response (line 329) | fn tool_response(calls: Vec<ToolCall>) -> ChatResponse {
  function text_response (line 339) | fn text_response(text: &str) -> ChatResponse {
  function xml_tool_response (line 349) | fn xml_tool_response(name: &str, args: &str) -> ChatResponse {
  function turn_returns_text_when_no_tools_called (line 365) | async fn turn_returns_text_when_no_tools_called() {
  function turn_executes_single_tool_then_returns (line 385) | async fn turn_executes_single_tool_then_returns() {
  function turn_handles_multi_step_tool_chain (line 413) | async fn turn_handles_multi_step_tool_chain() {
  function turn_bails_out_at_max_iterations (line 454) | async fn turn_bails_out_at_max_iterations() {
  function turn_handles_unknown_tool_gracefully (line 489) | async fn turn_handles_unknown_tool_gracefully() {
  function turn_recovers_from_tool_failure (line 529) | async fn turn_recovers_from_tool_failure() {
  function turn_recovers_from_tool_error (line 553) | async fn turn_recovers_from_tool_error() {
  function turn_propagates_provider_error (line 581) | async fn turn_propagates_provider_error() {
  function history_trims_after_max_messages (line 597) | async fn history_trims_after_max_messages() {
  function auto_save_stores_only_user_messages_in_memory (line 635) | async fn auto_save_stores_only_user_messages_in_memory() {
  function auto_save_disabled_does_not_store (line 673) | async fn auto_save_disabled_does_not_store() {
  function xml_dispatcher_parses_and_loops (line 695) | async fn xml_dispatcher_parses_and_loops() {
  function native_dispatcher_sends_tool_specs (line 715) | async fn native_dispatcher_sends_tool_specs() {
  function xml_dispatcher_does_not_send_tool_specs (line 731) | async fn xml_dispatcher_does_not_send_tool_specs() {
  function turn_handles_empty_text_response (line 741) | async fn turn_handles_empty_text_response() {
  function turn_handles_none_text_response (line 756) | async fn turn_handles_none_text_response() {
  function turn_preserves_text_alongside_tool_calls (line 776) | async fn turn_preserves_text_alongside_tool_calls() {
  function turn_handles_multiple_tools_in_one_response (line 816) | async fn turn_handles_multiple_tools_in_one_response() {
  function system_prompt_injected_on_first_turn (line 863) | async fn system_prompt_injected_on_first_turn() {
  function system_prompt_not_duplicated_on_second_turn (line 884) | async fn system_prompt_not_duplicated_on_second_turn() {
  function history_contains_all_expected_entries_after_tool_loop (line 911) | async fn history_contains_all_expected_entries_after_tool_loop() {
  function builder_fails_without_provider (line 959) | async fn builder_fails_without_provider() {
  function multi_turn_maintains_growing_history (line 976) | async fn multi_turn_maintains_growing_history() {
  function native_dispatcher_handles_stringified_arguments (line 1014) | async fn native_dispatcher_handles_stringified_arguments() {
  function xml_dispatcher_handles_nested_json (line 1041) | fn xml_dispatcher_handles_nested_json() {
  function xml_dispatcher_handles_empty_tool_call_tag (line 1065) | fn xml_dispatcher_handles_empty_tool_call_tag() {
  function xml_dispatcher_handles_unclosed_tool_call (line 1080) | fn xml_dispatcher_handles_unclosed_tool_call() {
  function conversation_message_serialization_roundtrip (line 1100) | fn conversation_message_serialization_roundtrip() {
  function xml_format_results_includes_status_and_output (line 1158) | fn xml_format_results_includes_status_and_output() {
  function native_format_results_maps_tool_call_ids (line 1189) | fn native_format_results_maps_tool_call_ids() {
  function xml_dispatcher_converts_history_to_provider_messages (line 1224) | fn xml_dispatcher_converts_history_to_provider_messages() {
  function native_dispatcher_converts_tool_results_to_tool_messages (line 1254) | fn native_dispatcher_converts_tool_results_to_tool_messages() {
  function xml_dispatcher_generates_tool_instructions (line 1278) | fn xml_dispatcher_generates_tool_instructions() {
  function native_dispatcher_returns_empty_instructions (line 1294) | fn native_dispatcher_returns_empty_instructions() {
  function clear_history_resets_conversation (line 1306) | async fn clear_history_resets_conversation() {
  function run_single_delegates_to_turn (line 1333) | async fn run_single_delegates_to_turn() {

FILE: src/approval/mod.rs
  type ApprovalRequest (line 18) | pub struct ApprovalRequest {
  type ApprovalResponse (line 26) | pub enum ApprovalResponse {
  type ApprovalLogEntry (line 37) | pub struct ApprovalLogEntry {
  type ApprovalManager (line 59) | pub struct ApprovalManager {
    method from_config (line 77) | pub fn from_config(config: &AutonomyConfig) -> Self {
    method for_non_interactive (line 93) | pub fn for_non_interactive(config: &AutonomyConfig) -> Self {
    method is_non_interactive (line 106) | pub fn is_non_interactive(&self) -> bool {
    method needs_approval (line 113) | pub fn needs_approval(&self, tool_name: &str) -> bool {
    method record_decision (line 154) | pub fn record_decision(
    method audit_log (line 181) | pub fn audit_log(&self) -> Vec<ApprovalLogEntry> {
    method session_allowlist (line 186) | pub fn session_allowlist(&self) -> HashSet<String> {
    method prompt_cli (line 194) | pub fn prompt_cli(&self, request: &ApprovalRequest) -> ApprovalResponse {
  function prompt_cli_interactive (line 202) | fn prompt_cli_interactive(request: &ApprovalRequest) -> ApprovalResponse {
  function summarize_args (line 224) | fn summarize_args(args: &serde_json::Value) -> String {
  function truncate_for_summary (line 249) | fn truncate_for_summary(input: &str, max_chars: usize) -> String {
  function supervised_config (line 266) | fn supervised_config() -> AutonomyConfig {
  function full_config (line 275) | fn full_config() -> AutonomyConfig {
  function auto_approve_tools_skip_prompt (line 285) | fn auto_approve_tools_skip_prompt() {
  function always_ask_tools_always_prompt (line 292) | fn always_ask_tools_always_prompt() {
  function unknown_tool_needs_approval_in_supervised (line 298) | fn unknown_tool_needs_approval_in_supervised() {
  function full_autonomy_never_prompts (line 305) | fn full_autonomy_never_prompts() {
  function readonly_never_prompts (line 313) | fn readonly_never_prompts() {
  function always_response_adds_to_session_allowlist (line 325) | fn always_response_adds_to_session_allowlist() {
  function always_ask_overrides_session_allowlist (line 341) | fn always_ask_overrides_session_allowlist() {
  function yes_response_does_not_add_to_allowlist (line 357) | fn yes_response_does_not_add_to_allowlist() {
  function audit_log_records_decisions (line 371) | fn audit_log_records_decisions() {
  function audit_log_contains_timestamp_and_channel (line 396) | fn audit_log_contains_timestamp_and_channel() {
  function summarize_args_object (line 414) | fn summarize_args_object() {
  function summarize_args_truncates_long_values (line 422) | fn summarize_args_truncates_long_values() {
  function summarize_args_unicode_safe_truncation (line 431) | fn summarize_args_unicode_safe_truncation() {
  function summarize_args_non_object (line 440) | fn summarize_args_non_object() {
  function non_interactive_manager_reports_non_interactive (line 449) | fn non_interactive_manager_reports_non_interactive() {
  function interactive_manager_reports_interactive (line 455) | fn interactive_manager_reports_interactive() {
  function non_interactive_auto_approve_tools_skip_approval (line 461) | fn non_interactive_auto_approve_tools_skip_approval() {
  function non_interactive_shell_skips_outer_approval_by_default (line 469) | fn non_interactive_shell_skips_outer_approval_by_default() {
  function non_interactive_always_ask_tools_need_approval (line 475) | fn non_interactive_always_ask_tools_need_approval() {
  function non_interactive_unknown_tools_need_approval_in_supervised (line 483) | fn non_interactive_unknown_tools_need_approval_in_supervised() {
  function non_interactive_full_autonomy_never_needs_approval (line 492) | fn non_interactive_full_autonomy_never_needs_approval() {
  function non_interactive_readonly_never_needs_approval (line 501) | fn non_interactive_readonly_never_needs_approval() {
  function non_interactive_session_allowlist_still_works (line 512) | fn non_interactive_session_allowlist_still_works() {
  function non_interactive_always_ask_overrides_session_allowlist (line 529) | fn non_interactive_always_ask_overrides_session_allowlist() {
  function approval_response_serde_roundtrip (line 546) | fn approval_response_serde_roundtrip() {
  function approval_request_serde (line 556) | fn approval_request_serde() {

FILE: src/auth/anthropic_token.rs
  type AnthropicAuthKind (line 6) | pub enum AnthropicAuthKind {
    method as_metadata_value (line 14) | pub fn as_metadata_value(self) -> &'static str {
    method from_metadata_value (line 21) | pub fn from_metadata_value(value: &str) -> Option<Self> {
  function detect_auth_kind (line 31) | pub fn detect_auth_kind(token: &str, explicit: Option<&str>) -> Anthropi...
  function parse_kind_from_metadata (line 57) | fn parse_kind_from_metadata() {
  function detect_prefers_override (line 70) | fn detect_prefers_override() {
  function detect_jwt_like_as_authorization (line 76) | fn detect_jwt_like_as_authorization() {
  function detect_default_for_api_prefix (line 82) | fn detect_default_for_api_prefix() {

FILE: src/auth/gemini_oauth.rs
  function gemini_oauth_client_id (line 27) | pub fn gemini_oauth_client_id() -> Option<String> {
  function gemini_oauth_client_secret (line 35) | pub fn gemini_oauth_client_secret() -> Option<String> {
  function get_oauth_credentials (line 42) | fn get_oauth_credentials() -> Result<(String, String)> {
  constant GOOGLE_OAUTH_AUTHORIZE_URL (line 52) | pub const GOOGLE_OAUTH_AUTHORIZE_URL: &str = "https://accounts.google.co...
  constant GOOGLE_OAUTH_TOKEN_URL (line 53) | pub const GOOGLE_OAUTH_TOKEN_URL: &str = "https://oauth2.googleapis.com/...
  constant GOOGLE_OAUTH_DEVICE_CODE_URL (line 54) | pub const GOOGLE_OAUTH_DEVICE_CODE_URL: &str = "https://oauth2.googleapi...
  constant GEMINI_OAUTH_REDIRECT_URI (line 55) | pub const GEMINI_OAUTH_REDIRECT_URI: &str = "http://localhost:1456/auth/...
  constant GEMINI_OAUTH_SCOPES (line 58) | pub const GEMINI_OAUTH_SCOPES: &str =
  type DeviceCodeStart (line 62) | pub struct DeviceCodeStart {
  type TokenResponse (line 72) | struct TokenResponse {
  type DeviceCodeResponse (line 87) | struct DeviceCodeResponse {
  type OAuthErrorResponse (line 98) | struct OAuthErrorResponse {
  function build_authorize_url (line 104) | pub fn build_authorize_url(pkce: &PkceState) -> Result<String> {
  function exchange_code_for_tokens (line 129) | pub async fn exchange_code_for_tokens(
  function refresh_access_token (line 185) | pub async fn refresh_access_token(client: &Client, refresh_token: &str) ...
  function start_device_code_flow (line 235) | pub async fn start_device_code_flow(client: &Client) -> Result<DeviceCod...
  function poll_device_code_tokens (line 282) | pub async fn poll_device_code_tokens(
  function receive_loopback_code (line 360) | pub async fn receive_loopback_code(expected_state: &str, timeout: Durati...
  function receive_code_from_stdin (line 417) | async fn receive_code_from_stdin(expected_state: &str) -> Result<String> {
  function parse_callback_request (line 437) | fn parse_callback_request(request: &str) -> Result<(String, String)> {
  function parse_code_from_redirect (line 467) | pub fn parse_code_from_redirect(input: &str, expected_state: Option<&str...
  function extract_account_email_from_id_token (line 504) | pub fn extract_account_email_from_id_token(id_token: &str) -> Option<Str...
  type EnvVarRestore (line 527) | struct EnvVarRestore {
    method set (line 533) | fn set(key: &'static str, value: &str) -> Self {
  method drop (line 541) | fn drop(&mut self) {
  function pkce_generates_valid_state (line 551) | fn pkce_generates_valid_state() {
  function authorize_url_contains_required_params (line 559) | fn authorize_url_contains_required_params() {
  function parse_code_from_url (line 575) | fn parse_code_from_url() {
  function parse_code_from_raw (line 582) | fn parse_code_from_raw() {
  function extract_email_from_id_token (line 589) | fn extract_email_from_id_token() {

FILE: src/auth/mod.rs
  constant OPENAI_CODEX_PROVIDER (line 18) | const OPENAI_CODEX_PROVIDER: &str = "openai-codex";
  constant ANTHROPIC_PROVIDER (line 19) | const ANTHROPIC_PROVIDER: &str = "anthropic";
  constant GEMINI_PROVIDER (line 20) | const GEMINI_PROVIDER: &str = "gemini";
  constant DEFAULT_PROFILE_NAME (line 21) | const DEFAULT_PROFILE_NAME: &str = "default";
  constant OPENAI_REFRESH_SKEW_SECS (line 22) | const OPENAI_REFRESH_SKEW_SECS: u64 = 90;
  constant OPENAI_REFRESH_FAILURE_BACKOFF_SECS (line 23) | const OPENAI_REFRESH_FAILURE_BACKOFF_SECS: u64 = 10;
  constant OAUTH_REFRESH_MAX_ATTEMPTS (line 24) | const OAUTH_REFRESH_MAX_ATTEMPTS: usize = 3;
  constant OAUTH_REFRESH_RETRY_BASE_DELAY_MS (line 25) | const OAUTH_REFRESH_RETRY_BASE_DELAY_MS: u64 = 350;
  type AuthService (line 29) | pub struct AuthService {
    method from_config (line 35) | pub fn from_config(config: &Config) -> Self {
    method new (line 40) | pub fn new(state_dir: &Path, encrypt_secrets: bool) -> Self {
    method load_profiles (line 47) | pub async fn load_profiles(&self) -> Result<AuthProfilesData> {
    method store_openai_tokens (line 51) | pub async fn store_openai_tokens(
    method store_gemini_tokens (line 66) | pub async fn store_gemini_tokens(
    method store_provider_token (line 81) | pub async fn store_provider_token(
    method set_active_profile (line 97) | pub async fn set_active_profile(
    method remove_profile (line 125) | pub async fn remove_profile(&self, provider: &str, requested_profile: ...
    method get_profile (line 131) | pub async fn get_profile(
    method get_provider_bearer_token (line 144) | pub async fn get_provider_bearer_token(
    method get_valid_openai_access_token (line 162) | pub async fn get_valid_openai_access_token(
    method get_valid_gemini_access_token (line 252) | pub async fn get_valid_gemini_access_token(
    method get_gemini_profile (line 342) | pub async fn get_gemini_profile(
  function normalize_provider (line 350) | pub fn normalize_provider(provider: &str) -> Result<String> {
  function state_dir_from_config (line 361) | pub fn state_dir_from_config(config: &Config) -> PathBuf {
  function default_profile_id (line 368) | pub fn default_profile_id(provider: &str) -> String {
  function resolve_requested_profile_id (line 372) | fn resolve_requested_profile_id(provider: &str, requested: &str) -> Stri...
  function select_profile_id (line 380) | pub fn select_profile_id(
  function refresh_openai_access_token_with_retries (line 409) | async fn refresh_openai_access_token_with_retries(
  function refresh_gemini_access_token_with_retries (line 441) | async fn refresh_gemini_access_token_with_retries(
  function refresh_lock_for_profile (line 473) | fn refresh_lock_for_profile(profile_id: &str) -> Arc<tokio::sync::Mutex<...
  function refresh_backoff_remaining (line 485) | fn refresh_backoff_remaining(profile_id: &str) -> Option<u64> {
  function set_refresh_backoff (line 497) | fn set_refresh_backoff(profile_id: &str, duration: Duration) {
  function clear_refresh_backoff (line 504) | fn clear_refresh_backoff(profile_id: &str) {
  function normalize_provider_aliases (line 517) | fn normalize_provider_aliases() {
  function select_profile_prefers_override_then_active_then_default (line 524) | fn select_profile_prefers_override_then_active_then_default() {

FILE: src/auth/oauth_common.rs
  type PkceState (line 14) | pub struct PkceState {
  function generate_pkce_state (line 24) | pub fn generate_pkce_state() -> PkceState {
  function random_base64url (line 37) | pub fn random_base64url(byte_len: usize) -> String {
  function url_encode (line 46) | pub fn url_encode(input: &str) -> String {
  function url_decode (line 59) | pub fn url_decode(input: &str) -> String {
  function parse_query_params (line 96) | pub fn parse_query_params(input: &str) -> BTreeMap<String, String> {
  function pkce_generation_is_valid (line 116) | fn pkce_generation_is_valid() {
  function pkce_challenge_is_sha256_of_verifier (line 125) | fn pkce_challenge_is_sha256_of_verifier() {
  function url_encode_basic (line 135) | fn url_encode_basic() {
  function url_decode_basic (line 142) | fn url_decode_basic() {
  function url_encode_decode_roundtrip (line 150) | fn url_encode_decode_roundtrip() {
  function parse_query_params_basic (line 158) | fn parse_query_params_basic() {
  function parse_query_params_encoded (line 165) | fn parse_query_params_encoded() {
  function parse_query_params_empty (line 172) | fn parse_query_params_empty() {
  function random_base64url_length (line 178) | fn random_base64url_length() {

FILE: src/auth/openai_oauth.rs
  constant OPENAI_OAUTH_CLIENT_ID (line 18) | pub const OPENAI_OAUTH_CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
  constant OPENAI_OAUTH_AUTHORIZE_URL (line 19) | pub const OPENAI_OAUTH_AUTHORIZE_URL: &str = "https://auth.openai.com/oa...
  constant OPENAI_OAUTH_TOKEN_URL (line 20) | pub const OPENAI_OAUTH_TOKEN_URL: &str = "https://auth.openai.com/oauth/...
  constant OPENAI_OAUTH_DEVICE_CODE_URL (line 21) | pub const OPENAI_OAUTH_DEVICE_CODE_URL: &str = "https://auth.openai.com/...
  constant OPENAI_OAUTH_REDIRECT_URI (line 22) | pub const OPENAI_OAUTH_REDIRECT_URI: &str = "http://localhost:1455/auth/...
  type DeviceCodeStart (line 25) | pub struct DeviceCodeStart {
  type TokenResponse (line 36) | struct TokenResponse {
  type DeviceCodeResponse (line 51) | struct DeviceCodeResponse {
  type OAuthErrorResponse (line 65) | struct OAuthErrorResponse {
  function build_authorize_url (line 71) | pub fn build_authorize_url(pkce: &PkceState) -> String {
  function exchange_code_for_tokens (line 91) | pub async fn exchange_code_for_tokens(
  function refresh_access_token (line 114) | pub async fn refresh_access_token(client: &Client, refresh_token: &str) ...
  function start_device_code_flow (line 131) | pub async fn start_device_code_flow(client: &Client) -> Result<DeviceCod...
  function poll_device_code_tokens (line 166) | pub async fn poll_device_code_tokens(
  function receive_loopback_code (line 228) | pub async fn receive_loopback_code(expected_state: &str, timeout: Durati...
  function parse_code_from_redirect (line 270) | pub fn parse_code_from_redirect(input: &str, expected_state: Option<&str...
  function extract_account_id_from_jwt (line 317) | pub fn extract_account_id_from_jwt(token: &str) -> Option<String> {
  function parse_token_response (line 341) | async fn parse_token_response(response: reqwest::Response) -> Result<Tok...
  function pkce_generation_is_valid (line 376) | fn pkce_generation_is_valid() {
  function parse_redirect_url_extracts_code (line 384) | fn parse_redirect_url_extracts_code() {
  function parse_redirect_accepts_raw_code (line 394) | fn parse_redirect_accepts_raw_code() {
  function parse_redirect_rejects_state_mismatch (line 400) | fn parse_redirect_rejects_state_mismatch() {
  function parse_redirect_rejects_error_without_code (line 406) | fn parse_redirect_rejects_error_without_code() {
  function extract_account_id_from_jwt_payload (line 418) | fn extract_account_id_from_jwt_payload() {

FILE: src/auth/profiles.rs
  constant CURRENT_SCHEMA_VERSION (line 13) | const CURRENT_SCHEMA_VERSION: u32 = 1;
  constant PROFILES_FILENAME (line 14) | const PROFILES_FILENAME: &str = "auth-profiles.json";
  constant LOCK_FILENAME (line 15) | const LOCK_FILENAME: &str = "auth-profiles.lock";
  constant LOCK_WAIT_MS (line 16) | const LOCK_WAIT_MS: u64 = 50;
  constant LOCK_TIMEOUT_MS (line 17) | const LOCK_TIMEOUT_MS: u64 = 10_000;
  type AuthProfileKind (line 21) | pub enum AuthProfileKind {
  type TokenSet (line 27) | pub struct TokenSet {
    method is_expiring_within (line 42) | pub fn is_expiring_within(&self, skew: Duration) -> bool {
  type AuthProfile (line 55) | pub struct AuthProfile {
    method fmt (line 75) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method new_oauth (line 90) | pub fn new_oauth(provider: &str, profile_name: &str, token_set: TokenS...
    method new_token (line 108) | pub fn new_token(provider: &str, profile_name: &str, token: String) ->...
  type AuthProfilesData (line 128) | pub struct AuthProfilesData {
  method default (line 136) | fn default() -> Self {
  type AuthProfilesStore (line 147) | pub struct AuthProfilesStore {
    method new (line 154) | pub fn new(state_dir: &Path, encrypt_secrets: bool) -> Self {
    method path (line 162) | pub fn path(&self) -> &Path {
    method load (line 166) | pub async fn load(&self) -> Result<AuthProfilesData> {
    method upsert_profile (line 171) | pub async fn upsert_profile(&self, mut profile: AuthProfile, set_activ...
    method remove_profile (line 191) | pub async fn remove_profile(&self, profile_id: &str) -> Result<bool> {
    method set_active_profile (line 207) | pub async fn set_active_profile(&self, provider: &str, profile_id: &st...
    method clear_active_profile (line 221) | pub async fn clear_active_profile(&self, provider: &str) -> Result<()> {
    method update_profile (line 229) | pub async fn update_profile<F>(&self, profile_id: &str, mut updater: F...
    method load_locked (line 249) | async fn load_locked(&self) -> Result<AuthProfilesData> {
    method save_locked (line 327) | async fn save_locked(&self, data: &AuthProfilesData) -> Result<()> {
    method read_persisted_locked (line 376) | async fn read_persisted_locked(&self) -> Result<PersistedAuthProfiles> {
    method write_persisted_locked (line 415) | async fn write_persisted_locked(&self, persisted: &PersistedAuthProfil...
    method encrypt_optional (line 452) | fn encrypt_optional(&self, value: Option<&str>) -> Result<Option<Strin...
    method decrypt_optional (line 459) | fn decrypt_optional(&self, value: Option<&str>) -> Result<(Option<Stri...
    method acquire_lock (line 469) | async fn acquire_lock(&self) -> Result<AuthProfileLockGuard> {
  type AuthProfileLockGuard (line 528) | struct AuthProfileLockGuard {
  method drop (line 533) | fn drop(&mut self) {
  type PersistedAuthProfiles (line 539) | struct PersistedAuthProfiles {
  method default (line 551) | fn default() -> Self {
  type PersistedAuthProfile (line 562) | struct PersistedAuthProfile {
  function default_schema_version (line 592) | fn default_schema_version() -> u32 {
  function default_now_rfc3339 (line 596) | fn default_now_rfc3339() -> String {
  function parse_profile_kind (line 600) | fn parse_profile_kind(value: &str) -> Result<AuthProfileKind> {
  function profile_kind_to_string (line 608) | fn profile_kind_to_string(kind: AuthProfileKind) -> &'static str {
  function parse_optional_datetime (line 615) | fn parse_optional_datetime(value: Option<&str>) -> Result<Option<DateTim...
  function parse_datetime (line 619) | fn parse_datetime(value: &str) -> Result<DateTime<Utc>> {
  function parse_datetime_with_fallback (line 625) | fn parse_datetime_with_fallback(value: &str) -> DateTime<Utc> {
  function profile_id (line 629) | pub fn profile_id(provider: &str, profile_name: &str) -> String {
  function profile_id_format (line 639) | fn profile_id_format() {
  function token_expiry_math (line 647) | fn token_expiry_math() {
  function store_roundtrip_with_encryption (line 662) | async fn store_roundtrip_with_encryption() {
  function atomic_write_replaces_file (line 703) | async fn atomic_write_replaces_file() {

FILE: src/channels/bluesky.rs
  type BlueskyChannel (line 9) | pub struct BlueskyChannel {
    method new (line 103) | pub fn new(handle: String, app_password: String) -> Self {
    method http_client (line 116) | fn http_client(&self) -> reqwest::Client {
    method create_session (line 121) | async fn create_session(&self) -> Result<()> {
    method refresh_session (line 152) | async fn refresh_session(&self) -> Result<()> {
    method get_access_jwt (line 184) | async fn get_access_jwt(&self) -> Result<String> {
    method get_did (line 197) | fn get_did(&self) -> String {
    method parse_notification (line 202) | fn parse_notification(&self, notif: &Notification) -> Option<ChannelMe...
    method update_seen (line 259) | async fn update_seen(&self, seen_at: &str) -> Result<()> {
  type BlueskyAuth (line 15) | struct BlueskyAuth {
  constant BSKY_API_BASE (line 22) | const BSKY_API_BASE: &str = "https://bsky.social/xrpc";
  constant POLL_INTERVAL (line 23) | const POLL_INTERVAL: Duration = Duration::from_secs(5);
  type CreateSessionResponse (line 26) | struct CreateSessionResponse {
  type RefreshSessionResponse (line 35) | struct RefreshSessionResponse {
  type NotificationListResponse (line 43) | struct NotificationListResponse {
  type Notification (line 50) | struct Notification {
  type NotificationAuthor (line 64) | struct NotificationAuthor {
  type CreateRecordRequest (line 73) | struct CreateRecordRequest {
  type PostRecord (line 80) | struct PostRecord {
  type ReplyRef (line 91) | struct ReplyRef {
  type PostRef (line 97) | struct PostRef {
  method name (line 279) | fn name(&self) -> &str {
  method send (line 283) | async fn send(&self, message: &SendMessage) -> Result<()> {
  method listen (line 351) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 419) | async fn health_check(&self) -> bool {
  function make_channel (line 428) | fn make_channel() -> BlueskyChannel {
  function make_notification (line 438) | fn make_notification(
  function parse_mention_notification (line 461) | fn parse_mention_notification() {
  function parse_reply_notification (line 479) | fn parse_reply_notification() {
  function skip_read_notifications (line 495) | fn skip_read_notifications() {
  function skip_own_notifications (line 509) | fn skip_own_notifications() {
  function skip_like_notifications (line 523) | fn skip_like_notifications() {
  function skip_empty_text (line 537) | fn skip_empty_text() {
  function reply_target_encoding (line 545) | fn reply_target_encoding() {
  function send_message_formatting (line 564) | fn send_message_formatting() {

FILE: src/channels/clawdtalk.rs
  type ClawdTalkChannel (line 16) | pub struct ClawdTalkChannel {
    method new (line 59) | pub fn new(config: ClawdTalkConfig) -> Self {
    constant TELNYX_API_URL (line 74) | const TELNYX_API_URL: &'static str = "https://api.telnyx.com/v2";
    method is_destination_allowed (line 77) | fn is_destination_allowed(&self, destination: &str) -> bool {
    method initiate_call (line 87) | pub async fn initiate_call(
    method speak (line 132) | pub async fn speak(&self, call_control_id: &str, text: &str) -> anyhow...
    method hangup (line 163) | pub async fn hangup(&self, call_control_id: &str) -> anyhow::Result<()> {
    method start_ai_conversation (line 184) | pub async fn start_ai_conversation(
  type ClawdTalkConfig (line 33) | pub struct ClawdTalkConfig {
  method name (line 49) | fn name() -> &'static str {
  method desc (line 52) | fn desc() -> &'static str {
  type CallSession (line 223) | pub struct CallSession {
  type CallRequest (line 231) | struct CallRequest {
  type AnsweringMachineDetection (line 244) | struct AnsweringMachineDetection {
  type CallResponse (line 250) | struct CallResponse {
  type SpeakRequest (line 258) | struct SpeakRequest {
  type AiConversationRequest (line 268) | struct AiConversationRequest {
  type VoiceSettings (line 275) | struct VoiceSettings {
  method name (line 282) | fn name(&self) -> &str {
  method send (line 286) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 304) | async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> anyhow::Resu...
  method health_check (line 323) | async fn health_check(&self) -> bool {
  type TelnyxWebhookEvent (line 344) | pub struct TelnyxWebhookEvent {
  type TelnyxWebhookData (line 349) | pub struct TelnyxWebhookData {
  type TelnyxCallPayload (line 355) | pub struct TelnyxCallPayload {
  function test_config (line 369) | fn test_config() -> ClawdTalkConfig {
  function creates_channel (line 380) | fn creates_channel() {
  function destination_allowed_exact_match (line 386) | fn destination_allowed_exact_match() {
  function destination_allowed_wildcard (line 393) | fn destination_allowed_wildcard() {
  function destination_allowed_empty_means_all (line 402) | fn destination_allowed_empty_means_all() {
  function webhook_event_deserializes (line 411) | fn webhook_event_deserializes() {

FILE: src/channels/cli.rs
  type CliChannel (line 7) | pub struct CliChannel;
    method new (line 10) | pub fn new() -> Self {
  method name (line 17) | fn name(&self) -> &str {
  method send (line 21) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 26) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  function cli_channel_name (line 67) | fn cli_channel_name() {
  function cli_channel_send_does_not_panic (line 72) | async fn cli_channel_send_does_not_panic() {
  function cli_channel_send_empty_message (line 86) | async fn cli_channel_send_empty_message() {
  function cli_channel_health_check (line 100) | async fn cli_channel_health_check() {
  function channel_message_struct (line 106) | fn channel_message_struct() {
  function channel_message_clone (line 126) | fn channel_message_clone() {

FILE: src/channels/dingtalk.rs
  constant DINGTALK_BOT_CALLBACK_TOPIC (line 10) | const DINGTALK_BOT_CALLBACK_TOPIC: &str = "/v1.0/im/bot/messages/get";
  type DingTalkChannel (line 14) | pub struct DingTalkChannel {
    method new (line 31) | pub fn new(client_id: String, client_secret: String, allowed_users: Ve...
    method http_client (line 40) | fn http_client(&self) -> reqwest::Client {
    method is_user_allowed (line 44) | fn is_user_allowed(&self, user_id: &str) -> bool {
    method parse_stream_data (line 48) | fn parse_stream_data(frame: &serde_json::Value) -> Option<serde_json::...
    method resolve_chat_id (line 56) | fn resolve_chat_id(data: &serde_json::Value, sender_id: &str) -> String {
    method register_connection (line 78) | async fn register_connection(&self) -> anyhow::Result<GatewayResponse> {
  type GatewayResponse (line 25) | struct GatewayResponse {
  method name (line 110) | fn name(&self) -> &str {
  method send (line 114) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 149) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 293) | async fn health_check(&self) -> bool {
  function test_name (line 303) | fn test_name() {
  function test_user_allowed_wildcard (line 309) | fn test_user_allowed_wildcard() {
  function test_user_allowed_specific (line 315) | fn test_user_allowed_specific() {
  function test_user_denied_empty (line 322) | fn test_user_denied_empty() {
  function test_config_serde (line 328) | fn test_config_serde() {
  function test_config_serde_defaults (line 341) | fn test_config_serde_defaults() {
  function parse_stream_data_supports_string_payload (line 351) | fn parse_stream_data_supports_string_payload() {
  function parse_stream_data_supports_object_payload (line 363) | fn parse_stream_data_supports_object_payload() {
  function resolve_chat_id_handles_numeric_group_conversation_type (line 375) | fn resolve_chat_id_handles_numeric_group_conversation_type() {

FILE: src/channels/discord.rs
  type DiscordChannel (line 14) | pub struct DiscordChannel {
    method new (line 24) | pub fn new(
    method http_client (line 41) | fn http_client(&self) -> reqwest::Client {
    method is_user_allowed (line 48) | fn is_user_allowed(&self, user_id: &str) -> bool {
    method bot_user_id_from_token (line 52) | fn bot_user_id_from_token(token: &str) -> Option<String> {
  function process_attachments (line 64) | async fn process_attachments(
  type DiscordAttachmentKind (line 108) | enum DiscordAttachmentKind {
    method from_marker (line 117) | fn from_marker(kind: &str) -> Option<Self> {
    method marker_name (line 128) | fn marker_name(&self) -> &'static str {
  type DiscordAttachment (line 140) | struct DiscordAttachment {
  function parse_attachment_markers (line 145) | fn parse_attachment_markers(message: &str) -> (String, Vec<DiscordAttach...
  function classify_outgoing_attachments (line 190) | fn classify_outgoing_attachments(
  function with_inline_attachment_urls (line 216) | fn with_inline_attachment_urls(
  function send_discord_message_json (line 234) | async fn send_discord_message_json(
  function send_discord_message_with_files (line 262) | async fn send_discord_message_with_files(
  constant BASE64_ALPHABET (line 310) | const BASE64_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm...
  constant DISCORD_MAX_MESSAGE_LENGTH (line 315) | const DISCORD_MAX_MESSAGE_LENGTH: usize = 2000;
  constant DISCORD_ACK_REACTIONS (line 316) | const DISCORD_ACK_REACTIONS: &[&str] = &["⚡️", "🦀", "🙌", "💪", "👌", "👀", ...
  function split_message_for_discord (line 320) | fn split_message_for_discord(message: &str) -> Vec<String> {
  function pick_uniform_index (line 366) | fn pick_uniform_index(len: usize) -> usize {
  function random_discord_ack_reaction (line 380) | fn random_discord_ack_reaction() -> &'static str {
  function encode_emoji_for_discord (line 389) | fn encode_emoji_for_discord(emoji: &str) -> String {
  function discord_reaction_url (line 401) | fn discord_reaction_url(channel_id: &str, message_id: &str, emoji: &str)...
  function mention_tags (line 409) | fn mention_tags(bot_user_id: &str) -> [String; 2] {
  function contains_bot_mention (line 413) | fn contains_bot_mention(content: &str, bot_user_id: &str) -> bool {
  function normalize_incoming_content (line 418) | fn normalize_incoming_content(
  function base64_decode (line 448) | fn base64_decode(input: &str) -> Option<String> {
  method name (line 486) | fn name(&self) -> &str {
  method send (line 490) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 541) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 805) | async fn health_check(&self) -> bool {
  method start_typing (line 815) | async fn start_typing(&self, recipient: &str) -> anyhow::Result<()> {
  method stop_typing (line 840) | async fn stop_typing(&self, recipient: &str) -> anyhow::Result<()> {
  method add_reaction (line 848) | async fn add_reaction(
  method remove_reaction (line 876) | async fn remove_reaction(
  function discord_channel_name (line 909) | fn discord_channel_name() {
  function base64_decode_bot_id (line 915) | fn base64_decode_bot_id() {
  function bot_user_id_extraction (line 922) | fn bot_user_id_extraction() {
  function empty_allowlist_denies_everyone (line 930) | fn empty_allowlist_denies_everyone() {
  function wildcard_allows_everyone (line 937) | fn wildcard_allows_everyone() {
  function specific_allowlist_filters (line 944) | fn specific_allowlist_filters() {
  function allowlist_is_exact_match_not_substring (line 959) | fn allowlist_is_exact_match_not_substring() {
  function allowlist_empty_string_user_id (line 967) | fn allowlist_empty_string_user_id() {
  function allowlist_with_wildcard_and_specific (line 973) | fn allowlist_with_wildcard_and_specific() {
  function allowlist_case_sensitive (line 986) | fn allowlist_case_sensitive() {
  function base64_decode_empty_string (line 994) | fn base64_decode_empty_string() {
  function base64_decode_invalid_chars (line 1000) | fn base64_decode_invalid_chars() {
  function bot_user_id_from_empty_token (line 1006) | fn bot_user_id_from_empty_token() {
  function contains_bot_mention_supports_plain_and_nick_forms (line 1012) | fn contains_bot_mention_supports_plain_and_nick_forms() {
  function normalize_incoming_content_requires_mention_when_enabled (line 1019) | fn normalize_incoming_content_requires_mention_when_enabled() {
  function normalize_incoming_content_strips_mentions_and_trims (line 1025) | fn normalize_incoming_content_strips_mentions_and_trims() {
  function normalize_incoming_content_rejects_empty_after_strip (line 1031) | fn normalize_incoming_content_rejects_empty_after_strip() {
  function mention_only_dm_bypasses_mention_gate (line 1039) | fn mention_only_dm_bypasses_mention_gate() {
  function mention_only_guild_message_without_mention_is_rejected (line 1050) | fn mention_only_guild_message_without_mention_is_rejected() {
  function mention_only_guild_message_with_mention_passes_and_strips (line 1061) | fn mention_only_guild_message_with_mention_passes_and_strips() {
  function split_empty_message (line 1074) | fn split_empty_message() {
  function split_short_message_under_limit (line 1080) | fn split_short_message_under_limit() {
  function split_message_exactly_2000_chars (line 1087) | fn split_message_exactly_2000_chars() {
  function split_message_just_over_limit (line 1095) | fn split_message_just_over_limit() {
  function split_very_long_message (line 1104) | fn split_very_long_message() {
  function split_prefer_newline_break (line 1118) | fn split_prefer_newline_break() {
  function split_prefer_space_break (line 1128) | fn split_prefer_space_break() {
  function split_without_good_break_points_hard_split (line 1135) | fn split_without_good_break_points_hard_split() {
  function split_multiple_breaks (line 1146) | fn split_multiple_breaks() {
  function split_preserves_content (line 1160) | fn split_preserves_content() {
  function split_unicode_content (line 1168) | fn split_unicode_content() {
  function split_newline_too_close_to_end (line 1183) | fn split_newline_too_close_to_end() {
  function split_multibyte_only_content_without_panics (line 1192) | fn split_multibyte_only_content_without_panics() {
  function split_chunks_always_within_discord_limit (line 1203) | fn split_chunks_always_within_discord_limit() {
  function split_message_with_multiple_newlines (line 1212) | fn split_message_with_multiple_newlines() {
  function typing_handles_start_empty (line 1221) | fn typing_handles_start_empty() {
  function start_typing_sets_handle (line 1228) | async fn start_typing_sets_handle() {
  function stop_typing_clears_handle (line 1236) | async fn stop_typing_clears_handle() {
  function stop_typing_is_idempotent (line 1245) | async fn stop_typing_is_idempotent() {
  function concurrent_typing_handles_are_independent (line 1252) | async fn concurrent_typing_handles_are_independent() {
  function encode_emoji_unicode_percent_encodes (line 1272) | fn encode_emoji_unicode_percent_encodes() {
  function encode_emoji_checkmark (line 1278) | fn encode_emoji_checkmark() {
  function encode_emoji_custom_guild_emoji_passthrough (line 1284) | fn encode_emoji_custom_guild_emoji_passthrough() {
  function encode_emoji_simple_ascii_char (line 1290) | fn encode_emoji_simple_ascii_char() {
  function random_discord_ack_reaction_is_from_pool (line 1296) | fn random_discord_ack_reaction_is_from_pool() {
  function discord_reaction_url_encodes_emoji_and_strips_prefix (line 1304) | fn discord_reaction_url_encodes_emoji_and_strips_prefix() {
  function discord_message_id_format_includes_discord_prefix (line 1315) | fn discord_message_id_format_includes_discord_prefix() {
  function discord_message_id_is_deterministic (line 1323) | fn discord_message_id_is_deterministic() {
  function discord_message_id_different_message_different_id (line 1332) | fn discord_message_id_different_message_different_id() {
  function discord_message_id_uses_snowflake_id (line 1340) | fn discord_message_id_uses_snowflake_id() {
  function discord_message_id_fallback_to_uuid_on_empty (line 1350) | fn discord_message_id_fallback_to_uuid_on_empty() {
  function split_message_code_block_at_boundary (line 1369) | fn split_message_code_block_at_boundary() {
  function split_message_single_long_word_exceeds_limit (line 1390) | fn split_message_single_long_word_exceeds_limit() {
  function split_message_exactly_at_limit_no_split (line 1408) | fn split_message_exactly_at_limit_no_split() {
  function split_message_one_over_limit_splits (line 1416) | fn split_message_one_over_limit_splits() {
  function split_message_many_short_lines (line 1423) | fn split_message_many_short_lines() {
  function split_message_only_whitespace (line 1442) | fn split_message_only_whitespace() {
  function split_message_emoji_at_boundary (line 1450) | fn split_message_emoji_at_boundary() {
  function split_message_consecutive_newlines_at_boundary (line 1465) | fn split_message_consecutive_newlines_at_boundary() {
  function process_attachments_empty_list_returns_empty (line 1478) | async fn process_attachments_empty_list_returns_empty() {
  function process_attachments_skips_unsupported_types (line 1485) | async fn process_attachments_skips_unsupported_types() {
  function parse_attachment_markers_extracts_supported_markers (line 1497) | fn parse_attachment_markers_extracts_supported_markers() {
  function parse_attachment_markers_keeps_invalid_marker_text (line 1510) | fn parse_attachment_markers_keeps_invalid_marker_text() {
  function classify_outgoing_attachments_splits_local_remote_and_unresolved (line 1519) | fn classify_outgoing_attachments_splits_local_remote_and_unresolved() {
  function with_inline_attachment_urls_appends_urls_and_unresolved_markers (line 1550) | fn with_inline_attachment_urls_appends_urls_and_unresolved_markers() {

FILE: src/channels/email_channel.rs
  type EmailConfig (line 40) | pub struct EmailConfig {
    method name (line 76) | fn name() -> &'static str {
    method desc (line 79) | fn desc() -> &'static str {
  function default_imap_port (line 84) | fn default_imap_port() -> u16 {
  function default_smtp_port (line 87) | fn default_smtp_port() -> u16 {
  function default_imap_folder (line 90) | fn default_imap_folder() -> String {
  function default_idle_timeout (line 93) | fn default_idle_timeout() -> u64 {
  function default_true (line 96) | fn default_true() -> bool {
  function default_subject (line 99) | fn default_subject() -> String {
  method default (line 104) | fn default() -> Self {
  type ImapSession (line 122) | type ImapSession = Session<TlsStream<TcpStream>>;
  type EmailChannel (line 125) | pub struct EmailChannel {
    method new (line 131) | pub fn new(config: EmailConfig) -> Self {
    method is_sender_allowed (line 139) | pub fn is_sender_allowed(&self, email: &str) -> bool {
    method strip_html (line 162) | pub fn strip_html(html: &str) -> String {
    method extract_sender (line 184) | fn extract_sender(parsed: &mail_parser::Message) -> String {
    method extract_text (line 194) | fn extract_text(parsed: &mail_parser::Message) -> String {
    method connect_imap (line 216) | async fn connect_imap(&self) -> Result<ImapSession> {
    method fetch_unseen (line 248) | async fn fetch_unseen(&self, session: &mut ImapSession) -> Result<Vec<...
    method wait_for_changes (line 331) | async fn wait_for_changes(
    method listen_with_idle (line 376) | async fn listen_with_idle(&self, tx: mpsc::Sender<ChannelMessage>) -> ...
    method run_idle_session (line 400) | async fn run_idle_session(&self, tx: &mpsc::Sender<ChannelMessage>) ->...
    method process_unseen (line 440) | async fn process_unseen(
    method create_smtp_transport (line 482) | fn create_smtp_transport(&self) -> Result<SmtpTransport> {
  type ParsedEmail (line 500) | struct ParsedEmail {
  type IdleWaitResult (line 509) | enum IdleWaitResult {
  method name (line 517) | fn name(&self) -> &str {
  method send (line 521) | async fn send(&self, message: &SendMessage) -> Result<()> {
  method listen (line 548) | async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> Result<()> {
  method health_check (line 556) | async fn health_check(&self) -> bool {
  function default_smtp_port_uses_tls_port (line 581) | fn default_smtp_port_uses_tls_port() {
  function email_config_default_uses_tls_smtp_defaults (line 586) | fn email_config_default_uses_tls_smtp_defaults() {
  function default_idle_timeout_is_29_minutes (line 593) | fn default_idle_timeout_is_29_minutes() {
  function seen_messages_starts_empty (line 598) | async fn seen_messages_starts_empty() {
  function seen_messages_tracks_unique_ids (line 605) | async fn seen_messages_tracks_unique_ids() {
  function email_config_default (line 618) | fn email_config_default() {
  function email_config_custom (line 634) | fn email_config_custom() {
  function email_config_clone (line 656) | fn email_config_clone() {
  function email_channel_new (line 681) | async fn email_channel_new() {
  function email_channel_name (line 691) | fn email_channel_name() {
  function is_sender_allowed_empty_list_denies_all (line 699) | fn is_sender_allowed_empty_list_denies_all() {
  function is_sender_allowed_wildcard_allows_all (line 710) | fn is_sender_allowed_wildcard_allows_all() {
  function is_sender_allowed_specific_email (line 722) | fn is_sender_allowed_specific_email() {
  function is_sender_allowed_domain_with_at_prefix (line 734) | fn is_sender_allowed_domain_with_at_prefix() {
  function is_sender_allowed_domain_without_at_prefix (line 746) | fn is_sender_allowed_domain_without_at_prefix() {
  function is_sender_allowed_case_insensitive (line 758) | fn is_sender_allowed_case_insensitive() {
  function is_sender_allowed_multiple_senders (line 770) | fn is_sender_allowed_multiple_senders() {
  function is_sender_allowed_wildcard_with_specific (line 787) | fn is_sender_allowed_wildcard_with_specific() {
  function is_sender_allowed_empty_sender (line 798) | fn is_sender_allowed_empty_sender() {
  function strip_html_basic (line 812) | fn strip_html_basic() {
  function strip_html_nested_tags (line 818) | fn strip_html_nested_tags() {
  function strip_html_multiple_lines (line 826) | fn strip_html_multiple_lines() {
  function strip_html_preserves_text (line 832) | fn strip_html_preserves_text() {
  function strip_html_handles_malformed (line 838) | fn strip_html_handles_malformed() {
  function strip_html_self_closing_tags (line 848) | fn strip_html_self_closing_tags() {
  function strip_html_attributes_preserved (line 855) | fn strip_html_attributes_preserved() {
  function strip_html_multiple_spaces_collapsed (line 863) | fn strip_html_multiple_spaces_collapsed() {
  function strip_html_special_characters (line 871) | fn strip_html_special_characters() {
  function default_imap_port_returns_993 (line 881) | fn default_imap_port_returns_993() {
  function default_smtp_port_returns_465 (line 886) | fn default_smtp_port_returns_465() {
  function default_imap_folder_returns_inbox (line 891) | fn default_imap_folder_returns_inbox() {
  function default_true_returns_true (line 896) | fn default_true_returns_true() {
  function email_config_serialize_deserialize (line 903) | fn email_config_serialize_deserialize() {
  function email_config_deserialize_with_defaults (line 929) | fn email_config_deserialize_with_defaults() {
  function idle_timeout_deserializes_explicit_value (line 947) | fn idle_timeout_deserializes_explicit_value() {
  function idle_timeout_deserializes_legacy_poll_interval_alias (line 961) | fn idle_timeout_deserializes_legacy_poll_interval_alias() {
  function idle_timeout_propagates_to_channel (line 975) | fn idle_timeout_propagates_to_channel() {
  function email_config_debug_output (line 985) | fn email_config_debug_output() {

FILE: src/channels/imessage.rs
  function extract_text_from_attributed_body (line 17) | fn extract_text_from_attributed_body(blob: &[u8]) -> Option<String> {
  function resolve_message_content (line 58) | fn resolve_message_content(rowid: i64, text: Option<String>, body: Optio...
  type IMessageChannel (line 73) | pub struct IMessageChannel {
    method new (line 79) | pub fn new(allowed_contacts: Vec<String>) -> Self {
    method is_contact_allowed (line 86) | fn is_contact_allowed(&self, sender: &str) -> bool {
  function escape_applescript (line 102) | fn escape_applescript(s: &str) -> String {
  function is_valid_imessage_target (line 117) | fn is_valid_imessage_target(target: &str) -> bool {
  method name (line 156) | fn name(&self) -> &str {
  method send (line 160) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 195) | async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> anyhow::Resu...
  method health_check (line 312) | async fn health_check(&self) -> bool {
  function get_max_rowid (line 327) | async fn get_max_rowid(db_path: &Path) -> anyhow::Result<i64> {
  function fetch_new_messages (line 345) | async fn fetch_new_messages(
  function creates_with_contacts (line 389) | fn creates_with_contacts() {
  function creates_with_empty_contacts (line 396) | fn creates_with_empty_contacts() {
  function wildcard_allows_anyone (line 402) | fn wildcard_allows_anyone() {
  function specific_contact_allowed (line 410) | fn specific_contact_allowed() {
  function unknown_contact_denied (line 417) | fn unknown_contact_denied() {
  function contact_case_insensitive (line 424) | fn contact_case_insensitive() {
  function empty_allowlist_denies_all (line 431) | fn empty_allowlist_denies_all() {
  function name_returns_imessage (line 438) | fn name_returns_imessage() {
  function wildcard_among_others_still_allows_all (line 444) | fn wildcard_among_others_still_allows_all() {
  function contact_with_spaces_exact_match (line 450) | fn contact_with_spaces_exact_match() {
  function escape_applescript_double_quotes (line 461) | fn escape_applescript_double_quotes() {
  function escape_applescript_backslashes (line 466) | fn escape_applescript_backslashes() {
  function escape_applescript_mixed (line 471) | fn escape_applescript_mixed() {
  function escape_applescript_injection_attempt (line 479) | fn escape_applescript_injection_attempt() {
  function escape_applescript_empty_string (line 500) | fn escape_applescript_empty_string() {
  function escape_applescript_no_special_chars (line 505) | fn escape_applescript_no_special_chars() {
  function escape_applescript_unicode (line 510) | fn escape_applescript_unicode() {
  function escape_applescript_newlines_escaped (line 515) | fn escape_applescript_newlines_escaped() {
  function valid_phone_number_simple (line 526) | fn valid_phone_number_simple() {
  function valid_phone_number_with_country_code (line 531) | fn valid_phone_number_with_country_code() {
  function valid_phone_number_with_spaces (line 536) | fn valid_phone_number_with_spaces() {
  function valid_phone_number_with_dashes (line 541) | fn valid_phone_number_with_dashes() {
  function valid_phone_number_international (line 546) | fn valid_phone_number_international() {
  function valid_email_simple (line 552) | fn valid_email_simple() {
  function valid_email_with_subdomain (line 557) | fn valid_email_with_subdomain() {
  function valid_email_with_plus (line 562) | fn valid_email_with_plus() {
  function valid_email_with_dots (line 567) | fn valid_email_with_dots() {
  function valid_email_icloud (line 572) | fn valid_email_icloud() {
  function invalid_target_empty (line 578) | fn invalid_target_empty() {
  function invalid_target_no_plus_prefix (line 584) | fn invalid_target_no_plus_prefix() {
  function invalid_target_too_short_phone (line 590) | fn invalid_target_too_short_phone() {
  function invalid_target_too_long_phone (line 596) | fn invalid_target_too_long_phone() {
  function invalid_target_email_no_at (line 602) | fn invalid_target_email_no_at() {
  function invalid_target_email_no_domain (line 607) | fn invalid_target_email_no_domain() {
  function invalid_target_email_no_local (line 612) | fn invalid_target_email_no_local() {
  function invalid_target_email_no_dot_in_domain (line 617) | fn invalid_target_email_no_dot_in_domain() {
  function invalid_target_injection_attempt (line 622) | fn invalid_target_injection_attempt() {
  function invalid_target_applescript_injection (line 628) | fn invalid_target_applescript_injection() {
  function invalid_target_special_chars (line 636) | fn invalid_target_special_chars() {
  function invalid_target_null_byte (line 642) | fn invalid_target_null_byte() {
  function invalid_target_newline (line 647) | fn invalid_target_newline() {
  function target_with_leading_trailing_whitespace_trimmed (line 652) | fn target_with_leading_trailing_whitespace_trimmed() {
  function create_test_db (line 663) | fn create_test_db() -> (tempfile::TempDir, std::path::PathBuf) {
  function get_max_rowid_empty_database (line 690) | async fn get_max_rowid_empty_database() {
  function get_max_rowid_with_messages (line 699) | async fn get_max_rowid_with_messages() {
  function get_max_rowid_nonexistent_database (line 731) | async fn get_max_rowid_nonexistent_database() {
  function fetch_new_messages_empty_database (line 738) | async fn fetch_new_messages_empty_database() {
  function fetch_new_messages_returns_correct_data (line 746) | async fn fetch_new_messages_returns_correct_data() {
  function fetch_new_messages_filters_by_rowid (line 789) | async fn fetch_new_messages_filters_by_rowid() {
  function fetch_new_messages_excludes_sent_messages (line 818) | async fn fetch_new_messages_excludes_sent_messages() {
  function fetch_new_messages_excludes_null_text_and_null_body (line 845) | async fn fetch_new_messages_excludes_null_text_and_null_body() {
  function fetch_new_messages_respects_limit (line 874) | async fn fetch_new_messages_respects_limit() {
  function fetch_new_messages_ordered_by_rowid_asc (line 900) | async fn fetch_new_messages_ordered_by_rowid_asc() {
  function fetch_new_messages_nonexistent_database (line 933) | async fn fetch_new_messages_nonexistent_database() {
  function fetch_new_messages_handles_special_characters (line 940) | async fn fetch_new_messages_handles_special_characters() {
  function fetch_new_messages_handles_unicode (line 964) | async fn fetch_new_messages_handles_unicode() {
  function fetch_new_messages_filters_empty_text (line 986) | async fn fetch_new_messages_filters_empty_text() {
  function fetch_new_messages_negative_rowid_edge_case (line 1009) | async fn fetch_new_messages_negative_rowid_edge_case() {
  function fetch_new_messages_large_rowid_edge_case (line 1031) | async fn fetch_new_messages_large_rowid_edge_case() {
  function make_attributed_body (line 1058) | fn make_attributed_body(text: &str) -> Vec<u8> {
  constant REAL_BLOB_TESTING (line 1091) | const REAL_BLOB_TESTING: &[u8] = &[
  constant REAL_BLOB_ONE (line 1110) | const REAL_BLOB_ONE: &[u8] = &[
  function extract_real_blob_testing_with_imsg (line 1126) | fn extract_real_blob_testing_with_imsg() {
  function extract_real_blob_single_char (line 1132) | fn extract_real_blob_single_char() {
  function extract_text_containing_end_marker_bytes (line 1139) | fn extract_text_containing_end_marker_bytes() {
  function extract_zero_length_returns_empty_string (line 1150) | fn extract_zero_length_returns_empty_string() {
  function extract_no_markers_returns_none (line 1160) | fn extract_no_markers_returns_none() {
  function extract_invalid_utf8_returns_none (line 1167) | fn extract_invalid_utf8_returns_none() {
  function extract_truncated_blob_returns_none (line 1174) | fn extract_truncated_blob_returns_none() {
  function extract_long_text_two_byte_length (line 1182) | fn extract_long_text_two_byte_length() {
  function extract_four_byte_length_prefix (line 1191) | fn extract_four_byte_length_prefix() {
  function extract_text_boundary_127_to_128 (line 1205) | fn extract_text_boundary_127_to_128() {
  function fetch_new_messages_reads_attributed_body_fallback (line 1216) | async fn fetch_new_messages_reads_attributed_body_fallback() {
  function fetch_new_messages_empty_text_falls_back_to_attributed_body (line 1239) | async fn fetch_new_messages_empty_text_falls_back_to_attributed_body() {
  function fetch_new_messages_prefers_text_over_attributed_body (line 1262) | async fn fetch_new_messages_prefers_text_over_attributed_body() {
  function fetch_new_messages_mixed_text_and_attributed_body (line 1285) | async fn fetch_new_messages_mixed_text_and_attributed_body() {

FILE: src/channels/irc.rs
  constant READ_TIMEOUT (line 13) | const READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs...
  type IrcChannel (line 23) | pub struct IrcChannel {
    method new (line 243) | pub fn new(cfg: IrcChannelConfig) -> Self {
    method is_user_allowed (line 260) | fn is_user_allowed(&self, nick: &str) -> bool {
    method connect (line 270) | async fn connect(
    method send_raw (line 297) | async fn send_raw(writer: &mut WriteHalf, line: &str) -> anyhow::Resul...
  type WriteHalf (line 38) | type WriteHalf = tokio::io::WriteHalf<tokio_rustls::client::TlsStream<to...
  constant IRC_STYLE_PREFIX (line 42) | const IRC_STYLE_PREFIX: &str = "\
  constant SENDER_PREFIX_RESERVE (line 50) | const SENDER_PREFIX_RESERVE: usize = 64;
  type IrcMessage (line 54) | struct IrcMessage {
    method parse (line 64) | fn parse(line: &str) -> Option<Self> {
    method nick (line 103) | fn nick(&self) -> Option<&str> {
  function encode_sasl_plain (line 117) | fn encode_sasl_plain(nick: &str, password: &str) -> String {
  function split_message (line 161) | fn split_message(message: &str, max_bytes: usize) -> Vec<String> {
  type IrcChannelConfig (line 229) | pub struct IrcChannelConfig {
  type NoVerify (line 307) | struct NoVerify;
    method verify_server_cert (line 310) | fn verify_server_cert(
    method verify_tls12_signature (line 321) | fn verify_tls12_signature(
    method verify_tls13_signature (line 330) | fn verify_tls13_signature(
    method supported_verify_schemes (line 339) | fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
  method name (line 349) | fn name(&self) -> &str {
  method send (line 353) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 372) | async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> anyhow::Resu...
  method health_check (line 601) | async fn health_check(&self) -> bool {
  function parse_privmsg_with_prefix (line 621) | fn parse_privmsg_with_prefix() {
  function parse_privmsg_dm (line 629) | fn parse_privmsg_dm() {
  function parse_ping (line 637) | fn parse_ping() {
  function parse_numeric_reply (line 645) | fn parse_numeric_reply() {
  function parse_no_trailing (line 653) | fn parse_no_trailing() {
  function parse_cap_ack (line 660) | fn parse_cap_ack() {
  function parse_empty_line_returns_none (line 667) | fn parse_empty_line_returns_none() {
  function parse_strips_crlf (line 673) | fn parse_strips_crlf() {
  function parse_command_uppercase (line 679) | fn parse_command_uppercase() {
  function nick_extraction_full_prefix (line 685) | fn nick_extraction_full_prefix() {
  function nick_extraction_nick_only (line 691) | fn nick_extraction_nick_only() {
  function nick_extraction_no_prefix (line 697) | fn nick_extraction_no_prefix() {
  function parse_authenticate_plus (line 703) | fn parse_authenticate_plus() {
  function sasl_plain_encode (line 712) | fn sasl_plain_encode() {
  function sasl_plain_empty_password (line 719) | fn sasl_plain_empty_password() {
  function split_short_message (line 728) | fn split_short_message() {
  function split_long_message (line 734) | fn split_long_message() {
  function split_exact_boundary (line 743) | fn split_exact_boundary() {
  function split_unicode_safe (line 750) | fn split_unicode_safe() {
  function split_empty_message (line 762) | fn split_empty_message() {
  function split_newlines_into_separate_lines (line 768) | fn split_newlines_into_separate_lines() {
  function split_crlf_newlines (line 774) | fn split_crlf_newlines() {
  function split_skips_empty_lines (line 780) | fn split_skips_empty_lines() {
  function split_trailing_newline (line 786) | fn split_trailing_newline() {
  function split_multiline_with_long_line (line 792) | fn split_multiline_with_long_line() {
  function split_only_newlines (line 804) | fn split_only_newlines() {
  function wildcard_allows_anyone (line 812) | fn wildcard_allows_anyone() {
  function specific_user_allowed (line 820) | fn specific_user_allowed() {
  function allowlist_case_insensitive (line 839) | fn allowlist_case_insensitive() {
  function empty_allowlist_denies_all (line 858) | fn empty_allowlist_denies_all() {
  function new_defaults_username_to_nickname (line 877) | fn new_defaults_username_to_nickname() {
  function new_uses_explicit_username (line 894) | fn new_uses_explicit_username() {
  function name_returns_irc (line 912) | fn name_returns_irc() {
  function new_stores_all_fields (line 918) | fn new_stores_all_fields() {
  function irc_config_serde_roundtrip (line 946) | fn irc_config_serde_roundtrip() {
  function irc_config_minimal_toml (line 977) | fn irc_config_minimal_toml() {
  function irc_config_default_port (line 998) | fn irc_config_default_port() {
  function make_channel (line 1008) | fn make_channel() -> IrcChannel {

FILE: src/channels/lark.rs
  constant FEISHU_BASE_URL (line 12) | const FEISHU_BASE_URL: &str = "https://open.feishu.cn/open-apis";
  constant FEISHU_WS_BASE_URL (line 13) | const FEISHU_WS_BASE_URL: &str = "https://open.feishu.cn";
  constant LARK_BASE_URL (line 14) | const LARK_BASE_URL: &str = "https://open.larksuite.com/open-apis";
  constant LARK_WS_BASE_URL (line 15) | const LARK_WS_BASE_URL: &str = "https://open.larksuite.com";
  constant LARK_ACK_REACTIONS_ZH_CN (line 17) | const LARK_ACK_REACTIONS_ZH_CN: &[&str] = &[
  constant LARK_ACK_REACTIONS_ZH_TW (line 20) | const LARK_ACK_REACTIONS_ZH_TW: &[&str] = &[
  constant LARK_ACK_REACTIONS_EN (line 29) | const LARK_ACK_REACTIONS_EN: &[&str] = &[
  constant LARK_ACK_REACTIONS_JA (line 39) | const LARK_ACK_REACTIONS_JA: &[&str] = &[
  type LarkAckLocale (line 51) | enum LarkAckLocale {
  type LarkPlatform (line 59) | enum LarkPlatform {
    method api_base (line 65) | fn api_base(self) -> &'static str {
    method ws_base (line 72) | fn ws_base(self) -> &'static str {
    method locale_header (line 79) | fn locale_header(self) -> &'static str {
    method proxy_service_key (line 86) | fn proxy_service_key(self) -> &'static str {
    method channel_name (line 93) | fn channel_name(self) -> &'static str {
  type PbHeader (line 106) | struct PbHeader {
  type PbFrame (line 116) | struct PbFrame {
    method header_value (line 132) | fn header_value<'a>(&'a self, key: &str) -> &'a str {
  type WsClientConfig (line 143) | struct WsClientConfig {
  type WsEndpointResp (line 150) | struct WsEndpointResp {
  type WsEndpoint (line 159) | struct WsEndpoint {
  type LarkEvent (line 168) | struct LarkEvent {
  type LarkEventHeader (line 174) | struct LarkEventHeader {
  type MsgReceivePayload (line 181) | struct MsgReceivePayload {
  type LarkSender (line 187) | struct LarkSender {
  type LarkSenderId (line 194) | struct LarkSenderId {
  type LarkMessage (line 199) | struct LarkMessage {
  constant WS_HEARTBEAT_TIMEOUT (line 212) | const WS_HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(300);
  constant LARK_TOKEN_REFRESH_SKEW (line 214) | const LARK_TOKEN_REFRESH_SKEW: Duration = Duration::from_secs(120);
  constant LARK_DEFAULT_TOKEN_TTL (line 216) | const LARK_DEFAULT_TOKEN_TTL: Duration = Duration::from_secs(7200);
  constant LARK_INVALID_ACCESS_TOKEN_CODE (line 218) | const LARK_INVALID_ACCESS_TOKEN_CODE: i64 = 99_991_663;
  function should_refresh_last_recv (line 222) | fn should_refresh_last_recv(msg: &WsMsg) -> bool {
  type CachedTenantToken (line 227) | struct CachedTenantToken {
  function extract_lark_response_code (line 232) | fn extract_lark_response_code(body: &serde_json::Value) -> Option<i64> {
  function is_lark_invalid_access_token (line 236) | fn is_lark_invalid_access_token(body: &serde_json::Value) -> bool {
  function should_refresh_lark_tenant_token (line 240) | fn should_refresh_lark_tenant_token(status: reqwest::StatusCode, body: &...
  function extract_lark_token_ttl_seconds (line 244) | fn extract_lark_token_ttl_seconds(body: &serde_json::Value) -> u64 {
  function next_token_refresh_deadline (line 259) | fn next_token_refresh_deadline(now: Instant, ttl_seconds: u64) -> Instant {
  function ensure_lark_send_success (line 267) | fn ensure_lark_send_success(
  type LarkChannel (line 290) | pub struct LarkChannel {
    method new (line 310) | pub fn new(
    method new_with_platform (line 329) | fn new_with_platform(
    method from_config (line 355) | pub fn from_config(config: &crate::config::schema::LarkConfig) -> Self {
    method from_lark_config (line 377) | pub fn from_lark_config(config: &crate::config::schema::LarkConfig) ->...
    method from_feishu_config (line 392) | pub fn from_feishu_config(config: &crate::config::schema::FeishuConfig...
    method http_client (line 406) | fn http_client(&self) -> reqwest::Client {
    method channel_name (line 410) | fn channel_name(&self) -> &'static str {
    method api_base (line 414) | fn api_base(&self) -> &'static str {
    method ws_base (line 418) | fn ws_base(&self) -> &'static str {
    method tenant_access_token_url (line 422) | fn tenant_access_token_url(&self) -> String {
    method bot_info_url (line 426) | fn bot_info_url(&self) -> String {
    method send_message_url (line 430) | fn send_message_url(&self) -> String {
    method message_reaction_url (line 434) | fn message_reaction_url(&self, message_id: &str) -> String {
    method resolved_bot_open_id (line 438) | fn resolved_bot_open_id(&self) -> Option<String> {
    method set_resolved_bot_open_id (line 445) | fn set_resolved_bot_open_id(&self, open_id: Option<String>) {
    method post_message_reaction_with_token (line 451) | async fn post_message_reaction_with_token(
    method try_add_ack_reaction (line 478) | async fn try_add_ack_reaction(&self, message_id: &str, emoji_type: &st...
    method get_ws_endpoint (line 549) | async fn get_ws_endpoint(&self) -> anyhow::Result<(String, WsClientCon...
    method listen_ws (line 578) | async fn listen_ws(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage...
    method is_user_allowed (line 838) | fn is_user_allowed(&self, open_id: &str) -> bool {
    method get_tenant_access_token (line 843) | async fn get_tenant_access_token(&self) -> anyhow::Result<String> {
    method invalidate_token (line 899) | async fn invalidate_token(&self) {
    method fetch_bot_open_id_with_token (line 904) | async fn fetch_bot_open_id_with_token(
    method refresh_bot_open_id (line 922) | async fn refresh_bot_open_id(&self) -> anyhow::Result<Option<String>> {
    method ensure_bot_open_id (line 960) | async fn ensure_bot_open_id(&self) {
    method send_text_once (line 982) | async fn send_text_once(
    method parse_event_payload (line 1004) | pub fn parse_event_payload(&self, payload: &serde_json::Value) -> Vec<...
    method listen_http (line 1187) | pub async fn listen_http(
  method name (line 1133) | fn name(&self) -> &str {
  method send (line 1137) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 1171) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 1179) | async fn health_check(&self) -> bool {
  function pick_uniform_index (line 1282) | fn pick_uniform_index(len: usize) -> usize {
  function random_from_pool (line 1295) | fn random_from_pool(pool: &'static [&'static str]) -> &'static str {
  function lark_ack_pool (line 1299) | fn lark_ack_pool(locale: LarkAckLocale) -> &'static [&'static str] {
  function map_locale_tag (line 1308) | fn map_locale_tag(tag: &str) -> Option<LarkAckLocale> {
  function find_locale_hint (line 1333) | fn find_locale_hint(value: &serde_json::Value) -> Option<String> {
  function detect_locale_from_post_content (line 1368) | fn detect_locale_from_post_content(content: &str) -> Option<LarkAckLocal...
  function is_japanese_kana (line 1379) | fn is_japanese_kana(ch: char) -> bool {
  function is_cjk_han (line 1388) | fn is_cjk_han(ch: char) -> bool {
  function is_traditional_only_han (line 1396) | fn is_traditional_only_han(ch: char) -> bool {
  function is_simplified_only_han (line 1415) | fn is_simplified_only_han(ch: char) -> bool {
  function detect_locale_from_text (line 1434) | fn detect_locale_from_text(text: &str) -> Option<LarkAckLocale> {
  function detect_lark_ack_locale (line 1450) | fn detect_lark_ack_locale(
  function random_lark_ack_reaction (line 1476) | fn random_lark_ack_reaction(
  type ParsedPostContent (line 1489) | struct ParsedPostContent {
  function parse_post_content_details (line 1494) | fn parse_post_content_details(content: &str) -> Option<ParsedPostContent> {
  function parse_post_content (line 1572) | fn parse_post_content(content: &str) -> Option<String> {
  function strip_at_placeholders (line 1577) | fn strip_at_placeholders(text: &str) -> String {
  function mention_matches_bot_open_id (line 1600) | fn mention_matches_bot_open_id(mention: &serde_json::Value, bot_open_id:...
  function should_respond_in_group (line 1609) | fn should_respond_in_group(
  function with_bot_open_id (line 1636) | fn with_bot_open_id(ch: LarkChannel, bot_open_id: &str) -> LarkChannel {
  function make_channel (line 1641) | fn make_channel() -> LarkChannel {
  function lark_channel_name (line 1656) | fn lark_channel_name() {
  function lark_ws_activity_refreshes_heartbeat_watchdog (line 1662) | fn lark_ws_activity_refreshes_heartbeat_watchdog() {
  function lark_ws_non_activity_frames_do_not_refresh_heartbeat_watchdog (line 1671) | fn lark_ws_non_activity_frames_do_not_refresh_heartbeat_watchdog() {
  function lark_group_response_requires_matching_bot_mention_when_ids_available (line 1677) | fn lark_group_response_requires_matching_bot_mention_when_ids_available() {
  function lark_group_response_requires_resolved_open_id_when_mention_only_enabled (line 1700) | fn lark_group_response_requires_resolved_open_id_when_mention_only_enabl...
  function lark_group_response_allows_post_mentions_for_bot_open_id (line 1708) | fn lark_group_response_allows_post_mentions_for_bot_open_id() {
  function lark_should_refresh_token_on_http_401 (line 1718) | fn lark_should_refresh_token_on_http_401() {
  function lark_should_refresh_token_on_body_code_99991663 (line 1727) | fn lark_should_refresh_token_on_body_code_99991663() {
  function lark_should_not_refresh_token_on_success_body (line 1739) | fn lark_should_not_refresh_token_on_success_body() {
  function lark_extract_token_ttl_seconds_supports_expire_and_expires_in (line 1748) | fn lark_extract_token_ttl_seconds_supports_expire_and_expires_in() {
  function lark_next_token_refresh_deadline_reserves_refresh_skew (line 1761) | fn lark_next_token_refresh_deadline_reserves_refresh_skew() {
  function lark_ensure_send_success_rejects_non_zero_code (line 1771) | fn lark_ensure_send_success_rejects_non_zero_code() {
  function lark_user_allowed_exact (line 1780) | fn lark_user_allowed_exact() {
  function lark_user_allowed_wildcard (line 1787) | fn lark_user_allowed_wildcard() {
  function lark_user_denied_empty (line 1800) | fn lark_user_denied_empty() {
  function lark_parse_challenge (line 1813) | fn lark_parse_challenge() {
  function lark_parse_valid_text_message (line 1826) | fn lark_parse_valid_text_message() {
  function lark_parse_unauthorized_user (line 1856) | fn lark_parse_unauthorized_user() {
  function lark_parse_non_text_message_skipped (line 1876) | fn lark_parse_non_text_message_skipped() {
  function lark_parse_empty_text_skipped (line 1902) | fn lark_parse_empty_text_skipped() {
  function lark_parse_wrong_event_type (line 1928) | fn lark_parse_wrong_event_type() {
  function lark_parse_missing_sender (line 1940) | fn lark_parse_missing_sender() {
  function lark_parse_unicode_message (line 1965) | fn lark_parse_unicode_message() {
  function lark_parse_missing_event (line 1993) | fn lark_parse_missing_event() {
  function lark_parse_invalid_content_json (line 2004) | fn lark_parse_invalid_content_json() {
  function lark_config_serde (line 2030) | fn lark_config_serde() {
  function lark_config_toml_roundtrip (line 2052) | fn lark_config_toml_roundtrip() {
  function lark_config_defaults_optional_fields (line 2073) | fn lark_config_defaults_optional_fields() {
  function lark_from_config_preserves_mode_and_region (line 2085) | fn lark_from_config_preserves_mode_and_region() {
  function lark_from_lark_config_ignores_legacy_feishu_flag (line 2109) | fn lark_from_lark_config_ignores_legacy_feishu_flag() {
  function lark_from_feishu_config_sets_feishu_platform (line 2132) | fn lark_from_feishu_config_sets_feishu_platform() {
  function lark_parse_fallback_sender_to_open_id (line 2153) | fn lark_parse_fallback_sender_to_open_id() {
  function lark_parse_group_message_requires_bot_mention_when_enabled (line 2181) | fn lark_parse_group_message_requires_bot_mention_when_enabled() {
  function lark_parse_group_post_message_accepts_at_when_top_level_mentions_empty (line 2241) | fn lark_parse_group_post_message_accepts_at_when_top_level_mentions_empt...
  function lark_parse_group_message_allows_without_mention_when_disabled (line 2272) | fn lark_parse_group_message_allows_without_mention_when_disabled() {
  function lark_reaction_url_matches_region (line 2300) | fn lark_reaction_url_matches_region() {
  function lark_reaction_locale_explicit_language_tags (line 2324) | fn lark_reaction_locale_explicit_language_tags() {
  function lark_reaction_locale_prefers_explicit_payload_locale (line 2334) | fn lark_reaction_locale_prefers_explicit_payload_locale() {
  function lark_reaction_locale_unsupported_payload_falls_back_to_text_script (line 2350) | fn lark_reaction_locale_unsupported_payload_falls_back_to_text_script() {
  function lark_reaction_locale_detects_simplified_and_traditional_text (line 2366) | fn lark_reaction_locale_detects_simplified_and_traditional_text() {
  function lark_reaction_locale_defaults_to_english_for_unsupported_text (line 2378) | fn lark_reaction_locale_defaults_to_english_for_unsupported_text() {
  function random_lark_ack_reaction_respects_detected_locale_pool (line 2386) | fn random_lark_ack_reaction_respects_detected_locale_pool() {

FILE: src/channels/linq.rs
  type LinqChannel (line 11) | pub struct LinqChannel {
    method new (line 21) | pub fn new(api_token: String, from_phone: String, allowed_senders: Vec...
    method is_sender_allowed (line 31) | fn is_sender_allowed(&self, phone: &str) -> bool {
    method phone_number (line 36) | pub fn phone_number(&self) -> &str {
    method media_part_to_image_marker (line 40) | fn media_part_to_image_marker(part: &serde_json::Value) -> Option<Stri...
    method sender_is_from_me (line 62) | fn sender_is_from_me(data: &serde_json::Value) -> bool {
    method sender_handle (line 83) | fn sender_handle(data: &serde_json::Value) -> Option<&str> {
    method chat_id (line 93) | fn chat_id(data: &serde_json::Value) -> Option<&str> {
    method message_parts (line 103) | fn message_parts(data: &serde_json::Value) -> Option<&Vec<serde_json::...
    method parse_webhook_payload (line 151) | pub fn parse_webhook_payload(&self, payload: &serde_json::Value) -> Ve...
  constant LINQ_API_BASE (line 18) | const LINQ_API_BASE: &str = "https://api.linqapp.com/api/partner/v3";
  method name (line 279) | fn name(&self) -> &str {
  method send (line 283) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 351) | async fn listen(&self, _tx: tokio::sync::mpsc::Sender<ChannelMessage>) -...
  method health_check (line 365) | async fn health_check(&self) -> bool {
  method start_typing (line 378) | async fn start_typing(&self, recipient: &str) -> anyhow::Result<()> {
  method stop_typing (line 395) | async fn stop_typing(&self, recipient: &str) -> anyhow::Result<()> {
  function verify_linq_signature (line 418) | pub fn verify_linq_signature(secret: &str, body: &str, timestamp: &str, ...
  function make_channel (line 457) | fn make_channel() -> LinqChannel {
  function linq_channel_name (line 466) | fn linq_channel_name() {
  function linq_sender_allowed_exact (line 472) | fn linq_sender_allowed_exact() {
  function linq_sender_allowed_wildcard (line 479) | fn linq_sender_allowed_wildcard() {
  function linq_sender_allowed_empty (line 486) | fn linq_sender_allowed_empty() {
  function linq_parse_valid_text_message (line 492) | fn linq_parse_valid_text_message() {
  function linq_parse_latest_webhook_shape (line 525) | fn linq_parse_latest_webhook_shape() {
  function linq_parse_skip_is_from_me (line 561) | fn linq_parse_skip_is_from_me() {
  function linq_parse_skip_latest_outbound_message (line 581) | fn linq_parse_skip_latest_outbound_message() {
  function linq_parse_skip_non_message_event (line 609) | fn linq_parse_skip_non_message_event() {
  function linq_parse_unauthorized_sender (line 624) | fn linq_parse_unauthorized_sender() {
  function linq_parse_empty_payload (line 644) | fn linq_parse_empty_payload() {
  function linq_parse_media_only_translated_to_image_marker (line 652) | fn linq_parse_media_only_translated_to_image_marker() {
  function linq_parse_media_non_image_still_skipped (line 677) | fn linq_parse_media_non_image_still_skipped() {
  function linq_parse_multiple_text_parts (line 701) | fn linq_parse_multiple_text_parts() {
  constant TEST_WEBHOOK_SECRET (line 725) | const TEST_WEBHOOK_SECRET: &str = "test_webhook_secret";
  function linq_signature_verification_valid (line 728) | fn linq_signature_verification_valid() {
  function linq_signature_verification_invalid (line 745) | fn linq_signature_verification_invalid() {
  function linq_signature_verification_stale_timestamp (line 759) | fn linq_signature_verification_stale_timestamp() {
  function linq_signature_verification_accepts_sha256_prefix (line 780) | fn linq_signature_verification_accepts_sha256_prefix() {
  function linq_signature_verification_accepts_uppercase_hex (line 796) | fn linq_signature_verification_accepts_uppercase_hex() {
  function linq_parse_normalizes_phone_with_plus (line 812) | fn linq_parse_normalizes_phone_with_plus() {
  function linq_parse_missing_data (line 838) | fn linq_parse_missing_data() {
  function linq_parse_missing_message_parts (line 848) | fn linq_parse_missing_message_parts() {
  function linq_parse_empty_text_value (line 867) | fn linq_parse_empty_text_value() {
  function linq_parse_fallback_reply_target_when_no_chat_id (line 887) | fn linq_parse_fallback_reply_target_when_no_chat_id() {
  function linq_phone_number_accessor (line 908) | fn linq_phone_number_accessor() {
  function linq_parse_new_format_text_message (line 916) | fn linq_parse_new_format_text_message() {
  function linq_parse_new_format_skip_is_me (line 950) | fn linq_parse_new_format_skip_is_me() {
  function linq_parse_new_format_skip_outbound_direction (line 975) | fn linq_parse_new_format_skip_outbound_direction() {
  function linq_parse_new_format_unauthorized_sender (line 997) | fn linq_parse_new_format_unauthorized_sender() {
  function linq_parse_new_format_media_image (line 1022) | fn linq_parse_new_format_media_image() {
  function linq_parse_new_format_multiple_parts (line 1049) | fn linq_parse_new_format_multiple_parts() {
  function linq_parse_new_format_fallback_reply_target_when_no_chat (line 1078) | fn linq_parse_new_format_fallback_reply_target_when_no_chat() {
  function linq_parse_new_format_normalizes_phone (line 1100) | fn linq_parse_new_format_normalizes_phone() {

FILE: src/channels/matrix.rs
  type MatrixChannel (line 30) | pub struct MatrixChannel {
    method fmt (line 46) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method normalize_optional_field (line 112) | fn normalize_optional_field(value: Option<String>) -> Option<String> {
    method new (line 118) | pub fn new(
    method new_with_session_hint (line 127) | pub fn new_with_session_hint(
    method new_with_session_hint_and_zeroclaw_dir (line 146) | pub fn new_with_session_hint_and_zeroclaw_dir(
    method encode_path_segment (line 180) | fn encode_path_segment(value: &str) -> String {
    method auth_header_value (line 201) | fn auth_header_value(&self) -> String {
    method matrix_store_dir (line 205) | fn matrix_store_dir(&self) -> Option<PathBuf> {
    method is_user_allowed (line 211) | fn is_user_allowed(&self, sender: &str) -> bool {
    method is_sender_allowed (line 215) | fn is_sender_allowed(allowed_users: &[String], sender: &str) -> bool {
    method is_supported_message_type (line 223) | fn is_supported_message_type(msgtype: &str) -> bool {
    method has_non_empty_body (line 227) | fn has_non_empty_body(body: &str) -> bool {
    method cache_event_id (line 231) | fn cache_event_id(
    method target_room_id (line 255) | async fn target_room_id(&self) -> anyhow::Result<String> {
    method get_my_identity (line 269) | async fn get_my_identity(&self) -> anyhow::Result<WhoAmIResponse> {
    method get_my_user_id (line 286) | async fn get_my_user_id(&self) -> anyhow::Result<String> {
    method matrix_client (line 290) | async fn matrix_client(&self) -> anyhow::Result<MatrixSdkClient> {
    method resolve_room_id (line 392) | async fn resolve_room_id(&self) -> anyhow::Result<String> {
    method ensure_room_accessible (line 427) | async fn ensure_room_accessible(&self, room_id: &str) -> anyhow::Resul...
    method room_is_encrypted (line 449) | async fn room_is_encrypted(&self, room_id: &str) -> anyhow::Result<boo...
    method ensure_room_supported (line 475) | async fn ensure_room_supported(&self, room_id: &str) -> anyhow::Result...
    method sync_filter_for_room (line 488) | fn sync_filter_for_room(room_id: &str, timeline_limit: usize) -> String {
    method log_e2ee_diagnostics (line 501) | async fn log_e2ee_diagnostics(&self, client: &MatrixSdkClient) {
  type SyncResponse (line 56) | struct SyncResponse {
  type Rooms (line 63) | struct Rooms {
  type JoinedRoom (line 69) | struct JoinedRoom {
  type Timeline (line 75) | struct Timeline {
  type TimelineEvent (line 81) | struct TimelineEvent {
  type EventContent (line 92) | struct EventContent {
  type WhoAmIResponse (line 100) | struct WhoAmIResponse {
  type RoomAliasResponse (line 107) | struct RoomAliasResponse {
  method name (line 538) | fn name(&self) -> &str {
  method send (line 542) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 661) | async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> anyhow::Resu...
  method health_check (line 932) | async fn health_check(&self) -> bool {
  method add_reaction (line 944) | async fn add_reaction(
  method remove_reaction (line 974) | async fn remove_reaction(
  method pin_message (line 1002) | async fn pin_message(&self, _channel_id: &str, message_id: &str) -> anyh...
  method unpin_message (line 1058) | async fn unpin_message(&self, _channel_id: &str, message_id: &str) -> an...
  function make_channel (line 1122) | fn make_channel() -> MatrixChannel {
  function creates_with_correct_fields (line 1132) | fn creates_with_correct_fields() {
  function strips_trailing_slash (line 1141) | fn strips_trailing_slash() {
  function no_trailing_slash_unchanged (line 1152) | fn no_trailing_slash_unchanged() {
  function multiple_trailing_slashes_strip_all (line 1163) | fn multiple_trailing_slashes_strip_all() {
  function trims_access_token (line 1174) | fn trims_access_token() {
  function session_hints_are_normalized (line 1185) | fn session_hints_are_normalized() {
  function empty_session_hints_are_ignored (line 1200) | fn empty_session_hints_are_ignored() {
  function matrix_store_dir_is_derived_from_zeroclaw_dir (line 1215) | fn matrix_store_dir_is_derived_from_zeroclaw_dir() {
  function matrix_store_dir_absent_without_zeroclaw_dir (line 1233) | fn matrix_store_dir_absent_without_zeroclaw_dir() {
  function encode_path_segment_encodes_room_refs (line 1247) | fn encode_path_segment_encodes_room_refs() {
  function supported_message_type_detection (line 1259) | fn supported_message_type_detection() {
  function body_presence_detection (line 1267) | fn body_presence_detection() {
  function send_content_uses_markdown_formatting (line 1275) | fn send_content_uses_markdown_formatting() {
  function sync_filter_for_room_targets_requested_room (line 1289) | fn sync_filter_for_room_targets_requested_room() {
  function event_id_cache_deduplicates_and_evicts_old_entries (line 1298) | fn event_id_cache_deduplicates_and_evicts_old_entries() {
  function trims_room_id_and_allowed_users (line 1326) | fn trims_room_id_and_allowed_users() {
  function wildcard_allows_anyone (line 1345) | fn wildcard_allows_anyone() {
  function specific_user_allowed (line 1357) | fn specific_user_allowed() {
  function unknown_user_denied (line 1363) | fn unknown_user_denied() {
  function user_case_insensitive (line 1370) | fn user_case_insensitive() {
  function empty_allowlist_denies_all (line 1382) | fn empty_allowlist_denies_all() {
  function name_returns_matrix (line 1393) | fn name_returns_matrix() {
  function sync_response_deserializes_empty (line 1399) | fn sync_response_deserializes_empty() {
  function sync_response_deserializes_with_events (line 1407) | fn sync_response_deserializes_with_events() {
  function sync_response_ignores_non_text_events (line 1450) | fn sync_response_ignores_non_text_events() {
  function whoami_response_deserializes (line 1476) | fn whoami_response_deserializes() {
  function event_content_defaults (line 1483) | fn event_content_defaults() {
  function event_content_supports_notice_msgtype (line 1491) | fn event_content_supports_notice_msgtype() {
  function invalid_room_reference_fails_fast (line 1505) | async fn invalid_room_reference_fails_fast() {
  function target_room_id_keeps_canonical_room_id_without_lookup (line 1520) | async fn target_room_id_keeps_canonical_room_id_without_lookup() {
  function target_room_id_uses_cached_alias_resolution (line 1533) | async fn target_room_id_uses_cached_alias_resolution() {
  function sync_response_missing_rooms_defaults (line 1547) | fn sync_response_missing_rooms_defaults() {

FILE: src/channels/mattermost.rs
  type MattermostChannel (line 8) | pub struct MattermostChannel {
    method new (line 23) | pub fn new(
    method http_client (line 44) | fn http_client(&self) -> reqwest::Client {
    method is_user_allowed (line 50) | fn is_user_allowed(&self, user_id: &str) -> bool {
    method get_bot_identity (line 56) | async fn get_bot_identity(&self) -> (String, String) {
    method parse_mattermost_post (line 273) | fn parse_mattermost_post(
  method name (line 88) | fn name(&self) -> &str {
  method send (line 92) | async fn send(&self, message: &SendMessage) -> Result<()> {
  method listen (line 133) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 206) | async fn health_check(&self) -> bool {
  method start_typing (line 216) | async fn start_typing(&self, recipient: &str) -> Result<()> {
  method stop_typing (line 263) | async fn stop_typing(&self, _recipient: &str) -> Result<()> {
  function contains_bot_mention_mm (line 335) | fn contains_bot_mention_mm(
  function is_mattermost_username_char (line 362) | fn is_mattermost_username_char(c: char) -> bool {
  function find_bot_mention_spans (line 366) | fn find_bot_mention_spans(text: &str, bot_username: &str) -> Vec<(usize,...
  function normalize_mattermost_content (line 413) | fn normalize_mattermost_content(
  function make_channel (line 458) | fn make_channel(allowed: Vec<String>, thread_replies: bool) -> Mattermos...
  function make_mention_only_channel (line 470) | fn make_mention_only_channel() -> MattermostChannel {
  function mattermost_url_trimming (line 482) | fn mattermost_url_trimming() {
  function mattermost_allowlist_wildcard (line 495) | fn mattermost_allowlist_wildcard() {
  function mattermost_parse_post_basic (line 501) | fn mattermost_parse_post_basic() {
  function mattermost_parse_post_thread_replies_enabled (line 520) | fn mattermost_parse_post_thread_replies_enabled() {
  function mattermost_parse_post_thread (line 537) | fn mattermost_parse_post_thread() {
  function mattermost_parse_post_ignore_self (line 554) | fn mattermost_parse_post_ignore_self() {
  function mattermost_parse_post_ignore_old (line 569) | fn mattermost_parse_post_ignore_old() {
  function mattermost_parse_post_no_thread_when_disabled (line 584) | fn mattermost_parse_post_no_thread_when_disabled() {
  function mattermost_existing_thread_always_threads (line 601) | fn mattermost_existing_thread_always_threads() {
  function mention_only_skips_message_without_mention (line 621) | fn mention_only_skips_message_without_mention() {
  function mention_only_accepts_message_with_at_mention (line 637) | fn mention_only_accepts_message_with_at_mention() {
  function mention_only_strips_mention_and_trims (line 654) | fn mention_only_strips_mention_and_trims() {
  function mention_only_rejects_empty_after_stripping (line 671) | fn mention_only_rejects_empty_after_stripping() {
  function mention_only_case_insensitive (line 687) | fn mention_only_case_insensitive() {
  function mention_only_detects_metadata_mentions (line 704) | fn mention_only_detects_metadata_mentions() {
  function mention_only_word_boundary_prevents_partial_match (line 726) | fn mention_only_word_boundary_prevents_partial_match() {
  function mention_only_mention_in_middle_of_text (line 743) | fn mention_only_mention_in_middle_of_text() {
  function mention_only_disabled_passes_all_messages (line 760) | fn mention_only_disabled_passes_all_messages() {
  function contains_mention_text_at_end (line 780) | fn contains_mention_text_at_end() {
  function contains_mention_text_at_start (line 791) | fn contains_mention_text_at_start() {
  function contains_mention_text_alone (line 802) | fn contains_mention_text_alone() {
  function no_mention_different_username (line 808) | fn no_mention_different_username() {
  function no_mention_partial_username (line 819) | fn no_mention_partial_username() {
  function mention_detects_later_valid_mention_after_partial_prefix (line 831) | fn mention_detects_later_valid_mention_after_partial_prefix() {
  function mention_followed_by_punctuation (line 842) | fn mention_followed_by_punctuation() {
  function mention_via_metadata_only (line 854) | fn mention_via_metadata_only() {
  function no_mention_empty_username_no_metadata (line 867) | fn no_mention_empty_username_no_metadata() {
  function normalize_strips_and_trims (line 875) | fn normalize_strips_and_trims() {
  function normalize_returns_none_for_no_mention (line 882) | fn normalize_returns_none_for_no_mention() {
  function normalize_returns_none_when_only_mention (line 889) | fn normalize_returns_none_when_only_mention() {
  function normalize_preserves_text_for_metadata_mention (line 896) | fn normalize_preserves_text_for_metadata_mention() {
  function normalize_strips_multiple_mentions (line 905) | fn normalize_strips_multiple_mentions() {
  function normalize_keeps_partial_username_mentions (line 913) | fn normalize_keeps_partial_username_mentions() {

FILE: src/channels/mochat.rs
  constant DEDUP_CAPACITY (line 10) | const DEDUP_CAPACITY: usize = 10_000;
  type MochatChannel (line 16) | pub struct MochatChannel {
    method new (line 26) | pub fn new(
    method http_client (line 41) | fn http_client(&self) -> reqwest::Client {
    method is_user_allowed (line 45) | fn is_user_allowed(&self, user_id: &str) -> bool {
    method is_duplicate (line 50) | async fn is_duplicate(&self, msg_id: &str) -> bool {
  method name (line 75) | fn name(&self) -> &str {
  method send (line 79) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 116) | async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) ->...
  method health_check (line 229) | async fn health_check(&self) -> bool {
  function test_name (line 249) | fn test_name() {
  function test_api_url_trailing_slash_stripped (line 255) | fn test_api_url_trailing_slash_stripped() {
  function test_user_allowed_wildcard (line 266) | fn test_user_allowed_wildcard() {
  function test_user_allowed_specific (line 272) | fn test_user_allowed_specific() {
  function test_user_denied_empty (line 284) | fn test_user_denied_empty() {
  function test_dedup (line 290) | async fn test_dedup() {
  function test_dedup_empty_id (line 298) | async fn test_dedup_empty_id() {
  function test_config_serde (line 305) | fn test_config_serde() {
  function test_config_serde_defaults (line 318) | fn test_config_serde_defaults() {

FILE: src/channels/mod.rs
  type ChannelNotifyObserver (line 118) | struct ChannelNotifyObserver {
  method record_event (line 125) | fn record_event(&self, event: &ObserverEvent) {
  method record_metric (line 154) | fn record_metric(&self, metric: &ObserverMetric) {
  method flush (line 157) | fn flush(&self) {
  method name (line 160) | fn name(&self) -> &str {
  method as_any (line 163) | fn as_any(&self) -> &dyn std::any::Any {
  type ConversationHistoryMap (line 169) | type ConversationHistoryMap = Arc<Mutex<HashMap<String, Vec<ChatMessage>...
  type PendingNewSessionSet (line 171) | type PendingNewSessionSet = Arc<Mutex<HashSet<String>>>;
  constant MAX_CHANNEL_HISTORY (line 173) | const MAX_CHANNEL_HISTORY: usize = 50;
  constant AUTOSAVE_MIN_MESSAGE_CHARS (line 177) | const AUTOSAVE_MIN_MESSAGE_CHARS: usize = 20;
  constant BOOTSTRAP_MAX_CHARS (line 180) | const BOOTSTRAP_MAX_CHARS: usize = 20_000;
  constant DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS (line 182) | const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2;
  constant DEFAULT_CHANNEL_MAX_BACKOFF_SECS (line 183) | const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60;
  constant MIN_CHANNEL_MESSAGE_TIMEOUT_SECS (line 184) | const MIN_CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 30;
  constant CHANNEL_MESSAGE_TIMEOUT_SECS (line 187) | const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300;
  constant CHANNEL_MESSAGE_TIMEOUT_SCALE_CAP (line 189) | const CHANNEL_MESSAGE_TIMEOUT_SCALE_CAP: u64 = 4;
  constant CHANNEL_PARALLELISM_PER_CHANNEL (line 190) | const CHANNEL_PARALLELISM_PER_CHANNEL: usize = 4;
  constant CHANNEL_MIN_IN_FLIGHT_MESSAGES (line 191) | const CHANNEL_MIN_IN_FLIGHT_MESSAGES: usize = 8;
  constant CHANNEL_MAX_IN_FLIGHT_MESSAGES (line 192) | const CHANNEL_MAX_IN_FLIGHT_MESSAGES: usize = 64;
  constant CHANNEL_TYPING_REFRESH_INTERVAL_SECS (line 193) | const CHANNEL_TYPING_REFRESH_INTERVAL_SECS: u64 = 4;
  constant CHANNEL_HEALTH_HEARTBEAT_SECS (line 194) | const CHANNEL_HEALTH_HEARTBEAT_SECS: u64 = 30;
  constant MODEL_CACHE_FILE (line 195) | const MODEL_CACHE_FILE: &str = "models_cache.json";
  constant MODEL_CACHE_PREVIEW_LIMIT (line 196) | const MODEL_CACHE_PREVIEW_LIMIT: usize = 10;
  constant MEMORY_CONTEXT_MAX_ENTRIES (line 197) | const MEMORY_CONTEXT_MAX_ENTRIES: usize = 4;
  constant MEMORY_CONTEXT_ENTRY_MAX_CHARS (line 198) | const MEMORY_CONTEXT_ENTRY_MAX_CHARS: usize = 800;
  constant MEMORY_CONTEXT_MAX_CHARS (line 199) | const MEMORY_CONTEXT_MAX_CHARS: usize = 4_000;
  constant CHANNEL_HISTORY_COMPACT_KEEP_MESSAGES (line 200) | const CHANNEL_HISTORY_COMPACT_KEEP_MESSAGES: usize = 12;
  constant CHANNEL_HISTORY_COMPACT_CONTENT_CHARS (line 201) | const CHANNEL_HISTORY_COMPACT_CONTENT_CHARS: usize = 600;
  constant PROACTIVE_CONTEXT_BUDGET_CHARS (line 208) | const PROACTIVE_CONTEXT_BUDGET_CHARS: usize = 400_000;
  constant CHANNEL_HOOK_MAX_OUTBOUND_CHARS (line 210) | const CHANNEL_HOOK_MAX_OUTBOUND_CHARS: usize = 20_000;
  type ProviderCacheMap (line 212) | type ProviderCacheMap = Arc<Mutex<HashMap<String, Arc<dyn Provider>>>>;
  type RouteSelectionMap (line 213) | type RouteSelectionMap = Arc<Mutex<HashMap<String, ChannelRouteSelection...
  function effective_channel_message_timeout_secs (line 215) | fn effective_channel_message_timeout_secs(configured: u64) -> u64 {
  function channel_message_timeout_budget_secs (line 219) | fn channel_message_timeout_budget_secs(
  type ChannelRouteSelection (line 229) | struct ChannelRouteSelection {
  type ChannelRuntimeCommand (line 239) | enum ChannelRuntimeCommand {
  type ModelCacheState (line 248) | struct ModelCacheState {
  type ModelCacheEntry (line 253) | struct ModelCacheEntry {
  type ChannelRuntimeDefaults (line 259) | struct ChannelRuntimeDefaults {
  type ConfigFileStamp (line 269) | struct ConfigFileStamp {
  type RuntimeConfigState (line 275) | struct RuntimeConfigState {
  function runtime_config_store (line 280) | fn runtime_config_store() -> &'static Mutex<HashMap<PathBuf, RuntimeConf...
  constant SYSTEMD_STATUS_ARGS (line 285) | const SYSTEMD_STATUS_ARGS: [&str; 3] = ["--user", "is-active", "zeroclaw...
  constant SYSTEMD_RESTART_ARGS (line 286) | const SYSTEMD_RESTART_ARGS: [&str; 3] = ["--user", "restart", "zeroclaw....
  constant OPENRC_STATUS_ARGS (line 287) | const OPENRC_STATUS_ARGS: [&str; 2] = ["zeroclaw", "status"];
  constant OPENRC_RESTART_ARGS (line 288) | const OPENRC_RESTART_ARGS: [&str; 2] = ["zeroclaw", "restart"];
  type InterruptOnNewMessageConfig (line 292) | struct InterruptOnNewMessageConfig {
    method enabled_for_channel (line 300) | fn enabled_for_channel(self, channel: &str) -> bool {
  type ChannelRuntimeContext (line 312) | struct ChannelRuntimeContext {
  type InFlightSenderTaskState (line 356) | struct InFlightSenderTaskState {
  type InFlightTaskCompletion (line 362) | struct InFlightTaskCompletion {
    method new (line 368) | fn new() -> Self {
    method mark_done (line 375) | fn mark_done(&self) {
    method wait (line 380) | async fn wait(&self) {
  function conversation_memory_key (line 388) | fn conversation_memory_key(msg: &traits::ChannelMessage) -> String {
  function conversation_history_key (line 396) | fn conversation_history_key(msg: &traits::ChannelMessage) -> String {
  function followup_thread_id (line 408) | fn followup_thread_id(msg: &traits::ChannelMessage) -> Option<String> {
  function interruption_scope_key (line 412) | fn interruption_scope_key(msg: &traits::ChannelMessage) -> String {
  function is_stop_command (line 424) | fn is_stop_command(content: &str) -> bool {
  function strip_tool_call_tags (line 440) | fn strip_tool_call_tags(message: &str) -> String {
  function channel_delivery_instructions (line 550) | fn channel_delivery_instructions(channel_name: &str) -> Option<&'static ...
  function build_channel_system_prompt (line 577) | fn build_channel_system_prompt(
  function normalize_cached_channel_turns (line 625) | fn normalize_cached_channel_turns(turns: Vec<ChatMessage>) -> Vec<ChatMe...
  function strip_tool_result_content (line 661) | fn strip_tool_result_content(text: &str) -> String {
  function supports_runtime_model_switch (line 677) | fn supports_runtime_model_switch(channel_name: &str) -> bool {
  function parse_runtime_command (line 681) | fn parse_runtime_command(channel_name: &str, content: &str) -> Option<Ch...
  function resolve_provider_alias (line 722) | fn resolve_provider_alias(name: &str) -> Option<String> {
  function resolved_default_provider (line 743) | fn resolved_default_provider(config: &Config) -> String {
  function resolved_default_model (line 750) | fn resolved_default_model(config: &Config) -> String {
  function runtime_defaults_from_config (line 757) | fn runtime_defaults_from_config(config: &Config) -> ChannelRuntimeDefaul...
  function runtime_config_path (line 768) | fn runtime_config_path(ctx: &ChannelRuntimeContext) -> Option<PathBuf> {
  function runtime_defaults_snapshot (line 775) | fn runtime_defaults_snapshot(ctx: &ChannelRuntimeContext) -> ChannelRunt...
  function config_file_stamp (line 795) | async fn config_file_stamp(path: &Path) -> Option<ConfigFileStamp> {
  function decrypt_optional_secret_for_runtime_reload (line 804) | fn decrypt_optional_secret_for_runtime_reload(
  function load_runtime_defaults_from_config_file (line 821) | async fn load_runtime_defaults_from_config_file(path: &Path) -> Result<C...
  function maybe_apply_runtime_config_update (line 860) | async fn maybe_apply_runtime_config_update(ctx: &ChannelRuntimeContext) ...
  function default_route_selection (line 938) | fn default_route_selection(ctx: &ChannelRuntimeContext) -> ChannelRouteS...
  function get_route_selection (line 947) | fn get_route_selection(ctx: &ChannelRuntimeContext, sender_key: &str) ->...
  function set_route_selection (line 956) | fn set_route_selection(ctx: &ChannelRuntimeContext, sender_key: &str, ne...
  function clear_sender_history (line 969) | fn clear_sender_history(ctx: &ChannelRuntimeContext, sender_key: &str) {
  function mark_sender_for_new_session (line 976) | fn mark_sender_for_new_session(ctx: &ChannelRuntimeContext, sender_key: ...
  function take_pending_new_session (line 983) | fn take_pending_new_session(ctx: &ChannelRuntimeContext, sender_key: &st...
  function replace_available_skills_section (line 990) | fn replace_available_skills_section(base_prompt: &str, refreshed_skills:...
  function refreshed_new_session_system_prompt (line 1033) | fn refreshed_new_session_system_prompt(ctx: &ChannelRuntimeContext) -> S...
  function compact_sender_history (line 1045) | fn compact_sender_history(ctx: &ChannelRuntimeContext, sender_key: &str)...
  function proactive_trim_turns (line 1084) | fn proactive_trim_turns(turns: &mut Vec<ChatMessage>, budget: usize) -> ...
  function append_sender_turn (line 1105) | fn append_sender_turn(ctx: &ChannelRuntimeContext, sender_key: &str, tur...
  function rollback_orphan_user_turn (line 1124) | fn rollback_orphan_user_turn(
  function should_skip_memory_context_entry (line 1160) | fn should_skip_memory_context_entry(key: &str, content: &str) -> bool {
  function is_context_window_overflow_error (line 1192) | fn is_context_window_overflow_error(err: &anyhow::Error) -> bool {
  function load_cached_model_preview (line 1208) | fn load_cached_model_preview(workspace_dir: &Path, provider_name: &str) ...
  function provider_cache_key (line 1235) | fn provider_cache_key(provider_name: &str, route_api_key: Option<&str>) ...
  function get_or_create_provider (line 1247) | async fn get_or_create_provider(
  function create_resilient_provider_nonblocking (line 1304) | async fn create_resilient_provider_nonblocking(
  function build_models_help_response (line 1325) | fn build_models_help_response(
  function build_providers_help_response (line 1370) | fn build_providers_help_response(current: &ChannelRouteSelection) -> Str...
  function handle_runtime_command_if_needed (line 1395) | async fn handle_runtime_command_if_needed(
  function build_memory_context (line 1492) | async fn build_memory_context(
  function extract_tool_context_summary (line 1549) | fn extract_tool_context_summary(history: &[ChatMessage], start_index: us...
  function sanitize_channel_response (line 1639) | fn sanitize_channel_response(response: &str, tools: &[Box<dyn Tool>]) ->...
  function strip_tool_narration (line 1656) | fn strip_tool_narration(message: &str) -> String {
  function is_tool_call_payload (line 1708) | fn is_tool_call_payload(value: &serde_json::Value, known_tool_names: &Ha...
  function is_tool_result_payload (line 1739) | fn is_tool_result_payload(
  function sanitize_tool_json_value (line 1755) | fn sanitize_tool_json_value(
  function is_line_isolated_json_segment (line 1800) | fn is_line_isolated_json_segment(message: &str, start: usize, end: usize...
  function strip_isolated_tool_json_artifacts (line 1809) | fn strip_isolated_tool_json_artifacts(message: &str, known_tool_names: &...
  function spawn_supervised_listener (line 1862) | fn spawn_supervised_listener(
  function spawn_supervised_listener_with_health_interval (line 1877) | fn spawn_supervised_listener_with_health_interval(
  function compute_max_in_flight_messages (line 1938) | fn compute_max_in_flight_messages(channel_count: usize) -> usize {
  function log_worker_join_result (line 1947) | fn log_worker_join_result(result: Result<(), tokio::task::JoinError>) {
  function spawn_scoped_typing_task (line 1953) | fn spawn_scoped_typing_task(
  function process_channel_message (line 1983) | async fn process_channel_message(
  function run_message_dispatch_loop (line 2773) | async fn run_message_dispatch_loop(
  function load_openclaw_bootstrap_files (line 2905) | fn load_openclaw_bootstrap_files(
  function build_system_prompt (line 2946) | pub fn build_system_prompt(
  function build_system_prompt_with_mode (line 2967) | pub fn build_system_prompt_with_mode(
  function build_system_prompt_with_mode_and_autonomy (line 2995) | pub fn build_system_prompt_with_mode_and_autonomy(
  function inject_workspace_file (line 3211) | fn inject_workspace_file(
  function normalize_telegram_identity (line 3255) | fn normalize_telegram_identity(value: &str) -> String {
  function bind_telegram_identity (line 3259) | async fn bind_telegram_identity(config: &Config, identity: &str) -> Resu...
  function maybe_restart_managed_daemon_service (line 3311) | fn maybe_restart_managed_daemon_service() -> Result<bool> {
  function handle_command (line 3406) | pub(crate) async fn handle_command(command: crate::ChannelCommands, conf...
  function build_channel_by_id (line 3468) | fn build_channel_by_id(config: &Config, channel_id: &str) -> Result<Arc<...
  function send_channel_message (line 3528) | async fn send_channel_message(
  type ChannelHealthState (line 3545) | enum ChannelHealthState {
  function classify_health_result (line 3551) | fn classify_health_result(
  type ConfiguredChannel (line 3561) | struct ConfiguredChannel {
  function collect_configured_channels (line 3566) | fn collect_configured_channels(
  function doctor_channels (line 3969) | pub async fn doctor_channels(config: Config) -> Result<()> {
  function start_channels (line 4030) | pub async fn start_channels(config: Config) -> Result<()> {
  function make_workspace (line 4529) | fn make_workspace() -> TempDir {
  function effective_channel_message_timeout_secs_clamps_to_minimum (line 4551) | fn effective_channel_message_timeout_secs_clamps_to_minimum() {
  function channel_message_timeout_budget_scales_with_tool_iterations (line 4564) | fn channel_message_timeout_budget_scales_with_tool_iterations() {
  function channel_message_timeout_budget_uses_safe_defaults_and_cap (line 4571) | fn channel_message_timeout_budget_uses_safe_defaults_and_cap() {
  function context_window_overflow_error_detector_matches_known_messages (line 4582) | fn context_window_overflow_error_detector_matches_known_messages() {
  function memory_context_skip_rules_exclude_history_blobs (line 4594) | fn memory_context_skip_rules_exclude_history_blobs() {
  function strip_tool_result_content_removes_blocks_and_header (line 4634) | fn strip_tool_result_content_removes_blocks_and_header() {
  function normalize_cached_channel_turns_merges_consecutive_user_turns (line 4654) | fn normalize_cached_channel_turns_merges_consecutive_user_turns() {
  function normalize_cached_channel_turns_merges_consecutive_assistant_turns (line 4668) | fn normalize_cached_channel_turns_merges_consecutive_assistant_turns() {
  function normalize_preserves_failure_marker_after_orphan_user_turn (line 4689) | fn normalize_preserves_failure_marker_after_orphan_user_turn() {
  function normalize_preserves_timeout_marker_after_orphan_user_turn (line 4707) | fn normalize_preserves_timeout_marker_after_orphan_user_turn() {
  function compact_sender_history_keeps_recent_truncated_messages (line 4722) | fn compact_sender_history_keeps_recent_truncated_messages() {
  function proactive_trim_drops_oldest_turns_when_over_budget (line 4804) | fn proactive_trim_drops_oldest_turns_when_over_budget() {
  function proactive_trim_noop_when_within_budget (line 4832) | fn proactive_trim_noop_when_within_budget() {
  function proactive_trim_preserves_last_turn_even_when_over_budget (line 4843) | fn proactive_trim_preserves_last_turn_even_when_over_budget() {
  function append_sender_turn_stores_single_turn_per_call (line 4851) | fn append_sender_turn_stores_single_turn_per_call() {
  function rollback_orphan_user_turn_removes_only_latest_matching_user_turn (line 4912) | fn rollback_orphan_user_turn_removes_only_latest_matching_user_turn() {
  function rollback_orphan_user_turn_also_removes_from_session_store (line 4984) | fn rollback_orphan_user_turn_also_removes_from_session_store() {
  type DummyProvider (line 5083) | struct DummyProvider;
  method chat_with_system (line 5087) | async fn chat_with_system(
  type RecordingChannel (line 5099) | struct RecordingChannel {
  type TelegramRecordingChannel (line 5108) | struct TelegramRecordingChannel {
  type SlackRecordingChannel (line 5113) | struct SlackRecordingChannel {
  method name (line 5119) | fn name(&self) -> &str {
  method send (line 5123) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 5131) | async fn listen(
  method start_typing (line 5138) | async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method stop_typing (line 5142) | async fn stop_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method name (line 5149) | fn name(&self) -> &str {
  method send (line 5153) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 5161) | async fn listen(
  method start_typing (line 5168) | async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method stop_typing (line 5172) | async fn stop_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method name (line 5179) | fn name(&self) -> &str {
  method send (line 5183) | async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 5191) | async fn listen(
  method start_typing (line 5198) | async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method stop_typing (line 5203) | async fn stop_typing(&self, _recipient: &str) -> anyhow::Result<()> {
  method add_reaction (line 5208) | async fn add_reaction(
  method remove_reaction (line 5222) | async fn remove_reaction(
  type SlowProvider (line 5237) | struct SlowProvider {
  method chat_with_system (line 5243) | async fn chat_with_system(
  type ToolCallingProvider (line 5255) | struct ToolCallingProvider;
  function tool_call_payload (line 5257) | fn tool_call_payload() -> String {
  function tool_call_payload_with_alias_tag (line 5264) | fn tool_call_payload_with_alias_tag() -> String {
  method chat_with_system (line 5273) | async fn chat_with_system(
  method chat_with_history (line 5283) | async fn chat_with_history(
  type ToolCallingAliasProvider (line 5300) | struct ToolCallingAliasProvider;
  method chat_with_system (line 5304) | async fn chat_with_system(
  method chat_with_history (line 5314) | async fn chat_with_history(
  type RawToolArtifactProvider (line 5331) | struct RawToolArtifactProvider;
  method chat_with_system (line 5335) | async fn chat_with_system(
  method chat_with_history (line 5345) | async fn chat_with_history(
  type IterativeToolProvider (line 5358) | struct IterativeToolProvider {
    method completed_tool_iterations (line 5363) | fn completed_tool_iterations(messages: &[ChatMessage]) -> usize {
  method chat_with_system (line 5373) | async fn chat_with_system(
  method chat_with_history (line 5383) | async fn chat_with_history(
  type HistoryCaptureProvider (line 5401) | struct HistoryCaptureProvider {
  method chat_with_system (line 5407) | async fn chat_with_system(
  method chat_with_history (line 5417) | async fn chat_with_history(
  type DelayedHistoryCaptureProvider (line 5433) | struct DelayedHistoryCaptureProvider {
  method chat_with_system (line 5440) | async fn chat_with_system(
  method chat_with_history (line 5450) | async fn chat_with_history(
  type MockPriceTool (line 5470) | struct MockPriceTool;
  type ModelCaptureProvider (line 5473) | struct ModelCaptureProvider {
  method chat_with_system (line 5480) | async fn chat_with_system(
  method chat_with_history (line 5490) | async fn chat_with_history(
  method name (line 5507) | fn name(&self) -> &str {
  method description (line 5511) | fn description(&self) -> &str {
  method parameters_schema (line 5515) | fn parameters_schema(&self) -> serde_json::Value {
  method execute (line 5525) | async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolR...
  function process_channel_message_executes_tool_calls_instead_of_sending_raw_json (line 5544) | async fn process_channel_message_executes_tool_calls_instead_of_sending_...
  function process_channel_message_telegram_does_not_persist_tool_summary_prefix (line 5623) | async fn process_channel_message_telegram_does_not_persist_tool_summary_...
  function process_channel_message_strips_unexecuted_tool_json_artifacts_from_reply (line 5716) | async fn process_channel_message_strips_unexecuted_tool_json_artifacts_f...
  function process_channel_message_executes_tool_calls_with_alias_tags (line 5794) | async fn process_channel_message_executes_tool_calls_with_alias_tags() {
  function process_channel_message_handles_models_command_without_llm_call (line 5873) | async fn process_channel_message_handles_models_command_without_llm_call...
  function process_channel_message_uses_route_override_provider_and_model (line 5971) | async fn process_channel_message_uses_route_override_provider_and_model() {
  function process_channel_message_prefers_cached_default_provider_instance (line 6073) | async fn process_channel_message_prefers_cached_default_provider_instanc...
  function process_channel_message_uses_runtime_default_model_from_store (line 6155) | async fn process_channel_message_uses_runtime_default_model_from_store() {
  function process_channel_message_respects_configured_max_tool_iterations_above_default (line 6274) | async fn process_channel_message_respects_configured_max_tool_iterations...
  function process_channel_message_reports_configured_max_tool_iterations_limit (line 6354) | async fn process_channel_message_reports_configured_max_tool_iterations_...
  type NoopMemory (line 6432) | struct NoopMemory;
  method name (line 6436) | fn name(&self) -> &str {
  method store (line 6440) | async fn store(
  method recall (line 6450) | async fn recall(
  method get (line 6459) | async fn get(&self, _key: &str) -> anyhow::Result<Option<crate::memory::...
  method list (line 6463) | async fn list(
  method forget (line 6471) | async fn forget(&self, _key: &str) -> anyhow::Result<bool> {
  method count (line 6475) | async fn count(&self) -> anyhow::Result<usize> {
  method health_check (line 6479) | async fn health_check(&self) -> bool {
  type RecallMemory (line 6484) | struct RecallMemory;
  method name (line 6488) | fn name(&self) -> &str {
  method store (line 6492) | async fn store(
  method recall (line 6502) | async fn recall(
  method get (line 6519) | async fn get(&self, _key: &str) -> anyhow::Result<Option<crate::memory::...
  method list (line 6523) | async fn list(
  method forget (line 6531) | async fn forget(&self, _key: &str) -> anyhow::Result<bool> {
  method count (line 6535) | async fn count(&self) -> anyhow::Result<usize> {
  method health_check (line 6539) | async fn health_check(&self) -> bool {
  function message_dispatch_processes_messages_in_parallel (line 6545) | async fn message_dispatch_processes_messages_in_parallel() {
  function message_dispatch_interrupts_in_flight_telegram_request_and_preserves_context (line 6642) | async fn message_dispatch_interrupts_in_flight_telegram_request_and_pres...
  function message_dispatch_interrupts_in_flight_slack_request_and_preserves_context (line 6757) | async fn message_dispatch_interrupts_in_flight_slack_request_and_preserv...
  function message_dispatch_interrupt_scope_is_same_sender_same_chat (line 6872) | async fn message_dispatch_interrupt_scope_is_same_sender_same_chat() {
  function process_channel_message_cancels_scoped_typing_task (line 6966) | async fn process_channel_message_cancels_scoped_typing_task() {
  function process_channel_message_adds_and_swaps_reactions (line 7044) | async fn process_channel_message_adds_and_swaps_reactions() {
  function prompt_contains_all_sections (line 7134) | fn prompt_contains_all_sections() {
  function prompt_injects_tools (line 7155) | fn prompt_injects_tools() {
  function prompt_includes_single_tool_protocol_block_after_append (line 7169) | fn prompt_includes_single_tool_protocol_block_after_append() {
  function prompt_injects_safety (line 7189) | fn prompt_injects_safety() {
  function prompt_injects_workspace_files (line 7199) | fn prompt_injects_workspace_files() {
  function prompt_missing_file_markers (line 7225) | fn prompt_missing_file_markers() {
  function prompt_bootstrap_only_if_exists (line 7236) | fn prompt_bootstrap_only_if_exists() {
  function prompt_no_daily_memory_injection (line 7256) | fn prompt_no_daily_memory_injection() {
  function prompt_runtime_metadata (line 7281) | fn prompt_runtime_metadata() {
  function prompt_skills_include_instructions_and_tools (line 7291) | fn prompt_skills_include_instructions_and_tools() {
  function prompt_skills_compact_mode_omits_instructions_but_keeps_tools (line 7326) | fn prompt_skills_compact_mode_omits_instructions_but_keeps_tools() {
  function prompt_skills_escape_reserved_xml_chars (line 7371) | fn prompt_skills_escape_reserved_xml_chars() {
  function prompt_truncation (line 7405) | fn prompt_truncation() {
  function prompt_empty_files_skipped (line 7424) | fn prompt_empty_files_skipped() {
  function channel_log_truncation_is_utf8_safe_for_multibyte_text (line 7438) | fn channel_log_truncation_is_utf8_safe_for_multibyte_text() {
  function prompt_contains_channel_capabilities (line 7454) | fn prompt_contains_channel_capabilities() {
  function full_autonomy_prompt_executes_allowed_tools_without_extra_approval (line 7473) | fn full_autonomy_prompt_executes_allowed_tools_without_extra_approval() {
  function readonly_prompt_explains_policy_blocks_without_fake_approval (line 7502) | fn readonly_prompt_explains_policy_blocks_without_fake_approval() {
  function prompt_workspace_path (line 7531) | fn prompt_workspace_path() {
  function full_autonomy_omits_approval_instructions (line 7539) | fn full_autonomy_omits_approval_instructions() {
  function supervised_autonomy_includes_approval_instructions (line 7573) | fn supervised_autonomy_includes_approval_instructions() {
  function channel_notify_observer_truncates_utf8_arguments_safely (line 7598) | fn channel_notify_observer_truncates_utf8_arguments_safely() {
  function conversation_memory_key_uses_message_id (line 7625) | fn conversation_memory_key_uses_message_id() {
  function followup_thread_id_prefers_thread_ts (line 7641) | fn followup_thread_id_prefers_thread_ts() {
  function followup_thread_id_falls_back_to_message_id (line 7660) | fn followup_thread_id_falls_back_to_message_id() {
  function conversation_memory_key_is_unique_per_message (line 7676) | fn conversation_memory_key_is_unique_per_message() {
  function autosave_keys_preserve_multiple_conversation_facts (line 7705) | async fn autosave_keys_preserve_multiple_conversation_facts() {
  function build_memory_context_includes_recalled_entries (line 7754) | async fn build_memory_context_includes_recalled_entries() {
  function build_memory_context_excludes_image_marker_entries (line 7769) | async fn build_memory_context_excludes_image_marker_entries() {
  function process_channel_message_restores_per_sender_history_on_follow_ups (line 7808) | async fn process_channel_message_restores_per_sender_history_on_follow_u...
  function process_channel_message_refreshes_available_skills_after_new_session (line 7914) | async fn process_channel_message_refreshes_available_skills_after_new_se...
  function process_channel_message_enriches_current_turn_without_persisting_context (line 8107) | async fn process_channel_message_enriches_current_turn_without_persistin...
  function process_channel_message_telegram_keeps_system_instruction_at_top_only (line 8201) | async fn process_channel_message_telegram_keeps_system_instruction_at_to...
  function extract_tool_context_summary_collects_alias_and_native_tool_calls (line 8305) | fn extract_tool_context_summary_collects_alias_and_native_tool_calls() {
  function extract_tool_context_summary_collects_prompt_mode_tool_result_names (line 8323) | fn extract_tool_context_summary_collects_prompt_mode_tool_result_names() {
  function extract_tool_context_summary_respects_start_index (line 8343) | fn extract_tool_context_summary_respects_start_index() {
  function strip_isolated_tool_json_artifacts_removes_tool_calls_and_results (line 8362) | fn strip_isolated_tool_json_artifacts_removes_tool_calls_and_results() {
  function strip_isolated_tool_json_artifacts_preserves_non_tool_json (line 8386) | fn strip_isolated_tool_json_artifacts_preserves_non_tool_json() {
  function aieos_identity_from_file (line 8400) | fn aieos_identity_from_file() {
  function aieos_identity_from_inline (line 8457) | fn aieos_identity_from_inline() {
  function aieos_fallback_to_openclaw_on_parse_error (line 8480) | fn aieos_fallback_to_openclaw_on_parse_error() {
  function aieos_empty_uses_openclaw (line 8498) | fn aieos_empty_uses_openclaw() {
  function openclaw_format_uses_bootstrap_files (line 8517) | fn openclaw_format_uses_bootstrap_files() {
  function none_identity_config_uses_openclaw (line 8536) | fn none_identity_config_uses_openclaw() {
  function classify_health_ok_true (line 8547) | fn classify_health_ok_true() {
  function classify_health_ok_false (line 8553) | fn classify_health_ok_false() {
  function classify_health_timeout (line 8559) | async fn classify_health_timeout() {
  function collect_configured_channels_includes_mattermost_when_configured (line 8570) | fn collect_configured_channels_includes_mattermost_when_configured() {
  type AlwaysFailChannel (line 8592) | struct AlwaysFailChannel {
  type BlockUntilClosedChannel (line 8597) | struct BlockUntilClosedChannel {
  method name (line 8604) | fn name(&self) -> &str {
  method send (line 8608) | async fn send(&self, _message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 8612) | async fn listen(
  method name (line 8623) | fn name(&self) -> &str {
  method send (line 8627) | async fn send(&self, _message: &SendMessage) -> anyhow::Result<()> {
  method listen (line 8631) | async fn listen(
  function supervised_listener_marks_error_and_restarts_on_failures (line 8642) | async fn supervised_listener_marks_error_and_restarts_on_failures() {
  function supervised_listener_refreshes_health_while_running (line 8669) | async fn supervised_listener_refreshes_health_while_running() {
  function maybe_restart_daemon_systemd_args_regression (line 8714) | fn maybe_restart_daemon_systemd_args_regression() {
  function maybe_restart_
Copy disabled (too large) Download .json
Condensed preview — 815 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (11,319K chars).
[
  {
    "path": ".cargo/audit.toml",
    "chars": 348,
    "preview": "# cargo-audit configuration\n# https://rustsec.org/\n\n[advisories]\nignore = [\n    # wasmtime vulns via extism 1.13.0 — no "
  },
  {
    "path": ".cargo/config.toml",
    "chars": 335,
    "preview": "[target.x86_64-unknown-linux-musl]\nrustflags = [\"-C\", \"link-arg=-static\"]\n\n[target.aarch64-unknown-linux-musl]\nrustflags"
  },
  {
    "path": ".claude/skills/github-issue/SKILL.md",
    "chars": 5818,
    "preview": "# Skill: github-issue\n\nFile a structured GitHub issue (bug report or feature request) for ZeroClaw interactively from Cl"
  },
  {
    "path": ".claude/skills/github-pr/SKILL.md",
    "chars": 7632,
    "preview": "# Skill: github-pr\n\nOpen or update a GitHub Pull Request for ZeroClaw. Handles creating new PRs with a fully filled-out "
  },
  {
    "path": ".claude/skills/skill-creator/LICENSE.txt",
    "chars": 11357,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": ".claude/skills/skill-creator/SKILL.md",
    "chars": 32987,
    "preview": "---\nname: skill-creator\ndescription: Create new skills, modify and improve existing skills, and measure skill performanc"
  },
  {
    "path": ".claude/skills/skill-creator/agents/analyzer.md",
    "chars": 10374,
    "preview": "# Post-hoc Analyzer Agent\n\nAnalyze blind comparison results to understand WHY the winner won and generate improvement su"
  },
  {
    "path": ".claude/skills/skill-creator/agents/comparator.md",
    "chars": 7281,
    "preview": "# Blind Comparator Agent\n\nCompare two outputs WITHOUT knowing which skill produced them.\n\n## Role\n\nThe Blind Comparator "
  },
  {
    "path": ".claude/skills/skill-creator/agents/grader.md",
    "chars": 9031,
    "preview": "# Grader Agent\n\nEvaluate expectations against an execution transcript and outputs.\n\n## Role\n\nThe Grader reviews a transc"
  },
  {
    "path": ".claude/skills/skill-creator/assets/eval_review.html",
    "chars": 7058,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": ".claude/skills/skill-creator/eval-viewer/generate_review.py",
    "chars": 16295,
    "preview": "#!/usr/bin/env python3\n\"\"\"Generate and serve a review page for eval results.\n\nReads the workspace directory, discovers r"
  },
  {
    "path": ".claude/skills/skill-creator/eval-viewer/viewer.html",
    "chars": 44975,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": ".claude/skills/skill-creator/references/schemas.md",
    "chars": 12058,
    "preview": "# JSON Schemas\n\nThis document defines the JSON schemas used by skill-creator.\n\n---\n\n## evals.json\n\nDefines the evals for"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".claude/skills/skill-creator/scripts/aggregate_benchmark.py",
    "chars": 14284,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nAggregate individual run results into benchmark summary statistics.\n\nReads grading.json files"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/generate_report.py",
    "chars": 12837,
    "preview": "#!/usr/bin/env python3\n\"\"\"Generate an HTML report from run_loop.py output.\n\nTakes the JSON output from run_loop.py and g"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/improve_description.py",
    "chars": 11108,
    "preview": "#!/usr/bin/env python3\n\"\"\"Improve a skill description based on eval results.\n\nTakes eval results (from run_eval.py) and "
  },
  {
    "path": ".claude/skills/skill-creator/scripts/package_skill.py",
    "chars": 4214,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSkill Packager - Creates a distributable .skill file of a skill folder\n\nUsage:\n    python uti"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/quick_validate.py",
    "chars": 3972,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nQuick validation script for skills - minimal version\n\"\"\"\n\nimport sys\nimport os\nimport re\nimpo"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/run_eval.py",
    "chars": 11464,
    "preview": "#!/usr/bin/env python3\n\"\"\"Run trigger evaluation for a skill description.\n\nTests whether a skill's description causes Cl"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/run_loop.py",
    "chars": 13605,
    "preview": "#!/usr/bin/env python3\n\"\"\"Run the eval + improve loop until all pass or max iterations reached.\n\nCombines run_eval.py an"
  },
  {
    "path": ".claude/skills/skill-creator/scripts/utils.py",
    "chars": 1661,
    "preview": "\"\"\"Shared utilities for skill-creator scripts.\"\"\"\n\nfrom pathlib import Path\n\n\n\ndef parse_skill_md(skill_path: Path) -> t"
  },
  {
    "path": ".claude/skills/zeroclaw/SKILL.md",
    "chars": 13671,
    "preview": "---\nname: zeroclaw\ndescription: \"Help users operate and interact with their ZeroClaw agent instance — through both the C"
  },
  {
    "path": ".claude/skills/zeroclaw/evals/evals.json",
    "chars": 856,
    "preview": "{\n  \"skill_name\": \"zeroclaw\",\n  \"evals\": [\n    {\n      \"id\": 0,\n      \"prompt\": \"how do i make my bot remember my name\","
  },
  {
    "path": ".claude/skills/zeroclaw/references/cli-reference.md",
    "chars": 9022,
    "preview": "# ZeroClaw CLI Reference\n\nComplete command reference for the `zeroclaw` binary.\n\n## Table of Contents\n\n1. [Agent](#agent"
  },
  {
    "path": ".claude/skills/zeroclaw/references/rest-api.md",
    "chars": 10153,
    "preview": "# ZeroClaw REST API Reference\n\nComplete endpoint reference for the ZeroClaw gateway HTTP API.\n\n## Table of Contents\n\n1. "
  },
  {
    "path": ".coderabbit.yaml",
    "chars": 1740,
    "preview": "# CodeRabbit configuration for ZeroClaw\n# Documentation: https://docs.coderabbit.ai/reference/configuration\n\nlanguage: e"
  },
  {
    "path": ".dockerignore",
    "chars": 938,
    "preview": "# Git history (may contain old secrets)\n.git\n.gitignore\n.githooks\n\n# Rust build artifacts (can be multiple GB)\ntarget\n\n#"
  },
  {
    "path": ".editorconfig",
    "chars": 41,
    "preview": "[*]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".envrc",
    "chars": 10,
    "preview": "use flake\n"
  },
  {
    "path": ".gemini/style-guide.md",
    "chars": 3271,
    "preview": "# ZeroClaw Code Style Guide\n\nThis style guide provides instructions for Gemini Code Assist when reviewing pull requests "
  },
  {
    "path": ".gitattributes",
    "chars": 1477,
    "preview": "# Git attributes for ZeroClaw\n# https://git-scm.com/docs/gitattributes\n\n# Auto detect text files and perform LF normaliz"
  },
  {
    "path": ".githooks/pre-commit",
    "chars": 199,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nif command -v gitleaks >/dev/null 2>&1; then\n  gitleaks protect --staged --redact"
  },
  {
    "path": ".githooks/pre-push",
    "chars": 1541,
    "preview": "#!/usr/bin/env bash\n#\n# pre-push hook — runs fmt, clippy, and tests before every push.\n# Install:  git config core.hooks"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 1742,
    "preview": "# Default owner for all files\n* @theonlyhennygod @JordanTheJet @SimianAstronaut7\n\n# Important functional modules\n/src/ag"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 3341,
    "preview": "name: Bug Report\ndescription: Report a reproducible defect in ZeroClaw\ntitle: \"[Bug]: \"\nlabels:\n  - bug\nbody:\n  - type: "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 651,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Security vulnerability report\n    url: https://github.com/zeroclaw-"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 3284,
    "preview": "name: Feature Request\ndescription: Propose an improvement or new capability\ntitle: \"[Feature]: \"\nlabels:\n  - enhancement"
  },
  {
    "path": ".github/actionlint.yaml",
    "chars": 71,
    "preview": "self-hosted-runner:\n    labels:\n        - blacksmith-2vcpu-ubuntu-2404\n"
  },
  {
    "path": ".github/codeql/codeql-config.yml",
    "chars": 282,
    "preview": "# CodeQL configuration for ZeroClaw\n#\n# We intentionally ignore integration tests under `tests/` because they often\n# co"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 955,
    "preview": "version: 2\n\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      interval: daily\n    target-bran"
  },
  {
    "path": ".github/label-policy.json",
    "chars": 393,
    "preview": "{\n  \"contributor_tier_color\": \"2ED9FF\",\n  \"contributor_tiers\": [\n    {\n      \"label\": \"distinguished contributor\",\n     "
  },
  {
    "path": ".github/labeler.yml",
    "chars": 2694,
    "preview": "\"docs\":\n  - changed-files:\n      - any-glob-to-any-file:\n          - \"docs/**\"\n          - \"**/*.md\"\n          - \"**/*.m"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 3987,
    "preview": "## Summary\n\nDescribe this PR in 2-5 bullets:\n\n- Base branch target (`master` for all contributions):\n- Problem:\n- Why it"
  },
  {
    "path": ".github/workflows/README.md",
    "chars": 492,
    "preview": "# Workflow Directory Layout\n\nGitHub Actions only loads workflow entry files from:\n\n- `.github/workflows/*.yml`\n- `.githu"
  },
  {
    "path": ".github/workflows/checks-on-pr.yml",
    "chars": 5620,
    "preview": "name: Quality Gate\n\non:\n  pull_request:\n    branches: [master]\n\nconcurrency:\n  group: checks-${{ github.event.pull_reque"
  },
  {
    "path": ".github/workflows/ci-run.yml",
    "chars": 6096,
    "preview": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\nconcurrency:\n  group: ci-${{ github"
  },
  {
    "path": ".github/workflows/cross-platform-build-manual.yml",
    "chars": 2500,
    "preview": "name: Cross-Platform Build\n\non:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nenv:\n  CARGO_TERM_COLOR: always\n  C"
  },
  {
    "path": ".github/workflows/master-branch-flow.md",
    "chars": 5850,
    "preview": "# Master Branch Delivery Flows\n\nThis document explains what runs when code is proposed to `master` and released.\n\nUse th"
  },
  {
    "path": ".github/workflows/pub-aur.yml",
    "chars": 5685,
    "preview": "name: Pub AUR Package\n\non:\n  workflow_call:\n    inputs:\n      release_tag:\n        description: \"Existing release tag (v"
  },
  {
    "path": ".github/workflows/pub-homebrew-core.yml",
    "chars": 8242,
    "preview": "name: Pub Homebrew Core\n\non:\n  workflow_dispatch:\n    inputs:\n      release_tag:\n        description: \"Existing release "
  },
  {
    "path": ".github/workflows/pub-scoop.yml",
    "chars": 5388,
    "preview": "name: Pub Scoop Manifest\n\non:\n  workflow_call:\n    inputs:\n      release_tag:\n        description: \"Existing release tag"
  },
  {
    "path": ".github/workflows/publish-crates-auto.yml",
    "chars": 4404,
    "preview": "name: Auto-sync crates.io\n\non:\n  push:\n    branches: [master]\n    paths:\n      - \"Cargo.toml\"\n\nconcurrency:\n  group: pub"
  },
  {
    "path": ".github/workflows/publish-crates.yml",
    "chars": 2553,
    "preview": "name: Publish to crates.io\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version to publish"
  },
  {
    "path": ".github/workflows/release-beta-on-push.yml",
    "chars": 12777,
    "preview": "name: Release Beta\n\non:\n  push:\n    branches: [master]\n\nconcurrency:\n  group: release-beta\n  cancel-in-progress: true\n\np"
  },
  {
    "path": ".github/workflows/release-stable-manual.yml",
    "chars": 16026,
    "preview": "name: Release Stable\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Stable version to releas"
  },
  {
    "path": ".github/workflows/tweet-release.yml",
    "chars": 11685,
    "preview": "name: Tweet Release\n\non:\n  # Called by release workflows AFTER all publish steps (docker, crates, website) complete.\n  w"
  },
  {
    "path": ".gitignore",
    "chars": 661,
    "preview": "/target\n/target-*/\nfirmware/*/target\nweb/dist/*\n!web/dist/.gitkeep\n*.db\n*.db-journal\n.DS_Store\n._*\n.wt-pr37/\n__pycache__"
  },
  {
    "path": ".markdownlint-cli2.yaml",
    "chars": 213,
    "preview": "config:\n  default: true\n  MD013: false\n  MD007: false\n  MD031: false\n  MD032: false\n  MD033: false\n  MD040: false\n  MD04"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 324,
    "preview": "{\n  \"recommendations\": [\n    \"rust-lang.rust-analyzer\",\n    \"vadimcn.vscode-lldb\",\n    \"serayuzgur.crates\",\n    \"bungcip"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1949,
    "preview": "{\n  \"version\": \"0.2.0\",\n  \"inputs\": [\n    {\n      \"id\": \"testName\",\n      \"description\": \"Exact test name to debug (e.g."
  },
  {
    "path": ".vscode/settings.json",
    "chars": 630,
    "preview": "{\n  \"git.autofetch\": true,\n  \"git.autofetchPeriod\": 90,\n  \"search.exclude\": {\n    \"**/target\": true\n  },\n  \"files.watche"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 3606,
    "preview": "{\n  \"version\": \"2.0.0\",\n  \"inputs\": [\n    {\n      \"id\": \"testFilter\",\n      \"description\": \"Test name or filter pattern\""
  },
  {
    "path": "CHANGELOG.md",
    "chars": 12,
    "preview": "# Changelog\n"
  },
  {
    "path": "CLAUDE.md",
    "chars": 3960,
    "preview": "# CLAUDE.md — ZeroClaw\n\n## Commands\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --all-targets -- -D warnings\ncargo "
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5230,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 23998,
    "preview": "# Contributing to ZeroClaw\n\nThanks for your interest in contributing to ZeroClaw! This guide will help you get started.\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 10822,
    "preview": "[workspace]\nmembers = [\".\", \"crates/robot-kit\"]\nresolver = \"2\"\n\n[package]\nname = \"zeroclawlabs\"\nversion = \"0.5.2\"\neditio"
  },
  {
    "path": "Dockerfile",
    "chars": 5826,
    "preview": "# syntax=docker/dockerfile:1.7\n\n# ── Stage 0: Frontend build ─────────────────────────────────────\nFROM node:22-alpine A"
  },
  {
    "path": "Dockerfile.ci",
    "chars": 823,
    "preview": "# Dockerfile.ci — CI/release image using pre-built binaries.\n# Used by release workflows to skip the ~60 min Rust compil"
  },
  {
    "path": "Dockerfile.debian",
    "chars": 4937,
    "preview": "# syntax=docker/dockerfile:1.7\n\n# ── Stage 0: Frontend build ─────────────────────────────────────\nFROM node:22-alpine A"
  },
  {
    "path": "Dockerfile.debian.ci",
    "chars": 1060,
    "preview": "# Dockerfile.debian.ci — CI/release Debian image using pre-built binaries.\n# Mirrors Dockerfile.ci but uses debian:bookw"
  },
  {
    "path": "Justfile",
    "chars": 1384,
    "preview": "# Justfile - Convenient command runner for ZeroClaw development\n# https://github.com/casey/just\n\n# Default recipe to dis"
  },
  {
    "path": "LICENSE-APACHE",
    "chars": 9723,
    "preview": "                              Apache License\n                        Version 2.0, January 2004\n                     http"
  },
  {
    "path": "LICENSE-MIT",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2025 ZeroClaw Labs\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "NOTICE",
    "chars": 1278,
    "preview": "ZeroClaw\nCopyright 2025 ZeroClaw Labs\n\nThis product includes software developed at ZeroClaw Labs (https://github.com/zer"
  },
  {
    "path": "README.ar.md",
    "chars": 32718,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.bn.md",
    "chars": 34431,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.cs.md",
    "chars": 36711,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.da.md",
    "chars": 35210,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.de.md",
    "chars": 38381,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.el.md",
    "chars": 36913,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.es.md",
    "chars": 38968,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.fi.md",
    "chars": 35844,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.fr.md",
    "chars": 39328,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.he.md",
    "chars": 31755,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.hi.md",
    "chars": 34289,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.hu.md",
    "chars": 36334,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.id.md",
    "chars": 37059,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.it.md",
    "chars": 38647,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.ja.md",
    "chars": 26606,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.ko.md",
    "chars": 27560,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.md",
    "chars": 35949,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.nb.md",
    "chars": 34956,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.nl.md",
    "chars": 37295,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.pl.md",
    "chars": 37785,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.pt.md",
    "chars": 38328,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.ro.md",
    "chars": 37857,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.ru.md",
    "chars": 36643,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.sv.md",
    "chars": 36791,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.th.md",
    "chars": 35847,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.tl.md",
    "chars": 36701,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.tr.md",
    "chars": 37270,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.uk.md",
    "chars": 36311,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.ur.md",
    "chars": 35472,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.vi.md",
    "chars": 34497,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "README.zh-CN.md",
    "chars": 23907,
    "preview": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/docs/assets/zeroclaw-bann"
  },
  {
    "path": "SECURITY.md",
    "chars": 2961,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 0.1.x   | "
  },
  {
    "path": "benches/agent_benchmarks.rs",
    "chars": 9983,
    "preview": "//! Performance benchmarks for ZeroClaw hot paths.\n//!\n//! Benchmarks cover:\n//!   - Tool dispatch (XML parsing, native "
  },
  {
    "path": "build.rs",
    "chars": 6328,
    "preview": "use std::fs;\nuse std::path::Path;\nuse std::process::Command;\nuse std::time::SystemTime;\n\nfn main() {\n    let dist_dir = "
  },
  {
    "path": "clippy.toml",
    "chars": 461,
    "preview": "# Clippy configuration for ZeroClaw.\n# Thresholds tuned to match codebase patterns and reduce noise from\n# existing allo"
  },
  {
    "path": "crates/robot-kit/Cargo.toml",
    "chars": 1807,
    "preview": "[package]\nname = \"zeroclaw-robot-kit\"\nversion = \"0.1.0\"\nedition = \"2021\"\nauthors = [\"theonlyhennygod\"]\nlicense = \"MIT OR"
  },
  {
    "path": "crates/robot-kit/PI5_SETUP.md",
    "chars": 13323,
    "preview": "# Raspberry Pi 5 Robot Setup Guide\n\nComplete guide to setting up a ZeroClaw-powered robot on Raspberry Pi 5.\n\n## Hardwar"
  },
  {
    "path": "crates/robot-kit/README.md",
    "chars": 5408,
    "preview": "# ZeroClaw Robot Kit\n\nA complete toolkit for building AI-powered robots with ZeroClaw. Designed for Raspberry Pi deploym"
  },
  {
    "path": "crates/robot-kit/SOUL.md",
    "chars": 2109,
    "preview": "# Buddy the Robot\n\nYou are Buddy, a friendly robot companion who loves to play with children!\n\n## Personality\n\n- **Playf"
  },
  {
    "path": "crates/robot-kit/robot.toml",
    "chars": 4555,
    "preview": "# ZeroClaw Robot Kit Configuration\n# Copy to ~/.zeroclaw/robot.toml\n\n# ================================================="
  },
  {
    "path": "crates/robot-kit/src/config.rs",
    "chars": 6963,
    "preview": "//! Robot configuration\n\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n/// Robot hardware configuration\n"
  },
  {
    "path": "crates/robot-kit/src/drive.rs",
    "chars": 13624,
    "preview": "//! Drive Tool - Motor control for omni-directional movement\n//!\n//! Supports multiple backends:\n//! - ROS2: Publishes g"
  },
  {
    "path": "crates/robot-kit/src/emote.rs",
    "chars": 11843,
    "preview": "//! Emote Tool - LED expressions and sound effects\n//!\n//! Control LED matrix/strips for robot \"expressions\" and play so"
  },
  {
    "path": "crates/robot-kit/src/lib.rs",
    "chars": 5237,
    "preview": "//! # ZeroClaw Robot Kit\n//!\n//! A standalone robotics toolkit that integrates with ZeroClaw for AI-powered robots.\n//!\n"
  },
  {
    "path": "crates/robot-kit/src/listen.rs",
    "chars": 6496,
    "preview": "//! Listen Tool - Speech-to-text via Whisper.cpp\n//!\n//! Records audio from microphone and transcribes using local Whisp"
  },
  {
    "path": "crates/robot-kit/src/look.rs",
    "chars": 8354,
    "preview": "//! Look Tool - Camera capture + vision model description\n//!\n//! Captures an image from the camera and optionally descr"
  },
  {
    "path": "crates/robot-kit/src/safety.rs",
    "chars": 18528,
    "preview": "//! Safety System - Collision avoidance, watchdogs, and emergency stops\n//!\n//! This module runs INDEPENDENTLY of the AI"
  },
  {
    "path": "crates/robot-kit/src/sense.rs",
    "chars": 14696,
    "preview": "//! Sense Tool - LIDAR, motion sensors, ultrasonic distance\n//!\n//! Provides environmental awareness through various sen"
  },
  {
    "path": "crates/robot-kit/src/speak.rs",
    "chars": 7463,
    "preview": "//! Speak Tool - Text-to-speech via Piper\n//!\n//! Converts text to speech using Piper TTS (fast, offline, runs on Pi).\n/"
  },
  {
    "path": "crates/robot-kit/src/tests.rs",
    "chars": 16320,
    "preview": "//! Integration tests for robot kit\n//!\n//! These tests verify the robot kit works correctly in various configurations:\n"
  },
  {
    "path": "crates/robot-kit/src/traits.rs",
    "chars": 3468,
    "preview": "//! Tool trait definition\n//!\n//! This defines the interface that all robot tools implement.\n//! It is compatible with Z"
  },
  {
    "path": "deny.toml",
    "chars": 1850,
    "preview": "# cargo-deny configuration — v2 schema\n# https://embarkstudios.github.io/cargo-deny/\n\n[advisories]\n# In v2, vulnerabilit"
  },
  {
    "path": "dev/README.md",
    "chars": 4897,
    "preview": "# ZeroClaw Development Environment\n\nA fully containerized development sandbox for ZeroClaw agents. This environment allo"
  },
  {
    "path": "dev/ci/Dockerfile",
    "chars": 660,
    "preview": "# syntax=docker/dockerfile:1.7\n\nFROM rust:1.92-slim@sha256:bf3368a992915f128293ac76917ab6e561e4dda883273c8f5c9f6f8ea37a3"
  },
  {
    "path": "dev/ci.sh",
    "chars": 4002,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nif [ -f \"dev/docker-compose.ci.yml\" ]; then\n  COMPOSE_FILE=\"dev/docker-compose.ci"
  },
  {
    "path": "dev/cli.sh",
    "chars": 3939,
    "preview": "#!/bin/bash\nset -e\n\n# Detect execution context (root or dev/)\nif [ -f \"dev/docker-compose.yml\" ]; then\n    BASE_DIR=\"dev"
  },
  {
    "path": "dev/config.template.toml",
    "chars": 334,
    "preview": "workspace_dir = \"/zeroclaw-data/workspace\"\nconfig_path = \"/zeroclaw-data/.zeroclaw/config.toml\"\n# This is the Ollama Bas"
  },
  {
    "path": "dev/docker-compose.ci.yml",
    "chars": 646,
    "preview": "name: zeroclaw-local-ci\n\nservices:\n    local-ci:\n        build:\n            context: ..\n            dockerfile: dev/ci/D"
  },
  {
    "path": "dev/docker-compose.yml",
    "chars": 1960,
    "preview": "# Development Environment for ZeroClaw Agentic Testing\n#\n# Use this for:\n# - Running the agent in a sandboxed environmen"
  },
  {
    "path": "dev/recompute_contributor_tiers.sh",
    "chars": 9086,
    "preview": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nSCRIPT_NAME=\"$(basename \"$0\")\"\n\nusage() {\n  cat <<USAGE\nRecompute contributor ti"
  },
  {
    "path": "dev/sandbox/Dockerfile",
    "chars": 1164,
    "preview": "FROM ubuntu:22.04@sha256:c7eb020043d8fc2ae0793fb35a37bff1cf33f156d4d4b12ccc7f3ef8706c38b1\n\n# Prevent interactive prompts"
  },
  {
    "path": "dev/test-termux-release.sh",
    "chars": 7735,
    "preview": "#!/usr/bin/env bash\n# Termux release validation script\n# Validates the aarch64-linux-android release artifact for Termux"
  },
  {
    "path": "dist/aur/.SRCINFO",
    "chars": 463,
    "preview": "pkgbase = zeroclaw\n\tpkgdesc = Zero overhead. Zero compromise. 100% Rust. The fastest, smallest AI assistant.\n\tpkgver = 0"
  },
  {
    "path": "dist/aur/PKGBUILD",
    "chars": 1048,
    "preview": "# Maintainer: zeroclaw-labs <bot@zeroclaw.dev>\npkgname=zeroclaw\npkgver=0.4.3\npkgrel=1\npkgdesc=\"Zero overhead. Zero compr"
  },
  {
    "path": "dist/scoop/zeroclaw.json",
    "chars": 992,
    "preview": "{\n    \"version\": \"0.5.2\",\n    \"description\": \"Zero overhead. Zero compromise. 100% Rust. The fastest, smallest AI assist"
  },
  {
    "path": "docker-compose.yml",
    "chars": 2206,
    "preview": "# ZeroClaw Docker Compose Example\n# \n# Quick start:\n#   1. Copy this file and set your API key\n#   2. Run: docker compos"
  },
  {
    "path": "docs/README.ar.md",
    "chars": 6128,
    "preview": "# مركز توثيق ZeroClaw\n\nهذه الصفحة هي نقطة الدخول الرئيسية لنظام التوثيق.\n\nآخر تحديث: **20 فبراير 2026**.\n\nالمراكز المترج"
  },
  {
    "path": "docs/README.bn.md",
    "chars": 6272,
    "preview": "# ZeroClaw ডকুমেন্টেশন হাব\n\nএই পৃষ্ঠাটি ডকুমেন্টেশন সিস্টেমের প্রধান প্রবেশ বিন্দু।\n\nসর্বশেষ আপডেট: **২০ ফেব্রুয়ারি ২০২"
  },
  {
    "path": "docs/README.cs.md",
    "chars": 6364,
    "preview": "# Dokumentační hub ZeroClaw\n\nTato stránka je hlavním vstupním bodem do dokumentačního systému.\n\nPoslední aktualizace: **"
  },
  {
    "path": "docs/README.da.md",
    "chars": 6410,
    "preview": "# ZeroClaw Dokumentationshub\n\nDenne side er det primære indgangspunkt til dokumentationssystemet.\n\nSidst opdateret: **20"
  },
  {
    "path": "docs/README.de.md",
    "chars": 6484,
    "preview": "# ZeroClaw Dokumentations-Hub\n\nDiese Seite ist der zentrale Einstiegspunkt in das Dokumentationssystem.\n\nZuletzt aktuali"
  },
  {
    "path": "docs/README.el.md",
    "chars": 6490,
    "preview": "# Κέντρο Τεκμηρίωσης ZeroClaw\n\nΑυτή η σελίδα είναι το κύριο σημείο εισόδου για το σύστημα τεκμηρίωσης.\n\nΤελευταία ενημέρ"
  },
  {
    "path": "docs/README.es.md",
    "chars": 6594,
    "preview": "# Centro de Documentación ZeroClaw\n\nEsta página es el punto de entrada principal del sistema de documentación.\n\nÚltima a"
  },
  {
    "path": "docs/README.fi.md",
    "chars": 6488,
    "preview": "# ZeroClaw-dokumentaatiokeskus\n\nTämä sivu on dokumentaatiojärjestelmän ensisijainen aloituspiste.\n\nViimeksi päivitetty: "
  },
  {
    "path": "docs/README.fr.md",
    "chars": 6541,
    "preview": "# Hub de Documentation ZeroClaw\n\nCette page est le point d'entrée principal du système de documentation.\n\nDernière mise "
  },
  {
    "path": "docs/README.he.md",
    "chars": 5989,
    "preview": "# מרכז התיעוד של ZeroClaw\n\nדף זה הוא נקודת הכניסה הראשית למערכת התיעוד.\n\nעדכון אחרון: **20 בפברואר 2026**.\n\nמרכזים מתורג"
  },
  {
    "path": "docs/README.hi.md",
    "chars": 6203,
    "preview": "# ZeroClaw दस्तावेज़ीकरण केंद्र\n\nयह पृष्ठ दस्तावेज़ीकरण प्रणाली का प्राथमिक प्रवेश बिंदु है।\n\nअंतिम अपडेट: **20 फरवरी 20"
  },
  {
    "path": "docs/README.hu.md",
    "chars": 6764,
    "preview": "# ZeroClaw Dokumentációs Központ\n\nEz az oldal a dokumentációs rendszer fő belépési pontja.\n\nUtolsó frissítés: **2026. fe"
  },
  {
    "path": "docs/README.id.md",
    "chars": 6618,
    "preview": "# Pusat Dokumentasi ZeroClaw\n\nHalaman ini adalah titik masuk utama untuk sistem dokumentasi.\n\nPembaruan terakhir: **21 F"
  },
  {
    "path": "docs/README.it.md",
    "chars": 6834,
    "preview": "# Hub della Documentazione ZeroClaw\n\nQuesta pagina è il punto di ingresso principale del sistema di documentazione.\n\nUlt"
  },
  {
    "path": "docs/README.ja.md",
    "chars": 3863,
    "preview": "# ZeroClaw ドキュメントハブ(日本語)\n\nこのページは日本語のドキュメント入口です。\n\n最終同期日: **2026-02-18**。\n\n> 注: コマンド名・設定キー・API パスは英語のまま記載します。実装の一次情報は英語版ドキ"
  },
  {
    "path": "docs/README.ko.md",
    "chars": 5678,
    "preview": "# ZeroClaw 문서 허브\n\n이 페이지는 문서 시스템의 기본 진입점입니다.\n\n마지막 업데이트: **2026년 2월 21일**.\n\n현지화된 허브: [简体中文](README.zh-CN.md) · [日本語](READM"
  },
  {
    "path": "docs/README.md",
    "chars": 5881,
    "preview": "# ZeroClaw Documentation Hub\n\nThis page is the primary entry point for the documentation system.\n\nLast refreshed: **Febr"
  },
  {
    "path": "docs/README.nb.md",
    "chars": 6659,
    "preview": "# ZeroClaw Dokumentasjonshub\n\nDenne siden er hovedinngangen til dokumentasjonssystemet.\n\nSist oppdatert: **21. februar 2"
  },
  {
    "path": "docs/README.nl.md",
    "chars": 6387,
    "preview": "# ZeroClaw Documentatiehub\n\nDeze pagina is het primaire toegangspunt voor het documentatiesysteem.\n\nLaatst bijgewerkt: *"
  },
  {
    "path": "docs/README.pl.md",
    "chars": 6521,
    "preview": "# Centrum Dokumentacji ZeroClaw\n\nTa strona jest głównym punktem wejścia do systemu dokumentacji.\n\nOstatnia aktualizacja:"
  },
  {
    "path": "docs/README.pt.md",
    "chars": 6518,
    "preview": "# Centro de Documentação ZeroClaw\n\nEsta página é o ponto de entrada principal do sistema de documentação.\n\nÚltima atuali"
  },
  {
    "path": "docs/README.ro.md",
    "chars": 6561,
    "preview": "# Centrul de Documentație ZeroClaw\n\nAceastă pagină este punctul de intrare principal al sistemului de documentație.\n\nUlt"
  },
  {
    "path": "docs/README.ru.md",
    "chars": 4578,
    "preview": "# Документация ZeroClaw (Русский)\n\nЭта страница — русскоязычная точка входа в документацию.\n\nПоследняя синхронизация: **"
  },
  {
    "path": "docs/README.sv.md",
    "chars": 6460,
    "preview": "# ZeroClaw Dokumentationshubb\n\nDenna sida är den primära ingångspunkten för dokumentationssystemet.\n\nSenast uppdaterad: "
  },
  {
    "path": "docs/README.th.md",
    "chars": 6301,
    "preview": "# ศูนย์กลางเอกสาร ZeroClaw\n\nหน้านี้เป็นจุดเริ่มต้นหลักของระบบเอกสาร\n\nอัปเดตล่าสุด: **21 กุมภาพันธ์ 2026**\n\nศูนย์กลางภาษา"
  },
  {
    "path": "docs/README.tl.md",
    "chars": 6529,
    "preview": "# Sentro ng Dokumentasyon ng ZeroClaw\n\nAng pahinang ito ang pangunahing entry point ng sistema ng dokumentasyon.\n\nHuling"
  },
  {
    "path": "docs/README.tr.md",
    "chars": 6468,
    "preview": "# ZeroClaw Dokümantasyon Merkezi\n\nBu sayfa, dokümantasyon sisteminin ana giriş noktasıdır.\n\nSon güncelleme: **21 Şubat 2"
  },
  {
    "path": "docs/README.uk.md",
    "chars": 6463,
    "preview": "# Центр документації ZeroClaw\n\nЦя сторінка є основною точкою входу до системи документації.\n\nОстаннє оновлення: **21 лют"
  },
  {
    "path": "docs/README.ur.md",
    "chars": 6200,
    "preview": "# ZeroClaw دستاویزات کا مرکز\n\nیہ صفحہ دستاویزات کے نظام کا بنیادی داخلی نقطہ ہے۔\n\nآخری تازہ کاری: **21 فروری 2026**۔\n\nمق"
  },
  {
    "path": "docs/README.vi.md",
    "chars": 6103,
    "preview": "# Hub Tài liệu ZeroClaw (Tiếng Việt)\n\nĐây là trang chủ tiếng Việt của hệ thống tài liệu.\n\nĐồng bộ lần cuối: **2026-02-21"
  },
  {
    "path": "docs/README.zh-CN.md",
    "chars": 5134,
    "preview": "# ZeroClaw 文档导航(简体中文)\n\n这是文档系统的中文入口页。\n\n最后对齐:**2026-03-14**。\n\n> 说明:命令、配置键、API 路径保持英文;实现细节以英文文档为准。\n\n## 快速入口\n\n| 我想要… | 建议阅读 "
  },
  {
    "path": "docs/SUMMARY.ar.md",
    "chars": 3578,
    "preview": "# ملخص توثيق ZeroClaw (جدول المحتويات الموحد)\n\nهذا الملف هو جدول المحتويات المرجعي لنظام التوثيق.\n\n> 📖 [النسخة الإنجليزي"
  },
  {
    "path": "docs/SUMMARY.bn.md",
    "chars": 3599,
    "preview": "# ZeroClaw ডকুমেন্টেশন সারাংশ (একীভূত সূচিপত্র)\n\nএই ফাইলটি ডকুমেন্টেশন সিস্টেমের প্রামাণিক সূচিপত্র।\n\n> 📖 [ইংরেজি সংস্কর"
  },
  {
    "path": "docs/SUMMARY.cs.md",
    "chars": 3683,
    "preview": "# Souhrn dokumentace ZeroClaw (Jednotný obsah)\n\nTento soubor je kanonický obsah dokumentačního systému.\n\n> 📖 [Anglická v"
  },
  {
    "path": "docs/SUMMARY.da.md",
    "chars": 3733,
    "preview": "# ZeroClaw Dokumentationsoversigt (Samlet indholdsfortegnelse)\n\nDenne fil er den kanoniske indholdsfortegnelse for dokum"
  },
  {
    "path": "docs/SUMMARY.de.md",
    "chars": 3812,
    "preview": "# ZeroClaw Dokumentationsübersicht (Einheitliches Inhaltsverzeichnis)\n\nDiese Datei ist das kanonische Inhaltsverzeichnis"
  },
  {
    "path": "docs/SUMMARY.el.md",
    "chars": 3768,
    "preview": "# Περίληψη Τεκμηρίωσης ZeroClaw (Ενοποιημένος Πίνακας Περιεχομένων)\n\nΑυτό το αρχείο αποτελεί τον κανονικό πίνακα περιεχο"
  },
  {
    "path": "docs/SUMMARY.es.md",
    "chars": 3778,
    "preview": "# Resumen de Documentación ZeroClaw (Tabla de Contenidos Unificada)\n\nEste archivo constituye la tabla de contenidos canó"
  },
  {
    "path": "docs/SUMMARY.fi.md",
    "chars": 3749,
    "preview": "# ZeroClaw-dokumentaation yhteenveto (Yhtenäinen sisällysluettelo)\n\nTämä tiedosto muodostaa dokumentaatiojärjestelmän ka"
  },
  {
    "path": "docs/SUMMARY.fr.md",
    "chars": 3808,
    "preview": "# Sommaire de la documentation ZeroClaw (Table des matières unifiée)\n\nCe fichier constitue la table des matières canoniq"
  },
  {
    "path": "docs/SUMMARY.he.md",
    "chars": 3510,
    "preview": "# סיכום תיעוד ZeroClaw (תוכן עניינים מאוחד)\n\nקובץ זה מהווה את תוכן העניינים הקנוני של מערכת התיעוד.\n\n> 📖 [English versio"
  },
  {
    "path": "docs/SUMMARY.hi.md",
    "chars": 3614,
    "preview": "# ZeroClaw दस्तावेज़ीकरण सारांश (एकीकृत विषय सूची)\n\nयह फ़ाइल दस्तावेज़ीकरण प्रणाली की कैनोनिकल विषय सूची है।\n\n> 📖 [Engli"
  },
  {
    "path": "docs/SUMMARY.hu.md",
    "chars": 3912,
    "preview": "# ZeroClaw Dokumentáció Összefoglaló (Egységes tartalomjegyzék)\n\nEz a fájl a dokumentációs rendszer kanonikus tartalomje"
  },
  {
    "path": "docs/SUMMARY.id.md",
    "chars": 3826,
    "preview": "# Ringkasan Dokumentasi ZeroClaw (Daftar Isi Terpadu)\n\nFile ini adalah daftar isi kanonik untuk sistem dokumentasi.\n\n> 📖"
  },
  {
    "path": "docs/SUMMARY.it.md",
    "chars": 3942,
    "preview": "# Riepilogo della Documentazione ZeroClaw (Indice Unificato)\n\nQuesto file è l'indice canonico del sistema di documentazi"
  },
  {
    "path": "docs/SUMMARY.ja.md",
    "chars": 3335,
    "preview": "# ZeroClaw ドキュメント目次(統合目次)\n\nこのファイルはドキュメントシステムの正規の目次です。\n\n> 📖 [English version](SUMMARY.md)\n\n最終更新:**2026年2月18日**。\n\n## 言語別入口"
  },
  {
    "path": "docs/SUMMARY.ko.md",
    "chars": 3489,
    "preview": "# ZeroClaw 문서 요약 (통합 목차)\n\n이 파일은 문서 시스템의 정식 목차입니다.\n\n> 📖 [English version](SUMMARY.md)\n\n마지막 업데이트: **2026년 2월 18일**.\n\n## 언어"
  },
  {
    "path": "docs/SUMMARY.md",
    "chars": 6291,
    "preview": "# ZeroClaw Docs Summary (Unified TOC)\n\nThis file is the canonical table of contents for the documentation system.\n\nLast "
  },
  {
    "path": "docs/SUMMARY.nb.md",
    "chars": 3908,
    "preview": "# ZeroClaw Dokumentasjonssammendrag (Samlet innholdsfortegnelse)\n\nDenne filen er den kanoniske innholdsfortegnelsen for "
  },
  {
    "path": "docs/SUMMARY.nl.md",
    "chars": 3767,
    "preview": "# ZeroClaw Documentatieoverzicht (Uniforme Inhoudsopgave)\n\nDit bestand is de canonieke inhoudsopgave van het documentati"
  },
  {
    "path": "docs/SUMMARY.pl.md",
    "chars": 3744,
    "preview": "# Podsumowanie Dokumentacji ZeroClaw (Ujednolicony Spis Treści)\n\nTen plik stanowi kanoniczny spis treści systemu dokumen"
  },
  {
    "path": "docs/SUMMARY.pt.md",
    "chars": 3732,
    "preview": "# Resumo da Documentação ZeroClaw (Índice Unificado)\n\nEste arquivo constitui o índice canônico do sistema de documentaçã"
  },
  {
    "path": "docs/SUMMARY.ro.md",
    "chars": 3735,
    "preview": "# Rezumatul Documentației ZeroClaw (Cuprins Unificat)\n\nAcest fișier constituie cuprinsul canonic al sistemului de docume"
  },
  {
    "path": "docs/SUMMARY.ru.md",
    "chars": 3752,
    "preview": "# Содержание документации ZeroClaw (Единое оглавление)\n\nЭтот файл является каноническим оглавлением системы документации"
  },
  {
    "path": "docs/SUMMARY.sv.md",
    "chars": 3768,
    "preview": "# ZeroClaw Dokumentationssammanfattning (Enhetlig Innehållsförteckning)\n\nDenna fil utgör den kanoniska innehållsförteckn"
  },
  {
    "path": "docs/SUMMARY.th.md",
    "chars": 3600,
    "preview": "# สรุปเอกสาร ZeroClaw (สารบัญรวม)\n\nไฟล์นี้เป็นสารบัญหลักของระบบเอกสาร\n\n> 📖 [English version](SUMMARY.md)\n\nอัปเดตล่าสุด: "
  },
  {
    "path": "docs/SUMMARY.tl.md",
    "chars": 3775,
    "preview": "# Buod ng Dokumentasyon ng ZeroClaw (Pinag-isang Talaan ng Nilalaman)\n\nAng file na ito ang canonical na talaan ng nilala"
  },
  {
    "path": "docs/SUMMARY.tr.md",
    "chars": 3678,
    "preview": "# ZeroClaw Dokümantasyon Özeti (Birleşik İçindekiler)\n\nBu dosya, dokümantasyon sisteminin kanonik içindekiler tablosudur"
  }
]

// ... and 615 more files (download for full content)

About this extraction

This page contains the full source code of the zeroclaw-labs/zeroclaw GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 815 files (10.3 MB), approximately 2.7M tokens, and a symbol index with 11958 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!