@stardustlc/dsh-review 0.0.0-stage → 0.3.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 +44 -0
- package/LICENSE +9 -0
- package/README.en.md +43 -0
- package/README.md +120 -2
- package/cordis.patch.yml +15 -0
- package/docs/DESIGN.md +76 -0
- package/docs/INTEGRATION.md +68 -0
- package/docs/VALIDATION.md +39 -0
- package/docs/review-policy.example.json +10 -0
- package/lib/check.d.ts +23 -0
- package/lib/check.js +17 -0
- package/lib/cli.d.ts +82 -0
- package/lib/cli.js +59 -0
- package/lib/config.d.ts +33 -0
- package/lib/config.js +69 -0
- package/lib/diff.d.ts +38 -0
- package/lib/diff.js +136 -0
- package/lib/dispatch.d.ts +58 -0
- package/lib/dispatch.js +50 -0
- package/lib/eval.d.ts +71 -0
- package/lib/eval.js +83 -0
- package/lib/exec.d.ts +26 -0
- package/lib/exec.js +82 -0
- package/lib/fingerprint.d.ts +12 -0
- package/lib/fingerprint.js +14 -0
- package/lib/gate.d.ts +81 -0
- package/lib/gate.js +135 -0
- package/lib/github.d.ts +2 -0
- package/lib/github.js +53 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.js +56 -0
- package/lib/ledger.d.ts +140 -0
- package/lib/ledger.js +125 -0
- package/lib/lenses.d.ts +43 -0
- package/lib/lenses.js +68 -0
- package/lib/modes.d.ts +30 -0
- package/lib/modes.js +62 -0
- package/lib/policy.d.ts +54 -0
- package/lib/policy.js +42 -0
- package/lib/preexisting.d.ts +8 -0
- package/lib/preexisting.js +39 -0
- package/lib/rules.d.ts +32 -0
- package/lib/rules.js +101 -0
- package/lib/run.d.ts +36 -0
- package/lib/run.js +277 -0
- package/lib/scope.d.ts +23 -0
- package/lib/scope.js +50 -0
- package/lib/target.d.ts +40 -0
- package/lib/target.js +134 -0
- package/lib/tools.d.ts +28 -0
- package/lib/tools.js +187 -0
- package/lib/verdict.d.ts +22 -0
- package/lib/verdict.js +25 -0
- package/package.json +67 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 变更记录
|
|
2
|
+
|
|
3
|
+
## 0.3.0(2026-10-17)
|
|
4
|
+
|
|
5
|
+
重做 v0.2 中尚未可靠接通的审阅闭环,按实际执行和证据决定是否完成。
|
|
6
|
+
|
|
7
|
+
- 修复原生子智能体接线:真实调用 agent、服务 receiver、prompt blocks、独立上下文和 activation.result 生命周期。
|
|
8
|
+
- 找问题、风险分类、逐条多数复核和 Deep 汇总分阶段执行;未完成/缺结果不再误放行。
|
|
9
|
+
- 每轮绑定快照和唯一编号;修复中文路径、索引/工作区混淆、大 diff 静默截断、旧文件复用和跨仓库缓存。
|
|
10
|
+
- 证据命令改经宿主受控终端;遵守权限、取消和后台作业清理,支持 Windows 官方 pwsh 预设桥接 Git Bash。
|
|
11
|
+
- 账本改为事务与原子更新;损坏阻塞,旧版证据保留但不继承批准。
|
|
12
|
+
- 三轮上限按完成轮次计算,后续复审使用真实增量行;新文件仍可审,旧问题须重新验证。
|
|
13
|
+
- 新增只读 GitHub 门禁 CLI,固定基准策略,核对 CI、线程与同提交真人批准;不执行合并或代批。
|
|
14
|
+
- 评测改为人工缺陷标签与干净对照的一对一匹配,冻结预测、区分未运行覆盖,避免无关 finding 冒充召回。
|
|
15
|
+
- 更新中英文说明、迁移/回滚文档、PR 模板与 Windows/Linux CI。
|
|
16
|
+
- 包名改为 @stardustlc/dsh-review,避免未加作用域的同名包指向另一作者仓库。
|
|
17
|
+
|
|
18
|
+
v0.2 的 head/findings 手工报告不再兼容,请改用 runId/targetId/taskId。旧版记载仅作为历史,当前能力与验证边界以 README 和 docs/VALIDATION.md 为准。
|
|
19
|
+
|
|
20
|
+
## 0.2.0(2026-10-17)
|
|
21
|
+
|
|
22
|
+
把文章里能落地的东西全做进插件,落地不了的生成作业书交给调用方 AI。
|
|
23
|
+
|
|
24
|
+
- 新增 `review_run`:生成镜头作业书 → 派给子智能体(`ctx.subagents`)→ 收结果 → 过把关层;无宿主能力时返回可执行作业书
|
|
25
|
+
- 新增 `review_gate`:未关闭的 blocker/important → `exitCode=1`,供 CI 拒绝合并
|
|
26
|
+
- 新增 `review_learn`:把账本里的重复文件/类型沉淀成 `REVIEW.md` 片段
|
|
27
|
+
- 新增 `review_issue`:issue 分类 + 复现要求 + 作业书
|
|
28
|
+
- 新增 `review_eval`:登记历史 bug commit、打分召回、生成评测作业书
|
|
29
|
+
- 把关层抽成 `gate.ts`,`review_report` 与 `review_run` 共用
|
|
30
|
+
- 修复:重复结论合并取最严(原先先到先得会把 blocker 降成 important),且同步到返回列表
|
|
31
|
+
- 新增 `lens` 归因与越镜记录 `declined`
|
|
32
|
+
- 测试 27 项
|
|
33
|
+
|
|
34
|
+
## 0.1.0(2026-10-17)
|
|
35
|
+
|
|
36
|
+
首个版本:以证据为准的代码审阅闭环。
|
|
37
|
+
|
|
38
|
+
- 五个工具:`review_diff` / `review_report` / `review_check` / `review_respond` / `review_status`
|
|
39
|
+
- 三档模式(quick / standard / deep),按改动路径自动分级,`REVIEW.md` 可覆盖
|
|
40
|
+
- 证据门槛:没有可执行证据的结论记为 `unverified`,不进入可发列表
|
|
41
|
+
- 去重:种类 + 文件 + 归一化代码行的指纹,重复只计数不复报
|
|
42
|
+
- pre-existing 标记:与 base 版本比对,旧代码里的问题单独归类
|
|
43
|
+
- 轮次收敛:默认 3 轮上限,第 2 轮起只看增量
|
|
44
|
+
- 噪音度量:处置(fixed / wrong / intentional / wontfix)驱动噪音率与复报率
|
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 STARDUSTLC666
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# dsh-review
|
|
2
|
+
|
|
3
|
+
[中文](README.md)
|
|
4
|
+
|
|
5
|
+
Independent code review for DeepSeek Harness: find concrete defects, verify them separately, preserve evidence and disclose unfinished work. Inspired by [the Claude Code team's workflow](https://claude.dev/blog/solving-code-review-at-anthropic/).
|
|
6
|
+
|
|
7
|
+
Requires Git, Node.js 22.19+ or 24+, and the official DSH services for tools, subagents, permission presets and jobs. Runtime acceptance targets DSH 0.2.1-rc.1. Start the conversation at the Git repository root.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
dsh plugin --profile web add @stardustlc/dsh-review
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For Desktop, install into the profile Desktop actually uses, then start a new conversation. No credentials are copied or modified.
|
|
14
|
+
|
|
15
|
+
Use the scoped package name. The unscoped npm package `dsh-review` belongs to another author and is unrelated to this repository.
|
|
16
|
+
|
|
17
|
+
Ask DSH to review your current changes with `review_run`, or provide `base: "main"` for committed changes and `staged: true` for the index. Interrupted runs can resume with their `runId`.
|
|
18
|
+
|
|
19
|
+
| Mode | Fresh-context finders | Independent verification | Synthesis |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| quick | Correctness and security | One verifier per candidate | Deterministic filtering |
|
|
22
|
+
| standard | Correctness, state, compatibility, security | Three voters, two must agree | Deterministic filtering |
|
|
23
|
+
| deep | Same four lenses | Three voters, two must agree | Additional arbiter preserving confirmed findings |
|
|
24
|
+
|
|
25
|
+
Every mode includes two separate risk classifications. Sensitive changes cannot be downgraded. Forked author context is rejected.
|
|
26
|
+
|
|
27
|
+
Each run is bound to an immutable base, HEAD, source snapshot and nonce. Missing or malformed output, changed code, exhausted budgets and incomplete verification block the AI gate. Evidence must cite actual snapshot source; an asserted command transcript alone is insufficient.
|
|
28
|
+
|
|
29
|
+
Posted findings require a fix or reasoned disposition. Fixes clear only after independent re-verification. Reviews default to three completed rounds; round three limits new findings to the actual incremental edits while re-checking unresolved findings. New files remain eligible. Binary changes require separate review.
|
|
30
|
+
|
|
31
|
+
The ten tools are `review_diff`, `review_run`, `review_report`, `review_check`, `review_respond`, `review_status`, `review_gate`, `review_learn`, `review_issue` and `review_eval`. External task submissions cannot prove independent execution and never pass the automatic gate. No tool publishes GitHub feedback or grants a human approval.
|
|
32
|
+
|
|
33
|
+
Evidence commands go through the live caller's controlled host terminal, including permission and cancellation handling. Read-only sessions refuse command execution. On the official Windows preset, controlled `pwsh` bridges to Git Bash when Bash is not exposed; Git Bash and PowerShell 7.3+ are required. There is no direct process fallback.
|
|
34
|
+
|
|
35
|
+
Default state is `DSH_HOME/plugins/dsh-review/<repository hash>/`. A workspace child directory can be configured explicitly. Corrupt ledgers fail closed; v1 evidence is backed up without inheriting old approvals.
|
|
36
|
+
|
|
37
|
+
Defaults: three rounds, incremental re-review after round two, 20 findings, two nits, severity floor `important`, 80 worker calls, concurrency three, ten-minute run timeout, two-minute command timeout, 4 MiB snapshot limit and the `spawn` backend. The legacy `evidenceRequired: false` setting cannot disable independent verification.
|
|
38
|
+
|
|
39
|
+
`dsh-review-gate` can combine the AI result with read-only GitHub evidence: successful required checks, resolved threads and current-commit human approvals. Trusted-author policy is loaded from the PR base commit, never its editable working files. Owners must separately enforce branch protection, CODEOWNERS, merge queues and release controls.
|
|
40
|
+
|
|
41
|
+
See [integration, migration and rollback](docs/INTEGRATION.md), [design and boundaries](docs/DESIGN.md), [validation](docs/VALIDATION.md) and [changelog](CHANGELOG.md). Evaluation uses curated defect matches, clean controls and separate coverage reporting; it does not claim production recall from synthetic tests.
|
|
42
|
+
|
|
43
|
+
Issues and PRs are welcome. Include reproducible inputs, versions and expected/actual behavior; redact secrets and private source. MIT License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,121 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-review
|
|
2
|
+
|
|
3
|
+
[English](README.en.md)
|
|
4
|
+
|
|
5
|
+
提交代码前,让独立审阅者找出具体缺陷,再交给另一组审阅者复核。你看到的是经过验证的问题、明确的未完成项和仍需人工决定的事。
|
|
6
|
+
|
|
7
|
+
参考 [Claude Code 团队的审阅流程](https://claude.dev/blog/solving-code-review-at-anthropic/),实现其中可在 DSH 内完成的审阅环节。它不授予人工批准,也不会自动发评论或合并代码。
|
|
8
|
+
|
|
9
|
+
## 使用
|
|
10
|
+
|
|
11
|
+
需要 Git、Node.js 22.19+ / 24+,以及提供子智能体、权限预设和作业管理服务的官方 DSH 运行环境。适配验收使用 DSH 0.2.1-rc.1。在 Git 仓库根目录创建会话后,让 DSH:
|
|
12
|
+
|
|
13
|
+
> 审阅当前改动,运行 dsh-review。只报告有具体触发条件、代码证据和独立复核的问题。审查没完成时不要说通过。
|
|
14
|
+
|
|
15
|
+
安装到 Web profile:
|
|
16
|
+
|
|
17
|
+
~~~bash
|
|
18
|
+
dsh plugin --profile web add @stardustlc/dsh-review
|
|
19
|
+
~~~
|
|
20
|
+
|
|
21
|
+
桌面版应安装到它实际使用的 profile,并在新的会话中加载插件。插件不会查找、复制或修改其他 profile 的密钥。
|
|
22
|
+
|
|
23
|
+
请使用带作用域的包名。npm 上未加作用域的 dsh-review 属于其他作者,不是本仓库。
|
|
24
|
+
|
|
25
|
+
常见调用:
|
|
26
|
+
|
|
27
|
+
~~~json
|
|
28
|
+
{"tool":"review_run","arguments":{"base":"main"}}
|
|
29
|
+
~~~
|
|
30
|
+
|
|
31
|
+
省略 base 审当前工作区;staged=true 审暂存区。需要中断后续工作时取消正在运行的工具;恢复未完成审查时使用返回的 runId。敏感改动始终使用 deep,不能通过参数降级。
|
|
32
|
+
|
|
33
|
+
## 审阅如何完成
|
|
34
|
+
|
|
35
|
+
| 模式 | 独立找问题 | 每个问题的复核 | 汇总 |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| quick | 正确性、安全,2 个任务 | 1 个独立审阅者 | 确定性把关 |
|
|
38
|
+
| standard | 正确性、状态、兼容性、安全,4 个任务 | 3 人投票,至少 2 票一致 | 确定性把关 |
|
|
39
|
+
| deep | 同 standard | 3 人投票,至少 2 票一致 | 另派汇总者,不能删掉已确认的问题 |
|
|
40
|
+
|
|
41
|
+
每轮还有两个独立风险判断任务,分别判断风险等级和是否需要额外人工审阅。所有任务使用新的上下文;继承作者对话的 fork 后端不会被接受。
|
|
42
|
+
|
|
43
|
+
- 每轮绑定 Git 基准、HEAD、完整文件快照和唯一运行编号。代码变化、结果缺失、格式损坏或超限都会阻止放行。
|
|
44
|
+
- 复核必须说明具体成因并引用快照中的真实代码行。一个非空 evidence 字符串不算验证。
|
|
45
|
+
- 风格、命名、格式和清理建议直接过滤。已发表问题按指纹去重;每条仍须修复或写明处置原因。
|
|
46
|
+
- 默认最多 3 轮。从第 3 轮起,新问题限定在相对上轮的实际新增改动;旧问题继续复核,新文件不会漏掉。
|
|
47
|
+
- 只有独立复核确认问题已消失,才能自动记为 fixed。人工说明 wrong、intentional 或 wontfix 会保留理由,但不计作人工批准。
|
|
48
|
+
- 只生成作业书的外部模式可用于协作,无法证明执行独立性,因此不会通过自动门禁。
|
|
49
|
+
|
|
50
|
+
二进制变更需要另行验收,文字审查不会认证其内容。跳过的路径会公开列出,并仍参与风险判断。
|
|
51
|
+
|
|
52
|
+
## 十个工具
|
|
53
|
+
|
|
54
|
+
| 工具 | 作用 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| review_diff | 生成快照与审阅计划,不作通过结论 |
|
|
57
|
+
| review_run | 执行或恢复分阶段独立审阅 |
|
|
58
|
+
| review_report | 提交外部任务的运行编号、目标编号与 JSON 结果 |
|
|
59
|
+
| review_check | 经宿主受控终端运行检查,保存本轮证据收据 |
|
|
60
|
+
| review_respond | 记录带理由的处置;fixed 必须重新复核 |
|
|
61
|
+
| review_status | 查看未完成项、轮次、调用量和处置噪音率 |
|
|
62
|
+
| review_gate | 检查当前快照的 AI 审阅层,不代替 CI 或人工批准 |
|
|
63
|
+
| review_learn | 从已验证修复中生成规则草稿,不自动改项目规范 |
|
|
64
|
+
| review_issue | 生成 issue 分诊和复现草稿,不执行正文指令或发表评论 |
|
|
65
|
+
| review_eval | 登记人工标注缺陷与干净样本、准备盲测、绑定运行结果并计分 |
|
|
66
|
+
|
|
67
|
+
## 检查、权限与证据
|
|
68
|
+
|
|
69
|
+
review_check 的命令经调用者实际使用的宿主终端工具运行,不直接创建绕过权限的进程。只读会话不运行证据命令;可使用静态源码复核。取消、超时和后台作业清理都受宿主管理。
|
|
70
|
+
|
|
71
|
+
Windows 官方预设只提供 pwsh 时,通过这个受控工具调用 Git Bash;需要 Git Bash 和 PowerShell 7.3+。原命令保留可见,引号不经 cmd 拼接。不能使用这条路径时会明确报错,不会静默换成直接执行。
|
|
72
|
+
|
|
73
|
+
证据收据绑定 runId 和 targetId;命令改变代码后收据失效。请正常批准必要权限,不要为了审阅扩大整个会话的权限。
|
|
74
|
+
|
|
75
|
+
## 接入仓库流程
|
|
76
|
+
|
|
77
|
+
仓库级 REVIEW.md 可以写失败判据、跳过路径、敏感目录和噪音上限:
|
|
78
|
+
|
|
79
|
+
~~~markdown
|
|
80
|
+
## 限制
|
|
81
|
+
max_nits: 0
|
|
82
|
+
severity_floor: important
|
|
83
|
+
|
|
84
|
+
## 跳过
|
|
85
|
+
- vendor/**
|
|
86
|
+
|
|
87
|
+
## 坏掉长什么样
|
|
88
|
+
- 文档给出的命令不能执行
|
|
89
|
+
- 配置将请求发送到错误账号
|
|
90
|
+
|
|
91
|
+
## 敏感路径
|
|
92
|
+
- src/billing/**
|
|
93
|
+
~~~
|
|
94
|
+
|
|
95
|
+
完整 PR 门禁还需独立的 CI、安全检查、人工批准和所有审阅线程的解决状态。新增命令 dsh-review-gate 只读取 GitHub 证据,策略固定从 PR 基准提交读取;机器人、旧提交和已撤回的批准不能填人工名额。
|
|
96
|
+
|
|
97
|
+
详见 [接入与回滚](docs/INTEGRATION.md)、[文章对应边界](docs/DESIGN.md) 和 [验证记录](docs/VALIDATION.md)。合并队列、功能开关、灰度和回滚由仓库/发布系统负责,安装插件不会自动启用这些制度。
|
|
98
|
+
|
|
99
|
+
## 状态与配置
|
|
100
|
+
|
|
101
|
+
默认状态保存在 DSH_HOME/plugins/dsh-review/<仓库摘要>/,不写入被审项目。可指定仓库内子目录,例如 .dsh-review。账本版本为 2;旧账本会保留备份,旧版本的“通过”不会沿用。
|
|
102
|
+
|
|
103
|
+
| 配置 | 默认 | 含义 |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| stateDir | @profile | profile 下按仓库隔离;也可指定仓库内子目录 |
|
|
106
|
+
| roundCap | 3 | 每个审阅系列的完成轮次上限 |
|
|
107
|
+
| diffOnlyAfterRound | 2 | 超过这轮后只对新增改动提出新问题 |
|
|
108
|
+
| maxFindings / maxNits | 20 / 2 | 问题数与 nit 上限 |
|
|
109
|
+
| severityFloor | important | 默认不发表 nit |
|
|
110
|
+
| maxCalls / maxParallel | 80 / 3 | 每轮实际派发预算、最大并发 |
|
|
111
|
+
| runTimeoutMs | 600000 | 每次执行的总超时 |
|
|
112
|
+
| commandTimeoutMs | 120000 | 单条证据命令超时 |
|
|
113
|
+
| maxDiffBytes | 4194304 | 补丁及源码快照总量上限;超出则未完成 |
|
|
114
|
+
| subagentProvider | spawn | 必须明确保证不继承作者上下文 |
|
|
115
|
+
|
|
116
|
+
evidenceRequired 为旧配置兼容项,设为 false 也不能关闭独立证据验证。v0.2 的手工 head/findings 提交接口已停用,迁移见接入文档。
|
|
117
|
+
|
|
118
|
+
欢迎提交 [Issues](https://github.com/STARDUSTLC666/dsh-review/issues) 和 PR,最好附具体触发输入、版本、预期结果与实际结果。请先去掉密钥和私有代码。
|
|
119
|
+
|
|
120
|
+
[更新记录](CHANGELOG.md) · MIT License
|
|
2
121
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# dsh-review 组合包补丁:把代码审阅插件行插入 profile。
|
|
2
|
+
#
|
|
3
|
+
# 示例(用户 profile 的 cordis.patch.yml):
|
|
4
|
+
# - id: review
|
|
5
|
+
# name: '@stardustlc/dsh-review'
|
|
6
|
+
# config:
|
|
7
|
+
# roundCap: 3
|
|
8
|
+
# evidenceRequired: true
|
|
9
|
+
# stateDir: '@profile'
|
|
10
|
+
- insert:
|
|
11
|
+
- id: review
|
|
12
|
+
name: '@stardustlc/dsh-review'
|
|
13
|
+
config:
|
|
14
|
+
roundCap: 3
|
|
15
|
+
evidenceRequired: true
|
package/docs/DESIGN.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 审阅设计与边界
|
|
2
|
+
|
|
3
|
+
参考:https://claude.dev/blog/solving-code-review-at-anthropic/
|
|
4
|
+
|
|
5
|
+
本文描述实现,不把文章中的团队制度冒充为插件已经启用的功能。
|
|
6
|
+
|
|
7
|
+
## 状态机
|
|
8
|
+
|
|
9
|
+
~~~mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
S[固定代码快照] --> F[独立找问题与两个风险判断]
|
|
12
|
+
F --> V[逐条独立复核与投票]
|
|
13
|
+
V --> A[Deep 独立汇总]
|
|
14
|
+
V --> G[证据、去重与处置把关]
|
|
15
|
+
A --> G
|
|
16
|
+
G --> C[当前快照的 AI 审阅结果]
|
|
17
|
+
C --> H[仓库 CI、安全检查与人工批准]
|
|
18
|
+
H --> R[仓库合并与发布制度]
|
|
19
|
+
~~~
|
|
20
|
+
|
|
21
|
+
每个结果必须携带 runId、targetId、taskId。原生任务返回值必须来自已完成的 activation.result;不从旧文件猜测任务成功。派发调用绑定真实 service receiver、当前调用 agent、结构化 prompt、cwd 和取消信号。
|
|
22
|
+
|
|
23
|
+
默认 spawn 不继承作者上下文。后端必须明确声明 inheritsParentContext=false。找问题、风险判断、复核、汇总各自使用新的子会话。独立上下文不代表模型统计独立,也不证明结论正确。
|
|
24
|
+
|
|
25
|
+
源码、差异、issue 和项目说明均视为审阅材料;它们不能授权扩大权限。检查经调用者的受控终端工具执行,使用相同权限、审批、取消与作业管理。只读环境使用静态代码证据。
|
|
26
|
+
|
|
27
|
+
## 快照与覆盖
|
|
28
|
+
|
|
29
|
+
HEAD、基准提交、补丁、实际文件内容、规则、指引和文件清单共同确定 targetId。文件清单由 NUL 分隔的 Git 输出获取,补丁单独解析。暂存区从索引取内容;提交比较使用 merge-base;工作区默认纳入未跟踪文件。
|
|
30
|
+
|
|
31
|
+
中文和转义路径保留原名。读取被截断、补丁无法解析、符号链接越界、状态损坏或结果缺少时,返回未完成/错误,不把它解释为零问题。二进制仍需人工或领域检查。
|
|
32
|
+
|
|
33
|
+
REVIEW.md 的全文和结构化规则进入审阅上下文;提交审查使用基准版本的规则,防止本次改动先放宽自己的审阅要求。项目说明文件不允许通过符号链接读取仓库外内容。
|
|
34
|
+
|
|
35
|
+
## 证据与问题生命周期
|
|
36
|
+
|
|
37
|
+
候选必须关联真实快照位置与本次引入行。复核者逐条尝试反驳,给出因果理由与可查源码见证,判断 confirm、reject、pre-existing 或 unverifiable。旧版本里有相同代码行本身不构成 pre-existing。
|
|
38
|
+
|
|
39
|
+
Standard/Deep 每条三个复核任务,至少两票同意才确认。证据冲突无法形成多数时留为未验证并阻止通过。Deep 汇总必须保留所有已确认编号,不能自行删掉阻塞问题。
|
|
40
|
+
|
|
41
|
+
指纹按文件、代码锚点、出现次数、类型和缺陷类别稳定去重;语义完全相同但表述/类别不同的问题,仍可能需要人工判断。所有已经发表的条目,包括启用的 nit 和 pre-existing,都必须说明处置。新目标上的旧问题会再次验证;代码未改时被反驳的旧结论计为 wrong,代码改变后确认消失才记为 fixed。
|
|
42
|
+
|
|
43
|
+
规则允许过滤风格类、压制 nit 和低于阈值的条目。过滤不是审查成功;完整执行、独立性、未验证数量和未解决问题仍分别检查。
|
|
44
|
+
|
|
45
|
+
## 收敛与预算
|
|
46
|
+
|
|
47
|
+
每个系列默认三轮已完成审阅。提交比较按仓库、分支与解析后的基准聚合;相同基准的不同 Git 别名不会重置轮次。工作区/暂存区在新的 HEAD 上开始新的局部系列。
|
|
48
|
+
|
|
49
|
+
第 3 轮的新增问题限定到相对上一完成轮次的真实差异,使用结构化 Git 比较计算行范围。旧的未解决问题不受新增问题过滤限制。新增文件不会因为以前没见过而漏审。
|
|
50
|
+
|
|
51
|
+
每次原生派发都计入预算,包括重试。并行度、候选数、输出大小与运行时长分别受限。取消必须等待宿主回收正在运行的子会话/命令。重复执行同一运行编号由租约阻止。
|
|
52
|
+
|
|
53
|
+
账本以事务重新读取最新状态,随机临时文件后原子替换。旧实例不得覆盖新数据;状态损坏保持阻塞。v1 账本保留备份,不继承“通过”结论。
|
|
54
|
+
|
|
55
|
+
## 文章各层如何落实
|
|
56
|
+
|
|
57
|
+
| 事项 | 实际交付 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| 先核验,再给出审阅结论 | 独立复核、源码见证、受控检查收据 |
|
|
60
|
+
| 多视角找问题和投票过滤 | 分阶段原生子智能体;Quick 为轻量复核 |
|
|
61
|
+
| 安全层和检查阻塞 | 所有模式保留安全镜头;PR 策略还要求独立 CI/安全检查 |
|
|
62
|
+
| 风险决定人工要求 | 两次风险判断、敏感路径下限、可信作者策略 |
|
|
63
|
+
| 每条回应与有限复审 | 带理由的处置、自动重新验证、三轮限制、增量范围 |
|
|
64
|
+
| 事故与干净样本评测 | 不可变人工标签、精确运行绑定、逐缺陷计分及未运行覆盖 |
|
|
65
|
+
| 合并队列、开关与灰度 | 接入说明;必须由仓库和发布系统实际配置 |
|
|
66
|
+
| 人的责任和回滚 | PR 模板与接入指南,不能由模型自签批准 |
|
|
67
|
+
|
|
68
|
+
GitHub 门禁是只读证据核对,不会自行开启分支保护或合并。它要求基准提交中的固定策略、同一 HEAD 上的成功检查、已解决线程和可信审阅者名单中的独立真人批准。标准 GitHub PR review 无法让作者批准自己的 PR,因此此实现只计非作者的人类 review,比文章允许“负责人自己计一人”的团队制度更严格。未实现额外的负责人签认系统。
|
|
69
|
+
|
|
70
|
+
## 评测可以证明什么
|
|
71
|
+
|
|
72
|
+
登记项包含真实不可变提交、基准、人工确认的缺陷位置/类型/类别/锚点;expected=[] 明确表示干净样本。评测任务不携带事故答案和修复说明。执行者仍需使用隔离 checkout,避免未来提交、标签和其他结果泄漏。
|
|
73
|
+
|
|
74
|
+
计分使用该运行完成时冻结的预测,而不是后来可修改的账本。逐条一对一匹配,输出召回、精度、干净样本误报率、cohort 和未运行清单。未运行不记成已通过,任何不相关 finding 不算命中。
|
|
75
|
+
|
|
76
|
+
当前合成用例只证明流程和计分规则。没有人工标注的生产事故集、独立模型对照和持续观测数据时,不能声称生产召回率、节约人工时间或降低事故率。评测工具提供准备/计分能力,没有暗中创建定时任务或执行全量生产盲测。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 接入、迁移与回滚
|
|
2
|
+
|
|
3
|
+
## 本地与 DSH
|
|
4
|
+
|
|
5
|
+
先在 Git 仓库根目录打开会话,运行构建/测试,再调用 review_run。它默认审工作区;指定 staged=true 会固定索引内容;指定 base=main 会审 merge-base 到 HEAD 的提交差异。base 与 staged 不可同时使用。
|
|
6
|
+
|
|
7
|
+
先保留 review_diff 返回的覆盖范围。遇到超限、符号链接、二进制、模型错误或权限问题,应拆分改动、补领域检查或交给人,不得把失败改成空数组后继续。
|
|
8
|
+
|
|
9
|
+
review_gate 的 ok=true 只说明当前目标的 AI 层完成且已发表问题均有处置,不代表测试、发布或人工批准通过。
|
|
10
|
+
|
|
11
|
+
## v0.2 → v0.3
|
|
12
|
+
|
|
13
|
+
原先只接受 head/findings 的提交方式不能防止跨工作区、旧代码和过期结果串用,现已停用。
|
|
14
|
+
|
|
15
|
+
1. review_run 返回 runId、targetId 和待完成 tasks。
|
|
16
|
+
2. 原生服务可用时自动执行全部阶段;否则每个外部审阅者提交携带 runId/targetId/taskId 的 JSON。
|
|
17
|
+
3. 外部结果用 review_report({runId,targetId,results:[...]}) 提交。下一阶段会返回新的任务,不可重复或覆盖已有任务。
|
|
18
|
+
4. review_check 也须传 runId、targetId、command,返回 receiptId。不能把随手写的 evidence 文本当作这个收据。
|
|
19
|
+
5. review_respond 须带运行编号、目标编号、fingerprint、disposition、至少 20 字符的具体 note。fixed 需修改代码后独立复审。
|
|
20
|
+
|
|
21
|
+
外部提交无法证明审阅者独立,因此始终不能填补自动门禁。原生运行不允许用 review_report 偷换结果。
|
|
22
|
+
|
|
23
|
+
默认状态改为 @profile。旧项目内 .dsh-review/state.json 不会被删除;如需迁移取证,可显式指定旧 stateDir,v1 将保留 state.json.v1.backup。旧批准不再有效,需要完整的新审查。
|
|
24
|
+
|
|
25
|
+
运行超时/失败后可用 runId 恢复。确需放弃时,在当前工具与子任务完全停止后调用 review_run({runId,abandon:true})。被放弃的运行不授予任何批准。达到完成轮次上限后应重新审视方案,不能不断改用别名绕过。
|
|
26
|
+
|
|
27
|
+
## PR 的完整门禁
|
|
28
|
+
|
|
29
|
+
先由仓库负责人把 docs/review-policy.example.json 复制为 .github/dsh-review-policy.json,填写实际的可信作者、可信审阅者和检查名称/提供方,再提交到受保护基准分支。示例名称不是已经存在的 CI job。trustedReviewers 为空时不计任何人工批准,不能用无关账号的 review 顶替负责人审阅。
|
|
30
|
+
|
|
31
|
+
受信任作业需要先完成本轮原生提交审查,随后执行:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
dsh-review-gate --run <本次运行编号> --root <审阅工作区>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
要求 GITHUB_EVENT_PATH 指向受信任的 pull_request 事件,GITHUB_TOKEN 只有 contents/read、pull-requests/read、checks/read 权限。程序只 GET REST API,以及执行只读 GraphQL 线程查询,不写评论或批准。
|
|
38
|
+
|
|
39
|
+
检查项:
|
|
40
|
+
|
|
41
|
+
- AI 审查必须是原生、完整且匹配当前提交,代码/基准不可变化。
|
|
42
|
+
- 策略从事件指向的基准提交读取,不接受 PR 工作树改写的策略。
|
|
43
|
+
- 每个指定 CI 检查必须来自指定 App、对应当前 HEAD、实际 success;skipped/neutral 不视为成功。
|
|
44
|
+
- 每条 review thread 必须解决,人类 change request 不能仍未撤回。
|
|
45
|
+
- 批准必须为可信审阅者名单中、当前 HEAD 的独立真人 review;机器人、作者本人、名单外账号、旧提交、重复用户与已撤回批准不计数。
|
|
46
|
+
- 非可信作者至少两个真人批准;可信作者按风险 0/1/2 人,第二分类器可提高人数。敏感路径不得低于下限。
|
|
47
|
+
|
|
48
|
+
本地只查看 AI 层时必须显式加 --ai-only。它会输出“未检查 CI 和人工批准”,不得作为完整合并授权。
|
|
49
|
+
|
|
50
|
+
## 受信任执行边界
|
|
51
|
+
|
|
52
|
+
不要从 PR 分支安装审阅器、工作流或门禁代码,也不要接收 PR 自带账本。固定审阅器的可信版本,在干净的 profile/临时状态目录生成本次结果;PR 代码只作为材料。运行其测试/安装脚本前使用真正的隔离环境和正常审批。账本是本地审阅记录,不是密码学签名。
|
|
53
|
+
|
|
54
|
+
仓库负责人仍需实际启用分支保护、必需检查、CODEOWNERS 和新提交撤销旧批准。需要合并队列时,重新针对与主分支合并后的版本验证;插件不会自动配置 GitHub 仓库设置。
|
|
55
|
+
|
|
56
|
+
只读 API 结果存在查询后的状态变化窗口,不能代替 GitHub 在合并瞬间执行的规则。CI 名称和提供方核对也不能代替保护工作流来源。
|
|
57
|
+
|
|
58
|
+
## 发布与回滚
|
|
59
|
+
|
|
60
|
+
PR 模板记录 Testing、Risk、How to revert 和负责人。高风险功能应在宿主项目中保留开关,先内部使用,再分阶段发布。让发布系统保存上一可用版本及回滚命令;发生事故时回滚,并由熟悉代码的人把真实缺陷和干净对照加入评测。
|
|
61
|
+
|
|
62
|
+
本插件没有自动合并、替人批准、修改分支保护、发布生产代码或自动回滚的动作。不要把安装插件等同于这些流程已经生效。
|
|
63
|
+
|
|
64
|
+
## 账本异常恢复
|
|
65
|
+
|
|
66
|
+
损坏账本保持原文件并阻止通过。先停止该工作区所有审阅进程,保存状态与事件日志,然后在新的空白 profile 中重新审阅,不要把损坏文件替换为空文件以伪造原目标通过。
|
|
67
|
+
|
|
68
|
+
write.lock 只用于极短的同步事务;进程崩溃留下时,必须先确认所有写入进程已停止,再保留诊断并移走该插件状态目录中的锁。不能在活跃写入时清锁。运行租约记录 owner PID;原进程确实退出后可恢复,不能同时运行两组同编号任务。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 验证记录
|
|
2
|
+
|
|
3
|
+
日期:2026-10-17。版本:@stardustlc/dsh-review 0.3.0。
|
|
4
|
+
|
|
5
|
+
## 本地回归
|
|
6
|
+
|
|
7
|
+
Windows 下执行 pnpm test,覆盖分阶段投票、汇总、失败关闭、真实增量、中文路径、暂存区、并发账本、损坏状态、旧输出、取消、权限接线、插件卸载、基准策略和评测误计分。
|
|
8
|
+
|
|
9
|
+
完整检查 72 项通过;随后追加的结论归因、严重度与可信审阅者回归也纳入测试。当前共 75 项。最终跨平台全量统计以对应提交的 CI 为准。没有把 SKIP 记为通过。
|
|
10
|
+
|
|
11
|
+
## 官方 DSH 运行时
|
|
12
|
+
|
|
13
|
+
隔离 profile,官方 DSH 0.2.1-rc.1,Windows。真实 tools.execute 和原生 subagents.startActivation;模型为本地合成适配器,不发送远程模型请求。
|
|
14
|
+
|
|
15
|
+
- 十个工具注册并正常渲染输出。
|
|
16
|
+
- Deep 一轮 10 个独立任务:4 个 finder、2 个风险判断、3 个 verifier、1 个 arbiter。
|
|
17
|
+
- 问题未处置时阻止通过;改动后旧审查失效,重新复核后自动清除已修复问题。
|
|
18
|
+
- 修复复审 9 个独立任务。两轮共 19 个真实子会话,不是模拟 dispatcher。
|
|
19
|
+
- Windows 经官方受控 pwsh 调用 Git Bash,node -e 的双引号保留,输出 42。
|
|
20
|
+
- 只读会话拒绝命令,未写出工作区外文件。
|
|
21
|
+
- 确认延迟写入命令已实际启动,再取消调用;等待后未发生延迟写入。
|
|
22
|
+
- 外部 review_report 能经宿主返回有效 JSON,但不能获得独立审查的自动通过。
|
|
23
|
+
|
|
24
|
+
复现:先安装官方 DSH,再设置 DSH_REVIEW_DSH_ROOT 为其 node_modules 目录,执行 npm run test:host。脚本只创建隔离数据,生成报告并停止自己启动的宿主,不操作已有桌面窗口。
|
|
25
|
+
|
|
26
|
+
## CI、安装和发布
|
|
27
|
+
|
|
28
|
+
仓库 CI 配置 Windows/Linux × Node.js 22/24,执行安装、构建、测试与包清单检查。平台结果以 GitHub Actions 对应提交为准,不能由本地 Windows 结果推断 Linux 通过。
|
|
29
|
+
|
|
30
|
+
包的干净安装、官方 CLI 安装、npm 上传与仓库 CI 是不同验证项;发布后的版本、tarball 完整性和安装结果需要分别核对。未加作用域的 dsh-review 是其他作者的包,不作为本仓库发布结果。
|
|
31
|
+
|
|
32
|
+
## 未声称通过的项目
|
|
33
|
+
|
|
34
|
+
- 没有用生产模型评测真实事故集,不声称模型召回率、误报率或节省人工时间。
|
|
35
|
+
- GitHub 门禁的批准/线程/检查规则有隔离 API 回放测试,未以真实受保护 PR 完成人工批准及合并演练。
|
|
36
|
+
- 没有改动宿主 UI,未用本次运行代替桌面原生交互验收。
|
|
37
|
+
- 没有开启远程仓库的分支保护、合并队列、功能开关、灰度或生产自动回滚。
|
|
38
|
+
|
|
39
|
+
这些边界不会因为本地测试通过而自动变成“已完成”。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"trustedAuthors": [],
|
|
3
|
+
"trustedReviewers": [],
|
|
4
|
+
"requiredChecks": [
|
|
5
|
+
{ "name": "build-and-test", "appSlug": "github-actions" },
|
|
6
|
+
{ "name": "security-review", "appSlug": "github-actions" }
|
|
7
|
+
],
|
|
8
|
+
"sensitivePaths": ["src/auth/**", "src/billing/**", "src/updater/**"],
|
|
9
|
+
"alwaysTwoPaths": [".github/workflows/**", "install/**"]
|
|
10
|
+
}
|
package/lib/check.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CheckResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
command: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
exitCode: number | null;
|
|
6
|
+
timedOut: boolean;
|
|
7
|
+
aborted: boolean;
|
|
8
|
+
truncated: boolean;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
output: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ToolExecutionContext {
|
|
13
|
+
agent?: any;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
export type CommandRunner = (input: {
|
|
17
|
+
command: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
signal: AbortSignal;
|
|
21
|
+
exec: ToolExecutionContext;
|
|
22
|
+
}) => Promise<Omit<CheckResult, 'command' | 'cwd' | 'durationMs'>>;
|
|
23
|
+
export declare function runCheck(root: string, command: string, timeoutMs: number, cwd?: string, runner?: CommandRunner, exec?: ToolExecutionContext): Promise<CheckResult>;
|
package/lib/check.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
export async function runCheck(root, command, timeoutMs, cwd, runner, exec = {}) {
|
|
4
|
+
if (!runner || !exec.agent)
|
|
5
|
+
throw new Error('review_check requires the DSH controlled Bash runner and a live calling agent; direct process execution is disabled');
|
|
6
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 600000)
|
|
7
|
+
throw new Error('timeoutMs must be an integer between 1000 and 600000');
|
|
8
|
+
const workdir = realpathSync(cwd ?? root), relative = path.relative(realpathSync(root), workdir);
|
|
9
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
10
|
+
throw new Error('Evidence command cwd must stay inside the workspace');
|
|
11
|
+
const started = Date.now(), deadline = AbortSignal.timeout(timeoutMs);
|
|
12
|
+
const signal = exec.signal ? AbortSignal.any([exec.signal, deadline]) : deadline;
|
|
13
|
+
signal.throwIfAborted();
|
|
14
|
+
const result = await runner({ command, cwd: workdir, timeoutMs, signal, exec });
|
|
15
|
+
signal.throwIfAborted();
|
|
16
|
+
return { ...result, command, cwd: workdir, durationMs: Date.now() - started };
|
|
17
|
+
}
|
package/lib/cli.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function main(argv?: string[]): Promise<{
|
|
3
|
+
ok: boolean;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
runId: string;
|
|
6
|
+
reasons: string[];
|
|
7
|
+
targetId?: undefined;
|
|
8
|
+
head?: undefined;
|
|
9
|
+
open?: undefined;
|
|
10
|
+
requiredHumanApprovals?: undefined;
|
|
11
|
+
boundary?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
exitCode: number;
|
|
15
|
+
runId: string;
|
|
16
|
+
targetId: string;
|
|
17
|
+
head: string;
|
|
18
|
+
reasons: string[];
|
|
19
|
+
open: import("./ledger.js").LedgerFinding[];
|
|
20
|
+
requiredHumanApprovals: number | null;
|
|
21
|
+
boundary: string;
|
|
22
|
+
} | {
|
|
23
|
+
scope: string;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
exitCode: number;
|
|
26
|
+
runId: string;
|
|
27
|
+
reasons: string[];
|
|
28
|
+
targetId?: undefined;
|
|
29
|
+
head?: undefined;
|
|
30
|
+
open?: undefined;
|
|
31
|
+
requiredHumanApprovals?: undefined;
|
|
32
|
+
boundary?: undefined;
|
|
33
|
+
} | {
|
|
34
|
+
scope: string;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
exitCode: number;
|
|
37
|
+
runId: string;
|
|
38
|
+
targetId: string;
|
|
39
|
+
head: string;
|
|
40
|
+
reasons: string[];
|
|
41
|
+
open: import("./ledger.js").LedgerFinding[];
|
|
42
|
+
requiredHumanApprovals: number | null;
|
|
43
|
+
boundary: string;
|
|
44
|
+
} | {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
exitCode: number;
|
|
47
|
+
reasons: string[];
|
|
48
|
+
governance: {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
reasons: string[];
|
|
51
|
+
tier: "low" | "medium" | "high";
|
|
52
|
+
trustedAuthor: boolean;
|
|
53
|
+
requiredHumanApprovals: number;
|
|
54
|
+
approvedHumans: string[];
|
|
55
|
+
boundary: string;
|
|
56
|
+
};
|
|
57
|
+
runId: string;
|
|
58
|
+
targetId?: undefined;
|
|
59
|
+
head?: undefined;
|
|
60
|
+
open?: undefined;
|
|
61
|
+
requiredHumanApprovals?: undefined;
|
|
62
|
+
boundary?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
ok: boolean;
|
|
65
|
+
exitCode: number;
|
|
66
|
+
reasons: string[];
|
|
67
|
+
governance: {
|
|
68
|
+
ok: boolean;
|
|
69
|
+
reasons: string[];
|
|
70
|
+
tier: "low" | "medium" | "high";
|
|
71
|
+
trustedAuthor: boolean;
|
|
72
|
+
requiredHumanApprovals: number;
|
|
73
|
+
approvedHumans: string[];
|
|
74
|
+
boundary: string;
|
|
75
|
+
};
|
|
76
|
+
runId: string;
|
|
77
|
+
targetId: string;
|
|
78
|
+
head: string;
|
|
79
|
+
open: import("./ledger.js").LedgerFinding[];
|
|
80
|
+
requiredHumanApprovals: number | null;
|
|
81
|
+
boundary: string;
|
|
82
|
+
}>;
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { Ledger } from './ledger.js';
|
|
5
|
+
import { resolveConfig } from './config.js';
|
|
6
|
+
import { assertCurrent } from './run.js';
|
|
7
|
+
import { automatedVerdict } from './verdict.js';
|
|
8
|
+
import { git } from './exec.js';
|
|
9
|
+
import { parsePolicy, evaluatePullRequest } from './policy.js';
|
|
10
|
+
import { githubEvidence } from './github.js';
|
|
11
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
12
|
+
const args = new Map(), flags = new Set();
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
if (argv[i] === '--ai-only')
|
|
15
|
+
flags.add(argv[i]);
|
|
16
|
+
else if (['--run', '--root', '--state-dir'].includes(argv[i]) && argv[i + 1] && !argv[i + 1].startsWith('--'))
|
|
17
|
+
args.set(argv[i], argv[++i]);
|
|
18
|
+
else
|
|
19
|
+
throw new Error('Usage: dsh-review-gate --run RUN_ID [--root PATH] [--state-dir DIR] [--ai-only]. Default requires a trusted GitHub pull_request event and GITHUB_TOKEN.');
|
|
20
|
+
}
|
|
21
|
+
const config = resolveConfig({ stateDir: args.get('--state-dir') ?? '@profile' }), root = realpathSync(args.get('--root') ?? process.cwd()), ledger = new Ledger(root, config.stateDir);
|
|
22
|
+
const runId = args.get('--run'), run = runId ? ledger.snapshot().runs[runId] : undefined;
|
|
23
|
+
if (!run || run.target.root !== root)
|
|
24
|
+
throw new Error('A native review run in this workspace is required');
|
|
25
|
+
await assertCurrent(run, ledger, config);
|
|
26
|
+
const automated = automatedVerdict(ledger.snapshot(), run.id);
|
|
27
|
+
if (flags.has('--ai-only'))
|
|
28
|
+
return { ...automated, scope: 'AI only; human approval and CI not checked' };
|
|
29
|
+
if (!automated.ok)
|
|
30
|
+
return automated;
|
|
31
|
+
if (run.target.kind !== 'commit')
|
|
32
|
+
throw new Error('Pull-request gating requires a commit/base review, not a worktree or index review');
|
|
33
|
+
if (!process.env.GITHUB_EVENT_PATH || !process.env.GITHUB_TOKEN)
|
|
34
|
+
throw new Error('Missing trusted GitHub event/token. Use --ai-only only for a local AI-layer check.');
|
|
35
|
+
const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8')), pr = event.pull_request, repository = event.repository?.full_name;
|
|
36
|
+
if (!pr || typeof repository !== 'string' || pr.head?.sha !== run.head || typeof pr.base?.sha !== 'string' || !/^[a-f0-9]{40,64}$/.test(pr.base.sha))
|
|
37
|
+
throw new Error('Trusted PR event does not match the reviewed commit');
|
|
38
|
+
// Always load owners' policy from the base commit, never the PR working files.
|
|
39
|
+
const policyRead = await git(root, ['show', '--no-ext-diff', '--no-textconv', pr.base.sha + ':.github/dsh-review-policy.json']);
|
|
40
|
+
if (!policyRead.ok)
|
|
41
|
+
throw new Error('Missing trusted base-commit review policy');
|
|
42
|
+
const mergeBase = await git(root, ['merge-base', pr.base.sha, run.head]);
|
|
43
|
+
if (!mergeBase.ok || mergeBase.stdout.trim() !== run.target.base)
|
|
44
|
+
throw new Error('Review base differs from current PR base');
|
|
45
|
+
const policy = parsePolicy(JSON.parse(policyRead.stdout)), evidence = await githubEvidence(repository, pr.number, process.env.GITHUB_TOKEN, run.head, pr.base.sha);
|
|
46
|
+
const governance = evaluatePullRequest(policy, evidence, run.risk);
|
|
47
|
+
return { ...automated, ok: automated.ok && governance.ok, exitCode: governance.ok ? 0 : 1, reasons: [...automated.reasons, ...governance.reasons], governance };
|
|
48
|
+
}
|
|
49
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
50
|
+
try {
|
|
51
|
+
const result = await main();
|
|
52
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
53
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
process.stderr.write(JSON.stringify({ ok: false, exitCode: 1, error: error instanceof Error ? error.message : String(error) }) + '\n');
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|