task-ledger 1.2.5 → 1.2.7
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 +2 -1
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/scripts/progress.py +117 -14
package/README.md
CHANGED
|
@@ -118,7 +118,8 @@
|
|
|
118
118
|
- 临时文件名唯一(`mkstemp`),写完 `fsync` 再 `os.replace`,无论成败都清理临时文件
|
|
119
119
|
- key 按字母序落盘,减少两人同时新增时的文本冲突
|
|
120
120
|
- 冲突时**报错要求重跑**,不静默覆盖
|
|
121
|
-
- 写入成功后脚本会**自动同步**:把本次改动的台账文件 `git add <精确文件>` + `commit`(消息 `ledger
|
|
121
|
+
- 写入成功后脚本会**自动同步**:把本次改动的台账文件 `git add <精确文件>` + `commit`(消息 `chore(ledger): <命令> <提交者>`,即 conventional commits 格式,多数仓库装了 commitlint 也认);当前分支有上游跟踪分支时再 `push`。全程确定性脚本,不依赖大模型
|
|
122
|
+
- 仓库若装了自定义提交检查并拒了这条消息,脚本会**再用 `--no-verify` 提交一次**(台账是数据文件,不过 lint);仍失败则把 git 的报错打到 stderr 并要求手动提交 —— **同步失败绝不静默**,但退出码保持 0(台账本身已写成功)
|
|
122
123
|
- 无 git 仓库 / 无上游跟踪 / 被 `.gitignore` 排除 / 无实际变更 都会静默跳过;`--no-sync` 或环境变量 `TASK_LEDGER_NO_SYNC=1` 可关闭
|
|
123
124
|
- 冲突**仍不靠 `git pull` / `rebase` 解决**:并发靠指纹复查 + 原子写防覆盖,冲突时**报错要求重跑**,不静默合并
|
|
124
125
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: task-ledger
|
|
3
3
|
description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)时加载;对某个具体任务要求结案或退回时,只认用户的明确指令;只是改代码、讨论方案时不要用。
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.6
|
|
5
5
|
allowed-tools: Read,Write,Bash,Grep
|
|
6
6
|
---
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/progress.json, history, worklog) driven by a CLI script. Standalone install for CodeBuddy and other agents; also bundled inside the dsh-task-ledger DSH plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skill",
|
package/scripts/progress.py
CHANGED
|
@@ -354,9 +354,17 @@ def commit_guarded(
|
|
|
354
354
|
write_json(path, data)
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
def _git(
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
def _git(
|
|
358
|
+
*args: str, timeout: int | None = None, check: bool = False
|
|
359
|
+
) -> subprocess.CompletedProcess:
|
|
360
|
+
"""跑一条 git 命令,吞掉输出(同步用,不污染 stdout)。
|
|
361
|
+
|
|
362
|
+
check=True 让失败抛异常(推送要能被下面的兜底捕获);提交则靠返回码分辨,
|
|
363
|
+
因为不同仓库的提交检查(commitlint 等)会拒掉消息,需要看 stderr 原文。
|
|
364
|
+
"""
|
|
365
|
+
return subprocess.run(
|
|
366
|
+
list(args), capture_output=True, text=True, timeout=timeout, check=check
|
|
367
|
+
)
|
|
360
368
|
|
|
361
369
|
|
|
362
370
|
def _git_actor(repo_root: str) -> str:
|
|
@@ -369,7 +377,8 @@ def git_sync(action: str) -> None:
|
|
|
369
377
|
"""写成功后调用一次:把本次改动的台账文件提交;有上游跟踪分支则推送。
|
|
370
378
|
|
|
371
379
|
纯脚本、确定性,不依赖大模型。无 git / 无上游 / 全被 ignore / 无实际变更 都静默跳过。
|
|
372
|
-
|
|
380
|
+
同步失败(提交被仓库钩子拒后连 --no-verify 也失败、推送被拒、意外异常)一律打到
|
|
381
|
+
stderr 提示,绝不静默;但退出码不变 —— 台账本身已经写成功,别让调用方误判去重做。
|
|
373
382
|
"""
|
|
374
383
|
if os.environ.get("TASK_LEDGER_NO_SYNC"):
|
|
375
384
|
return
|
|
@@ -382,29 +391,49 @@ def git_sync(action: str) -> None:
|
|
|
382
391
|
if top.returncode != 0:
|
|
383
392
|
return # 不是 git 项目 → 跳过(规则1:有才提交)
|
|
384
393
|
root = top.stdout.strip()
|
|
394
|
+
g = ("git", "-C", root)
|
|
385
395
|
# 只 add 本次真实改动、且未被 ignore 的文件
|
|
386
396
|
to_add = [
|
|
387
397
|
str(f)
|
|
388
398
|
for f in files
|
|
389
|
-
if _git(
|
|
399
|
+
if _git(*g, "check-ignore", "-q", str(f)).returncode != 0
|
|
390
400
|
]
|
|
391
401
|
if not to_add:
|
|
392
402
|
return # 全被 ignore → 尊重用户选择,不强行提交
|
|
393
|
-
_git(
|
|
394
|
-
if _git(
|
|
403
|
+
_git(*g, "add", *to_add)
|
|
404
|
+
if _git(*g, "diff", "--cached", "--quiet").returncode == 0:
|
|
395
405
|
return # 无实际变更
|
|
396
406
|
actor = _git_actor(root)
|
|
397
|
-
|
|
407
|
+
# 消息按 conventional commits 写:多数仓库装了 commitlint,只认白名单里的 type
|
|
408
|
+
msg = f"chore(ledger): {action} {actor}"
|
|
409
|
+
done = _git(*g, "commit", "-m", msg)
|
|
410
|
+
if done.returncode != 0:
|
|
411
|
+
# 各仓库提交规则各不相同(type 白名单、大小写、scope 枚举……),台账是数据文件,
|
|
412
|
+
# 过不了就让这一次跳过钩子提交,别把台账永久锁在本地暂存区。
|
|
413
|
+
done = _git(*g, "commit", "--no-verify", "-m", msg)
|
|
414
|
+
if done.returncode == 0:
|
|
415
|
+
print(
|
|
416
|
+
f"提示:仓库提交检查拒绝了消息「{msg}」,已用 --no-verify 提交本次台账改动。",
|
|
417
|
+
file=sys.stderr,
|
|
418
|
+
)
|
|
419
|
+
if done.returncode != 0:
|
|
420
|
+
print(
|
|
421
|
+
"提示:台账已写入本地,但自动提交失败,请手动提交 ledger/:"
|
|
422
|
+
f"{(done.stderr or done.stdout or '').strip()}",
|
|
423
|
+
file=sys.stderr,
|
|
424
|
+
)
|
|
425
|
+
return
|
|
398
426
|
# 规则2:有上游跟踪分支才推
|
|
399
|
-
|
|
427
|
+
upstream = _git(*g, "rev-parse", "--abbrev-ref", "@{upstream}")
|
|
428
|
+
if upstream.returncode != 0:
|
|
400
429
|
return
|
|
401
430
|
try:
|
|
402
|
-
_git(
|
|
431
|
+
_git(*g, "push", timeout=30, check=True)
|
|
403
432
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
404
433
|
# 非快进:自动 rebase 一次再推;冲突 / 超时则放弃,绝不 --force
|
|
405
434
|
try:
|
|
406
|
-
_git(
|
|
407
|
-
_git(
|
|
435
|
+
_git(*g, "pull", "--rebase", "--autostash", timeout=30, check=True)
|
|
436
|
+
_git(*g, "push", timeout=30, check=True)
|
|
408
437
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
409
438
|
print(
|
|
410
439
|
"提示:本地已提交,但推送被拒(他人已推 / 需认证 / 超时)。"
|
|
@@ -412,8 +441,81 @@ def git_sync(action: str) -> None:
|
|
|
412
441
|
file=sys.stderr,
|
|
413
442
|
)
|
|
414
443
|
except Exception as exc: # noqa: BLE001 —— 同步失败不应污染台账主流程
|
|
415
|
-
print(
|
|
416
|
-
|
|
444
|
+
print(
|
|
445
|
+
f"提示:写后自动同步未执行({type(exc).__name__}: {exc})。数据已写入本地。",
|
|
446
|
+
file=sys.stderr,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# 只读命令不碰 git;其余(含写类与 tags)写前刷新 ledger/。
|
|
451
|
+
READ_ONLY_COMMANDS = {"list", "show", "history", "validate"}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def git_presync(action: str, args: argparse.Namespace) -> None:
|
|
455
|
+
"""写命令执行前把 ledger/ 刷成远端最新,避免基于过期读取整文件回写。
|
|
456
|
+
|
|
457
|
+
只动 ledger/ 目录(不碰项目其余文件),与 git_sync 共用 --no-sync / TASK_LEDGER_NO_SYNC。
|
|
458
|
+
无 git / 无上游 / 远端尚无该路径 静默跳过;拉取失败只提示不中止(推送端 rebase 兜底)。
|
|
459
|
+
本地未提交的 ledger 改动会被远端覆盖——正是「取最新再改」语义,仅打提示。
|
|
460
|
+
"""
|
|
461
|
+
if os.environ.get("TASK_LEDGER_NO_SYNC"):
|
|
462
|
+
return
|
|
463
|
+
if action in READ_ONLY_COMMANDS:
|
|
464
|
+
return
|
|
465
|
+
# tags --all 是纯读,不拉取
|
|
466
|
+
if action == "tags" and not getattr(args, "rename", None):
|
|
467
|
+
return
|
|
468
|
+
try:
|
|
469
|
+
repo = find_repo(getattr(args, "repo", None))
|
|
470
|
+
except Fail:
|
|
471
|
+
return
|
|
472
|
+
ledger_dir = repo / "ledger"
|
|
473
|
+
top = _git("git", "-C", str(repo), "rev-parse", "--show-toplevel")
|
|
474
|
+
if top.returncode != 0:
|
|
475
|
+
return
|
|
476
|
+
git_root = Path(top.stdout.strip())
|
|
477
|
+
try:
|
|
478
|
+
ledger_rel = str(ledger_dir.resolve().relative_to(git_root.resolve()))
|
|
479
|
+
except ValueError:
|
|
480
|
+
return # ledger 不在 git 仓内
|
|
481
|
+
ups = _git("git", "-C", str(git_root), "rev-parse", "--abbrev-ref", "@{upstream}")
|
|
482
|
+
if ups.returncode != 0:
|
|
483
|
+
return # 无上游跟踪分支
|
|
484
|
+
upstream = ups.stdout.strip()
|
|
485
|
+
remote = upstream.split("/", 1)[0]
|
|
486
|
+
try:
|
|
487
|
+
_git("git", "-C", str(git_root), "fetch", remote, timeout=30, check=True)
|
|
488
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
489
|
+
print(
|
|
490
|
+
"提示:写前拉取失败(离线 / 无权限 / 超时),将基于本地当前内容写入;"
|
|
491
|
+
"若他人已改动,推送时可能冲突。",
|
|
492
|
+
file=sys.stderr,
|
|
493
|
+
)
|
|
494
|
+
return
|
|
495
|
+
if _git(
|
|
496
|
+
"git", "-C", str(git_root), "status", "--porcelain", ledger_rel
|
|
497
|
+
).stdout.strip():
|
|
498
|
+
print(
|
|
499
|
+
"提示:本地 ledger/ 有未提交改动,写前拉取将用远端最新覆盖。",
|
|
500
|
+
file=sys.stderr,
|
|
501
|
+
)
|
|
502
|
+
# 只取远端 ledger/ 到工作树;该路径在远端不存在(全新台账)时忽略
|
|
503
|
+
try:
|
|
504
|
+
_git(
|
|
505
|
+
"git",
|
|
506
|
+
"-C",
|
|
507
|
+
str(git_root),
|
|
508
|
+
"checkout",
|
|
509
|
+
upstream,
|
|
510
|
+
"--",
|
|
511
|
+
ledger_rel,
|
|
512
|
+
timeout=30,
|
|
513
|
+
check=True,
|
|
514
|
+
)
|
|
515
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
516
|
+
pass
|
|
517
|
+
# 取消预暂存:只更新工作树,避免把他人改动混进本次提交(git_sync 只 add 本次改动的文件)
|
|
518
|
+
_git("git", "-C", str(git_root), "reset", "--", ledger_rel)
|
|
417
519
|
|
|
418
520
|
|
|
419
521
|
def require_task_id(tid: str) -> str:
|
|
@@ -2277,6 +2379,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2277
2379
|
if getattr(args, "no_sync", False):
|
|
2278
2380
|
os.environ["TASK_LEDGER_NO_SYNC"] = "1"
|
|
2279
2381
|
try:
|
|
2382
|
+
git_presync(args.command, args) # 写前刷新 ledger/ 到远端最新
|
|
2280
2383
|
args.func(args)
|
|
2281
2384
|
except Fail as exc:
|
|
2282
2385
|
print(f"错误:{exc}", file=sys.stderr)
|