dsh-task-ledger 0.0.3 → 0.0.6

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.
@@ -0,0 +1,251 @@
1
+ # task-ledger
2
+
3
+ > 这份文档**给人看**:它是什么、为什么这么设计、一次调用背后发生什么。
4
+ > 给 agent 的执行说明在 `SKILL.md`,两者不重复;`SKILL.md` 里**没有**引用本文件,所以它不会进 agent 上下文、不占 token。
5
+
6
+ ## 一、它做什么
7
+
8
+ 管一个项目 / 工作区的任务进度(开发活、琐事、调研、备忘都算),一共三件东西:
9
+
10
+ | 管什么 | 落在哪 | 谁写 |
11
+ |---|---|---|
12
+ | 在办任务的状态 | `ledger/progress.json` | **只由脚本写** |
13
+ | 每个任务的过程正文 | `ledger/worklog/<年月>/<日>-<时分>-<说明>-<随机码>.md` | **建档只由脚本按模板做**,之后正文 **agent 直接编辑** |
14
+ | 已结案任务,一行一条(8 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / 摘要 / 过程文档) | `ledger/history/<年月>.md`(按**结案时间**分月;存量 `ledger/PROGRESS-HISTORY.md` 只读兼容、永不重写) | **只由脚本写**;查询走 `history` 子命令 |
15
+
16
+ 覆盖的动作:建任务、认领、交接、改状态、结案、查在办、查历史、体检。
17
+
18
+ 典型场景:
19
+
20
+ - 问「现在该做什么 / 我还有什么没做完」→ 查在办
21
+ - 说「新建一个任务 / 这活我领了 / 卡住了 / 转给某人 / 这活做完了」→ 改状态或结案
22
+ - 问「某件老活是什么时候做的」「谁改过什么」「拉某人的工作日志」→ `history` 子命令(只读,可直接跑)
23
+
24
+ ## 二、为什么这么设计
25
+
26
+ ### 顶层目录为什么是 `ledger/`,不是 `docs/`
27
+
28
+ `docs/` 是**项目自己的地盘** —— 项目的文档规范、设计、样例都住在那里,各有各的规矩。
29
+ 把进度三件套塞进 `docs/`,等于让「进度管理」去占用别人已经安排好的空间;
30
+ 换一个没有 `docs/` 的项目、或项目把 `docs/` 用作别的用途,就又得重新商量放在哪。
31
+
32
+ 所以本 skill 统一用**独立的 `ledger/` 目录**:任何项目、任何工作区都用同一个名字,
33
+ 既不依赖项目原有的 `docs/`,也不与它发生关系(项目原有 `docs/` 原样不动、不进本体系)。
34
+ 进度是「跨项目长一个样」的东西,就该有一个跨项目固定的家。
35
+
36
+ ### 状态用 JSON,不用 Markdown
37
+
38
+ 状态要频繁改(`doing` → `blocked` → `done`),Markdown 里改一个词很容易顺手改到别处。JSON 结构化、字段固定、一次只动一个值,改动面最小。
39
+
40
+ ### 状态与过程正文分离
41
+
42
+ 状态改动频繁、正文频繁重写,混在一个文件里必然互相踩。所以拆成两个文件:**JSON 只放数据,正文全在 `worklog/`**。JSON 里一个字的过程描述都不放 —— 一旦开始写段落,改动面就不可控了。
43
+
44
+ 唯一进 JSON 的「文档类」信息是过程文档的**路径**(`worklog` 字段,如 `worklog/202610/06-0000-doc-arch-review-7a3c.md`)。它不是描述而是索引:按月分目录之后文件名不再等于任务号,靠推算容易找错,记一笔最省事。
45
+
46
+ ### 历史层的 owner 是独立列
47
+
48
+ 「谁改过什么」「拉某人的工作日志」靠 `history --owner` **精确匹配**。owner 必须独立成列,不能缝在工作包标题里:缝进去就只能文本搜索,`a` 会命中 `a-11`;切分也只能靠「最后一个 ` @`」这种启发式,标题里出现 `@`(如 `rulemux@0.1.0`)就切错。独立列让按人查退化成列比较,与 `list --owner` 查在办是同一套精确语义。
49
+
50
+ ### 记录时间精确到秒,任务号 / 文件名只到分
51
+
52
+ 两者用途不同,精度也不同:**任务号、worklog 目录与文件名**只到分(`年月日-时分` / `YYYYMM`)—— 够排序、够肉眼定位,且短;**所有记录时间**(`progress.json` 的 `created` / `started` / `finished`、历史表的时间列、worklog 头部的创建 / 封卷行)到**秒** —— 按时间段检索、比对都靠它,分不够。
53
+
54
+ 写入前一律经 `normalize_time` 归一化:`2026-10-06`、`2026-10-06 14:30`、`2026-10-06 14:30:05` 都接受,缺的补 `00:00:00`。读旧数据时又**兼容只到分**的写法(老历史行照读不误),只有新写入的才带秒。
55
+
56
+ ### 历史表为什么带创建时间
57
+
58
+ 历史层的过滤口径是**创建时间**(`list` / `history` 的 `--since` / `--until` 都按它),因为「这活是什么时候建的」比「什么时候被结案」更常用、也更稳。所以历史行同时存**结案时间**(第一列)与**创建时间**(第二列)。升级旧行时创建时间先读它链接的 worklog 头部「创建」行,读不到用升级时刻 —— 不留空。
59
+
60
+ ### 历史层为什么按月归档
61
+
62
+ `close` 每结案一行就往历史层追加,早期是单一文件 `ledger/PROGRESS-HISTORY.md`:项目一两年下来它无限增长,每次结案都要整文件读入、升级、重写 —— 结案成本随历史总量线性变差。
63
+
64
+ 现在按**结案时间**分月落 `ledger/history/<年月>.md`,与 worklog 的 `ledger/worklog/<年月>/` 同构:
65
+
66
+ - **写入时分月,不做定时外挪** —— 外挪需要一个「谁定时触发」的角色,必然漂移;写入时分月零新增动作,且结案只碰当月小文件
67
+ - **存量文件原地只读** —— 改造前的 `ledger/PROGRESS-HISTORY.md` 永不重写、永不删除,读侧 glob 兼容;它里面的旧格式行也不会被自动升级(读侧三格式兼容 + `validate` 提示已覆盖)
68
+ - **文档链接跟着所在文件走** —— 行里的 worklog 链接相对该行所在的历史文件目录解析:存量文件里是 `worklog/…`,月文件里是 `../worklog/…`,两边都可点击、可定位
69
+ - **查询无感** —— `history` / `validate` 合并全部来源,过滤、排序、`--json` 行为不变;行尾附所在历史文件,`--json` 记录带 `src` 字段
70
+
71
+ ### 状态文件只由脚本写
72
+
73
+ 两个人(或两个 agent)同时编辑同一个文件会互相覆盖。脚本的写入靠两层保证:
74
+
75
+ 1. **原子替换**:先写同目录的临时文件,再 `os.replace` —— 保证文件不会出现半截内容
76
+ 2. **写前复查指纹**:读的时候记下文件的**内容哈希**(sha256),写之前再比一次;变了就报错中止、要求重跑。
77
+ 刻意不用 mtime+size —— 并发写入可能落在同一时间精度内、字节数又恰好相同,那样指纹不变,复查就形同虚设
78
+
79
+ 第 2 条是**发现并中止**,不是自动合并 —— 它把「静默覆盖别人」变成「明确报错让你重来一次」。所以并发下偶尔会遇到「被改动过,重跑」,这是设计如此,不是 bug。
80
+
81
+ ### worklog 头部只写不会变的东西
82
+
83
+ 原设计里 worklog 头部带 `状态:open · owner:(待认领)`,实测发现:**`claim` 之后头部还写着 `open`,成了过期副本**。删掉 —— 状态真源只有 `progress.json` 一个。
84
+
85
+ 现在头部只写两样**写入后就不再变**的:**创建时间 + 创建人**(`new` 建档时写)、**封卷时间 + 封卷人**(`close` 结案时追加)。会变的东西(状态、当前 owner)一律不写。
86
+
87
+ ### 任务号 = 创建时间 + 语义名 + 4 位随机码
88
+
89
+ `登录接入 SSO`(2026-10-15 14:30 建的)→ `20261015-1430-登录接入-sso-1eed`。
90
+
91
+ - 前置 `年月日-时分`:一眼看出这个任务是什么时候建的,也天然按时间排序
92
+ - 中段语义名:一眼知道是什么任务(所以保留汉字,纯中文标题也能生成)
93
+ - 后缀 4 位随机码:**必须保留** —— 任务号只在在办任务里去重,而 worklog 结案后仍留在磁盘上,去掉随机码会让新任务与已结案的过程文档同名、覆盖历史
94
+ - 不用纯 UUID:文件名会变得完全不可读
95
+ - 想自己指定就传 `--id`
96
+
97
+ ### owner 只认 git 身份,不做兜底
98
+
99
+ `owner` 一律取 `git config user.name`。取不到就**直接报错退出**(提示「需要用户名才能执行」),没有默认值、没有备选标识、没有降级路径。
100
+
101
+ 理由:拿一个编造的身份去写"谁在做这个任务",比写不进去危险得多。
102
+
103
+ ### 只能改自己的任务
104
+
105
+ `set` 和 `close` 都先取身份,再校验「这个任务的 owner 是不是你」:**不是就拒绝**;任务还没人认领(`open`)也拒绝,提示先 `claim`。要接手就走 `set --owner` 交接 —— 一次显式动作。
106
+
107
+ 理由有两层:一是别人替你结案,你会失去对进度的掌控;二是如果只取了身份却不校验归属,`owner` 这个字段就只是个装饰品。
108
+
109
+ ### 不依赖任何框架注入的变量
110
+
111
+ 早先写成 `{baseDir}/scripts/progress.py` —— 那是某一家的注入变量,换个 harness 就没有。现在写成「本 SKILL.md 所在目录」这种不依赖注入的表述。
112
+
113
+ 同理,**项目位置必须由调用方用 `--repo` 传**:脚本只知道自己在哪,不可能知道你现在在哪个项目里干活。
114
+
115
+ ### 并发策略
116
+
117
+ - 所有写命令(`init` / `new` / `claim` / `set` / `close`)走同一套:**读 → 改内存 → 复查指纹 → 原子写**
118
+ - 临时文件名唯一(`mkstemp`),写完 `fsync` 再 `os.replace`,无论成败都清理临时文件
119
+ - key 按字母序落盘,减少两人同时新增时的文本冲突
120
+ - 冲突时**报错要求重跑**,不静默覆盖
121
+ - 脚本里**没有任何** `git pull` / `rebase` / `commit` —— 冲突不要靠拉取去"解决"
122
+
123
+ ### 结案是原子操作
124
+
125
+ `close` 一次完成三件事,**顺序是有意排的**:先写历史索引 → 再封卷过程文档 → **最后**才从状态表里移除。
126
+
127
+ 为什么是这个顺序:万一中途崩溃,最坏结果是「历史多了一行、状态表里任务还在」,重跑一次就对上了;反过来先删状态、后写历史,崩溃会让任务**既不在状态表、也不在历史** —— 记录凭空消失。
128
+
129
+ 落盘时任一步失败会**整体回滚**,并逐文件报告哪些恢复成功、哪些没成功(回滚本身失败时不会掩盖原始错误)。
130
+
131
+ ### 分类与标签为什么不设枚举
132
+
133
+ 本 skill 跨所有工作区使用(开发 / 运维 / 个人事务 / 调研备忘都算),任何固定枚举都覆盖不全、颗粒度难定;「让 Agent 判断会不准」也不能靠穷举分类消解。所以分两层、都自由:
134
+
135
+ - **`category` 单值**:答「这事是什么类型」。一条一值,`count by` 干净,是人眼扫视和看板统计的轴
136
+ - **`tags` 1~5 个**:答「这事涉及什么」(对象 / 领域 / 模块)。兜住长尾与新领域,写错了只影响一个切面
137
+
138
+ 两层都只硬校验**形态**(≤4 字 / 纯英文 ≤3 词 / ≤5 个),不校验语义 —— 语义 correctness 交给口径(SKILL.md 的归一化规则 + 正反示例)和巡检归并机制,不交给枚举。
139
+
140
+ ### category 精确匹配、tag 包含匹配
141
+
142
+ 两种查询语义不同是有意的:category 是统计轴,同值才能比较,必须精确;tag 是容错下钻,「bug」命中「bug修复」正是包含匹配要的效果 —— 它天然吃掉一部分同义变体,剩下的漂移靠 `tags --all` 暴露、`tags --rename` 归并。
143
+
144
+ ### 为什么只硬校验上限、不设标签数量下限
145
+
146
+ 设下限(如必须 3 个标签)会逼 Agent 凑数 —— 凑出来的正是噪声,与「容忍错误」的初衷相反。上限(≤5)与字数(≤4 字 / 英文 ≤3 词)是形态红线,必须硬校验;数量只引导、不强制。
147
+
148
+ ### 分类/标签进 history 列(2026-10-15 存量改造时加入)
149
+
150
+ 初版设计是「不进列、回链读」:history 解析写死 6 列,加列要迁移存量。用户评审时要求反转——已结案记录也要能按分类/标签查,于是升为 **8 列**(结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / 摘要 / 过程文档):
151
+
152
+ - `close` 从 task 直写分类/标签终值(`--category`/`--tags` 覆盖过的用覆盖值)
153
+ - 存量 4/5/6 列由 `upgrade_history` 升级为 8 列:分类/标签**回链该行 worklog 头部**读(`labels_for_log`,与 created 回填同一模式),读不到留空、不编造
154
+ - 回链没有废弃:`history` 子命令对仍是 4/5/6 列的旧行(未升级文件)继续回链兜底,`cmd_history` 里「列空才回链」
155
+
156
+ 代价说清:加列动了 parse/upgrade/close 全链路,靠「逐行按自己列数判别 + 不敢升级就按原列数追加」的老机制兜底;冒烟覆盖了 8 列写入、6 列升级(owner 剥 @ 防 `@@`)、4 列升级、坏行退回四条路径。
157
+
158
+ ### rename 为什么不碰已封卷文档
159
+
160
+ 与「封卷即不可变」同一哲学:rename 只改状态表与在办 worklog 头部;已结案记录的旧标签留在原文档里。这类残留在 `tags --all` 的频次表里可见,看板侧自行归并 —— 台账只做记录,不做历史改写。
161
+
162
+ ### 「分类/标签」行为什么由 render 动态插入
163
+
164
+ 不占模板占位符 ⇒ `assets/worklog.md` 与 `FALLBACK_TEMPLATE` 都不用改,两个模板永远一致;空值(存量任务、Agent 没总结)自动不写行,向后兼容零成本。生成、解析、覆盖三处共用 `label_line` 一个格式定义,防漂移。
165
+
166
+ ## 三、运行逻辑
167
+
168
+ ### 一次调用发生什么
169
+
170
+ ```mermaid
171
+ graph LR
172
+ U[用户说一句话] --> A[agent 读 SKILL.md 判断意图]
173
+ A -->|只读直接跑| R[list / show / validate / history]
174
+ A -->|先复述参数再确认| W[init / new / claim / set / close]
175
+ W --> P[ledger/progress.json]
176
+ W --> H[ledger/history/年月.md]
177
+ W --> M[ledger/worklog/年月/日-时分-说明-码.md]
178
+ A -->|直接编辑| M
179
+ A -->|只读查询| H
180
+ W -->|返回该任务文档全文| A
181
+ ```
182
+
183
+ ### 状态机
184
+
185
+ ```mermaid
186
+ stateDiagram-v2
187
+ [*] --> open: new(不带 --self)
188
+ [*] --> doing: new --self
189
+ open --> doing: claim(认领,写 owner)
190
+ doing --> blocked: set --status blocked
191
+ blocked --> doing: set --status doing
192
+ doing --> open: set --status open(退回未认领,清空 owner)
193
+ blocked --> open: set --status open
194
+ doing --> [*]: close(进历史)
195
+ blocked --> [*]: close(进历史)
196
+ note right of doing
197
+ set --owner 交接:owner 换成别人
198
+ (只有当前 owner 能执行)
199
+ end note
200
+ ```
201
+
202
+ - `done` **只能由 `close` 产生**;`set --status done` 会被拒绝
203
+ - **退回未认领**:owner 用 `set --status open` 把任务退回去,owner 清空、之后别人可以重新 `claim`;这条不能与 `--owner` 同时给
204
+ - `set --owner <某人>` 是**交接**,由当前 owner 执行,执行后 owner 换成那个人
205
+ - 状态表里不允许残留 `done`(`validate` 会报)
206
+
207
+ ### 命令一览
208
+
209
+ | 子命令 | 作用 | 需要 git 身份 | 会返回任务文档全文 |
210
+ |---|---|---|---|
211
+ | `init` | 建状态表 | 否 | 否 |
212
+ | `new` | 建任务并建档 | **是**(记 creator) | 是 |
213
+ | `claim` | 认领待认领任务 | **是** | 是 |
214
+ | `set` | 改状态 / 交接 | **是**(且必须是该任务的 owner) | 是 |
215
+ | `close` | 结案归档 | **是**(且必须是该任务的 owner) | 否 |
216
+ | `list` | 查在办任务 | 仅 `--mine` | 否 |
217
+ | `history` | 查已结案任务(只读) | 仅 `--mine` | 否(只给过程文档链接) |
218
+ | `show` | 单任务详情 | 否 | 是 |
219
+ | `validate` | 校验一致性 | 否 | 否 |
220
+
221
+ 参数细节看 `SKILL.md`,或直接跑 `<子命令> --help`。
222
+
223
+ ### 返回什么
224
+
225
+ | 命令 | 返回 |
226
+ |---|---|
227
+ | `new` / `claim` / `set` / `show` | 状态行 + 该任务过程文档的**路径与全文** |
228
+ | `close` | 结案结果与摘要 |
229
+ | `list` | 在办任务列表(不含正文,避免撑爆上下文) |
230
+ | `history` | 已结案紧凑列表(默认不含摘要,避免撑爆上下文) |
231
+
232
+ 返回文档全文是有意的:agent 拿到就能直接编辑,不用再去找文件、拼路径。
233
+ `history` 是唯一的例外:只回过程文档的**链接**,正文另读 —— 历史层摘要就是这套体系里唯一的「正文」,默认不吐。
234
+
235
+ ### 失败约定
236
+
237
+ | 情况 | 行为 |
238
+ |---|---|
239
+ | 成功 | 退出码 0 |
240
+ | 业务错误(参数值 / 状态 / 权限不对) | 输出到 stderr,**退出码 1**,消息以「错误:」开头 |
241
+ | 命令行参数错误(argparse) | 退出码 2 |
242
+ | 未预期的内部失败 | 输出到 stderr,**退出码 3**(与可纠正的业务错误区分开) |
243
+ | 用户中断 | 退出码 130 |
244
+ | 输出被下游截断(如 `\| head`) | 静默退出,退出码 0,不打 traceback |
245
+
246
+ **报错就停下并原样报告,不要绕过脚本直接改 `ledger/progress.json` 或历史层(`ledger/history/<年月>.md`)。**
247
+
248
+ ## 四、已知边界与待办
249
+
250
+ 1. 本 skill 位于工作区 `_Skills/task-ledger/`;是否安装到全局 skill 目录由使用者决定 —— 未安装时其它项目不会自动触发。
251
+ 2. `scripts/progress.py` 里的 `FALLBACK_TEMPLATE` 是**无注释的最小骨架**,只在 `assets/worklog.md` 读不到时启用 —— 两处注释多寡不同不是缺陷,**正例只放模板文件**。
package/skill/SKILL.md ADDED
@@ -0,0 +1,176 @@
1
+ ---
2
+ name: task-ledger
3
+ description: 管理任务进度(开发任务、个人事务、运维杂事、调研备忘等一切在办的事):新建、认领、交接、改状态、结案、查在办、查历史,状态存于 ledger/progress.json。当用户说「新建一个任务」「建个坑」「开个新活」「立项」「我要做 X」「记一下要做 X」「这事先记着」「这活我领了」「开始做 X」「卡住了」「先放一放」「转给某人」「这活做完了 / 结案」「现在该做什么」「我还有什么没做完」「谁在做这个」「某件老活是什么时候做的」「谁改过什么」「拉某人的工作日志」「上月 / 这周做过什么」时使用此技能;在带有 ledger/progress.json 的项目里开工之前,也用它查一下当前在办任务。非代码工作区(个人事务、运维杂事、调研、备忘)同样适用。
4
+ description_zh: "任务进度管理:新建 / 认领 / 交接 / 改状态 / 结案 / 查在办 / 查历史"
5
+ description_en: "Manage task progress: create, claim, hand over, update, close, query in-progress and closed history"
6
+ version: 1.0.0
7
+ allowed-tools: Read,Write,Bash,Grep
8
+ ---
9
+
10
+ # task-ledger
11
+
12
+ ## 它管什么
13
+
14
+ | 文件 | 装什么 | 谁写 |
15
+ |---|---|---|
16
+ | `ledger/progress.json` | 在办任务的状态:任务号 / 标题 / 创建人 / owner / 状态 / 时间 / **分类(`category`)与标签(`tags`)** / 过程文档路径(`worklog` 字段) | **只由脚本写** |
17
+ | `ledger/worklog/<年月>/<日>-<时分>-<说明>-<随机码>.md` | 该任务的过程正文,固定五节:背景 / 待办 / 下一步 / 过程 / 结论 | **建档只由脚本按模板做**,之后正文由 agent 直接编辑 |
18
+ | `ledger/history/<年月>.md` | 已结案任务,一行一条(8 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / **owner** / 摘要 / 过程文档);按**结案时间**分月。存量 `ledger/PROGRESS-HISTORY.md` 只读兼容、永不重写 | **只由脚本写**;查询走 `history` 子命令 |
19
+
20
+ 这三个文件之外不要另建副本。**不要直接编辑状态表和历史索引 —— 一律走脚本。**
21
+ 过程文档的**建档也一律走脚本**(`new`):脚本按模板生成五节骨架、把路径写进状态表;
22
+ 你只负责往里填正文,**不要自己拼文件名、也不要另建一个 md 顶替它**。
23
+
24
+ **台账 ≠ 产出物**:台账(`ledger/`)只装「什么时候建 / 谁在做 / 做到哪 / 什么时候结案」
25
+ 以及**指向成果的链接**;真正的成果(代码、方案、清单、报告)按**所在项目自己的约定**落盘,
26
+ **不进 `ledger/`**。两条链不交叉 —— 成果是给人用的,台账是给「未来的自己」查的。
27
+
28
+ ## 什么时候用
29
+
30
+ ✅ 该用:
31
+
32
+ - 「现在该做什么」「我还有什么没做完」「谁在做这个」
33
+ - 「新建一个任务」「我要做 X」
34
+ - 「这活我领了」「这个我来做」
35
+ - 「卡住了」「先放一放」「转给某人来做」
36
+ - 「这活做完了」「结案」
37
+ - 「某件老活是什么时候做的」
38
+ - 「谁改过什么」「拉某人的工作日志」
39
+ - 在已有 `ledger/progress.json` 的项目里开工之前,先查一眼在办任务
40
+
41
+ ❌ 不该用:
42
+
43
+ - 项目没有 `ledger/` 且只是临时一次性的事 → **不要自动 init**,先问用户
44
+ - 只想改某个任务的过程正文 → 那是编辑 Markdown:`show` 会把该文档**全文和路径一起**给你,直接在它上面改(文档已由脚本建好,不要另建一个)。**已结案**任务的过程文档别用 `show` 找(它可能已不在状态表)—— 用 `history` 拿到文档链接再读
45
+
46
+ ## 先判断意图,再选命令
47
+
48
+ | 用户说什么 | 意图 | 动作 |
49
+ |---|---|---|
50
+ | 「现在该做什么」「我还有啥没做完」 | 查自己的在办 | `list --mine` |
51
+ | 「谁在做 X」「有哪些在办任务」 | 查全部在办 | `list` |
52
+ | 「有没有卡住的」 | 按状态查 | `list --status blocked` |
53
+ | 「某件老活是什么时候做的」 | 查已结案 | `history`(只读,可直接跑) |
54
+ | 「谁改过什么」「拉某人的工作日志」 | 按人查已结案 | `history --owner "<名字>"`(**精确匹配**) |
55
+ | 「这周 / 这个月建了啥活」 | 按创建时间查已结案 | `history --since 2026-10-01` |
56
+ | 「新建一个任务」「我要做 X」 | 建任务 | `new` |
57
+ | 「这活我领了」「我来做这个」 | 认领 | `claim` |
58
+ | 「卡住了」「先放一放」 | 改状态 | `set --status blocked` |
59
+ | 「继续做」「解卡了」 | 改状态 | `set --status doing` |
60
+ | 「转给某人」「交接给某人」 | 换负责人 | `set --owner "<新负责人>"` |
61
+ | 「这活做完了」「验收通过」「结案」 | 结案归档 | `close`(**这句必须由用户说**) |
62
+ | 项目里还没有状态表 | 初始化 | `init` |
63
+ | 状态表看着不对劲 | 体检 | `validate` |
64
+
65
+ ## 命令怎么调
66
+
67
+ ```bash
68
+ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当前工作区> [参数]
69
+ ```
70
+
71
+ > 下表所有命令都省略了 `--repo`,**实际调用时必须带上当前工作区**。
72
+
73
+ - **脚本位置**:用「本 SKILL.md 所在目录」拼出 `scripts/progress.py` 的路径。**不要依赖任何框架注入的变量**(`{baseDir}` / `$SKILL_DIR` 这类各家 harness 各不相同、未必存在);若执行环境就在本 skill 目录内,可直接用 `scripts/progress.py`
74
+ - **`--repo` 传当前工作区(项目根)**:脚本只知道自己在哪,不可能知道你在哪个项目里干活,这一步必须由调用方给出
75
+ - 参数细节用 `<子命令> --help` 查
76
+
77
+ | 子命令 | 作用 | 关键参数 |
78
+ |---|---|---|
79
+ | `init` | 建 `ledger/progress.json` | — |
80
+ | `new` | 新建任务,并自动建过程文档 | `--title`(必填)、`--id`(可选)、`--self`、`--created`(可选,补录创建时间)、`--category` / `--tags`(可选,分类 / 标签,见下节口径) |
81
+ | `claim` | 认领一个待认领任务 | `--id`(必填) |
82
+ | `set` | 改状态 / 换负责人 | `--id`(必填)、`--status`、`--owner` |
83
+ | `close` | 结案:移出状态 + 追加历史 + 封卷过程文档 | `--id`、`--summary`(都必填)、`--finished`(可选,结案时间)、`--category` / `--tags`(可选,结案时覆盖修正一次) |
84
+ | `list` | 查询在办任务 | `--mine`、`--owner`、`--status`、`--since`、`--until`、`--category`(精确)、`--tag`(包含)、`--json` |
85
+ | `history` | 查已结案任务(只读) | `--owner`、`--mine`、`--since`、`--until`、`--limit`、`--category`(精确)、`--tag`(包含)、`--json`;另:`--count-only`(只数行数、封顶 1 万输出 `{count, capped}`,显示级统计)、`--months N`(只读最近 N 个月文件,不含存量平铺文件) |
86
+ | `show` | 单任务详情 | `--id`(必填) |
87
+ | `tags` | 分类/标签巡检与归并 | `--all`(全量频次表)、`--rename 旧 新`(归并在办任务的同值) |
88
+ | `validate` | 校验状态表与过程文档是否一致 | — |
89
+
90
+ ### 命令返回什么
91
+
92
+ | 命令 | 返回 |
93
+ |---|---|
94
+ | `new` / `claim` / `set` / `show` | 状态行 + **该任务过程文档的路径与全文** —— 拿到就能直接编辑,不用再去找文件、拼路径(`history` 是例外:只给链接不给全文) |
95
+ | `close` | 结案结果与摘要 |
96
+ | `list` | 在办任务列表(**不含**文档正文,避免撑爆上下文) |
97
+ | `history` | 已结案紧凑列表(结案时间 / 创建时间 / owner / 工作包 / 过程文档链接,行尾附所在历史文件),**默认不含摘要**;要摘要走 `--json` |
98
+
99
+ ### 几个必须知道的点
100
+
101
+ - **状态取值**:`open`(已建待认领)/ `doing` / `blocked` / `done`。**`done` 只能由 `close` 产生**,不要用 `set --status done`。
102
+ - **`new --self`** = 建完即认领(直接 `doing`);**不带 `--self`** = 空开,等别人 `claim`。
103
+ - **任务号自动生成**:创建时间(`年月日-时分`)+ 标题里的字母、数字、汉字 + 4 位随机码(如 `20261015-1430-登录接入-sso-7f3a`)。想指定就传 `--id`。
104
+ - **owner 取 `git config user.name`**:认领人**只能是当前 git 身份,不能由外部传入**;读不到就直接报错退出(提示「需要用户名才能执行」),不做任何兜底 —— 先让用户配好 git 身份。(`history` 只有 `--mine` 会取这个身份;`--owner <别人>` 是只读查询,不校验身份)
105
+ - **只能动自己名下的任务**:`set` 与 `close` 同样先取身份,再校验 `owner` 是不是你。**不是你的直接拒绝**;任务还是 `open`(没人认领)时也拒绝,提示先 `claim`。别人要动你的任务,必须你先交接(`set --owner`)。**`history` 是只读查询,不在此列** —— 查别人的已结案不需要对方交接。
106
+
107
+ ## 时间
108
+
109
+ - **精度**:任务号、worklog 目录与文件名只到**分**(`年月日-时分`);其余所有记录时间都到**秒**(`YYYY-MM-DD HH:MM:SS`)。
110
+ - **谁来给**:默认不传 —— 脚本取当前时刻(`new` 创建、`claim` / `set` 开始、`close` 结案)。**只在「要记的时间不是现在」时才传**:
111
+ - `close --finished`:用户明说这活是什么时候完成的(不传=当前时刻)。
112
+ - `new --created`:补录一个过去的创建时间(不传=当前时刻)。
113
+ - **怎么写**:格式随意、只要对 —— `2026-10-06`、`2026-10-06 14:30`、`2026-10-06 14:30:05` 都行,脚本补全到秒(没传秒补 `:00`,没传分秒补 `00:00`);传了但不对(如 `2026-13-40`)会被拒。
114
+ - **结案时间必须存在**:`close` 一定写(不传=当前)。
115
+ - **按时间筛**:`list` / `history` 都有 `--since`(起)/ `--until`(止),都按**创建时间**,可只给其一;只给日期时,起=当天 `00:00:00`、止=当天 `23:59:59`(含整天)。`history` 输出里**第一列是结案时间、第二列才是创建时间**,排序也按创建时间。
116
+
117
+ ## 过程文档的生命周期
118
+
119
+ 1. **建档** —— 执行 `new` 时,脚本**必须**按 `assets/worklog.md` 模板创建 `ledger/worklog/<年月>/<日>-<时分>-<说明>-<随机码>.md`(`<年月>` 取任务创建月份,如 `202610`;年月已经在目录名里,文件名不再重复)。路径同时写进状态表的 `worklog` 字段,查的时候不必靠推算。
120
+ 2. **推进** —— 这个 md 由 **agent 直接编辑**(它是正文,不是状态数据,不走脚本)。五节(背景 / 待办 / 下一步 / 过程 / 结论)**标题结构不能动**。**各写什么、写成什么样算对,模板注释里给了正例**,照它填即可。
121
+ 3. **交付(待验收)** —— 活干完了但**用户还没验收**时,任务仍留在 `doing`:worklog 的「待办」写「待用户验收」,「下一步」写清验收该看什么(哪条命令、哪个文件)。**这一段不产出状态变化,只写正文。**
122
+ 4. **封卷** —— 执行 `close` 时,脚本自动打封卷标记并把头部状态改为完成,此后不再修改。历史行按**结案时间**落 `ledger/history/<年月>.md`(存量 `ledger/PROGRESS-HISTORY.md` 只读、不再写入);封卷后要找回这篇文档:`history` 会给出它的链接与所在历史文件(只读、无需确认)。
123
+
124
+ ## 分类与标签(Agent 自行总结,不设枚举)
125
+
126
+ **为什么不设枚举**:本 skill 跨所有工作区(开发 / 运维 / 个人事务 / 调研备忘),任何固定分类都覆盖不全、颗粒度难定。所以分类与标签都由 Agent 在 `new` 建档时按本节口径**自行总结**,脚本只硬校验形态(≤4 字 / 纯英文 ≤3 词 / ≤5 个),不校验语义。
127
+
128
+ | 层 | 答什么 | 约束 | 用途 |
129
+ |---|---|---|---|
130
+ | `--category`(单值) | 这事**是什么类型**(缺陷修复 / 调研 / 建设 / 运维 / 事务 / 其他) | 只有一个;吃不准就写「其他」 | 人眼扫视 + 看板统计(一条一值,`count by` 干净) |
131
+ | `--tags`(1~5 个) | 这事**涉及什么**(对象 / 领域 / 模块,如 `镜像拉取`、`Hindsight`) | 至多 5 个;宁缺毋滥,**不凑数** | 下钻查询(包含匹配,容忍变体) |
132
+
133
+ 三条边界(用户确认,勿破):
134
+
135
+ 1. **口径错开**:tags 不许写 category 的同义词 —— category=缺陷修复 时,tags 别再写「bug修复」
136
+ 2. **数量软约束**:3~5 个是目标不是硬指标 —— 设下限会逼出凑数标签,凑的正是噪声;脚本只卡上限
137
+ 3. **统计与查询分工**:统计只认 category(精确匹配,同值才能比较);tags 只做查询(包含匹配,「bug」命中「bug修复」正是要的效果)
138
+
139
+ 写法口径(归一化,跨工作区才稳):
140
+
141
+ - 名词性短语,不带时态、不用动词收尾:写「缺陷修复」,不写「修复了」「修 bug」
142
+ - 中文优先,不中英混排;纯英文 ≤3 个单词(`fix a bug` 可以,`fix a login bug` 超了)
143
+ - 标签之间用**逗号**分隔 —— 空格是英文多词标签自身的,不当分隔符(`--tags "fix a bug,docker"`)
144
+ - 正例:`--category 缺陷修复 --tags "镜像拉取,超时重试"`;反例:`--category 已完成登录修复`、`--tags 修 bug`
145
+
146
+ 时机与修正:`new` 建档时一并总结(标题 + 背景语义最全);`close` 可用 `--category` / `--tags` 覆盖修正一次;日常漂移用 `tags --rename 旧 新` 归并(只改在办任务,已封卷文档不动)。看板侧先跑 `tags --all` 看全量频次与低频变体,再决定查什么、归并什么。已结案记录的分类/标签落在 history 行的「分类/标签」列(8 列结构,close 时从 task 终值写入)。
147
+
148
+ ## 写操作红线
149
+
150
+ `init` / `new` / `claim` / `set` / `close` 都有副作用,但**分两档**:
151
+
152
+ **一、动手类(`init` / `new` / `claim` / `set`)**:执行前先把完整参数复述给用户并取得确认(任务号、标题、负责人、目标状态)。不得先执行再补报告,也不得一次执行多条用户没逐条确认的写操作。
153
+
154
+ **二、`close` 是独立授权,不是做任务流程的最后一步**:
155
+
156
+ - 只能由用户**明确的验收 / 结案指令**触发(「验收通过」「这活结了吧」)。
157
+ - **agent 自己判断「我干完了」不算触发条件** —— 那是交付,不是结案。交付后停在 `doing`,把做了什么、`--summary` 打算怎么写交给用户,**等用户回话**(这段时间 worklog 怎么记见下节)。
158
+ - 用户没点头就不许先斩后奏,也不许替用户下验收结论。**宁可任务停在 `doing`,也不要悄悄进历史。**
159
+
160
+ `list` / `show` / `validate` / `history` 是只读,可以直接跑。
161
+
162
+ ## 失败处理
163
+
164
+ 脚本失败时以非 0 退出码 + stderr 说明原因:
165
+
166
+ | 退出码 | 含义 |
167
+ |---|---|
168
+ | 0 | 成功 |
169
+ | 1 | 业务错误(参数值 / 状态 / 权限不对,可纠正) |
170
+ | 2 | 命令行参数错误 |
171
+ | 3 | 未预期的内部失败(应上报,不要重试) |
172
+ | 130 | 被中断 |
173
+
174
+ **报错就停下并原样报告,不要绕过脚本直接改 `ledger/progress.json` 或历史层(`ledger/history/<年月>.md`)。**
175
+
176
+ 并发冲突(文件在操作期间被改动)会报错并中止 —— 重新执行一次即可,**不要用 `git pull` / `rebase` 去「解决」**。
@@ -0,0 +1,31 @@
1
+ # <id>:<title>
2
+
3
+ > **创建**:<created>(@<creator>)
4
+
5
+ <!-- 「> **分类**:… | **标签**:…」行由脚本按 new --category / --tags 自动插在创建行后;
6
+ 两者皆空则不写此行。close 时可用 --category / --tags 覆盖修正。 -->
7
+
8
+ ## 背景
9
+
10
+ <!-- 为什么要做这件事。写触发它的原话、现象或链接,不要写「优化 XX」这类过三个月看不懂的。
11
+ 例:用户说「导出的 Excel 中文列名乱码」(附原话 / 截图 / 报错) -->
12
+
13
+ ## 待办
14
+
15
+ <!-- 还没做的、卡在哪、依赖谁。做到一半的、没验证的、等别人给东西的,都算。
16
+ 例:等宿主 1.2 才能复测流式接口;错误分支尚未测 -->
17
+
18
+ ## 下一步
19
+
20
+ <!-- 写到具体做哪一步,不是「继续推进」。
21
+ 例:改 src/export.ts:88 的编码参数,然后跑一次冒烟 -->
22
+
23
+ ## 过程
24
+
25
+ <!-- 踩坑、定位、证据。带 revision 和 文件:行号 或符号名(行号会漂移)。
26
+ 例:rc4f2a1 的 src/host.ts:112 返回 null,是因为没等 ready 事件就发了请求 -->
27
+
28
+ ## 结论
29
+
30
+ <!-- 收尾时填:做成了什么、留了什么坑。
31
+ 例:已修复,但只覆盖 UTF-8;GBK 另开任务 -->
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "task-ledger",
3
+ "version": "1.0.1",
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
+ "keywords": [
6
+ "skill",
7
+ "agent-skill",
8
+ "task-ledger",
9
+ "progress",
10
+ "todo"
11
+ ],
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/cq-guojia/dsh-task-ledger.git"
16
+ },
17
+ "files": [
18
+ "SKILL.md",
19
+ "README.md",
20
+ "scripts/",
21
+ "assets/"
22
+ ]
23
+ }