task-ledger 1.3.2 → 1.3.4
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 +2 -2
- package/SKILL.md +6 -6
- package/package.json +1 -1
- package/references/troubleshooting.md +4 -1
- package/scripts/ledgerkit/actions/bootstrap.py +5 -1
- package/scripts/ledgerkit/actions/validate.py +13 -1
- package/scripts/ledgerkit/core/roots.py +74 -0
- package/scripts/ledgerkit/shell/cli.py +12 -12
- package/scripts/ledgerkit/shell/render.py +6 -0
- package/scripts/ledgerkit/shell/runner.py +8 -1
package/README.md
CHANGED
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
直接跑:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
python3 <SKILL.md 所在目录>/scripts/progress.py <子命令> --repo
|
|
34
|
+
python3 <SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <工作区根>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
子命令:`new` `claim` `set` `log` `close` `list` `history` `show` `
|
|
37
|
+
子命令:`new` `claim` `set` `log` `close` `list` `history` `show` `validate`。命令参考(参数 / 一句话 / 举例)、任务生命周期、纪律(数据完整性 / 人类授权 / 故障处置)全在 `SKILL.md`;参数细节看 `<子命令> --help`(不要凭记忆编)。
|
|
38
38
|
|
|
39
39
|
## 深入参考(references/,按需读)
|
|
40
40
|
|
package/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.4
|
|
5
5
|
allowed-tools: Read,Edit,Bash,Grep
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -131,10 +131,10 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
131
131
|
## 怎么调用
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo
|
|
134
|
+
python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <工作区根>
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
- 定位项目:`--repo
|
|
137
|
+
- 定位项目:`--repo <工作区根>`(**直接含有 `ledger/` 的那个目录**)给了就校验后用;不给就从当前目录逐级向上找。`ledger/` 本身、以及它下面的任何子目录都不是根,指过去会被直接拒绝(判据只看位置,不看目录里有哪些文件 —— 项目根自带 `pool.json`、`history/`、`worklog/` 都不影响)。
|
|
138
138
|
- `--no-sync`(放在子命令**前**):用户明确要求「先别同步」时才用;之后自己 `git add ledger && commit && push`。
|
|
139
139
|
- `--json` 只对只读命令(`list` / `show` / `history` / `validate`)有意义;写命令不接收 `--json`,传了是用法错。`--json` 里的时间字段是 UTC,人话输出是本地时间。
|
|
140
140
|
- 命令回执只用来确认生效;任务状态以 `show` / `list` 为准。
|
|
@@ -145,7 +145,7 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当
|
|
|
145
145
|
|
|
146
146
|
1. **建(只建,不认领)—— 默认**:「新建一个任务」「新建个任务,我们来干 X」「新建个任务,我需要处理 Y」→ `new --title "…"`。落公共池、待认领。
|
|
147
147
|
2. **建并认领 —— 仅限明确说法**:用户明确说「新建一个任务**并开始做 / 并执行**」这类带着「现在就开始」的话 → `new --title "…" --self`。直接归自己名下、「进行中」。**除这种明确说法,其他一律按第 1 条只建** —— 别替用户决定他要开始做;拿不定,问一句。
|
|
148
|
-
3. **找已有的**:用户说「我们来做 X」→ 先 `list --mine` 找自己名下的,在做的直接接着做;没有 → `list --pool` 里找,找到先问用户:「X 还没认领,认领它吗?」→ 用户点头再 `claim`;哪儿都没有 →
|
|
148
|
+
3. **找已有的**:用户说「我们来做 X」→ 先 `list --mine` 找自己名下的,在做的直接接着做;没有 → `list --pool` 里找,找到先问用户:「X 还没认领,认领它吗?」→ 用户点头再 `claim`;哪儿都没有 → **不擅自建**:问「要为 X 建个任务吗?」,同意才按第 1/2 条建,不同意就只干活、不登记。
|
|
149
149
|
4. **认领**:`claim --id <任务号>` —— 认到自己名下,状态即「进行中」。
|
|
150
150
|
5. **推进**:改 worklog 前五节 → 按「过程文档」节的收工三步记 `log`。卡住也 `log`(写清卡在哪,状态不动);「先放一放」同理 —— 是暂停,不是释放。
|
|
151
151
|
6. **交付待验收**:任务留「进行中」,「待办」写「待用户验收」,「下一步」写清验收看什么;照收工三步记一条 `log --note "已交付待验收:…"`。
|
|
@@ -193,11 +193,11 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当
|
|
|
193
193
|
|
|
194
194
|
### 人类授权(跑之前问不问)
|
|
195
195
|
|
|
196
|
-
项目没有 `ledger/` ⇒ 先问「要不要建个台账记这活?」,不许自建。用户同意 ⇒ 直接跑第一条写命令,且**必须带 `--repo
|
|
196
|
+
项目没有 `ledger/` ⇒ 先问「要不要建个台账记这活?」,不许自建。用户同意 ⇒ 直接跑第一条写命令,且**必须带 `--repo <工作区根>`**(根是直接含有 `ledger/` 的那个目录,`ledger/` 本身不是根、指到它会被拒绝):不带时脚本只从当前目录逐级往上找第一个含 `ledger/` 的目录,一个都没有就退 1 报「找不到台账」,绝不就地建;台账由这条写命令顺带建出来,没有 `init` 命令。之后可以不带 `--repo`,但查找是**逐级往上**的 —— 在本项目的子目录里跑可能命中上层项目的台账,拿不准就一直带 `--repo <工作区根>`。
|
|
197
197
|
|
|
198
198
|
| 命令 | 档位 | 要求 |
|
|
199
199
|
|---|---|---|
|
|
200
|
-
| `new` / `claim` / `set` | 动手类 |
|
|
200
|
+
| `new` / `claim` / `set` | 动手类 | 执行前复述完整参数并取得确认;**`new` 仅当用户原话点名要建且标题可直取原话时免复述**,标题需另拟或拿不准仍复述一次(`claim`/`set` 一律复述) |
|
|
201
201
|
| `log` | 免确认 | 本任务的推进 / 卡点 / 交付记录直接跑,跑完一句话交代「已记执行记录:…」;要替用户下结论,先问(任务不在自己名下会直接被拒) |
|
|
202
202
|
| `close` | 独立授权 | 仅认明确说「结束掉 / close 掉」(完整判定见生命周期第 8 条);说得含糊必须反问「是要结束这个任务吗?」;**agent 自判「干完了」不算** |
|
|
203
203
|
| `list` / `show` / `history` / `validate` | 只读 | 直接跑 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
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",
|
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
- **「本地有未推送的台账提交:… 处理:先 git push;或者加 --no-sync 只写本地(之后要记得推)」**:没有「自动补推再写」这一步,就是让你先推。
|
|
55
55
|
- **「有还没入库的同名台账文件(提交会被它们挡住)」**:同名文件还没 `git add`,先入库。
|
|
56
56
|
- **「仓库处于中间态(rebase-merge / rebase-apply / MERGE_HEAD):先跑 git status 看提示,完成或中止它(git rebase --abort / git merge --abort)再跑本命令」**:上次 rebase / merge 没收尾。
|
|
57
|
-
- **「找不到台账:从当前目录往上都没有 ledger/ 目录」**:`--repo`
|
|
57
|
+
- **「找不到台账:从当前目录往上都没有 ledger/ 目录」**:`--repo` 指对**工作区根**(直接含有 `ledger/` 的那个目录);第一条写命令会自动把 `ledger/` 建出来,不用先初始化。
|
|
58
|
+
- **「--repo 指到了台账目录本身」**:你把 `ledger/` 当成了根;它的上一级才是工作区根,按报错里给的路径改。
|
|
59
|
+
- **「--repo 指到了台账目录里面」**:你指到了 `ledger/` 下面的某个子目录(如 `ledger/worklog`);工作区根是那个 `ledger/` 的上一级,按报错里给的路径改。
|
|
60
|
+
- **「--repo 指向的不是一个目录」**:路径写错或不存在,要指到工作区根。
|
|
58
61
|
|
|
59
62
|
### 并发 / 锁(退 1 或提示)
|
|
60
63
|
|
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
from ..core.identity import Identity
|
|
12
12
|
from ..core.paths import LEDGER_DIR, OWNERS_DIR, POOL_REL, shard_rel, to_repo_rel
|
|
13
|
+
from ..core.roots import as_root
|
|
13
14
|
from ..store.atomic import ensure_dir
|
|
14
15
|
from ..store.owners_index import OwnersIndex
|
|
15
16
|
from ..store.task_store import TaskStore
|
|
@@ -17,7 +18,10 @@ from ..store.task_store import TaskStore
|
|
|
17
18
|
|
|
18
19
|
def bootstrap(repo: Path, me: Identity) -> Identity:
|
|
19
20
|
"""写序:`owners/` 目录 → 池 → 索引(登记自己)→ 我的分片。已登记 ⇒ 零写。"""
|
|
20
|
-
|
|
21
|
+
repo = as_root(
|
|
22
|
+
repo, source="工作区根"
|
|
23
|
+
) # 门 3:写前最后确认,绝不在 ledger/ 里再套一层
|
|
24
|
+
ensure_dir(repo / LEDGER_DIR) # 曾是 to_repo_rel(LEDGER_DIR) ⇒ 会拼出 ledger/ledger
|
|
21
25
|
ensure_dir(repo / to_repo_rel(OWNERS_DIR))
|
|
22
26
|
TaskStore(repo, POOL_REL).ensure_file()
|
|
23
27
|
index = OwnersIndex(repo)
|
|
@@ -14,7 +14,13 @@ from pathlib import Path
|
|
|
14
14
|
from ..core import history_md
|
|
15
15
|
from ..core.identity import Identity
|
|
16
16
|
from ..core.labels import STATUSES
|
|
17
|
-
from ..core.paths import
|
|
17
|
+
from ..core.paths import (
|
|
18
|
+
LEDGER_DIR,
|
|
19
|
+
LEGACY_HISTORY_REL,
|
|
20
|
+
OWNERS_REL,
|
|
21
|
+
POOL_REL,
|
|
22
|
+
to_repo_rel,
|
|
23
|
+
)
|
|
18
24
|
from ..core.time import now
|
|
19
25
|
from ..core.worklog_md import (
|
|
20
26
|
duplicated_sections,
|
|
@@ -199,6 +205,12 @@ def validate(repo: Path, me: Identity, deep: bool = False) -> ValidationReport:
|
|
|
199
205
|
notices.append(f"存量平铺文件 {rel(repo, legacy)} 只读兼容,不会重写。")
|
|
200
206
|
if not (repo / to_repo_rel(OWNERS_REL)).exists():
|
|
201
207
|
notices.append("还没有 owners.json(索引):跑一条写命令会自动建。")
|
|
208
|
+
stray = repo / LEDGER_DIR / LEDGER_DIR
|
|
209
|
+
if stray.is_dir():
|
|
210
|
+
problems.append(
|
|
211
|
+
f"{rel(repo, stray)} 是错位的台账目录(根被当成 ledger/ 时写出来的幻影);"
|
|
212
|
+
"确认哪份是真台账后删掉多余的,命令一律带 --repo <工作区根>。"
|
|
213
|
+
)
|
|
202
214
|
|
|
203
215
|
return ValidationReport(
|
|
204
216
|
problems=tuple(problems),
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""工作区根的判定:哪个目录才是「直接含有 `ledger/` 的那个根」。
|
|
2
|
+
|
|
3
|
+
全仓唯一实现 —— `shell/` 与 `actions/` 都调这里,不各写一份(否则口径会漂)。
|
|
4
|
+
判据是**结构**(含不含 `ledger/`、自己在不在 `ledger/` 里),不看目录里有哪些文件:
|
|
5
|
+
项目根自带 `pool.json`、`history/` + `worklog/`、`owners.json` + `owners/` 这类同名文件
|
|
6
|
+
或目录都很常见,按「里面有什么」判会把正常项目根误当成台账目录(2026-10-25 改)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from ..errors import Fail
|
|
14
|
+
from .paths import LEDGER_DIR
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _under_ledger(p: Path) -> bool:
|
|
18
|
+
"""`p` 自己叫 `ledger`,或它落在某个 `ledger/` 目录里面。"""
|
|
19
|
+
if p.name == LEDGER_DIR.name:
|
|
20
|
+
return True
|
|
21
|
+
return any(anc.name == LEDGER_DIR.name for anc in p.parents)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def as_root(repo: str | Path, *, source: str = "--repo") -> Path:
|
|
25
|
+
"""校验「工作区根」,只看结构:
|
|
26
|
+
|
|
27
|
+
- 直接含 `ledger/` ⇒ 就是根。哪怕项目根自己叫 `ledger`、哪怕它自己也有 `pool.json`;
|
|
28
|
+
- 不含 `ledger/`、但自己叫 `ledger` 或落在某个 `ledger/` 下 ⇒ 是台账目录 / 台账里面,拒;
|
|
29
|
+
- 其余(含全新空目录)⇒ 收,第一条写命令会在它下面建 `ledger/`。
|
|
30
|
+
"""
|
|
31
|
+
p = Path(repo).expanduser()
|
|
32
|
+
if not p.is_dir():
|
|
33
|
+
raise Fail(
|
|
34
|
+
f"{source} 指向的不是一个目录:{p}\n"
|
|
35
|
+
f" 处理:指向工作区根(直接含有 ledger/ 的那个目录,不是 ledger/ 本身)。"
|
|
36
|
+
)
|
|
37
|
+
if (p / LEDGER_DIR).is_dir():
|
|
38
|
+
return p.resolve()
|
|
39
|
+
if _under_ledger(p):
|
|
40
|
+
anc = next(a for a in (p, *p.parents) if a.name == LEDGER_DIR.name)
|
|
41
|
+
root = anc.parent
|
|
42
|
+
if anc is p:
|
|
43
|
+
what = f"指到了台账目录本身:{p}"
|
|
44
|
+
why = f"它的上一级 {root} 才是工作区根 —— 在它下面跑会再建出 {p / LEDGER_DIR}。"
|
|
45
|
+
else:
|
|
46
|
+
what = f"指到了台账目录里面:{p}"
|
|
47
|
+
why = f"它已经在 {anc} 这个台账目录底下 —— 工作区根是 {anc.parent}。"
|
|
48
|
+
raise Fail(
|
|
49
|
+
f"{source} {what}\n"
|
|
50
|
+
f" {why}\n"
|
|
51
|
+
f" 处理:把 {source} 指到 {root}\n"
|
|
52
|
+
f" 例:progress.py list --repo {root}"
|
|
53
|
+
)
|
|
54
|
+
return p.resolve()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def root_of(start: str | Path) -> Path | None:
|
|
58
|
+
"""从 `start` 往上定位工作区根;找不到返回 None。
|
|
59
|
+
|
|
60
|
+
① 自己在台账目录里(名字叫 `ledger`、且里面没有再套一层 `ledger/`)⇒ 根是它的上一级。
|
|
61
|
+
按位置判,不看里面有什么:项目根自带 `history/` + `worklog/` 不会被误当成台账目录。
|
|
62
|
+
② 兜底:从**自己**开始往上,取最近的、直接含 `ledger/` 的目录。`start` 必须优先于
|
|
63
|
+
祖先,否则工作区根恰好叫 `ledger` 时会被父目录抢走(2026-10-25 改)。
|
|
64
|
+
"""
|
|
65
|
+
here = Path(start).resolve()
|
|
66
|
+
for anc in (here, *here.parents):
|
|
67
|
+
if anc.name == LEDGER_DIR.name and not (anc / LEDGER_DIR).is_dir():
|
|
68
|
+
root = anc.parent
|
|
69
|
+
if (root / LEDGER_DIR).is_dir():
|
|
70
|
+
return root
|
|
71
|
+
for cand in (here, *here.parents):
|
|
72
|
+
if (cand / LEDGER_DIR).is_dir():
|
|
73
|
+
return cand
|
|
74
|
+
return None
|
|
@@ -12,7 +12,7 @@ import sys
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from ..actions.query import HistoryQuery, ListQuery
|
|
15
|
-
from ..core.
|
|
15
|
+
from ..core.roots import as_root, root_of
|
|
16
16
|
from ..core.time import to_utc
|
|
17
17
|
from ..errors import BUSINESS, USAGE, Fail
|
|
18
18
|
from . import render, runner
|
|
@@ -27,7 +27,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
27
27
|
prog="progress.py", description="任务台账(本地文件 + git 同步)"
|
|
28
28
|
)
|
|
29
29
|
parser.add_argument(
|
|
30
|
-
"--repo", help="
|
|
30
|
+
"--repo", help="工作区根(直接含有 ledger/ 的那个目录;默认按当前目录向上找)"
|
|
31
31
|
)
|
|
32
32
|
parser.add_argument(
|
|
33
33
|
"--no-sync", action="store_true", help="只写本地,不取最新 / 不提交 / 不推送"
|
|
@@ -169,22 +169,22 @@ def _split_tags(value: str | None) -> tuple[str, ...]:
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
def resolve_repo(arg: str | None, start: Path | None = None) -> Path:
|
|
172
|
-
"""`--repo`
|
|
172
|
+
"""`--repo` 给了就校验后用;没给就从 `start`(默认 cwd)往上找。
|
|
173
173
|
|
|
174
174
|
找不到 ⇒ Fail:绝不在当前目录自作主张建台账(2026-10-22 设计定稿)。
|
|
175
175
|
建台账是第一条写命令的事,不需要、也没有 `init` 子命令。
|
|
176
176
|
"""
|
|
177
177
|
if arg:
|
|
178
|
-
return
|
|
178
|
+
return as_root(arg, source="--repo")
|
|
179
179
|
here = (start or Path.cwd()).resolve()
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
root = root_of(here)
|
|
181
|
+
if root is None:
|
|
182
|
+
raise Fail(
|
|
183
|
+
"找不到台账:从当前目录往上都没有 ledger/ 目录。\n"
|
|
184
|
+
" 处理:用 --repo <工作区根> 指定(根是直接含 ledger/ 的那个目录,不是 ledger/ 本身);\n"
|
|
185
|
+
" 或先 cd 到项目根再跑。第一条写命令会自动把 ledger/ 建出来,不用先初始化。"
|
|
186
|
+
)
|
|
187
|
+
return root
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def _repo_of(args: argparse.Namespace) -> Path:
|
|
@@ -41,6 +41,12 @@ def fail_message(exc: BaseException, *, title: str = "错误") -> None:
|
|
|
41
41
|
_err(f"{title}:{exc}")
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def repo_banner(repo: Any, *, machine: bool) -> None:
|
|
45
|
+
"""只读命令开头自报「读的是哪个台账」(输出是 JSON 时不打,否则污染机器可读结果)。"""
|
|
46
|
+
if not machine:
|
|
47
|
+
_out(f"台账:{repo}")
|
|
48
|
+
|
|
49
|
+
|
|
44
50
|
def _json(data: Any) -> None:
|
|
45
51
|
sys.stdout.write(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
|
|
46
52
|
|
|
@@ -91,6 +91,11 @@ def _run(command: str, args: argparse.Namespace, log: dirty.WriteLog) -> int:
|
|
|
91
91
|
# 只读:不建 GitRepo、不进事务、不提交
|
|
92
92
|
if spec.readonly:
|
|
93
93
|
value = spec.func(repo, me, **params)
|
|
94
|
+
# 先自报读的是哪个台账:`--repo` 指错目录时输出会变成一片「没有任务」,不带这一行
|
|
95
|
+
# 就完全看不出读错了地方。`--count-only` 也出 JSON,同样不打(2026-10-25 改)
|
|
96
|
+
render.repo_banner(
|
|
97
|
+
repo, machine=as_json or bool(getattr(args, "count_only", False))
|
|
98
|
+
)
|
|
94
99
|
_render(spec, value, args, as_json=as_json)
|
|
95
100
|
if getattr(value, "problems", ()):
|
|
96
101
|
return BUSINESS # validate 有 problems ⇒ 退出码 1(§六)
|
|
@@ -219,7 +224,9 @@ def _repo_of(args: argparse.Namespace) -> Path:
|
|
|
219
224
|
"""
|
|
220
225
|
repo = getattr(args, "repo", None)
|
|
221
226
|
if repo:
|
|
222
|
-
|
|
227
|
+
from ..core.roots import as_root # 门 2 兜底:绕过 cli 直调 runner 也不收错位根
|
|
228
|
+
|
|
229
|
+
return as_root(repo, source="--repo")
|
|
223
230
|
from .cli import resolve_repo # 延迟导入:cli 在模块层 import runner
|
|
224
231
|
|
|
225
232
|
return resolve_repo(None)
|