task-ledger 1.2.2 → 1.2.4
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 +5 -2
- package/SKILL.md +2 -2
- package/package.json +1 -1
- package/references/state-model.md +6 -5
- package/scripts/progress.py +77 -2
package/README.md
CHANGED
|
@@ -118,7 +118,9 @@
|
|
|
118
118
|
- 临时文件名唯一(`mkstemp`),写完 `fsync` 再 `os.replace`,无论成败都清理临时文件
|
|
119
119
|
- key 按字母序落盘,减少两人同时新增时的文本冲突
|
|
120
120
|
- 冲突时**报错要求重跑**,不静默覆盖
|
|
121
|
-
-
|
|
121
|
+
- 写入成功后脚本会**自动同步**:把本次改动的台账文件 `git add <精确文件>` + `commit`(消息 `ledger(<命令>): <提交者>`);当前分支有上游跟踪分支时再 `push`。全程确定性脚本,不依赖大模型
|
|
122
|
+
- 无 git 仓库 / 无上游跟踪 / 被 `.gitignore` 排除 / 无实际变更 都会静默跳过;`--no-sync` 或环境变量 `TASK_LEDGER_NO_SYNC=1` 可关闭
|
|
123
|
+
- 冲突**仍不靠 `git pull` / `rebase` 解决**:并发靠指纹复查 + 原子写防覆盖,冲突时**报错要求重跑**,不静默合并
|
|
122
124
|
|
|
123
125
|
### 结案是原子操作
|
|
124
126
|
|
|
@@ -192,7 +194,8 @@ stateDiagram-v2
|
|
|
192
194
|
note right of doing
|
|
193
195
|
set --owner 交接:owner 换成别人
|
|
194
196
|
(只有当前 owner 能执行)
|
|
195
|
-
|
|
197
|
+
超期 = 接任务满 14 天;停滞 = 最后更新满 3 天
|
|
198
|
+
积压 = 待认领满 30 天
|
|
196
199
|
(都由看板按时间算,不是状态)
|
|
197
200
|
end note
|
|
198
201
|
```
|
package/SKILL.md
CHANGED
|
@@ -34,7 +34,7 @@ allowed-tools: Read,Write,Bash,Grep
|
|
|
34
34
|
| 「谁在做 X」「有哪些在办」 | `list`;在等认领的加 `--status open` |
|
|
35
35
|
| 「这个任务到哪一步了」「这轮做了什么」 | `show --id`(任务 JSON + worklog 全文) |
|
|
36
36
|
| 「某件老活什么时候做的」「谁做过哪些活」 | `history`(可按 `--owner` / `--since` 筛) |
|
|
37
|
-
| 「哪些任务停太久」 | `list --json`:`updated` 满 3 天=停滞、`started` 满
|
|
37
|
+
| 「哪些任务停太久」 | `list --json`:`updated` 满 3 天=停滞、`started` 满 14 天=超期、`created` 满 30 天仍 `open`=积压(看板算,别改状态) |
|
|
38
38
|
| 「新建一个任务」 | `new`(`--self` = 建完即认领) |
|
|
39
39
|
| 「这活我领了」 | `claim`(只对 `open` 有效) |
|
|
40
40
|
| 「开始做 / 接着做 X」 | 还是 `open` 就先 `claim`;已是 `doing` 就直接改正文 + `log` |
|
|
@@ -61,7 +61,7 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当
|
|
|
61
61
|
|
|
62
62
|
## 铁律
|
|
63
63
|
|
|
64
|
-
- **状态只有 `open` / `doing`**;`done` 只能由 `close` 产生。**没有 `blocked`
|
|
64
|
+
- **状态只有 `open` / `doing`**;`done` 只能由 `close` 产生。**没有 `blocked` 状态**:超期 = `started` 满 14 天、停滞 = `updated` 满 3 天、积压 = `created` 满 30 天且仍 `open`,都由看板按时间算,`progress.json` 里不存、不手改;卡在哪写进正文。
|
|
65
65
|
- **`log` 就是刷新 `updated` 的动作**:该记不记,任务会被看板标成停滞。
|
|
66
66
|
- **owner = 当前 `git config user.name`**,不能由外部传入;读不到就报错(先让用户配 git)。**只能动自己名下**的任务(`open` 的先 `claim`),不是你的直接拒绝 —— 不要绕过脚本改 JSON。
|
|
67
67
|
- **换人只能「释放 → 认领」**:skill 不提供交接,随便写的名字都是假的。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 状态与时间模型
|
|
2
2
|
|
|
3
|
-
> TL;DR:状态只有 `open` / `doing`;`done` 由 `close`
|
|
3
|
+
> TL;DR:状态只有 `open` / `doing`;`done` 由 `close` 产生。超期 = `started` 满 14 天、停滞 = `updated` 满 3 天、积压 = `created` 满 30 天仍 `open`,看板算、不存字段。`updated` 由 `new/claim/set/log` 刷新。owner 只取当前 git 身份。
|
|
4
4
|
|
|
5
5
|
## 状态机
|
|
6
6
|
|
|
@@ -12,15 +12,16 @@ doing ──set --status open──> open # 释放认领,执行记录
|
|
|
12
12
|
- `open`:已建、等认领。`doing`:已认领、在做。`done` 只由 `close` 产生。
|
|
13
13
|
- **没有 `blocked` 状态**(2026-10-19 取消)。旧数据里残留的 `blocked` 读侧当进行中:`list --status blocked` 可捞出,用 `set --status doing` 归位;新写入一律拒绝。
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 三个标记(看板计算,不是状态)
|
|
16
16
|
|
|
17
17
|
| 标记 | 依据 | 阈值 |
|
|
18
18
|
|---|---|---|
|
|
19
|
-
|
|
|
19
|
+
| 积压 | `created`(建了)至今仍 `open`(没人认领) | 满 30 天 |
|
|
20
|
+
| 超期 | `started`(接任务)至今,中途更新不影响 | 满 14 天 |
|
|
20
21
|
| 停滞 | `updated`(最后推进)至今 | 满 3 天 |
|
|
21
22
|
|
|
22
|
-
- 真源:`packages/plugin/src/host/services/home.service.ts` 的 `OVERDUE_DAYS =
|
|
23
|
-
-
|
|
23
|
+
- 真源:`packages/plugin/src/host/services/home.service.ts` 的 `BACKLOG_DAYS = 30` / `OVERDUE_DAYS = 14` / `STALE_DAYS = 3`;看板随响应下发三个天数,界面文案不硬编码。
|
|
24
|
+
- 脚本不算这三个值,`progress.json` 里也不存;agent 要回答「哪些停太久」时用 `list --json` 的 `created` / `updated` / `started` 与当前时间比。
|
|
24
25
|
- 老数据没有 `updated` 时,读侧回退到 `started`,再到 `created`。
|
|
25
26
|
|
|
26
27
|
## 时间
|
package/scripts/progress.py
CHANGED
|
@@ -38,7 +38,7 @@ except ImportError:
|
|
|
38
38
|
msvcrt = None
|
|
39
39
|
|
|
40
40
|
STATUSES = ("open", "doing")
|
|
41
|
-
# 旧数据只读兼容:早期版本有 blocked
|
|
41
|
+
# 旧数据只读兼容:早期版本有 blocked 状态(手动标「卡住」)。现在超期按时间判定
|
|
42
42
|
# (接任务满 7 天),不再允许新写入;list / validate / 看板仍能吃老文件。
|
|
43
43
|
LEGACY_STATUSES = ("blocked",)
|
|
44
44
|
PROGRESS_REL = Path("ledger/progress.json")
|
|
@@ -245,6 +245,10 @@ def read_json(path: Path) -> dict[str, Any]:
|
|
|
245
245
|
return data
|
|
246
246
|
|
|
247
247
|
|
|
248
|
+
# 本次进程内被台账写入的真实文件路径,供写成功后一次性自动同步(git commit/push)。
|
|
249
|
+
CHANGED_FILES: list[Path] = []
|
|
250
|
+
|
|
251
|
+
|
|
248
252
|
def write_text_atomic(path: Path, text: str) -> None:
|
|
249
253
|
"""原子写文本:同目录唯一临时名 + fsync + os.replace,写完清理临时文件。
|
|
250
254
|
|
|
@@ -265,6 +269,7 @@ def write_text_atomic(path: Path, text: str) -> None:
|
|
|
265
269
|
fh.flush()
|
|
266
270
|
os.fsync(fh.fileno())
|
|
267
271
|
os.replace(tmp, path)
|
|
272
|
+
CHANGED_FILES.append(path) # 记下本次改动,供写后自动同步
|
|
268
273
|
tmp = None
|
|
269
274
|
except OSError as exc:
|
|
270
275
|
raise Fail(f"写 {path} 失败:{exc}")
|
|
@@ -349,6 +354,68 @@ def commit_guarded(
|
|
|
349
354
|
write_json(path, data)
|
|
350
355
|
|
|
351
356
|
|
|
357
|
+
def _git(*args: str, timeout: int | None = None) -> subprocess.CompletedProcess:
|
|
358
|
+
"""跑一条 git 命令,吞掉输出(同步用,不污染 stdout)。"""
|
|
359
|
+
return subprocess.run(list(args), capture_output=True, text=True, timeout=timeout)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _git_actor(repo_root: str) -> str:
|
|
363
|
+
"""提交者:复用 git 身份;取不到用占位,不阻断提交。"""
|
|
364
|
+
out = _git("git", "-C", repo_root, "config", "user.name")
|
|
365
|
+
return out.stdout.strip() or "ledger-script"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def git_sync(action: str) -> None:
|
|
369
|
+
"""写成功后调用一次:把本次改动的台账文件提交;有上游跟踪分支则推送。
|
|
370
|
+
|
|
371
|
+
纯脚本、确定性,不依赖大模型。无 git / 无上游 / 全被 ignore / 无实际变更 都静默跳过。
|
|
372
|
+
同步是尽力而为:任何异常都不影响已完成的台账写入(只打印提示)。
|
|
373
|
+
"""
|
|
374
|
+
if os.environ.get("TASK_LEDGER_NO_SYNC"):
|
|
375
|
+
return
|
|
376
|
+
try:
|
|
377
|
+
files = [p for p in CHANGED_FILES if p.exists()]
|
|
378
|
+
if not files:
|
|
379
|
+
return
|
|
380
|
+
# 从任一文件反查仓库根,不依赖调用方透传 --repo
|
|
381
|
+
top = _git("git", "-C", str(files[0].parent), "rev-parse", "--show-toplevel")
|
|
382
|
+
if top.returncode != 0:
|
|
383
|
+
return # 不是 git 项目 → 跳过(规则1:有才提交)
|
|
384
|
+
root = top.stdout.strip()
|
|
385
|
+
# 只 add 本次真实改动、且未被 ignore 的文件
|
|
386
|
+
to_add = [
|
|
387
|
+
str(f)
|
|
388
|
+
for f in files
|
|
389
|
+
if _git("git", "-C", root, "check-ignore", "-q", str(f)).returncode != 0
|
|
390
|
+
]
|
|
391
|
+
if not to_add:
|
|
392
|
+
return # 全被 ignore → 尊重用户选择,不强行提交
|
|
393
|
+
_git("git", "-C", root, "add", *to_add)
|
|
394
|
+
if _git("git", "-C", root, "diff", "--cached", "--quiet").returncode == 0:
|
|
395
|
+
return # 无实际变更
|
|
396
|
+
actor = _git_actor(root)
|
|
397
|
+
_git("git", "-C", root, "commit", "-m", f"ledger({action}): {actor}")
|
|
398
|
+
# 规则2:有上游跟踪分支才推
|
|
399
|
+
if _git("git", "-C", root, "rev-parse", "--abbrev-ref", "@{upstream}").returncode != 0:
|
|
400
|
+
return
|
|
401
|
+
try:
|
|
402
|
+
_git("git", "-C", root, "push", timeout=30)
|
|
403
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
404
|
+
# 非快进:自动 rebase 一次再推;冲突 / 超时则放弃,绝不 --force
|
|
405
|
+
try:
|
|
406
|
+
_git("git", "-C", root, "pull", "--rebase", "--autostash", timeout=30)
|
|
407
|
+
_git("git", "-C", root, "push", timeout=30)
|
|
408
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
409
|
+
print(
|
|
410
|
+
"提示:本地已提交,但推送被拒(他人已推 / 需认证 / 超时)。"
|
|
411
|
+
"请手动 `git pull --rebase` 解决后重试;本次改动已安全留在本地。",
|
|
412
|
+
file=sys.stderr,
|
|
413
|
+
)
|
|
414
|
+
except Exception as exc: # noqa: BLE001 —— 同步失败不应污染台账主流程
|
|
415
|
+
print(f"提示:写后自动同步未执行({type(exc).__name__}: {exc})。数据已写入本地。",
|
|
416
|
+
file=sys.stderr)
|
|
417
|
+
|
|
418
|
+
|
|
352
419
|
def require_task_id(tid: str) -> str:
|
|
353
420
|
"""任务号会参与拼路径,任何命令拿到它都要先验一遍。"""
|
|
354
421
|
if not ID_RE.fullmatch(tid):
|
|
@@ -1364,7 +1431,7 @@ def cmd_set(args: argparse.Namespace) -> None:
|
|
|
1364
1431
|
)
|
|
1365
1432
|
if status == "blocked":
|
|
1366
1433
|
raise Fail(
|
|
1367
|
-
"没有 blocked
|
|
1434
|
+
"没有 blocked 状态:超期由时间自动判定(接任务满 14 天仍未结案),"
|
|
1368
1435
|
"状态只有 open / doing;卡在哪里请写进 worklog。"
|
|
1369
1436
|
)
|
|
1370
1437
|
if status == "done":
|
|
@@ -2070,6 +2137,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2070
2137
|
prog="progress.py",
|
|
2071
2138
|
description="管理项目任务进度(ledger/progress.json + worklog + 历史)",
|
|
2072
2139
|
)
|
|
2140
|
+
parser.add_argument(
|
|
2141
|
+
"--no-sync",
|
|
2142
|
+
action="store_true",
|
|
2143
|
+
help="跳过写后自动 git 提交 / 推送(等价于 TASK_LEDGER_NO_SYNC=1)",
|
|
2144
|
+
)
|
|
2073
2145
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
2074
2146
|
|
|
2075
2147
|
def add_repo(p: argparse.ArgumentParser) -> None:
|
|
@@ -2202,6 +2274,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2202
2274
|
|
|
2203
2275
|
def main(argv: list[str] | None = None) -> int:
|
|
2204
2276
|
args = build_parser().parse_args(argv)
|
|
2277
|
+
if getattr(args, "no_sync", False):
|
|
2278
|
+
os.environ["TASK_LEDGER_NO_SYNC"] = "1"
|
|
2205
2279
|
try:
|
|
2206
2280
|
args.func(args)
|
|
2207
2281
|
except Fail as exc:
|
|
@@ -2221,6 +2295,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2221
2295
|
except Exception as exc: # noqa: BLE001 —— 兜底:未预期的失败用独立退出码,与业务错误区分
|
|
2222
2296
|
print(f"错误:未预期的失败 {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
2223
2297
|
return 3
|
|
2298
|
+
git_sync(args.command) # 写成功后一次性自动同步(commit / push)
|
|
2224
2299
|
return 0
|
|
2225
2300
|
|
|
2226
2301
|
|