dsh-issue2pr 0.1.0 → 0.3.1

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.
@@ -6,11 +6,11 @@ import { spawn } from "node:child_process";
6
6
  import { existsSync, readdirSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
- import { writeArtifact, readArtifact } from "../store.js";
9
+ import { writeArtifact, readArtifact } from "../core/store.js";
10
10
  import { requireArtifact, readRepoFile, logEvent } from "./helpers.js";
11
- import { slugify, timestamp } from "../store.js";
12
- import { saveRun, sessionPatchesReady } from "../pipeline.js";
13
- import { sysOf, paramsOf } from "../stageConfig.js";
11
+ import { slugify, timestamp } from "../core/store.js";
12
+ import { saveRun, sessionPatchesReady } from "../core/pipeline.js";
13
+ import { sysOf, paramsOf } from "../core/stageConfig.js";
14
14
 
15
15
  // —— claude CLI 进程管理(stop/删除 Run 时杀进程树,避免孤儿 claude 继续写仓库) ——
16
16
  const activeExternals = new Map(); // runDir → ChildProcess
@@ -29,15 +29,18 @@ export function killExternal(runDir) {
29
29
 
30
30
  // claude 可执行文件解析:阶段配置(params.claudeBin)> 环境变量 ISSUE2PR_CLAUDE_BIN > 常见安装位置探测。
31
31
  // 开源环境安装路径各异,UI「配置」可显式指定;PATH 里未必有 claude(如 npm 全局目录不在系统 PATH)。
32
- // preflight 端点复用本函数做健康探测(项目页显示 claude CLI 是否就绪)。
33
- export function resolveClaudeBin(cfgBin) {
34
- if (cfgBin) return cfgBin;
35
- if (process.env.ISSUE2PR_CLAUDE_BIN) return process.env.ISSUE2PR_CLAUDE_BIN;
32
+ // preflight 端点与 lib/agents.js 的发现/门禁复用本函数做健康探测。
33
+ export function claudeCommonCandidates() {
36
34
  const home = homedir();
37
- const cands = process.platform === "win32"
35
+ return process.platform === "win32"
38
36
  ? [join(home, ".npm-global", "claude.cmd"), join(home, ".npm_global", "claude.cmd"), join(home, "AppData", "Roaming", "npm", "claude.cmd"), join(home, ".local", "bin", "claude.exe")]
39
37
  : [join(home, ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"];
40
- return cands.find((p) => existsSync(p)) || "claude";
38
+ }
39
+
40
+ export function resolveClaudeBin(cfgBin) {
41
+ if (cfgBin) return cfgBin;
42
+ if (process.env.ISSUE2PR_CLAUDE_BIN) return process.env.ISSUE2PR_CLAUDE_BIN;
43
+ return claudeCommonCandidates().find((p) => existsSync(p)) || "claude";
41
44
  }
42
45
 
43
46
  // 无人值守跑 claude:-p headless;skip-permissions 无交互放行;--add-dir 允许写 run 产物目录(cwd 是仓库)。
@@ -168,7 +171,11 @@ async function delegateClaude(rcx, graph) {
168
171
  return { artifact: "06-implementation/", summary: "Claude Code 已执行任务包:" + countPatches(runDir) + " 份 patch + report,等待人工复核" + statsSummary(stats) };
169
172
  }
170
173
 
171
- const reason = r.error || (r.timeout ? "超时被终止" : "退出码 " + r.code + (r.stderr ? ":" + String(r.stderr).slice(0, 300) : ""));
174
+ // 认证类失败(403 IP 白名单 / 未登录)给出可操作引导——到项目页委外智能体卡片重跑门禁
175
+ const rawReason = r.error || (r.timeout ? "超时被终止" : "退出码 " + r.code + (r.stderr ? ":" + String(r.stderr).slice(0, 300) : ""));
176
+ const authHit = /40[13]|authenticat|api[- ]?key|ip access|not logged in/i.test(String(r.stderr || "") + "\n" + String(r.stdout || ""));
177
+ const reason = rawReason + (authHit
178
+ ? ";疑似认证/授权问题——请到「项目」页委外智能体卡片重跑测试门禁(检查 Claude 登录状态、API Key 的 IP 白名单或代理出口)" : "");
172
179
  setExec({ status: r.error ? "skipped" : "failed", exitCode: r.code, stats, error: String(reason).slice(0, 500), finishedAt: new Date().toISOString() });
173
180
  logEvent(rcx, { kind: "tool", name: "Claude Code 执行未产出补丁", detail: String(reason).slice(0, 500), ok: false });
174
181
  // 回退等人工:保持 external 语义(UI 显示"等外部执行"并拦截空产物 approve),人可接管 session-task.md
@@ -3,8 +3,9 @@
3
3
  import { readFileSync, existsSync, readdirSync } from "node:fs";
4
4
  import { execFile } from "node:child_process";
5
5
  import { join } from "node:path";
6
- import { appendArtifactLine } from "../store.js";
6
+ import { appendArtifactLine } from "../core/store.js";
7
7
  import { logEvent } from "./helpers.js";
8
+ import { resetRepoClean } from "../infra/repoState.js";
8
9
 
9
10
  // 带stdin输入的异步执行(git apply - 用 stdin 收 diff)
10
11
  function execStdin(cmd, args, opts, input) {
@@ -27,12 +28,15 @@ async function hashRepo(repoDir) {
27
28
  // patch 清单:优先 coder-report.json(builtin 写 patches 字段;claude 委托写 tasks 字段且
28
29
  // patch 路径相对 06-implementation/,这里统一归一化为相对 runDir 全路径);
29
30
  // report 缺失时退化为扫描目录(文件名序 = 应用序)。
30
- function collectPatches(runDir) {
31
+ // 导出供 delegateVerify 复用:验证清单 = P7 应用清单,同一口径。
32
+ export function collectPatches(runDir) {
31
33
  const normalize = (p) => !p ? null
32
34
  : { patch: p.startsWith("06-implementation/") ? p : "06-implementation/" + p.replace(/^\/+/, "") };
33
35
  const reportPath = join(runDir, "06-implementation", "coder-report.json");
34
36
  if (existsSync(reportPath)) {
35
- const report = JSON.parse(readFileSync(reportPath, "utf8"));
37
+ let report;
38
+ try { report = JSON.parse(readFileSync(reportPath, "utf8")); }
39
+ catch { throw new Error("coder-report.json 损坏(非法 JSON),无法解析补丁清单;请回退 P6 重跑或修复报告文件"); }
36
40
  const list = Array.isArray(report.patches) ? report.patches
37
41
  : Array.isArray(report.tasks) ? report.tasks.map((t) => ({ patch: t.patch }))
38
42
  : [];
@@ -47,6 +51,11 @@ function collectPatches(runDir) {
47
51
  export default async function execute(rcx) {
48
52
  const patches = collectPatches(rcx.runDir);
49
53
  if (!patches.length) throw new Error("P7 无 patch 可应用(06-implementation/patches/ 为空且无 coder-report.json)");
54
+ // A1/A2 修复:应用补丁前先把工作区重置回 HEAD 基线。
55
+ // 打回重跑/回退重跑时上一轮已应用的补丁仍在工作区(直接 re-apply 必失败);
56
+ // worktree 兜底共用基线 repo 的路径下同理清残留。reset 不碰 .gitignore 的目录(如 node_modules)。
57
+ await resetRepoClean(rcx.repoDir);
58
+ logEvent(rcx, { kind: "git", name: "工作区基线重置", detail: "P7 应用补丁前 git reset --hard + git clean -fd" });
50
59
  for (const p of patches) {
51
60
  const diff = readFileSync(join(rcx.runDir, p.patch), "utf8");
52
61
  const hashBefore = await hashRepo(rcx.repoDir);
@@ -3,9 +3,9 @@
3
3
  import { exec } from "node:child_process";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
- import { writeArtifact } from "../store.js";
6
+ import { writeArtifact } from "../core/store.js";
7
7
  import { logEvent } from "./helpers.js";
8
- import { DEFAULT_TEST_TIMEOUT_MS } from "../stageConfig.js";
8
+ import { DEFAULT_TEST_TIMEOUT_MS } from "../core/stageConfig.js";
9
9
 
10
10
  function detectCommand(repoDir) {
11
11
  const pkg = join(repoDir, "package.json");
@@ -1,7 +1,7 @@
1
1
  // lib/stages/p9-reviewer.js — 调研 §9:Review 是 PRBuilder 的前置门控
2
- import { writeArtifact, readArtifact } from "../store.js";
2
+ import { writeArtifact, readArtifact } from "../core/store.js";
3
3
  import { requireArtifact, maybeDelegate } from "./helpers.js";
4
- import { sysOf, paramsOf } from "../stageConfig.js";
4
+ import { sysOf, paramsOf } from "../core/stageConfig.js";
5
5
 
6
6
  export default async function execute(rcx) {
7
7
  const delegated = await maybeDelegate(rcx, "P9");
package/package.json CHANGED
@@ -1,25 +1,54 @@
1
1
  {
2
2
  "name": "dsh-issue2pr",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "description": "Issue-to-PR 可验证交付链:项目配置 → 11 阶段流水线 → 人工复核 → PR 说明(DSH 全局插件)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
- "exports": { ".": "./index.js", "./client": "./client.js", "./package.json": "./package.json" },
8
- "scripts": { "test": "node --test \"tests/**/*.test.js\"" },
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./client": "./client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "scripts": {
13
+ "test": "node --test \"tests/**/*.test.js\""
14
+ },
9
15
  "repository": {
10
16
  "type": "git",
11
17
  "url": "git+https://github.com/LONGSASASASASA/dsh-issue2pr.git"
12
18
  },
13
- "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "issue", "pull-request", "pipeline", "code-review", "cordis"],
19
+ "keywords": [
20
+ "dsh",
21
+ "dsh-plugin",
22
+ "deepseek-harness",
23
+ "issue",
24
+ "pull-request",
25
+ "pipeline",
26
+ "code-review",
27
+ "cordis"
28
+ ],
14
29
  "author": "LONGSASASASASA",
15
30
  "license": "MIT",
16
- "files": ["index.js", "client.js", "lib/", "cordis.patch.yml", "docs/assets/"],
17
- "publishConfig": { "registry": "https://registry.npmjs.org/" },
31
+ "files": [
32
+ "index.js",
33
+ "client.js",
34
+ "lib/",
35
+ "cordis.patch.yml",
36
+ "docs/assets/"
37
+ ],
38
+ "publishConfig": {
39
+ "registry": "https://registry.npmjs.org/"
40
+ },
18
41
  "dsh": {
19
- "bundle": { "patch": "./cordis.patch.yml" },
42
+ "bundle": {
43
+ "patch": "./cordis.patch.yml"
44
+ },
20
45
  "client": {
21
46
  "platform": "web",
22
- "inject": ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-settings"]
47
+ "inject": [
48
+ "@deepseek-ai/dsh-client-runtime",
49
+ "@deepseek-ai/dsh-client-locale",
50
+ "@deepseek-ai/dsh-client-ui-settings"
51
+ ]
23
52
  }
24
53
  },
25
54
  "peerDependencies": {
File without changes
File without changes
File without changes