task-ledger 1.1.0 → 1.2.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 +19 -12
- package/SKILL.md +85 -132
- package/assets/worklog.md +11 -12
- package/package.json +3 -2
- package/references/log-note.md +47 -0
- package/references/state-model.md +43 -0
- package/references/taxonomy.md +26 -0
- package/references/troubleshooting.md +26 -0
- package/references/worklog.md +37 -0
- package/scripts/progress.py +144 -35
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|---|---|---|
|
|
12
12
|
| 在办任务的状态 | `ledger/progress.json` | **只由脚本写** |
|
|
13
13
|
| 每个任务的过程正文 | `ledger/worklog/<年月>/<日>-<时分>-<说明>-<随机码>.md` | **建档只由脚本按模板做**,之后正文 **agent 直接编辑** |
|
|
14
|
-
| 已结案任务,一行一条(
|
|
14
|
+
| 已结案任务,一行一条(9 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / creator / 摘要 / 过程文档) | `ledger/history/<年月>.md`(按**结案时间**分月;存量 `ledger/PROGRESS-HISTORY.md` 只读兼容、永不重写) | **只由脚本写**;查询走 `history` 子命令 |
|
|
15
15
|
|
|
16
16
|
覆盖的动作:建任务、认领、交接、改状态、结案、查在办、查历史、体检。
|
|
17
17
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
### 状态用 JSON,不用 Markdown
|
|
37
37
|
|
|
38
|
-
状态要频繁改(`
|
|
38
|
+
状态要频繁改(`open` → `doing` → 结案),Markdown 里改一个词很容易顺手改到别处。JSON 结构化、字段固定、一次只动一个值,改动面最小。
|
|
39
39
|
|
|
40
40
|
### 状态与过程正文分离
|
|
41
41
|
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
|
|
103
103
|
### 只能改自己的任务
|
|
104
104
|
|
|
105
|
-
`set` 和 `
|
|
105
|
+
`set`、`close` 和 `log` 都先取身份,再校验「这个任务的 owner 是不是你」:**不是就拒绝**;任务还没人认领(`open`)也拒绝,提示先 `claim`。要接手就走 `set --owner` 交接 —— 一次显式动作。
|
|
106
106
|
|
|
107
107
|
理由有两层:一是别人替你结案,你会失去对进度的掌控;二是如果只取了身份却不校验归属,`owner` 这个字段就只是个装饰品。
|
|
108
108
|
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
|
|
115
115
|
### 并发策略
|
|
116
116
|
|
|
117
|
-
- 所有写命令(`init` / `new` / `claim` / `set` / `close`)走同一套:**读 → 改内存 → 复查指纹 → 原子写**
|
|
117
|
+
- 所有写命令(`init` / `new` / `claim` / `set` / `log` / `close`)走同一套:**读 → 改内存 → 复查指纹 → 原子写**
|
|
118
118
|
- 临时文件名唯一(`mkstemp`),写完 `fsync` 再 `os.replace`,无论成败都清理临时文件
|
|
119
119
|
- key 按字母序落盘,减少两人同时新增时的文本冲突
|
|
120
120
|
- 冲突时**报错要求重跑**,不静默覆盖
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
graph LR
|
|
172
172
|
U[用户说一句话] --> A[agent 读 SKILL.md 判断意图]
|
|
173
173
|
A -->|只读直接跑| R[list / show / validate / history]
|
|
174
|
-
A
|
|
174
|
+
A -->|写操作| W[init / new / claim / set / log / close]
|
|
175
175
|
W --> P[ledger/progress.json]
|
|
176
176
|
W --> H[ledger/history/年月.md]
|
|
177
177
|
W --> M[ledger/worklog/年月/日-时分-说明-码.md]
|
|
@@ -187,21 +187,19 @@ stateDiagram-v2
|
|
|
187
187
|
[*] --> open: new(不带 --self)
|
|
188
188
|
[*] --> doing: new --self
|
|
189
189
|
open --> doing: claim(认领,写 owner)
|
|
190
|
-
doing -->
|
|
191
|
-
blocked --> doing: set --status doing
|
|
192
|
-
doing --> open: set --status open(退回未认领,清空 owner)
|
|
193
|
-
blocked --> open: set --status open
|
|
190
|
+
doing --> open: set --status open(释放认领,清空 owner)
|
|
194
191
|
doing --> [*]: close(进历史)
|
|
195
|
-
blocked --> [*]: close(进历史)
|
|
196
192
|
note right of doing
|
|
197
193
|
set --owner 交接:owner 换成别人
|
|
198
194
|
(只有当前 owner 能执行)
|
|
195
|
+
阻塞 = 接任务满 7 天;停滞 = 最后更新满 3 天
|
|
196
|
+
(都由看板按时间算,不是状态)
|
|
199
197
|
end note
|
|
200
198
|
```
|
|
201
199
|
|
|
202
200
|
- `done` **只能由 `close` 产生**;`set --status done` 会被拒绝
|
|
203
201
|
- **退回未认领**:owner 用 `set --status open` 把任务退回去,owner 清空、之后别人可以重新 `claim`;这条不能与 `--owner` 同时给
|
|
204
|
-
- `set --owner <某人>` 是**交接**,由当前 owner 执行,执行后 owner
|
|
202
|
+
- `set --owner <某人>` 是**交接**,由当前 owner 执行,执行后 owner 换成那个人。**该能力只为将来的 UI 保留**:SKILL.md 不向 agent 暴露它,换人一律走「释放 → 认领」,避免随便编一个名字挂上去
|
|
205
203
|
- 状态表里不允许残留 `done`(`validate` 会报)
|
|
206
204
|
|
|
207
205
|
### 命令一览
|
|
@@ -211,11 +209,13 @@ stateDiagram-v2
|
|
|
211
209
|
| `init` | 建状态表 | 否 | 否 |
|
|
212
210
|
| `new` | 建任务并建档 | **是**(记 creator) | 是 |
|
|
213
211
|
| `claim` | 认领待认领任务 | **是** | 是 |
|
|
214
|
-
| `set` | 改状态 / 交接 | **是**(且必须是该任务的 owner) | 是 |
|
|
212
|
+
| `set` | 改状态 / 交接 / 释放认领(`--status open`) | **是**(且必须是该任务的 owner) | 是 |
|
|
213
|
+
| `log` | 往 worklog「执行记录」补一条 | **是**(且必须是该任务的 owner) | 否(只回一行确认) |
|
|
215
214
|
| `close` | 结案归档 | **是**(且必须是该任务的 owner) | 否 |
|
|
216
215
|
| `list` | 查在办任务 | 仅 `--mine` | 否 |
|
|
217
216
|
| `history` | 查已结案任务(只读) | 仅 `--mine` | 否(只给过程文档链接) |
|
|
218
217
|
| `show` | 单任务详情 | 否 | 是 |
|
|
218
|
+
| `tags` | 分类/标签巡检(`--all` 只读)/ 归并(`--rename` 批量写) | 否 | 否 |
|
|
219
219
|
| `validate` | 校验一致性 | 否 | 否 |
|
|
220
220
|
|
|
221
221
|
参数细节看 `SKILL.md`,或直接跑 `<子命令> --help`。
|
|
@@ -225,6 +225,7 @@ stateDiagram-v2
|
|
|
225
225
|
| 命令 | 返回 |
|
|
226
226
|
|---|---|
|
|
227
227
|
| `new` / `claim` / `set` / `show` | 状态行 + 该任务过程文档的**路径与全文** |
|
|
228
|
+
| `log` | 一行确认(记了什么) |
|
|
228
229
|
| `close` | 结案结果与摘要 |
|
|
229
230
|
| `list` | 在办任务列表(不含正文,避免撑爆上下文) |
|
|
230
231
|
| `history` | 已结案紧凑列表(默认不含摘要,避免撑爆上下文) |
|
|
@@ -249,3 +250,9 @@ stateDiagram-v2
|
|
|
249
250
|
|
|
250
251
|
1. 本 skill 位于工作区 `_Skills/task-ledger/`;是否安装到全局 skill 目录由使用者决定 —— 未安装时其它项目不会自动触发。
|
|
251
252
|
2. `scripts/progress.py` 里的 `FALLBACK_TEMPLATE` 是**无注释的最小骨架**,只在 `assets/worklog.md` 读不到时启用 —— 两处注释多寡不同不是缺陷,**正例只放模板文件**。
|
|
253
|
+
|
|
254
|
+
## 五、维护者须知
|
|
255
|
+
|
|
256
|
+
- `SKILL.md` 的 frontmatter `version` 与 `package.json` 的 `version` 保持同值;发版时一起改,打 `skill-v<version>` tag。
|
|
257
|
+
- **`SKILL.md` 是每次会话整份注入的**:只放「不做就会写错数据」的规则与路由,细节进 `references/`(按需读,索引在 SKILL.md 末尾)。加体积前先自问「脚本或 `--help` 能不能兜住」。
|
|
258
|
+
- 改完跑 `cd packages/plugin && npm run verify`(build + typecheck + smoke);`copy-skill` 会把 `scripts/` `assets/` `references/` 一并拷进插件内嵌副本,不要直接改那份拷贝。
|
package/SKILL.md
CHANGED
|
@@ -1,176 +1,129 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: task-ledger
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
description_en: "Manage task progress: create, claim, hand over, update, close, query in-progress and closed history"
|
|
6
|
-
version: 1.0.0
|
|
3
|
+
description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)时加载;对某个具体任务要求结案或退回时,只认用户的明确指令;只是改代码、讨论方案时不要用。
|
|
4
|
+
version: 1.2.1
|
|
7
5
|
allowed-tools: Read,Write,Bash,Grep
|
|
8
6
|
---
|
|
9
7
|
|
|
10
8
|
# task-ledger
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
台账只管三件事:**谁在做、做到哪、什么时候建 / 结**。状态只在 `progress.json`,叙事只在 worklog,**全部经脚本改**。
|
|
13
11
|
|
|
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` 子命令 |
|
|
12
|
+
## 三个文件
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
| 文件 | 装什么 |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `ledger/progress.json` | 在办任务:标题 / owner / 状态 / 时间(`created` `started` `updated` `finished`)/ 分类标签 / 过程文档路径。**只由脚本写** |
|
|
17
|
+
| `ledger/worklog/…md` | 任务正文,六节:背景 / 待办 / 下一步 / 过程 / 结论 / **执行记录**(末节) |
|
|
18
|
+
| `ledger/history/<年月>.md` | 已结案,一行一条。**只由脚本写** |
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
前五节你直接编辑;**末节「执行记录」只由脚本追加,一个字都不许手写**。
|
|
21
|
+
不另建副本、不手改状态表和历史、不自己拼 worklog 文件名(`new` 会建好并给路径)。
|
|
22
|
+
**台账 ≠ 产出物**:成果按所在项目自己的约定落盘,台账只放「建/做/结」和指向成果的链接。
|
|
27
23
|
|
|
28
24
|
## 什么时候用
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- 「现在该做什么」「我还有什么没做完」「谁在做这个」
|
|
33
|
-
- 「新建一个任务」「我要做 X」
|
|
34
|
-
- 「这活我领了」「这个我来做」
|
|
35
|
-
- 「卡住了」「先放一放」「转给某人来做」
|
|
36
|
-
- 「这活做完了」「结案」
|
|
37
|
-
- 「某件老活是什么时候做的」
|
|
38
|
-
- 「谁改过什么」「拉某人的工作日志」
|
|
39
|
-
- 在已有 `ledger/progress.json` 的项目里开工之前,先查一眼在办任务
|
|
40
|
-
|
|
41
|
-
❌ 不该用:
|
|
26
|
+
❌ 项目没有 `ledger/` 且只是临时一次性的事 → 不要自动 `init`,先问。
|
|
27
|
+
❌ 只想改某任务正文 → 用 `show` 拿路径与全文直接改,不要另建 md;已结案的用 `history` 找链接。
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
- 只想改某个任务的过程正文 → 那是编辑 Markdown:`show` 会把该文档**全文和路径一起**给你,直接在它上面改(文档已由脚本建好,不要另建一个)。**已结案**任务的过程文档别用 `show` 找(它可能已不在状态表)—— 用 `history` 拿到文档链接再读
|
|
29
|
+
## 意图 → 命令
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
| 「卡住了」「先放一放」 |
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
31
|
+
| 用户说 | 动作 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| 「现在该做什么」「我还有啥没做完」 | `list --mine` |
|
|
34
|
+
| 「谁在做 X」「有哪些在办」 | `list`;在等认领的加 `--status open` |
|
|
35
|
+
| 「这个任务到哪一步了」「这轮做了什么」 | `show --id`(任务 JSON + worklog 全文) |
|
|
36
|
+
| 「某件老活什么时候做的」「谁做过哪些活」 | `history`(可按 `--owner` / `--since` 筛) |
|
|
37
|
+
| 「哪些任务停太久」 | `list --json`:`updated` 满 3 天=停滞、`started` 满 7 天=阻塞(看板算,别改状态) |
|
|
38
|
+
| 「新建一个任务」 | `new`(`--self` = 建完即认领) |
|
|
39
|
+
| 「这活我领了」 | `claim`(只对 `open` 有效) |
|
|
40
|
+
| 「开始做 / 接着做 X」 | 还是 `open` 就先 `claim`;已是 `doing` 就直接改正文 + `log` |
|
|
41
|
+
| 「继续做」「推进一下」 | 改「下一步 / 过程」,再 `log --note "本轮做了 X,下一步 Y"` |
|
|
42
|
+
| 「记一下这轮」「这段做完了」 | `log --id <任务号> --note "…"`(agent 可主动做) |
|
|
43
|
+
| 「卡住了」「先放一放」 | 写清卡在哪(不改状态),再 `log` |
|
|
44
|
+
| 「退回待认领」「让别人接」 | `set --status open`(记 `release`),回待认领 |
|
|
45
|
+
| 「转给某人」 | **skill 不换人**:你先释放,对方自己 `claim`;不许替用户写别人的名字 |
|
|
46
|
+
| 「彻底不做了」「取消这个任务」 | `close --summary "放弃:原因"`(需用户明说) |
|
|
47
|
+
| 「做完了」「验收通过」「结案」 | `close`(需用户明说,见红线) |
|
|
48
|
+
| 「把某任务过程补一下」 | `show --id` → 改前五节 → `log` |
|
|
49
|
+
| 项目还没有台账 / 状态表可疑 | `init`(先问)/ `validate` |
|
|
50
|
+
| 「标签太乱」「两个标签一个意思」 | `tags --all` 看频次 → `tags --rename 旧 新` |
|
|
64
51
|
|
|
65
52
|
## 命令怎么调
|
|
66
53
|
|
|
67
54
|
```bash
|
|
68
|
-
python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当前工作区>
|
|
55
|
+
python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当前工作区>
|
|
69
56
|
```
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- **`--repo` 传当前工作区(项目根)**:脚本只知道自己在哪,不可能知道你在哪个项目里干活,这一步必须由调用方给出
|
|
75
|
-
- 参数细节用 `<子命令> --help` 查
|
|
58
|
+
子命令:`init` `new` `claim` `set` `log` `close` `list` `history` `show` `tags` `validate`。
|
|
59
|
+
参数细节一律看 `<子命令> --help`(不要凭记忆编参数)。
|
|
60
|
+
写类命令返回 worklog 路径 + 全文;`list` / `history` 只返回摘要,不吐正文。
|
|
76
61
|
|
|
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` |
|
|
62
|
+
## 铁律
|
|
98
63
|
|
|
99
|
-
|
|
64
|
+
- **状态只有 `open` / `doing`**;`done` 只能由 `close` 产生。**没有 `blocked` 状态**:阻塞 = `started` 满 7 天、停滞 = `updated` 满 3 天,都由看板按时间算,`progress.json` 里不存、不手改;卡在哪写进正文。
|
|
65
|
+
- **`log` 就是刷新 `updated` 的动作**:该记不记,任务会被看板标成停滞。
|
|
66
|
+
- **owner = 当前 `git config user.name`**,不能由外部传入;读不到就报错(先让用户配 git)。**只能动自己名下**的任务(`open` 的先 `claim`),不是你的直接拒绝 —— 不要绕过脚本改 JSON。
|
|
67
|
+
- **换人只能「释放 → 认领」**:skill 不提供交接,随便写的名字都是假的。
|
|
68
|
+
- **「执行记录」禁手改**:只由脚本按 `new / claim / set / release / log / close` 追加,只增不改;手写=伪造时间线,且不刷新 `updated`。
|
|
69
|
+
- **升级前建的老 worklog 没有这一节**:不用你补,脚本第一次记时自动在文末补一节;文档丢失时 `log` 报错(`updated` 已刷新),用 `show` 查路径,**不要手写**。
|
|
70
|
+
- **旧数据里的 `blocked`**:读侧当进行中;`list --status blocked` 可捞出,用 `set --status doing` 归位。
|
|
100
71
|
|
|
101
|
-
|
|
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` 是只读查询,不在此列** —— 查别人的已结案不需要对方交接。
|
|
72
|
+
细节见 `references/state-model.md`。
|
|
106
73
|
|
|
107
|
-
##
|
|
74
|
+
## 过程文档
|
|
108
75
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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` 输出里**第一列是结案时间、第二列才是创建时间**,排序也按创建时间。
|
|
76
|
+
1. **建档**:`new` 按模板建六节骨架并把路径写进状态表。
|
|
77
|
+
2. **推进**:你直接编辑前五节,标题不许改名;改完立刻 `log --note "这轮做了什么"`。
|
|
78
|
+
3. **交付(待验收)**:任务仍留 `doing`,「待办」写「待用户验收」,「下一步」写清验收该看什么;这轮**照样要走收工三步**。结论只写可验证事实,评价等用户验收。
|
|
79
|
+
4. **封卷**:`close` 打封卷标记,此后不再改;文档靠 `history` 给的回链找回。
|
|
116
80
|
|
|
117
|
-
|
|
81
|
+
细节见 `references/worklog.md`。
|
|
118
82
|
|
|
119
|
-
|
|
120
|
-
2. **推进** —— 这个 md 由 **agent 直接编辑**(它是正文,不是状态数据,不走脚本)。五节(背景 / 待办 / 下一步 / 过程 / 结论)**标题结构不能动**。**各写什么、写成什么样算对,模板注释里给了正例**,照它填即可。
|
|
121
|
-
3. **交付(待验收)** —— 活干完了但**用户还没验收**时,任务仍留在 `doing`:worklog 的「待办」写「待用户验收」,「下一步」写清验收该看什么(哪条命令、哪个文件)。**这一段不产出状态变化,只写正文。**
|
|
122
|
-
4. **封卷** —— 执行 `close` 时,脚本自动打封卷标记并把头部状态改为完成,此后不再修改。历史行按**结案时间**落 `ledger/history/<年月>.md`(存量 `ledger/PROGRESS-HISTORY.md` 只读、不再写入);封卷后要找回这篇文档:`history` 会给出它的链接与所在历史文件(只读、无需确认)。
|
|
83
|
+
## 每次收工必做(「收工」= 这一轮对用户有可见产出)
|
|
123
84
|
|
|
124
|
-
|
|
85
|
+
1. 改正文:这轮变化的事实都落笔,「下一步」写具体动作,不写「继续推进」。
|
|
86
|
+
2. 跑 `log`:一轮至少一条,正文没变也要记(同时重写「下一步」)。
|
|
87
|
+
3. 回读末行(`tail -n 3 <路径>` 或 `show --id`):时间 = 现在、说明 = 这轮的事。
|
|
125
88
|
|
|
126
|
-
|
|
89
|
+
交付前必须走完这三步;不走,这轮等于没发生,看板还会标停滞。
|
|
127
90
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| `--tags`(1~5 个) | 这事**涉及什么**(对象 / 领域 / 模块,如 `镜像拉取`、`Hindsight`) | 至多 5 个;宁缺毋滥,**不凑数** | 下钻查询(包含匹配,容忍变体) |
|
|
91
|
+
**什么算一个节点**:明天你自己想不起来、或换个人接手会看不出的事,就记一条;一次状态/结论变化对应一条,不对应一次工具调用。
|
|
92
|
+
`--note` 一句一条、约 60 字、含命令 / `文件:行号` / 符号名 / 结论之一。
|
|
93
|
+
不记:读文件、跑 `list`、改错别字、依赖锁文件变更。
|
|
132
94
|
|
|
133
|
-
|
|
95
|
+
细节与正反例见 `references/log-note.md`。
|
|
134
96
|
|
|
135
|
-
|
|
136
|
-
2. **数量软约束**:3~5 个是目标不是硬指标 —— 设下限会逼出凑数标签,凑的正是噪声;脚本只卡上限
|
|
137
|
-
3. **统计与查询分工**:统计只认 category(精确匹配,同值才能比较);tags 只做查询(包含匹配,「bug」命中「bug修复」正是要的效果)
|
|
97
|
+
## 分类与标签
|
|
138
98
|
|
|
139
|
-
|
|
99
|
+
`new` 时自行总结:`--category` 答「是什么类型」(单值,≤4 字,吃不准写「其他」);`--tags` 答「涉及什么」(0~5 个,写不出就留空)。
|
|
100
|
+
口径错开:tags 不许写 category 的同义词。统计只认 category,查询用 tags(包含匹配)。
|
|
140
101
|
|
|
141
|
-
|
|
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 终值写入)。
|
|
102
|
+
细节见 `references/taxonomy.md`。
|
|
147
103
|
|
|
148
104
|
## 写操作红线
|
|
149
105
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
106
|
+
| 命令 | 档位 | 要求 |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `init` / `new` / `claim` / `set` | 动手类 | 执行前把完整参数复述给用户并取得确认 |
|
|
109
|
+
| `log` | 免确认 | 本任务的推进 / 卡点 / 交付记录直接跑,跑完一句话交代「已记执行记录:…」;要替用户下结论、或替别人记,先问 |
|
|
110
|
+
| `close` | 独立授权 | 只认用户明确的验收 / 结案 / 放弃指令;**agent 自判「干完了」不算** —— 那是交付,停在 `doing` 等用户回话 |
|
|
111
|
+
| `tags --rename` | 批量写 | 先 `tags --all`,把影响条数与任务号报给用户,确认后再改 |
|
|
112
|
+
| `list` / `show` / `history` / `validate` / `tags --all` | 只读 | 直接跑 |
|
|
155
113
|
|
|
156
|
-
|
|
157
|
-
- **agent 自己判断「我干完了」不算触发条件** —— 那是交付,不是结案。交付后停在 `doing`,把做了什么、`--summary` 打算怎么写交给用户,**等用户回话**(这段时间 worklog 怎么记见下节)。
|
|
158
|
-
- 用户没点头就不许先斩后奏,也不许替用户下验收结论。**宁可任务停在 `doing`,也不要悄悄进历史。**
|
|
114
|
+
## 出错即停
|
|
159
115
|
|
|
160
|
-
|
|
116
|
+
非 0 退出就停手、原样报告,**不要绕过脚本手改 `progress.json` 或历史层**。
|
|
117
|
+
并发冲突(文件被改动)重跑一次即可,不要用 `git pull` / `rebase` 去「解决」。
|
|
161
118
|
|
|
162
|
-
|
|
119
|
+
细节见 `references/troubleshooting.md`。
|
|
163
120
|
|
|
164
|
-
|
|
121
|
+
## references(按需读,别全读)
|
|
165
122
|
|
|
166
|
-
|
|
|
123
|
+
| 何时读 | 文件 |
|
|
167
124
|
|---|---|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
174
|
-
**报错就停下并原样报告,不要绕过脚本直接改 `ledger/progress.json` 或历史层(`ledger/history/<年月>.md`)。**
|
|
175
|
-
|
|
176
|
-
并发冲突(文件在操作期间被改动)会报错并中止 —— 重新执行一次即可,**不要用 `git pull` / `rebase` 去「解决」**。
|
|
125
|
+
| 要传 `--created` / `--finished` 补录时间、看到旧 `blocked`、答「停太久」要解释口径 | `references/state-model.md` |
|
|
126
|
+
| 填正文前、补老任务文档、`log` 报错找不到文档 | `references/worklog.md` |
|
|
127
|
+
| 拿不准一条记录够不够格、`--note` 怎么写 | `references/log-note.md` |
|
|
128
|
+
| 写分类/标签、用户说标签太乱要归并 | `references/taxonomy.md` |
|
|
129
|
+
| 任何非 0 退出、并发冲突、`validate` 报错 | `references/troubleshooting.md` |
|
package/assets/worklog.md
CHANGED
|
@@ -2,30 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
> **创建**:<created>(@<creator>)
|
|
4
4
|
|
|
5
|
-
<!-- 「> **分类**:… | **标签**:…」行由脚本按 new --category / --tags
|
|
6
|
-
两者皆空则不写此行。close 时可用 --category / --tags 覆盖修正。 -->
|
|
5
|
+
<!-- 「> **分类**:… | **标签**:…」行由脚本按 new --category / --tags 插入;两者皆空则不写。 -->
|
|
7
6
|
|
|
8
7
|
## 背景
|
|
9
8
|
|
|
10
|
-
<!--
|
|
11
|
-
例:用户说「导出的 Excel 中文列名乱码」(附原话 / 截图 / 报错) -->
|
|
9
|
+
<!-- 为什么做:触发它的原话 / 现象 / 链接 -->
|
|
12
10
|
|
|
13
11
|
## 待办
|
|
14
12
|
|
|
15
|
-
<!--
|
|
16
|
-
例:等宿主 1.2 才能复测流式接口;错误分支尚未测 -->
|
|
13
|
+
<!-- 还没做的、没验证的、等别人给东西的 -->
|
|
17
14
|
|
|
18
15
|
## 下一步
|
|
19
16
|
|
|
20
|
-
<!--
|
|
21
|
-
例:改 src/export.ts:88 的编码参数,然后跑一次冒烟 -->
|
|
17
|
+
<!-- 下一个具体动作(文件:行号 级别),不要写「继续推进」 -->
|
|
22
18
|
|
|
23
19
|
## 过程
|
|
24
20
|
|
|
25
|
-
<!--
|
|
26
|
-
例:rc4f2a1 的 src/host.ts:112 返回 null,是因为没等 ready 事件就发了请求 -->
|
|
21
|
+
<!-- 踩坑与证据:带 revision + 文件:行号 或符号名 -->
|
|
27
22
|
|
|
28
23
|
## 结论
|
|
29
24
|
|
|
30
|
-
<!--
|
|
31
|
-
|
|
25
|
+
<!-- 做成了什么、留了什么坑 -->
|
|
26
|
+
|
|
27
|
+
## 执行记录
|
|
28
|
+
|
|
29
|
+
<!-- 本节由脚本维护:new / claim / set / release / log / close 自动追加,请勿手改。
|
|
30
|
+
补记用 progress.py log --id <任务号> --note "一句话"。 -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"SKILL.md",
|
|
19
19
|
"README.md",
|
|
20
20
|
"scripts/",
|
|
21
|
-
"assets/"
|
|
21
|
+
"assets/",
|
|
22
|
+
"references/"
|
|
22
23
|
]
|
|
23
24
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 执行记录:什么算一个节点
|
|
2
|
+
|
|
3
|
+
> TL;DR:一件事「明天你想不起来、换个人接手看不出」就记一条;一次状态/结论变化对应一条,不对应一次工具调用。`--note` 一句 60 字内,必须含具体对象。
|
|
4
|
+
|
|
5
|
+
## 判据
|
|
6
|
+
|
|
7
|
+
**明天你自己想不起来、或换个人接手会看不出** —— 就是一个节点,记一条。
|
|
8
|
+
一条 `log` 对应**一次状态变化或一次结论变化**,不对应一次工具调用。
|
|
9
|
+
|
|
10
|
+
## 该记
|
|
11
|
+
|
|
12
|
+
| 触发 | `--note` 写成什么 |
|
|
13
|
+
|---|---|
|
|
14
|
+
| 用户提了新要求 / 改了目标 | 新要求是什么,覆盖了哪条旧做法 |
|
|
15
|
+
| 方案变更 / 方案被排除 | 新方案(或排除了哪个)+ 为什么 |
|
|
16
|
+
| 改完一批文件 | 动了哪几个模块、验证没有 |
|
|
17
|
+
| 跑完验证(**通过或失败都算**) | 跑了什么、结果、下一步是否因此变 |
|
|
18
|
+
| 被卡住 / 停下 / 等外部 | 卡在哪一环、缺什么、等谁 |
|
|
19
|
+
| 拿到关键外部事实(版本 / 接口形状 / 已核实结论) | 结论 + 来源标识,免得下次重新查 |
|
|
20
|
+
| 交付(待验收) | 交付了什么、验收该看哪 |
|
|
21
|
+
| 释放 / 重新认领 | 脚本已自动记,别重复记 |
|
|
22
|
+
|
|
23
|
+
## 不该记
|
|
24
|
+
|
|
25
|
+
读文件、查命令、跑 `list`、想了一下;纯格式化 / 改错别字 / 依赖锁文件自动变更 —— 这些留在会话或 git 里。
|
|
26
|
+
|
|
27
|
+
## 写法
|
|
28
|
+
|
|
29
|
+
- 一句一条,`--note` 会被压成单行;**第一句说明白做了什么**,约 60 字以内,细节写正文对应小节。
|
|
30
|
+
- 一轮做了明显不相关的两件事 → 记两条,别拼成一条。
|
|
31
|
+
- 自检:一条记录至少能回答「动了什么」—— 含命令 / `文件:行号` / 符号名 / 结论之一。
|
|
32
|
+
|
|
33
|
+
正例:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
--note "把 home.service 的停滞口径改成 updated 满 3 天(home.service.ts:41-52)"
|
|
37
|
+
--note "验证通过:verify 25 项;下一步补 UI 的 overdue 标记"
|
|
38
|
+
--note "卡在远端鉴权,缺 deploy key,等用户给"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
反例:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
--note "继续推进" # 零信息量
|
|
45
|
+
--note "我改了代码,应该没问题了" # 无具体对象 + 替用户下结论
|
|
46
|
+
--note "改了 a.ts,又跑了测试,还回了消息" # 三件事挤一条
|
|
47
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 状态与时间模型
|
|
2
|
+
|
|
3
|
+
> TL;DR:状态只有 `open` / `doing`;`done` 由 `close` 产生。阻塞 = `started` 满 7 天、停滞 = `updated` 满 3 天,看板算、不存字段。`updated` 由 `new/claim/set/log` 刷新。owner 只取当前 git 身份。
|
|
4
|
+
|
|
5
|
+
## 状态机
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
open ──claim──> doing ──close──> ledger/history(移出状态表)
|
|
9
|
+
doing ──set --status open──> open # 释放认领,执行记录类型 release
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- `open`:已建、等认领。`doing`:已认领、在做。`done` 只由 `close` 产生。
|
|
13
|
+
- **没有 `blocked` 状态**(2026-10-19 取消)。旧数据里残留的 `blocked` 读侧当进行中:`list --status blocked` 可捞出,用 `set --status doing` 归位;新写入一律拒绝。
|
|
14
|
+
|
|
15
|
+
## 两个标记(看板计算,不是状态)
|
|
16
|
+
|
|
17
|
+
| 标记 | 依据 | 阈值 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 阻塞 | `started`(接任务)至今,中途更新不影响 | 满 7 天 |
|
|
20
|
+
| 停滞 | `updated`(最后推进)至今 | 满 3 天 |
|
|
21
|
+
|
|
22
|
+
- 真源:`packages/plugin/src/host/services/home.service.ts` 的 `OVERDUE_DAYS = 7` / `STALE_DAYS = 3`;看板随响应下发 `staleDays`,界面文案不硬编码。
|
|
23
|
+
- 脚本不算这两个值,`progress.json` 里也不存;agent 要回答「哪些停太久」时用 `list --json` 的 `updated` / `started` 与当前时间比。
|
|
24
|
+
- 老数据没有 `updated` 时,读侧回退到 `started`,再到 `created`。
|
|
25
|
+
|
|
26
|
+
## 时间
|
|
27
|
+
|
|
28
|
+
- **精度**:任务号、worklog 目录与文件名到分(`年月日-时分`);其余记录到秒(`YYYY-MM-DD HH:MM:SS`)。
|
|
29
|
+
- **默认不传**:`new` 创建、`claim` / `set` 推进、`log` 刷新 `updated`、`close` 结案,脚本都取当前时刻。
|
|
30
|
+
- **只在补录过去时间时传**:`new --created`、`close --finished`。格式 `YYYY-MM-DD[ HH:MM[:SS]]`,脚本补全到秒;非法值拒绝。
|
|
31
|
+
**`new --created` 补录时 `updated` 会一并等于该时间**,之后记得补一条 `log`,否则看板算它停滞。
|
|
32
|
+
- **筛选语义**:`list` / `history` 的 `--since` / `--until` 按**创建时间**;只给日期时,起 = 当天 `00:00:00`、止 = 当天 `23:59:59`(含整天)。
|
|
33
|
+
- **history 列序**:第一列结案时间、第二列创建时间;排序按创建时间。
|
|
34
|
+
|
|
35
|
+
## 任务号
|
|
36
|
+
|
|
37
|
+
`年月日-时分` + 标题里的字母/数字/汉字 + 4 位随机码(如 `20261015-1430-登录接入-sso-7f3a`)。要指定就传 `--id`(小写字母、数字、汉字、连字符)。
|
|
38
|
+
|
|
39
|
+
## 身份
|
|
40
|
+
|
|
41
|
+
- owner 恒取 `git config user.name`;读不到直接报错,**不做兜底**(先让用户配好 git)。
|
|
42
|
+
- 取身份的命令:`new`(记 creator)、`claim`、`set`、`close`、`log`、`list --mine`、`history --mine`。`history --owner <别人>` 是只读查询,不校验身份。
|
|
43
|
+
- `set` / `close` / `log` 都校验「owner 是不是你」;不是你的、或任务还没人认领,直接拒绝。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 分类与标签
|
|
2
|
+
|
|
3
|
+
> TL;DR:`category` 答「是什么类型」(单值,≤4 字,吃不准写「其他」);`tags` 答「涉及什么」(0~5 个,写不出就留空)。两者口径错开;统计只认 category,查询用 tags。
|
|
4
|
+
|
|
5
|
+
为什么不设枚举:本 skill 跨所有工作区(开发 / 运维 / 个人事务 / 调研备忘),固定分类覆盖不全。分类与标签由 agent 在 `new` 时按本节口径自行总结;脚本只校验形态(≤4 字 / 纯英文 ≤3 词 / ≤5 个),不校验语义。
|
|
6
|
+
|
|
7
|
+
## 两条边界
|
|
8
|
+
|
|
9
|
+
1. **口径错开**:tags 不许写 category 的同义词 —— `category=缺陷修复` 时,tags 别再写「bug修复」。
|
|
10
|
+
2. **统计与查询分工**:统计只认 category(精确匹配,同值才能比较);tags 只做查询(包含匹配,「bug」命中「bug修复」正是要的效果)。
|
|
11
|
+
3. **数量不设下限**:3~5 个是目标不是硬指标;设下限会逼出凑数标签。
|
|
12
|
+
|
|
13
|
+
## 写法口径
|
|
14
|
+
|
|
15
|
+
- 名词性短语,不带时态、不用动词收尾:写「缺陷修复」,不写「修复了」「修 bug」。
|
|
16
|
+
- 中文优先,不中英混排;纯英文 ≤3 个单词(`fix a bug` 可以,`fix a login bug` 超了)。
|
|
17
|
+
- 标签之间用**逗号**分隔;空格属于英文多词标签自身(`--tags "fix a bug,docker"`)。
|
|
18
|
+
|
|
19
|
+
正例:`--category 缺陷修复 --tags "镜像拉取,超时重试"`
|
|
20
|
+
反例:`--category 已完成登录修复`、`--tags 修 bug`
|
|
21
|
+
|
|
22
|
+
## 时机与修正
|
|
23
|
+
|
|
24
|
+
- `new` 建档时一并总结(标题 + 背景语义最全)。
|
|
25
|
+
- `close` 可用 `--category` / `--tags` 覆盖修正一次;终值写进 history 行的「分类 / 标签」列。
|
|
26
|
+
- 日常漂移:先 `tags --all` 看全量频次与低频变体,再 `tags --rename 旧 新` 归并(只改在办任务,已封卷文档不动;批量写,见写操作红线)。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 排错
|
|
2
|
+
|
|
3
|
+
> TL;DR:非 0 退出就停手、原样报告,别绕过脚本改 JSON。并发冲突重跑一次,不要用 `git pull` / `rebase` 去「解决」。
|
|
4
|
+
|
|
5
|
+
## 退出码
|
|
6
|
+
|
|
7
|
+
| 码 | 含义 | 处置 |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 0 | 成功 | — |
|
|
10
|
+
| 1 | 业务错误(参数值 / 状态 / 权限不对) | 按提示纠正后重试;改不了的报告用户 |
|
|
11
|
+
| 2 | 命令行参数错误 | 看 `<子命令> --help` 重新拼参数 |
|
|
12
|
+
| 3 | 未预期的内部失败 | 上报,不要重试 |
|
|
13
|
+
| 130 | 被中断 | 重跑一次 |
|
|
14
|
+
|
|
15
|
+
## 常见错误
|
|
16
|
+
|
|
17
|
+
- **「需要用户名才能执行」**:`git config user.name` 没配。让用户配好,不要自拟名字。
|
|
18
|
+
- **「不是你的任务 / 先 claim」**:任务 owner 不是你或是 `open`。让别人接手只能走「你释放 → 对方 `claim`」;不要用别的名字绕过。
|
|
19
|
+
- **「状态不一致」**:对已在 `doing` 的任务又跑了 `claim`。直接改正文 + `log` 即可。
|
|
20
|
+
- **`log` 报「没写进过程文档」**:worklog 路径丢失 / 不可读。用 `show --id` 查路径;`progress.json` 的 `updated` 已刷新,不要手写「执行记录」。
|
|
21
|
+
- **「文件在操作期间被改动」**(并发指纹冲突):重新执行一次即可。
|
|
22
|
+
- **`validate` 报问题**:按行号读原文,先核对 `progress.json` 与 worklog 是否一致;能通过脚本修的走 `set` / `log`,**不要手改 JSON**。
|
|
23
|
+
|
|
24
|
+
## 硬约束
|
|
25
|
+
|
|
26
|
+
不要直接编辑 `ledger/progress.json`、`ledger/history/*.md`;不要另建 worklog;不要手写「执行记录」节。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 过程文档(worklog)
|
|
2
|
+
|
|
3
|
+
> TL;DR:六节固定;前五节 agent 改,末节「执行记录」只由脚本写。改完正文必须 `log`,交付也要 `log`。老文档缺末节时脚本自动补。
|
|
4
|
+
|
|
5
|
+
## 六节结构
|
|
6
|
+
|
|
7
|
+
| 节 | 写什么 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 背景 | 为什么做:触发它的原话 / 现象 / 链接 |
|
|
10
|
+
| 待办 | 还没做的、没验证的、等别人给东西的 |
|
|
11
|
+
| 下一步 | 下一个**具体动作**(文件:行号 级别),不要写「继续推进」 |
|
|
12
|
+
| 过程 | 踩坑与证据:带 revision + `文件:行号` 或符号名 |
|
|
13
|
+
| 结论 | 做成了什么、留了什么坑;只写可验证事实,评价留给用户验收 |
|
|
14
|
+
| 执行记录 | 脚本维护,**禁手改** |
|
|
15
|
+
|
|
16
|
+
标题结构不能改名。建档只由 `new` 做(按 `assets/worklog.md` 模板生成骨架并写入状态表 `worklog` 字段)。
|
|
17
|
+
|
|
18
|
+
## 执行记录
|
|
19
|
+
|
|
20
|
+
- 一行一事件:`- 2026-10-19 12:08:33 · log · @GuoJia-Agent · 说明`。
|
|
21
|
+
- 类型:`new`(建)/ `claim`(认领)/ `set`(改状态、owner 变更)/ `release`(`set --status open` 释放)/ `log`(手记)/ `close`(结案)。
|
|
22
|
+
- **只追加、不改旧行**:`@谁` 记的是这次动手的人,不是任务现 owner —— 释放/重新认领后旧记录仍留在原负责人名下。
|
|
23
|
+
- 老 worklog 没有这一节:脚本第一次记时在文末自动补一节(正文不迁移),agent 不用也不许手动加。
|
|
24
|
+
- 过程文档丢失:`log` 报错退出(`updated` 已刷新),用 `show --id` 查路径,**不要手写这一节**。
|
|
25
|
+
|
|
26
|
+
## 编辑既有任务
|
|
27
|
+
|
|
28
|
+
1. `show --id` 拿任务 JSON + worklog 全文与路径;
|
|
29
|
+
2. 直接改前五节;
|
|
30
|
+
3. 立刻 `log --id <任务号> --note "补了…/改了…"`。
|
|
31
|
+
|
|
32
|
+
已结案任务:worklog 已封卷、不在状态表,用 `history` 给出的回链找回,只读不改。
|
|
33
|
+
|
|
34
|
+
## 交付与封卷
|
|
35
|
+
|
|
36
|
+
- **交付(待验收)**:任务仍留 `doing`;「待办」写「待用户验收」,「下一步」写清验收看什么(哪条命令 / 哪个文件);按收工三步记一条 `log --note "已交付待验收:…"`。
|
|
37
|
+
- **封卷**:`close` 打封卷标记,此后不再修改;历史行按结案时间写入 `ledger/history/<年月>.md`(存量 `ledger/PROGRESS-HISTORY.md` 只读兼容,不再写入)。
|
package/scripts/progress.py
CHANGED
|
@@ -37,7 +37,10 @@ try:
|
|
|
37
37
|
except ImportError:
|
|
38
38
|
msvcrt = None
|
|
39
39
|
|
|
40
|
-
STATUSES = ("open", "doing"
|
|
40
|
+
STATUSES = ("open", "doing")
|
|
41
|
+
# 旧数据只读兼容:早期版本有 blocked 状态(手动标「卡住」)。现在阻塞按时间判定
|
|
42
|
+
# (接任务满 7 天),不再允许新写入;list / validate / 看板仍能吃老文件。
|
|
43
|
+
LEGACY_STATUSES = ("blocked",)
|
|
41
44
|
PROGRESS_REL = Path("ledger/progress.json")
|
|
42
45
|
# 存量:改造前的单文件历史层,只读兼容、永不重写
|
|
43
46
|
HISTORY_REL = Path("ledger/PROGRESS-HISTORY.md")
|
|
@@ -52,7 +55,7 @@ HISTORY_TITLE = "# 进度历史(已结案)"
|
|
|
52
55
|
# 但「谁建的」不变)。看板「新增(我的)= 我创建的」要它;旧 8 列无此列,
|
|
53
56
|
# upgrade_history / cmd_history 回链 worklog 头部「创建」行补(与 created 同一来源,不丢)。
|
|
54
57
|
HISTORY_HEADER = "| 结案时间 | 创建时间 | 工作包 | 分类 | 标签 | owner | creator | 完成了什么 | 过程文档 |"
|
|
55
|
-
HISTORY_SEP = "
|
|
58
|
+
HISTORY_SEP = "|---|---|---|---|---|---|---|---|---|"
|
|
56
59
|
FROZEN_MARK = "✅ 完成封卷"
|
|
57
60
|
ID_RE = re.compile(r"[a-z0-9\u4e00-\u9fff][a-z0-9\u4e00-\u9fff-]*\Z")
|
|
58
61
|
# 读侧兼容:秒可有可无(旧数据只到分)。写侧一律到秒,见 normalize_time。
|
|
@@ -65,6 +68,14 @@ LABEL_MAX_WORDS_EN = 3
|
|
|
65
68
|
TAGS_MAX_COUNT = 5
|
|
66
69
|
CJK_RE = re.compile(r"[\u4e00-\u9fff]")
|
|
67
70
|
|
|
71
|
+
# 执行记录:worklog 末尾由脚本维护的一节。老文档没有这一节就不动(不迁移);
|
|
72
|
+
# 有这一节时脚本按节点自动追加,agent 改完正文跑 log 补一条。
|
|
73
|
+
EXEC_HEAD = "## 执行记录"
|
|
74
|
+
EXEC_HINT = (
|
|
75
|
+
"<!-- 本节由脚本维护:new / claim / set / close / log 自动追加,请勿手改。\n"
|
|
76
|
+
' 要补一条就跑 progress.py log --id <任务号> --note "做了什么"。 -->'
|
|
77
|
+
)
|
|
78
|
+
|
|
68
79
|
SKILL_DIR = Path(__file__).resolve().parent.parent
|
|
69
80
|
WORKLOG_TEMPLATE = SKILL_DIR / "assets" / "worklog.md"
|
|
70
81
|
|
|
@@ -81,6 +92,8 @@ FALLBACK_TEMPLATE = """# <id>:<title>
|
|
|
81
92
|
## 过程
|
|
82
93
|
|
|
83
94
|
## 结论
|
|
95
|
+
|
|
96
|
+
## 执行记录
|
|
84
97
|
"""
|
|
85
98
|
|
|
86
99
|
|
|
@@ -1134,6 +1147,58 @@ def find_worklog(
|
|
|
1134
1147
|
return None
|
|
1135
1148
|
|
|
1136
1149
|
|
|
1150
|
+
def exec_line(when: str, who: str | None, kind: str, note: str) -> str:
|
|
1151
|
+
"""一条执行记录:`- 时间 · 类型 · @某人 · 说明`。
|
|
1152
|
+
|
|
1153
|
+
类型取脚本子命令名(new / claim / set / log / close),纯 ASCII、可 grep 统计;
|
|
1154
|
+
记录只追加不改写 —— 移交后旧记录仍记在原来那个人名下。
|
|
1155
|
+
"""
|
|
1156
|
+
head = f"- {when} · {kind}"
|
|
1157
|
+
return f"{head} · @{who} · {note}" if who else f"{head} · {note}"
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def append_exec_log(text: str, line: str) -> str:
|
|
1161
|
+
"""把一条记录追加到「执行记录」节末尾。
|
|
1162
|
+
|
|
1163
|
+
升级前建的老 worklog 没有这一节:在文末补一节再写(不迁移正文,也不让记录静默丢掉)。
|
|
1164
|
+
"""
|
|
1165
|
+
lines = text.splitlines()
|
|
1166
|
+
at = next((i for i, ln in enumerate(lines) if ln.strip() == EXEC_HEAD), None)
|
|
1167
|
+
if at is None:
|
|
1168
|
+
return text.rstrip() + f"\n\n{EXEC_HEAD}\n\n{EXEC_HINT}\n\n{line}\n"
|
|
1169
|
+
end = next(
|
|
1170
|
+
(i for i in range(at + 1, len(lines)) if lines[i].startswith("## ")), len(lines)
|
|
1171
|
+
)
|
|
1172
|
+
insert = end
|
|
1173
|
+
while insert > at + 1 and not lines[insert - 1].strip():
|
|
1174
|
+
insert -= 1
|
|
1175
|
+
lines.insert(insert, line)
|
|
1176
|
+
return "\n".join(lines) + "\n"
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
def record_exec(
|
|
1180
|
+
repo: Path, tid: str, task: dict[str, Any], who: str | None, kind: str, note: str
|
|
1181
|
+
) -> bool:
|
|
1182
|
+
"""给任务的过程文档补一条执行记录(`who` = 这次动手的人,不是任务现 owner)。
|
|
1183
|
+
|
|
1184
|
+
返回是否真的落盘;`log` 用它判断要不要报错。调用方必须已经持有 progress_lock ——
|
|
1185
|
+
与 close 的封卷(读全文→改写→落盘)互斥,否则两条记录并发追加会互相覆盖。
|
|
1186
|
+
"""
|
|
1187
|
+
log = find_worklog(repo, tid, task)
|
|
1188
|
+
if log is None:
|
|
1189
|
+
return False
|
|
1190
|
+
try:
|
|
1191
|
+
text = log.read_text(encoding="utf-8")
|
|
1192
|
+
except (OSError, UnicodeDecodeError):
|
|
1193
|
+
return False
|
|
1194
|
+
line = exec_line(now(), who, kind, note)
|
|
1195
|
+
try:
|
|
1196
|
+
write_text_atomic(log, append_exec_log(text, line))
|
|
1197
|
+
except OSError:
|
|
1198
|
+
return False
|
|
1199
|
+
return True
|
|
1200
|
+
|
|
1201
|
+
|
|
1137
1202
|
def write_worklog(repo: Path, tid: str, task: dict[str, Any]) -> Path:
|
|
1138
1203
|
log = repo / worklog_dir_for(task) / worklog_name(tid)
|
|
1139
1204
|
write_text_atomic(log, render_worklog(tid, task))
|
|
@@ -1218,6 +1283,7 @@ def cmd_new(args: argparse.Namespace) -> None:
|
|
|
1218
1283
|
"status": "doing" if args.self else "open",
|
|
1219
1284
|
"created": stamp,
|
|
1220
1285
|
"started": stamp if args.self else None,
|
|
1286
|
+
"updated": stamp,
|
|
1221
1287
|
"finished": None,
|
|
1222
1288
|
"category": category,
|
|
1223
1289
|
"tags": tags,
|
|
@@ -1236,6 +1302,7 @@ def cmd_new(args: argparse.Namespace) -> None:
|
|
|
1236
1302
|
except Fail:
|
|
1237
1303
|
pass
|
|
1238
1304
|
raise Fail(f"创建失败,已从状态表撤销该任务:{exc}")
|
|
1305
|
+
record_exec(repo, tid, task, creator, "new", f"创建任务({task['status']})")
|
|
1239
1306
|
print(f"已创建任务 {tid}")
|
|
1240
1307
|
print(f" 标题 {task['title']}")
|
|
1241
1308
|
print(f" 状态 {task['status']}")
|
|
@@ -1271,10 +1338,13 @@ def cmd_claim(args: argparse.Namespace) -> None:
|
|
|
1271
1338
|
f"任务 {tid} 当前状态是 {task.get('status')},只有 open 才能认领。"
|
|
1272
1339
|
)
|
|
1273
1340
|
|
|
1341
|
+
stamp = now()
|
|
1274
1342
|
task["owner"] = owner
|
|
1275
1343
|
task["status"] = "doing"
|
|
1276
|
-
task["started"] = task.get("started") or
|
|
1344
|
+
task["started"] = task.get("started") or stamp
|
|
1345
|
+
task["updated"] = stamp
|
|
1277
1346
|
commit_guarded(path, before, data)
|
|
1347
|
+
record_exec(repo, tid, task, owner, "claim", "认领 → doing")
|
|
1278
1348
|
print(f"已认领 {tid} → doing,owner = @{owner}")
|
|
1279
1349
|
print_worklog(repo, tid, task)
|
|
1280
1350
|
|
|
@@ -1292,12 +1362,17 @@ def cmd_set(args: argparse.Namespace) -> None:
|
|
|
1292
1362
|
raise Fail(
|
|
1293
1363
|
"--status open(退回未认领)不能同时给 --owner;要交给别人就别改回 open。"
|
|
1294
1364
|
)
|
|
1295
|
-
if status
|
|
1296
|
-
raise Fail(
|
|
1365
|
+
if status == "blocked":
|
|
1366
|
+
raise Fail(
|
|
1367
|
+
"没有 blocked 状态:阻塞由时间自动判定(接任务满 7 天仍未结案),"
|
|
1368
|
+
"状态只有 open / doing;卡在哪里请写进 worklog。"
|
|
1369
|
+
)
|
|
1297
1370
|
if status == "done":
|
|
1298
1371
|
raise Fail(
|
|
1299
1372
|
"不要把状态直接改成 done —— 用 close 结案(它会同时移出状态、追加历史、封卷 worklog)。"
|
|
1300
1373
|
)
|
|
1374
|
+
if status is not None and status not in STATUSES:
|
|
1375
|
+
raise Fail(f"--status 只能是 {' / '.join(STATUSES)};结案请用 close。")
|
|
1301
1376
|
|
|
1302
1377
|
repo = find_repo(args.repo)
|
|
1303
1378
|
tid = require_task_id(args.id)
|
|
@@ -1328,11 +1403,40 @@ def cmd_set(args: argparse.Namespace) -> None:
|
|
|
1328
1403
|
task["started"] = task.get("started") or now()
|
|
1329
1404
|
changed.append(f"owner {old or '(无)'} → {task['owner']}")
|
|
1330
1405
|
|
|
1406
|
+
task["updated"] = now()
|
|
1331
1407
|
commit_guarded(path, before, data)
|
|
1408
|
+
# 释放认领(退回 open)单列一个类型,便于一眼看出「被释放过」
|
|
1409
|
+
kind = "release" if status == "open" else "set"
|
|
1410
|
+
record_exec(repo, tid, task, me, kind, ",".join(changed))
|
|
1332
1411
|
print(f"已更新 {tid}:" + ",".join(changed))
|
|
1333
1412
|
print_worklog(repo, tid, task)
|
|
1334
1413
|
|
|
1335
1414
|
|
|
1415
|
+
def cmd_log(args: argparse.Namespace) -> None:
|
|
1416
|
+
repo = find_repo(args.repo)
|
|
1417
|
+
tid = require_task_id(args.id)
|
|
1418
|
+
note = " ".join(args.note.split())
|
|
1419
|
+
if not note:
|
|
1420
|
+
raise Fail("--note 不能为空:一句话写清这轮做了什么。")
|
|
1421
|
+
me = git_user()
|
|
1422
|
+
path = repo / PROGRESS_REL
|
|
1423
|
+
with progress_lock(path):
|
|
1424
|
+
before, data = read_guarded(path)
|
|
1425
|
+
task = data["tasks"].get(tid)
|
|
1426
|
+
if task is None:
|
|
1427
|
+
raise Fail(f"没有任务 {tid}(可能未建或已结案;封卷后不再记)。")
|
|
1428
|
+
require_owner(task, tid, me)
|
|
1429
|
+
task["updated"] = now()
|
|
1430
|
+
commit_guarded(path, before, data)
|
|
1431
|
+
written = record_exec(repo, tid, task, me, "log", note)
|
|
1432
|
+
if not written:
|
|
1433
|
+
raise Fail(
|
|
1434
|
+
f"没写进 {tid} 的过程文档(文件不存在或不可读);progress.json 的 updated 已刷新。"
|
|
1435
|
+
"先用 show --id 确认 worklog 路径,不要手写「执行记录」。"
|
|
1436
|
+
)
|
|
1437
|
+
print(f"已记入 {tid} 的执行记录:{note}")
|
|
1438
|
+
|
|
1439
|
+
|
|
1336
1440
|
def cmd_close(args: argparse.Namespace) -> None:
|
|
1337
1441
|
repo = find_repo(args.repo)
|
|
1338
1442
|
tid = require_task_id(args.id)
|
|
@@ -1414,29 +1518,33 @@ def cmd_close(args: argparse.Namespace) -> None:
|
|
|
1414
1518
|
if close_tags is not None
|
|
1415
1519
|
else list(task.get("tags") or []),
|
|
1416
1520
|
)
|
|
1417
|
-
new_log = stamp_frozen(log_text, finished, owner)
|
|
1418
1521
|
else:
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
else list(task.get("tags") or [])
|
|
1434
|
-
),
|
|
1522
|
+
log_text = render_worklog(
|
|
1523
|
+
tid,
|
|
1524
|
+
dict(
|
|
1525
|
+
task,
|
|
1526
|
+
status="done",
|
|
1527
|
+
category=(
|
|
1528
|
+
close_category
|
|
1529
|
+
if close_category is not None
|
|
1530
|
+
else task.get("category")
|
|
1531
|
+
),
|
|
1532
|
+
tags=(
|
|
1533
|
+
close_tags
|
|
1534
|
+
if close_tags is not None
|
|
1535
|
+
else list(task.get("tags") or [])
|
|
1435
1536
|
),
|
|
1436
1537
|
),
|
|
1437
|
-
finished,
|
|
1438
|
-
owner,
|
|
1439
1538
|
)
|
|
1539
|
+
# 结案本身是最后一个节点:封卷前补上这条记录,封卷后不再追加
|
|
1540
|
+
new_log = stamp_frozen(
|
|
1541
|
+
append_exec_log(
|
|
1542
|
+
log_text,
|
|
1543
|
+
exec_line(finished, me, "close", f"结案:{summary}"),
|
|
1544
|
+
),
|
|
1545
|
+
finished,
|
|
1546
|
+
owner,
|
|
1547
|
+
)
|
|
1440
1548
|
|
|
1441
1549
|
# 落盘前复查:三个文件都没被别人动过
|
|
1442
1550
|
for p, mark in (
|
|
@@ -1539,18 +1647,13 @@ def cmd_list(args: argparse.Namespace) -> None:
|
|
|
1539
1647
|
if args.status is not None:
|
|
1540
1648
|
wanted = [s.strip() for s in args.status.split(",") if s.strip()]
|
|
1541
1649
|
if not wanted:
|
|
1542
|
-
raise Fail("--status 不能为空;逗号分隔,如 doing,
|
|
1543
|
-
|
|
1650
|
+
raise Fail("--status 不能为空;逗号分隔,如 doing,open")
|
|
1651
|
+
# blocked 只用于捞旧数据(新任务不可能再有这个值)
|
|
1652
|
+
bad = [s for s in wanted if s not in STATUSES and s not in LEGACY_STATUSES]
|
|
1544
1653
|
if bad:
|
|
1545
1654
|
raise Fail(
|
|
1546
1655
|
f"--status 含非法值 {', '.join(bad)};可选 {' / '.join(STATUSES)}"
|
|
1547
1656
|
)
|
|
1548
|
-
if "done" in wanted:
|
|
1549
|
-
print(
|
|
1550
|
-
"提示:done 的任务已结案并移出状态表,用 history 子命令查"
|
|
1551
|
-
"(已结案在 ledger/history/<年月>.md,存量在 ledger/PROGRESS-HISTORY.md)。",
|
|
1552
|
-
file=sys.stderr,
|
|
1553
|
-
)
|
|
1554
1657
|
|
|
1555
1658
|
since = normalize_time(args.since, field="--since") if args.since else None
|
|
1556
1659
|
until = (
|
|
@@ -1734,7 +1837,7 @@ def cmd_validate(args: argparse.Namespace) -> None:
|
|
|
1734
1837
|
if not task.get("title"):
|
|
1735
1838
|
problems.append(f"{tid}: 缺 title")
|
|
1736
1839
|
status = task.get("status")
|
|
1737
|
-
if status not in STATUSES:
|
|
1840
|
+
if status not in STATUSES and status not in LEGACY_STATUSES:
|
|
1738
1841
|
problems.append(f"{tid}: status={status!r} 非法")
|
|
1739
1842
|
if status == "open" and task.get("owner"):
|
|
1740
1843
|
problems.append(f"{tid}: status=open 却有 owner")
|
|
@@ -2006,10 +2109,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2006
2109
|
p = sub.add_parser("set", help="改状态 / 交接(结案请用 close)")
|
|
2007
2110
|
p.add_argument("--id", required=True)
|
|
2008
2111
|
p.add_argument("--status", help=f"{' / '.join(STATUSES)}(done 请走 close)")
|
|
2009
|
-
p.add_argument("--owner", help="
|
|
2112
|
+
p.add_argument("--owner", help="交接给谁(仅供 UI / 维护者;agent 提示词不暴露)")
|
|
2010
2113
|
add_repo(p)
|
|
2011
2114
|
p.set_defaults(func=cmd_set)
|
|
2012
2115
|
|
|
2116
|
+
p = sub.add_parser("log", help="往 worklog 的「执行记录」节补一条记录")
|
|
2117
|
+
p.add_argument("--id", required=True)
|
|
2118
|
+
p.add_argument("--note", required=True, help="一句话:这轮做了什么")
|
|
2119
|
+
add_repo(p)
|
|
2120
|
+
p.set_defaults(func=cmd_log)
|
|
2121
|
+
|
|
2013
2122
|
p = sub.add_parser("close", help="结案:移出状态 + 追加历史 + 封卷 worklog")
|
|
2014
2123
|
p.add_argument("--id", required=True)
|
|
2015
2124
|
p.add_argument("--summary", required=True, help="做成了什么,一句话")
|
|
@@ -2024,7 +2133,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2024
2133
|
p = sub.add_parser("list", help="查询在办任务")
|
|
2025
2134
|
p.add_argument("--mine", action="store_true", help="只看自己的")
|
|
2026
2135
|
p.add_argument("--owner", help="只看某个人的")
|
|
2027
|
-
p.add_argument("--status", help="逗号分隔,如 doing,
|
|
2136
|
+
p.add_argument("--status", help="逗号分隔,如 doing,open")
|
|
2028
2137
|
p.add_argument("--category", help="按分类过滤(精确匹配)")
|
|
2029
2138
|
p.add_argument("--tag", help="按标签过滤(包含匹配,命中任一面即算)")
|
|
2030
2139
|
p.add_argument(
|