@feng0531/dsh-code-review 0.56.0 → 0.58.0
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.
- package/CHANGELOG.md +9 -1
- package/CORE-ALIGNMENT-TASKS.md +5 -2
- package/CORE-SOURCE-INVENTORY.md +6 -3
- package/RELEASE-NOTES.md +11 -6
- package/evaluation/README.md +24 -0
- package/evaluation/REVIEWBENCH-LOCAL.md +47 -0
- package/package.json +2 -2
- package/scripts/review-bench-adapter.mjs +125 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
本文件介绍对使用者有意义的变化。安装与参数见 [README](README.md),本次发布正文见 [发布说明](RELEASE-NOTES.md)。历史提交和安装包保留在 [GitHub Releases](https://github.com/f-e-n-g-0531/dsh-code-review/releases);本文不把开发日志当作功能介绍。
|
|
4
4
|
|
|
5
|
+
## 0.58.0 — 本地评测入口可靠性整理
|
|
6
|
+
|
|
7
|
+
新增无需 Docker 的实验性 ReviewBench 本地入口与使用说明。要求显式本地仓库、模型与端点;复用严格 JSON 校验,拒绝截断和夹杂文本。未完成审查、不可映射的旧侧发现及超限结果明确失败并保存原报告,不伪装为空发现;同一行的不同问题不再误合并。mock 输出与可评分候选隔离,运行结果不覆盖。正常 DSH 工具行为、权限与预算不变;不代表真实模型质量已得到验证。
|
|
8
|
+
|
|
9
|
+
## 0.57.0 — 对齐结论与边界收尾
|
|
10
|
+
|
|
11
|
+
完成组合场景整体一致性验收与完整语义导航的源码对照收尾:范围内已对齐项清空「仍需补齐」;参考独有的会话存储、断点续跑、豁免与 token 预估记为有意不同;完整语义调用方、别名与跨目录实例上下文明确为需语义解析器的范围外。不改变审查行为或参数。449 项完整回归与四组 CI 通过;npm、Release 与原产物 108,451 字节一致,双哈希和清单通过。
|
|
12
|
+
|
|
5
13
|
## 0.56.0 — 依赖清单与配置文件专用指导
|
|
6
14
|
|
|
7
|
-
为 Cargo.toml、composer.json、package.json、pom.xml、build.gradle 等依赖清单补充版本可复现、依赖声明约束与发布元数据的保守缺陷提示;为 GitHub 工作流/配置、yaml、properties、json 等配置文件补充最小权限、模板字段与键名拼写的保守缺陷提示。按具体路径与后缀选择,仍为证据门控提示,不是解析或格式校验结果。只读批准快照,不改变审查行为或参数。
|
|
15
|
+
为 Cargo.toml、composer.json、package.json、pom.xml、build.gradle 等依赖清单补充版本可复现、依赖声明约束与发布元数据的保守缺陷提示;为 GitHub 工作流/配置、yaml、properties、json 等配置文件补充最小权限、模板字段与键名拼写的保守缺陷提示。按具体路径与后缀选择,仍为证据门控提示,不是解析或格式校验结果。只读批准快照,不改变审查行为或参数。449 项完整回归、实际宿主、隔离安装与四组 CI 通过;npm、Release 与原产物 108,081 字节一致,双哈希和清单通过。
|
|
8
16
|
|
|
9
17
|
## 0.55.0 — 模板引擎与基础设施即代码专用指导
|
|
10
18
|
|
package/CORE-ALIGNMENT-TASKS.md
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
## 结论与边界
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
保守语法范围内的核心审查能力已对齐并正式交付:变更采集、范围保护、审查流程、检索、字面/命名空间/类实例调用方发现、命名与默认定义、历史同版本上下文、三层依赖链、语言与各类文件(模板、基础设施、依赖清单、配置)的专用指导及五类协议提示、多轮×窗口×检索×分组×验证的组合验收。以下剩余项不在本目标的保守范围或由目标明确后置,不猜测也不虚假宣称完成:
|
|
25
25
|
|
|
26
26
|
- 继承、原型链与动态分派:需要语言解析器或类型检查器做语义绑定,超出「只读批准快照、不放宽预算」的保守边界。
|
|
27
|
+
- 完整语义调用方、别名与跨目录实例上下文:需要语义解析器;保守的命名/默认/命名空间成员/类实例方法导航已交付,其余不猜测。
|
|
27
28
|
- SVN 历史区间等旧侧独有来源:历史/旧侧三层链、oldContextPaths、删除旧正文复用已交付;SVN 历史区间按设计不支持。
|
|
28
29
|
- 真实模型质量评测:目标明确后置,保存真实输出、人工裁决、分开统计,不作为对齐前提。
|
|
29
30
|
|
|
@@ -31,7 +32,9 @@
|
|
|
31
32
|
|
|
32
33
|
## 当前验证结论
|
|
33
34
|
|
|
34
|
-
- 最近完整回归:
|
|
35
|
+
- 最近完整回归:449 项通过。
|
|
36
|
+
- v0.57.0 已正式交付对齐结论与边界收尾。449 项回归与四组 CI 通过;npm、Release 与原包 108,451 字节一致,SHA-256、SHA-512、清单与发布说明核验通过;不改变审查行为或参数。
|
|
37
|
+
- v0.56.0 已正式交付依赖清单与配置文件专用指导。449 项回归、实际宿主、隔离安装与四组 CI 通过;npm、Release 与原包 108,081 字节一致,SHA-256、SHA-512、清单与发布说明核验通过;不改变审查行为或参数。
|
|
35
38
|
- v0.55.0 已正式交付模板引擎与基础设施即代码专用指导。446 项回归、实际宿主、隔离安装与四组 CI 通过;npm、Release 与原包 106,969 字节一致,SHA-256、SHA-512、清单与发布说明核验通过;不改变审查行为或参数。
|
|
36
39
|
- v0.54.0 已正式交付对齐结论与边界收尾。443 项回归、实际宿主与四组 CI 通过;npm、Release 与原包 104,761 字节一致,SHA-256、SHA-512、清单与发布说明核验通过;不改变审查行为或参数。
|
|
37
40
|
- v0.53.0 已正式交付分组×验证×检索的组合验收。443 项回归、实际宿主、13 项独立安装专项与四组 CI 通过;npm、Release 与原包 104,757 字节一致,双哈希与清单核验通过;不改变审查行为或参数。
|
package/CORE-SOURCE-INVENTORY.md
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
| 参考模块 | 当前对应能力 | 仍需补齐 |
|
|
12
12
|
|---|---|---|
|
|
13
|
-
| agent、delegate | 风险规划、分组、窗口、综合、多次审查、失败回退 |
|
|
13
|
+
| agent、delegate | 风险规划、分组、窗口、综合、多次审查、失败回退 | |
|
|
14
14
|
| llmloop | 模型循环、有限检索、候选反证、明确结束状态 | 真实模型质量验证 |
|
|
15
|
-
| tool | 批准快照内的文件查找、搜索、读取和证据校验 |
|
|
15
|
+
| tool | 批准快照内的文件查找、搜索、读取和证据校验 | |
|
|
16
16
|
| config/rules | 语言和文件用途检查提示、显式项目规则 | |
|
|
17
17
|
| diff、gitcmd、pathutil | 只读变更、固定历史对象、路径保护、命令限额 | 不照搬实时无界读取 |
|
|
18
|
-
| model、session/manifest | 覆盖状态、未完成原因、失败与取消区别 |
|
|
18
|
+
| model、session/manifest | 覆盖状态、未完成原因、失败与取消区别 | |
|
|
19
19
|
|
|
20
20
|
## 有意不同的实现
|
|
21
21
|
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
- 默认一次审查,可选三次,不照搬参考默认次数和对话继承方式。
|
|
26
26
|
- 历史重命名按删除和新增表达;区间比较使用明确端点,不自动计算合并基点。
|
|
27
27
|
- 参考中的风格偏好、无条件性能建议和错误绝对规则不作为缺陷标准。
|
|
28
|
+
- 不建会话存储、断点续跑与豁免:参考的 reused/waived 覆盖集合与续跑流程属会话/历史管理,单次只读审查不纳入。
|
|
29
|
+
- 不做 pre-run token 预估警告:用共享输入字节与调用预算在运行中约束,实际覆盖以报告终态为准。
|
|
30
|
+
- 未覆盖项保留 pending 并报 partial,不扫成 failed:诚实暴露未完成,不伪造终态。
|
|
28
31
|
|
|
29
32
|
## 语言指导的现状与剩余细化
|
|
30
33
|
|
package/RELEASE-NOTES.md
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
# v0.
|
|
1
|
+
# v0.58.0 发布说明
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 本地评测入口可靠性整理
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
新增无需 Docker 的实验性 ReviewBench 本地评测入口和说明,复用现有审查核心。配置、模型调用、结果映射分开测试;模型和端点显式指定,不改变 DSH 宿主配置。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- 复用严格 JSON 校验,拒绝截断响应和夹杂文字;HTTP 错误不输出正文。
|
|
8
|
+
- 未完成审查、无法表达为 head 行号的发现以及超限结果明确失败,保存完整原报告,不静默丢弃或假装成功。
|
|
9
|
+
- 同一行的不同问题保留,只去除完全相同的导出记录;旧结果不会被覆盖。
|
|
10
|
+
- mock 只生成冒烟文件,不生成可评分候选。
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
## 使用与边界
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
正常 DSH 插件工具、权限和预算保持不变。实验入口采用单次直审,不启用检索、复核或业务分组;其结果不能代表完整插件配置。尚未完成真实模型、真实基准 PR 或 judge 评分验证。
|
|
15
|
+
|
|
16
|
+
本地入口说明见 [本地评测指南](evaluation/REVIEWBENCH-LOCAL.md),插件日常使用见 [README](README.md)。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 评测说明
|
|
2
|
+
|
|
3
|
+
这个目录用于复现已知行为、检查模型输入和整理少量试跑观察,不进入 npm 安装包。
|
|
4
|
+
|
|
5
|
+
## 样本
|
|
6
|
+
|
|
7
|
+
- [资源生命周期](lifecycle/README.md):异常后是否释放资源,有缺陷输入和保留保护的对照。
|
|
8
|
+
- [请求乱序](stale-result/README.md):旧请求晚完成是否覆盖最新选择。
|
|
9
|
+
- [历史规则检查](history-rule-extension/README.md):本项目曾经错误接受不合法文件后缀的真实模块级复现。
|
|
10
|
+
|
|
11
|
+
样本测试通过表示“预期行为已被复现”,包括缺陷行为;不表示模型已经找到缺陷或样本已经修复。
|
|
12
|
+
|
|
13
|
+
## 离线运行
|
|
14
|
+
|
|
15
|
+
node --test test/evaluation-*.test.mjs
|
|
16
|
+
node evaluation/preview.mjs
|
|
17
|
+
|
|
18
|
+
第二条命令只读取固定合成输入,校验内容指纹并计算初始输入大小,不调用模型。输出不含源码或答案标签;它也不是宿主执行授权。
|
|
19
|
+
|
|
20
|
+
## 真实模型观察
|
|
21
|
+
|
|
22
|
+
已有六次小规模观察见 [试跑结果](PILOT-RESULTS.md)。其中一次证据复核未完成,其他结果也不能外推成质量指标。新评测仍后置,开始条件见 [运行条件](PILOT-GATE.md)。
|
|
23
|
+
|
|
24
|
+
运行真实评测时,只提供单组源码和必要契约,不发送答案说明、测试断言或其他组实现。保留原始输出,并由人工分别裁决候选生成和复核。
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ReviewBench 本地评测入口(实验)
|
|
2
|
+
|
|
3
|
+
无需 Docker,不提交公开排行榜,也不改变 DSH 宿主的模型配置。此入口直接调用审查核心,采用 **single-pass-direct**:一次审查,不启用检索、复核或业务分组。因此它的成绩不能代表 DSH 插件完整配置的质量。
|
|
4
|
+
|
|
5
|
+
## 前置条件
|
|
6
|
+
|
|
7
|
+
- Node.js ≥22、Git、已安装本仓库依赖。
|
|
8
|
+
- 本地已有基准 PR 的仓库及完整 base/head 提交对象。
|
|
9
|
+
- 正式运行显式设置 OpenAI-compatible Chat Completions 端点和模型。只支持该协议,不能直接用 Anthropic Messages 的密钥或端点。
|
|
10
|
+
- 模型密钥通过环境变量提供,不要粘贴到会话、命令历史或提交到仓库。
|
|
11
|
+
|
|
12
|
+
## PowerShell 示例
|
|
13
|
+
|
|
14
|
+
```powershell
|
|
15
|
+
$env:RB_REPO_DIR = 'G:\benchmark-repo'
|
|
16
|
+
$env:RB_BASE = '<40位base SHA>'
|
|
17
|
+
$env:RB_HEAD = '<40位head SHA>'
|
|
18
|
+
$env:RB_NWO = 'owner/repo'
|
|
19
|
+
$env:RB_PR_NUMBER = '123'
|
|
20
|
+
$env:RB_AGENT = 'dsh-code-review-local'
|
|
21
|
+
$env:RB_OUT = 'G:\benchmark-output\fresh-run\findings.json'
|
|
22
|
+
$env:RB_CONFIG_MODEL = '<模型名>'
|
|
23
|
+
$env:RB_MODEL_BASE_URL = 'https://<可信端点>/v1'
|
|
24
|
+
# 在安全环境中预先设置 RB_CONFIG_API_KEY、OPENAI_API_KEY 或 DEEPSEEK_API_KEY。
|
|
25
|
+
node scripts/review-bench-adapter.mjs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
输出父目录须存在,每次使用新的运行目录;已有结果不会被覆盖。读取 base/head 的唯一 merge-base,审查 merge-base 到 head 的固定对象;不修改被审查仓库,不读取黄金标注。
|
|
29
|
+
|
|
30
|
+
## 输出与失败规则
|
|
31
|
+
|
|
32
|
+
- `RB_OUT.report.json` 保存完整原报告、mock 标志、配置档与 merge-base,便于追溯覆盖与限制。
|
|
33
|
+
- 正式候选只在报告 completed 且所有选中文件 completed 时生成。partial/failed/cancelled、未完成文件不冒充空发现或成功。
|
|
34
|
+
- 旧侧行、文件级/属性级锚点无法无损表达成基准要求的 head 行号;当前明确失败并保留原报告,不静默丢弃或猜测位置。
|
|
35
|
+
- 不按行号合并不同问题;只去掉完全相同的导出记录。超过 200 条报错,不裁剪。
|
|
36
|
+
- 单次请求沿用 120 秒生命周期;整次运行另有 14 分钟截止,不放宽核心输入/调用预算。
|
|
37
|
+
- HTTP 错误不输出响应正文;完整 JSON 或单个完整 JSON fence 使用项目现有严格解析,截断和夹杂文本被拒绝。
|
|
38
|
+
|
|
39
|
+
## 离线冒烟
|
|
40
|
+
|
|
41
|
+
设置 `RB_MOCK=1` 后运行。只生成 `.report.json` 与 `.smoke.json`,不生成可评分候选 `RB_OUT`,避免离线结果误入正式评分。mock 不调用真实模型,也不证明检出质量。
|
|
42
|
+
|
|
43
|
+
## 验证边界
|
|
44
|
+
|
|
45
|
+
这里提供入口和局部回归,不包含下载全部 PR 的调度器,也不包含真实模型或 LLM judge 的评分结果。使用 ReviewBench 的独立 judge 前,需确认候选 PR 身份与基准语料一致,保持失败样本在总数中,并人工核验模型裁决。正式成绩、数据集完整性与生产配置效果仍需单独验证。
|
|
46
|
+
|
|
47
|
+
来源:[agent 契约](https://github.com/review-bench/ReviewBench/blob/main/AGENT_CONTRACT.md)、[评分输入](https://github.com/review-bench/ReviewBench/blob/main/docs/JUDGING_INPUT.md)。
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feng0531/dsh-code-review",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
|
7
7
|
"main": "./index.mjs",
|
|
8
8
|
"exports": { ".": "./index.mjs", "./package.json": "./package.json", "./locale/*.json": "./locale/*.json" },
|
|
9
|
-
"files": ["index.mjs", "cordis.patch.yml", "src/", "locale/", "README.md", "CHANGELOG.md", "RELEASE-NOTES.md", "DEVELOPMENT.md", "CORE-ALIGNMENT-TASKS.md", "CORE-SOURCE-INVENTORY.md", "LICENSE", "LICENSES/"],
|
|
9
|
+
"files": ["scripts/review-bench-adapter.mjs", "evaluation/REVIEWBENCH-LOCAL.md", "index.mjs", "cordis.patch.yml", "src/", "locale/", "README.md", "CHANGELOG.md", "RELEASE-NOTES.md", "DEVELOPMENT.md", "CORE-ALIGNMENT-TASKS.md", "CORE-SOURCE-INVENTORY.md", "LICENSE", "LICENSES/"],
|
|
10
10
|
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
|
|
11
11
|
"repository": { "type": "git", "url": "https://github.com/f-e-n-g-0531/dsh-code-review.git" },
|
|
12
12
|
"type": "module",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Local-only benchmark entrypoint; does not load or change DSH host settings.
|
|
2
|
+
import { checked } from '../src/process.mjs';
|
|
3
|
+
import { captureSnapshot } from '../src/snapshot.mjs';
|
|
4
|
+
import { reviewSnapshot } from '../src/review.mjs';
|
|
5
|
+
import { modelJson } from '../src/model-json.mjs';
|
|
6
|
+
import { writeFile } from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const RESPONSE_BYTES = 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
export function benchmarkConfig(env) {
|
|
13
|
+
const base = env.RB_BASE, head = env.RB_HEAD;
|
|
14
|
+
if (![base, head].every(value => typeof value === 'string' && /^[0-9a-f]{40}$/.test(value))) throw new Error('RB_BASE/RB_HEAD must be full lowercase Git SHAs');
|
|
15
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(env.RB_NWO ?? '') || env.RB_NWO.split('/').some(part => part === '.' || part === '..')) throw new Error('Invalid RB_NWO');
|
|
16
|
+
if (!/^[1-9][0-9]*$/.test(env.RB_PR_NUMBER ?? '') || !Number.isSafeInteger(Number(env.RB_PR_NUMBER))) throw new Error('Invalid RB_PR_NUMBER');
|
|
17
|
+
if (!env.RB_REPO_DIR || !env.RB_OUT) throw new Error('Set RB_REPO_DIR and RB_OUT for local evaluation');
|
|
18
|
+
const mock = env.RB_MOCK === '1';
|
|
19
|
+
if (env.RB_MOCK !== undefined && !['0', '1'].includes(env.RB_MOCK)) throw new Error('Invalid RB_MOCK');
|
|
20
|
+
if (!mock && (!env.RB_CONFIG_MODEL || !env.RB_MODEL_BASE_URL)) throw new Error('Set an explicit RB_CONFIG_MODEL and RB_MODEL_BASE_URL');
|
|
21
|
+
let endpoint;
|
|
22
|
+
if (!mock) {
|
|
23
|
+
const url = new URL(env.RB_MODEL_BASE_URL);
|
|
24
|
+
if (url.username || url.password || url.search || url.hash || (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) throw new Error('Invalid model endpoint; use HTTPS or local HTTP without URL credentials');
|
|
25
|
+
endpoint = url.href.replace(/\/+$/, '') + '/chat/completions';
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
repo: path.resolve(env.RB_REPO_DIR), out: path.resolve(env.RB_OUT), base, head,
|
|
29
|
+
nwo: env.RB_NWO, prNumber: Number(env.RB_PR_NUMBER),
|
|
30
|
+
agent: env.RB_AGENT || 'dsh-code-review', mock, endpoint,
|
|
31
|
+
model: env.RB_CONFIG_MODEL,
|
|
32
|
+
apiKey: env.RB_CONFIG_API_KEY || env.OPENAI_API_KEY || env.DEEPSEEK_API_KEY || '',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function responseJson(response) {
|
|
37
|
+
if (!response.body) throw new Error('Model response has no body');
|
|
38
|
+
const reader = response.body.getReader(), chunks = [];
|
|
39
|
+
let size = 0;
|
|
40
|
+
try {
|
|
41
|
+
for (;;) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done) break;
|
|
44
|
+
size += value.byteLength;
|
|
45
|
+
if (size > RESPONSE_BYTES) throw new Error('Model response exceeds byte limit');
|
|
46
|
+
chunks.push(Buffer.from(value));
|
|
47
|
+
}
|
|
48
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
49
|
+
} finally {
|
|
50
|
+
await reader.cancel().catch(() => {});
|
|
51
|
+
reader.releaseLock();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function benchmarkExecutor(config, fetchImpl = fetch) {
|
|
56
|
+
return async request => {
|
|
57
|
+
const response = await fetchImpl(config.endpoint, {
|
|
58
|
+
method: 'POST', redirect: 'error', signal: request.signal,
|
|
59
|
+
headers: { 'Content-Type': 'application/json', ...(config.apiKey ? { Authorization: 'Bearer ' + config.apiKey } : {}) },
|
|
60
|
+
body: JSON.stringify({ model: config.model, messages: [
|
|
61
|
+
{ role: 'system', content: request.instructions },
|
|
62
|
+
{ role: 'user', content: request.input },
|
|
63
|
+
], temperature: 0, max_tokens: 4096 }),
|
|
64
|
+
});
|
|
65
|
+
// Error bodies can contain credentials or source; never echo them.
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
await response.body?.cancel().catch(() => {});
|
|
68
|
+
throw new Error('Model HTTP status ' + response.status);
|
|
69
|
+
}
|
|
70
|
+
const data = await responseJson(response);
|
|
71
|
+
if (data?.choices?.length !== 1 || data.choices[0]?.finish_reason !== 'stop') throw new Error('Model response incomplete or ambiguous');
|
|
72
|
+
const content = data.choices[0]?.message?.content;
|
|
73
|
+
if (typeof content !== 'string' || !content) throw new Error('Model returned no text');
|
|
74
|
+
return JSON.parse(modelJson(content));
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function benchmarkFindings(snapshot, report, agent) {
|
|
79
|
+
if (report.status !== 'completed' || !report.files?.length || report.files.some(file => file.status !== 'completed')) throw new Error('Benchmark review incomplete; see saved report');
|
|
80
|
+
const paths = new Map(snapshot.files.map(file => [file.id, file.path]));
|
|
81
|
+
const seen = new Set(), findings = [];
|
|
82
|
+
for (const finding of report.findings) {
|
|
83
|
+
const anchor = finding.anchor, file = paths.get(finding.fileId);
|
|
84
|
+
if (!file || file.startsWith('/') || file.includes('\\') || file.split('/').some(part => ['', '.', '..'].includes(part))) throw new Error('Unrepresentable finding path');
|
|
85
|
+
// Do not invent target coordinates or silently remove a valid old-side issue.
|
|
86
|
+
if (anchor?.kind !== 'line' || anchor.side !== 'new' || !Number.isSafeInteger(anchor.start) || !Number.isSafeInteger(anchor.end) || anchor.start < 1 || anchor.end < anchor.start) throw new Error('Finding has no valid head-side line range; see saved report');
|
|
87
|
+
if (finding.verification && (finding.verification.status !== 'completed' || finding.verification.verdict !== 'supported')) throw new Error('Finding requires explicit adjudication; see saved report');
|
|
88
|
+
const message = [finding.title, finding.evidence, finding.trigger, finding.impact, finding.suggestion].filter(value => typeof value === 'string' && value.trim()).join(' ');
|
|
89
|
+
if (!message) throw new Error('Empty finding message');
|
|
90
|
+
const mapped = { file, start_line: anchor.start, end_line: anchor.end, message, producer: agent };
|
|
91
|
+
const identity = JSON.stringify(mapped);
|
|
92
|
+
if (!seen.has(identity)) { seen.add(identity); findings.push(mapped); }
|
|
93
|
+
}
|
|
94
|
+
if (findings.length > 200) throw new Error('Benchmark finding limit exceeded; no clipping performed');
|
|
95
|
+
return findings;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function runBenchmark(env = process.env) {
|
|
99
|
+
const config = benchmarkConfig(env);
|
|
100
|
+
const signal = AbortSignal.timeout(14 * 60 * 1000), started = Date.now();
|
|
101
|
+
const bases = (await checked('git', ['merge-base', '--all', config.base, config.head], { cwd: config.repo, signal })).toString('utf8').trim().split(/\r?\n/);
|
|
102
|
+
if (bases.length !== 1 || !/^[0-9a-f]{40}$/.test(bases[0])) throw new Error('Expected a unique merge base; no arbitrary base selected');
|
|
103
|
+
const snapshot = await captureSnapshot(config.repo, { baseRevision: bases[0], targetRevision: config.head, signal });
|
|
104
|
+
const executor = config.mock ? async () => ({ findings: [], limitations: [] }) : benchmarkExecutor(config);
|
|
105
|
+
console.log('profile=single-pass-direct mock=' + config.mock + ' model=' + (config.model || 'none'));
|
|
106
|
+
const report = await reviewSnapshot(snapshot, executor, { signal, reviewRounds: 1, maxCalls: 100, timeoutMs: 120_000 });
|
|
107
|
+
signal.throwIfAborted();
|
|
108
|
+
// Each run owns fresh output paths; never mix attempts or overwrite prior results.
|
|
109
|
+
await writeFile(config.out + '.report.json', JSON.stringify({ mock: config.mock, profile: 'single-pass-direct', mergeBase: bases[0], report }, null, 2) + '\n', { flag: 'wx' });
|
|
110
|
+
const findings = benchmarkFindings(snapshot, report, config.agent);
|
|
111
|
+
const output = {
|
|
112
|
+
pr: { repo: 'https://github.com/' + config.nwo, pr_number: config.prNumber, base: config.base, head: config.head },
|
|
113
|
+
agent: config.agent, findings, usage: { time_in_ms: Date.now() - started },
|
|
114
|
+
};
|
|
115
|
+
// Smoke output is intentionally not a scoreable candidate file.
|
|
116
|
+
await writeFile(config.mock ? config.out + '.smoke.json' : config.out, JSON.stringify(output, null, 2) + '\n', { flag: 'wx' });
|
|
117
|
+
return output;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
121
|
+
runBenchmark().catch(() => {
|
|
122
|
+
console.error('Benchmark adapter failed; no successful result implied. Check configuration and any saved report.');
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
});
|
|
125
|
+
}
|