dsh-task-ledger 0.0.100 → 0.0.102
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/host/repositories/runner.d.ts +1 -1
- package/dist/host/repositories/runner.js +10 -7
- package/dist/host/skill/registration.d.ts +2 -1
- package/dist/host/skill/registration.js +4 -3
- package/package.json +1 -1
- package/skill/README.md +4 -2
- package/skill/SKILL.md +17 -14
- package/skill/assets/worklog.md +3 -3
- package/skill/package.json +1 -1
- package/skill/references/init-identity.md +3 -3
- package/skill/references/log-note.md +1 -1
- package/skill/references/state-model.md +1 -1
- package/skill/references/troubleshooting.md +1 -1
- package/skill/scripts/bin/ledger-darwin-arm64 +0 -0
- package/skill/scripts/bin/ledger-linux-amd64 +0 -0
- package/skill/scripts/bin/ledger-windows-amd64.exe +0 -0
- package/skill/scripts/progress +44 -0
- package/skill/scripts/ledgerkit/__init__.py +0 -0
- package/skill/scripts/ledgerkit/actions/__init__.py +0 -0
- package/skill/scripts/ledgerkit/actions/atom.py +0 -154
- package/skill/scripts/ledgerkit/actions/bootstrap.py +0 -37
- package/skill/scripts/ledgerkit/actions/claim.py +0 -88
- package/skill/scripts/ledgerkit/actions/close_task.py +0 -193
- package/skill/scripts/ledgerkit/actions/guard.py +0 -158
- package/skill/scripts/ledgerkit/actions/handoff.py +0 -93
- package/skill/scripts/ledgerkit/actions/init_identity.py +0 -90
- package/skill/scripts/ledgerkit/actions/list_tasks.py +0 -74
- package/skill/scripts/ledgerkit/actions/log_progress.py +0 -67
- package/skill/scripts/ledgerkit/actions/new_task.py +0 -130
- package/skill/scripts/ledgerkit/actions/query.py +0 -35
- package/skill/scripts/ledgerkit/actions/query_history.py +0 -220
- package/skill/scripts/ledgerkit/actions/registry.py +0 -82
- package/skill/scripts/ledgerkit/actions/relabel.py +0 -105
- package/skill/scripts/ledgerkit/actions/release.py +0 -76
- package/skill/scripts/ledgerkit/actions/result.py +0 -90
- package/skill/scripts/ledgerkit/actions/set_task.py +0 -73
- package/skill/scripts/ledgerkit/actions/show_task.py +0 -55
- package/skill/scripts/ledgerkit/actions/validate.py +0 -189
- package/skill/scripts/ledgerkit/core/__init__.py +0 -0
- package/skill/scripts/ledgerkit/core/history_md.py +0 -152
- package/skill/scripts/ledgerkit/core/identity.py +0 -54
- package/skill/scripts/ledgerkit/core/ids.py +0 -58
- package/skill/scripts/ledgerkit/core/labels.py +0 -90
- package/skill/scripts/ledgerkit/core/paths.py +0 -92
- package/skill/scripts/ledgerkit/core/roots.py +0 -74
- package/skill/scripts/ledgerkit/core/time.py +0 -103
- package/skill/scripts/ledgerkit/core/worklog_md.py +0 -437
- package/skill/scripts/ledgerkit/env.py +0 -94
- package/skill/scripts/ledgerkit/errors.py +0 -15
- package/skill/scripts/ledgerkit/notices.py +0 -28
- package/skill/scripts/ledgerkit/shell/__init__.py +0 -0
- package/skill/scripts/ledgerkit/shell/cli.py +0 -536
- package/skill/scripts/ledgerkit/shell/gitops.py +0 -668
- package/skill/scripts/ledgerkit/shell/merge.py +0 -266
- package/skill/scripts/ledgerkit/shell/presync.py +0 -23
- package/skill/scripts/ledgerkit/shell/render.py +0 -216
- package/skill/scripts/ledgerkit/shell/runner.py +0 -232
- package/skill/scripts/ledgerkit/store/__init__.py +0 -0
- package/skill/scripts/ledgerkit/store/atomic.py +0 -130
- package/skill/scripts/ledgerkit/store/dirty.py +0 -108
- package/skill/scripts/ledgerkit/store/history_store.py +0 -134
- package/skill/scripts/ledgerkit/store/json_store.py +0 -166
- package/skill/scripts/ledgerkit/store/locks.py +0 -64
- package/skill/scripts/ledgerkit/store/owners_index.py +0 -127
- package/skill/scripts/ledgerkit/store/task_store.py +0 -28
- package/skill/scripts/ledgerkit/store/worklog_store.py +0 -180
- package/skill/scripts/progress.py +0 -20
package/dist/client.js
CHANGED
|
@@ -4883,8 +4883,8 @@ body[data-ds-dark-theme]{
|
|
|
4883
4883
|
onClick: onBack
|
|
4884
4884
|
}, `← ${t("backToConversation")}`), (0, react.createElement)("div", { style: titleStyle }, t("panelTitle"), (0, react.createElement)("span", {
|
|
4885
4885
|
style: buildTagStyle,
|
|
4886
|
-
title: `v0.0.
|
|
4887
|
-
}, `v0.0.
|
|
4886
|
+
title: `v0.0.102 · 261027-164833-849`
|
|
4887
|
+
}, `v0.0.102 261027-164833-849`))), (0, react.createElement)("div", { style: {
|
|
4888
4888
|
display: "flex",
|
|
4889
4889
|
alignItems: "center",
|
|
4890
4890
|
gap: "var(--dtl-space-2)"
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
/**
|
|
4
|
-
* 内嵌 skill
|
|
4
|
+
* 内嵌 skill 入口的默认路径:dist/host/repositories/runner.js → 包根 skill/scripts/progress
|
|
5
5
|
* (比原 host/script.js 深一层 ⇒ 相对路径多一级 `../`)。
|
|
6
|
+
*
|
|
7
|
+
* 这是个薄 shell 包装,按平台 exec `skill/scripts/bin/ledger-<os>-<arch>`(自带运行时的
|
|
8
|
+
* 静态二进制)——所以本插件不再需要系统装 python。
|
|
6
9
|
* 构建时 scripts/copy-skill.mjs 把 packages/skill 拷到 packages/plugin/skill/。
|
|
7
10
|
*/
|
|
8
|
-
const DEFAULT_SCRIPT_PATH = fileURLToPath(new URL("../../../skill/scripts/progress
|
|
11
|
+
const DEFAULT_SCRIPT_PATH = fileURLToPath(new URL("../../../skill/scripts/progress", import.meta.url));
|
|
9
12
|
/** 单次脚本调用超时上限(ms)。实测单仓 5 万行全量 1.2s,10s 是宽松上限。 */
|
|
10
13
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
14
|
/**
|
|
@@ -16,14 +19,14 @@ const DEFAULT_TIMEOUT_MS = 10_000;
|
|
|
16
19
|
*/
|
|
17
20
|
export const WRITE_TIMEOUT_MS = 180_000;
|
|
18
21
|
/**
|
|
19
|
-
*
|
|
22
|
+
* 起一次台账程序、收 stdout / stderr。出错一律归一成人话(起不了程序 / 超时 / 非零退出)。
|
|
20
23
|
* 解析留给调用方:读命令要 JSON,写命令要原文。
|
|
21
24
|
*/
|
|
22
25
|
function exec(sub, args, opts) {
|
|
23
26
|
const scriptPath = opts?.scriptPath ?? DEFAULT_SCRIPT_PATH;
|
|
24
27
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
25
28
|
return new Promise((resolve) => {
|
|
26
|
-
const child = spawn(
|
|
29
|
+
const child = spawn(scriptPath, [sub, ...args], {
|
|
27
30
|
cwd: opts?.cwd,
|
|
28
31
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29
32
|
});
|
|
@@ -60,7 +63,7 @@ function exec(sub, args, opts) {
|
|
|
60
63
|
if (code !== 0) {
|
|
61
64
|
settle({
|
|
62
65
|
ok: false,
|
|
63
|
-
error: stderr.trim() ||
|
|
66
|
+
error: stderr.trim() || `台账程序退出码 ${String(code)}`,
|
|
64
67
|
});
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
@@ -69,7 +72,7 @@ function exec(sub, args, opts) {
|
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
72
|
-
*
|
|
75
|
+
* 读命令:跑内嵌台账程序,收 stdout JSON。
|
|
73
76
|
* 不解析任何台账格式——解析真源只有 skill 脚本一份(2026-10-17 用户拍板)。
|
|
74
77
|
*
|
|
75
78
|
* @param sub - 子命令(list / history / show…)
|
|
@@ -101,7 +104,7 @@ export async function runLedgerCommand(sub, args, opts) {
|
|
|
101
104
|
if (!result.ok)
|
|
102
105
|
return result;
|
|
103
106
|
const output = result.stdout.trim();
|
|
104
|
-
// 退出码 0 但 stderr 有字 =
|
|
107
|
+
// 退出码 0 但 stderr 有字 = 写成功、同步失败(台账程序的既定行为),必须带出去
|
|
105
108
|
return result.stderr
|
|
106
109
|
? { ok: true, output, notice: result.stderr }
|
|
107
110
|
: { ok: true, output };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** 台账入口绝对路径(提示词里直接给「原样执行这一条」的命令用;与注册共用同一份目录知识)。
|
|
2
|
+
* 它是按平台选二进制的薄包装,不需要 python。 */
|
|
2
3
|
export declare const SKILL_SCRIPT: string;
|
|
3
4
|
/** 宿主 ctx.skills.register 的最小面(外部事实文档的结构面)。 */
|
|
4
5
|
export interface SkillRegistration {
|
|
@@ -9,8 +9,9 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
const SKILL_DIR = fileURLToPath(new URL("../../../skill", import.meta.url));
|
|
10
10
|
/** SKILL.md 里指示脚本位置的占位符(注册时替换为绝对路径,模型据此找到脚本)。 */
|
|
11
11
|
const SKILL_DIR_TOKEN = "<本 SKILL.md 所在目录>";
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/** 台账入口绝对路径(提示词里直接给「原样执行这一条」的命令用;与注册共用同一份目录知识)。
|
|
13
|
+
* 它是按平台选二进制的薄包装,不需要 python。 */
|
|
14
|
+
export const SKILL_SCRIPT = `${SKILL_DIR}/scripts/progress`;
|
|
14
15
|
/** frontmatter 最小解析:只取 name / description(不引 YAML 库;值去首尾引号)。 */
|
|
15
16
|
function parseFrontmatter(text) {
|
|
16
17
|
const match = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
@@ -44,7 +45,7 @@ export function buildSkillRegistration(skillDir = SKILL_DIR) {
|
|
|
44
45
|
if (content.includes(SKILL_DIR_TOKEN)) {
|
|
45
46
|
throw new Error("占位符替换失败:SKILL.md 里仍残留脚本路径占位符");
|
|
46
47
|
}
|
|
47
|
-
if (!content.includes(`${skillDir}/scripts/progress
|
|
48
|
+
if (!content.includes(`${skillDir}/scripts/progress`)) {
|
|
48
49
|
throw new Error("占位符替换异常:替换后找不到脚本路径");
|
|
49
50
|
}
|
|
50
51
|
return { name, description, content, path: `${skillDir}/SKILL.md` };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.102",
|
|
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/README.md
CHANGED
|
@@ -31,9 +31,11 @@
|
|
|
31
31
|
直接跑:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
34
|
+
<SKILL.md 所在目录>/scripts/progress <子命令> --repo <工作区根>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
`scripts/progress` 按平台 exec `scripts/bin/ledger-<os>-<arch>`(静态二进制;已带 linux/amd64、darwin/arm64 与 windows/amd64)。
|
|
38
|
+
|
|
37
39
|
子命令:`new` `claim` `set` `log` `close` `list` `history` `show` `whoami` `validate`。命令参考(参数 / 一句话 / 举例)、任务生命周期、纪律(数据完整性 / 人类授权 / 故障处置)全在 `SKILL.md`;参数细节看 `<子命令> --help`(不要凭记忆编)。
|
|
38
40
|
|
|
39
41
|
## 深入参考(references/,按需读)
|
|
@@ -46,4 +48,4 @@ python3 <SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <工作
|
|
|
46
48
|
|
|
47
49
|
## 本仓自己的台账
|
|
48
50
|
|
|
49
|
-
本仓库(`dsh-task-ledger
|
|
51
|
+
本仓库(`dsh-task-ledger`)也用自己的这套脚本(`scripts/progress` → `src/` 编译出的二进制)管自己的活,台账数据在 `ledger/`;它和本 skill 源码 `packages/skill/` 是两回事 —— **改 skill 请别动 `ledger/`**。
|
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.29
|
|
5
5
|
allowed-tools: Read,Edit,Bash,Grep
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -45,11 +45,11 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
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
|
|
|
@@ -91,7 +92,7 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
91
92
|
|
|
92
93
|
干什么:刷任务的最后进度时间 + 在过程文档「执行记录」节追加一行。改正文必配一条 log(见纪律)。
|
|
93
94
|
|
|
94
|
-
举例:一轮干完 → `log --id <任务号> --note "改了认领即 doing(claim.
|
|
95
|
+
举例:一轮干完 → `log --id <任务号> --note "改了认领即 doing(internal/actions/claim.go:56);下一步同步文档"`。
|
|
95
96
|
|
|
96
97
|
### close — 结案
|
|
97
98
|
|
|
@@ -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
|
|
|
@@ -174,9 +175,11 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
174
175
|
## 怎么调用
|
|
175
176
|
|
|
176
177
|
```bash
|
|
177
|
-
|
|
178
|
+
<本 SKILL.md 所在目录>/scripts/progress <子命令> --repo <工作区根>
|
|
178
179
|
```
|
|
179
180
|
|
|
181
|
+
`scripts/progress` 是个薄 shell 包装,按平台 exec `scripts/bin/ledger-<os>-<arch>`(自带运行时的静态二进制;已提供 linux/amd64、darwin/arm64 与 windows/amd64)。平台或架构没有对应产物时,它会明确报错,不会静默失败。
|
|
182
|
+
|
|
180
183
|
- 定位项目:`--repo <工作区根>`(**直接含有 `ledger/` 的那个目录**)给了就校验后用;不给就从当前目录逐级向上找。`ledger/` 本身、以及它下面的任何子目录都不是根,指过去会被直接拒绝(判据只看位置,不看目录里有哪些文件 —— 项目根自带 `pool.json`、`history/`、`worklog/` 都不影响)。
|
|
181
184
|
- `--no-sync`(放在子命令**前**):用户明确要求「先别同步」时才用;之后自己 `git add ledger && commit && push`。
|
|
182
185
|
- `--json` 只对只读命令(`list` / `show` / `history` / `whoami` / `validate`)有意义;写命令不接收 `--json`,传了是用法错。
|
|
@@ -214,16 +217,16 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <工
|
|
|
214
217
|
|
|
215
218
|
- 「执行记录」节名一个字都别动;老文档缺这一节也不用补。
|
|
216
219
|
- 过程文档找不到时 `log` 只提示不失败;用 `show` 查路径,不要手写。
|
|
217
|
-
- 头部是四行,由脚本维护、**都不要手改**,顺序固定
|
|
220
|
+
- 头部是四行,由脚本维护、**都不要手改**,顺序固定 `标签 → 状态 → 创建 → 封卷`,值一律 `· ` 分隔:
|
|
218
221
|
|
|
219
222
|
```markdown
|
|
220
|
-
-
|
|
223
|
+
- **标签**:建设、DSH、台账
|
|
221
224
|
- **状态**:doing · 2026-10-27 09:12:33 · @某人 · 8d557c6d-…
|
|
222
225
|
- **创建**:2026-10-26 23:38:02 · @某人 · 8d557c6d-…
|
|
223
226
|
- **封卷**:2026-10-27 09:20:10 · @某人 · 8d557c6d-…
|
|
224
227
|
```
|
|
225
228
|
|
|
226
|
-
|
|
229
|
+
标签行与封卷行不一定有(没给标签、没结案就没有)。老文档头部可能还带着「**分类**」段,那是旧格式,不用补也不用改(脚本下次改写标签行时会一并清掉)。老文档没有状态行是合法的(`show` 回退查在办,`--json` 标 `legacy: true`),不用补。
|
|
227
230
|
- 执行记录行格式 `- 时间(UTC) · 类型 · @谁 · 说明`,`@谁` 是这次动手的人、不是任务现归属;只追加、不改旧行。行首「类型」是脚本内部动作名(会见到 `release`,它就是 `set --release`);认不出就别当成新命令,按 `--help` 查。
|
|
228
231
|
- 只改某条任务的正文:`show --id <任务号>` 拿路径直接改,不另建 md;已结案的用 `history --months 12 --json` 找回链接(**人话输出里没有链接,必须加 `--json`**)——默认只查近 3 个月,结案久了的要显式放大窗口;只读不改。
|
|
229
232
|
|
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
|
## 背景
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
## 执行记录
|
|
31
31
|
|
|
32
32
|
<!-- 本节由脚本维护:new / claim / set / release / log / close 自动追加,请勿手改。
|
|
33
|
-
补记用 progress
|
|
33
|
+
补记用 progress log --id <任务号> --note "一句话"。 -->
|
package/skill/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.29",
|
|
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",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
1. 只读报名单(你在非终端里裸跑,只会打印候选、零写):
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
|
|
14
|
+
<本 SKILL.md 所在目录>/scripts/progress init --repo <工作区根>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
2. 把名册给用户选,并把这**同一条命令**交给用户在 ta 自己的终端跑(真终端里它自己会变成 ↑/↓
|
|
17
|
+
2. 把名册给用户选,并把这**同一条命令**交给用户在 ta 自己的终端跑(真终端里它自己会变成 ↑/↓ 选择框:↑/↓ 移动、Enter 确认、末尾「+ 新建身份」输入名字、最后 Y/N 确认、ESC/Ctrl-C 取消):
|
|
18
18
|
- 名册有内容 ⇒「名册现有:@A(uuid)、@B(uuid)。请在你的终端运行上面这条,它会让你选。」
|
|
19
19
|
- 名册为空 ⇒「名册为空。请在你的终端运行上面这条,输入一个用户名新建。」
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py init --repo <工作区
|
|
|
26
26
|
|
|
27
27
|
## 硬规则
|
|
28
28
|
|
|
29
|
-
- **不诊断、不排查**:不要 `ls ~/.taskledger`、不要 `cat identity.json`、不要读 skill 源码(`env.
|
|
29
|
+
- **不诊断、不排查**:不要 `ls ~/.taskledger`、不要 `cat identity.json`、不要读 skill 源码(`internal/env/env.go` / `internal/actions/init_identity.go` 等)或台账去查「为什么没身份」。缺就是未初始化,忽略上下文里已做的排查动作,直接按上面流程走。
|
|
30
30
|
- **不得**替用户执行 `init --claim` / `init --new` / 任何带 `--yes` 的写法——脚本也会拒绝。
|
|
31
31
|
- **不得**基于「上一轮确认过 / 同名 / 记得 uuid」自行重建身份:身份文件缺失就是未初始化,必须让用户重新亲自选。重建会换新 uuid、断开旧任务关联。
|
|
32
32
|
- 不读 `ledger/*.json`、worklog、identity 文件去「自己看」。
|
|
@@ -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` 一次性升级,脚本不认旧列数。
|
|
@@ -117,7 +117,7 @@ notices(只提示):历史解析警告、历史有结案行但任务还在
|
|
|
117
117
|
- **写前**:先 guard 三查 —— scope 内非 worklog 台账文件有未提交改动 / 有未推送的台账提交 / 有未入库的同名文件,命中即中止;再定向取最新:自举 scope(`owners.json` + `pool.json` + 我的分片)加动作 scope(`new` 加目标文件与当月 worklog 目录;`claim` / `set` 还加索引里其他人的全部分片;`close` 加当月 `history/`;任务没有 `worklog` 字段时退整个 `worklog/`)。干净就刷成远端最新,本地有未提交手写改动就 stash → 快进 → 叠回,冲突即中止。`worklog/` 目录只刷新本地已存在的文件(上游新增的不会自动补下来)。
|
|
118
118
|
- **写后**:只 `git add` 台账路径,然后 `commit --no-verify` —— 提交失败退 1;push 失败只提示、退 0,被拒时自动 `rebase --autostash` 合并一次:不同任务自动合并,同一任务两边都改留冲突标记让人定;本地有非台账提交时不 rebase。
|
|
119
119
|
- **回滚**:命令失败时,写前刷下来的内容按字节还原、本次新建的文件删掉;业务写的自补偿只有两处 —— `new`(删任务行 + 删文档)、`close`(三个文件按字节还原)。
|
|
120
|
-
- **`--no-sync` / `TASK_LEDGER_NO_SYNC=1`**:跳过 guard、取最新、提交与推送;自举照跑;有写入时会提示「只写了本地文件、未签入 git」。注意它挂在**顶层**:`progress
|
|
120
|
+
- **`--no-sync` / `TASK_LEDGER_NO_SYNC=1`**:跳过 guard、取最新、提交与推送;自举照跑;有写入时会提示「只写了本地文件、未签入 git」。注意它挂在**顶层**:`progress --no-sync <子命令> …`,放子命令后面是用法错。
|
|
121
121
|
- **环境**:git ≥ 2.27(自动合并依赖 `rebase --autostash`)。不是 git 仓库 ⇒ 同步整体跳过、台账照写,并提示「只写了本地文件」;连 git 二进制都没装也照样写(身份只读本机 identity 文件,与 git 无关)。
|
|
122
122
|
|
|
123
123
|
## 硬约束
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# 台账命令入口:只按平台选对二进制再 exec,逻辑全在编译产物里。
|
|
3
|
+
# 旧写法(Python 版已挪到 legacy-python/ 作对照):python3 legacy-python/progress.py <子命令> --repo <工作区根>
|
|
4
|
+
set -eu
|
|
5
|
+
|
|
6
|
+
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
7
|
+
|
|
8
|
+
case "$(uname -s)" in
|
|
9
|
+
Linux) os=linux ;;
|
|
10
|
+
Darwin) os=darwin ;;
|
|
11
|
+
MINGW* | MSYS* | CYGWIN*) os=windows ;;
|
|
12
|
+
*)
|
|
13
|
+
echo "错误:这个平台($(uname -s))还没有配套的台账二进制。" >&2
|
|
14
|
+
echo " 处理:在装有 Go 的机器上跑 packages/skill/go 的构建脚本,产出 scripts/bin/ledger-<os>-<arch>。" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
case "$(uname -m)" in
|
|
20
|
+
x86_64 | amd64) arch=amd64 ;;
|
|
21
|
+
arm64 | aarch64) arch=arm64 ;;
|
|
22
|
+
*)
|
|
23
|
+
echo "错误:不认识的 CPU 架构($(uname -m)),没有配套的台账二进制。" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
bin="$dir/bin/ledger-$os-$arch"
|
|
29
|
+
if [ "$os" = "windows" ]; then
|
|
30
|
+
bin="$bin.exe"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [ ! -x "$bin" ]; then
|
|
34
|
+
# 打包 / 拷贝常常丢可执行位:自己救一次,救不回来再报错
|
|
35
|
+
chmod +x "$bin" 2>/dev/null || true
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [ ! -x "$bin" ]; then
|
|
39
|
+
echo "错误:找不到可执行的 $bin" >&2
|
|
40
|
+
echo " 处理:在装有 Go 的机器上跑 packages/skill/go 的构建脚本重新产出产物。" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
exec "$bin" "$@"
|
|
File without changes
|
|
File without changes
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
"""事务的唯一入口,20 行:把这次要碰的文件按固定顺序一次锁住。
|
|
2
|
-
|
|
3
|
-
只加锁,不管写盘、不碰 git、不打印。
|
|
4
|
-
|
|
5
|
-
三条硬约束:
|
|
6
|
-
1. 锁集**只许宽不许窄**;锁集以外的文件一个字节都不许写。
|
|
7
|
-
2. **所有判定依据必须在锁内重读** —— 锁外读到的快照只能用来决定"锁多宽"。
|
|
8
|
-
3. **任何失败补偿都不许再开一个 `atom`**(文件锁不可重入),只能在同一事务内做完。
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import contextlib
|
|
14
|
-
from collections.abc import Iterator
|
|
15
|
-
from dataclasses import dataclass
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import Any
|
|
18
|
-
|
|
19
|
-
from ..core.identity import Identity, normalize
|
|
20
|
-
from ..core.paths import (
|
|
21
|
-
HISTORY_DIR,
|
|
22
|
-
OWNERS_REL,
|
|
23
|
-
POOL_REL,
|
|
24
|
-
WORKLOG_DIR,
|
|
25
|
-
history_path_for,
|
|
26
|
-
shard_rel,
|
|
27
|
-
worklog_dir_for,
|
|
28
|
-
worklog_rel,
|
|
29
|
-
)
|
|
30
|
-
from ..store.locks import ledger_lock
|
|
31
|
-
from ..store.owners_index import OwnersIndex
|
|
32
|
-
from ..store.task_store import TaskStore
|
|
33
|
-
|
|
34
|
-
ACTIONS = ("bootstrap", "new", "claim", "set", "log", "close", "tags")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass(frozen=True)
|
|
38
|
-
class Scope:
|
|
39
|
-
"""这次要碰哪些文件。`files` 加锁(也逐个刷),`dirs` 只刷盘(展开成已存在的文件)。"""
|
|
40
|
-
|
|
41
|
-
files: tuple[Path, ...] = ()
|
|
42
|
-
dirs: tuple[Path, ...] = ()
|
|
43
|
-
|
|
44
|
-
def __or__(self, other: Scope) -> Scope:
|
|
45
|
-
return Scope(
|
|
46
|
-
files=_merge(self.files, other.files),
|
|
47
|
-
dirs=_merge(self.dirs, other.dirs),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _merge(a: tuple[Path, ...], b: tuple[Path, ...]) -> tuple[Path, ...]:
|
|
52
|
-
out: list[Path] = []
|
|
53
|
-
for item in (*a, *b):
|
|
54
|
-
if item not in out:
|
|
55
|
-
out.append(item)
|
|
56
|
-
return tuple(out)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _pool(repo: Path) -> Path:
|
|
60
|
-
return POOL_REL
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _shard(repo: Path, me: Identity) -> Path:
|
|
64
|
-
return shard_rel(me.uuid)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _other_shards(repo: Path, me: Identity) -> tuple[Path, ...]:
|
|
68
|
-
"""索引里除我之外的分片(宁宽:认领的前置要查、交接要写对方那份)。"""
|
|
69
|
-
index = OwnersIndex(repo)
|
|
70
|
-
me_key = normalize(me.uuid)
|
|
71
|
-
out: list[Path] = []
|
|
72
|
-
for key, record in index.all().items():
|
|
73
|
-
if key == me_key:
|
|
74
|
-
continue
|
|
75
|
-
name = (record or {}).get("file")
|
|
76
|
-
if name:
|
|
77
|
-
out.append(Path("owners") / str(name))
|
|
78
|
-
return tuple(out)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _worklog_of(
|
|
82
|
-
repo: Path, params: dict[str, Any]
|
|
83
|
-
) -> tuple[tuple[Path, ...], tuple[Path, ...]]:
|
|
84
|
-
"""这条任务的过程文档:查得到给文件,查不到退回整个 `worklog/` 目录(宁宽)。"""
|
|
85
|
-
tid = params.get("tid")
|
|
86
|
-
if not tid:
|
|
87
|
-
return (), (WORKLOG_DIR,)
|
|
88
|
-
pool = TaskStore(repo, POOL_REL)
|
|
89
|
-
task = pool.get(str(tid))
|
|
90
|
-
if task is None:
|
|
91
|
-
index = OwnersIndex(repo)
|
|
92
|
-
for path in index.shards().values():
|
|
93
|
-
rel = Path(path).relative_to(repo / "ledger")
|
|
94
|
-
task = TaskStore(repo, rel).get(str(tid))
|
|
95
|
-
if task is not None:
|
|
96
|
-
break
|
|
97
|
-
worklog = (task or {}).get("worklog")
|
|
98
|
-
if worklog:
|
|
99
|
-
return (Path(worklog),), ()
|
|
100
|
-
return (), (WORKLOG_DIR,)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def lock_scope(repo: Path, me: Identity, action: str, params: dict[str, Any]) -> Scope:
|
|
104
|
-
"""算出这次可能碰哪些文件(**在锁外跑**,宁可算宽)。七个分支与 §四 的表逐行对应。"""
|
|
105
|
-
if action not in ACTIONS:
|
|
106
|
-
raise ValueError(f"未知的 lock_scope action:{action!r}")
|
|
107
|
-
mine = _shard(repo, me)
|
|
108
|
-
pool = _pool(repo)
|
|
109
|
-
index = OWNERS_REL
|
|
110
|
-
others = _other_shards(repo, me)
|
|
111
|
-
|
|
112
|
-
if action == "bootstrap":
|
|
113
|
-
return Scope(files=(index, pool, mine))
|
|
114
|
-
|
|
115
|
-
if action == "new":
|
|
116
|
-
mine_flag = bool(params.get("mine"))
|
|
117
|
-
target = mine if mine_flag else pool
|
|
118
|
-
created = params.get("created") or ""
|
|
119
|
-
tid = params.get("tid")
|
|
120
|
-
# 本次要新建那篇文档:路径算得出来(锁集允许含不存在的路径),不放进去写集就超出锁集
|
|
121
|
-
doc = (
|
|
122
|
-
(Path(worklog_rel(str(tid), created)),) if tid and len(created) >= 7 else ()
|
|
123
|
-
)
|
|
124
|
-
month_dir = worklog_dir_for(created) if len(created) >= 7 else WORKLOG_DIR
|
|
125
|
-
return Scope(files=(target, *doc), dirs=(month_dir,))
|
|
126
|
-
|
|
127
|
-
if action == "claim":
|
|
128
|
-
wfiles, wdirs = _worklog_of(repo, params)
|
|
129
|
-
return Scope(files=(pool, mine, *others, *wfiles), dirs=wdirs)
|
|
130
|
-
|
|
131
|
-
if action == "set":
|
|
132
|
-
wfiles, wdirs = _worklog_of(repo, params)
|
|
133
|
-
# 分支取并集:release 写池、handoff 写对方分片、指向自己且未认领先认领(写我的分片);已在名下则空转不写盘
|
|
134
|
-
return Scope(files=(mine, pool, *others, *wfiles), dirs=wdirs)
|
|
135
|
-
|
|
136
|
-
if action == "log":
|
|
137
|
-
wfiles, wdirs = _worklog_of(repo, params)
|
|
138
|
-
return Scope(files=(mine, *wfiles), dirs=wdirs)
|
|
139
|
-
|
|
140
|
-
if action == "close":
|
|
141
|
-
wfiles, wdirs = _worklog_of(repo, params)
|
|
142
|
-
finished = params.get("finished") or ""
|
|
143
|
-
history = history_path_for(finished) if len(finished) >= 7 else HISTORY_DIR
|
|
144
|
-
return Scope(files=(mine, history, *wfiles), dirs=wdirs)
|
|
145
|
-
|
|
146
|
-
# 六个分支已覆盖 ACTIONS 全集(入口校验在前),走到这里说明表与分支不同步
|
|
147
|
-
raise ValueError(f"lock_scope 没有处理 {action!r}")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
@contextlib.contextmanager
|
|
151
|
-
def atom(repo: Path, *rels: Path) -> Iterator[None]:
|
|
152
|
-
"""按路径排序后依次加锁,退出即完成。不建记账本、不写盘、不提交、不打印。"""
|
|
153
|
-
with ledger_lock(repo, *rels):
|
|
154
|
-
yield
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"""开工前的自举:台账文件不存在就建,没登记过就把自己登记进去、建自己的分片。
|
|
2
|
-
|
|
3
|
-
这三份由**外壳**单独一个 `atom` 包住,它自己不开事务、不拿锁。
|
|
4
|
-
不参与「提交说明 / where / 打印」。
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
from ..core.identity import Identity
|
|
12
|
-
from ..core.paths import LEDGER_DIR, OWNERS_DIR, POOL_REL, shard_rel, to_repo_rel
|
|
13
|
-
from ..core.roots import as_root
|
|
14
|
-
from ..store.atomic import ensure_dir
|
|
15
|
-
from ..store.owners_index import OwnersIndex
|
|
16
|
-
from ..store.task_store import TaskStore
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def bootstrap(repo: Path, me: Identity) -> Identity:
|
|
20
|
-
"""写序:`owners/` 目录 → 池 → **我的分片 → 索引(登记自己)**。已登记 ⇒ 零写。
|
|
21
|
-
|
|
22
|
-
分片必须排在登记之**前**:反过来的话,硬崩(SIGKILL / 断电)正好落在两步之间,
|
|
23
|
-
会留下「索引里有我、磁盘上没有我的分片」——此后**所有人**的 list / show 都在
|
|
24
|
-
`assert_present` 处报「登记的任务文件找不到了」,而 `ensure` 看到记录已存在就零写,
|
|
25
|
-
这个坏状态永远不自愈。按现在的顺序崩,最多留一份孤儿分片(`validate` 会报出来),
|
|
26
|
-
下一条写命令 bootstrap 时会把它登记回去。
|
|
27
|
-
"""
|
|
28
|
-
repo = as_root(
|
|
29
|
-
repo, source="工作区根"
|
|
30
|
-
) # 门 3:写前最后确认,绝不在 ledger/ 里再套一层
|
|
31
|
-
ensure_dir(repo / LEDGER_DIR) # 曾是 to_repo_rel(LEDGER_DIR) ⇒ 会拼出 ledger/ledger
|
|
32
|
-
ensure_dir(repo / to_repo_rel(OWNERS_DIR))
|
|
33
|
-
TaskStore(repo, POOL_REL).ensure_file()
|
|
34
|
-
TaskStore(repo, shard_rel(me.uuid), identity=me).ensure_file()
|
|
35
|
-
index = OwnersIndex(repo)
|
|
36
|
-
_, _ = index.ensure(me) # 登记自己(已登记则零写)
|
|
37
|
-
return me
|