🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Issue 到 PR 流水线 LONGSASASASASA/dsh-issue2pr

DeepSeek Harnessv0.4.0
LONGSASASASASA48 天前有提交已提供中文文档扫描:含敏感能力

把一条 Issue 拆成十一段可审查的交付证据链

30.7/100
综合分
1
GitHub 星标
191
npm 周下载
—/5
用户评分

数据截至 2026/9/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-issue2pr
npm 源 · dsh-issue2pr 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 48 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-issue2pr @ 0.4.0
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 15:08:03

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 150 处,展示前 117 条)
  • 读取本地文件
    index.js:121
    if (existsSync(join(repoDir, ".git"))) return Promise.resolve(repoDir);
  • 写入本地文件
    index.js:123
    mkdirSync(repoDir, { recursive: true });
  • 执行外部命令固定命令
    index.js:133
    execFile("git", ["clone", "--depth", "1", credential.uri, repoDir], {
  • 读取本地文件
    index.js:174
    const out = JSON.parse(readFileSync(join(runDir, "09-failure-analysis.json"), "utf8"));
  • 读取本地文件
    index.js:407
    if (!existsSync(projectsDir)) return;
  • 读取本地文件
    index.js:408
    for (const slug of readdirSync(projectsDir)) {
  • 读取本地文件
    index.js:410
    if (!existsSync(runsDir)) continue;
  • 读取本地文件
    index.js:411
    for (const id of readdirSync(runsDir)) {
  • 发起网络请求
    index.js:523
    const resp = await fetch(api, {
  • 执行外部命令固定命令
    index.js:539
    const runGit = __testHooks?.runGit || ((args, opts, cb) => execFile("git", args, opts, cb));
  • 执行外部命令固定命令
    index.js:633
    const runGit = __testHooks?.runGit || ((args, opts, cb) => execFile("git", args, opts, cb));
  • 执行外部命令来自配置/变量
    index.js:635
    || ((args, opts, cb) => execFile(process.platform === "win32" ? "where" : "which", args, opts, cb));
  • 发起网络请求
    client.js:910
    return fetch(API + path).then(function (r) { return r.json().catch(function () { return {}; }); });
  • 发起网络请求
    client.js:913
    return fetch(API + path, {
  • 发起网络请求
    client.js:920
    return fetch(API + path, { method: "DELETE" })
  • 发起网络请求
    client.js:924
    return fetch(API + path, {
  • 发起网络请求
    client.js:3523
    fetch(API + "/ui-state", {
  • 发起网络请求
    client.js:3636
    const r = await fetch(API + "/assistant/ask", {
  • 写入本地文件
    lib/core/store.js:53
    mkdirSync(projectDir(root, norm.slug), { recursive: true });
  • 写入本地文件
    lib/core/store.js:55
    writeFileSync(tmp, JSON.stringify({ ...norm, updatedAt: new Date().toISOString() }, null, 2));
  • 写入本地文件
    lib/core/store.js:56
    renameSync(tmp, projectFile(root, norm.slug));
  • 读取本地文件
    lib/core/store.js:60
    if (!existsSync(projectFile(root, slug))) return null;
  • 读取本地文件
    lib/core/store.js:61
    return JSON.parse(readFileSync(projectFile(root, slug), "utf8"));
  • 读取本地文件
    lib/core/store.js:66
    if (!existsSync(dir)) return [];
  • 读取本地文件
    lib/core/store.js:67
    return readdirSync(dir).flatMap((slug) => {
  • 读取本地文件
    lib/core/store.js:77
    if (!existsSync(file)) return {};
  • 读取本地文件
    lib/core/store.js:78
    try { return JSON.parse(readFileSync(file, "utf8")) || {}; }
  • 写入本地文件
    lib/core/store.js:84
    writeFileSync(tmp, JSON.stringify(state, null, 2));
  • 写入本地文件
    lib/core/store.js:85
    renameSync(tmp, join(root, "ui-state.json"));
  • 写入本地文件
    lib/core/store.js:103
    mkdirSync(join(runDir, sub), { recursive: true });
  • 删除本地文件
    lib/core/store.js:121
    try { rmSync(tmp, { force: true }); } catch { }
  • 删除本地文件递归
    lib/core/store.js:176
    rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 120 });
  • 读取本地文件
    lib/core/pipeline.js:36
    return existsSync(runFile(runDir)) ? JSON.parse(readFileSync(runFile(runDir), "utf8")) : null;
  • 读取本地文件
    lib/core/pipeline.js:36
    return existsSync(runFile(runDir)) ? JSON.parse(readFileSync(runFile(runDir), "utf8")) : null;
  • 读取本地文件
    lib/core/pipeline.js:48
    if (existsSync(join(runDir, "06-implementation", "coder-report.json"))) return true;
  • 读取本地文件
    lib/core/pipeline.js:50
    if (!existsSync(dir)) return false;
  • 读取本地文件
    lib/core/pipeline.js:51
    return readdirSync(dir).some((f) => f.endsWith(".diff"));
  • 写入本地文件
    lib/core/pipeline.js:64
    mkdirSync(join(runDir, "trace"), { recursive: true });
  • 写入本地文件
    lib/core/pipeline.js:65
    appendFileSync(join(runDir, "trace", "spans.jsonl"), JSON.stringify({ at: new Date().toISOString(), ...span }) + "\n");
  • 执行外部命令固定命令
    lib/delegate/delegateVerify.js:23
    const child = execFile("git", args, {
  • 读取本地文件
    lib/delegate/delegateVerify.js:48
    if (existsSync(reportPath)) {
  • 读取本地文件
    lib/delegate/delegateVerify.js:49
    try { JSON.parse(readFileSync(reportPath, "utf8")); }
  • 读取本地文件
    lib/delegate/delegateVerify.js:74
    if (!rcx.repoDir || !existsSync(rcx.repoDir)) {
  • 删除本地文件递归
    lib/delegate/delegateVerify.js:110
    if (tmp) { try { rmSync(tmp, { recursive: true, force: true }); } catch { } }
  • 执行外部命令来自配置/变量
    lib/stages/p7-patch.js:14
    const child = execFile(cmd, args, { ...opts, stdio: ["pipe", "pipe", "pipe"], maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
  • 读取本地文件
    lib/stages/p7-patch.js:48
    if (existsSync(reportPath)) {
  • 读取本地文件
    lib/stages/p7-patch.js:50
    try { report = JSON.parse(readFileSync(reportPath, "utf8")); }
  • 读取本地文件
    lib/stages/p7-patch.js:58
    if (!existsSync(dir)) return [];
  • 读取本地文件
    lib/stages/p7-patch.js:59
    return readdirSync(dir).filter((f) => f.endsWith(".diff")).sort()
  • 读取本地文件
    lib/stages/p7-patch.js:129
    if (!existsSync(file)) throw new Error("无法回滚:patch ledger 不存在");
  • 读取本地文件
    lib/stages/p7-patch.js:130
    const lines = readFileSync(file, "utf8").split(/ ? /).filter((line) => line.trim());
  • 执行外部命令固定命令
    lib/delegate/executors/claude-code.js:45
    spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true });
  • 读取本地文件
    lib/delegate/executors/claude-code.js:70
    return claudeCommonCandidates().find((p) => existsSync(p)) || "claude";
  • 执行外部命令来自配置/变量
    lib/delegate/executors/claude-code.js:83
    child = spawn(bin, args, { cwd: repoDir, shell: true, windowsHide: true, env: env || process.env });
  • 写入本地文件
    lib/delegate/executors/claude-code.js:165
    const log = logPath ? createWriteStream(logPath, { flags: "a" }) : null;
  • 读取本地文件
    lib/infra/relayAuth.js:23
    const j = JSON.parse(readFileSync(relayFile(root), "utf8"));
  • 删除本地文件
    lib/infra/relayAuth.js:36
    try { unlinkSync(relayFile(root)); } catch { }
  • 读取本地文件
    lib/infra/relayAuth.js:51
    const j = JSON.parse(readFileSync(relayFile(root), "utf8"));
  • 执行外部命令来自配置/变量
    lib/delegate/agents.js:31
    try { child = spawn(bin, args, { shell: true, windowsHide: true }); }
  • 执行外部命令来自配置/变量
    lib/delegate/agents.js:59
    try { child = spawn(bin, args, { shell: true, windowsHide: true, env: { ...process.env, ...claudeAuthEnv(opts && opts.auth) } }); }
  • 执行外部命令来自配置/变量
    lib/delegate/agents.js:73
    execFile(process.platform === "win32" ? "where" : "which", args, { ...opts, windowsHide: true }, cb);
  • 执行外部命令固定命令
    lib/delegate/agents.js:78
    execFile("npm", args, { ...opts, shell: process.platform === "win32" }, cb);
  • 读取本地文件
    lib/delegate/agents.js:91
    agents.push({ path: t, source, label: AGENT_SOURCE_LABELS[source] || source, exists: existsSync(t) });
  • 读取本地文件
    lib/delegate/agents.js:142
    if (!/^claude( cmd| exe)?$/i.test(target) || existsSync(target)) {
  • 读取本地文件
    lib/delegate/agents.js:143
    if (existsSync(target)) detail = "已定位 " + target;
  • 读取密钥环境变量
    lib/stages/helpers.js:38
    const ghToken = (ghConn && ghConn.kind === "github" && ghConn.token) || process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
  • 请求外部主机api.github.com
    lib/stages/helpers.js:41
    resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${num}`, {
  • 发起网络请求
    lib/stages/helpers.js:41
    resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${num}`, {
  • 请求外部主机api
    lib/stages/helpers.js:65
    resp = await fetch(`https://${glHost}/api/v4/projects/${encodeURIComponent(ns + "/" + name)}/issues/${glNum}`, {
  • 发起网络请求
    lib/stages/helpers.js:65
    resp = await fetch(`https://${glHost}/api/v4/projects/${encodeURIComponent(ns + "/" + name)}/issues/${glNum}`, {
  • 发起网络请求
    lib/stages/helpers.js:78
    const resp = await fetch(uri, { headers: { "User-Agent": "dsh-issue2pr" } });
  • 读取本地文件
    lib/stages/helpers.js:82
    if (!existsSync(uri)) throw new Error("触发文档不存在: " + uri);
  • 读取本地文件
    lib/stages/helpers.js:83
    return readFileSync(uri, "utf8");
  • 读取本地文件
    lib/stages/helpers.js:90
    if (out.length >= max || !existsSync(dir)) return;
  • 读取本地文件
    lib/stages/helpers.js:91
    for (const name of readdirSync(dir)) {
  • 读取本地文件
    lib/stages/helpers.js:94
    if (statSync(full).isDirectory()) walk(full);
  • 读取本地文件
    lib/stages/helpers.js:106
    if (!existsSync(full)) return `<文件不存在: ${rel}>`;
  • 读取本地文件
    lib/infra/connections.js:51
    if (!existsSync(file)) return [];
  • 读取本地文件
    lib/infra/connections.js:53
    const raw = JSON.parse(readFileSync(file, "utf8"));
  • 读取本地文件
    lib/core/stageConfig.js:331
    return existsSync(dir) && readdirSync(dir).some((f) => f.endsWith(ext));
  • 读取本地文件
    lib/core/stageConfig.js:331
    return existsSync(dir) && readdirSync(dir).some((f) => f.endsWith(ext));
  • 删除本地文件递归
    lib/core/stageConfig.js:341
    try { rmSync(join(runDir, rel), { force: true, recursive: true }); removed.push(rel); } catch { }
  • 读取本地文件
    lib/core/stageConfig.js:346
    if (existsSync(dir)) {
  • 读取本地文件
    lib/core/stageConfig.js:347
    for (const f of readdirSync(dir)) if (f.endsWith(".diff")) kill("06-implementation/patches/" + f);
  • 执行外部命令固定命令
    lib/infra/repoState.js:13
    execFile("git", args, { cwd, timeout: timeoutMs, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
  • 读取本地文件
    lib/infra/repoState.js:39
    if (existsSync(join(wt, ".git"))) return wt;
  • 读取本地文件
    lib/infra/repoState.js:40
    if (!existsSync(join(base, ".git"))) return base;
  • 读取本地文件
    lib/infra/repoState.js:56
    if (!existsSync(wt) || !existsSync(join(base, ".git"))) return;
  • 读取本地文件
    lib/assistant.js:34
    if (existsSync(dir)) patches = readdirSync(dir).filter((f) => f.endsWith(".diff")).length;
  • 读取本地文件
    lib/assistant.js:34
    if (existsSync(dir)) patches = readdirSync(dir).filter((f) => f.endsWith(".diff")).length;
  • 读取本地文件
    lib/assistant.js:36
    try { tasks = JSON.parse(readFileSync(join(runDir, "05-task-graph.json"), "utf8")).nodes.length; } catch { }
  • 读取本地文件
    lib/assistant.js:37
    return { patches, tasks, report: existsSync(join(runDir, "06-implementation", "coder-report.json")) };
  • 读取本地文件
    lib/assistant.js:43
    if (!existsSync(runsDir)) return null;
  • 读取本地文件
    lib/assistant.js:44
    const ids = readdirSync(runsDir).filter((id) => existsSync(join(runsDir, id, "run.json"))).sort();
  • 执行外部命令固定命令
    lib/infra/patchEvidence.js:24
    const child = execFile("git", args, {
  • 删除本地文件递归
    lib/infra/patchEvidence.js:119
    try { rmSync(tempDir, { recursive: true, force: true }); } catch { }
  • 读取本地文件
    lib/infra/patchEvidence.js:141
    if (!existsSync(rcx.repoDir)) {
  • 读取本地文件
    lib/stages/p6-coder.js:19
    return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith(".diff")).length : 0;
  • 读取本地文件
    lib/stages/p6-coder.js:19
    return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith(".diff")).length : 0;
  • 读取本地文件
    lib/stages/p6-coder.js:92
    const latest = existsSync(patchesAbs) ? readdirSync(patchesAbs).filter((f) => f.endsWith(".diff")).sort().slice(-1)[0] : "";
  • 读取本地文件
    lib/stages/p6-coder.js:92
    const latest = existsSync(patchesAbs) ? readdirSync(patchesAbs).filter((f) => f.endsWith(".diff")).sort().slice(-1)[0] : "";
  • 读取本地文件
    lib/stages/p8-test-runner.js:12
    if (existsSync(pkg) && JSON.parse(readFileSync(pkg, "utf8")).scripts?.test) return "npm test";
  • 读取本地文件
    lib/stages/p8-test-runner.js:12
    if (existsSync(pkg) && JSON.parse(readFileSync(pkg, "utf8")).scripts?.test) return "npm test";
  • 执行外部命令来自配置/变量
    lib/stages/p8-test-runner.js:22
    exec(command, { cwd, encoding: "utf8", timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024,
  • 写入本地文件
    lib/delegate/executors/claude-auth.js:80
    writeFileSync(path, JSON.stringify(settings), { mode: 0o600 });
  • 删除本地文件
    lib/delegate/executors/claude-auth.js:86
    try { rmSync(path, { force: true }); } catch { }
  • 写入本地文件
    lib/infra/secretStore.js:30
    try { chmodSync(path, mode); } catch { }
  • 写入本地文件
    lib/infra/secretStore.js:34
    mkdirSync(path, { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/infra/secretStore.js:55
    writeFileSync(tmp, String(content), { encoding: "utf8", mode });
  • 写入本地文件
    lib/infra/secretStore.js:57
    renameSync(tmp, target);
  • 读取本地文件
    lib/infra/secretStore.js:60
    if (existsSync(tmp)) {
  • 删除本地文件
    lib/infra/secretStore.js:61
    try { unlinkSync(tmp); } catch { }
  • 读取本地文件
    lib/infra/secretStore.js:238
    const warning = existsSync(path) ? this.fallbackPresentWarning() : "";
  • 读取本地文件
    lib/infra/secretStore.js:243
    if (!existsSync(path)) return null;
  • 读取本地文件
    lib/infra/secretStore.js:245
    const value = readFileSync(path, "utf8");
  • 读取本地文件
    lib/infra/secretStore.js:276
    if (existsSync(oldPath)) {
  • 读取本地文件
    lib/infra/secretStore.js:309
    if (existsSync(path)) {
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
涉及外部主机:api.github.com、api

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-settings
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 分段输入契约
  • 可独立审查产物

从一条 Issue 到一份被合并的 PR,每一段都有自己的输入契约、失败信号、可回滚产物与可独立审查的 Artifact。

README

由 DeepSeek 最新模型翻译生成
从一条 Issue 到一份被合并的 PR,中间不能跳步。 每一段路径都有自己的输入契约、失败信号、可回滚产物与可独立审查的 Artifact。 dsh-issue2pr 是一个运行在 DSH 宿主里的全局插件:把一条 GitHub / GitLab Issue(或本地需求文档)交给一条 11 阶段流水线,经过检索、理解、诊断、规划、多智能体编码、真实测试、三维审查与 Gate 评测,最终产出一份忠实反映修改与验证过程的 PR 说明——以及一整条可打回、重跑、回滚与追溯的证据链。 它不是又一个「一把梭」的 coding agent。真正的交付物不是 PR 文本,而是一份可审查的系统:每段产出有编号产物落盘,每次状态变更有 Trace,每个关键节点有人工复核门。 ✨ 特性总览 - 🔗 11 阶段全链路:Issue 分析 → 检索 → 代码理解 → 根因假设 → 任务规划 → 编码 → 补丁管线 → 测试 → 审查 → PR 构建,一步不省。 - 🚦 人工复核门:P5 / P6 / P9 / P11 四道门控,approve 放行、reject 带意见打回;复核模式可选每阶段 / 仅关键 / 全自动。委托阶段放行前必须通过机器验证(产物完整 + 对 HEAD 基线可应用),全自动模式同样先验证再放行——不空手放行、不带病放行。
展开完整文档(README 剩余 1 节)
从一条 Issue 到一份被合并的 PR,中间不能跳步。
每一段路径都有自己的输入契约、失败信号、可回滚产物与可独立审查的 Artifact。

dsh-issue2pr 是一个运行在 DSH 宿主里的全局插件:把一条 GitHub / GitLab Issue(或本地需求文档)交给一条 11 阶段流水线,经过检索、理解、诊断、规划、多智能体编码、真实测试、三维审查与 Gate 评测,最终产出一份忠实反映修改与验证过程的 PR 说明——以及一整条可打回、重跑、回滚与追溯的证据链。

它不是又一个「一把梭」的 coding agent。真正的交付物不是 PR 文本,而是一份可审查的系统:每段产出有编号产物落盘,每次状态变更有 Trace,每个关键节点有人工复核门。

✨ 特性总览

- 🔗 11 阶段全链路:Issue 分析 → 检索 → 代码理解 → 根因假设 → 任务规划 → 编码 → 补丁管线 → 测试 → 审查 → PR 构建,一步不省。
- 🚦 人工复核门:P5 / P6 / P9 / P11 四道门控,approve 放行、reject 带意见打回;复核模式可选每阶段 / 仅关键 / 全自动。委托阶段放行前必须通过机器验证(产物完整 + 对 HEAD 基线可应用),全自动模式同样先验证再放行——不空手放行、不带病放行。
- 🧾 产物证据链:每个阶段落盘编号产物(01-issue-analysis.json … 11-eval-report.json),输入来自上游、输出去往下游契约,可独立审查、可打回重做。
- 🔁 可回滚:Patch Pipeline 带台账(ledger),只撤销 Agent 引入的修改,不碰用户自己的代码。
- 🧪 真实测试:结果必须来自真实工具执行——在克隆的仓库里跑真实测试命令,完整输出落盘可回溯。
- 🤝 多智能体 + 外部委托:P6 内置 Planner / Coder / Reviewer 协同;也可把任务包委托 DSH 会话、Claude Code CLI 或 DSH 原生智能体执行。
- 🖥️ 工作台 UI:项目 / 运行 / 产物 / 配置 / 说明五页 + 悬浮智能助手(实时上下文问答、流式输出、可拖拽缩放)。
- 🔌 Git 托管连接:GitHub / GitLab / 华为云 CodeArts 凭据全局共享,克隆与 Issue 抓取自动注入;带探活与仓库连通性测试。
- 🧭 逐阶段配置:每个阶段可单独改提示词、换模型、调超时与专属参数,改完下一阶段即生效。
- 📡 全程可观测:trace/events.jsonl 记录每次 LLM 调用、git 操作、测试执行的开始 / 完成 / 失败。

🧭 工作原理

一次 Run 就是一条证据链:从触发源读入 Issue 原文,主流程 P1→P9→P11 依次推进、每步落盘产物;任何阶段失败立即停下进入 P10 分类旁路;到复核门时等待人工 approve / reject,打回意见会传回该阶段重新执行(委托阶段的旧外部产物同时清场;P7 应用补丁前会把工作区重置回基线,不残留上一轮补丁)。

阶段一览

| 阶段 | 名称 | 职责 | 落盘产物 | 复核门 |
| P1 | IssueAnalyzer | 自然语言 Issue → 结构化契约:现象 / 触发条件 / 影响范围 / 可验证成功标准 / 风险等级 | 01-issue-analysis.json | |
| P2 | Search Layer | 按文件清单选候选文件,附理由与置信度 | 02-search-candidates.json | |
| P3 | 代码理解 | 深读候选源码,输出关键函数、调用链与修改点(路径:行号 锚点) | 03-code-understanding.md | |
| P4 | 假设 | 可验证根因假设:每条带证据、验证文件与验证方法 | 04-hypotheses.json | |
| P5 | 规划器 | 拆解为有依赖的 TaskGraph,节点可独立验证 | 05-task-graph.json | ✅ |
| P6 | 代码优化 | 多智能体协同,或委托外部执行器(见下) | 06-implementation/patches/.diff | ✅ |
| P7 | 补丁流水线 | diff 版本校验 → 落盘,写 patch 台账(回滚依据) | ledger/patch-ledger.jsonl | |
| P8 | 测试运行器 | 真实仓库执行测试命令,完整输出落盘 | 07-test-report.json | |
| P9 | 审查器 | 三维门控:① Diff 范围 ② API 与安全 ③ 测试补强与说明忠实——测试通过 ≠ 可合并 | 08-review-report.json | ✅ |
| P10 | 失败分类器 | 六类归因(实现 / 根因 / 测试选择 / 环境 / 权限 / 反复失败)→ 重跑 / 回滚 / 转人工 | 09-failure-analysis.json | 仅失败时 |
| P11 | PR 构建器 + 评测 | 忠实 PR 说明 + Gate 六项评测 | 10-pr-description.md · 11-eval-report.json | ✅ |

五条不变命题

从 Issue 到 PR 这条链上,无论 Agent 如何演进,这 5 条不能变。

| 命题 | 含义 |
| 不可跳步 | 分析、检索、理解、规划、修改、测试、Review、评测,省了任何一步都不能称为「交付」。 |
| 可定位 | 任何失败都必须落到具体模块;没有归属的失败不允许简单重跑。 |
| 可回滚 | Agent 引入的修改必须可逆,且能区分「用户改的」与「Agent 改的」。 |
| 可审查 | Patch、测试日志、PR 说明与评测报告——每一份都要能被打回重做。 |
| 可复现 | 全链路 Trace 从 Issue 贯穿到 PR;没有 Trace,PR 的生成过程无法解释。 |

两种关键模式

复核模式 reviewMode——哪些阶段停下来等人:

| 取值 | 行为 |
| every | 每个阶段完成后都进入复核门 |
| key-only | 仅 P5 / P6 / P9 / P11 停(推荐) |
| auto | 全自动推进(委托阶段仍需产物验证,见下) |

P6 执行模式 p6Mode——代码由谁写:

| 取值 | 行为 |
| builtin | 内置多智能体:Planner 派单 → 并行 Coder(TDD / 最小 diff 纪律)→ Reviewer 门控 |
| session | 生成任务包交给 DSH 会话人工执行,完成并通过复核门放行 |
| claude | 任务包自动委托 Claude Code CLI 无人值守执行,失败回退等人工会话 |
| dsh | 任务包交给 DSH 宿主原生智能体(ctx.agents)执行,零外部认证 |

委外产物验证

委托阶段(session / claude / 各阶段委托开关)的放行不看「文件存在」,看「产物可用」,验证分两层:

1. 结构完整:补丁清单与 P7 应用清单同口径;逐份补丁存在、非空、形如 unified diff;coder-report.json 可解析且清单与文件一致。
2. 应用性演练:用一次性 git 索引从 HEAD 构建基线,按应用序逐份 git apply --cached 演练——不碰工作区,语义与 P7 完全一致;对基线不可应用的补丁在此拦截。

三条放行路径同一口径:人工门 approve 必须验证通过;全自动(auto)插件轮询产物就绪(默认 5s)自动验证,通过即放行并落 auto-approve 审计记录,连续 3 次不过则 Run 显式失败并走 P10 归因;advance 直通(如 claude 同步执行完)验证不过就地失败——绝不把坏补丁带进 P7。

委外智能体:发现 + 测试门禁

选 claude 模式后,项目页与配置页出现「委外智能体」绑定卡:

- 多方式发现(去重合并):项目配置 > 环境变量 ISSUE2PR_CLAUDE_BIN > 常见安装位置 > npm config get prefix 全局目录 > PATH;也可手动指定完整路径。
- 测试门禁:保存前真实跑一次极小调用,三步全绿(定位 → --version → 认证微任务)才能保存——403 IP access denied by API-Key restrictions、未登录这类认证错误在绑定时就拦截并给出处置提示,而不是等 Run 走到 P6 才失败。微任务仅一轮、几十 token。
- 改过绑定路径后门禁结果即失效需重测;服务端保存时另有 --version 级快检兜底。

🚀 快速开始

环境要求

- DSH 宿主(插件随 dsh web 同生共死)、Node.js ≥ 18、git
- 可选:Claude Code CLI(仅 p6Mode: "claude" 需要)
- LLM:优先用 DSH 宿主当前默认模型,也可逐阶段覆盖

安装

已发布到 npm(推荐——预构建包,无需授权构建脚本):

dsh plugin --profile web add dsh-issue2pr

或从源码安装——克隆进 DSH 插件目录后重启 DSH:

Windows: C:\Users\\.dsh\plugins\ · macOS / Linux: ~/.dsh/plugins/
cd ~/.dsh/plugins
git clone https://github.com/LONGSASASASASA/dsh-issue2pr.git dsh-issue2pr
也可用 dsh-plugin-manager 插件管理,或审阅源码后用 dsh plugin --profile web add github:LONGSASASASASA/dsh-issue2pr# 锁定 commit 安装。数据与插件目录分离(见数据布局),升级插件不丢数据。

五分钟跑通第一单

1. 建项目:DSH 侧边栏「Issue2PR」→ 项目页 → 新建,填名称、slug、仓库地址;触发源用 Issue 链接或本地需求文档路径。
2. 配连接(私有仓库需要):「Git 托管连接」添加凭据并点「测试」探活;公开仓库与 GITHUB_TOKEN 环境变量可直接用。
3. 发起 Run:粘贴 Issue 链接或选本地需求文档,插件自动浅克隆主仓库并开始推进。
4. 盯运行、过复核门:运行页看阶段进度与事件流;复核门查看产物,approve 放行或 reject 带意见打回。
5. 拿 PR 说明:P11 通过后打开 10-pr-description.md——背景、根因、修改点、验证证据、风险齐全,据此开 PR;11-eval-report.json 是 Gate 六项自评。

环境不对劲?项目页的 preflight 健康探测会显示 git 版本、claude CLI 是否就绪、默认模型从哪来。

⚙️ 配置详解

项目配置(project.json)

{
"name": "我的项目", "slug": "my-project", // 小写字母/数字/连字符
"repos": ["https://github.com/me/my-project.git"],
"triggers": [
{ "kind": "issue", "uri": "https://github.com/me/my-project/issues/42" },
{ "kind": "requirement", "uri": "C:/docs/需求-登录修复.md" } ], "reviewMode": "key-only", // every | key-only | auto "p6Mode": "builtin", // builtin | session | claude | dsh "testCommand": "npm test", // 留空则自动探测 package.json 的 test 脚本
"stageConfig": { / 逐阶段覆盖,见下 / }
}

逐阶段配置(stageConfig)

每阶段可独立覆盖,未配置项回落默认值,改动在下一阶段即时生效:

"stageConfig": {
"P3": {
"provider": "deepseek-official", // 本阶段换模型 "model": "deepseek-v4-pro", "reasoningEffort": "high", "timeoutMs": 600000, "maxTokens": 16384,
"prompts": { "": "你是 Code Understanding……(覆盖默认提示词)" },
"params": { "deepReadFiles": 10, "fileChars": 8000 }
},
"P6": {
"delegate": { "mode": "session" }, // 本阶段委托外部智能体,产出就绪才放行
"params": {
"claudeBin": "C:\\Users\\you\\AppData\\Roaming\\npm\\claude.cmd",
"claudeTimeoutMin": 120,
"claudeAuthPreset": "glm", // none | glm | custom(认证中转,403 根治) "claudeRelayModel": "glm-5.3", // glm 预设的驱动模型 "claudePermission": "acceptEdits", // acceptEdits | bypass | dontAsk "dshTimeoutMin": 120 } },
"P9": { "params": { "diffChars": 2000 } }
}

阶段专属参数(repoScanMax、deepReadFiles、diffChars、claudeBin 等)在「配置」页有中文说明与默认值——凡影响执行行为的数值与路径都不硬编码,全部可被项目配置覆盖。

Git 托管连接

| 类型 | 凭据 | 说明 |
| GitHub | Access Token | 克隆注入 x-access-token;Issue 抓取与探活走 /user |
| GitLab | Personal Access Token | 支持自建实例(自定义 host);注入 oauth2 |
| 华为云 CodeArts | HTTPS 密码 + 用户名 | 凭 git ls-remote 真实仓库测试连通 |

- 全局共享一份(~/.dsh/issue2pr/connections.json),多项目复用;ssh 地址走本机密钥,不注入。Run 进行中改连接,下一阶段即生效。
- 凭据不落 connections.json:SecretStore 按 keychain-first 工作(macOS Keychain / Linux Secret Service;Windows 构建无内置 Credential Manager 适配器,明确降级到 /secrets/ 非加密文件,仅尝试 0700/0600 权限收紧,不保证 ACL 隔离)。UI 一律只显示掩码,事件日志自动抹除 URL 凭据段;旧版明文 token 首次读取时自动迁移,密钥环不可用时显式警告「非加密文件回退」。
- Git clone / ls-remote 的 URI/argv 不携带 token,认证头仅注入子进程环境;同机可读进程环境的用户应视为可信。

委外执行器(P6 自动执行通道) P6「自动执行」有两个执行器,同一套任务包契约(session-task.md → patches/.diff + coder-report.json)、同一套产物机器验证,按项目自由切换:

| | claude-code(委托 Claude Code) | dsh-agent(DSH 原生智能体) |
| 形态 | 本机 claude CLI 子进程(-p headless + stream-json) | 宿主内置 agent loop 进程内执行(ctx.agents) |
| 模型 | 本机 claude 认证(可走中转) | 宿主模型路由(Models 页) |
| 外部认证 | 需要(CLI 登录态 / 中转 token) | 零外部认证 |
| 403 IP 白名单 | 配认证中转后根治 | 天然免疫 |
| 依赖 | claude CLI ≥ 2.1(推荐 ≥ 2.1.140) | DSH 宿主提供 ctx.agents(完全重启 DSH 生效) |

两者都先过测试门禁才能保存:claude 三步(定位 → 版本 → 认证),dsh 两步(智能体服务 + 模型路由 → 真实微任务)。

认证中转(403 根治):公司网络出口漂移会让带 IP 白名单的 API Key 间歇 403。绑定卡「认证中转」选 GLM Coding Plan 并保存 token 后,claude 改打 https://open.bigmodel.cn/api/anthropic(官方 Anthropic 兼容端点,无 IP 白名单校验)。因用户级 ~/.claude/settings.json 的 env 块会覆盖进程环境变量,中转经 --settings 临时文件注入(连 ANTHROPIC_MODEL 等模型映射一并覆盖,避免残留模型名报错);token 由同一套 SecretStore 管理(relay-auth.json 只存 secretRef),临时文件用后即删、日志零 token。也可选「自定义网关」指向自建 claude-code-router / new-api。

权限档位:claude 执行默认 acceptEdits + 宽白名单,替代裸 --dangerously-skip-permissions;claudePermission 可回退 bypass 或收紧 dontAsk。

执行可靠性:stream-json 逐帧解析(is_error / 0-token 空结果显式判失败);stdout 实时落盘 external-exec.log;超时杀整棵进程树;session_id 落档 run.json(为 --resume 留钩)。

📦 数据与产物布局

所有数据集中在 ~/.dsh/issue2pr/,一次 Run 一个目录 = 一条完整证据链:

~/.dsh/issue2pr/
├─ connections.json # Git 托管连接元数据 + secretRef(不含 token) ├─ relay-auth.json # claude 中转元数据 + secretRef(不含 token) ├─ secrets/ # 仅 keychain 不可用时使用;非加密 │ └─ .secret ├─ ui-state.json # UI 选中记忆兜底(宿主重启不丢) └─ projects/ └─ / ├─ project.json # 项目配置(含 stageConfig) ├─ repo/ # git clone --depth 1 的主仓库 └─ runs// # 一次 Run 一个目录 ├─ run.json # 状态机唯一事实源(每步落盘) ├─ 01-issue-analysis.json # P1 … P11 的编号产物 ├─ 02-search-candidates.json ├─ 03-code-understanding.md ├─ 04-hypotheses.json ├─ 05-task-graph.json ├─ 06-implementation/ # patches/.diff + coder-report.json ├─ ledger/patch-ledger.jsonl # P7 台账:rollback 的依据 ├─ 07-test-report.json # + 08-test-output.txt(完整测试输出) ├─ 08-review-report.json ├─ 09-failure-analysis.json # 仅失败路径 ├─ 10-pr-description.md # + 11-eval-report.json(Gate 六项) ├─ reviews/.json # 每一次复核决定(approve/reject + 意见) └─ trace/ # events.jsonl + spans.jsonl 全程可观测

repo/ 是基线克隆,worktrees/ 是每个 Run 的独立工作区(同项目并发 Run 互不污染,Run 删除时一并清理)。

🖥️ 界面一览

| 页面 | 你在这里做什么 |
| 项目 | 新建 / 编辑项目、仓库、触发源、复核模式,管理连接,发起 Run |
| 运行 | 阶段进度、逐阶段事件流(LLM / git / 测试)、复核门审批、停止 / 回退重跑 / 回滚 / 删除 |
| 产物 | 按 Run 浏览产物树,逐文件预览(200KB 内) |
| 配置 | 逐阶段编辑提示词、模型路由、超时、专属参数与委托开关 |
| 说明 | 内置使用说明与阶段速查 |

右上角悬浮智能助手:读取当前页面 / 项目 / Run 作为实时上下文,流式回答「跑到哪了、为什么失败」,面板可拖动缩放、跨重启记忆位置。
🔌 REST API 一览

插件随宿主注册在 /issue2pr 前缀下,UI 之外也可脚本调用:

| 方法 | 路径 | 用途 |
| GET | /issue2pr/api/ping | 健康检查 |
| GET | /issue2pr/api/stage-defaults | 阶段能力表与默认提示词 |
| GET/POST | /issue2pr/api/ui-state | UI 偏好兜底存储 |
| GET/POST/DELETE | /issue2pr/api/connections[/:id] | Git 托管连接管理 |
| POST | /issue2pr/api/connections/test · test-repo | 凭据探活 / 真实仓库连通(git ls-remote) |
| GET/PUT/DELETE | /issue2pr/api/relay-auth | claude 认证中转配置(token 走 SecretStore) |
| GET | /issue2pr/api/preflight | 环境健康探测(git / claude CLI / LLM 路由来源) |
| GET | /issue2pr/api/agents/discover?slug= | 委外智能体多方式发现 |
| POST | /issue2pr/api/agents/test | 委外智能体测试门禁 |
| POST | /issue2pr/api/check-local | 本地触发源存在性 |
| POST | /issue2pr/api/assistant/ask | 悬浮助手问答(NDJSON 流式) |
| GET/POST | /issue2pr/api/projects | 项目列表 / 保存 |
| DELETE | /issue2pr/api/projects/:slug?confirm=slug | 删除项目(防误删确认) |
| GET/POST | /issue2pr/api/projects/:slug/runs | Run 列表 / 发起(同秒重复返回 409) |
| GET | .../runs/:runId | Run 详情(含外部执行进度) |
| POST | .../runs/:runId/stop · rerun · review · rollback · open | 停止 / 回退重跑 / 复核 / 回滚 / 打开产物目录 |
| DELETE | .../runs/:runId | 删除 Run |
| GET | .../runs/:runId/tree · artifact?path= | 产物树 / 读取单个产物 |

🧪 开发与测试

npm install
npm test # node --test,覆盖 API / 流水线 / 各阶段执行器 / 连接 / 存储

| 模块 | 职责 |
| index.js | REST API + 驱动循环(node 半,随宿主同生共死) |
| client.js | 工作台 UI(web 半) |
| lib/core/ | 流水线骨架:pipeline.js 状态机(advance / applyReview)、stageConfig.js 阶段能力表与委托任务包、store.js 目录规则与产物读写(唯一写盘点) |
| lib/stages/ | p1…p11 11 个阶段执行器(输入契约 → 产物落盘) |
| lib/infra/ | 运行设施:llm.js LLM 路由与契约解析、connections.js 托管连接与凭据脱敏、repoState.js 基线克隆与 per-Run worktree、secretStore.js / relayAuth.js / patchEvidence.js |
| lib/delegate/ | 委外体系:agents.js 发现与测试门禁、delegateVerify.js 产物机器验证、executors/(claude-code / dsh-agent 执行器) |
| lib/assistant.js | 智能助手上下文聚合 |
| tests/ | unit/ 单测 · stages/ 阶段执行器 · integration/ API 与流水线装配 · manual/ 人工演练(e2e-live.mjs 等不入 npm test) |

设计调研(端到端架构、Sub-Agent 取舍、缓存与记忆边界、评测指标)见 issue2pr-research.html;工作台视觉方案见 docs/design-taste.md 与 design-demo.html。

📌 当前边界

诚实清单——用之前先知道这些:

- 主仓库 --depth 1 浅克隆,当前按单主仓库工作(repos[0])。
- P10 只做失败分类与建议动作,不自动 replan;重跑 / 回滚由人工在运行页触发。
- 测试命令自动探测只认 package.json 的 test 脚本,其他语言请显式配置 testCommand。
- secrets/ fallback 不等于加密,Windows 上连 ACL 收紧都不保证;请按备份 / 管理员权限 / 同机进程可读风险保护整个数据目录,不要提交进任何仓库。
- 产物在线预览上限 200KB,更大的文件请在产物目录直接打开。
- claude 委托在宿主进程内无人值守执行(默认 acceptEdits + 宽白名单),请按仓库敏感度自行评估。
- 中转 token 与 Git 凭据共用 SecretStore;API 会显示当前是系统 keychain 还是非加密文件回退。

🤝 贡献

欢迎 Issue 与 PR!约定:

- 外科手术式改动:只动必须动的地方,不顺手重构。
- 行为变更先补测试(node --test),再动实现。
- 阶段执行器保持「输入契约 → 产物落盘」形态,不在阶段内私设全局状态。
- 提交信息用中文,格式参照既有历史(feat: / fix: / chore: …)。

📄 许可证

MIT © LONGSASASASASA
预览分享卡片长什么样 ↓LONGSASASASASA/dsh-issue2pr
分享 · DeepSeek Harness Hub
LONGSASASASASA/dsh-issue2pr

把一条 Issue 拆成十一段可审查的交付证据链

这是你的仓库?认领条目

回到顶部