@gavinc-cn/touchstone-dsh 0.1.0 → 0.1.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.
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/agents.py +0 -0
- package/board.py +348 -26
- package/chat.py +0 -0
- package/db.py +1 -1
- package/dsh-plugin/install.sh +0 -0
- package/dsh-plugin/lib/agent-driver.js +190 -42
- package/dsh-plugin/lib/index.js +181 -22
- package/dshdriver.py +0 -0
- package/dshevents.py +35 -3
- package/feishu.py +29 -8
- package/package.json +8 -3
- package/runner.py +11 -0
- package/server.py +46 -4
- package/sessparse.py +95 -1
- package/touchstone.py +2 -1
- package/webui/dist-plugin/assets/index-DtwKoi1T.js +473 -0
- package/webui/dist-plugin/index.html +1 -1
- package/worktree.py +100 -0
- package/webui/dist-plugin/assets/index-eHpxBfP6.js +0 -473
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&family=Noto+Serif+SC:wght@400;500;700;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/touchstone/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/touchstone/assets/index-DtwKoi1T.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/touchstone/assets/index-B0WoP1Qy.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/worktree.py
CHANGED
|
@@ -268,6 +268,106 @@ def status(project, card):
|
|
|
268
268
|
"dirty": bool(out.strip()), "exists": True}
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
# ---------- 合并就绪度(「通过」前判定:有没有待合并提交,2026-10-07 批次) ----------
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def head_branch(root):
|
|
275
|
+
"""主仓库当前检出的分支名;游离 HEAD / 读取失败返回 ("", 中文原因)。
|
|
276
|
+
|
|
277
|
+
平台的「主分支」口径就是它——worktree 由该分支检出,改动也回流到它。
|
|
278
|
+
不猜 `master`/`main`:仓库默认分支名由仓库自己决定,猜错会把改动合到
|
|
279
|
+
错误的分支上(用户 2026-10-07 约定:以项目所在分支为准)。
|
|
280
|
+
"""
|
|
281
|
+
rc, out, err = _git(["rev-parse", "--abbrev-ref", "HEAD"], root)
|
|
282
|
+
name = (out or "").strip()
|
|
283
|
+
if rc != 0:
|
|
284
|
+
return "", (err or "无法读取主仓库分支")
|
|
285
|
+
if not name or name == "HEAD":
|
|
286
|
+
return "", "主仓库处于游离 HEAD 状态,无法判定主分支"
|
|
287
|
+
return name, ""
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def merge_status(project, card):
|
|
291
|
+
"""卡片 worktree 的合并就绪度(只读判定;「通过」时据此决定是否交给 agent 合并)。
|
|
292
|
+
|
|
293
|
+
返回 dict(**不抛异常**):
|
|
294
|
+
{ok, error, path, branch, target, exists, branch_exists,
|
|
295
|
+
dirty, dirty_count, ahead, behind}
|
|
296
|
+
- `target`:主仓库当前分支(见 `head_branch`),空串=判定失败;
|
|
297
|
+
- `ahead`:`target..branch` 的提交数——**>0 即有改动还没回流主分支**,
|
|
298
|
+
平台据此弹「交给 agent 合并」;=0 表示无待合并提交(「通过」直接完成);
|
|
299
|
+
- `behind`:`branch..target` 的提交数——主分支领先多少(agent 需先同步);
|
|
300
|
+
- `dirty`/`dirty_count`:工作树未提交改动(含未跟踪;口径与 `status` 同);
|
|
301
|
+
- `ok=False` 只在「工作树/主仓库读不到」时出现,调用方按「不拦用户」处置。
|
|
302
|
+
"""
|
|
303
|
+
out = {"ok": False, "error": "", "path": "", "branch": branch_for(card["id"]),
|
|
304
|
+
"target": "", "exists": False, "branch_exists": False,
|
|
305
|
+
"dirty": False, "dirty_count": 0, "ahead": 0, "behind": 0}
|
|
306
|
+
path = (card["worktree"] or "").strip()
|
|
307
|
+
out["path"] = path
|
|
308
|
+
if not path:
|
|
309
|
+
out["error"] = "该卡片没有独立 worktree"
|
|
310
|
+
return out
|
|
311
|
+
if not os.path.isdir(path):
|
|
312
|
+
out["error"] = "worktree 目录不存在"
|
|
313
|
+
return out
|
|
314
|
+
out["exists"] = True
|
|
315
|
+
root, err = repo_root(project["project_dir"])
|
|
316
|
+
if err:
|
|
317
|
+
out["error"] = f"项目目录不是 git 仓库({err})"
|
|
318
|
+
return out
|
|
319
|
+
target, err = head_branch(root)
|
|
320
|
+
if err:
|
|
321
|
+
out["error"] = err
|
|
322
|
+
return out
|
|
323
|
+
out["target"] = target
|
|
324
|
+
branch = out["branch"]
|
|
325
|
+
rc, _, _ = _git(["rev-parse", "--verify", "--quiet", f"refs/heads/{branch}"], root)
|
|
326
|
+
out["branch_exists"] = rc == 0
|
|
327
|
+
if not out["branch_exists"]:
|
|
328
|
+
# 分支不存在(被删/从未创建):没有可合并的东西,按「无待合并」处置
|
|
329
|
+
out["ok"] = True
|
|
330
|
+
return out
|
|
331
|
+
rc, cout, err = _git(["rev-list", "--count", f"{target}..{branch}"], root)
|
|
332
|
+
if rc != 0:
|
|
333
|
+
out["error"] = f"无法读取待合并提交数:{err}"
|
|
334
|
+
return out
|
|
335
|
+
out["ahead"] = _int(cout)
|
|
336
|
+
rc, cout, err = _git(["rev-list", "--count", f"{branch}..{target}"], root)
|
|
337
|
+
if rc != 0:
|
|
338
|
+
out["error"] = f"无法读取落后提交数:{err}"
|
|
339
|
+
return out
|
|
340
|
+
out["behind"] = _int(cout)
|
|
341
|
+
ok, lines, err = _porcelain(path)
|
|
342
|
+
if not ok:
|
|
343
|
+
out["error"] = f"无法读取工作树状态:{err}"
|
|
344
|
+
return out
|
|
345
|
+
out["dirty"] = bool(lines)
|
|
346
|
+
out["dirty_count"] = len(lines)
|
|
347
|
+
out["ok"] = True
|
|
348
|
+
return out
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _int(text):
|
|
352
|
+
"""git 输出取整(异常输出归一 0,不抛)。"""
|
|
353
|
+
try:
|
|
354
|
+
return int((text or "").strip().splitlines()[0])
|
|
355
|
+
except (ValueError, IndexError):
|
|
356
|
+
return 0
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _porcelain(path):
|
|
360
|
+
"""工作树 `git status --porcelain` 的非空行(含未跟踪);读不到返回 (False, [], 原因)。
|
|
361
|
+
|
|
362
|
+
dirty 判据与 `status` 同口径(有输出即脏)——两处读的是同一条命令,
|
|
363
|
+
合并判定一次取到「脏不脏」与「脏几处」。
|
|
364
|
+
"""
|
|
365
|
+
rc, out, err = _git(["status", "--porcelain"], path)
|
|
366
|
+
if rc != 0:
|
|
367
|
+
return False, [], err
|
|
368
|
+
return True, [ln for ln in (out or "").splitlines() if ln.strip()], ""
|
|
369
|
+
|
|
370
|
+
|
|
271
371
|
def remove(project, card):
|
|
272
372
|
"""清理卡片 worktree(plan D8:**只在干净时**、绝不用 `--force`);返回 (ok, err)。
|
|
273
373
|
|