@weotro/dx 0.1.9 → 0.1.11
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/README.md +4 -0
- package/agent-references/ship-issue-pr-core.md +36 -11
- package/package.json +1 -1
- package/skills/ask_cc/SKILL.md +18 -0
- package/skills/ask_cc/agents/openai.yaml +2 -0
- package/skills/ask_cc/scripts/ask_cc.py +12 -0
- package/skills/cc-ship-issue-pr/references/runtime.md +1 -1
- package/skills/delegate-cc/SKILL.md +54 -0
- package/skills/delegate-cc/agents/openai.yaml +2 -0
- package/skills/delegate-cc/scripts/delegate_cc.py +144 -0
- package/skills/delegate-cc/scripts/test_delegate_cc.py +90 -0
- package/skills/oo-ship-issue-pr/SKILL.md +2 -2
- package/skills/oo-ship-issue-pr/references/runtime.md +25 -28
package/README.md
CHANGED
|
@@ -307,6 +307,10 @@ dx cache clear -Y
|
|
|
307
307
|
- `~/.codex/skills` 中包内管理的同名非软链接 skill 会被清理;已有软链接不会按旧副本删除。
|
|
308
308
|
- 从包内移除的历史 skill 会在 `~/.agents/skills`、`~/.claude/skills`、`~/.codex/skills` 中一并清理。
|
|
309
309
|
- 不属于包内管理的其他用户自有 skill 目录会保留。
|
|
310
|
+
- 新增的 `ask_cc`(兼容咨询入口)和 `delegate-cc`(Claude Code 咨询与任务委派)会自动一同安装,无需修改初始化名单;保留 `ask_cc` 的旧名称及脚本入口。
|
|
311
|
+
- 这两个 skill 仅在显式调用时启用。运行脚本需要本机的 `python3` 和已认证的 `claude` CLI;`dx initial` 不安装这些依赖或修改认证配置。
|
|
312
|
+
|
|
313
|
+
安装或升级 dx 后,运行 `dx initial` 即可同步。默认脚本位置为 `~/.agents/skills/delegate-cc/scripts/delegate_cc.py`;旧咨询入口为 `~/.agents/skills/ask_cc/scripts/ask_cc.py`。
|
|
310
314
|
|
|
311
315
|
关于 `help`:
|
|
312
316
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
| 模板 | Issue/PR 正文结构一律取自 `.github/`,禁止自造结构 |
|
|
46
46
|
| Heredoc | 多行 Issue body、commit message、PR body、PR comment 必须用 heredoc |
|
|
47
47
|
| 暂存 | 按明确路径 `git add <path>`,禁止 `git add -A` / `git add .` |
|
|
48
|
-
| 验证 | 发 PR
|
|
48
|
+
| 验证 | 发 PR 前必须有覆盖当前版本的相关验证记录;按下方证据复用规则补跑失效或缺失项 |
|
|
49
49
|
| 正文质量 | 模板占位注释、`TODO`、`TBD`、「稍后补充」残留一律阻塞 |
|
|
50
50
|
| 合并 | 只能 `gh pr merge --squash --auto`,禁止 `--admin` |
|
|
51
51
|
| 真完成 | 必须轮询到 `mergedAt` 非空,并回访关联 Issue 状态 |
|
|
@@ -136,7 +136,7 @@ gh pr status
|
|
|
136
136
|
| 当前分支已有 OPEN PR | 阶段七 | 上一行全部,外加阶段六的 PR body 读回校验与依赖冲突检查 |
|
|
137
137
|
| 工作树干净、无分支差异、无 OPEN PR | 输出「无需交付」并结束 | — |
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
快捷入口只省掉已经做过的动作,不豁免任何门禁。先收集本次任务已有的验收、审查和验证记录,按下方证据复用规则核对。补齐前禁止进入目标阶段:没有 Issue ID 就先建 Issue,分支不合规就先改分支,验证缺失或失效就补跑,依赖 PR 未真合并就停下。
|
|
140
140
|
|
|
141
141
|
阻塞条件:不在 Git 仓库;`gh auth status` 不可用;当前分支为 `main` 且无法创建新分支。
|
|
142
142
|
|
|
@@ -236,9 +236,30 @@ git diff --stat
|
|
|
236
236
|
|
|
237
237
|
改动范围超出 Issue 目标、或引入未经说明的依赖时,主进程收敛回范围内,不带进提交。
|
|
238
238
|
|
|
239
|
+
## 验收、审查与验证证据复用
|
|
240
|
+
|
|
241
|
+
证据绑定代码和输入,不绑定「PR 创建前/后」或阶段编号。默认实现后做最低充分验证、提交并创建 PR,再在阶段七完成首次正式审查;进入本流程前已有的有效审查可以直接承接,无需为了发 PR 额外增加一轮。单纯 commit、push、创建 PR、更新正文或进入合并阶段不触发重审或重跑。
|
|
242
|
+
|
|
243
|
+
每次完成验收、审查或权威验证时记录以下信息;先保存在本次任务记录中,创建 PR 后将来源与复用结论写入审查报告:
|
|
244
|
+
|
|
245
|
+
- **版本与范围**:仓库、目标 base 分支及其 SHA、merge-base、被检查的 HEAD 与 tree SHA、覆盖的 diff/文件和 Issue 验收标准。未提交改动用包含相关新增文件的内容快照标识;提交后核对实际内容一致,再关联 commit,不能只记录当时的 HEAD。
|
|
246
|
+
- **审查证据**:实际 reviewer、是否独立于实现/修复、报告来源、findings 及处理状态。实现者自检、方案咨询和一句「已审查」不能代替独立审查报告。
|
|
247
|
+
- **验证证据**:实际执行者、命令与参数、结果及日志来源、影响结果的环境/工具链/依赖和配置标识(不记录密钥)。验证过程中格式化、代码生成或钩子改了输入时,记录最终版本并补跑受影响项。
|
|
248
|
+
|
|
249
|
+
复用前由主进程确认来源可读、范围充分、结论仍适用。主进程先前亲自执行并读取输出的验证可以复用;外部执行者的结果仍按入口运行时契约处理。混有无关工作树改动时,必须确认它们没有影响被验证的提交内容,否则在隔离的 worktree 验证目标版本。
|
|
250
|
+
|
|
251
|
+
| 当前状态 | 处理 |
|
|
252
|
+
|---|---|
|
|
253
|
+
| 内容、base、验收范围及相关验证输入均未变,证据完整 | 直接复用,记录来源;已有完整审查无需再次通读同一 diff |
|
|
254
|
+
| 只有 commit SHA 变化,tree、base 和其他输入一致 | 核对内容后将原证据关联新 HEAD,无需重跑 |
|
|
255
|
+
| 有新增修改、修复、验收标准变化,或 base/依赖/配置/环境变化 | 比较前后差异,复查增量及其影响的调用方、不变量和验收项;只重跑受影响的验证命令,保留未受影响证据并记录理由 |
|
|
256
|
+
| 缺少记录、无法确认版本一致或无法界定影响范围 | 补做缺失检查;无法界定影响时审查完整当前 diff,并执行阶段五适用的验证矩阵 |
|
|
257
|
+
|
|
258
|
+
审查与验证分别判断是否可复用;测试通过不能代替代码审查。当前 diff 仍须过复杂度门禁:重改动只有自审记录时,仍需补齐 7.3 的条件式独立审查或按入口契约记录降级,才算审查完整。历史失败和未处理 findings 继续保留,不能因复用而改记为通过。PR 前已完成的独立外部审查计入同一交付的「每 PR 最多一次」额度;历史额度与当前覆盖范围分别记录,后续新增改动由主进程补审。
|
|
259
|
+
|
|
239
260
|
## 阶段五:验证与提交
|
|
240
261
|
|
|
241
|
-
|
|
262
|
+
按改动范围确定最低充分验证,复用有效记录,仅执行缺失或失效项:
|
|
242
263
|
|
|
243
264
|
| 改动范围 | 必跑命令 |
|
|
244
265
|
|---|---|
|
|
@@ -277,6 +298,8 @@ MSG
|
|
|
277
298
|
|
|
278
299
|
标题必须是 Conventional Commit。既有问题的顺手修复单独用 `chore(precheck):` 提交,与本次功能改动分开。
|
|
279
300
|
|
|
301
|
+
提交后核对 commit 内容与验证快照一致;提交钩子或暂存范围造成内容差异时,按证据复用规则补验,再进入阶段六。
|
|
302
|
+
|
|
280
303
|
## 阶段六:PR 创建或更新
|
|
281
304
|
|
|
282
305
|
```bash
|
|
@@ -334,7 +357,7 @@ git merge --no-commit --no-ff origin/main
|
|
|
334
357
|
|
|
335
358
|
## 阶段七:审查修复循环
|
|
336
359
|
|
|
337
|
-
最多 3
|
|
360
|
+
最多 3 轮实际审查修复。首次进入先核对已有证据,每轮只补齐 Issue 验收、验证流水线、代码审查和逐项修复中的缺失或失效项。证据全部有效且满足终止条件时,发布复用报告后直接进入阶段八;复用不算新一轮审查。
|
|
338
361
|
|
|
339
362
|
### 7.1 Issue 验收(主进程)
|
|
340
363
|
|
|
@@ -343,7 +366,7 @@ gh issue view <ISSUE_ID> --json title,body,state
|
|
|
343
366
|
gh pr diff <PR_NUMBER>
|
|
344
367
|
```
|
|
345
368
|
|
|
346
|
-
|
|
369
|
+
先确认 Issue 标准及 PR diff 相对已有记录的变化。已有证据覆盖的验收项直接引用;对新增、变化或缺证据的标准逐条核对:
|
|
347
370
|
|
|
348
371
|
| 状态 | 后续 |
|
|
349
372
|
|---|---|
|
|
@@ -354,16 +377,16 @@ gh pr diff <PR_NUMBER>
|
|
|
354
377
|
|
|
355
378
|
### 7.2 验证流水线(主进程)
|
|
356
379
|
|
|
357
|
-
|
|
380
|
+
按证据复用规则补跑阶段五中缺失或受本轮变化影响的命令;阶段五或上一轮修复后已验证同一输入时直接引用结果。失败必须保留完整错误:文件、行号、命令、失败摘要。
|
|
358
381
|
|
|
359
382
|
### 7.3 代码审查
|
|
360
383
|
|
|
361
|
-
|
|
384
|
+
主进程先核对已有审查的版本、范围、reviewer 和问题处理状态。没有有效审查时通读 `gh pr diff` 全部改动;已有完整有效审查时直接复用,有后续修改时只审增量及受影响上下文。无法界定影响范围时恢复完整审查。核查 diff 涉及文件内的真实 bug,不主动扩大到无关模块。
|
|
362
385
|
|
|
363
386
|
外部专家独立审查是条件式补充,同时满足两条才派发:
|
|
364
387
|
|
|
365
388
|
1. 实际 diff 命中**复杂度门禁**任一信号(重改动)。
|
|
366
|
-
2.
|
|
389
|
+
2. 本次交付尚未用过外部专家审查——同一 PR 最多一次,含创建 PR 前对本次交付的独立审查;派发前查任务记录与本 PR 已有报告的 reviewer、独立性和覆盖范围,确认已有独立外部审查即只由主进程补审未覆盖的改动。轻改动 PR 的这次额度可以留到后续 diff 扩大首次命中信号时再用。
|
|
367
390
|
|
|
368
391
|
使用入口技能声明的 review 通道。需要挑战实现方案、状态机、不变量或安全边界时改用 adversarial review;需要对照 Issue 验收标准或排除已确认问题时,把定制上下文一并传入。
|
|
369
392
|
|
|
@@ -388,6 +411,8 @@ gh pr comment <PR_NUMBER> --body-file - <<'MSG'
|
|
|
388
411
|
- Critical:0 / Major:0 / Minor:0
|
|
389
412
|
- reviewer:主进程自审 / 外部专家 review / 外部专家 adversarial review
|
|
390
413
|
- 复杂度门禁:轻改动(未命中信号)/ 重改动(命中信号 N);外部专家额度:未用 / 本轮已用 / 此前已用
|
|
414
|
+
- 证据:base / merge-base / HEAD / tree;验收、审查与验证各自的原记录来源和覆盖范围
|
|
415
|
+
- 本次处理:复用 / 增量补查 / 首次完整检查;变化与失效原因(如有);复用报告沿用原轮次
|
|
391
416
|
|
|
392
417
|
### 验证流水线
|
|
393
418
|
|
|
@@ -413,7 +438,7 @@ MSG
|
|
|
413
438
|
|
|
414
439
|
修复默认主进程直接改;只有单条修复自身命中**复杂度门禁**信号(判定的是这次修复的改动面,不是原问题的严重级)才派外部专家,规则同阶段四。
|
|
415
440
|
|
|
416
|
-
|
|
441
|
+
修复后复查问题与受影响上下文,重跑受影响的验证并更新证据,再发布修复报告评论,列出已修复项及其 commit、拒绝项及理由、follow-up 项及 Issue 编号。下一轮直接承接这些证据。
|
|
417
442
|
|
|
418
443
|
### 循环终止
|
|
419
444
|
|
|
@@ -423,10 +448,10 @@ MSG
|
|
|
423
448
|
|
|
424
449
|
## 阶段八:合并
|
|
425
450
|
|
|
426
|
-
|
|
451
|
+
合并前读取 PR 的最新 `headRefOid`、base 并刷新目标分支,核对远端 head 与本地已检查提交一致、相关工作树输入与证据一致。若有变化,按证据复用规则回到阶段七补齐;全部有效时直接发布验证总结评论,引用已完成的实际命令与证据来源、审查轮数和问题统计。验证失败时禁止写「可合并」。
|
|
427
452
|
|
|
428
453
|
```bash
|
|
429
|
-
gh pr merge <PR_NUMBER> --squash --auto
|
|
454
|
+
gh pr merge <PR_NUMBER> --squash --auto --match-head-commit <已核对的HEAD_SHA>
|
|
430
455
|
gh pr view <PR_NUMBER> --json state,mergedAt,url
|
|
431
456
|
```
|
|
432
457
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ask_cc
|
|
3
|
+
description: 仅在用户显式调用 $ask_cc 或明确要求使用 ask_cc 技能,或用户已显式调用的 oo-ship-issue-pr 流程要求咨询时使用;不要通过关键词、任务类型或上下文自动触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ask_cc
|
|
7
|
+
|
|
8
|
+
使用 [delegate-cc](../delegate-cc/SKILL.md) 的咨询流程获取答案或第二意见,默认不授予工具权限。
|
|
9
|
+
|
|
10
|
+
若用户明确要求修改代码或执行开发任务,转入该技能的 `task` 模式,按任务范围完成实现和验收。
|
|
11
|
+
|
|
12
|
+
旧命令 `scripts/ask_cc.py --prompt-file /绝对路径/问题.txt` 继续可用,仅运行咨询模式;模型顺序和降级逻辑复用 `delegate-cc`。
|
|
13
|
+
|
|
14
|
+
`dx initial` 会同时安装这两个技能。使用当前 `SKILL.md` 所在目录下的脚本;默认安装后的调用方式:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 "$HOME/.agents/skills/ask_cc/scripts/ask_cc.py" --prompt-file /绝对路径/问题.txt
|
|
18
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""兼容旧咨询入口;统一实现位于 delegate-cc。"""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "delegate-cc" / "scripts"))
|
|
8
|
+
from delegate_cc import ask, main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
main([*sys.argv[1:], "--mode", "consult"])
|
|
@@ -86,7 +86,7 @@ LIVE
|
|
|
86
86
|
|
|
87
87
|
- 阶段四方案讨论:只读 Codex agent;主进程先写初步方案,最终拍板权在主进程。
|
|
88
88
|
- 阶段四实现与阶段 7.4 重修复:可写 Codex agent,prompt 要求不 commit、不 push、不碰 GitHub、不递归委派。
|
|
89
|
-
- 阶段 7.3
|
|
89
|
+
- 阶段 7.3:先按共享流程复用已有审查;复杂度门禁命中且本次交付尚未用过 Codex 独立审查时执行 review(创建 PR 前的审查也计数);需要挑战方案时执行 adversarial review。
|
|
90
90
|
- reviewer:`Claude Code 主进程自审 / codex native review / codex adversarial review / codex 只读 subagent`。
|
|
91
91
|
|
|
92
92
|
Codex 返回后主进程必须直读源码和 diff 核验,不采信转述。重改动实现通道不可用时阻塞;条件式独立审查不可用时由主进程完成本轮自审并在报告标明降级。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: delegate-cc
|
|
3
|
+
description: 仅在用户显式调用 $delegate-cc 或明确要求使用 delegate-cc 技能,或已获用户调用授权的 ask_cc、oo-ship-issue-pr 流程要求委派时使用;不要通过关键词、任务类型或上下文自动触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# delegate-cc
|
|
7
|
+
|
|
8
|
+
把 Claude Code 当作协作者。模型顺序固定为 Fable 5.1(`claude-fable-5-1`)→ Opus 5(`claude-opus-5`)→ 当前 sub-agent。保留本机现有认证。
|
|
9
|
+
|
|
10
|
+
## 1. 准备交接
|
|
11
|
+
|
|
12
|
+
- **咨询 `consult`**:用户需要答案、决策建议或评审意见。提供问题、相关代码或事实、约束;CLI 无工具权限,只返回建议。
|
|
13
|
+
- **执行 `task`**:用户要求实现、修复或把开发任务交给 CC。任务书必须包含工作目录、目标、可修改文件或模块、适用的项目指令、验收标准和仓库实际验证命令。明确告知 CC 还有其他协作者,保留他人的改动,只负责分配范围。
|
|
14
|
+
|
|
15
|
+
执行前检查 `git status --short` 和已有 diff,记录涉及文件的初始状态及未跟踪文件。分配互不重叠的文件;需要并发改同一文件时,先拆分责任或使用独立 worktree,指定由谁整合。worktree 不会自动携带未提交改动,必须提供任务所需的当前版本。CC 执行期间,主 agent 不同时修改其负责的文件。
|
|
16
|
+
|
|
17
|
+
将任务书写入临时 UTF-8 文件,只传必要上下文,排除凭据。完成条件:CC 无需当前对话即可理解目标、权限范围与完成标准。
|
|
18
|
+
|
|
19
|
+
## 2. 调用 CLI
|
|
20
|
+
|
|
21
|
+
需要本机提供 `python3` 和已完成认证的 `claude` CLI;`dx initial` 只同步技能文件,不安装运行时或配置认证。
|
|
22
|
+
|
|
23
|
+
运行当前 `SKILL.md` 所在目录下的 `scripts/delegate_cc.py`。以下路径适用于 `dx initial` 的默认安装位置;通过其他方式安装时使用实际技能目录:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 咨询:默认模式,单个模型默认超时 120 秒。
|
|
27
|
+
python3 "$HOME/.agents/skills/delegate-cc/scripts/delegate_cc.py" --prompt-file /绝对路径/问题.txt
|
|
28
|
+
|
|
29
|
+
# 执行:必须指定工作目录,单次默认超时 900 秒。
|
|
30
|
+
python3 "$HOME/.agents/skills/delegate-cc/scripts/delegate_cc.py" --mode task --cwd /绝对路径/工作区 --prompt-file /绝对路径/任务.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
需要 CC 自行运行验证命令时,按本次授权追加 `--allow-command '仓库实际验证命令'`,可重复传入。使用具体命令而非开放全部 Bash;检查命令本身及其调用脚本的作用。现有 Claude Code 权限规则仍生效,`--allow-command` 只是增加允许规则,不是独立沙箱。文件责任范围通过任务书和验收控制,需要隔离时使用 worktree 或已有沙箱。
|
|
34
|
+
|
|
35
|
+
执行模式保留默认编码系统提示,开放 `Read,Glob,Grep,Edit,Write,Bash`,使用 `acceptEdits`,不能获批的工具请求自动拒绝。两种模式都关闭 hooks、MCP 和技能,避免额外任务链;主 agent 应把所需项目规则及技能要求写入任务书。本技能不使用 `bypassPermissions`,也不扩大任务至提交、推送、部署或对外发送消息。
|
|
36
|
+
|
|
37
|
+
通过工具异步运行,至少每 60 秒向用户汇报进度。`--timeout` 可按任务规模调整;超时会终止执行任务的进程组。完成条件:获得 JSON 结果或明确的执行失败,旧进程已结束。
|
|
38
|
+
|
|
39
|
+
## 3. 验收或接续
|
|
40
|
+
|
|
41
|
+
- `ok`:咨询获得答案;检查是否回应原问题,不能把空泛回复当作完成。
|
|
42
|
+
- `review_required`:执行已返回报告,尚待主 agent 验收。对照初始状态检查 diff、新文件及责任范围,运行必要的格式化、检查和测试。发现越界修改时先核对归属,只修正本次 CC 引入的变化。`permission_denials` 非零时核实未执行工作,使用已获授权的宿主工具完成,或准确报告实际权限阻塞。
|
|
43
|
+
- `continuation_required`:任务可能已部分执行。先检查现有 diff、测试状态及外部副作用;更新任务书为“保留已完成部分,仅完成剩余工作”。若 `next_model` 有值,以 `--start-model` 传入该值接续;每个模型最多尝试一次。禁止将同一任务从头盲目重放。
|
|
44
|
+
- `fallback_required`、第二模型也失败或 CLI 无法运行:当前执行者已经是 sub-agent 时直接接手;主 agent 则委派一个继承当前模型的 sub-agent,环境不支持委派时自己接手。提供原任务、已完成改动和剩余工作,明确跳过本技能及 CLI,避免递归。权限拒绝不能靠切换模型绕过。
|
|
45
|
+
|
|
46
|
+
咨询模式由脚本自动依次尝试两个模型;执行模式每次只运行一个模型,由主 agent 检查现场后接续。认证、额度、模型不可用和超时都不等于任务完成。
|
|
47
|
+
|
|
48
|
+
完成条件:咨询得到实质答案,或执行任务的代码和验证满足验收标准;仍有缺口时明确列出,而非仅转述 CC 的成功声明。
|
|
49
|
+
|
|
50
|
+
## 4. 交付
|
|
51
|
+
|
|
52
|
+
用简体中文说明答案或实际改动、验证结果、未完成项,简短注明使用的模型及降级情况。`requested_model` 是请求 ID,不保证服务端未自动切换模型。清理本次临时任务文件;保留仍有成果待整合的 worktree。
|
|
53
|
+
|
|
54
|
+
参数或模型出错时核对本机 `claude --help`、[CLI 参考](https://code.claude.com/docs/en/cli-reference)和[模型 ID](https://platform.claude.com/docs/en/models/overview)。用 `claude auth status` 区分认证状态,`unrecognized_model` 不表示未登录。
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""向 Claude Code 委派咨询或代码任务,并报告降级与接续状态。"""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import shutil
|
|
10
|
+
import signal
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MODELS = ("claude-fable-5-1", "claude-opus-5")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_task(command, prompt, timeout, cwd):
|
|
19
|
+
"""超时或中断时终止整个任务进程组,避免后台写入与接续任务竞争。"""
|
|
20
|
+
with subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
21
|
+
stderr=subprocess.PIPE, text=True, encoding="utf-8",
|
|
22
|
+
errors="replace", cwd=cwd, start_new_session=True) as process:
|
|
23
|
+
try:
|
|
24
|
+
stdout, stderr = process.communicate(prompt, timeout=timeout)
|
|
25
|
+
except BaseException:
|
|
26
|
+
try:
|
|
27
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
28
|
+
except ProcessLookupError:
|
|
29
|
+
pass
|
|
30
|
+
process.communicate()
|
|
31
|
+
raise
|
|
32
|
+
return subprocess.CompletedProcess(command, process.returncode, stdout, stderr)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ask(prompt, timeout=120, *, mode="consult", cwd=None, start_model=None, allow_commands=()):
|
|
36
|
+
if mode not in ("consult", "task"):
|
|
37
|
+
raise ValueError("未知模式")
|
|
38
|
+
if mode == "task" and (cwd is None or not Path(cwd).is_dir()):
|
|
39
|
+
raise ValueError("代码任务必须指定存在的工作目录")
|
|
40
|
+
if mode == "consult" and (start_model is not None or allow_commands):
|
|
41
|
+
raise ValueError("咨询模式不接受任务专用参数")
|
|
42
|
+
attempts = []
|
|
43
|
+
cli = shutil.which("claude")
|
|
44
|
+
if not cli:
|
|
45
|
+
return {"status": "fallback_required", "attempts": [], "reason": "未找到 claude 命令"}
|
|
46
|
+
models = MODELS if mode == "consult" else (start_model or MODELS[0],)
|
|
47
|
+
for model in models:
|
|
48
|
+
if model not in MODELS:
|
|
49
|
+
raise ValueError("模型不在委派链中")
|
|
50
|
+
print(f"正在调用 {model}({mode})…", file=sys.stderr, flush=True)
|
|
51
|
+
command = [
|
|
52
|
+
cli, "--print", "--model", model, "--output-format", "json",
|
|
53
|
+
"--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
|
|
54
|
+
"--disable-slash-commands", "--no-session-persistence",
|
|
55
|
+
"--settings", '{"disableAllHooks":true}',
|
|
56
|
+
]
|
|
57
|
+
if mode == "consult":
|
|
58
|
+
command += ["--tools", "", "--system-prompt", "你是决策与问答顾问。根据提供的事实用简体中文回答,说明关键理由和不确定性。仅提供建议。"]
|
|
59
|
+
else:
|
|
60
|
+
command += [
|
|
61
|
+
"--tools", "Read,Glob,Grep,Edit,Write,Bash",
|
|
62
|
+
"--permission-mode", "acceptEdits", "--permission-prompts", "none",
|
|
63
|
+
"--append-system-prompt",
|
|
64
|
+
"你是协作开发者。按任务书实际修改代码并验证。你不是唯一的执行者,保留其他人的改动,遵守分配的文件范围。"
|
|
65
|
+
"先阅读适用的 AGENTS.md 和 CLAUDE.md;接续任务先检查当前状态,仅完成剩余工作。"
|
|
66
|
+
"仅执行本地开发和验证,不提交、推送、发布或对外发消息。权限被拒绝时报告具体阻塞,不尝试绕过。"
|
|
67
|
+
"完成后用简体中文报告变更文件、验证命令及结果、未完成项。",
|
|
68
|
+
]
|
|
69
|
+
if allow_commands:
|
|
70
|
+
command += ["--allowedTools", *[f"Bash({item})" for item in allow_commands]]
|
|
71
|
+
try:
|
|
72
|
+
process = run_task(command, prompt, timeout, cwd) if mode == "task" else subprocess.run(
|
|
73
|
+
command, input=prompt, text=True, encoding="utf-8",
|
|
74
|
+
errors="replace", capture_output=True, timeout=timeout, cwd=cwd,
|
|
75
|
+
)
|
|
76
|
+
except subprocess.TimeoutExpired:
|
|
77
|
+
attempts.append({"requested_model": model, "reason": "调用超时"})
|
|
78
|
+
continue
|
|
79
|
+
except OSError as error:
|
|
80
|
+
attempts.append({"requested_model": model, "reason": f"无法运行 CLI:{type(error).__name__}"})
|
|
81
|
+
break
|
|
82
|
+
try:
|
|
83
|
+
payload = json.loads(process.stdout)
|
|
84
|
+
except (ValueError, TypeError):
|
|
85
|
+
payload = {}
|
|
86
|
+
if not isinstance(payload, dict):
|
|
87
|
+
payload = {}
|
|
88
|
+
answer = payload.get("result")
|
|
89
|
+
if (process.returncode == 0 and payload.get("type") == "result"
|
|
90
|
+
and payload.get("subtype") == "success"
|
|
91
|
+
and payload.get("is_error") is not True
|
|
92
|
+
and isinstance(answer, str) and answer.strip()):
|
|
93
|
+
return {"status": "review_required" if mode == "task" else "ok", "requested_model": model,
|
|
94
|
+
"permission_denials": len(payload.get("permission_denials") or []),
|
|
95
|
+
"answer": answer.strip(), "attempts": attempts}
|
|
96
|
+
# 原始错误可能包含账户信息,仅输出有界的分类原因。
|
|
97
|
+
diagnostic = (process.stderr + process.stdout).lower()
|
|
98
|
+
if any(word in diagnostic for word in ("quota", "credit", "usage limit", "rate_limit", "rate limit", "429")):
|
|
99
|
+
reason = "额度不足或限流"
|
|
100
|
+
elif any(word in diagnostic for word in ("authentication", "unauthorized", "not logged in", "401")):
|
|
101
|
+
reason = "认证不可用"
|
|
102
|
+
elif any(word in diagnostic for word in ("unrecognized_model", "issue with the selected model", "model_not_found")):
|
|
103
|
+
reason = "模型名称无效或当前账户无权访问"
|
|
104
|
+
else:
|
|
105
|
+
reason = f"CLI 未返回有效答案(退出码 {process.returncode})"
|
|
106
|
+
attempts.append({"requested_model": model, "reason": reason})
|
|
107
|
+
result = {"status": "fallback_required", "attempts": attempts}
|
|
108
|
+
if mode == "task":
|
|
109
|
+
result.update(status="continuation_required", reason="检查现有改动后接续,前次任务可能已经部分执行",
|
|
110
|
+
next_model=MODELS[1] if models[0] == MODELS[0] else None)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def main(argv=None):
|
|
115
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
116
|
+
parser.add_argument("--prompt-file", required=True, type=Path)
|
|
117
|
+
parser.add_argument("--mode", choices=("consult", "task"), default="consult")
|
|
118
|
+
parser.add_argument("--cwd", type=Path)
|
|
119
|
+
parser.add_argument("--start-model", choices=MODELS)
|
|
120
|
+
parser.add_argument("--allow-command", action="append", default=[])
|
|
121
|
+
parser.add_argument("--timeout", type=float)
|
|
122
|
+
args = parser.parse_args(argv)
|
|
123
|
+
if args.timeout is None:
|
|
124
|
+
args.timeout = 900 if args.mode == "task" else 120
|
|
125
|
+
if not math.isfinite(args.timeout) or args.timeout <= 0:
|
|
126
|
+
parser.error("--timeout 必须为有限正数")
|
|
127
|
+
if args.mode == "task" and (args.cwd is None or not args.cwd.is_dir()):
|
|
128
|
+
parser.error("任务模式必须通过 --cwd 指定存在的工作目录")
|
|
129
|
+
if args.mode == "consult" and (args.start_model or args.allow_command):
|
|
130
|
+
parser.error("--start-model 和 --allow-command 仅用于任务模式")
|
|
131
|
+
try:
|
|
132
|
+
prompt = args.prompt_file.read_text(encoding="utf-8")
|
|
133
|
+
if not prompt.strip():
|
|
134
|
+
raise ValueError("问题为空")
|
|
135
|
+
except (OSError, UnicodeError, ValueError):
|
|
136
|
+
result = {"status": "fallback_required", "attempts": [], "reason": "问题文件不可读或为空,请宿主使用原问题兜底"}
|
|
137
|
+
else:
|
|
138
|
+
result = ask(prompt, args.timeout, mode=args.mode, cwd=args.cwd,
|
|
139
|
+
start_model=args.start_model, allow_commands=args.allow_command)
|
|
140
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""复现 CLI 模型校验失败,覆盖模型顺序及错误分类。"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import unittest
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
import delegate_cc as ask_cc
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def result(answer, failed=False):
|
|
14
|
+
return subprocess.CompletedProcess([], int(failed), json.dumps({
|
|
15
|
+
"type": "result", "subtype": "success", "is_error": failed,
|
|
16
|
+
"result": answer,
|
|
17
|
+
}), "")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AskCCTest(unittest.TestCase):
|
|
21
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
22
|
+
def test_task_permissions_and_review(self, _which):
|
|
23
|
+
with tempfile.TemporaryDirectory() as cwd, patch.object(ask_cc, "run_task", return_value=result("完成")) as run:
|
|
24
|
+
response = ask_cc.ask("修改代码", mode="task", cwd=cwd, allow_commands=["python3 -m unittest"])
|
|
25
|
+
command = run.call_args.args[0]
|
|
26
|
+
self.assertEqual(response["status"], "review_required")
|
|
27
|
+
self.assertEqual(command[command.index("--permission-mode") + 1], "acceptEdits")
|
|
28
|
+
self.assertIn("Bash(python3 -m unittest)", command)
|
|
29
|
+
self.assertNotIn("bypassPermissions", command)
|
|
30
|
+
|
|
31
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
32
|
+
def test_partial_task_is_not_blindly_retried(self, _which):
|
|
33
|
+
with tempfile.TemporaryDirectory() as cwd:
|
|
34
|
+
def partial(*args):
|
|
35
|
+
Path(cwd, "partial.txt").write_text("已完成部分")
|
|
36
|
+
raise subprocess.TimeoutExpired("claude", 1)
|
|
37
|
+
with patch.object(ask_cc, "run_task", side_effect=partial) as run:
|
|
38
|
+
response = ask_cc.ask("修改代码", mode="task", cwd=cwd)
|
|
39
|
+
self.assertEqual(run.call_count, 1)
|
|
40
|
+
self.assertEqual(Path(cwd, "partial.txt").read_text(), "已完成部分")
|
|
41
|
+
self.assertEqual(response["status"], "continuation_required")
|
|
42
|
+
self.assertEqual(response["next_model"], "claude-opus-5")
|
|
43
|
+
|
|
44
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
45
|
+
def test_second_task_failure_requests_local_fallback(self, _which):
|
|
46
|
+
with tempfile.TemporaryDirectory() as cwd, patch.object(ask_cc, "run_task", return_value=result("usage limit", True)):
|
|
47
|
+
response = ask_cc.ask("接续", mode="task", cwd=cwd, start_model="claude-opus-5")
|
|
48
|
+
self.assertEqual(response["status"], "continuation_required")
|
|
49
|
+
self.assertIsNone(response["next_model"])
|
|
50
|
+
|
|
51
|
+
def test_task_requires_explicit_directory(self):
|
|
52
|
+
with self.assertRaises(ValueError):
|
|
53
|
+
ask_cc.ask("修改代码", mode="task")
|
|
54
|
+
|
|
55
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
56
|
+
def test_supported_model_ids_and_fallback(self, _which):
|
|
57
|
+
seen = []
|
|
58
|
+
|
|
59
|
+
def cli(command, **kwargs):
|
|
60
|
+
model = command[command.index("--model") + 1]
|
|
61
|
+
seen.append(model)
|
|
62
|
+
if model == "claude-fable-5-1":
|
|
63
|
+
return result("usage limit exceeded", True)
|
|
64
|
+
if model == "claude-opus-5":
|
|
65
|
+
return result("OK")
|
|
66
|
+
return result(f"There's an issue with the selected model ({model}).", True)
|
|
67
|
+
|
|
68
|
+
with patch.object(ask_cc.subprocess, "run", side_effect=cli):
|
|
69
|
+
response = ask_cc.ask("仅回答 OK")
|
|
70
|
+
self.assertEqual(response["status"], "ok")
|
|
71
|
+
self.assertEqual(seen, ["claude-fable-5-1", "claude-opus-5"])
|
|
72
|
+
self.assertEqual(response["answer"], "OK")
|
|
73
|
+
|
|
74
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
75
|
+
def test_unrecognized_model_is_reported(self, _which):
|
|
76
|
+
failure = result("There's an issue with the selected model (fable-5.1). It may not exist or you may not have access to it.", True)
|
|
77
|
+
with patch.object(ask_cc.subprocess, "run", return_value=failure):
|
|
78
|
+
response = ask_cc.ask("问题")
|
|
79
|
+
self.assertEqual(response["status"], "fallback_required")
|
|
80
|
+
self.assertEqual(response["attempts"][0]["reason"], "模型名称无效或当前账户无权访问")
|
|
81
|
+
|
|
82
|
+
@patch.object(ask_cc.shutil, "which", return_value="/fake/claude")
|
|
83
|
+
def test_login_error_is_distinct(self, _which):
|
|
84
|
+
with patch.object(ask_cc.subprocess, "run", return_value=result("Not logged in · Please run /login", True)):
|
|
85
|
+
response = ask_cc.ask("问题")
|
|
86
|
+
self.assertEqual(response["attempts"][0]["reason"], "认证不可用")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
unittest.main()
|
|
@@ -5,7 +5,7 @@ description: 仅在用户显式调用 $oo-ship-issue-pr、/oo-ship-issue-pr 或
|
|
|
5
5
|
|
|
6
6
|
# OO Ship Issue PR
|
|
7
7
|
|
|
8
|
-
Codex
|
|
8
|
+
Codex 入口:主进程执行轻改动;Claude 咨询与独立审查调用 [ask_cc](../ask_cc/SKILL.md),重改动实现与修复调用 [delegate-cc](../delegate-cc/SKILL.md) 的 `task` 模式。调用、模型选择和降级均复用这两个技能。
|
|
9
9
|
|
|
10
10
|
## 步骤一:加载共享流程
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ test -f "$SHIP_CORE"
|
|
|
18
18
|
完整读取 `SHIP_CORE` 后执行全部阶段。运行时映射如下:
|
|
19
19
|
|
|
20
20
|
- 主进程:当前 Codex。
|
|
21
|
-
-
|
|
21
|
+
- 外部专家:通过 `ask_cc` 或 `delegate-cc` 调用的 Claude Code;审查使用独立上下文,不复用实现或修复会话。
|
|
22
22
|
- 入口显示名:`OO Ship Issue PR`。
|
|
23
23
|
- Follow-up:Codex `spawn_agent` 的 `worker` 角色。
|
|
24
24
|
|
|
@@ -2,50 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
仅在共享流程进入外部专家或 follow-up 分支时读取。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Claude 技能路由
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
用户显式调用 `oo-ship-issue-pr` 后,按下表调用其依赖技能。先完整读取对应 `SKILL.md`,再执行其中的流程;路径相对于本文件,`dx initial` 会将这些技能安装在同一个 skills 根目录下。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| 共享流程阶段 | 调用技能 | 本阶段目标 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| 阶段四方案讨论 | [ask_cc](../../ask_cc/SKILL.md) | 只读咨询,裁决初步方案、备选与取舍 |
|
|
12
|
+
| 阶段 7.3 review | [ask_cc](../../ask_cc/SKILL.md) | 对照 Issue 与当前 diff 返回独立 findings |
|
|
13
|
+
| 阶段 7.3 adversarial review | [ask_cc](../../ask_cc/SKILL.md) | 挑战假设、不变量、反例与失败路径 |
|
|
14
|
+
| 阶段四重改动实现、阶段 7.4 重修复 | [delegate-cc](../../delegate-cc/SKILL.md) 的 `task` 模式 | 按最终方案修改代码并提供验证结果 |
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
本入口只补充交付上下文与验收要求。CLI 参数、模型顺序、权限配置、等待与超时、状态解析、接续及降级统一按依赖技能执行,不在这里另写调用脚本或重试链。依赖文件缺失时先运行 `dx initial` 恢复;仍无法读取时报告缺失路径,不临时仿写该技能。
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
- 当前阶段是只读方案讨论、只读 review、adversarial review、实现还是修复;只承接这一阶段,不运行完整 `oo-ship-issue-pr`。
|
|
15
|
-
- Issue 原文、逐条验收标准、base、主进程初步方案或最终方案,以及该阶段所需的 diff、已确认问题和验证上下文。
|
|
16
|
-
- 先读取根与涉及目录的 `AGENTS.md`;它不是代码库中唯一执行者,保护已有未提交改动,不回退其他人的修改。
|
|
17
|
-
- 只读阶段保持工作树不变;实现或修复阶段的所有权限定为 prompt 列出的验收范围和涉及文件,改动留在工作树。
|
|
18
|
-
- 不 commit、不 push、不操作 GitHub、不递归委派;最后返回证据、结论、修改路径和未运行验证。
|
|
18
|
+
## 交接上下文
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
每次交接包含当前 worktree 绝对路径、阶段、Issue 原文与逐条验收标准、base,以及该阶段所需的方案、代码和验证证据。任务仅覆盖当前阶段,不运行完整 `oo-ship-issue-pr`;Git 历史、GitHub 操作、权威验证和最终事实核验由 Codex 主进程负责。
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
### 咨询与独立审查
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
- 普通 review:以 findings 为主,按严重级排序;每项包含严重级、`文件:行号`、证据、影响和具体修法。
|
|
26
|
-
- adversarial review:先挑战方案假设、构造反例和失败路径,再输出同样格式的 findings;没有证据的问题不进入报告。
|
|
24
|
+
`ask_cc` 的咨询模式无工具权限。主进程先读取适用的 `AGENTS.md`、相关源码和当前 diff,将必要内容连同文件路径、行号、验证结果写进咨询材料;审查材料覆盖整个 PR diff,另附影响判断所需的上下文。仅提供仓库路径或要求 Claude 自行读文件不构成完整交接。证据不足时由主进程补充材料后继续同一审查阶段。
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
- 方案讨论:附主进程初步方案、备选与取舍、希望裁决的具体问题;最终方案由主进程拍板。
|
|
27
|
+
- 普通 review:要求按严重级返回 findings,每项包含 `文件:行号`、证据、影响和具体修法。
|
|
28
|
+
- adversarial review:先挑战方案假设、构造反例和失败路径,再按同样格式返回 findings。
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
审查使用全新独立上下文,不复用实现或修复会话。通过依赖技能降级后,只有未参与实现或修复的独立执行者完成的审查才计入外部专家审查;主进程自审不得冒充独立审查。
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
### 实现与修复
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
按 `delegate-cc` 的任务书要求,附最终方案原文、可修改文件或模块、适用项目指令、验收标准及仓库实际验证命令。保护已有改动,只在指定 worktree 和分配范围内工作,改动留在工作树。需要 Claude 运行验证时按该技能声明允许的具体命令。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
## 验收与报告
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
取得结果、等待停止、处理部分改动、接续或降级均按依赖技能执行。主进程取得最终结果后直读源码和 diff,核验结论、合并重复项并记录拒绝依据;仅派发成功或返回成功声明不算阶段完成。
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
外部执行者的验证仅作参考。交接材料附本阶段需补跑的清单,并要求报告未运行的验证及原因;权威验证由 Codex 主进程执行并阅读输出。主进程已有验证按共享流程的证据复用规则承接,后续阶段只补跑缺失或失效项。
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
阶段 7.3 仍服从共享流程的证据复用、复杂度门禁和每 PR 最多一次外部专家审查约束。创建 PR 前已有的独立外部审查也继续计数并按覆盖范围复用;同一阶段因证据不足补充材料或按依赖技能降级,不能被当作后续轮次重新派发审查的额度。
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
- 阶段四实现与阶段 7.4 重修复:可写 fresh Codex subagent。
|
|
46
|
-
- 阶段 7.3:复杂度门禁命中且本 PR 尚未用过外部专家审查时执行 review;需要挑战方案时执行 adversarial review。成功返回的 fresh Codex subagent 计入本 PR 唯一一次外部专家审查,Codex 主进程降级不计入;已有报告中的历史外部专家审查仍按共享流程计数。
|
|
47
|
-
- reviewer:`Codex 主进程自审 / fresh Codex subagent review / fresh Codex subagent adversarial review / Codex 主进程降级 review / Codex 主进程降级 adversarial review`,按本轮实际执行者填写;其他专家阶段同样记录实际使用 fresh Codex subagent 或 Codex 主进程降级。
|
|
44
|
+
reviewer 按实际执行者记录为 `Codex 主进程自审`、`Claude Code(ask_cc)review/adversarial review` 或实际降级执行者,并记录依赖技能返回的请求模型及降级情况。实现与修复记录使用 `delegate-cc task` 及实际执行者;自身接管的审查记为自审,不计入外部专家审查。
|
|
48
45
|
|
|
49
46
|
## Follow-up
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
完整 follow-up 包含 Git/PR 交付,使用 Codex `spawn_agent` 的 `worker` 角色在后台承接;Claude 技能只负责上表中的专家阶段。prompt 指定 worktree 绝对路径并要求调用 `oo-ship-issue-pr`。明确告知 worker 它不是代码库中唯一执行者,不回退其他人的改动,只在指定 worktree 工作。派发深度只允许一层;worker 发现的新 follow-up 只建 Issue。
|