pluginbench 0.5.0__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.
- pluginbench/__init__.py +3 -0
- pluginbench/__main__.py +3 -0
- pluginbench/assessment-charts.html +790 -0
- pluginbench/assessment.py +316 -0
- pluginbench/assessment_charts.py +1225 -0
- pluginbench/assessment_metrics.py +1956 -0
- pluginbench/assessment_models.py +127 -0
- pluginbench/baselines.py +792 -0
- pluginbench/claude.py +374 -0
- pluginbench/cli.py +672 -0
- pluginbench/code_diff.py +207 -0
- pluginbench/codex.py +451 -0
- pluginbench/codex_events.py +585 -0
- pluginbench/collectors.py +591 -0
- pluginbench/compare.py +927 -0
- pluginbench/components.py +129 -0
- pluginbench/coverage_report.py +610 -0
- pluginbench/datasets.py +207 -0
- pluginbench/evaluation.example.yaml +45 -0
- pluginbench/evaluation.py +1651 -0
- pluginbench/evaluation_report.py +1004 -0
- pluginbench/fallow_parse.mjs +101 -0
- pluginbench/harbor_backend.py +2946 -0
- pluginbench/isolation.py +314 -0
- pluginbench/judge.py +683 -0
- pluginbench/language_analysis.py +915 -0
- pluginbench/ledger.py +217 -0
- pluginbench/measurement_profiles.py +616 -0
- pluginbench/native_treatment_runtime.js +221 -0
- pluginbench/native_treatments.py +155 -0
- pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
- pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
- pluginbench/observer_data/go-analysis-v1/main.go +64 -0
- pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
- pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
- pluginbench/parallel.py +101 -0
- pluginbench/processes.py +100 -0
- pluginbench/remeasure.py +992 -0
- pluginbench/run_lock.py +227 -0
- pluginbench/run_status.py +518 -0
- pluginbench/study.py +1858 -0
- pluginbench/study_review.py +924 -0
- pluginbench/submissions.py +486 -0
- pluginbench/task_sources.py +403 -0
- pluginbench/treatments.py +388 -0
- pluginbench/trial_containers.py +94 -0
- pluginbench/vendor/ECHARTS-LICENSE +222 -0
- pluginbench/vendor/ECHARTS-NOTICE +5 -0
- pluginbench/vendor/ZRENDER-LICENSE +29 -0
- pluginbench/vendor/echarts.min.js +48 -0
- pluginbench-0.5.0.dist-info/METADATA +294 -0
- pluginbench-0.5.0.dist-info/RECORD +58 -0
- pluginbench-0.5.0.dist-info/WHEEL +4 -0
- pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
- pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Executed inside the attempt's private environment, before the coding command.
|
|
2
|
+
// This uses the CLI's installation and discovery APIs; it never starts a model turn.
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
const crypto = require("node:crypto");
|
|
8
|
+
const { spawn, execFileSync } = require("node:child_process");
|
|
9
|
+
const readline = require("node:readline");
|
|
10
|
+
|
|
11
|
+
const [root, cwd] = process.argv.slice(2);
|
|
12
|
+
const payload = JSON.parse(fs.readFileSync(path.join(root, "treatment.json"), "utf8"));
|
|
13
|
+
const codexHome = fs.realpathSync(process.env.CODEX_HOME);
|
|
14
|
+
const home = fs.realpathSync(process.env.HOME);
|
|
15
|
+
const hash = bytes => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
16
|
+
const check = (condition, message) => { if (!condition) throw new Error(message); };
|
|
17
|
+
const canonical = value => fs.realpathSync(value);
|
|
18
|
+
const inside = (value, parent) => value === parent || value.startsWith(parent + path.sep);
|
|
19
|
+
|
|
20
|
+
function cli(args) {
|
|
21
|
+
return JSON.parse(execFileSync("codex", args, {
|
|
22
|
+
cwd, env: process.env, encoding: "utf8", timeout: 30000, maxBuffer: 8 * 1024 * 1024,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function treeEntries(directory, relative = ".") {
|
|
27
|
+
const file = path.join(directory, relative);
|
|
28
|
+
const stat = fs.lstatSync(file);
|
|
29
|
+
check(!stat.isSymbolicLink(), "Installed treatment contains a symlink: " + file);
|
|
30
|
+
check(stat.isDirectory() || stat.isFile(), "Installed treatment has a nonregular asset: " + file);
|
|
31
|
+
const entry = { path: relative, kind: stat.isDirectory() ? "directory" : "file", mode: stat.mode & 0o7777 };
|
|
32
|
+
if (stat.isFile()) {
|
|
33
|
+
const bytes = fs.readFileSync(file);
|
|
34
|
+
Object.assign(entry, { sha256: hash(bytes), size: bytes.length });
|
|
35
|
+
}
|
|
36
|
+
const entries = [entry];
|
|
37
|
+
if (stat.isDirectory()) {
|
|
38
|
+
for (const name of fs.readdirSync(file).sort()) {
|
|
39
|
+
entries.push(...treeEntries(directory, relative === "." ? name : relative + "/" + name));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return entries;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function verifyTree(directory, expected) {
|
|
46
|
+
const observed = treeEntries(directory);
|
|
47
|
+
const expectedByPath = new Map(expected.entries.map(entry => [entry.path, entry]));
|
|
48
|
+
check(observed.length === expectedByPath.size, "Installed treatment inventory differs: " + directory);
|
|
49
|
+
// Harbor's upload step grants readability. Restore and check the frozen modes.
|
|
50
|
+
for (const entry of observed) {
|
|
51
|
+
const original = expectedByPath.get(entry.path);
|
|
52
|
+
check(original && original.kind === entry.kind && original.sha256 === entry.sha256
|
|
53
|
+
&& original.size === entry.size, "Installed treatment bytes differ: " + directory + "/" + entry.path);
|
|
54
|
+
fs.chmodSync(path.join(directory, entry.path), original.mode);
|
|
55
|
+
check((fs.statSync(path.join(directory, entry.path)).mode & 0o7777) === original.mode,
|
|
56
|
+
"Installed treatment mode differs: " + directory + "/" + entry.path);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function discover() {
|
|
61
|
+
const child = spawn("codex", ["app-server"], { cwd, env: process.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
62
|
+
const pending = new Map();
|
|
63
|
+
let nextId = 1;
|
|
64
|
+
let stderr = "";
|
|
65
|
+
child.stderr.on("data", bytes => { stderr += bytes.toString(); });
|
|
66
|
+
const lines = readline.createInterface({ input: child.stdout });
|
|
67
|
+
const fail = error => {
|
|
68
|
+
for (const item of pending.values()) item.reject(error);
|
|
69
|
+
pending.clear();
|
|
70
|
+
};
|
|
71
|
+
child.on("error", fail);
|
|
72
|
+
child.on("exit", code => fail(new Error("Native discovery exited: " + code + " " + stderr.slice(0, 1000))));
|
|
73
|
+
lines.on("line", line => {
|
|
74
|
+
let message;
|
|
75
|
+
try { message = JSON.parse(line); } catch { fail(new Error("Malformed native discovery JSON")); return; }
|
|
76
|
+
if (!Object.hasOwn(message, "id")) return;
|
|
77
|
+
const item = pending.get(message.id);
|
|
78
|
+
if (!item) return;
|
|
79
|
+
pending.delete(message.id);
|
|
80
|
+
if (message.error) item.reject(new Error(JSON.stringify(message.error)));
|
|
81
|
+
else item.resolve(message.result);
|
|
82
|
+
});
|
|
83
|
+
const request = (method, params) => new Promise((resolve, reject) => {
|
|
84
|
+
const id = nextId++;
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
pending.delete(id);
|
|
87
|
+
reject(new Error("Native discovery timed out: " + method));
|
|
88
|
+
}, 20000);
|
|
89
|
+
pending.set(id, {
|
|
90
|
+
resolve: value => { clearTimeout(timer); resolve(value); },
|
|
91
|
+
reject: error => { clearTimeout(timer); reject(error); },
|
|
92
|
+
});
|
|
93
|
+
child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
await request("initialize", { clientInfo: { name: "pluginbench", version: "1" }, capabilities: { experimentalApi: true } });
|
|
97
|
+
child.stdin.write(JSON.stringify({ method: "initialized", params: {} }) + "\n");
|
|
98
|
+
const skills = await request("skills/list", { cwds: [cwd], forceReload: true });
|
|
99
|
+
const hooks = await request("hooks/list", { cwds: [cwd] });
|
|
100
|
+
return { skills, hooks };
|
|
101
|
+
} finally {
|
|
102
|
+
lines.close();
|
|
103
|
+
child.stdin.end();
|
|
104
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
105
|
+
await new Promise(resolve => {
|
|
106
|
+
const timer = setTimeout(() => { child.kill("SIGKILL"); }, 1000);
|
|
107
|
+
child.once("exit", () => { clearTimeout(timer); resolve(); });
|
|
108
|
+
child.kill("SIGTERM");
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function main() {
|
|
115
|
+
const guidancePath = path.join(codexHome, "AGENTS.md");
|
|
116
|
+
check(!fs.existsSync(guidancePath), "Undeclared native global instructions already exist");
|
|
117
|
+
// The prepared source must remain unchanged. Native global instructions apply
|
|
118
|
+
// to this single attempt and cannot become part of its source patch.
|
|
119
|
+
if (payload.inventory.guidance.length) {
|
|
120
|
+
const bytes = fs.readFileSync(path.join(root, "AGENTS.md"));
|
|
121
|
+
check(hash(bytes) === payload.guidance_sha256 && bytes.length === payload.guidance_bytes,
|
|
122
|
+
"Staged instructions changed");
|
|
123
|
+
fs.writeFileSync(guidancePath, bytes, { flag: "wx", mode: 0o600 });
|
|
124
|
+
}
|
|
125
|
+
const expectedSkills = new Map();
|
|
126
|
+
for (const skill of payload.inventory.skills) {
|
|
127
|
+
const directory = path.join(home, ".agents/skills", skill.directory);
|
|
128
|
+
verifyTree(directory, skill);
|
|
129
|
+
expectedSkills.set(canonical(path.join(directory, "SKILL.md")), { name: skill.name, pluginId: null });
|
|
130
|
+
}
|
|
131
|
+
const installs = [];
|
|
132
|
+
if (payload.inventory.plugins.length) {
|
|
133
|
+
const added = cli(["plugin", "marketplace", "add", path.join(root, "marketplace"), "--json"]);
|
|
134
|
+
check(added.marketplaceName === payload.marketplace, "Native marketplace identity differs");
|
|
135
|
+
for (const plugin of payload.inventory.plugins) {
|
|
136
|
+
const id = plugin.name + "@" + payload.marketplace;
|
|
137
|
+
const installed = cli(["plugin", "add", id, "--json"]);
|
|
138
|
+
check(installed.pluginId === id && typeof installed.installedPath === "string", "Native plugin identity differs");
|
|
139
|
+
const installedRoot = canonical(installed.installedPath);
|
|
140
|
+
check(inside(installedRoot, path.join(codexHome, "plugins/cache")), "Native plugin installed outside private cache");
|
|
141
|
+
verifyTree(installedRoot, plugin);
|
|
142
|
+
installs.push({ ...installed, expected: plugin });
|
|
143
|
+
for (const skill of plugin.skills) {
|
|
144
|
+
expectedSkills.set(canonical(path.join(installedRoot, skill.path, "SKILL.md")), {
|
|
145
|
+
name: plugin.name + ":" + skill.name, pluginId: id,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const listed = cli(["plugin", "list", "--json"]);
|
|
151
|
+
check(Array.isArray(listed.installed) && listed.installed.length === installs.length,
|
|
152
|
+
"Native plugin inventory contains missing or undeclared installations: " + JSON.stringify({
|
|
153
|
+
expected: installs.map(item => item.pluginId),
|
|
154
|
+
installed: listed.installed?.map(item => ({
|
|
155
|
+
pluginId: item.pluginId, installed: item.installed, enabled: item.enabled,
|
|
156
|
+
marketplaceName: item.marketplaceName, installPolicy: item.installPolicy,
|
|
157
|
+
installPolicySource: item.installPolicySource,
|
|
158
|
+
})),
|
|
159
|
+
}));
|
|
160
|
+
for (const installed of installs) {
|
|
161
|
+
check(listed.installed.some(item => item.pluginId === installed.pluginId && item.installed === true && item.enabled === true),
|
|
162
|
+
"Native plugin is not installed and enabled: " + installed.pluginId);
|
|
163
|
+
}
|
|
164
|
+
const discovery = await discover();
|
|
165
|
+
const skillsEntry = discovery.skills.data?.find(entry => canonical(entry.cwd) === canonical(cwd));
|
|
166
|
+
const hooksEntry = discovery.hooks.data?.find(entry => canonical(entry.cwd) === canonical(cwd));
|
|
167
|
+
check(skillsEntry && skillsEntry.errors.length === 0, "Native skill discovery failed");
|
|
168
|
+
check(hooksEntry && hooksEntry.errors.length === 0 && hooksEntry.warnings.length === 0,
|
|
169
|
+
"Native hook discovery failed or warned");
|
|
170
|
+
const found = new Set();
|
|
171
|
+
for (const skill of skillsEntry.skills) {
|
|
172
|
+
const location = canonical(skill.path);
|
|
173
|
+
const expected = expectedSkills.get(location);
|
|
174
|
+
if (!expected) {
|
|
175
|
+
check(skill.scope === "system" && inside(location, path.join(codexHome, "skills/.system")),
|
|
176
|
+
"Undeclared native skill discovered: " + skill.name);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
check(skill.enabled === true && skill.name === expected.name && (skill.pluginId || null) === expected.pluginId,
|
|
180
|
+
"Native skill identity/availability differs: " + skill.name);
|
|
181
|
+
found.add(location);
|
|
182
|
+
}
|
|
183
|
+
check(found.size === expectedSkills.size, "Native runtime did not discover every declared skill");
|
|
184
|
+
let expectedHookCount = 0;
|
|
185
|
+
for (const plugin of installs) {
|
|
186
|
+
const expected = [];
|
|
187
|
+
for (const config of plugin.expected.hooks) {
|
|
188
|
+
for (const [event, groups] of Object.entries(config.hooks || config)) {
|
|
189
|
+
for (const group of groups) for (const hook of group.hooks) expected.push({ event, group, hook });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const observed = hooksEntry.hooks.filter(hook => hook.pluginId === plugin.pluginId);
|
|
193
|
+
check(observed.length === expected.length, "Native plugin hook inventory differs: " + plugin.pluginId);
|
|
194
|
+
for (const { event, group, hook } of expected) {
|
|
195
|
+
const eventName = event[0].toLowerCase() + event.slice(1);
|
|
196
|
+
check(observed.some(item => item.eventName === eventName && item.enabled === true
|
|
197
|
+
&& item.source === "plugin" && item.handlerType === "command" && !item.async
|
|
198
|
+
&& item.command === hook.command && (item.matcher || null) === (group.matcher || null)
|
|
199
|
+
&& inside(canonical(item.sourcePath), canonical(plugin.installedPath))),
|
|
200
|
+
"Native plugin hook was not registered faithfully: " + plugin.pluginId + "/" + event);
|
|
201
|
+
}
|
|
202
|
+
expectedHookCount += expected.length;
|
|
203
|
+
}
|
|
204
|
+
check(hooksEntry.hooks.length === expectedHookCount, "Undeclared native hooks discovered");
|
|
205
|
+
const receipt = {
|
|
206
|
+
status: "verified", delivery: payload.delivery, inventory: payload.inventory,
|
|
207
|
+
guidance: { path: payload.inventory.guidance.length ? guidancePath : null,
|
|
208
|
+
sha256: payload.guidance_sha256, bytes: payload.guidance_bytes, scope: "native global instructions" },
|
|
209
|
+
plugins: listed.installed, skills: skillsEntry, hooks: hooksEntry,
|
|
210
|
+
hook_trust: "per-coding-invocation bypass for the verified frozen package sources",
|
|
211
|
+
hook_execution: "not observed by discovery; target execution evidence is separate",
|
|
212
|
+
skill_activation: "not observed by discovery; native availability is verified",
|
|
213
|
+
effective_config: fs.readFileSync(path.join(codexHome, "config.toml"), "utf8"),
|
|
214
|
+
};
|
|
215
|
+
process.stdout.write(JSON.stringify(receipt) + "\n");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
main().catch(error => {
|
|
219
|
+
process.stderr.write(String(error.stack || error) + "\n");
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Install declared treatments through the native Codex CLI in an owned runtime.
|
|
2
|
+
|
|
3
|
+
Harbor owns skill upload, configuration and execution. This adapter runs after
|
|
4
|
+
Harbor's configuration/skill setup and before its first coding command. The
|
|
5
|
+
companion Node program checks the actual installation and native discovery.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import shlex
|
|
13
|
+
import shutil
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
19
|
+
|
|
20
|
+
from pluginbench.treatments import Treatment, resolve_treatment, treatment_inventory
|
|
21
|
+
|
|
22
|
+
DELIVERY_POLICY = "native-instructions-skills-plugins-v1"
|
|
23
|
+
MARKETPLACE = "pluginbench-frozen"
|
|
24
|
+
REMOTE_ROOT = "/tmp/pluginbench-treatments"
|
|
25
|
+
MAX_GUIDANCE_BYTES = 32768
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NativeCapabilities(BaseModel):
|
|
29
|
+
"""One capability policy for vanilla and every treatment in a study."""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(extra="forbid")
|
|
32
|
+
|
|
33
|
+
subagents: bool = True
|
|
34
|
+
max_threads: int = Field(default=4, strict=True, ge=1, le=16)
|
|
35
|
+
max_depth: int = Field(
|
|
36
|
+
default=1, strict=True, ge=1, le=2,
|
|
37
|
+
description="Native V1 depth preference; ignored by model-selected V2 agents.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def codex_config(self, *, model: str, effort: str) -> dict[str, object]:
|
|
41
|
+
return {
|
|
42
|
+
"features": {
|
|
43
|
+
"code_mode": True,
|
|
44
|
+
"code_mode_only": True,
|
|
45
|
+
"plugins": True,
|
|
46
|
+
"remote_plugin": False,
|
|
47
|
+
"apps": False,
|
|
48
|
+
"hooks": True,
|
|
49
|
+
"multi_agent": self.subagents,
|
|
50
|
+
},
|
|
51
|
+
"project_doc_max_bytes": MAX_GUIDANCE_BYTES,
|
|
52
|
+
"agents": {
|
|
53
|
+
# Unlike feature flags, this also disables model-selected V2 agents.
|
|
54
|
+
"enabled": self.subagents,
|
|
55
|
+
"max_threads": self.max_threads,
|
|
56
|
+
"max_depth": self.max_depth,
|
|
57
|
+
"default_subagent_model": model,
|
|
58
|
+
"default_subagent_reasoning_effort": effort,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def admit(treatment: Treatment, harness: str) -> dict[str, Any]:
|
|
64
|
+
"""Reject unsupported inputs before container preparation or any model call."""
|
|
65
|
+
if harness != "codex":
|
|
66
|
+
raise ValueError("native treatment execution is currently qualified for Codex only")
|
|
67
|
+
resolved = resolve_treatment(treatment, Path.cwd(), harness)
|
|
68
|
+
inventory = treatment_inventory(resolved)
|
|
69
|
+
guidance = b"\n\n".join(path.read_bytes() for path in resolved.guidance)
|
|
70
|
+
if len(guidance) > MAX_GUIDANCE_BYTES:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"native guidance exceeds {MAX_GUIDANCE_BYTES} bytes; refusing silent truncation"
|
|
73
|
+
)
|
|
74
|
+
return inventory
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def stage(treatment: Treatment, directory: Path) -> dict[str, Any]:
|
|
78
|
+
"""Build a local marketplace from complete, already-frozen packages."""
|
|
79
|
+
inventory = admit(treatment, "codex")
|
|
80
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
guidance = b"\n\n".join(path.read_bytes() for path in treatment.guidance)
|
|
82
|
+
(directory / "AGENTS.md").write_bytes(guidance)
|
|
83
|
+
market = directory / "marketplace"
|
|
84
|
+
manifest_dir = market / ".agents/plugins"
|
|
85
|
+
manifest_dir.mkdir(parents=True)
|
|
86
|
+
packages = []
|
|
87
|
+
for source, item in zip(treatment.plugins, inventory["plugins"], strict=True):
|
|
88
|
+
name = item["name"]
|
|
89
|
+
shutil.copytree(source, market / "plugins" / name, ignore=shutil.ignore_patterns(".git"))
|
|
90
|
+
packages.append({
|
|
91
|
+
"name": name,
|
|
92
|
+
"source": {"source": "local", "path": "./plugins/" + name},
|
|
93
|
+
"policy": {"installation": "AVAILABLE", "authentication": "ON_INSTALL"},
|
|
94
|
+
"category": "Productivity",
|
|
95
|
+
})
|
|
96
|
+
(manifest_dir / "marketplace.json").write_text(
|
|
97
|
+
json.dumps({"name": MARKETPLACE, "plugins": packages}) + "\n"
|
|
98
|
+
)
|
|
99
|
+
payload = {
|
|
100
|
+
"delivery": DELIVERY_POLICY,
|
|
101
|
+
"inventory": inventory,
|
|
102
|
+
"guidance_sha256": hashlib.sha256(guidance).hexdigest(),
|
|
103
|
+
"guidance_bytes": len(guidance),
|
|
104
|
+
"marketplace": MARKETPLACE,
|
|
105
|
+
}
|
|
106
|
+
(directory / "treatment.json").write_text(json.dumps(payload, sort_keys=True) + "\n")
|
|
107
|
+
shutil.copy2(Path(__file__).with_name("native_treatment_runtime.js"), directory / "install.cjs")
|
|
108
|
+
return payload
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def install(
|
|
112
|
+
*, environment: object, treatment: Treatment,
|
|
113
|
+
env: dict[str, str], cwd: str, artifacts: Path,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Use the same environment/configuration as the upcoming native coding CLI."""
|
|
116
|
+
if env.get("CODEX_HOME") != "/tmp/codex-home":
|
|
117
|
+
raise ValueError("native treatment installation requires Harbor's private CODEX_HOME")
|
|
118
|
+
with tempfile.TemporaryDirectory(prefix="pluginbench-treatment-") as temporary:
|
|
119
|
+
local = Path(temporary).resolve()
|
|
120
|
+
payload = stage(treatment, local)
|
|
121
|
+
await getattr(environment, "upload_dir")(source_dir=local, target_dir=REMOTE_ROOT)
|
|
122
|
+
result = await getattr(environment, "exec")(
|
|
123
|
+
command=(
|
|
124
|
+
"if [ -s ~/.nvm/nvm.sh ]; then . ~/.nvm/nvm.sh; fi; "
|
|
125
|
+
f"node {REMOTE_ROOT}/install.cjs {REMOTE_ROOT} {shlex.quote(cwd)}"
|
|
126
|
+
" 2>/logs/agent/native-treatment.stderr"
|
|
127
|
+
),
|
|
128
|
+
env=env,
|
|
129
|
+
cwd=cwd,
|
|
130
|
+
timeout_sec=120,
|
|
131
|
+
)
|
|
132
|
+
receipt_file = artifacts / "native-treatment.json"
|
|
133
|
+
stdout = str(getattr(result, "stdout", ""))
|
|
134
|
+
(artifacts / "native-treatment.stdout").write_text(stdout)
|
|
135
|
+
await getattr(environment, "download_file")(
|
|
136
|
+
source_path="/logs/agent/native-treatment.stderr",
|
|
137
|
+
target_path=artifacts / "native-treatment.stderr",
|
|
138
|
+
)
|
|
139
|
+
if getattr(result, "return_code") != 0:
|
|
140
|
+
receipt_file.write_text(json.dumps({
|
|
141
|
+
"status": "failed", "delivery": DELIVERY_POLICY,
|
|
142
|
+
"inventory": payload["inventory"],
|
|
143
|
+
"reason": "native installation or discovery failed; coding was not started",
|
|
144
|
+
}, indent=2) + "\n")
|
|
145
|
+
raise ValueError("native treatment installation/discovery failed; model call refused")
|
|
146
|
+
try:
|
|
147
|
+
receipt = json.loads(stdout)
|
|
148
|
+
except ValueError as error:
|
|
149
|
+
raise ValueError("native treatment discovery returned invalid evidence") from error
|
|
150
|
+
if not isinstance(receipt, dict) or receipt.get("status") != "verified":
|
|
151
|
+
raise ValueError("native treatment discovery did not verify installation")
|
|
152
|
+
if receipt.get("inventory") != payload["inventory"]:
|
|
153
|
+
raise ValueError("native treatment receipt describes different package contents")
|
|
154
|
+
receipt_file.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n")
|
|
155
|
+
return receipt
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
|
2
|
+
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
|
3
|
+
github.com/uudashr/gocognit v1.3.0 h1:Xdw5JwAoYmX3WAB+wsXY3nxEwdw7+MWAnM0wCAAv7A4=
|
|
4
|
+
github.com/uudashr/gocognit v1.3.0/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q=
|
|
5
|
+
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
|
6
|
+
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
|
7
|
+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
8
|
+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
9
|
+
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
|
10
|
+
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Parse spans with Go's standard parser; all scores come from pinned analyzer libraries.
|
|
2
|
+
package main
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"github.com/fzipp/gocyclo"
|
|
8
|
+
"github.com/uudashr/gocognit"
|
|
9
|
+
"go/ast"
|
|
10
|
+
"go/parser"
|
|
11
|
+
"go/token"
|
|
12
|
+
"os"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type function struct {
|
|
16
|
+
Path string `json:"path"`
|
|
17
|
+
Name string `json:"name"`
|
|
18
|
+
Line int `json:"line"`
|
|
19
|
+
Col int `json:"col"`
|
|
20
|
+
EndLine int `json:"end_line"`
|
|
21
|
+
LineCount int `json:"line_count"`
|
|
22
|
+
Cognitive int `json:"cognitive"`
|
|
23
|
+
Cyclomatic int `json:"cyclomatic"`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func main() {
|
|
27
|
+
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
|
28
|
+
fmt.Println("go-analysis-v1 gocognit=1.3.0 gocyclo=0.6.0")
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
rows := []function{}
|
|
32
|
+
for _, path := range os.Args[1:] {
|
|
33
|
+
fset := token.NewFileSet()
|
|
34
|
+
file, err := parser.ParseFile(fset, path, nil, 0)
|
|
35
|
+
if err != nil {
|
|
36
|
+
fmt.Fprintln(os.Stderr, err)
|
|
37
|
+
os.Exit(2)
|
|
38
|
+
}
|
|
39
|
+
ast.Inspect(file, func(node ast.Node) bool {
|
|
40
|
+
var fn *ast.FuncDecl
|
|
41
|
+
name := ""
|
|
42
|
+
switch n := node.(type) {
|
|
43
|
+
case *ast.FuncDecl:
|
|
44
|
+
fn = n
|
|
45
|
+
name = n.Name.Name
|
|
46
|
+
if n.Recv != nil {
|
|
47
|
+
name = fmt.Sprintf("method@%d:%s", fset.Position(n.Pos()).Line, name)
|
|
48
|
+
}
|
|
49
|
+
case *ast.FuncLit:
|
|
50
|
+
name = fmt.Sprintf("closure@%d:%d", fset.Position(n.Pos()).Line, fset.Position(n.Pos()).Column)
|
|
51
|
+
fn = &ast.FuncDecl{Type: n.Type, Body: n.Body, Name: ast.NewIdent(name)}
|
|
52
|
+
default:
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
start, end := fset.Position(node.Pos()), fset.Position(node.End())
|
|
56
|
+
rows = append(rows, function{path, name, start.Line, start.Column, end.Line, end.Line - start.Line + 1, gocognit.Complexity(fn), gocyclo.Complexity(node)})
|
|
57
|
+
return true
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
if err := json.NewEncoder(os.Stdout).Encode(rows); err != nil {
|
|
61
|
+
fmt.Fprintln(os.Stderr, err)
|
|
62
|
+
os.Exit(2)
|
|
63
|
+
}
|
|
64
|
+
}
|