From a6b828dc21ca0a4e9cf87c309d6444f7f3fb4391 Mon Sep 17 00:00:00 2001 From: uipreliga Date: Thu, 20 Aug 2026 19:38:24 -0700 Subject: [PATCH] feat(plugin): add optimize-skill and the --split row filter it measures with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the `/coder-eval:optimize-skill` skill — prose only, no Python — plus the one product change it needs to measure anything: `Dataset.split_field` and a `--split` row filter in `task_loader.expand_dataset`. Both are inert when unused: a dataset with no `split_field` and a run with no `--split` behave exactly as before. Stage B here is a manual reading of two runs. There is no gate, no statistics and no `coder_eval.optimize` package in this PR. One published skill changes behaviour: `analyze`'s frontmatter `description` is replaced by the measured winner of the round that tutorial 08 documents. Also fixes a dependency defect this PR's CI surfaced, unrelated to the split: `evaluation/judge_bedrock.py` imports `httpx`, which `pyproject.toml` never declared. It arrived transitively via `anthropic` until `anthropic` 1.0.0 (released 2026-08-20) moved to `httpx2`. A FRESH resolve — what `uv tool install` and `pip install coder-eval` do, since a wheel carries no lockfile — then stopped installing `httpx`, so `criteria/llm_judge.py` raised on import, the discovery loop swallowed it, and `llm_judge` vanished from the registry. `uv.lock` hid this from every `uv sync --frozen` job. `httpx` is now declared, and `tests/test_declared_dependencies.py` asserts the invariant on the DECLARATION rather than the installed set — the only form that fails in the locked jobs, where this bug was invisible. Imports guarded by `try/except ImportError` are derived as optional and exempt, so the soft dependencies (`google.antigravity`, `openai`) need no allowlist. Squashed from feat/plugin-optimize-skill: 7376062 feat(dataset): 1/3 — add Dataset.split_field and the --split row filter 240d66c feat(plugin): 2/3 — add the optimize-skill skill and split-label the activation template 8920410 docs: 3/3 — add the skill-optimization tutorial, and fix the reachability guidance it disproved 2c30397 style: apply ruff format to the reachability lint assertion ae3c39b docs(harness): record the all-skipped-run-exits-0 gap found while adding --split b53c7d4 fix: code review fixes for the split-field / optimize-skill plan d7d56f1 feat(plugin): promote a measured `analyze` description, and close the two open findings 844348d docs(tutorial): Stage C completed — the analyze promotion is confirmed on holdout e340b58 docs: record the bare-name collision hazard, and mark the plan complete Co-Authored-By: Claude Opus 5 (1M context) --- .claude/harness-candidates.md | 15 + .claude/shared/run-layout.md | 47 ++ CLAUDE.md | 4 +- README.md | 6 +- docs/AB_EXPERIMENTS.md | 1 + docs/DATASETS.md | 59 +- docs/PLUGIN.md | 17 +- docs/TASK_DEFINITION_GUIDE.md | 2 + docs/USER_GUIDE.md | 3 +- docs/llms.txt | 1 + docs/tutorials/07-plugin-in-claude-code.md | 8 +- docs/tutorials/08-optimizing-a-skill.md | 515 ++++++++++++++++++ docs/tutorials/README.md | 1 + mkdocs.yml | 1 + plugins/coder-eval/README.md | 8 +- plugins/coder-eval/reference/run-layout.md | 47 ++ .../reference/templates/activation-rows.jsonl | 12 +- .../reference/templates/activation.yaml | 35 +- plugins/coder-eval/skills/analyze/SKILL.md | 2 +- .../coder-eval/skills/check-skill/SKILL.md | 34 +- plugins/coder-eval/skills/ci/SKILL.md | 10 +- plugins/coder-eval/skills/init/SKILL.md | 2 +- plugins/coder-eval/skills/lint-tasks/SKILL.md | 2 +- .../coder-eval/skills/optimize-skill/SKILL.md | 349 ++++++++++++ plugins/coder-eval/skills/task/SKILL.md | 2 +- pyproject.toml | 6 + src/coder_eval/cli/run_command.py | 17 + src/coder_eval/models/tasks.py | 12 + src/coder_eval/orchestration/config.py | 8 + src/coder_eval/orchestration/experiment.py | 1 + src/coder_eval/orchestration/task_loader.py | 68 ++- tasks/skills/lint-tasks-activation-rows.jsonl | 28 + tasks/skills/lint-tasks-activation.yaml | 89 +++ tests/test_custom_lint.py | 162 +++++- tests/test_dataset_expansion.py | 246 +++++++++ tests/test_declared_dependencies.py | 128 +++++ uv.lock | 2 + 37 files changed, 1894 insertions(+), 56 deletions(-) create mode 100644 docs/tutorials/08-optimizing-a-skill.md create mode 100644 plugins/coder-eval/skills/optimize-skill/SKILL.md create mode 100644 tasks/skills/lint-tasks-activation-rows.jsonl create mode 100644 tasks/skills/lint-tasks-activation.yaml create mode 100644 tests/test_declared_dependencies.py diff --git a/.claude/harness-candidates.md b/.claude/harness-candidates.md index e067e9ad..4d9e58e4 100644 --- a/.claude/harness-candidates.md +++ b/.claude/harness-candidates.md @@ -455,3 +455,18 @@ with the two `action.yml` items above — one considered change to the action's `final_status`, which does not exist in `run.json` and would have made a new assertion dead on arrival. Guard: assert the key set that non-Python consumers depend on, mirroring how CE030 pins doc/schema parity. +## From the split-field / optimize-skill plan (2026-08-12) + +- [ ] **A run whose every task is skipped exits 0 — a green run of zero tasks.** When + `resolve_all_tasks` demotes every task to `skipped_tasks` (a load failure, `skip: true`, + or now a `--split` selector matching no labelled row), the run reports success: nothing + failed, so the exit gate in `cli/run_command.py` — which keys only on failed/errored tasks + and suite gates — passes. Verified directly: `coder-eval run --split holdou` + prints one yellow "1 task file(s) skipped" line and exits 0. This is pre-existing, but + `--split` makes it reachable by a one-character CLI typo rather than a broken file, and + the whole point of a holdout confirmation is that you trust its verdict. Not guarded, and + not a five-minute fix: making an all-skipped run non-green changes exit semantics for + every skipped-task path (including deliberate `skip: true` suites and tag filters that + match nothing), so it needs a decision about which of those should be fatal, plus tests + per case. A narrower option is to fail only when a CLI *selector* (`--split`, `--tags`) + eliminated everything, since that is unambiguously a user error rather than repo state. diff --git a/.claude/shared/run-layout.md b/.claude/shared/run-layout.md index 57edc4a6..7e62e707 100644 --- a/.claude/shared/run-layout.md +++ b/.claude/shared/run-layout.md @@ -14,6 +14,53 @@ runs/////{task.json, task.log, artifacts/} - `task.json.malformed` — present only on the docker degrade path: when an existing `task.json` fails to parse (schema skew from a stale `:latest` image, or a truncated/torn write), the docker runner moves the unparseable original aside to this sidecar and writes a synthetic `final_status=ERROR` `task.json` in its place. Diagnostic-only; `rglob("task.json")` consumers do not match it. - `task.log` — the human-readable task log; `artifacts/` — files the agent produced. +## Suite rollups (dataset-backed tasks only) + +A task carrying `dataset:` fans out into one row-task per row and additionally writes a +per-suite rollup: + +``` +runs////{suite.json, suite.md} +``` + +`` is the original (pre-fan-out) `task_id`. Nothing is written for a task +without `dataset:`. + +`suite.json` carries the suite's pass counts plus `criterion_aggregates[]` — one entry per +criterion that opted into across-row aggregation, each with: + +- `criterion_type`, and `description` (set when a task stacks several criteria of the same + type, e.g. one `skill_triggered` per skill — that is what distinguishes them); +- `rows_total` and `rows_excluded` — the denominator and what was dropped from it. A row + that errored before criteria ran (a timeout, say) is **excluded** rather than scored, so + metrics are computed over `rows_total - rows_excluded`; +- `metrics` — a **flat** name → float map. Classification-style criteria emit + `accuracy`, `macro_f1`, and per-label `precision.