dsh-task-ledger 0.0.99 → 0.0.101
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/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +14 -13
- package/skill/assets/worklog.md +2 -2
- package/skill/package.json +1 -1
- package/skill/references/state-model.md +2 -2
- package/skill/scripts/ledgerkit/actions/close_task.py +6 -8
- package/skill/scripts/ledgerkit/actions/new_task.py +0 -1
- package/skill/scripts/ledgerkit/actions/registry.py +2 -2
- package/skill/scripts/ledgerkit/actions/relabel.py +17 -33
- package/skill/scripts/ledgerkit/actions/set_task.py +13 -15
- package/skill/scripts/ledgerkit/actions/validate.py +1 -1
- package/skill/scripts/ledgerkit/core/history_md.py +1 -1
- package/skill/scripts/ledgerkit/core/labels.py +8 -9
- package/skill/scripts/ledgerkit/core/worklog_md.py +7 -9
- package/skill/scripts/ledgerkit/shell/cli.py +1 -5
- package/skill/scripts/ledgerkit/store/history_store.py +1 -1
- package/skill/scripts/ledgerkit/store/worklog_store.py +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.101",
|
|
4
4
|
"description": "Task ledger board for dsh (DeepSeek Harness): read-only board over task-ledger skill workspaces. The task-ledger skill is bundled — install the plugin, get the skill globally.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
package/skill/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: task-ledger
|
|
3
3
|
description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)、给任务结案或退回(做完了 / 验收通过 / 不做了 / 退回待认领)、整理标签或检查台账一致性时加载;只是改代码、讨论方案时不要用。
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.27
|
|
5
5
|
allowed-tools: Read,Edit,Bash,Grep
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -41,15 +41,15 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
41
41
|
|
|
42
42
|
| 参数 | 含义 |
|
|
43
43
|
|---|---|
|
|
44
|
-
| `--title "…"` | 任务标题(必填)——
|
|
44
|
+
| `--title "…"` | 任务标题(必填)—— 写任务核心、一句话说清干啥,不要拿分类名当标题;它用来生成任务号和过程文件名,建议 4~20 字 |
|
|
45
45
|
| `--id` | 自定义任务号(一般不传;只能用小写字母、数字、汉字和连字符) |
|
|
46
46
|
| `--created` | 给过去漏记的任务补建时,补录实际创建日期(`YYYY-MM-DD`) |
|
|
47
47
|
| `--self` | 建并认领:直接归自己名下、状态「进行中」。仅当用户明确说「并开始做 / 并执行」时才用(判定见生命周期第 2 条) |
|
|
48
|
-
| `--category` / `--tags` | 分类 /
|
|
48
|
+
| `--category` / `--tags` | 分类 / 标签,**只在建任务时能设分类**,口径见本节末尾 |
|
|
49
49
|
|
|
50
50
|
干什么:建任务、按模板建好过程文档。不加 `--self` = 只建,落公共池等认领;加 `--self` = 建并认领。
|
|
51
51
|
|
|
52
|
-
分类 /
|
|
52
|
+
分类 / 标签口径(`--category` 只有 `new` 有;`--tags` 三个命令都有):`--category` 答「是什么类型」,单值,中文 ≤4 字 / 纯英文 ≤3 词,吃不准写「其他」,**建任务时给一次,之后谁都改不了**(`set` / `close` 没有这个参数,历史行里就是建任务时那个值;标题与创建时间同理,建完即不可变)。`--tags` 答「涉及什么」,0~5 个、逗号分隔,写不出就不传(传空串等于没传),随时可改。**分类只进台账与历史**(`list` / `history` 看得到),**不写进过程文档**;过程文档头部只有标签行,标签可以涵盖分类(「建设」「修改」这类词直接当标签写)。查询用 tags(包含匹配,见 `list --tag`)。名词性短语、中文优先。正例:`--category 缺陷修复 --tags "镜像拉取,超时重试"`;反例:`--category 已完成登录修复`、`--tags 修 bug`。
|
|
53
53
|
|
|
54
54
|
举例:用户说「新建个任务:把发布流程走通」→ `new --title "把发布流程走通"`(只建,不认领);用户明确说「新建个任务并开始做」→ `new --title "把发布流程走通" --self`。
|
|
55
55
|
|
|
@@ -63,24 +63,25 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
63
63
|
|
|
64
64
|
举例:用户说「这活我领了」→ `claim --id 20261023-1117-把发布流程走通-7f3a`。
|
|
65
65
|
|
|
66
|
-
### set — 释放任务 /
|
|
66
|
+
### set — 释放任务 / 改标签
|
|
67
67
|
|
|
68
68
|
| 参数 | 含义 |
|
|
69
69
|
|---|---|
|
|
70
70
|
| `--id <任务号>` | 哪条任务(必填) |
|
|
71
71
|
| `--release` | 释放回公共池(不带人) |
|
|
72
72
|
| `--owner <别人的uuid>` | 把任务转交给对方(预留通道,**agent 不用**,见纪律;只收 uuid,传名字当场报错) |
|
|
73
|
-
| `--
|
|
74
|
-
| `--tags <a、b>` | 改标签(顿号 / 逗号分隔,最多 5 个) |
|
|
73
|
+
| `--tags <a、b>` | 改标签(顿号 / 逗号分隔,最多 5 个;写台账字段 + 过程文档头部标签行) |
|
|
75
74
|
|
|
76
75
|
干什么:两组用法,**不能混在一次调用里** ——
|
|
77
76
|
|
|
78
77
|
- `--release` / `--owner`:改归属。释放回公共池、状态回「待认领」,别人即可认领;没有单独的「改状态」入口。**这两个必须二选一、不能同给**。
|
|
79
|
-
- `--
|
|
78
|
+
- `--tags`:改标签。只改自己名下**在办**任务;给了就整组替换(没给的标签等于删掉)。同时改台账与过程文档头部那行(没有就插到最上面,有就原地改)。已结案(在历史里)的任务改不了。
|
|
79
|
+
|
|
80
|
+
**没有改分类的入口**:分类建任务时定死,`set` / `close` 都没有 `--category`。
|
|
80
81
|
|
|
81
82
|
只给 `--id` 会报错,要求你指明要做哪件事。
|
|
82
83
|
|
|
83
|
-
举例:用户明确说「把这个任务退回去」→ `set --id <任务号> --release
|
|
84
|
+
举例:用户明确说「把这个任务退回去」→ `set --id <任务号> --release`;「给这任务打上台账标签」→ `set --id <任务号> --tags 台账、看板`。
|
|
84
85
|
|
|
85
86
|
### log — 记一条进展
|
|
86
87
|
|
|
@@ -100,7 +101,7 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
100
101
|
| `--id <任务号>` | 结哪条(必填) |
|
|
101
102
|
| `--summary "…"` | 结案摘要(必填;放弃写「放弃:原因」) |
|
|
102
103
|
| `--finished` | 给漏记的旧任务补用时,补录实际结案时间(`YYYY-MM-DD[ HH:MM[:SS]]`) |
|
|
103
|
-
| `--
|
|
104
|
+
| `--tags` | 覆盖标签终值(写进历史行;文档头部只写标签)。分类不可改,历史行用它建任务时的值 |
|
|
104
105
|
|
|
105
106
|
干什么:结案并封卷过程文档、移出自己名下。**封卷后只读**(见生命周期第 8 条)。
|
|
106
107
|
|
|
@@ -214,16 +215,16 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <工
|
|
|
214
215
|
|
|
215
216
|
- 「执行记录」节名一个字都别动;老文档缺这一节也不用补。
|
|
216
217
|
- 过程文档找不到时 `log` 只提示不失败;用 `show` 查路径,不要手写。
|
|
217
|
-
- 头部是四行,由脚本维护、**都不要手改**,顺序固定
|
|
218
|
+
- 头部是四行,由脚本维护、**都不要手改**,顺序固定 `标签 → 状态 → 创建 → 封卷`,值一律 `· ` 分隔:
|
|
218
219
|
|
|
219
220
|
```markdown
|
|
220
|
-
-
|
|
221
|
+
- **标签**:建设、DSH、台账
|
|
221
222
|
- **状态**:doing · 2026-10-27 09:12:33 · @某人 · 8d557c6d-…
|
|
222
223
|
- **创建**:2026-10-26 23:38:02 · @某人 · 8d557c6d-…
|
|
223
224
|
- **封卷**:2026-10-27 09:20:10 · @某人 · 8d557c6d-…
|
|
224
225
|
```
|
|
225
226
|
|
|
226
|
-
|
|
227
|
+
标签行与封卷行不一定有(没给标签、没结案就没有)。老文档头部可能还带着「**分类**」段,那是旧格式,不用补也不用改(脚本下次改写标签行时会一并清掉)。老文档没有状态行是合法的(`show` 回退查在办,`--json` 标 `legacy: true`),不用补。
|
|
227
228
|
- 执行记录行格式 `- 时间(UTC) · 类型 · @谁 · 说明`,`@谁` 是这次动手的人、不是任务现归属;只追加、不改旧行。行首「类型」是脚本内部动作名(会见到 `release`,它就是 `set --release`);认不出就别当成新命令,按 `--help` 查。
|
|
228
229
|
- 只改某条任务的正文:`show --id <任务号>` 拿路径直接改,不另建 md;已结案的用 `history --months 12 --json` 找回链接(**人话输出里没有链接,必须加 `--json`**)——默认只查近 3 个月,结案久了的要显式放大窗口;只读不改。
|
|
229
230
|
|
package/skill/assets/worklog.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
- **状态**:<status>
|
|
4
4
|
- **创建**:<created> · @<creator>
|
|
5
5
|
|
|
6
|
-
<!--
|
|
7
|
-
「-
|
|
6
|
+
<!-- 头部四行由脚本维护,顺序固定:标签 → 状态 → 创建 → 封卷。
|
|
7
|
+
「- **标签**:…」按 new --tags 插在最上面,没有标签就不写(分类只进台账与历史,不进本文档);
|
|
8
8
|
「- **封卷**:…」在结案时加在最后。 -->
|
|
9
9
|
|
|
10
10
|
## 背景
|
package/skill/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.27",
|
|
4
4
|
"description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/pool.json + owners/<hash>.json shards + owners.json index, 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",
|
|
@@ -30,7 +30,7 @@ close → 历史层 + 封卷 + 移出自己名下
|
|
|
30
30
|
- 进门:`--since` / `--until` / `--created` / `--finished` 按本机墙钟给,脚本内部转 UTC 再比较 / 落盘;除这四个参数没有任何地方要调用方传时间。
|
|
31
31
|
- **过程文档正文不动**:`show --json` 的 `worklog.text` 是原文,里面的时间是当时写进去的 UTC。
|
|
32
32
|
- 存量老数据写的是写入者本地时间、无标记,无法与 UTC 区分(JSON 是 2026-10-22 前,执行记录行是 2026-10-27 前)。
|
|
33
|
-
- **history 文件 11 列**:结案时间 / 创建时间 /
|
|
33
|
+
- **history 文件 11 列**:结案时间 / 创建时间 / 标题 / 分类 / 标签 / owner / owner_name / creator / creator_name / 摘要 / 过程文档;排序与 `--since` / `--until` 按**结案时间**。(`history` 命令的人话输出只展示其中几列:结案时间、负责人、标题。)
|
|
34
34
|
- **owner / creator 两列存的是 uuid**(与任务里一致,身份只认 uuid);紧随其后的 `owner_name` / `creator_name` 两列**落盘**结案那一刻的显示名 —— 改名不回写旧行,看历史看到的就是当时那个名字。老行(升级前落的)没有名字时按名册兜底补一次展示值,**不回写文件**。
|
|
35
35
|
|
|
36
36
|
## 任务号
|
|
@@ -47,7 +47,7 @@ close → 历史层 + 封卷 + 移出自己名下
|
|
|
47
47
|
|
|
48
48
|
## 历史层
|
|
49
49
|
|
|
50
|
-
- 按**结案时间**分月存放,11 列(列序:owner 之后是 `owner_name`,再是 creator、`creator_name`);`close`
|
|
50
|
+
- 按**结案时间**分月存放,11 列(列序:owner 之后是 `owner_name`,再是 creator、`creator_name`);`close` 写历史行时:分类用任务原值(**建任务后不可改**),标签用终值。
|
|
51
51
|
- 存量平铺历史文件只读兼容、永不重写;`history` 连同它在内、按下面的月份区间合并。
|
|
52
52
|
- **查询区间**:`history` 先由时间参数算出要读的月份文件,再逐个「存在才读、缺月跳过」,不扫磁盘。无时间参数默认最近 3 个自然月;`--months N`(近 N 月)与 `--since/--until`(结案时间闭区间)二选一;一次最多读 13 个历史文件(按月分文件,不区分连续/分散,只数总数),超出直接报错、不读任何文件。末列文档链接只按**文件名**消费(取 basename),链接里的 `../worklog/<年月>/` 前缀只为阅读器跳转服务,解析时忽略。
|
|
53
53
|
- `history --json` 直接按文件现状解析,旧行没有的列不编造(名字列为空就按名册补展示值,不回写);存量九列的历史文件按 `docs/history-creator-upgrade.md` 一次性升级,脚本不认旧列数。
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
from ..core.identity import Identity
|
|
12
|
-
from ..core.labels import
|
|
12
|
+
from ..core.labels import parse_tags
|
|
13
13
|
from ..core.paths import history_path_for, shard_rel, to_repo_rel, worklog_name
|
|
14
14
|
from ..core.worklog_md import (
|
|
15
15
|
StatusLine,
|
|
@@ -36,7 +36,6 @@ def close_task(
|
|
|
36
36
|
*,
|
|
37
37
|
summary: str,
|
|
38
38
|
finished: str,
|
|
39
|
-
category: str | None = None,
|
|
40
39
|
tags: tuple[str, ...] | None = None,
|
|
41
40
|
) -> Result:
|
|
42
41
|
"""写序四步:改标签行 → 历史加一行 → 封卷 → 删分片(永远是最后一步)。"""
|
|
@@ -45,7 +44,6 @@ def close_task(
|
|
|
45
44
|
text_summary = " ".join((summary or "").split())
|
|
46
45
|
if not text_summary:
|
|
47
46
|
raise Fail("结案要写一句「完成了什么」(--summary 不能为空)。")
|
|
48
|
-
new_category = normalize_label(category, field="分类") if category else None
|
|
49
47
|
new_tags = parse_tags(tags) if tags else None
|
|
50
48
|
|
|
51
49
|
logs = WorklogStore(repo)
|
|
@@ -70,8 +68,8 @@ def close_task(
|
|
|
70
68
|
# 分片里在、但行内 owner 不是我 ⇒ 台账脏了(validate 会报⑤):先拦下来,别用我的 uuid 记历史
|
|
71
69
|
require_owner(task, tid, me)
|
|
72
70
|
|
|
73
|
-
#
|
|
74
|
-
final_category =
|
|
71
|
+
# 分类取任务原值(**不可改**);标签给了参数用新值,没给沿用原值
|
|
72
|
+
final_category = task.get("category")
|
|
75
73
|
final_tags = tuple(new_tags) if new_tags else tuple(task.get("tags") or ())
|
|
76
74
|
|
|
77
75
|
doc_path = logs.find(tid, task.get("worklog"))
|
|
@@ -128,10 +126,10 @@ def close_task(
|
|
|
128
126
|
"(可能是编辑器或 git 操作),已中止、未写入任何内容;重跑一次即可。"
|
|
129
127
|
)
|
|
130
128
|
|
|
131
|
-
# ① 改标签行(给了 --
|
|
132
|
-
if doc_path is not None and
|
|
129
|
+
# ① 改标签行(给了 --tags 才做,且必须在封卷之前)
|
|
130
|
+
if doc_path is not None and new_tags:
|
|
133
131
|
text = logs.read(doc_path)
|
|
134
|
-
logs.write(doc_path, rewrite_label_line(text,
|
|
132
|
+
logs.write(doc_path, rewrite_label_line(text, final_tags))
|
|
135
133
|
|
|
136
134
|
# ② 历史加一行(先 ensure_dir:首次结案时 history/ 可能还不存在)
|
|
137
135
|
ensure_dir(hist_path.parent)
|
|
@@ -43,14 +43,14 @@ ACTIONS: dict[str, ActionSpec] = {
|
|
|
43
43
|
"set": ActionSpec(
|
|
44
44
|
set_task.set_task,
|
|
45
45
|
"result",
|
|
46
|
-
params=("tid", "owner", "release", "
|
|
46
|
+
params=("tid", "owner", "release", "tags"),
|
|
47
47
|
lock_action="set",
|
|
48
48
|
),
|
|
49
49
|
"log": ActionSpec(log_progress.log_progress, "result", params=("tid", "note")),
|
|
50
50
|
"close": ActionSpec(
|
|
51
51
|
close_task.close_task,
|
|
52
52
|
"result",
|
|
53
|
-
params=("tid", "summary", "finished", "
|
|
53
|
+
params=("tid", "summary", "finished", "tags"),
|
|
54
54
|
),
|
|
55
55
|
"list": ActionSpec(
|
|
56
56
|
list_tasks.list_tasks, "list_view", readonly=True, params=("q",)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""改标签:分片 `tags` + 过程文档头部标签行,一次写两处。
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**分类不可改**(2026-10-27 用户拍板):它在 `new` 时定死,之后没有任何入口能改 —
|
|
4
|
+
不进过程文档、不进 `set` / `close` 参数;历史行里那个值就是建任务时那个值。
|
|
5
|
+
|
|
6
|
+
只动「标签」这一件事,不碰归属与状态 —— 归属走 `--owner` / `--release`,
|
|
4
7
|
状态只随命令语义变。已结案的任务不在任何分片里,改不了(历史行是快照)。
|
|
5
8
|
"""
|
|
6
9
|
|
|
@@ -9,7 +12,7 @@ from __future__ import annotations
|
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
|
|
11
14
|
from ..core.identity import Identity
|
|
12
|
-
from ..core.labels import
|
|
15
|
+
from ..core.labels import parse_tags
|
|
13
16
|
from ..core.paths import shard_rel
|
|
14
17
|
from ..core.time import now
|
|
15
18
|
from ..core.worklog_md import (
|
|
@@ -26,32 +29,20 @@ from .guard import require_owner
|
|
|
26
29
|
from .result import Result, rel
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
def _describe(category: str | None, tags: list[str]) -> str:
|
|
30
|
-
"""执行记录里那一句说明:`建设 · DSH、台账`。"""
|
|
31
|
-
parts = [category] if category else []
|
|
32
|
-
if tags:
|
|
33
|
-
parts.append("、".join(tags))
|
|
34
|
-
return " · ".join(parts)
|
|
35
|
-
|
|
36
|
-
|
|
37
32
|
def relabel(
|
|
38
33
|
repo: Path,
|
|
39
34
|
me: Identity,
|
|
40
35
|
tid: str,
|
|
41
36
|
*,
|
|
42
|
-
category: str | None = None,
|
|
43
37
|
tags: str | tuple[str, ...] | None = None,
|
|
44
38
|
) -> Result:
|
|
45
39
|
"""写序:算最终值 → 过程文档(标签行 + 执行记录 + 状态行,一次写)→ 分片。
|
|
46
40
|
|
|
47
41
|
`tags` 收字符串(顿号 / 逗号分隔)或已切好的元组 —— 外壳可能已经切过一遍。
|
|
48
42
|
"""
|
|
49
|
-
new_category = normalize_label(category, field="分类") if category else None
|
|
50
43
|
new_tags = parse_tags(tags) if tags else None
|
|
51
|
-
if
|
|
52
|
-
raise Fail(
|
|
53
|
-
"改分类 / 标签至少要给一个:`--category <分类>` 或 `--tags <a、b>`。"
|
|
54
|
-
)
|
|
44
|
+
if new_tags is None:
|
|
45
|
+
raise Fail("改标签要给 `--tags <a、b>`。")
|
|
55
46
|
|
|
56
47
|
mine = TaskStore(repo, shard_rel(me.uuid), identity=me)
|
|
57
48
|
task = mine.get(tid) # 锁内重读
|
|
@@ -61,19 +52,12 @@ def relabel(
|
|
|
61
52
|
)
|
|
62
53
|
require_owner(task, tid, me)
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
[str(tag) for tag in new_tags]
|
|
68
|
-
if new_tags is not None
|
|
69
|
-
else [str(tag) for tag in (task.get("tags") or ())]
|
|
70
|
-
)
|
|
71
|
-
if not final_category and not final_tags:
|
|
72
|
-
raise Fail("分类与标签都是空的,没有可写的值。")
|
|
55
|
+
final_tags: list[str] = [str(tag) for tag in new_tags]
|
|
56
|
+
if not final_tags:
|
|
57
|
+
raise Fail("标签是空的,没有可写的值。")
|
|
73
58
|
|
|
74
59
|
when = now()
|
|
75
60
|
updated = dict(task)
|
|
76
|
-
updated["category"] = final_category
|
|
77
61
|
updated["tags"] = final_tags
|
|
78
62
|
updated["updated"] = when
|
|
79
63
|
|
|
@@ -82,15 +66,15 @@ def relabel(
|
|
|
82
66
|
if path is None:
|
|
83
67
|
mine.put(tid, updated)
|
|
84
68
|
return Result(
|
|
85
|
-
changed=("
|
|
69
|
+
changed=("tags", "updated"),
|
|
86
70
|
where=rel(repo, mine.path),
|
|
87
|
-
message=f"{tid}
|
|
71
|
+
message=f"{tid}:改标签",
|
|
88
72
|
notices=("提示:这条任务没有过程文档,只改了分片,文档与历史里看不到。",),
|
|
89
73
|
)
|
|
90
74
|
|
|
91
75
|
text = logs.read(path)
|
|
92
|
-
note = "
|
|
93
|
-
text = rewrite_label_line(text,
|
|
76
|
+
note = "改标签:" + "、".join(final_tags)
|
|
77
|
+
text = rewrite_label_line(text, final_tags)
|
|
94
78
|
text = append_exec_log(text, exec_line(when, me.stamp(), "set", note))
|
|
95
79
|
text = rewrite_status_line(
|
|
96
80
|
text, StatusLine(task.get("status") or "doing", me.name, me.uuid, when)
|
|
@@ -99,7 +83,7 @@ def relabel(
|
|
|
99
83
|
mine.put(tid, updated)
|
|
100
84
|
|
|
101
85
|
return Result(
|
|
102
|
-
changed=("
|
|
86
|
+
changed=("tags", "updated"),
|
|
103
87
|
where=rel(repo, mine.path),
|
|
104
|
-
message=f"{tid}
|
|
88
|
+
message=f"{tid}:改标签",
|
|
105
89
|
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""改归属 / 改标签的入口:决定走哪个动作,**自己不加锁、不改任何文件**。
|
|
2
2
|
|
|
3
|
-
它是全树唯一的跨动作引用点(import `release` / `handoff` / `claim`)。
|
|
3
|
+
它是全树唯一的跨动作引用点(import `release` / `handoff` / `claim` / `relabel`)。
|
|
4
4
|
|
|
5
5
|
任务状态只随命令语义变(认领 → 进行中、释放 → 待认领、结案 → 历史),
|
|
6
6
|
**没有单独的「改状态」入口**:`--status` 于 2026-10-23 删除 —— 全仓没有任何
|
|
7
7
|
产品调用方(看板只有 claim / release / close 三个写路由),且它能制造
|
|
8
|
-
|
|
8
|
+
:「归我名下、状态却还是待认领」的坏状态(看板上显示成没人认领)。
|
|
9
|
+
|
|
10
|
+
**也没有改分类的入口**(2026-10-27 删除 `--category`):分类在 `new` 时定死,
|
|
11
|
+
之后谁都改不了;标题与创建时间同理,建完即不可变。`--tags` 仍可随时改。
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
from __future__ import annotations
|
|
@@ -23,10 +26,7 @@ from . import release as release_mod
|
|
|
23
26
|
from .guard import require_uuid
|
|
24
27
|
from .result import Result, rel
|
|
25
28
|
|
|
26
|
-
_MIXED =
|
|
27
|
-
"--category / --tags 不能与 --release / --owner 同时给:"
|
|
28
|
-
"一个是改分类标签,一个是改归属,分两次跑。"
|
|
29
|
-
)
|
|
29
|
+
_MIXED = "--tags 与 --release / --owner 不能同给:一个改标签,一个改归属。"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def set_task(
|
|
@@ -36,28 +36,26 @@ def set_task(
|
|
|
36
36
|
*,
|
|
37
37
|
owner: str | None = None,
|
|
38
38
|
release: bool = False,
|
|
39
|
-
category: str | None = None,
|
|
40
39
|
tags: str | tuple[str, ...] | None = None,
|
|
41
40
|
) -> Result:
|
|
42
41
|
"""路由:`--release` = 释放;别人的 uuid = 交接;自己的 uuid = 已认领则空转、没认领先认领;
|
|
43
|
-
`--
|
|
42
|
+
`--tags` = 改标签(写分片 + 过程文档头部标签行)。
|
|
44
43
|
"""
|
|
45
|
-
relabel_wanted = bool(category or tags)
|
|
46
44
|
if release:
|
|
47
45
|
if owner is not None:
|
|
48
46
|
raise Fail("--release 与 --owner 不能同时给:释放不带人,交接不带释放。")
|
|
49
|
-
if
|
|
47
|
+
if tags:
|
|
50
48
|
raise Fail(_MIXED)
|
|
51
49
|
return release_mod.release(repo, me, tid)
|
|
52
50
|
if owner is not None:
|
|
53
|
-
if
|
|
51
|
+
if tags:
|
|
54
52
|
raise Fail(_MIXED)
|
|
55
53
|
else:
|
|
56
|
-
if
|
|
57
|
-
return relabel_mod.relabel(repo, me, tid,
|
|
54
|
+
if tags:
|
|
55
|
+
return relabel_mod.relabel(repo, me, tid, tags=tags)
|
|
58
56
|
raise Fail(
|
|
59
57
|
"set 必须给一样:`--release`(释放回公共池)/ `--owner <对方 uuid>`(交接)/ "
|
|
60
|
-
"`--
|
|
58
|
+
"`--tags <a、b>`(改标签)。"
|
|
61
59
|
)
|
|
62
60
|
# 交接目标只收 uuid:名字查不到就当场报错,绝不去猜「他大概是这个人」
|
|
63
61
|
target = require_uuid(owner, field="--owner")
|
|
@@ -15,7 +15,7 @@ from .time import TIME_RE, time_key
|
|
|
15
15
|
|
|
16
16
|
HISTORY_TITLE = "# 进度历史(已结案)"
|
|
17
17
|
HISTORY_HEADER = (
|
|
18
|
-
"| 结案时间 | 创建时间 |
|
|
18
|
+
"| 结案时间 | 创建时间 | 标题 | 分类 | 标签 "
|
|
19
19
|
"| owner | owner_name | creator | creator_name | 完成了什么 | 过程文档 |"
|
|
20
20
|
)
|
|
21
21
|
HISTORY_SEP = "|---|---|---|---|---|---|---|---|---|---|---|"
|
|
@@ -48,21 +48,20 @@ def parse_tags(raw: str | Iterable[str] | None) -> tuple[str, ...]:
|
|
|
48
48
|
return tuple(out)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def label_line(
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
def label_line(tags: Iterable[str] | None) -> str:
|
|
52
|
+
"""过程文档头部的「标签」行;标签为空返回空串(此行不写)。
|
|
53
|
+
|
|
54
|
+
分类**不进过程文档**(2026-10-27 用户拍板):它只活在分片字段与历史表里,
|
|
55
|
+
标签可涵盖分类(「建设」「修改」这类词直接当标签写)。
|
|
56
|
+
"""
|
|
56
57
|
tag_list = [t for t in (tags or ()) if t]
|
|
57
|
-
if tag_list
|
|
58
|
-
segs.append(f"**标签**:{'、'.join(tag_list)}")
|
|
59
|
-
return "- " + " · ".join(segs) if segs else ""
|
|
58
|
+
return f"- **标签**:{'、'.join(tag_list)}" if tag_list else ""
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
def parse_label_line(text: str) -> tuple[str | None, tuple[str, ...]]:
|
|
63
62
|
"""从过程文档头部(第一个 `## ` 小节之前)解析分类与标签。
|
|
64
63
|
|
|
65
|
-
跳过 HTML
|
|
64
|
+
跳过 HTML 注释块:模板注释里的示例不是数据。老文档的分类段照样能解析出来。
|
|
66
65
|
"""
|
|
67
66
|
in_comment = False
|
|
68
67
|
for line in text.splitlines():
|
|
@@ -21,8 +21,8 @@ EXEC_HINT = (
|
|
|
21
21
|
' 补记用 progress.py log --id <任务号> --note "一句话"。 -->'
|
|
22
22
|
)
|
|
23
23
|
FROZEN_MARK = "封卷"
|
|
24
|
-
#:
|
|
25
|
-
#:
|
|
24
|
+
#: 头部四行的规范顺序:标签 → 状态 → 创建 → 封卷。
|
|
25
|
+
#: 标签行只在有标签时写(分类不进文档),「封卷」只在结案时写,另两行必有。
|
|
26
26
|
HEAD_KEY_ORDER = ("meta", "status", "created", "frozen")
|
|
27
27
|
#: 头部行前缀:新格式是无序列表 `- `,老文档是引用块 `> ` —— 读的时候两种都认。
|
|
28
28
|
_HEAD_BULLET = r"^(?:\s*>\s?|\s*[-*]\s+)"
|
|
@@ -168,7 +168,6 @@ def render_worklog(
|
|
|
168
168
|
title: str,
|
|
169
169
|
created: str,
|
|
170
170
|
creator: str,
|
|
171
|
-
category: str | None = None,
|
|
172
171
|
tags: Iterable[str] | None = None,
|
|
173
172
|
status: StatusLine | None = None,
|
|
174
173
|
template: str,
|
|
@@ -198,12 +197,13 @@ def render_worklog(
|
|
|
198
197
|
"<status>",
|
|
199
198
|
render_status_body(status.status, status.name, status.uuid, status.when),
|
|
200
199
|
)
|
|
201
|
-
meta = label_line(
|
|
200
|
+
meta = label_line(tags)
|
|
202
201
|
if not meta:
|
|
203
202
|
return text
|
|
204
203
|
return "\n".join(_insert_head_line(text.splitlines(), meta, "meta")) + "\n"
|
|
205
204
|
|
|
206
205
|
|
|
206
|
+
#: 头部「标签」行;老格式的「分类」段也一并匹配 —— 改写时整行替换,分类自然消失。
|
|
207
207
|
_META_LINE_RE = re.compile(_HEAD_BULLET + r"\*\*(?:分类|标签)\*\*")
|
|
208
208
|
|
|
209
209
|
|
|
@@ -362,11 +362,9 @@ def stamp_frozen(text: str, finished: str, who: str | None = "") -> str:
|
|
|
362
362
|
return "\n".join(_insert_head_line(text.splitlines(), mark, "frozen")) + "\n"
|
|
363
363
|
|
|
364
364
|
|
|
365
|
-
def rewrite_label_line(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
"""改写头部的分类 / 标签行:有就地替换,没有就按头部顺序插进去。"""
|
|
369
|
-
line = label_line(category, tags)
|
|
365
|
+
def rewrite_label_line(text: str, tags: Iterable[str] | None) -> str:
|
|
366
|
+
"""改写头部的标签行:有就地替换(老文档的分类段一并清掉),没有就按头部顺序插进去。"""
|
|
367
|
+
line = label_line(tags)
|
|
370
368
|
lines = text.splitlines()
|
|
371
369
|
for i, ln in enumerate(lines):
|
|
372
370
|
if ln.startswith("## "):
|
|
@@ -45,13 +45,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
45
45
|
claim = sub.add_parser("claim", help="认领公共池里的任务")
|
|
46
46
|
claim.add_argument("--id", required=True, dest="tid")
|
|
47
47
|
|
|
48
|
-
setp = sub.add_parser("set", help="释放 / 交接 /
|
|
48
|
+
setp = sub.add_parser("set", help="释放 / 交接 / 改标签")
|
|
49
49
|
setp.add_argument("--id", required=True, dest="tid")
|
|
50
50
|
setp.add_argument("--owner", help="交给别人的 uuid(交接)")
|
|
51
51
|
setp.add_argument("--release", action="store_true", help="释放回公共池")
|
|
52
|
-
setp.add_argument(
|
|
53
|
-
"--category", help="改分类(与 --tags 一起改过程文档头部分类/标签行)"
|
|
54
|
-
)
|
|
55
52
|
setp.add_argument("--tags", help="改标签,顿号 / 逗号分隔")
|
|
56
53
|
|
|
57
54
|
logp = sub.add_parser("log", help="记一条进展")
|
|
@@ -62,7 +59,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
62
59
|
close.add_argument("--id", required=True, dest="tid")
|
|
63
60
|
close.add_argument("--summary", required=True)
|
|
64
61
|
close.add_argument("--finished")
|
|
65
|
-
close.add_argument("--category")
|
|
66
62
|
close.add_argument("--tags")
|
|
67
63
|
|
|
68
64
|
listp = sub.add_parser("list", help="查在办任务")
|
|
@@ -53,7 +53,7 @@ class HistoryStore:
|
|
|
53
53
|
) -> str:
|
|
54
54
|
"""这个月的历史里已经有这条了吗?三态:`hit` / `miss` / `undecidable`。
|
|
55
55
|
|
|
56
|
-
有末列就比**文件名**(不比整条路径)。末列为空的行改按「创建时间 +
|
|
56
|
+
有末列就比**文件名**(不比整条路径)。末列为空的行改按「创建时间 + 标题」两列比
|
|
57
57
|
—— 那种行在调用方给不出 `created` / `title` 时判不了,明说 `undecidable`,不拿 miss 冒充。
|
|
58
58
|
"""
|
|
59
59
|
records, _ = history_md.parse_history(self.read(path))
|
|
@@ -130,7 +130,6 @@ class WorklogStore:
|
|
|
130
130
|
title: str,
|
|
131
131
|
created: str,
|
|
132
132
|
creator: str,
|
|
133
|
-
category: str | None = None,
|
|
134
133
|
tags: tuple[str, ...] = (),
|
|
135
134
|
status: worklog_md.StatusLine | None = None,
|
|
136
135
|
first_exec: str | None = None,
|
|
@@ -144,7 +143,6 @@ class WorklogStore:
|
|
|
144
143
|
title=title,
|
|
145
144
|
created=created,
|
|
146
145
|
creator=creator,
|
|
147
|
-
category=category,
|
|
148
146
|
tags=tags,
|
|
149
147
|
status=status,
|
|
150
148
|
template=self.template(),
|