dsh-task-ledger 0.0.129 → 0.0.130

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.
@@ -35,6 +35,8 @@ function toRecord(raw, workspace) {
35
35
  : path.join(workspace.path, "ledger", "history", text(raw.worklog)),
36
36
  src: deleteSrc,
37
37
  line,
38
+ // 整行指纹:删除时原样回传给 skill(`delete --expect`),行已变就会被拒,防止行号漂移删错行。
39
+ fingerprint: text(raw.fingerprint),
38
40
  };
39
41
  }
40
42
  export async function readHistory(workspace, options) {
@@ -7,9 +7,11 @@ export type TaskActionStep = "check" | "sync" | "write" | "commit" | "push";
7
7
  *
8
8
  * @param summary - 取消 / 结案的摘要、删除的留言;留空就不传 `--summary`。
9
9
  * @param onStep - 过程步骤回调(逐条转发给界面做进度翻牌),不传就不让 skill 打标记。
10
- * @param history - 给就是删历史行(`delete --src/--line`),不给就是删在办任务(`delete --id`)。
10
+ * @param history - 给就是删历史行(`delete --src/--line [--expect]`),不给就是删在办任务(`delete --id`)。
11
+ * `fingerprint` 是该行指纹:skill 写前核对,行已变(行号漂移 / 别处改过)就整条拒绝。
11
12
  */
12
13
  export declare function runTaskAction(repo: string, id: string, action: TaskAction, summary?: string, onStep?: (step: TaskActionStep) => void, history?: {
13
14
  readonly src: string;
14
15
  readonly line: number;
16
+ readonly fingerprint?: string;
15
17
  }): Promise<void>;
@@ -19,7 +19,8 @@ const ACTION_NAME = {
19
19
  *
20
20
  * @param summary - 取消 / 结案的摘要、删除的留言;留空就不传 `--summary`。
21
21
  * @param onStep - 过程步骤回调(逐条转发给界面做进度翻牌),不传就不让 skill 打标记。
22
- * @param history - 给就是删历史行(`delete --src/--line`),不给就是删在办任务(`delete --id`)。
22
+ * @param history - 给就是删历史行(`delete --src/--line [--expect]`),不给就是删在办任务(`delete --id`)。
23
+ * `fingerprint` 是该行指纹:skill 写前核对,行已变(行号漂移 / 别处改过)就整条拒绝。
23
24
  */
24
25
  export async function runTaskAction(repo, id, action, summary = "", onStep, history) {
25
26
  const opts = {
@@ -63,7 +64,15 @@ export async function runTaskAction(repo, id, action, summary = "", onStep, hist
63
64
  result = await runLedgerCommand("delete", [
64
65
  ...(history === undefined
65
66
  ? ["--id", id]
66
- : ["--src", history.src, "--line", String(history.line)]),
67
+ : [
68
+ "--src",
69
+ history.src,
70
+ "--line",
71
+ String(history.line),
72
+ ...((history.fingerprint ?? "") === ""
73
+ ? []
74
+ : ["--expect", history.fingerprint ?? ""]),
75
+ ]),
67
76
  ...(summary === "" ? [] : ["--summary", summary]),
68
77
  "--repo",
69
78
  repo,
@@ -287,6 +287,9 @@ export function registerRoutes(webServer, deps) {
287
287
  body.line > 0
288
288
  ? body.line
289
289
  : 0;
290
+ // 行指纹(skill `history --json` 的 fingerprint):删历史行必须带 ——
291
+ // 行号只是坐标,只有整行指纹能证明「要删的还是用户看到的那一条」。
292
+ const fingerprint = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
290
293
  const historyDelete = action === "delete" && src !== "";
291
294
  if (historyDelete) {
292
295
  if (!src.startsWith("history/") ||
@@ -295,6 +298,8 @@ export function registerRoutes(webServer, deps) {
295
298
  throw new Error(`${BAD_REQUEST}历史文件路径不合法`);
296
299
  if (line === 0)
297
300
  throw new Error(`${BAD_REQUEST}历史行号不合法`);
301
+ if (!/^[0-9a-f]{12}$/.test(fingerprint))
302
+ throw new Error(`${BAD_REQUEST}历史行指纹不合法(缺少行身份,删除已拒绝)`);
298
303
  }
299
304
  else if (id === "") {
300
305
  throw new Error(`${BAD_REQUEST}必须给任务号`);
@@ -308,7 +313,7 @@ export function registerRoutes(webServer, deps) {
308
313
  repo: workspace.path,
309
314
  id,
310
315
  summary,
311
- ...(historyDelete ? { src, line } : {}),
316
+ ...(historyDelete ? { src, line, fingerprint } : {}),
312
317
  };
313
318
  }
314
319
  catch (error) {
@@ -8,6 +8,8 @@ export interface TaskActionInput {
8
8
  /** 删除历史行时的定位(相对 ledger/ 的路径 + 原文行号);不给就是删在办任务。 */
9
9
  readonly src?: string;
10
10
  readonly line?: number;
11
+ /** 该行指纹(skill `history --json` 的 fingerprint):写前核对,行已变则拒绝删。 */
12
+ readonly fingerprint?: string;
11
13
  /** 取消 / 删除时的一句话留言(可选;进过程文档执行记录)。 */
12
14
  readonly summary?: string;
13
15
  }
@@ -11,7 +11,7 @@ import { runTaskAction, } from "../repositories/task.repository.js";
11
11
  */
12
12
  export async function actOnTask(input, onStep) {
13
13
  await runTaskAction(input.repo, input.id, input.action, input.summary ?? "", onStep, input.src !== undefined && input.line !== undefined
14
- ? { src: input.src, line: input.line }
14
+ ? { src: input.src, line: input.line, fingerprint: input.fingerprint ?? "" }
15
15
  : undefined);
16
16
  invalidate();
17
17
  }
@@ -240,6 +240,8 @@ export interface HistoryRecord {
240
240
  readonly src: string;
241
241
  /** 原文行号(1 起);0 = 定位不到,不给删。 */
242
242
  readonly line: number;
243
+ /** 整行指纹(skill `history --json` 的 fingerprint):删这行时回传,skill 写前核对;空 = 不给删。 */
244
+ readonly fingerprint: string;
243
245
  }
244
246
  /** `/history/list` 响应:选中那个月的结案记录(按结案时间正序)。
245
247
  * 分页不在这里做 —— 整月一次下发,前端按行号做虚拟滚动。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-task-ledger",
3
- "version": "0.0.129",
3
+ "version": "0.0.130",
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",
@@ -57,6 +57,6 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "dsh-plugin-core": "^0.30.1"
60
+ "dsh-plugin-core": "^0.30.2"
61
61
  }
62
62
  }
package/skill/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  - **谁在做、做到哪**:状态只在台账 JSON —— `ledger/pool.json`(公共池)+ 每人一份 `ledger/owners/<hash>.json` 分片,索引 `ledger/owners.json`。
10
10
  - **过程正文**:全在 `ledger/worklog/<年月>/<月日>-<时分>-<说明>-<码>.md`(六节),agent 直接编辑前五节,末节「执行记录」只由脚本写。
11
- - **已结案**:一行一条在 `ledger/history/<年月>.md`(15 列),只由脚本写。
11
+ - **已结案**:一行一条在 `ledger/history/<年月>.md`(16 列),只由脚本写。
12
12
 
13
13
  状态改动与正文重写分开、全部经脚本改 —— 不另建副本、不手改状态表和历史。覆盖动作:建任务、认领、释放、结案、查在办、查历史、标签归并、体检。
14
14
 
@@ -20,7 +20,7 @@
20
20
  | `ledger/owners.json` | 索引:身份 uuid → 分片文件(`name` 只做显示 / 模糊筛) |
21
21
  | `ledger/owners/<hash>.json` | 每人一份分片:自己认领的任务 |
22
22
  | `ledger/worklog/<年月>/*.md` | 任务正文(六节) |
23
- | `ledger/history/<年月>.md` | 已结案(一行一条,15 列) |
23
+ | `ledger/history/<年月>.md` | 已结案(一行一条,16 列) |
24
24
 
25
25
  ## 为什么这么设计
26
26
 
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: task-ledger
3
3
  description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)、给任务结案 / 取消作废 / 退回(做完了 / 验收通过 / 取消 / 作废 / 退回待认领)、删除建错的或垃圾任务(只认「删除」二字)、整理标签或检查台账一致性时加载;只是改代码、讨论方案时不要用。
4
- version: 1.3.40
4
+ version: 1.3.53
5
5
  allowed-tools: Read,Edit,Bash,Grep
6
6
  ---
7
7
 
@@ -44,13 +44,14 @@ allowed-tools: Read,Edit,Bash,Grep
44
44
  | `--id` | 自定义任务号(一般不传;只能用小写字母、数字、汉字和连字符) |
45
45
  | `--created` | 给过去漏记的任务补建时,补录实际创建日期(`YYYY-MM-DD`) |
46
46
  | `--self` | 建并认领:直接归自己名下、状态「进行中」。仅当用户明确说「并开始做 / 并执行」时才用(判定见生命周期第 2 条) |
47
- | `--category` / `--tags` | 分类 / 标签,**只在建任务时能设分类**,口径见本节末尾 |
47
+ | `--tags <a、b>` | 标签,**必填**(不给直接报错);口径见本节末尾 |
48
+ | `--category` | 分类,**只在建任务时能设**(之后谁都改不了) |
48
49
 
49
50
  干什么:建任务、按模板建好过程文档。不加 `--self` = 只建,落公共池等认领;加 `--self` = 建并认领。
50
51
 
51
- 分类 / 标签口径(`--category` 只有 `new` 有;`--tags` 三个命令都有):`--category` 答「是什么类型」,单值,中文 ≤4 字 / 纯英文 ≤3 词,吃不准写「其他」,**建任务时给一次,之后谁都改不了**(`set` / `close` 没有这个参数,历史行里就是建任务时那个值;标题与创建时间同理,建完即不可变)。`--tags` 答「涉及什么」,0~5 个、逗号分隔,建任务时能想到就给(传空串等于没传);在办期间用 `set --tags` 补;**结案是最该给终值的一段** —— 任务做完了、全貌最清楚,用 `close --tags` 定下来,不传就沿用原标签。**分类只进台账与历史**(`list` / `history` 看得到),**不写进过程文档**;过程文档头部只有标签行,标签可以涵盖分类(「建设」「修改」这类词直接当标签写)。查询用 tags(包含匹配,见 `list --tag`)。名词性短语、中文优先。正例:`--category 缺陷修复 --tags "镜像拉取,超时重试"`;反例:`--category 已完成登录修复`、`--tags 修 bug`。
52
+ 分类 / 标签口径(`--category` 只有 `new` 有;`--tags` 三个命令都有):`--category` 答「是什么类型」,单值,中文 ≤4 字 / 纯英文 ≤3 词,吃不准写「其他」,**建任务时给一次,之后谁都改不了**(`set` / `close` 没有这个参数,历史行里就是建任务时那个值;标题与创建时间同理,建完即不可变)。`--tags` 答「涉及什么」,1~5 个、逗号分隔,**建任务必填**(没标签的任务事后没人补,建之前就想清涉及什么,别拿标题词硬凑);在办期间用 `set --tags` 改;**结案是最该给终值的一段** —— 任务做完了、全貌最清楚,用 `close --tags` 定下来,不传就沿用原标签。**分类只进台账与历史**(`list` / `history` 看得到),**不写进过程文档**;过程文档头部只有标签行,标签可以涵盖分类(「建设」「修改」这类词直接当标签写)。查询用 tags(包含匹配,见 `list --tag`)。名词性短语、中文优先。正例:`--category 缺陷修复 --tags "镜像拉取,超时重试"`;反例:`--category 已完成登录修复`、`--tags 修 bug`。
52
53
 
53
- 举例:用户说「新建个任务:把发布流程走通」→ `new --title "把发布流程走通"`(只建,不认领);用户明确说「新建个任务并开始做」→ `new --title "把发布流程走通" --self`。
54
+ 举例:用户说「新建个任务:把发布流程走通」→ `new --title "把发布流程走通" --tags 发布流程,CI`(只建,不认领);用户明确说「新建个任务并开始做」→ 同样带上 `--tags` 再加 `--self`。
54
55
 
55
56
  ### claim — 认领
56
57
 
@@ -134,8 +135,9 @@ allowed-tools: Read,Edit,Bash,Grep
134
135
 
135
136
  | 参数 | 含义 |
136
137
  |---|---|
137
- | `--id <任务号>` | 删除在办任务(公共池或自己名下分片):移进历史,结果列留空、删除三列落值;公共池任务不必先 `claim` |
138
+ | `--id <任务号>` | 删除在办任务(公共池或自己名下分片):移进历史,结果列留空、删除四列落值(含删除人显示名);公共池任务不必先 `claim` |
138
139
  | `--src <历史文件> --line <行号>` | 删除历史表格里那一行。`--src` 收相对 `ledger/` 的路径(如 `history/202610.md`),`--line` 是 `history --json` 给出的 `line`;行上原来的结果列与摘要一律不动 |
140
+ | `--expect <指纹>` | 可省,配 `--src` 用:把 `history --json` 的 `fingerprint` 原样回传。写前核对这一行还是不是它(行号只是坐标,文件被改写过就会漂移),**对不上就整条拒绝、一个字节都不写**。建议凡拿过 `fingerprint` 就带上 |
139
141
  | `--summary <留言>` | 可省;只进过程文档执行记录,不写历史摘要 |
140
142
  | `--repo <工作区根>` | 同上 |
141
143
 
@@ -337,8 +339,8 @@ allowed-tools: Read,Edit,Bash,Grep
337
339
 
338
340
  用户要「做某件事」时按这条线走。
339
341
 
340
- 1. **建(只建,不认领)—— 默认**:「新建一个任务」「新建个任务,我们来干 X」「新建个任务,我需要处理 Y」→ `new --title "…"`。落公共池、待认领。
341
- 2. **建并认领 —— 仅限明确说法**:用户明确说「新建一个任务**并开始做 / 并执行**」这类带着「现在就开始」的话 → `new --title "…" --self`。直接归自己名下、「进行中」。**除这种明确说法,其他一律按第 1 条只建** —— 别替用户决定他要开始做;拿不定,问一句。
342
+ 1. **建(只建,不认领)—— 默认**:「新建一个任务」「新建个任务,我们来干 X」「新建个任务,我需要处理 Y」→ `new --title "…" --tags a,b`(`--tags` 必填)。落公共池、待认领。
343
+ 2. **建并认领 —— 仅限明确说法**:用户明确说「新建一个任务**并开始做 / 并执行**」这类带着「现在就开始」的话 → `new --title "…" --tags a,b --self`。直接归自己名下、「进行中」。**除这种明确说法,其他一律按第 1 条只建** —— 别替用户决定他要开始做;拿不定,问一句。
342
344
  3. **找已有的**:用户说「我们来做 X」→ 先 `list --mine` 找自己名下的,在做的直接接着做;没有 → `list --pool` 里找,找到先问用户:「X 还没认领,认领它吗?」→ 用户点头再 `claim`;哪儿都没有 → **不擅自建**:问「要为 X 建个任务吗?」,同意才按第 1/2 条建,不同意就只干活、不登记。
343
345
  4. **认领**:`claim --id <任务号>` —— 认到自己名下,状态即「进行中」。
344
346
  5. **推进**:改 worklog 前五节 → 按「过程文档」节的收工三步记 `log`。卡住也 `log`(写清卡在哪,状态不动);「先放一放 / 现在不做了,一会儿做」同理 —— 是暂停,不是释放,更不是取消(取消只有用户明确说「取消 / 作废」才算)。
@@ -379,7 +381,7 @@ allowed-tools: Read,Edit,Bash,Grep
379
381
  - **Sealed**: 2026-10-27 09:20:10 · @某人 · 8d557c6d-…
380
382
  ```
381
383
 
382
- 标签行与封卷行不一定有(没给标签、没结案就没有)。旧中文头部(`- **标签**:…` / 「**分类**」段)是旧格式,不再兼容,将统一迁移;`show --json` 不再标 `legacy`。
384
+ 标签行新建必有(`--tags` 必填;早于这条规则的老文档可能没有);封卷行只在结案后有。旧中文头部(`- **标签**:…` / 「**分类**」段)是旧格式,不再兼容,将统一迁移;`show --json` 不再标 `legacy`。
383
385
  - 执行记录行格式 `- 时间(UTC) · 类型 · @谁 · 说明`,`@谁` 是这次动手的人、不是任务现归属;只追加、不改旧行。行首「类型」是脚本内部动作名(会见到 `release`,它就是 `set --release`);认不出就别当成新命令,按 `--help` 查。
384
386
  - 只改某条任务的正文:`show --id <任务号>` 拿路径直接改,不另建 md;已结案的用 `history --months 12 --json` 找回链接(**人话输出里没有链接,必须加 `--json`**)——默认只查近 3 个月,结案久了的要显式放大窗口;只读不改。
385
387
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.51",
3
+ "version": "1.3.53",
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 文件 15 列**:Finished / Created / Title / Category / Tags / Owner / Owner Name / Creator / Creator Name / Summary / Result / Worklog / Deleted / Deleted By / Deleted At(2026-10-29 起写英文表头);只认 15 列,列数不符的行静默跳过,不告警、不兼容、不提示迁移。末三列是删除标记 `yes` / 删除人 uuid / UTC 删除时刻,未删留空;`Deleted == "yes"` 的行从所有列表、统计、趋势中整行忽略,原始行仍留在文件里。排序与 `--since` / `--until` 按**结案时间**。(`history` 命令的人话输出只展示其中几列:结案时间、负责人、标题。)
33
+ - **history 文件 16 列**:Finished / Created / Title / Category / Tags / Owner / Owner Name / Creator / Creator Name / Summary / Result / Worklog / Deleted / Deleted By / Deleted By Name / Deleted At(2026-10-29 起写英文表头);只认 16 列,列数不符的行静默跳过,不告警、不兼容、不提示迁移。末四列是删除标记 `yes` / 删除人 uuid / 删除人显示名快照(`@名字`,读时剥 `@`)/ UTC 删除时刻,未删留空;`Deleted == "yes"` 的行从所有列表、统计、趋势中整行忽略,原始行仍留在文件里。排序与 `--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
- - 按**结案时间**分月存放,15 列(列序:Owner 之后是 `Owner Name`,再是 Creator、`Creator Name`,Summary 之后是 Result,然后是 Worklog,末尾三列是 `Deleted` / `Deleted By` / `Deleted At`);`close` 写历史行时:分类用任务原值(**建任务后不可改**),标签用终值;`delete` 删在办任务时结果列留空、删除三列落值。
50
+ - 按**结案时间**分月存放,16 列(列序:Owner 之后是 `Owner Name`,再是 Creator、`Creator Name`,Summary 之后是 Result,然后是 Worklog,末尾四列是 `Deleted` / `Deleted By` / `Deleted By Name` / `Deleted At`);`close` 写历史行时:分类用任务原值(**建任务后不可改**),标签用终值;`delete` 删在办任务时结果列留空、删除四列落值。
51
51
  - 存量平铺历史文件只读兼容、永不重写;`history` 连同它在内、按下面的月份区间合并。
52
52
  - **查询区间**:`history` 先由时间参数算出要读的月份文件,再逐个「存在才读、缺月跳过」,不扫磁盘。无时间参数默认最近 3 个自然月;`--months N`(近 N 月)与 `--since/--until`(结案时间闭区间)二选一;一次最多读 13 个历史文件(按月分文件,不区分连续/分散,只数总数),超出直接报错、不读任何文件。末列文档链接只按**文件名**消费(取 basename),链接里的 `../worklog/<年月>/` 前缀只为阅读器跳转服务,解析时忽略。
53
- - `history --json` 直接按文件现状解析,名字列为空就按名册补展示值,不回写;只认 15 列,脚本不写迁移 / 兼容程序,老工作区由 agent 逐个手工改为 15 列。
53
+ - `history --json` 直接按文件现状解析,名字列为空就按名册补展示值,不回写;只认 16 列,脚本不写迁移 / 兼容程序,老工作区由 agent 逐个手工改为 16 列。