coder-eval 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coder_eval/.gitattributes +2 -0
- coder_eval/__init__.py +3 -0
- coder_eval/agent.py +302 -0
- coder_eval/agents/__init__.py +41 -0
- coder_eval/agents/_logging.py +89 -0
- coder_eval/agents/antigravity_agent.py +811 -0
- coder_eval/agents/claude_code_agent.py +1701 -0
- coder_eval/agents/codex_agent.py +2055 -0
- coder_eval/agents/noop_agent.py +100 -0
- coder_eval/agents/registry.py +163 -0
- coder_eval/agents/watchdog.py +116 -0
- coder_eval/analysis.py +88 -0
- coder_eval/cli/__init__.py +87 -0
- coder_eval/cli/aggregate_command.py +153 -0
- coder_eval/cli/console.py +7 -0
- coder_eval/cli/evaluate_command.py +164 -0
- coder_eval/cli/plan_command.py +152 -0
- coder_eval/cli/report_command.py +121 -0
- coder_eval/cli/run_command.py +686 -0
- coder_eval/cli/run_helpers.py +137 -0
- coder_eval/cli/run_task_internal_command.py +210 -0
- coder_eval/cli/utils.py +37 -0
- coder_eval/config.py +212 -0
- coder_eval/criteria/__init__.py +163 -0
- coder_eval/criteria/_classification_aggregate.py +106 -0
- coder_eval/criteria/agent_judge.py +425 -0
- coder_eval/criteria/base.py +248 -0
- coder_eval/criteria/classification_match.py +107 -0
- coder_eval/criteria/command_executed.py +181 -0
- coder_eval/criteria/commands_efficiency.py +73 -0
- coder_eval/criteria/file_check.py +119 -0
- coder_eval/criteria/file_contains.py +85 -0
- coder_eval/criteria/file_exists.py +45 -0
- coder_eval/criteria/file_matches_regex.py +86 -0
- coder_eval/criteria/json_check.py +184 -0
- coder_eval/criteria/llm_judge.py +260 -0
- coder_eval/criteria/reference_comparison.py +130 -0
- coder_eval/criteria/run_command.py +220 -0
- coder_eval/criteria/skill_triggered.py +111 -0
- coder_eval/criteria/uipath_eval.py +223 -0
- coder_eval/errors/__init__.py +26 -0
- coder_eval/errors/agent.py +26 -0
- coder_eval/errors/budget.py +28 -0
- coder_eval/errors/categories.py +205 -0
- coder_eval/errors/categorization.py +240 -0
- coder_eval/errors/executor.py +124 -0
- coder_eval/errors/judge.py +12 -0
- coder_eval/errors/retry.py +211 -0
- coder_eval/errors/timeout.py +63 -0
- coder_eval/evaluation/__init__.py +10 -0
- coder_eval/evaluation/checker.py +260 -0
- coder_eval/evaluation/judge_anthropic.py +55 -0
- coder_eval/evaluation/judge_bedrock.py +114 -0
- coder_eval/evaluation/judge_context.py +497 -0
- coder_eval/evaluation/judge_models.py +53 -0
- coder_eval/evaluation/judge_persistence.py +291 -0
- coder_eval/evaluation/judge_usage.py +50 -0
- coder_eval/evaluation/sub_agent.py +231 -0
- coder_eval/evaluation/summaries.py +48 -0
- coder_eval/evaluation/verdict_tool.py +213 -0
- coder_eval/formatting.py +191 -0
- coder_eval/isolation/__init__.py +15 -0
- coder_eval/isolation/docker_runner.py +1253 -0
- coder_eval/logging_config.py +399 -0
- coder_eval/models/__init__.py +337 -0
- coder_eval/models/agent_config.py +373 -0
- coder_eval/models/container_paths.py +26 -0
- coder_eval/models/criteria.py +942 -0
- coder_eval/models/enums.py +121 -0
- coder_eval/models/experiment.py +314 -0
- coder_eval/models/judge.py +90 -0
- coder_eval/models/judge_defaults.py +11 -0
- coder_eval/models/limits.py +89 -0
- coder_eval/models/merge_strategy.py +132 -0
- coder_eval/models/mutations.py +95 -0
- coder_eval/models/results.py +787 -0
- coder_eval/models/routing.py +160 -0
- coder_eval/models/sandbox.py +371 -0
- coder_eval/models/tasks.py +631 -0
- coder_eval/models/telemetry.py +454 -0
- coder_eval/models/templates.py +108 -0
- coder_eval/orchestration/__init__.py +13 -0
- coder_eval/orchestration/batch.py +690 -0
- coder_eval/orchestration/config.py +111 -0
- coder_eval/orchestration/config_merge.py +431 -0
- coder_eval/orchestration/evaluation.py +94 -0
- coder_eval/orchestration/experiment.py +837 -0
- coder_eval/orchestration/overrides.py +206 -0
- coder_eval/orchestration/task_loader.py +437 -0
- coder_eval/orchestrator.py +2078 -0
- coder_eval/path_utils.py +79 -0
- coder_eval/plugins.py +81 -0
- coder_eval/pricing.py +169 -0
- coder_eval/py.typed +0 -0
- coder_eval/reports.py +1066 -0
- coder_eval/reports_experiment.py +761 -0
- coder_eval/reports_html.py +1659 -0
- coder_eval/reports_stats.py +250 -0
- coder_eval/resources/__init__.py +137 -0
- coder_eval/resources/default_experiment.yaml +85 -0
- coder_eval/resources/default_ignore_patterns.yaml +60 -0
- coder_eval/resources/tags.yaml +55 -0
- coder_eval/sandbox.py +1127 -0
- coder_eval/scoring/__init__.py +11 -0
- coder_eval/scoring/ast_similarity.py +38 -0
- coder_eval/scoring/complexity.py +115 -0
- coder_eval/scoring/quality.py +154 -0
- coder_eval/scoring/signature_similarity.py +57 -0
- coder_eval/scoring/similarity.py +103 -0
- coder_eval/scoring/token_similarity.py +44 -0
- coder_eval/simulation/__init__.py +27 -0
- coder_eval/simulation/termination.py +88 -0
- coder_eval/simulation/user_simulator.py +324 -0
- coder_eval/streaming/__init__.py +44 -0
- coder_eval/streaming/callbacks.py +57 -0
- coder_eval/streaming/collector.py +193 -0
- coder_eval/streaming/events.py +198 -0
- coder_eval/streaming/renderers.py +248 -0
- coder_eval/streaming/wire.py +115 -0
- coder_eval/telemetry.py +407 -0
- coder_eval/utils.py +517 -0
- coder_eval-0.8.2.dist-info/METADATA +242 -0
- coder_eval-0.8.2.dist-info/RECORD +127 -0
- coder_eval-0.8.2.dist-info/WHEEL +4 -0
- coder_eval-0.8.2.dist-info/entry_points.txt +5 -0
- coder_eval-0.8.2.dist-info/licenses/LICENSE +201 -0
- coder_eval-0.8.2.dist-info/licenses/NOTICE +18 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Shared statistical + prompt-config helpers for the markdown and HTML reporters.
|
|
2
|
+
|
|
3
|
+
Kept in a standalone module so ``reports_html`` can consume them without
|
|
4
|
+
importing ``reports_experiment`` (which in turn imports ``reports_html``
|
|
5
|
+
for its HTML-write helpers, and would otherwise form a cycle).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import random
|
|
13
|
+
import statistics as _stats
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from coder_eval.models import EvaluationResult, ExperimentVariant, TaskExperimentSummary
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Statistical helpers (stdlib statistics module)
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def mean(values: list[float]) -> float:
|
|
28
|
+
return _stats.mean(values) if values else 0.0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def stddev(values: list[float]) -> float:
|
|
32
|
+
"""Sample standard deviation (Bessel-corrected). Returns 0.0 for n < 2."""
|
|
33
|
+
return _stats.stdev(values) if len(values) >= 2 else 0.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def welch_t_test(a: list[float], b: list[float]) -> float | None:
|
|
37
|
+
"""Two-tailed p-value using Welch's t-test via stdlib NormalDist approximation.
|
|
38
|
+
|
|
39
|
+
Uses the normal approximation for the t-distribution when df is large (>30),
|
|
40
|
+
and falls back to exact Welch-Satterthwaite otherwise. Returns None if either
|
|
41
|
+
group has fewer than 2 observations.
|
|
42
|
+
"""
|
|
43
|
+
n_a, n_b = len(a), len(b)
|
|
44
|
+
if n_a < 2 or n_b < 2:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
mean_a, mean_b = _stats.mean(a), _stats.mean(b)
|
|
48
|
+
var_a = _stats.variance(a)
|
|
49
|
+
var_b = _stats.variance(b)
|
|
50
|
+
|
|
51
|
+
se_sq = var_a / n_a + var_b / n_b
|
|
52
|
+
if se_sq == 0:
|
|
53
|
+
return 1.0
|
|
54
|
+
|
|
55
|
+
t_stat = abs(mean_a - mean_b) / math.sqrt(se_sq)
|
|
56
|
+
|
|
57
|
+
# Conservative normal approximation: treat t as z-score.
|
|
58
|
+
# Overestimates p slightly for small df (heavier tails), but correct in
|
|
59
|
+
# direction and sufficient for display purposes (no incomplete-beta needed).
|
|
60
|
+
return 2.0 * _stats.NormalDist().cdf(-t_stat)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def fmt_mean_sd(values: list[float], fmt: str = ".3f") -> str:
|
|
64
|
+
"""Format mean ± stddev string. Omits ± when n < 2 (stddev undefined)."""
|
|
65
|
+
if not values:
|
|
66
|
+
return "N/A"
|
|
67
|
+
m = mean(values)
|
|
68
|
+
if len(values) < 2:
|
|
69
|
+
return f"{m:{fmt}}"
|
|
70
|
+
sd = stddev(values)
|
|
71
|
+
return f"{m:{fmt}} ± {sd:{fmt}}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fmt_p(p: float | None) -> str:
|
|
75
|
+
"""Format p-value for display."""
|
|
76
|
+
if p is None:
|
|
77
|
+
return "—"
|
|
78
|
+
if p < 0.001:
|
|
79
|
+
return "<0.001"
|
|
80
|
+
return f"{p:.3f}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Replicate statistics helpers (stdlib random + statistics)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def bootstrap_mean_ci(
|
|
89
|
+
values: list[float],
|
|
90
|
+
n_resamples: int = 1000,
|
|
91
|
+
confidence: float = 0.95,
|
|
92
|
+
seed: int = 0,
|
|
93
|
+
) -> tuple[float, float, float]:
|
|
94
|
+
"""Percentile-bootstrap confidence interval for the mean.
|
|
95
|
+
|
|
96
|
+
Returns (mean, ci_low, ci_high). When ``len(values) < 2``, returns
|
|
97
|
+
(values[0], values[0], values[0]) or (0, 0, 0) for empty input.
|
|
98
|
+
Uses ``random.Random(seed)`` for determinism.
|
|
99
|
+
"""
|
|
100
|
+
if not values:
|
|
101
|
+
return (0.0, 0.0, 0.0)
|
|
102
|
+
m = sum(values) / len(values)
|
|
103
|
+
if len(values) < 2:
|
|
104
|
+
return (m, m, m)
|
|
105
|
+
rng = random.Random(seed)
|
|
106
|
+
n = len(values)
|
|
107
|
+
resampled_means = sorted(sum(rng.choice(values) for _ in range(n)) / n for _ in range(n_resamples))
|
|
108
|
+
alpha = (1.0 - confidence) / 2.0
|
|
109
|
+
lo = resampled_means[int(alpha * n_resamples)]
|
|
110
|
+
hi = resampled_means[int((1.0 - alpha) * n_resamples) - 1]
|
|
111
|
+
return (m, lo, hi)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def wilson_interval(successes: int, n: int, confidence: float = 0.95) -> tuple[float, float]:
|
|
115
|
+
"""Wilson score interval for a binomial proportion. Returns (low, high).
|
|
116
|
+
|
|
117
|
+
More reliable than the normal approximation at small N and near 0/1.
|
|
118
|
+
"""
|
|
119
|
+
if n <= 0:
|
|
120
|
+
return (0.0, 0.0)
|
|
121
|
+
z = _stats.NormalDist().inv_cdf((1.0 + confidence) / 2.0)
|
|
122
|
+
p_hat = successes / n
|
|
123
|
+
denom = 1.0 + z * z / n
|
|
124
|
+
center = (p_hat + z * z / (2.0 * n)) / denom
|
|
125
|
+
half = (z * math.sqrt(p_hat * (1.0 - p_hat) / n + z * z / (4.0 * n * n))) / denom
|
|
126
|
+
return (max(0.0, center - half), min(1.0, center + half))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def paired_bootstrap_diff_ci(
|
|
130
|
+
a: list[float],
|
|
131
|
+
b: list[float],
|
|
132
|
+
n_resamples: int = 1000,
|
|
133
|
+
confidence: float = 0.95,
|
|
134
|
+
seed: int = 0,
|
|
135
|
+
) -> tuple[float, float, float] | None:
|
|
136
|
+
"""Paired bootstrap of mean(a_i - b_i).
|
|
137
|
+
|
|
138
|
+
Returns (mean_diff, ci_low, ci_high) or None if lengths differ or < 2.
|
|
139
|
+
"""
|
|
140
|
+
if len(a) != len(b) or len(a) < 2:
|
|
141
|
+
return None
|
|
142
|
+
diffs = [ai - bi for ai, bi in zip(a, b, strict=True)]
|
|
143
|
+
return bootstrap_mean_ci(diffs, n_resamples=n_resamples, confidence=confidence, seed=seed)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def cohens_d(a: list[float], b: list[float]) -> float | None:
|
|
147
|
+
"""Paired Cohen's d = mean(a_i - b_i) / stddev(a_i - b_i)."""
|
|
148
|
+
if len(a) != len(b) or len(a) < 2:
|
|
149
|
+
return None
|
|
150
|
+
diffs = [ai - bi for ai, bi in zip(a, b, strict=True)]
|
|
151
|
+
s = stddev(diffs)
|
|
152
|
+
return (sum(diffs) / len(diffs)) / s if s > 0 else None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Prompt config + variant-result loaders
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def has_final_reply(result: EvaluationResult) -> bool:
|
|
161
|
+
"""True iff any iteration emitted a non-empty ResultMessage.result.
|
|
162
|
+
|
|
163
|
+
Mirrors the evalboard rendering: a "final reply" is a text answer the
|
|
164
|
+
agent produced that becomes the trailing entry in the Turn timeline.
|
|
165
|
+
"""
|
|
166
|
+
for t in result.iterations:
|
|
167
|
+
if t.result_summary is not None:
|
|
168
|
+
r = t.result_summary.result
|
|
169
|
+
if isinstance(r, str) and r.strip():
|
|
170
|
+
return True
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def visible_turn_count(result: EvaluationResult) -> int:
|
|
175
|
+
"""Count of agent actions visible in the timeline so far.
|
|
176
|
+
|
|
177
|
+
A "turn" here is one entry rendered in the Turn timeline: each tool
|
|
178
|
+
invocation contributes 1, plus 1 for the final assistant reply when
|
|
179
|
+
present. This is the canonical metric — distinct from the SDK's
|
|
180
|
+
``num_turns`` which counts assistant *messages* and can bundle tool
|
|
181
|
+
use with trailing text into a single turn.
|
|
182
|
+
"""
|
|
183
|
+
commands = sum(len(t.commands) for t in result.iterations)
|
|
184
|
+
return commands + (1 if has_final_reply(result) else 0)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def expected_turns_overage(result: EvaluationResult) -> tuple[int, int] | None:
|
|
188
|
+
"""Return ``(visible_turns, expected)`` when the visible-events turn
|
|
189
|
+
count strictly exceeds ``run_limits.expected_turns``; else ``None``.
|
|
190
|
+
|
|
191
|
+
Safe against missing ``task_config``, missing ``run_limits``, and
|
|
192
|
+
non-int ``expected_turns`` values.
|
|
193
|
+
"""
|
|
194
|
+
task_cfg = result.task_config
|
|
195
|
+
if task_cfg is None:
|
|
196
|
+
return None
|
|
197
|
+
run_limits = (task_cfg.resolved or {}).get("run_limits") or {}
|
|
198
|
+
if not isinstance(run_limits, dict):
|
|
199
|
+
return None
|
|
200
|
+
expected = run_limits.get("expected_turns")
|
|
201
|
+
if not isinstance(expected, int) or expected < 1:
|
|
202
|
+
return None
|
|
203
|
+
actual = visible_turn_count(result)
|
|
204
|
+
if actual > expected:
|
|
205
|
+
return actual, expected
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def describe_prompt_config(variant: ExperimentVariant) -> str:
|
|
210
|
+
"""Return a short description of the variant's prompt configuration.
|
|
211
|
+
|
|
212
|
+
Returns strings like ``"(base prompt)"``, ``"(prompt override)"``, or
|
|
213
|
+
``"(2 mutations: prefix, suffix)"``.
|
|
214
|
+
"""
|
|
215
|
+
if variant.initial_prompt is not None or variant.initial_prompt_file is not None:
|
|
216
|
+
return "(prompt override)"
|
|
217
|
+
if variant.prompt_mutations:
|
|
218
|
+
type_names = [m.type for m in variant.prompt_mutations]
|
|
219
|
+
return f"({len(type_names)} mutations: {', '.join(type_names)})"
|
|
220
|
+
return "(base prompt)"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def load_variant_eval_results(
|
|
224
|
+
run_dir: Path, variant_id: str, task_summaries: list[TaskExperimentSummary]
|
|
225
|
+
) -> list[EvaluationResult]:
|
|
226
|
+
"""Load EvaluationResult objects for a variant from disk.
|
|
227
|
+
|
|
228
|
+
Walks all ``<run_dir>/<variant_id>/<task_id>/NN/task.json`` replicate
|
|
229
|
+
subdirs for each task in ``task_summaries`` and returns every result that
|
|
230
|
+
loads successfully.
|
|
231
|
+
"""
|
|
232
|
+
variant_dir = run_dir / variant_id
|
|
233
|
+
results: list[EvaluationResult] = []
|
|
234
|
+
|
|
235
|
+
if not variant_dir.is_dir():
|
|
236
|
+
return results
|
|
237
|
+
|
|
238
|
+
for ts in task_summaries:
|
|
239
|
+
task_dir = variant_dir / ts.task_id
|
|
240
|
+
if not task_dir.is_dir():
|
|
241
|
+
continue
|
|
242
|
+
for rep_subdir in sorted(task_dir.glob("[0-9][0-9]")):
|
|
243
|
+
task_json = rep_subdir / "task.json"
|
|
244
|
+
if task_json.exists():
|
|
245
|
+
try:
|
|
246
|
+
results.append(EvaluationResult.model_validate_json(task_json.read_text(encoding="utf-8")))
|
|
247
|
+
except Exception:
|
|
248
|
+
logger.warning("Failed to load %s for variant report", task_json, exc_info=True)
|
|
249
|
+
|
|
250
|
+
return results
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Resource management for default configurations."""
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@lru_cache(maxsize=1)
|
|
10
|
+
def load_default_ignore_patterns() -> set[str]:
|
|
11
|
+
"""Load default ignore patterns from YAML resource file.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Set of pattern strings to ignore during file operations.
|
|
15
|
+
|
|
16
|
+
Note:
|
|
17
|
+
Result is cached for performance. Changes to the YAML file
|
|
18
|
+
require process restart to take effect.
|
|
19
|
+
"""
|
|
20
|
+
resource_dir = Path(__file__).parent
|
|
21
|
+
patterns_file = resource_dir / "default_ignore_patterns.yaml"
|
|
22
|
+
|
|
23
|
+
if not patterns_file.exists():
|
|
24
|
+
# Fallback to hardcoded patterns if file missing
|
|
25
|
+
return {
|
|
26
|
+
".venv",
|
|
27
|
+
"venv",
|
|
28
|
+
"ENV",
|
|
29
|
+
"env",
|
|
30
|
+
".git",
|
|
31
|
+
".svn",
|
|
32
|
+
"__pycache__",
|
|
33
|
+
".pytest_cache",
|
|
34
|
+
"*.pyc",
|
|
35
|
+
"*.egg-info",
|
|
36
|
+
"dist",
|
|
37
|
+
"build",
|
|
38
|
+
"node_modules",
|
|
39
|
+
".DS_Store",
|
|
40
|
+
"Thumbs.db",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
with open(patterns_file, encoding="utf-8") as f:
|
|
44
|
+
data = yaml.safe_load(f)
|
|
45
|
+
|
|
46
|
+
# Flatten all categories into a single set
|
|
47
|
+
patterns = set()
|
|
48
|
+
for category in data.values():
|
|
49
|
+
if isinstance(category, list):
|
|
50
|
+
patterns.update(category)
|
|
51
|
+
|
|
52
|
+
return patterns
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def normalize_ignore_pattern_entry(raw: str) -> str:
|
|
56
|
+
"""Strip whitespace from an ignore-pattern entry and validate its shape.
|
|
57
|
+
|
|
58
|
+
Returns the cleaned entry. Raises ``ValueError`` on empty / whitespace-only
|
|
59
|
+
entries and on bare ``"!"`` (negation with no target). Shared by the
|
|
60
|
+
runtime resolver (:func:`get_ignore_patterns`) and Pydantic
|
|
61
|
+
``field_validator`` hooks on ``SandboxConfig.ignore_patterns`` and
|
|
62
|
+
``AgentConfig.ignore_patterns`` so malformed YAML fails at load time
|
|
63
|
+
rather than mid-run.
|
|
64
|
+
"""
|
|
65
|
+
entry = raw.strip()
|
|
66
|
+
if not entry:
|
|
67
|
+
raise ValueError(f"ignore pattern entry is empty or whitespace-only: {raw!r}")
|
|
68
|
+
if entry == "!":
|
|
69
|
+
raise ValueError("ignore pattern entry '!' is bare negation with no target")
|
|
70
|
+
return entry
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_ignore_patterns(additional_patterns: list[str] | None = None) -> set[str]:
|
|
74
|
+
"""Get ignore patterns with optional additions and negations.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
additional_patterns: Optional list of pattern overrides. Plain entries
|
|
78
|
+
are added on top of the defaults; entries prefixed with ``!`` are
|
|
79
|
+
removed from the defaults (gitignore-style negation). Example:
|
|
80
|
+
``["!dist", "!node_modules", "*.bak"]`` un-ignores ``dist`` and
|
|
81
|
+
``node_modules`` (so vendored JS build outputs survive sandbox
|
|
82
|
+
copy) and additionally ignores ``*.bak`` files.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Combined set of default and additional patterns, with negations
|
|
86
|
+
applied to the defaults.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If any entry fails :func:`normalize_ignore_pattern_entry`.
|
|
90
|
+
"""
|
|
91
|
+
patterns = load_default_ignore_patterns().copy()
|
|
92
|
+
|
|
93
|
+
if additional_patterns:
|
|
94
|
+
for raw in additional_patterns:
|
|
95
|
+
entry = normalize_ignore_pattern_entry(raw)
|
|
96
|
+
if entry.startswith("!"):
|
|
97
|
+
patterns.discard(entry[1:])
|
|
98
|
+
else:
|
|
99
|
+
patterns.add(entry)
|
|
100
|
+
|
|
101
|
+
return patterns
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def matches_pattern(path_part: str, pattern: str) -> bool:
|
|
105
|
+
"""Check if a path part matches an ignore pattern.
|
|
106
|
+
|
|
107
|
+
Supports:
|
|
108
|
+
- Exact match: "node_modules"
|
|
109
|
+
- Prefix wildcard: "*.pyc" (matches "file.pyc")
|
|
110
|
+
- Suffix wildcard: "test_*" (matches "test_utils.py")
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
path_part: Single path component (e.g., "file.pyc")
|
|
114
|
+
pattern: Pattern to match against
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if path_part matches pattern
|
|
118
|
+
"""
|
|
119
|
+
if pattern.startswith("*"):
|
|
120
|
+
return path_part.endswith(pattern[1:])
|
|
121
|
+
elif pattern.endswith("*"):
|
|
122
|
+
return path_part.startswith(pattern[:-1])
|
|
123
|
+
else:
|
|
124
|
+
return path_part == pattern
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def should_ignore_path(path: Path, patterns: set[str]) -> bool:
|
|
128
|
+
"""Check if any part of a path matches ignore patterns.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
path: Path to check
|
|
132
|
+
patterns: Set of patterns to match against
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if any part of the path matches any pattern
|
|
136
|
+
"""
|
|
137
|
+
return any(any(matches_pattern(str(part), pattern) for pattern in patterns) for part in path.parts)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Default experiment — provides baseline agent configuration.
|
|
2
|
+
#
|
|
3
|
+
# Tasks that omit the `agent` field inherit these defaults via the
|
|
4
|
+
# 4-layer merge chain: default.yaml → experiment defaults → task YAML → variant.
|
|
5
|
+
# All agent properties are listed explicitly so users know the baseline.
|
|
6
|
+
|
|
7
|
+
experiment_id: default
|
|
8
|
+
description: "Default experiment - provides baseline agent configuration"
|
|
9
|
+
|
|
10
|
+
defaults:
|
|
11
|
+
# Number of replicates for each task (repeats the same task N times per variant).
|
|
12
|
+
# Useful for quantifying variance in non-deterministic agent behavior.
|
|
13
|
+
# Overridable per-variant or via --repeats on the CLI.
|
|
14
|
+
# repeats: 1
|
|
15
|
+
|
|
16
|
+
# Run-time caps (turns, wall-clock, tokens, USD). Override per-variant or per-task via
|
|
17
|
+
# field-merge — a task or variant can set a single key without replacing the whole block.
|
|
18
|
+
# Per-row dataset multiplication: a 100-row task with max_usd: 0.10 permits up to
|
|
19
|
+
# $10 cumulative spend.
|
|
20
|
+
run_limits:
|
|
21
|
+
# Maximum agent inner-loop turns per iteration (null = SDK default).
|
|
22
|
+
# Typical range in tasks: 3-18 depending on complexity.
|
|
23
|
+
max_turns: 20
|
|
24
|
+
# Soft target: when cumulative SDK turns across all iterations of a task
|
|
25
|
+
# exceed this, the orchestrator logs a one-shot warning and the report
|
|
26
|
+
# surfaces a badge. Does NOT abort the run (max_turns is the hard cap).
|
|
27
|
+
# expected_turns: 15
|
|
28
|
+
# Maximum total seconds for the entire task evaluation (all iterations combined).
|
|
29
|
+
task_timeout: 600
|
|
30
|
+
# Maximum seconds per agent turn / communicate call (null = no limit).
|
|
31
|
+
turn_timeout: 300
|
|
32
|
+
# Optional token / cost caps (leave commented to disable).
|
|
33
|
+
# max_input_tokens: 100000
|
|
34
|
+
# max_output_tokens: 20000
|
|
35
|
+
# max_total_tokens: 120000
|
|
36
|
+
# max_usd: 0.50
|
|
37
|
+
# count_cached_input: false
|
|
38
|
+
|
|
39
|
+
agent:
|
|
40
|
+
# Agent type (currently only "claude-code" is supported)
|
|
41
|
+
type: claude-code
|
|
42
|
+
|
|
43
|
+
# Permission mode for agent actions:
|
|
44
|
+
# default - ask for confirmation on each action
|
|
45
|
+
# acceptEdits - auto-accept file edits, confirm other actions
|
|
46
|
+
# plan - read-only, no file modifications allowed
|
|
47
|
+
# bypassPermissions - auto-accept all actions (use with caution)
|
|
48
|
+
permission_mode: acceptEdits
|
|
49
|
+
|
|
50
|
+
# Specific model to use (null = defer to Claude Code CLI default)
|
|
51
|
+
# Examples: claude-sonnet-4-6, claude-opus-4-6
|
|
52
|
+
model: claude-sonnet-4-6
|
|
53
|
+
|
|
54
|
+
# Allowed tools — restrict which tools the agent can use (null = all tools)
|
|
55
|
+
# Most tasks use ["Bash"] or ["Bash", "Read", "Write"]. This default
|
|
56
|
+
# provides the common set needed for general-purpose coding tasks.
|
|
57
|
+
allowed_tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "Skill"]
|
|
58
|
+
|
|
59
|
+
# Claude Code plugins to load (null = none)
|
|
60
|
+
# Example: [{"type": "local", "path": "/path/to/skills"}]
|
|
61
|
+
plugins: null
|
|
62
|
+
|
|
63
|
+
# Additional patterns to ignore during file change detection (beyond defaults)
|
|
64
|
+
# Example: ["*.log", "__pycache__"]
|
|
65
|
+
ignore_patterns: []
|
|
66
|
+
|
|
67
|
+
# Claude Code SDK pass-through options — anything coder_eval doesn't own
|
|
68
|
+
# directly (e.g. `effort`, `max_thinking_tokens`). Keys must be
|
|
69
|
+
# ClaudeAgentOptions fields and must not be framework-managed (model,
|
|
70
|
+
# allowed_tools, permission_mode, hooks, mcp_servers, ...). Deep-merged
|
|
71
|
+
# across experiment / task / variant / --sdk-option layers, so a higher
|
|
72
|
+
# layer adding one key doesn't wipe the rest.
|
|
73
|
+
# Example: sdk_options: {effort: high}
|
|
74
|
+
# sdk_options: {}
|
|
75
|
+
|
|
76
|
+
# Sandbox-internal cleanup appended after each task's own post_run (MST-9674).
|
|
77
|
+
# PR #250 sandbox-scopes Node/npm installs to <sandbox>/{node_modules,.npm-prefix};
|
|
78
|
+
# removing them keeps preserved-sandbox artifacts small and is a no-op in normal
|
|
79
|
+
# (non-preserved) runs where the tempdir is rmtree'd anyway.
|
|
80
|
+
post_run:
|
|
81
|
+
- command: "rm -rf node_modules .npm-prefix"
|
|
82
|
+
timeout: 30
|
|
83
|
+
|
|
84
|
+
variants:
|
|
85
|
+
- variant_id: default
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Default file/directory patterns to ignore during file operations
|
|
2
|
+
# These patterns apply to:
|
|
3
|
+
# - Template directory copying (sandbox setup)
|
|
4
|
+
# - Workspace copying into an agent_judge sub-agent sandbox
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
virtual_environments:
|
|
8
|
+
- ".venv"
|
|
9
|
+
- "venv"
|
|
10
|
+
- "ENV"
|
|
11
|
+
- "env"
|
|
12
|
+
- "virtualenv"
|
|
13
|
+
|
|
14
|
+
# Version control
|
|
15
|
+
version_control:
|
|
16
|
+
- ".git"
|
|
17
|
+
- ".svn"
|
|
18
|
+
- ".hg"
|
|
19
|
+
|
|
20
|
+
# Python artifacts
|
|
21
|
+
python_artifacts:
|
|
22
|
+
- "__pycache__"
|
|
23
|
+
- "*.pyc"
|
|
24
|
+
- "*.pyo"
|
|
25
|
+
- "*.pyd"
|
|
26
|
+
- ".pytest_cache"
|
|
27
|
+
- ".ruff_cache"
|
|
28
|
+
- "*.egg-info"
|
|
29
|
+
- ".eggs"
|
|
30
|
+
- "dist"
|
|
31
|
+
- "build"
|
|
32
|
+
|
|
33
|
+
# IDE and editor files
|
|
34
|
+
ide_files:
|
|
35
|
+
- ".vscode"
|
|
36
|
+
- ".idea"
|
|
37
|
+
- "*.swp"
|
|
38
|
+
- "*.swo"
|
|
39
|
+
- "*~"
|
|
40
|
+
- ".DS_Store"
|
|
41
|
+
- "Thumbs.db"
|
|
42
|
+
|
|
43
|
+
# Package managers
|
|
44
|
+
package_managers:
|
|
45
|
+
- "node_modules"
|
|
46
|
+
- ".npm"
|
|
47
|
+
- "bower_components"
|
|
48
|
+
|
|
49
|
+
# Test and coverage
|
|
50
|
+
test_coverage:
|
|
51
|
+
- ".coverage"
|
|
52
|
+
- "htmlcov"
|
|
53
|
+
- ".tox"
|
|
54
|
+
- ".nox"
|
|
55
|
+
|
|
56
|
+
# Compiled files
|
|
57
|
+
compiled:
|
|
58
|
+
- "*.so"
|
|
59
|
+
- "*.dylib"
|
|
60
|
+
- "*.dll"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Suggested vocabulary of tags for post-run task reviews.
|
|
2
|
+
#
|
|
3
|
+
# This file is a *suggested* vocabulary, not a strict allowlist. The
|
|
4
|
+
# /coder-eval-review skill prefers names from this list but emits whatever
|
|
5
|
+
# fits. A periodic vocabulary lint reports tags appearing in review.json
|
|
6
|
+
# files that aren't here, so drift is visible
|
|
7
|
+
# without blocking new categories from emerging.
|
|
8
|
+
#
|
|
9
|
+
# Tags can describe failure modes, agent behaviors, or positive traits
|
|
10
|
+
# (e.g. optimal-path, called-tools). To add a vocabulary entry, open a PR.
|
|
11
|
+
#
|
|
12
|
+
# Each entry:
|
|
13
|
+
# name - kebab-case canonical slug (required)
|
|
14
|
+
# definition - one short sentence describing what the tag means (required)
|
|
15
|
+
# examples - optional list of short, illustrative snippets
|
|
16
|
+
tags:
|
|
17
|
+
- name: validation-error
|
|
18
|
+
definition: Schema or CLI validation failed for a generated artifact (e.g. .flow, .xaml, JSON).
|
|
19
|
+
examples:
|
|
20
|
+
- "Schema validation failed: expected string, received undefined"
|
|
21
|
+
- "Invalid .flow: missing required field 'startNode'"
|
|
22
|
+
- name: sandbox-leak
|
|
23
|
+
definition: Agent read or wrote files outside its assigned task directory.
|
|
24
|
+
examples:
|
|
25
|
+
- "Read sibling runs/<other_run>/artifacts/..."
|
|
26
|
+
- name: agent-loop
|
|
27
|
+
definition: Agent retried the same failing action repeatedly without adapting.
|
|
28
|
+
examples:
|
|
29
|
+
- "Same Bash command failed 5 times in a row with identical error"
|
|
30
|
+
- name: tool-error
|
|
31
|
+
definition: Bash/Read/Edit/SDK tool failed for reasons unrelated to task logic.
|
|
32
|
+
examples:
|
|
33
|
+
- "Edit failed: file not found"
|
|
34
|
+
- "Bash: command not found"
|
|
35
|
+
- name: model-nondeterminism
|
|
36
|
+
definition: Same task flips pass/fail across replicates with no code change.
|
|
37
|
+
- name: judge-disagreement
|
|
38
|
+
definition: LLM-judge verdict inconsistent with artifacts produced.
|
|
39
|
+
- name: infra
|
|
40
|
+
definition: Sandbox, network, auth, or external-service failure unrelated to the agent.
|
|
41
|
+
examples:
|
|
42
|
+
- "Auth token expired mid-run"
|
|
43
|
+
- "Network timeout reaching external service"
|
|
44
|
+
- name: criteria-bug
|
|
45
|
+
definition: Agent produced correct output but criteria did not match (false negative).
|
|
46
|
+
examples:
|
|
47
|
+
- "command_executed expected 'uip run' but agent used 'uipath run'"
|
|
48
|
+
- name: turn-timeout-too-short
|
|
49
|
+
definition: Task hit its per-turn timeout; the limit is too tight for the work the task requires.
|
|
50
|
+
examples:
|
|
51
|
+
- "300s turn_timeout exceeded at 80 turns; analysis recommends 600s"
|
|
52
|
+
- name: max-turns-too-low
|
|
53
|
+
definition: Task ran out of turns before completing; max_turns budget is below what the task realistically needs.
|
|
54
|
+
examples:
|
|
55
|
+
- "MAX_TURNS_EXHAUSTED: max_turns=50 but 86 turns used"
|