docs: agent guides, extending & report-schema references, and coder-eval.com/docs migration#48
Conversation
|
Claude finished @uipreliga's task in 1m 31s —— View job Reviewing PR #48: Documentation Updates
|
Add missing documentation and fix defects found auditing docs against code. New docs: - docs/agents/ — move Codex guide to agents/CODEX.md; add CLAUDE_CODE.md (default agent) and ANTIGRAVITY.md (Gemini agent). - docs/EXTENDING.md — plugin SPI: custom agents, custom criteria, and register_pricing. - docs/REPORT_SCHEMA.md — field-level reference for run.json / task.json / variant.json / experiment.json / suite.json. USER_GUIDE: - document the `aggregate` command and `report --format`; complete the run flag table (--type/--repeats/--resume/--sample/--sample-per-stratum/ --include-skipped) and add plan --experiment, evaluate --run-dir. - add a Suite Thresholds & Classification Metrics section; add agent auth env vars (CODEX_*, GEMINI_API_KEY, ANTIGRAVITY_MODEL); clarify UIPATH_PLUGIN_MARKETPLACE_DIR vs PLUGIN_TOOLS_DIR. TASK_DEFINITION_GUIDE: - add the missing commands_efficiency and classification_match sections and complete the scoring-types summary (all 14 types). - fix llm_judge max_tokens default (1000 -> 2000), agent_judge default tools (read-only Bash/Read/Glob/Grep, not Write/Edit), and the llm_judge verdict wording (forced submit_verdict tool call). Other: - fix phantom experiments/example.yaml references in the agent guides. - regroup mkdocs nav (Agents / Running & scaling / Extending / Reference); align README/index/llms.txt doc indexes. - remove docs/IDEAS.md (internal backlog) and its mkdocs exclude. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs now serve from the Astro/Starlight site at coder-eval.com/docs (synced from this repo's docs/), replacing the uipath.github.io mkdocs site. - llms.txt: rewrite all absolute doc URLs to coder-eval.com/docs/* using the Starlight slug scheme (lowercase-kebab, no trailing slash — e.g. USER_GUIDE -> user-guide, agents/CODEX -> agents/codex). llms.txt is copied verbatim by the site's sync script, so these must be absolute + correct. - README.md: repoint the Docs badge and the comparison / full-docs links to coder-eval.com/docs; remove the Ruff badge. - mkdocs.yml: set site_url to the new canonical https://coder-eval.com/docs/. Relative .md cross-links inside docs are left untouched — the site's sync-docs script rewrites them to routes at build time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The PyPI project's Documentation link now points at the docs site rather than the GitHub docs/ tree, matching the Homepage and the README docs links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…exes PR #37 shipped a packaged CI gate — a composite GitHub Action (action.yml), JUnit XML output (reports_junit.py + `run --junit-xml` / `report -f junit`), and an optional per-task score floor — but the docs site had no reference for it (only the walkthrough tutorial and the README section). - docs/CI_GATE.md (new): reference for the composite Action (inputs/outputs, env-passthrough credentials, minimum-task-score floor, security), JUnit output (both entry points, what it's built from, GitHub/Azure ingestion). - Add it to the index surfaces: docs/index.md, mkdocs.yml nav (Running & scaling), llms.txt, and the README documentation table. - tutorials/02-ci-pipeline.md: repoint the action reference link from the off-site ../../README.md (broke the strict docs build and the Starlight link rewriter) to the new on-site CI_GATE.md. mkdocs build --strict passes clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CE027 (doc/env parity) flags the copy-pasteable `NAME=value` assignment form for framework-prefixed env vars unless a Settings field or a literal os.environ read backs them. CODER_EVAL_RAW_SDK_LOG is genuinely consumed, but via an indirected module constant (_logging.py: `_RAW_SDK_LOG_ENV = "..."` then `os.environ.get(_RAW_SDK_LOG_ENV)`), which the rule's src-scan can't see. The rule intentionally leaves prose mentions alone, so state it as prose rather than an assignment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f0f5848 to
b7c4d03
Compare
The project's prose product name is "Coder Eval" (two words), but many docs — especially page titles/headings — spelled it "coder_eval", the module/repo identifier. Standardize prose and titles to the brand name via a context-aware pass that leaves every technical identifier untouched: the coder-eval CLI/package (hyphen), Python modules (coder_eval.models/.plugins/.agents/.criteria), paths (src/coder_eval, coder_eval/resources), repo URLs (UiPath/coder_eval[.git]), script names (coder_eval_entrypoint.sh), and anything inside code fences or inline backticks. 47 prose/title occurrences across docs/ + README. mkdocs build --strict and CE027 (doc/env parity) both pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub Pages is not yet switched on for this repo on uipath.github.io, and the published docs currently serve from coder-eval.com/docs (synced separately by the website). Comment out the `push:` trigger so the `Docs / publish (push)` job no longer runs on merges to main; `workflow_dispatch` is kept so it can be run manually and the trigger re-enabled by uncommenting once Pages is on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bai-uipath
left a comment
There was a problem hiding this comment.
Fix what you agree with, otherwise lgtm — the docs land well; my notes are all about the nav/IA, none blocking.
Nav organization
- Top-level sprawl — 8 groups, two of them single-item.
ExtendingandReferenceeach wrap exactly one page, so the group label just restates the page title (Extending→ "Extending Coder Eval",Reference→ "Report Schema") and costs an expand to reach one doc. Fold the long tail under oneAdvancedgroup and the top level drops to 6 with both single-item groups gone:
- Home
- Tutorials
- Guides: User Guide, Task Definition Guide
- Agents: Claude Code, Codex, Antigravity
- Advanced: A/B Experiments, Bring Your Own Dataset, Docker Isolation,
CI Gate & GitHub Action, Extending Coder Eval, Report Schema
- How It Compares
Report Schemais the one page that might earn its own group. It targets JSON consumers (dashboards, CI parsers), not people running evals — a distinct audience. But with one page today, aReferencegroup isn't justified yet; merge it intoAdvancednow and split it back out the moment there's a second reference doc.Running & scalingis a grab-bag. "Scaling" doesn't naturally cover Docker isolation or CI gating — the group is really "everything that isn't a tutorial or an agent," which is whatAdvancedsays more honestly.- Ordering is curated, not alphabetical — keep it that way. Home → Tutorials → Guides → Agents is the right journey-first order; don't sort it A–Z. Minor: inside
Running & scalingthe order (A/B, BYOD, Docker, CI) is neither alphabetical nor a journey — reorder or leave it, doesn't matter much once it'sAdvanced. - Capitalization is inconsistent across group labels. Title Case (
Home,Tutorials,Guides,Agents,Extending,Reference) mixed with sentence case (Running & scaling,How it compares). Pick one — I'd go Title Case for section headers (Running & Scaling,How It Compares).
Follow-up, not this PR
- Four parallel doc indexes now need hand-syncing — the mkdocs
nav, the README docs table,docs/index.md, anddocs/llms.txt. Adding a doc means editing four places and they drift. Out of scope here, but whatever grouping you settle on above, apply it to at leastnav+index.mdso the two grouped surfaces agree, and consider generating the flat indexes from one source later.
Minor: the docs.yml push-trigger disable is the only behavioral change in an otherwise docs PR — it's well-commented and reversible, fine to ship.
Review feedback on PR #48 (nav/IA): - Merge "Running & scaling", "Extending", and "Reference" into a single "Advanced" group — the two single-item groups only restated their page titles and cost an expand to reach one doc. Top level drops 8 → 6. - Title Case the remaining sentence-case label ("How It Compares"). - Reorder the flat doc indexes (docs/index.md, README, llms.txt) so agent guides sit right after the guides, matching the nav order. Report Schema can split back out into its own "Reference" group once a second reference doc exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
uipreliga
left a comment
There was a problem hiding this comment.
Review: coder_eval — pr:48 (25 files) axis:1,2,3,4,5,6,7,8
Scope: pr:48 (25 files) axis:1,2,3,4,5,6,7,8 · branch docs/agent-guides-extending-report-schema · 57d3ad9 · 2026-07-24T00:46Z · workflow variant
Change class: trivial — documentation, MkDocs site config, and package metadata only; no product code path is touched (the only non-docs edits are a commented-out docs auto-publish trigger, nav/site_url, and a Documentation URL)
The engine is healthy (code quality, typing, security, architecture and error handling all 9.7+, zero high-severity findings, no production-code defects), but risk is concentrated in the documentation adopters actually execute: an unresolvable CI action pin, a run command that silently evaluates zero tasks and exits 0, and two harness contracts (stratified-sample seeding, effective allowed_tools baseline) whose omission lets identical agent output produce a different task score — no code needs fixing, but about a day of doc and release-tag corrections is required before these pages are safe to copy-paste.
Summary
| Axis | Score | 🔴 | 🟠 | 🟡 | 🔵 | Top Issue |
|---|---|---|---|---|---|---|
| 1. Code Quality & Style | 9.7 / 10 | 0 | 0 | 0 | 3 | docs/EXTENDING.md prints calculate_cost with keyword names that do not exist on the real function |
| 2. Type Safety | 9.9 / 10 | 0 | 0 | 0 | 1 | EXTENDING.md config-class snippet omits the # type: ignore[assignment] every in-repo instance of that discriminator override carries |
| 3. Test Health | 9.4 / 10 | 0 | 0 | 1 | 1 | Disabling the docs.yml push: trigger leaves mkdocs gh-deploy --strict reachable only via workflow_dispatch — no automatic mkdocs/link check remains in CI |
| 4. Security | 9.8 / 10 | 0 | 0 | 0 | 2 | docs/CI_GATE.md Security section omits workflow-hardening guidance (least-privilege permissions: and SHA-pinning the recommended third-party action) |
| 5. Architecture & Design | 9.8 / 10 | 0 | 0 | 0 | 2 | EXTENDING.md lists discard_pending_turn() as an optional override, contradicting the Agent base-class contract |
| 6. Error Handling & Resilience | 9.8 / 10 | 0 | 0 | 0 | 2 | docs/CI_GATE.md overstates the score gate's NaN handling — non-finite rows are skipped, not failed closed |
| 7. API Surface & Maintainability | 8 / 10 | 0 | 1 | 2 | 0 | New CI reference tells every adopter to pin UiPath/coder_eval@v0, a ref that does not exist on the repo |
| 8. Evaluation Harness Quality | 6.4 / 10 | 0 | 2 | 3 | 1 | Agent guides publish coder-eval run experiments/<file>.yaml (experiment YAML passed positionally as TASK_FILE) — the documented command does not work |
Overall Score: 9.1 / 10 · Weakest Axis: Evaluation Harness Quality at 6.4 / 10
Totals: 🔴 0 · 🟠 3 · 🟡 6 · 🔵 12 across 8 axes.
Blockers
- [Axis 7] New CI reference tells every adopter to pin
UiPath/coder_eval@v0, a ref that does not exist on the repo (docs/CI_GATE.md:22) —docs/CI_GATE.md:22is the canonical, newly-published reference for the composite Action and its copy-paste snippet reads:
- uses: UiPath/coder_eval@v0 # becomes @v1 once 1.0.0 ships; @vX.Y.Z pins exactly(repeated verbatim at docs/CI_GATE.md:67). But no v0 ref exists on the repository at PR HEAD. Verified two ways: gh api repos/UiPath/coder_eval/tags --paginate -q '.[].name' returns only v0.8.9 v0.8.8 v0.8.7 v0.8.6 v0.8.5 v0.8.4 v0.8.3 v0.8.2 v0.0.1, and gh api repos/UiPath/coder_eval/git/matching-refs/tags/v0 returns only those same v0.8.x tags — no bare refs/tags/v0. The branch list (gh api repos/UiPath/coder_eval/branches) likewise contains no v0. GitHub Actions resolves @v0 as a tag-or-branch ref, so every user who copies this snippet gets a hard Unable to resolve action UiPath/coder_eval@v0 at job start.
The moving-major-tag machinery exists but has never run against a release: .github/workflows/release.yml:217-227 (Move major action tag (vN -> this release) → MAJOR="v${VERSION%%.*}"; git tag -f "$MAJOR" "v${VERSION}"; git push -f origin "$MAJOR") — the tag will only appear on the next cut release.
Fix: either (a) cut/push the v0 tag before merging this doc (a one-line git tag -f v0 v0.8.9 && git push -f origin v0), or (b) change the documented pin to a ref that resolves today, e.g. - uses: UiPath/coder_eval@v0.8.9 # pin exactly; @v0 tracks the latest 0.x once published. Note README.md:105/:142 and docs/tutorials/02-ci-pipeline.md:172 carry the same @v0 (pre-existing), so fix (a) resolves all four sites at once.
2. [Axis 8] Agent guides publish coder-eval run experiments/<file>.yaml (experiment YAML passed positionally as TASK_FILE) — the documented command does not work (docs/agents/ANTIGRAVITY.md:63) — ANTIGRAVITY.md:60-63 says "Or override the agent type for every task in an experiment:" followed by coder-eval run experiments/model-comparison.yaml --type antigravity; docs/agents/CODEX.md:75-78 carries the identical form (coder-eval run experiments/model-comparison.yaml --type codex) and that line was edited by this PR. The positional argument of run is task_files (src/coder_eval/cli/run_command.py:126-129: "Path(s) to task YAML file(s). Defaults to all tasks/ recursively."); experiments are supplied via --experiment, -e (run_command.py:229-234). I executed the documented form: uv run coder-eval run experiments/model-comparison.yaml --run-dir tmp/_probe_run → "Skipping task file experiments/model-comparison.yaml — ValueError: Invalid task definition: 2 validation errors for TaskDefinition (task_id, success_criteria)", then "Running 0 task(s)", "Results: 0/0 succeeded", process exit code 0. A user (or a CI job) that copies this line gets a green run that evaluated nothing. Fix both files to the form already used in docs/AB_EXPERIMENTS.md:37 — coder-eval run -e experiments/model-comparison.yaml --type antigravity (same for codex).
3. [Axis 8] New --sample-per-stratum row (docs/USER_GUIDE.md:46) omits that stratified sampling re-draws every run; dataset.sample_seed — the knob that pins it — is documented nowhere in docs/ (docs/USER_GUIDE.md:46) — The new flag rows read: line 45 | --sample N | For dataset-backed tasks, run a fixed-seed random N-row sample (reproducible; cheap smoke test). ... | and line 46 | --sample-per-stratum N | For dataset-backed tasks, keep up to N rows per stratum (stratify_field). Overridden by --sample. |. The juxtaposition implies both are reproducible, but src/coder_eval/orchestration/task_loader.py:399-403 seeds only the flat sample (_SMOKE_SAMPLE_SEED = 0, line 27, used at line 405) while the stratified path uses stratum_seed = ds.sample_seed — None by default, so random.Random(None) re-draws a different row set on every run. A suite gated on pass_rate / classification suite_thresholds therefore scores a different row set each run for identical agent behaviour, and dataset.sample_seed (the knob that pins it) appears in no file under docs/ (grep -rn sample_seed docs/ → no hits). Add the caveat to line 46 and document dataset.sample_seed — e.g. "stratified sampling re-draws each run unless dataset.sample_seed is set; set it to pin a reproducible sample".
Non-blocking, but please consider before merge
- [Axis 3] Disabling the docs.yml
push:trigger leavesmkdocs gh-deploy --strictreachable only via workflow_dispatch — no automatic mkdocs/link check remains in CI (.github/workflows/docs.yml:18) — The PR replaces the trigger with# push:/# branches: [main](lines 18-19), leaving onlyworkflow_dispatch:(line 24). The job's final step isrun: mkdocs gh-deploy --force --strict(line 55) — that--strictbuild was the ONLY mechanical check in the repo that fails on a broken internal link, anav:entry pointing at a missing file, or an orphaned page. I confirmed nothing replaces it:grep -n -i "mkdocs|docs/" .github/workflows/pr-checks.ymlreturns zero hits;tests/lint/runner.py'sALL_RULESare all AST rules over.pyfiles; the only doc-facing check istests/lint/doc_env_parity.py(CE027), whose own docstring scopes it to framework-prefixed env-var assignments ("only assignments are checked, not bare prose mentions"). This is the highest-risk moment to drop it: the same PR adds 5 new pages, restructuresnav:into 4 new groups, renamesdocs/CODEX_AGENT_GUIDE.md→docs/agents/CODEX.md, and deletesdocs/IDEAS.md. Add apull_request:job (or a step in pr-checks.yml) that runsmkdocs build --strict(build only, no deploy — it needs no secrets and no Pages), so link/nav breakage fails CI even while gh-pages publishing stays disabled. - [Axis 7] USER_GUIDE CLI reference mis-enumerates
coder-eval report --format(omitsjunit) and omits--junit-xmlfrom the run flag table (docs/USER_GUIDE.md:102) —docs/USER_GUIDE.mdbills itself as "The full command, configuration, and output reference" (line 10), and this PR expanded both itsrunandreportflag tables — but the JUnit surface is missing from both, while the sibling doc added in the same PR documents it.
(a) The newly-added row at docs/USER_GUIDE.md:102 reads:
| `--format, -f` | `md` (default) or `html`. `html` re-renders each `task.json` under the run dir to a `task.html` beside it (or to `-o` when exactly one task is found). |
The code accepts three values — src/coder_eval/cli/report_command.py:53: if fmt not in ("md", "html", "junit"): — and report_command.py:26-32 documents 'junit' (JUnit XML from run.json). A reader taking this table as exhaustive would conclude -f junit does not exist, yet docs/CI_GATE.md:105 instructs exactly coder-eval report runs/latest -f junit.
(b) The run flag table (docs/USER_GUIDE.md:32-53), which this PR grew by six rows, has no --junit-xml entry, though the flag exists (src/coder_eval/cli/run_command.py:176-180: "--junit-xml", help="Write a JUnit XML report of task results to this path (for CI test-report ingestion).") and docs/CI_GATE.md:102 instructs coder-eval run tasks/*.yaml --junit-xml coder-eval-junit.xml.
Fix: change line 102 to `md` (default), `html`, or `junit` (noting junit defaults to <run-dir>/junit.xml per report_command.py:66), and add a --junit-xml row to the run table linking to CI_GATE.md.
3. [Axis 7] docs/EXTENDING.md intro claims all three seams work "without editing the base package", but the criterion seam requires editing models/criteria.py and coder_eval/criteria/ (docs/EXTENDING.md:10) — docs/EXTENDING.md:10-14 opens with:
Coder Eval is extensible along three seams, all designed so a third party can add
capability **without editing the base package**:
1. **Agents** — register a new `agent.type` via the plugin SPI.
2. **Criteria** — add a new success-criterion type via a decorator + auto-discovery.
3. **Pricing** — contribute USD rates for models your plugin runs.
Seams 1 and 3 are genuinely out-of-tree (plugins.py::load_plugins → entry-point register(AgentRegistry); pricing.register_pricing). Seam 2 is not, and the same page says so 124 lines later — docs/EXTENDING.md:134: "In models/criteria.py, subclass BaseSuccessCriterion … then add it to the SuccessCriterion union", and docs/EXTENDING.md:146: "Union membership is required — a run validates that every union member's type has a registered checker, and rejects unknown type tags in YAML." Both files named there live inside the base package (src/coder_eval/models/criteria.py), and BaseSuccessCriterion carries extra="forbid", so a criterion registered purely out-of-tree via @register_criterion can never be referenced from task YAML.
This is the one page a plugin author reads before planning work, so the over-claim costs a wasted design round. Fix: scope the intro claim to the seams that hold, e.g. "Agents and pricing register fully out-of-tree via the plugin SPI; a new criterion additionally needs its model added to the SuccessCriterion union in the base package (see §2) — auto-discovery covers the checker, not the YAML schema."
4. [Axis 8] "Bring Your Own Dataset" pointers resolve to BYOD.md ("Bring Your Own Docker") — dataset fan-out has no documented page (docs/USER_GUIDE.md:185) — New line 185 reads Any criterion on a **dataset-backed** task (see [Bring Your Own Dataset](BYOD.md)), and new line 45 ends See [BYOD](BYOD.md). — but docs/BYOD.md:8 is titled # Bring Your Own Docker (BYOD) and its front-matter description is "Use a custom Docker image with Coder Eval"; grep -n dataset docs/BYOD.md returns nothing. The other new pointer, docs/TASK_DEFINITION_GUIDE.md:817 (on a [dataset-backed task](#task-yaml-structure)), targets the "## Task YAML Structure" section (TASK_DEFINITION_GUIDE.md:39-56), whose top-level key list has no dataset: entry. Net effect: the dataset: key, ${row.<field>} substitution, stratify_field, sample_per_stratum and sample_seed — the fan-out feature the whole new "Suite Thresholds & Classification Metrics" section and the new classification_match criterion depend on — are documented nowhere in docs/. Either add a real dataset section (to TASK_DEFINITION_GUIDE.md's Task YAML Structure) and repoint these links at it, or stop labelling BYOD.md "Bring Your Own Dataset" (mkdocs.yml:91, docs/index.md:81, README.md:184 use that label for the same Docker page).
5. [Axis 8] docs/agents/CLAUDE_CODE.md config table misstates field contracts (nullability of ignore_patterns; model/allowed_tools defaults overridden by experiments/default.yaml) (docs/agents/CLAUDE_CODE.md:99) — Line 99 says | allowed_tools | list[str] \| null | Tool allowlist. Unset ⇒ all tools allowed. | and line 97 says | model | str \| null | Specific model id. Omit to use the backend/route default. | (line 78 repeats it: model: claude-sonnet-4-5-20250929 # optional; omit to use the route default). Both are wrong for a real run: layer 1 of the merge is experiments/default.yaml (shipped in the wheel as coder_eval/resources/default_experiment.yaml per pyproject.toml:133), which sets model: claude-sonnet-4-6 and allowed_tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "Skill"]. Verified with a task whose agent block is only type: claude-code: uv run coder-eval plan prints Default agent config: {... 'model': 'claude-sonnet-4-6', 'allowed_tools': ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Skill'] ...} and Variant 'default': claude-code (claude-sonnet-4-6). So omitting allowed_tools silently withholds Task/WebFetch/TodoWrite/etc. State the resolved baseline ("unset ⇒ the baseline allowlist from experiments/default.yaml; set allowed_tools: null explicitly for all tools") rather than the bare field default.
6. [Axis 8] REPORT_SCHEMA's token-bucket invariant isn't implementable as written: the per-message bucket fields are never named, and input_tokens denotes different quantities on TranscriptMessage vs TokenUsage (docs/REPORT_SCHEMA.md:134) — Lines 134-138 state "Summing the four token buckets across messages (assistant + the synthetic reconciliation entry) equals token_usage exactly", but the TurnRecord entry (line 129) describes messages only as list[TranscriptMessage] and never names its bucket fields. A consumer implementing from this doc hits a name collision: AssistantMessage/ReconciliationMessage carry input_tokens / output_tokens / cache_creation_tokens / cache_read_tokens (src/coder_eval/models/telemetry.py:220-241, 291-297) and the residual is computed against usage.uncached_input_tokens (streaming/collector.py:111-114), whereas the doc's own TokenUsage section (line 208-210) defines input_tokens as the computed sum of all three input buckets — so sum(messages[].input_tokens) == token_usage.uncached_input_tokens, NOT token_usage.input_tokens. Also, docs/agents/CLAUDE_CODE.md:168-171 says "each turn carries one synthetic role="reconciliation" entry", but collector.py:115-116 returns the messages unchanged when every residual is 0 (and skips it entirely when token_usage is None, collector.py:175-176). Spell out the four per-message field names and the input_tokens ↔ uncached_input_tokens mapping, and soften the "each turn carries one" claim to "when the residual is non-zero".
Nits
- [Axis 1] docs/EXTENDING.md prints
calculate_costwith keyword names that do not exist on the real function (docs/EXTENDING.md:225) — Line 225 documents the API ascalculate_cost(model, uncached_input, output, cache_creation=0, cache_read=0). The real signature in src/coder_eval/pricing.py:156-162 iscalculate_cost(model: str, uncached_input_tokens: int, output_tokens: int, cache_creation_tokens: int = 0, cache_read_tokens: int = 0), so a caller copyingcache_creation=0/cache_read=0gets a TypeError. Print the real parameter names. - [Axis 1] mkdocs.yml nav keeps the old
coder_evalbranding the rest of this branch normalized to "Coder Eval" (mkdocs.yml:77) — Line 77 reads- 02 · Running coder_eval in CI: tutorials/02-ci-pipeline.md, while the same PR renamed that title everywhere else — the page's own H1 (docs/tutorials/02-ci-pipeline.md:8# Tutorial 02 — Running Coder Eval in CI), docs/tutorials/README.md:17, README.md:51/:95, docs/comparison.md:132 and docs/llms.txt:41 all now say "Running Coder Eval in CI". mkdocs.yml is the only survivingcoder_evalspelling in a user-visible title; update it to match. - [Axis 1] Moved docs/agents/CODEX.md was not brought to parity with its two new sibling agent guides (
docs/agents/CODEX.md:192) — CODEX.md now sits in thedocs/agents/group alongside two new guides, butgrep -n '](\.\|](\.\./\|](USER_GUIDE\|](TASK_DEF' docs/agents/CODEX.mdreturns zero hits — it contains no intra-repo links at all, and its References section (lines 190-193) lists only three external OpenAI URLs. Both siblings end with a cross-linking block (docs/agents/CLAUDE_CODE.md:189-194 links Task Definition Guide, User Guide, Codex, Antigravity and EXTENDING.md; docs/agents/ANTIGRAVITY.md:198-203 does the same). Add the equivalent../TASK_DEFINITION_GUIDE.md/../USER_GUIDE.md/CLAUDE_CODE.md/ANTIGRAVITY.md/../EXTENDING.mdlinks so the three agent guides read as one set. - [Axis 2] EXTENDING.md config-class snippet omits the
# type: ignore[assignment]every in-repo instance of that discriminator override carries (docs/EXTENDING.md:76) — The copy-pasteable snippet at docs/EXTENDING.md:75-77 isclass MyAgentConfig(BaseAgentConfig): / type: Literal["my-agent"] = "my-agent".BaseAgentConfig.typeis declaredtype: str | None = Field(default=None, ...)(src/coder_eval/models/agent_config.py:130), so narrowing it to aLiteralin a subclass is an incompatible mutable-attribute override — every in-tree instance suppresses it explicitly:ClaudeCodeAgentConfig(agent_config.py:187),AntigravityAgentConfig(agent_config.py:270), and the BYOA worked example this same page links to, tests/fixtures/byoa_demo_plugin/byoa_demo.py:33 (type: Literal["byoa-demo"] = "byoa-demo" # type: ignore[assignment]). A plugin author copying the doc into a type-checked package gets a reportIncompatibleVariableOverride with no hint. Add the trailing# type: ignore[assignment]to the snippet, matching the fixture. - [Axis 3] agent_judge failure-mode bullet left on the old JSON-parse wording while the parallel llm_judge bullet was updated (
docs/TASK_DEFINITION_GUIDE.md:790) — The PR rewrote the llm_judge failure modes to- The judge never emits the forced ``submit_verdict`` tool call (no verdict returned)(line 713), but the agent_judge section 77 lines later still reads- Non-JSON final message from the judge (parse failure)(line 790). agent_judge uses the same forced-tool mechanism:src/coder_eval/criteria/agent_judge.py:271doesconfig.allowed_tools = list({*(config.allowed_tools or []), SUBMIT_VERDICT_MCP_TOOL_NAME})and its system prompt (agent_judge.py:88-89) says "Do NOT emit JSON in text — use thesubmit_verdicttool. If you do not callsubmit_verdictthe grade is zero." Update line 790 to mirror line 713. - [Axis 4] docs/CI_GATE.md Security section omits workflow-hardening guidance (least-privilege
permissions:and SHA-pinning the recommended third-party action) (docs/CI_GATE.md:91) — docs/CI_GATE.md is the new authoritative CI reference and its### Securitysection (lines 91-96) covers only two risks: "Evaluated tasks execute agent-generated code. Do not run this action underpull_request_targetwith secrets exposed to untrusted fork PRs. For untrusted tasks, use the Docker driver — thetempdirdriver is not a security boundary." It never tells the caller to restrict the job'sGITHUB_TOKEN. Neither documented snippet (- uses: UiPath/coder_eval@v0at line 22 and line 67) sits inside a workflow that declarespermissions:, and the hand-rolled workflow the page links to (docs/tutorials/02-ci-pipeline.md lines 49-141) also has nopermissions:block — so a copied setup inherits the repo default, which on repos created before Feb-2023 defaults (or is org-configured) to read/write forcontents,packages,issues, etc. That token is present in the runner env of the very job that, by this page's own admission, "execute[s] agent-generated code" in a sandbox that is "not a security boundary". Contrast the repo's own workflows, which set an explicit block in every single case:.github/workflows/pr-checks.yml:18-19(permissions:/contents: read) — and that is the workflow that dogfoods this action (uses: ./at pr-checks.yml:856) — pluscodeql.yml:13-14,conventional-commits.yml:12-14,publish-testpypi.yml:31-32,release.yml:44-46,docker-publish.yml:30-32, andclaude-pr-review.yml:20(permissions: {}). Fix: add one line to the### Securitysection recommendingpermissions: contents: read(narrowed further only where a step needs it) on any workflow calling the action, and add the block to the snippets at CI_GATE.md:21-28 / :66-73 and to the tutorial workflow at docs/tutorials/02-ci-pipeline.md:76-83. CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - [Axis 4] docs.yml is the only workflow with
contents: writeand the only one using mutable action tags plus an unpinned PyPI range (.github/workflows/docs.yml:37) — This PR edits docs.yml (comments out thepush:trigger, lines 12-24) but leaves a pinning gap that is unique to this file among all eight workflows. docs.yml grantspermissions:/contents: write(lines 26-27) — the only workflow in the repo with write access to repo contents — yet checks out and builds using mutable tags:- uses: actions/checkout@v4(line 37) and- uses: actions/setup-python@v5(line 38), plus an unpinned dependency rangerun: pip install "mkdocs-material[imaging]>=9.5,<10"(line 47). Every other workflow SHA-pins, without exception:actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2andactions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0appear in pr-checks.yml:44,47, codeql.yml:28, release.yml:81,90, publish-testpypi.yml:47,50, docker-publish.yml:54, conventional-commits.yml:41, and claude-pr-review.yml:51; even the publishedaction.yml:80pinsastral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0. A repointed upstream tag or a maliciousmkdocs-materialrelease therefore executes in a job holding acontents: writetoken (the tj-actions/changed-files March-2025 tag-repointing incident is the exact shape). Exposure is currently reduced because this PR leaves onlyworkflow_dispatch:(line 24), so a maintainer must trigger it — but the comment at line 17 says "Re-enable by uncommenting thepush:trigger below", which restores automatic execution on everydocs/**merge to main. Fix: SHA-pin lines 37-38 to the same commits the other workflows use, and pin mkdocs-material to an exact version (or a lockfile) — ideally before thepush:trigger is uncommented. CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - [Axis 5] EXTENDING.md lists discard_pending_turn() as an optional override, contradicting the Agent base-class contract (
docs/EXTENDING.md:91) — docs/EXTENDING.md:91-92 reads: "Optional overrides (sensible defaults exist):kill(),kill_sync()(called from a non-asyncio watchdog thread — must not await),discard_pending_turn()."kill()/kill_sync()are genuinely meant to be overridden (src/coder_eval/agent.py:255,:282are no-op defaults), butdiscard_pending_turn()is not:src/coder_eval/agent.py:264-281owns the cross-retry rollback of_iteration_was_incrementedandself._iterationthat the orchestrator relies on after every failedcommunicate(), and CLAUDE.md's "Adding a New Agent" step 5 states "discard_pending_turn()andget_state()are concrete on the base and need no override." Presenting it as an optional override invites a third-party agent to reimplement that bookkeeping and silently break iteration-count rollback. Movediscard_pending_turn()out of the "optional overrides" list and state that it (andget_state()) are concrete on the base and must not be overridden. - [Axis 5] Doc index duplicated across four surfaces with no guard; mkdocs nav label and llms.txt URL scheme have already drifted (
mkdocs.yml:77) — The same doc index is now maintained in four independent places —README.md:180-189,docs/index.md:73-90,docs/llms.txt:22-46, and themkdocs.ymlnav:block (lines 74-99) — with nothing in tests/ or CI checking them against each other, and two drifts already exist at PR HEAD. (1)mkdocs.yml:77reads- 02 · Running coder_eval in CI: tutorials/02-ci-pipeline.mdwhile every other surface uses the brand spelling introduced by commit 5dea1c7:docs/tutorials/02-ci-pipeline.md:8(# Tutorial 02 — Running Coder Eval in CI),docs/tutorials/README.md:17,README.md:51,docs/llms.txt:41— so the rendered sidebar is the only place showing the old repo-name spelling. (2) The canonical URL scheme is inconsistent between the two surfaces this PR touched:mkdocs.yml:6setssite_url: https://coder-eval.com/docs/with nouse_directory_urls/slugify override, so an mkdocs build emits…/docs/USER_GUIDE/, whereasdocs/llms.txt:26advertiseshttps://coder-eval.com/docs/user-guidefor that same page — one of the two must be wrong whichever site actually serves the content. Fix themkdocs.yml:77label, and pick one canonical URL scheme (either setsite_urlto whatever the externally-synced coder-eval.com site actually serves, or regenerate llms.txt from the file paths); longer term, generatedocs/llms.txt(and ideally the README/index tables) frommkdocs.ymlnav:so there is a single source of truth. - [Axis 6] docs/CI_GATE.md overstates the score gate's NaN handling — non-finite rows are skipped, not failed closed (
docs/CI_GATE.md:89) — Lines 88-89 state: "Errored tasks (null score) are left tocoder-eval's exit code; a malformed/NaNscore fails closed." The action's gate does the opposite for a mixed run —action.yml:188filters rows withif isinstance(s, (int, float)) and not isinstance(s, bool) and math.isfinite(s):, so a NaN/infweighted_scorerow is excluded fromrows;worst = min(rows)(line 192) then only ranges over the finite rows andok = worst is not None and worst[0] >= floor(line 193) passes. It only "fails closed" when every row is unusable (emptyrows→ the "no scored tasks in run.json" error at line 199). Reword to match: "a malformed/NaNscore is excluded from the comparison so it cannot mask a below-floor task; the gate fails only if no scored task remains." - [Axis 6] EXTENDING.md's criterion error contract omits the JudgeInfrastructureError fail-loud escape hatch (
docs/EXTENDING.md:178) — Lines 177-178 tell plugin authors: "Do not overridecheck()— it's final and wraps_check_implwith error handling (an exception becomes a score-0.0 result with the error captured)." That is true for every exception except one:src/coder_eval/criteria/base.py:75-78re-raisesJudgeInfrastructureError("Judge infra failure is NOT an agent failure — do not score it 0.0. Propagates to Orchestrator.run()'s broad except → FinalStatus.ERROR"). As written, a third-party criterion whose backing service is down has no documented way to fail the run loud and will silently score the agent 0.0 — an evaluation-correctness trap. Add one line: infrastructure failures (network/service unavailable, not an agent fault) should raiseJudgeInfrastructureError(exported fromcoder_eval.errors), which propagates and marks the taskFinalStatus.ERRORinstead of scoring 0.0. - [Axis 8] TASK_DEFINITION_GUIDE's table of contents and shared-field table not updated for the two criterion sections this PR adds (
docs/TASK_DEFINITION_GUIDE.md:20) — The PR adds### commands_efficiency(line 619) and### classification_match(line 795), but the criterion list in the Table of Contents (lines 20-32) still enumerates only 12 entries — jumping fromcommand_executed(line 29) straight touipath_eval(line 30) and fromagent_judge(line 32) toskill_triggered(line 33) — while line 383 states "The framework supports 14 criterion types." Add the two missing TOC rows. While there, the shared-field table at lines 389-394 listsdescription/weight/pass_threshold/stop_whenbut notsuite_thresholds, even though the new sections (lines 817, 842) and USER_GUIDE.md's new Suite Thresholds section tell authors to set it on a criterion.
What's Missing
Tests:
- 🟠 No parity/golden test guards the brand-new published contract in docs/REPORT_SCHEMA.md (+251) against the Pydantic models it describes — nothing asserts that the documented keys of
run.json/variant.json/task.json/suite.jsonstill matchmodels/results.py,models/telemetry.pyand the dict keysreports_experiment.pyactually writes. The token-bucket-invariant defect (per-message bucket names never spelled out;messages[].input_tokens↔token_usage.uncached_input_tokens) is the first instance of exactly the drift class this page now invites, and there is no mechanism to catch the second. Add a test that walks the documented field tables and asserts each name exists on the corresponding model (or generate the tables from the models). (trigger: docs/REPORT_SCHEMA.md) (restates: Axis 8: REPORT_SCHEMA's token-bucket invariant isn't implementable as written) - 🟠 Nothing validates that copy-paste commands published in docs actually parse against the real Typer CLI.
coder-eval run experiments/model-comparison.yaml --type <kind>(docs/agents/ANTIGRAVITY.md:63, docs/agents/CODEX.md:78) exits 0 having evaluated 0 tasks, and no test or lint rule would have flagged it. Cheapest mechanical guard: a CE0nn lint rule that flags any fencedcoder-eval runline whose positional argument is a path underexperiments/(must use-e), plus a smoke test that extracts fencedcoder-eval …blocks from docs/ and asserts each parses via the Typer app without executing. (trigger: docs/agents/ANTIGRAVITY.md) (restates: Axis 8: Agent guides publishcoder-eval run experiments/<file>.yaml— the documented command does not work) - 🟡 After commenting out docs.yml's
push:trigger the repo has zero automatic mkdocs validation —mkdocsappears in no other workflow, pr-checks.yml has no docs step, and everytests/lint/rules/*rule is an AST rule over.py. This PR is the worst moment to lose it: 5 new pages, a 4-group nav restructure, one rename (CODEX_AGENT_GUIDE.md → agents/CODEX.md) and one deletion (IDEAS.md). Add a build-onlymkdocs build --strictjob onpull_request(needs no secrets and no Pages) so broken internal links and nav entries pointing at missing files fail CI while gh-pages publishing stays off. (trigger: .github/workflows/docs.yml) (restates: Axis 3: Disabling the docs.yml push trigger leaves mkdocs --strict reachable only via workflow_dispatch) - 🟡 The USER_GUIDE CLI flag tables are hand-maintained with no test asserting they enumerate every Typer option and every accepted enum value —
--junit-xml(run_command.py:176-180) is absent and--formatis documented asmd|htmlwhile report_command.py:55 acceptsmd|html|junit. A test that diffs the option names declared incli/*.pyagainst the table rows in docs/USER_GUIDE.md (and asserts documented value enumerations are supersets of the code's membership checks) would have caught both halves at authoring time. (trigger: docs/USER_GUIDE.md) (restates: Axis 7: USER_GUIDE CLI reference mis-enumeratescoder-eval report --formatand omits--junit-xml)
Parallel paths:
- 🟡 CLAUDE.md's "Extension Points" / "Adding a New Agent" / "Registering Model Pricing" sections are the in-repo parallel copy of the new docs/EXTENDING.md, and neither was updated to reference the other. They already disagree: CLAUDE.md states "
discard_pending_turn()andget_state()are concrete on the base and need no override", while EXTENDING.md:91 listsdiscard_pending_turn()as an optional override. Pick one as authoritative and have the other link to it, so the plugin-SPI contract has a single source of truth. (trigger: docs/EXTENDING.md) (restates: Axis 5: EXTENDING.md lists discard_pending_turn() as an optional override) - 🟡 The doc index is now maintained in four hand-written surfaces (README.md:177-191, docs/index.md:73-90, docs/llms.txt:22-46, mkdocs.yml nav:74-99) and this PR added the new pages to all four independently — with no guard. Residual drift at PR HEAD: mkdocs.yml:77 is the only surface still carrying the pre-5dea1c7
coder_evalspelling, and README.md's table is the only one lacking a "How it compares" row that index.md and llms.txt both carry. Generate llms.txt (and ideally the two tables) from mkdocsnav:, or add a parity test. (trigger: mkdocs.yml) (restates: Axis 5: Doc index duplicated across four surfaces with no guard) - 🟡 The unresolvable
UiPath/coder_eval@v0action pin is not confined to the new page — the same ref appears at README.md:105 and :142 and docs/tutorials/02-ci-pipeline.md:172, so a doc-only fix in CI_GATE.md would leave three copies broken. Fix once at the source (git tag -f v0 v0.8.9 && git push -f origin v0, which release.yml:217-227 will maintain from the next cut) rather than editing four files. (trigger: docs/CI_GATE.md) (restates: Axis 7: New CI reference tells every adopter to pinUiPath/coder_eval@v0, a ref that does not exist) - 🔵 The CODEX_AGENT_GUIDE.md → docs/agents/CODEX.md rename left two stale mentions of the old path in files outside this PR's scope: tests/lint/doc_env_parity.py:20 (rule docstring example) and tests/test_custom_lint.py:677 (
"[Codex Agent Guide](docs/CODEX_AGENT_GUIDE.md)"fixture string). Neither is a live link so nothing breaks today, but both are now references to a path that no longer exists and should be renamed with the file. (No surviving reference to the deleted docs/IDEAS.md exists anywhere in the repo — that deletion is clean.) (trigger: docs/agents/CODEX.md)
Downstream consumers:
- 🟡 This PR repoints every externally-published docs URL to https://coder-eval.com/docs (README.md badges + :24/:25/:205, pyproject.toml:56
Documentation, and all 18 URLs in docs/llms.txt) in the same commit range that disables the only workflow which publishes the site — yet nothing in the repo describes what syncs coder-eval.com or verifies the new URL shape. mkdocs.yml has nouse_directory_urls/slugify override, so a build serves…/docs/USER_GUIDE/while llms.txt advertises…/docs/user-guide; every one of the 12 non-tutorial doc URLs is affected. State the sync mechanism and reconcile the slug scheme (or add a link-check of llms.txt against the live site). (trigger: docs/llms.txt) (restates: Axis 5: Doc index duplicated across four surfaces with no guard) - 🔵 pyproject.toml's
Documentation = "https://coder-eval.com/docs"is packaging metadata that ships to PyPI on the next release, and the PyPI long_description is README.md — whose in-body doc links this PR also moved off-repo. No release-time check asserts those targets resolve, so a broken or not-yet-synced docs host becomes a published-package defect that can only be fixed by cutting a new version. Add a cheap link check to release.yml (or the pre-release checklist) covering the project URLs and the README's absolute links. (trigger: pyproject.toml)
Display & mapping dicts:
- 🔵 The new
Agents:nav group (mkdocs.yml:82-85) and the matching agent-guide rows in README.md/docs/index.md/docs/llms.txt cover 3 of the 4 registeredAgentKindvalues —none(NoOpAgent, src/coder_eval/agents/noop_agent.py,AgentKind.NONE) has no entry and no pointer, even though it is a documented user-facingagent.typewith its own section at docs/TASK_DEFINITION_GUIDE.md:142. Add a one-line "No-op / system tasks" pointer to the Agents group so the guide set matches the registry. (trigger: mkdocs.yml) - 🔵 docs/TASK_DEFINITION_GUIDE.md's hand-maintained index tables were not extended for the material this PR adds: the criterion TOC (lines 20-32) still lists 12 of the 14 types (missing
commands_efficiencyat :619 andclassification_matchat :795, while line 383 asserts "14 criterion types"), and the shared-field table (lines 389-394) omitssuite_thresholdseven though the new sections tell authors to set it. Same class as an unextended rendering dict — a new variant added without updating the map that enumerates them. (trigger: docs/TASK_DEFINITION_GUIDE.md) (restates: Axis 8: TASK_DEFINITION_GUIDE's table of contents and shared-field table not updated)
Daily/nightly:
- 🟡 The PR touches no Python, so the nightly/cron run path, the DockerRunner entrypoint and the Bedrock judge route are behaviourally unaffected — but the blast radius is not zero and is unstated. (1) docs/REPORT_SCHEMA.md now publishes the run-record/
task.json/run.jsonshape that the out-of-repocoder-eval-uipath/ evalboard consumer reads; the PR names no owner, no versioning policy, and no rule that a change toreports*.pyormodels/results.pymust update this page in lockstep. (2) docs/CI_GATE.md hands every downstream CI adopter the pinUiPath/coder_eval@v0, which does not resolve — a gate copied from this page fails at job start. State both explicitly, and add REPORT_SCHEMA.md to the checklist for any report-schema change. (trigger: docs/REPORT_SCHEMA.md) (restates: Axis 7: New CI reference tells every adopter to pinUiPath/coder_eval@v0, a ref that does not exist)
Harness & Lint Improvements
Static checks (lint / type):
- [ce-lint] Implement CE026 — the workflow-YAML rule already claimed (but never built) in
/Users/religa/src/coder_eval/.claude/harness-candidates.md:16. New non-AST checker/Users/religa/src/coder_eval/tests/lint/workflow_hardening.py, wired as a dedicated test class intests/test_custom_lint.pyexactly the way CE027 (tests/lint/doc_env_parity.py) is wired (the runner intests/lint/runner.pyis AST-over-.pyonly, so this rule must not be aBaseRule). Three checks over.github/workflows/*.yml,action.yml, and every ```yaml fence indocs/**/*.md+ `README.md`: (1) every `uses:` step must pin a 40-hex commit SHA (allowlist first-party `uses: ./` and `UiPath/coder_eval@…`); (2) every workflow file must declare a top-level `permissions:` block, and any documented workflow snippet that invokes the action must show one; (3) any `pip install "pkg>=X,<Y"` in a job whose `permissions:` grants a `write` scope must pin an exact version or a lockfile. Prevents: Directly catches `.github/workflows/docs.yml:37-38` (`actions/checkout@v4`, `actions/setup-python@v5` — the only floating tags in the repo, in the only job holding `contents: write` at docs.yml:26-27) and `docs.yml:47` (`mkdocs-material[imaging]>=9.5,<10` unpinned in that same job), plus the missing `permissions: contents: read` in the published snippets at `docs/CI_GATE.md:21-28` / `:66-73` and `docs/tutorials/02-ci-pipeline.md:76-83`. - [bandit-codeql] Add a GitHub-Actions static analyzer to the
quality-gatejob in.github/workflows/pr-checks.yml:31— eitherzizmor(uvx zizmor .github/workflows/ action.yml) or CodeQL'sactionslanguage pack in.github/workflows/codeql.yml. Both flag unpinned third-party actions, over-broadpermissions:, andpull_request_target+ untrusted-checkout patterns out of the box, so this is the belt to CE026's braces for the workflow files themselves (CE026 still carries the doc-fence and pip-pin checks, which neither tool sees). Prevents: Same two Axis-4 findings —.github/workflows/docs.yml:26-27/37-38(contents: write+ mutable tags, the tj-actions/changed-files tag-repointing shape) and the absent least-privilege guidance indocs/CI_GATE.md:91. - [ce-lint] New CE028 — CLI ↔
docs/USER_GUIDE.mdflag parity. Checker/Users/religa/src/coder_eval/tests/lint/doc_cli_parity.py, wired as a test class intests/test_custom_lint.py(CE027 pattern; it imports live objects the same way CE027 importsSettings). Introspect the Typer app (coder_eval.cli:app→ click command tree) and assert: (a) every declared option long-name ofrun/plan/reportappears verbatim in the corresponding flag table indocs/USER_GUIDE.md; (b) for any option whose accepted values are a closed set, every value appears in that option's doc row. To make (b) mechanical rather than grep-guessy, also convertreport_command.py's ad-hoc membership checkif fmt not in ("md", "html", "junit")(src/coder_eval/cli/report_command.py:55) into aStrEnum/Literaltyper choice — the same closed-set-instead-of-string-tuple move CE018 already enforces forFinalStatus— so the rule can read the choices off the enum. Prevents: Catches both halves of the mediumdocs/USER_GUIDE.md:102finding: the--format, -frow enumerating onlymd/htmlwhile the code acceptsjunit, and the missing--junit-xmlrow (the sole omission out ofrun_command.py's 20 declared options). Would also fail on any future flag added torun/reportwithout a doc row. - [ce-lint] New CE029 — Pydantic schema ↔ documentation parity, one module
/Users/religa/src/coder_eval/tests/lint/doc_schema_parity.pywith four sub-checks (again wired as atests/test_custom_lint.pyclass, CE027 style, since all inputs are Markdown + live models): (1) every top-level field ofTaskDefinitionappears as a key in the## Task YAML Structureblock ofdocs/TASK_DEFINITION_GUIDE.md:39-56; (2) every member of theSuccessCriterionunion (src/coder_eval/models/criteria.py:972) has both a### <type>section and a Table-of-Contents entry, and the prose count ("The framework supports N criterion types", TASK_DEFINITION_GUIDE.md:383) equalslen(union); (3) every field ofBaseSuccessCriterionappears in the shared-field table at TASK_DEFINITION_GUIDE.md:389-394; (4) every field name mentioned indocs/REPORT_SCHEMA.mdresolves to a real field on the model it is documented under, and every field of the models that doc claims to describe (TurnRecord,TokenUsage,AssistantMessage/ReconciliationMessage) is named at least once. Prevents: Sub-check (1) catches the undocumenteddataset:key behind the mislabeled 'Bring Your Own Dataset' pointers (docs/USER_GUIDE.md:45,:185,docs/TASK_DEFINITION_GUIDE.md:817) —dataset,stratify_field,sample_per_stratum,sample_seedappear nowhere in docs/ today. (2) and (3) catch the missingcommands_efficiency/classification_matchTOC rows and the missingsuite_thresholdsshared-field row (docs/TASK_DEFINITION_GUIDE.md:20). (4) catchesdocs/REPORT_SCHEMA.md:134stating the four-bucket invariant while never naminginput_tokens/output_tokens/cache_creation_tokens/cache_read_tokensor theinput_tokens ↔ uncached_input_tokensmapping. - [ce-lint] New CE030 — documented-API signature parity. Extend
doc_schema_parity.py(or a sibling module) with a check that scansdocs/**/*.md+README.mdfor call snippets of an explicit whitelist of exported callables (calculate_cost,register_pricing,create_agent,AgentRegistry.register) in both inline-code spans and fenced blocks, parses each call withast.parse, and validates every keyword name and the arity againstinspect.signatureof the real function. Whitelisted, not repo-wide, to keep false positives at zero. Prevents: Catchesdocs/EXTENDING.md:225—calculate_cost(model, uncached_input, output, cache_creation=0, cache_read=0)versus the realcalculate_cost(model, uncached_input_tokens, output_tokens, cache_creation_tokens=0, cache_read_tokens=0)(src/coder_eval/pricing.py:156-162). Note the defect sits in prose backticks, not a python fence, so the doc-snippet typecheck below cannot reach it — hence the separate rule. - [pyright] Type-check the documentation's Python snippets: a small extractor (
tests/lint/doc_snippets.py) that writes every ```python fence indocs/**/*.mdinto `tmp/doc_snippets/_.py`, plus a `make docs-typecheck` target running `pyright` over that directory with the repo's existing standard-mode config, added to `make verify`. Snippets that are intentionally partial opt out via a `` marker above the fence. Prevents: Catches `docs/EXTENDING.md:76` — the copy-pasteable `class MyAgentConfig(BaseAgentConfig): type: Literal["my-agent"] = "my-agent"` snippet missing the `# type: ignore[assignment]` that every in-tree instance carries (`agent_config.py:187`, `:270`, `tests/fixtures/byoa_demo_plugin/byoa_demo.py:33`); pyright reports `reportIncompatibleVariableOverride` on exactly that shape. Also guards future drift in the plugin/criterion snippets at EXTENDING.md:43-51, :137-144, :156-173, :199-211. - [ce-lint] New CE031 — doc-index single-source parity. Checker asserting that the four hand-maintained indexes list the same set of pages and the same labels:
mkdocs.ymlnav:(lines 74-99),README.md:180-189,docs/index.md:73-90, anddocs/llms.txt:22-46; and that eachnav:label matches the target page's H1/front-matter title after normalization. Add a second assertion that every URL indocs/llms.txtmaps to a real docs file under thesite_urlscheme configured inmkdocs.yml:6. Longer term the rule becomes moot by generatingdocs/llms.txt(and the README/index tables) fromnav:— the rule is the cheap interim guard. Prevents: Catchesmkdocs.yml:77(02 · Running coder_eval in CIvs. the page's own H1Tutorial 02 — Running Coder Eval in CIand all three other surfaces, post-5dea1c7) and thesite_url…/docs/USER_GUIDE/vs.docs/llms.txt:26…/docs/user-guidescheme drift. A near-miss variant of the label check (link text sharing most tokens with the target H1 but not matching it) also flags[Bring Your Own Dataset](BYOD.md)pointing at a page titledBring Your Own Docker (BYOD)—docs/USER_GUIDE.md:45/:185,docs/index.md:81,README.md:184,mkdocs.yml:91. - [ce-lint] New CE032 — agent-config doc tables must state the resolved baseline, not the bare Pydantic default. For each field documented in a table under
docs/agents/*.md, if the packaged layer-1 baseline (experiments/default.yaml, shipped ascoder_eval/resources/default_experiment.yamlperpyproject.toml:133) sets that key, require the doc cell to nameexperiments/default.yamlor quote the baseline value. Cheap to implement: parse the YAML, parse the Markdown table, string-contains check. Prevents: Catchesdocs/agents/CLAUDE_CODE.md:97(model— 'omit to use the backend/route default', but layer 1 setsclaude-sonnet-4-6),:99(allowed_tools— 'Unset ⇒ all tools allowed', but layer 1 sets a 7-tool allowlist genuinely enforced atclaude_code_agent.py:1077), and the same claim repeated at:78.
Harness improvements (not statically reachable):
- Add a
mkdocs build --strictjob (build only, no deploy, no secrets, no Pages) to.github/workflows/pr-checks.ymltriggered onpull_request, plus amake docs-checktarget so it runs locally. Keep.github/workflows/docs.yml's publish path disabled as this PR intends — the point is to separate validation from publishing, which the repo currently conflates in onemkdocs gh-deploy --force --strictstep (docs.yml:53). Why not static: Needs a real MkDocs build: the link graph,nav:→file resolution, and theme/plugin rendering are only known once the site is assembled; no grep or AST pass resolves relative links across 40+ pages. Note--strictempirically does NOT fail on orphaned pages, so the CE031 index-parity rule still earns its keep alongside it. Prevents: The medium finding at.github/workflows/docs.yml:18— disabling thepush:trigger left the only mechanical link/nav check reachable solely viaworkflow_dispatch, exactly when the PR adds 5 pages, restructuresnav:into 4 groups, renamesdocs/CODEX_AGENT_GUIDE.md→docs/agents/CODEX.mdand deletesdocs/IDEAS.md; the preceding Docs run on main (id 30047136850) failed on precisely this rot class. Also subsumes the deferred 'dead-relative-link checker' candidate in.claude/harness-candidates.md. - Make
coder-eval runfail loudly instead of silently green: exit non-zero when zero tasks were resolved, and when any task file was skipped for a validation error (todayorchestration/experiment.py:638prints⚠ 1 task file(s) skipped, thenRunning 0 task(s)/Results: 0/0 succeeded, exit code 0 — measured). Add a regression test asserting the exit code for (a) an empty resolution and (b) a partially-skipped batch, with an explicit opt-out flag for callers who genuinely want best-effort behavior. Why not static: It is a runtime exit-code contract, not a source pattern — the offending invocation parses cleanly and only misbehaves after YAML loading andTaskDefinitionvalidation at run time. Prevents: The high finding ondocs/agents/ANTIGRAVITY.md:63/docs/agents/CODEX.md:78(coder-eval run experiments/model-comparison.yaml --type …, missing-e): the doc fix repairs two lines, but this change removes the whole hazard class of a CI gate going green having evaluated nothing. - Docs-command smoke test: extract every fenced
coder-eval …invocation fromdocs/**/*.md+README.md, run each throughcoder-eval plan(dry, no agent) in a pytest, and assert it exits 0 and resolves ≥1 task. Commands needing credentials or a live agent opt out via a<!-- no-smoke -->marker. Complements the deferred 'docs-vs-harness smoke test' candidate already listed in.claude/harness-candidates.md. Why not static: Requires the loader to actually resolve the YAML path into task definitions —coder-eval run experiments/foo.yaml --type codexis syntactically valid CLI and only fails semantically once validation rejects the experiment file. Prevents: The same ANTIGRAVITY.md:63 / CODEX.md:78 finding, plus the general class of documented commands that parse but evaluate nothing; would also have surfaced the-f junit/--junit-xmlsurfaces documented only indocs/CI_GATE.md:109/:112and absent fromdocs/USER_GUIDE.md. - Close the release-process gap behind the
@v0pin: (a) add a post-release verification step to.github/workflows/release.ymlafter theMove major action tag (vN -> this release)step (lines 217-227) assertinggit ls-remote origin refs/tags/v$MAJORresolves; (b) add one CI job (or a nightly) that consumes the published refuses: UiPath/coder_eval@v0instead ofuses: ./— today the only dogfood job (pr-checks.yml:856) uses the local checkout, so the ref users are told to copy is never exercised; (c) as an immediate unblock, push the tag once:git tag -f v0 v0.8.9 && git push -f origin v0. Why not static: Ref existence lives on the remote — a lint rule can check that the documented major matches the project version, but only a network call (or an actual Actions resolution) can proverefs/tags/v0exists. Prevents: The high finding atdocs/CI_GATE.md:22(repeated at:67,README.md:105/:142,docs/tutorials/02-ci-pipeline.md:172):UiPath/coder_eval@v0does not resolve on the repo, so every adopter copying the canonical snippet getsUnable to resolve action UiPath/coder_eval@v0at job start. - Reproducibility guard for stratified sampling: persist the effective seed and the drawn row ids into
run.jsonfor every dataset-backed run, and emit a warning when a task carriessuite_thresholdswhile the stratified path runs unseeded (orchestration/task_loader.py:403,stratum_seed = ds.sample_seeddefaulting toNone). A gated run then becomes replayable from its own artifact rather than only from a hand-setdataset.sample_seed. Why not static: The seed is drawn at run time; a static rule can at most flag thesuite_thresholds-without-sample_seedYAML combination, and even that is a legitimate configuration (the nightly activation suite deliberately re-draws each run). Prevents: The high finding atdocs/USER_GUIDE.md:46—--sample-per-stratumdocumented beside the reproducible--samplewith no hint that it re-draws each run, anddataset.sample_seeddocumented nowhere in docs/; a suite gated onpass_rateor classificationsuite_thresholdsscores a different row set each run for identical agent behaviour. - Golden/property test for the token-bucket invariant across both agents and both modes: assert
Σ(assistant + reconciliation buckets) == token_usageon recorded fixtures, with explicit cases for (a) a zero-residual turn whereEventCollectoremits no reconciliation entry (streaming/collector.py:115-116,:175-176), and (b) simulation mode, whereUserMessagecarries its owninput_tokens/output_tokens(models/telemetry.py:181-182) that the collector deliberately excludes — so a consumer summing all ofTurnRecord.messagesover-counts. Why not static: Needs a live or recorded event stream reduced throughEventCollector; the invariant is arithmetic over runtime-produced messages, not any source-level shape. Prevents: The medium finding atdocs/REPORT_SCHEMA.md:134and its companion overstatements indocs/agents/CLAUDE_CODE.md:168-173('each turn carries one syntheticreconciliationentry'; invariant stated over all ofTurnRecord.messages) — a test that fails on both the zero-residual and simulation cases forces the doc wording to track the code.
Top 5 Priority Actions
- Restore the reproducibility caveat the CLI help already carries at docs/USER_GUIDE.md:46 — src/coder_eval/orchestration/task_loader.py:403 passes stratum_seed = ds.sample_seed (None by default), so a suite gated on suite_thresholds scores a different row subset every run for identical agent behaviour; also document dataset.sample_seed and stratify_field, which appear nowhere under docs/.
- Correct the allowed_tools/model contract at docs/agents/CLAUDE_CODE.md:99 (and :78/:97) — 'Unset ⇒ all tools allowed' is false once experiments/default.yaml (shipped as coder_eval/resources/default_experiment.yaml) injects allowed_tools [Bash, Read, Write, Edit, Glob, Grep, Skill] and model claude-sonnet-4-6, so omitting the key silently withholds Task/WebFetch/TodoWrite and depresses scores; state the resolved baseline plus the explicit 'allowed_tools: null' escape hatch.
- Add the JudgeInfrastructureError escape hatch to the criterion error contract at docs/EXTENDING.md:177-178 — src/coder_eval/criteria/base.py:75-78 re-raises it so infrastructure outages become FinalStatus.ERROR instead of a score-0.0 verdict, and as written a third-party criterion whose backing service is down silently grades the agent 0.0.
- Fix the experiment invocation at docs/agents/ANTIGRAVITY.md:63 and docs/agents/CODEX.md:78 to 'coder-eval run -e experiments/model-comparison.yaml --type ...' (the form already correct at docs/AB_EXPERIMENTS.md:37) — the positional arg is task_files (src/coder_eval/cli/run_command.py:126-129), and the documented command reproduces as 'Running 0 task(s) / Results: 0/0 succeeded' with exit code 0, an unconditionally green CI gate that evaluated nothing.
- Cut and push the missing moving major tag (git tag -f v0 v0.8.9 && git push -f origin v0) — docs/CI_GATE.md:22 and :67, README.md:105/:142 and docs/tutorials/02-ci-pipeline.md:172 all pin UiPath/coder_eval@v0, which resolves to nothing today because release.yml:217-227's major-tag step landed after the last release, so every adopter hits 'Unable to resolve action'; one tag fixes all five sites with zero doc edits.
Stats: 0 🔴 · 3 🟠 · 6 🟡 · 12 🔵 across 8 axes reviewed.
New docs/DATASETS.md is the real "Bring Your Own Dataset" guide: fan-out
semantics, the two row sources, ${row.<field>} substitution scope, row-id
rules with their load-time error shapes, the full sampling precedence (with
a warning admonition on sample_seed's default nondeterminism), suite-level
scoring, and two worked examples quoted verbatim from tasks/.
TASK_DEFINITION_GUIDE.md gains a `### dataset` subsection documenting all
six Dataset fields, which also gives the two previously dangling
"[dataset-backed task](#task-yaml-structure)" links a section that actually
defines dataset:. USER_GUIDE.md's --sample rows now point at DATASETS.md.
Dataset.sample_seed's description opened mid-sentence; rewritten to complete
sentences with every original fact retained.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs/BYOD.md was "Bring Your Own Docker" — a stale, partly-wrong duplicate of DOCKER_ISOLATION.md whose title collided with the dataset feature everything linked it for. Delete it and fold the material it uniquely carried into DOCKER_ISOLATION.md: a pre-built-custom-image section and a troubleshooting table for the two failure modes (image not found, base-version mismatch). Two stale claims die with it: the CLI override is not a "future enhancement" (-D sandbox.docker.image= ships today), and the base image tag tracks the package version, not 0.1.0. Every inbound link now points at DATASETS.md, including the ../BYOD.md form in tutorials/04 (whose link text also said "Bring Your Own Data") and the published llms.txt route. tasks/byod_smoke_test.yaml and its template keep their names — CI runs them by tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run_limits was documented in three fragments — a four-key YAML comment inside the Agent Configuration block, and prose subsections for expected_turns and stop_early — with the four budget caps (max_input_tokens, max_output_tokens, max_total_tokens, max_usd) plus the two cache-counting switches documented nowhere. Replace all of it with a dedicated Run Limits section carrying one table of all 11 fields with defaults and constraints, and the semantics that actually bite: checked after each completed turn, cumulative, subject-agent only, and max_usd silently skipped (one-shot warning) when no turn reports a cost. The name collision between run_limits.max_total_tokens (aborts the task) and simulation.max_total_tokens (ends the dialog, task still scored) is now cross-referenced from both sides. TaskDefinition.skip was user-facing and undocumented despite being used by a shipped task; document it as the quarantine mechanism it is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-turn user simulation shipped with a field table and nothing else — no page explaining when to reach for it, how to design a withholding persona, or what it costs. Add DIALOG_MODE.md as that page; the task guide keeps ownership of the field defaults and the new page links to it rather than restating them. Writing it surfaced four claims in the existing guide that the source contradicts, all now fixed in both places: - Trials get no "/trial-N" task_id suffix; they fan out through the ordinary replicate directories (<task_id>/<NN>/). - The result field is simulation.replicate_index, not simulation.trial_id. - Termination precedence is run_limits -> stop_on_criteria_pass -> max_turns -> max_total_tokens -> stop_token, not stop_token second. evaluate_stop doesn't handle the stop token at all; the simulator is only asked for another utterance once nothing else has fired. - parallel_trials has no consumer anywhere in src/ — concurrency comes from --max-parallel alone. Documented as having no effect rather than left claiming trials run concurrently. The security section also records that the simulator's system prompt inlines the task description and the pinned initial_prompt, which matters because a persona designed to withhold a requirement is defeated if that requirement is spelled out in description. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "A/B a Prompt" recipe used `text:` where PromptSuffix's field is
`content:`; since every mutation model sets extra="forbid", the published
snippet raised ValidationError when copy-pasted. Fix it and replace the
stale source-code pointer with a catalog table of all four mutation types
(fields, defaults, ordering, the literal-{name} template semantics, and the
defaults-then-variant composition), all verified against mutations.py.
CE029 (tests/lint/doc_examples.py) is the standing sensor: it extracts every
fenced yaml block from README + docs/**, classifies which are whole task or
experiment documents, and validates those against their Pydantic models.
Fragments, schematic `{ ... }` blocks, and unparseable YAML are skipped by
design (the rule is a floor, not a proof — false positives would block the
build for everyone), and a `<!-- lint-skip: doc-yaml -->` marker is the
escape hatch. 81 blocks today → 11 self-contained, 0 failing; it catches the
exact text:/content: regression it exists for.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CE030 (tests/lint/doc_schema_parity.py) closes the loop on the P0/P1 defect class: every field of a small, explicit registry of user-facing models (TaskDefinition, RunLimits, Dataset, SimulationConfig) must appear as inline code in its doc page, or carry an EXEMPT entry with a reason it is not user-authored. Adding a field now forces a doc update or a reasoned exemption in the same change — the failure this whole overhaul kept fixing by hand. Scope is deliberately bounded: an explicit four-model registry, no recursion into nested models, and a simple backtick-wrapped-name match (a floor, not a proof — documented as such). Only TaskDefinition.suite_id / row_id are exempt, both set by the dataset expander. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README's Documentation table, docs/index.md's "Where to go next" table, and docs/llms.txt's ## Docs / ## Tutorials sections were three hand-maintained copies of the nav — exactly the drift this overhaul keeps fixing (they still listed BYOD, collapsed the three agents into one row, and omitted DATASETS / DIALOG_MODE). Make mkdocs.yml's `nav:` plus a new `extra.docs_index` blurb map the single source of truth: tests/lint/doc_indexes.py renders all three surfaces between markers, `make docs-indexes` writes them, and CE028 re-renders and diffs against disk. CE028 also fails if a nav page lacks a blurb (or vice versa), if a docs/ page is missing from the nav (the check that would have caught this whole bug class), or if the hand-written tutorials table drifts from the nav. nav gains Dialog Mode; the tutorial-02 label is brand-fixed to "Coder Eval". The generated surfaces pick up the accepted content changes: three agent rows, a How It Compares row, nav labels as link text, and llms ## Docs in nav order. load_nav parses mkdocs.yml with a PRIVATE SafeLoader subclass so the `!ENV` tag is tolerated without disabling unknown-tag rejection globally; python-object tags still raise. CLAUDE.md records the SSOT and anchor conventions and the CE001+ range fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the class parallel_trials was documented, set in a shipped task YAML, and defaulted to True — but nothing in src/ ever read it. Trial concurrency is entirely --max-parallel's job: trials fan out into ordinary ResolvedTask units sharing one asyncio.Semaphore, with no per-task grouping a `parallel_trials: false` could act on. Wiring it up would be a real, unrequested feature; the honest fix for dead config is removal. Drop the field, its one task-YAML use, its test assertion, and the doc mentions (task guide simulation table + example, DIALOG_MODE.md). CE031 (tests/lint/dead_config_fields.py) makes the class recur-proof: for a small registry of behavior-driving config models (SimulationConfig, RunLimits, Dataset), every field must be read as an attribute somewhere in src/ or carry an EXEMPT reason. It scans the whole src/ tree for attribute accesses, so it would have flagged parallel_trials (green on the tree now that it's gone). The registry deliberately excludes serialization/telemetry models and TaskDefinition, whose fields round-trip through model_dump and would false-positive under the attribute rule; name collisions across models cause false negatives, never false build breaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5753745 to
15036aa
Compare
Review feedback on PR #48 (nav/IA): - Merge "Running & scaling", "Extending", and "Reference" into a single "Advanced" group — the two single-item groups only restated their page titles and cost an expand to reach one doc. Top level drops 8 → 6. - Title Case the remaining sentence-case label ("How It Compares"). - Reorder the flat doc indexes (docs/index.md, README, llms.txt) so agent guides sit right after the guides, matching the nav order. Report Schema can split back out into its own "Reference" group once a second reference doc exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary
Fills the biggest documentation gaps, fixes defects found auditing the docs against the code, and migrates all doc links to the new home at coder-eval.com/docs.
New docs
docs/agents/— moved the Codex guide toagents/CODEX.mdand added:agents/CLAUDE_CODE.md— the default agent: config surface, direct vs. Bedrock auth, sandbox isolation, early stop, telemetry.agents/ANTIGRAVITY.md— the Google Antigravity (Gemini) agent (previously undocumented):[antigravity]extra,GEMINI_API_KEYauth,thinking_level, native skills, and the caveat that it ignorespermission_mode/tool allowlists.docs/EXTENDING.md— the plugin SPI: custom agents (theAgentABC checklist +coder_eval.pluginsentry point), custom criteria (@register_criterion), andregister_pricing.docs/REPORT_SCHEMA.md— field-level reference forrun.json/task.json/variant.json/experiment.json/suite.json, token buckets, andFinalStatus.USER_GUIDE
aggregatecommand andreport --format; completed therunflag table (--type/--repeats/--resume/--sample/--sample-per-stratum/--include-skipped) and addedplan --experiment,evaluate --run-dir.CODEX_*,GEMINI_API_KEY,ANTIGRAVITY_MODEL); clarifiedUIPATH_PLUGIN_MARKETPLACE_DIRvsPLUGIN_TOOLS_DIR.TASK_DEFINITION_GUIDE fixes
commands_efficiencyandclassification_matchsections and completed the scoring-types summary (all 14 types).llm_judgemax_tokensdefault (1000 → 2000),agent_judgedefault tools (read-onlyBash/Read/Glob/Grep, notWrite/Edit), and thellm_judgeverdict wording (forcedsubmit_verdicttool call).Link migration → coder-eval.com/docs
llms.txt: absolute doc URLs rewritten tocoder-eval.com/docs/*in the Starlight slug scheme (lowercase-kebab, no trailing slash). Relative.mdcross-links left untouched — the site'ssync-docsrewrites them at build time.README.md: Docs badge + comparison/full-docs links repointed; Ruff badge removed.mkdocs.ymlsite_urlandpyproject.tomlDocumentationURL →coder-eval.com/docs.Other
experiments/example.yamlreferences in the agent guides.docs/IDEAS.md(internal backlog) and its mkdocs exclude.Verification
mkdocs build --strictpasses with zero warnings.uipath.github.ioreferences remain in source; all model IDs used in docs resolve in the pricing table.make verify/ the Python gate.🤖 Generated with Claude Code