task-ledger 1.3.4 → 1.3.6
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 +1 -1
- package/SKILL.md +30 -17
- package/package.json +1 -1
- package/references/state-model.md +12 -10
- package/references/troubleshooting.md +19 -7
- package/scripts/ledgerkit/actions/atom.py +2 -2
- package/scripts/ledgerkit/actions/bootstrap.py +9 -2
- package/scripts/ledgerkit/actions/claim.py +7 -6
- package/scripts/ledgerkit/actions/close_task.py +17 -12
- package/scripts/ledgerkit/actions/guard.py +38 -16
- package/scripts/ledgerkit/actions/handoff.py +24 -8
- package/scripts/ledgerkit/actions/init_identity.py +90 -0
- package/scripts/ledgerkit/actions/list_tasks.py +21 -3
- package/scripts/ledgerkit/actions/log_progress.py +5 -2
- package/scripts/ledgerkit/actions/new_task.py +11 -7
- package/scripts/ledgerkit/actions/query.py +4 -2
- package/scripts/ledgerkit/actions/query_history.py +55 -9
- package/scripts/ledgerkit/actions/registry.py +1 -1
- package/scripts/ledgerkit/actions/release.py +25 -4
- package/scripts/ledgerkit/actions/set_task.py +18 -7
- package/scripts/ledgerkit/actions/validate.py +0 -4
- package/scripts/ledgerkit/core/history_md.py +47 -250
- package/scripts/ledgerkit/core/identity.py +34 -7
- package/scripts/ledgerkit/core/labels.py +0 -1
- package/scripts/ledgerkit/core/paths.py +0 -1
- package/scripts/ledgerkit/core/worklog_md.py +23 -3
- package/scripts/ledgerkit/env.py +70 -71
- package/scripts/ledgerkit/shell/cli.py +60 -3
- package/scripts/ledgerkit/shell/gitops.py +14 -1
- package/scripts/ledgerkit/shell/render.py +3 -2
- package/scripts/ledgerkit/shell/runner.py +1 -1
- package/scripts/ledgerkit/store/history_store.py +26 -81
- package/scripts/ledgerkit/store/owners_index.py +16 -3
- package/scripts/ledgerkit/store/task_store.py +2 -2
|
@@ -47,7 +47,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
47
47
|
|
|
48
48
|
setp = sub.add_parser("set", help="释放 / 交接任务")
|
|
49
49
|
setp.add_argument("--id", required=True, dest="tid")
|
|
50
|
-
setp.add_argument("--owner")
|
|
50
|
+
setp.add_argument("--owner", help="交给别人的 uuid(交接)")
|
|
51
|
+
setp.add_argument("--release", action="store_true", help="释放回公共池")
|
|
51
52
|
|
|
52
53
|
logp = sub.add_parser("log", help="记一条进展")
|
|
53
54
|
logp.add_argument("--id", required=True, dest="tid")
|
|
@@ -63,7 +64,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
63
64
|
listp = sub.add_parser("list", help="查在办任务")
|
|
64
65
|
listp.add_argument("--pool", action="store_true")
|
|
65
66
|
listp.add_argument("--mine", action="store_true")
|
|
66
|
-
listp.add_argument(
|
|
67
|
+
listp.add_argument(
|
|
68
|
+
"--owner", help="某个人的 uuid(精确匹配;名字请用 --owner-name)"
|
|
69
|
+
)
|
|
70
|
+
listp.add_argument(
|
|
71
|
+
"--owner-name", dest="owner_name", help="按显示名模糊筛(子串、不分大小写)"
|
|
72
|
+
)
|
|
67
73
|
listp.add_argument("--status")
|
|
68
74
|
listp.add_argument("--category")
|
|
69
75
|
listp.add_argument("--tag")
|
|
@@ -76,7 +82,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
76
82
|
showp.add_argument("--json", action="store_true")
|
|
77
83
|
|
|
78
84
|
hist = sub.add_parser("history", help="查已结案")
|
|
79
|
-
hist.add_argument(
|
|
85
|
+
hist.add_argument(
|
|
86
|
+
"--owner", help="某个人的 uuid(精确匹配;名字请用 --owner-name)"
|
|
87
|
+
)
|
|
88
|
+
hist.add_argument(
|
|
89
|
+
"--owner-name", dest="owner_name", help="按显示名模糊筛(子串、不分大小写)"
|
|
90
|
+
)
|
|
80
91
|
hist.add_argument("--mine", action="store_true")
|
|
81
92
|
hist.add_argument("--category")
|
|
82
93
|
hist.add_argument("--tag")
|
|
@@ -93,6 +104,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
93
104
|
valp.add_argument("--deep", action="store_true")
|
|
94
105
|
valp.add_argument("--json", action="store_true")
|
|
95
106
|
|
|
107
|
+
initp = sub.add_parser("init", help="初始化本机身份(一台机器只一次)")
|
|
108
|
+
initp.add_argument("--claim", help="认领名册里已有的 uuid")
|
|
109
|
+
initp.add_argument("--new", action="store_true", dest="new_id", help="新建身份")
|
|
110
|
+
initp.add_argument("--name", help="新建身份时的显示名字(必填)")
|
|
111
|
+
|
|
96
112
|
# 宿主(插件 / SKILL.md)的调用写法是 `progress.py list --repo <仓库>`:
|
|
97
113
|
# `--repo` 挂在**子命令后面**,所以每个子命令都要认;子命令给的值覆盖顶层那个。
|
|
98
114
|
for parser_ in sub.choices.values():
|
|
@@ -119,6 +135,7 @@ def to_query(args: argparse.Namespace) -> None:
|
|
|
119
135
|
args.q = ListQuery(
|
|
120
136
|
scope=scope,
|
|
121
137
|
owner=getattr(args, "owner", None),
|
|
138
|
+
owner_name=getattr(args, "owner_name", None),
|
|
122
139
|
statuses=tuple(
|
|
123
140
|
s.strip()
|
|
124
141
|
for s in (getattr(args, "status", "") or "").split(",")
|
|
@@ -134,6 +151,7 @@ def to_query(args: argparse.Namespace) -> None:
|
|
|
134
151
|
elif command == "history":
|
|
135
152
|
args.q = HistoryQuery(
|
|
136
153
|
owner=getattr(args, "owner", None),
|
|
154
|
+
owner_name=getattr(args, "owner_name", None),
|
|
137
155
|
mine=bool(getattr(args, "mine", False)),
|
|
138
156
|
category=getattr(args, "category", None),
|
|
139
157
|
tag=getattr(args, "tag", None),
|
|
@@ -206,6 +224,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
206
224
|
to_query(args)
|
|
207
225
|
|
|
208
226
|
command = command_of(args)
|
|
227
|
+
if command == "init":
|
|
228
|
+
return _run_init(args)
|
|
209
229
|
try:
|
|
210
230
|
# 定位只做一次:解析好的工作区根挂回 args,runner 与整命令锁都拿它
|
|
211
231
|
args.repo = str(resolve_repo(getattr(args, "repo", None)))
|
|
@@ -249,6 +269,43 @@ def _lock_dir(repo: Path) -> Path:
|
|
|
249
269
|
return lock_dir
|
|
250
270
|
|
|
251
271
|
|
|
272
|
+
def _run_init(args: argparse.Namespace) -> int:
|
|
273
|
+
"""init 不进 runner、不解析台账、不碰 git:只写机器身份文件。"""
|
|
274
|
+
from ..actions.init_identity import init_identity
|
|
275
|
+
|
|
276
|
+
repo: Path | None = None
|
|
277
|
+
raw = getattr(args, "repo", None)
|
|
278
|
+
try:
|
|
279
|
+
repo = as_root(raw, source="--repo") if raw else root_of(Path.cwd().resolve())
|
|
280
|
+
except Fail:
|
|
281
|
+
repo = None
|
|
282
|
+
try:
|
|
283
|
+
res = init_identity(
|
|
284
|
+
repo,
|
|
285
|
+
claim=getattr(args, "claim", None),
|
|
286
|
+
new=bool(getattr(args, "new_id", False)),
|
|
287
|
+
name=getattr(args, "name", "") or "",
|
|
288
|
+
)
|
|
289
|
+
except Fail as exc:
|
|
290
|
+
render.fail_message(exc)
|
|
291
|
+
return BUSINESS
|
|
292
|
+
if res.get("need_choose"):
|
|
293
|
+
print("这台机器还没初始化。当前仓库里已有的用户:")
|
|
294
|
+
for opt in res.get("options", []):
|
|
295
|
+
print(f" {opt['uuid']} {opt['name']}")
|
|
296
|
+
print("请重跑:init --claim=<uuid> 认领,或 init --new --name=<名字> 新建")
|
|
297
|
+
return 0
|
|
298
|
+
if res.get("already"):
|
|
299
|
+
print(
|
|
300
|
+
f"已经初始化过,未做改动:{res['name'] or res['uuid'][:8]}({res['uuid']})"
|
|
301
|
+
)
|
|
302
|
+
print(f"文件:{res['path']}")
|
|
303
|
+
return 0
|
|
304
|
+
print(f"已初始化:{res['name'] or res['uuid'][:8]}({res['uuid']})")
|
|
305
|
+
print(f"文件:{res['path']}")
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
|
|
252
309
|
def _readonly(command: str) -> bool:
|
|
253
310
|
from ..actions.registry import spec_of
|
|
254
311
|
|
|
@@ -30,7 +30,13 @@ GIT_TIMEOUT = 30
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _git(*args: str, timeout: int | None = GIT_TIMEOUT) -> subprocess.CompletedProcess:
|
|
33
|
-
"""跑一条 git。没装 git 不当崩溃:返回 127 的空结果,由调用方按返回码处理。
|
|
33
|
+
"""跑一条 git。没装 git 不当崩溃:返回 127 的空结果,由调用方按返回码处理。
|
|
34
|
+
|
|
35
|
+
**超时也折成返回码 124**,不许抛出去:`sync_down` 的 fetch 分支、`push` 的重试、
|
|
36
|
+
`_rebase_retry` 全按返回码判「远端不通」,抛异常会被 runner 兜底成「未预期错误」
|
|
37
|
+
(退出码 3),把「拉取失败、按本地最新写」这条既定降级路径整条跳过。触发场景是
|
|
38
|
+
弱网 / 网络黑洞 / 代理挂住 —— 远端不立刻拒绝,fetch 一直挂到超时(2026-10-25 修)。
|
|
39
|
+
"""
|
|
34
40
|
try:
|
|
35
41
|
return subprocess.run(
|
|
36
42
|
list(args), capture_output=True, text=True, timeout=timeout, check=False
|
|
@@ -39,6 +45,13 @@ def _git(*args: str, timeout: int | None = GIT_TIMEOUT) -> subprocess.CompletedP
|
|
|
39
45
|
return subprocess.CompletedProcess(
|
|
40
46
|
list(args), 127, stdout="", stderr="git: command not found"
|
|
41
47
|
)
|
|
48
|
+
except subprocess.TimeoutExpired:
|
|
49
|
+
return subprocess.CompletedProcess(
|
|
50
|
+
list(args),
|
|
51
|
+
124,
|
|
52
|
+
stdout="",
|
|
53
|
+
stderr=f"git: timed out after {timeout}s",
|
|
54
|
+
)
|
|
42
55
|
|
|
43
56
|
|
|
44
57
|
@contextlib.contextmanager
|
|
@@ -101,7 +101,8 @@ def show_view(res: ShowResult, *, as_json: bool) -> None:
|
|
|
101
101
|
task = res.task
|
|
102
102
|
_out(f"{task.get('tid', '')}:{task.get('title', '')}")
|
|
103
103
|
_out(
|
|
104
|
-
f" 状态:{task.get('status', '')}
|
|
104
|
+
f" 状态:{task.get('status', '')} "
|
|
105
|
+
f"负责人:{task.get('owner_name') or task.get('owner') or '(未认领)'}"
|
|
105
106
|
)
|
|
106
107
|
_out(
|
|
107
108
|
f" 创建:{fmt_local(task.get('created'))} "
|
|
@@ -143,7 +144,7 @@ def history_view(res: HistoryResult, q: HistoryQuery | None, *, as_json: bool) -
|
|
|
143
144
|
for row in res.records:
|
|
144
145
|
_out(
|
|
145
146
|
f" {fmt_local(row.get('finished'))} "
|
|
146
|
-
f"{row.get('owner') or '(无人)':<12} {row.get('title', '')}"
|
|
147
|
+
f"{row.get('owner_name') or row.get('owner') or '(无人)':<12} {row.get('title', '')}"
|
|
147
148
|
)
|
|
148
149
|
|
|
149
150
|
|
|
@@ -213,7 +213,7 @@ def _task_snapshot(repo: Path, me) -> dict[str, Any]:
|
|
|
213
213
|
|
|
214
214
|
tasks: dict[str, Any] = {}
|
|
215
215
|
tasks.update(TaskStore(repo, POOL_REL).all())
|
|
216
|
-
tasks.update(TaskStore(repo, shard_rel(me.
|
|
216
|
+
tasks.update(TaskStore(repo, shard_rel(me.uuid)).all())
|
|
217
217
|
return tasks
|
|
218
218
|
|
|
219
219
|
|
|
@@ -9,9 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from ..core import history_md
|
|
12
|
-
from ..core.paths import HISTORY_DIR,
|
|
12
|
+
from ..core.paths import HISTORY_DIR, to_repo_rel
|
|
13
13
|
from .atomic import write_text_atomic
|
|
14
|
-
from .worklog_store import WorklogStore
|
|
15
14
|
|
|
16
15
|
HIT = "hit"
|
|
17
16
|
MISS = "miss"
|
|
@@ -25,16 +24,12 @@ class HistoryStore:
|
|
|
25
24
|
# ---- 读 ----
|
|
26
25
|
|
|
27
26
|
def sources(self) -> list[Path]:
|
|
28
|
-
"""按月的 `history/*.md
|
|
27
|
+
"""按月的 `history/*.md`(按文件名排序)。"""
|
|
29
28
|
base = self.repo / to_repo_rel(HISTORY_DIR)
|
|
30
|
-
|
|
31
|
-
legacy = self.repo / to_repo_rel(LEGACY_HISTORY_REL)
|
|
32
|
-
if legacy.is_file():
|
|
33
|
-
files.append(legacy)
|
|
34
|
-
return files
|
|
29
|
+
return sorted(base.glob("*.md")) if base.is_dir() else []
|
|
35
30
|
|
|
36
31
|
def month_key(self, path: Path) -> str:
|
|
37
|
-
"""按月文件的 `YYYYMM
|
|
32
|
+
"""按月文件的 `YYYYMM`。"""
|
|
38
33
|
stem = Path(path).stem
|
|
39
34
|
return stem if len(stem) == 6 and stem.isdigit() else ""
|
|
40
35
|
|
|
@@ -46,16 +41,6 @@ class HistoryStore:
|
|
|
46
41
|
except OSError as exc:
|
|
47
42
|
raise ValueError(f"读 {path} 失败:{exc}") from exc
|
|
48
43
|
|
|
49
|
-
def _read_worklog(self, path: Path):
|
|
50
|
-
"""升级要回链的 worklog 头部:读文件这件事留在 store,core 只消费返回的字典。"""
|
|
51
|
-
logs = WorklogStore(self.repo)
|
|
52
|
-
|
|
53
|
-
def read(log_name: str) -> dict[str, Any] | None:
|
|
54
|
-
found = logs.by_name(log_name)
|
|
55
|
-
return logs.head_meta(found) if found else None
|
|
56
|
-
|
|
57
|
-
return read
|
|
58
|
-
|
|
59
44
|
# ---- 判重 ----
|
|
60
45
|
|
|
61
46
|
def has_log(
|
|
@@ -79,6 +64,11 @@ class HistoryStore:
|
|
|
79
64
|
if Path(worklog).name == Path(log_name).name:
|
|
80
65
|
return HIT
|
|
81
66
|
continue
|
|
67
|
+
if log_name is None and worklog:
|
|
68
|
+
# 历史行里有链接,但这次判不出对应的是哪篇文档(任务的过程文档丢了 / 被移走):
|
|
69
|
+
# 判不了就明说。拿 miss 冒充会再追加一行 ⇒ 同一条任务两条结案记录。
|
|
70
|
+
undecidable = True
|
|
71
|
+
continue
|
|
82
72
|
if not worklog:
|
|
83
73
|
if created and title:
|
|
84
74
|
if row.get("created") == created and row.get("title") == title:
|
|
@@ -90,11 +80,7 @@ class HistoryStore:
|
|
|
90
80
|
# ---- 算 ----
|
|
91
81
|
|
|
92
82
|
def append_row(self, path: Path, row: dict[str, Any]) -> dict[str, Any]:
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
返回四个键:`text` / `upgraded` / `duplicate` / `fallback_cols`(`None` = 正常 9 列)。
|
|
96
|
-
老表列少又不敢整体升级时,按现有列数追加并让业务层提示,绝不硬写 9 列把表格挤歪。
|
|
97
|
-
"""
|
|
83
|
+
"""算出「追加这一行后的新全文」,**不落盘**。返回 `text` / `duplicate`。"""
|
|
98
84
|
original = self.read(path)
|
|
99
85
|
duplicate = (
|
|
100
86
|
self.has_log(
|
|
@@ -105,67 +91,26 @@ class HistoryStore:
|
|
|
105
91
|
)
|
|
106
92
|
== HIT
|
|
107
93
|
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
read_worklog=self._read_worklog(path),
|
|
122
|
-
fallback_created=row.get("created") or "",
|
|
123
|
-
)
|
|
124
|
-
if upgraded_text is None:
|
|
125
|
-
text = original
|
|
126
|
-
upgraded = False
|
|
127
|
-
fallback_cols = cols
|
|
128
|
-
else:
|
|
129
|
-
text, _ = upgraded_text
|
|
130
|
-
upgraded = True
|
|
131
|
-
fallback_cols = None
|
|
132
|
-
line = (
|
|
133
|
-
history_md.history_line(
|
|
134
|
-
row.get("finished") or "",
|
|
135
|
-
row.get("created"),
|
|
136
|
-
row.get("title") or "",
|
|
137
|
-
row.get("owner"),
|
|
138
|
-
row.get("summary") or "",
|
|
139
|
-
row.get("worklog"),
|
|
140
|
-
row.get("category"),
|
|
141
|
-
row.get("tags"),
|
|
142
|
-
row.get("creator"),
|
|
143
|
-
)
|
|
144
|
-
if fallback_cols is None
|
|
145
|
-
else history_md.history_line_for_cols(
|
|
146
|
-
fallback_cols,
|
|
147
|
-
row.get("finished") or "",
|
|
148
|
-
row.get("created"),
|
|
149
|
-
row.get("title") or "",
|
|
150
|
-
row.get("owner"),
|
|
151
|
-
row.get("summary") or "",
|
|
152
|
-
row.get("worklog"),
|
|
153
|
-
row.get("category"),
|
|
154
|
-
row.get("tags"),
|
|
155
|
-
row.get("creator"),
|
|
156
|
-
)
|
|
94
|
+
text = original if original.strip() else _fresh_table()
|
|
95
|
+
line = history_md.history_line(
|
|
96
|
+
row.get("finished") or "",
|
|
97
|
+
row.get("created"),
|
|
98
|
+
row.get("title") or "",
|
|
99
|
+
row.get("owner"),
|
|
100
|
+
row.get("summary") or "",
|
|
101
|
+
row.get("worklog"),
|
|
102
|
+
category=row.get("category"),
|
|
103
|
+
tags=row.get("tags"),
|
|
104
|
+
creator=row.get("creator"),
|
|
105
|
+
owner_name=row.get("owner_name"),
|
|
106
|
+
creator_name=row.get("creator_name"),
|
|
157
107
|
)
|
|
158
|
-
return {
|
|
159
|
-
"text": _append_line(text, line),
|
|
160
|
-
"upgraded": upgraded,
|
|
161
|
-
"duplicate": duplicate,
|
|
162
|
-
"fallback_cols": fallback_cols,
|
|
163
|
-
}
|
|
108
|
+
return {"text": _append_line(text, line), "duplicate": duplicate}
|
|
164
109
|
|
|
165
110
|
# ---- 写 ----
|
|
166
111
|
|
|
167
112
|
def write(self, path: Path, text: str) -> None:
|
|
168
|
-
"""
|
|
113
|
+
"""历史表唯一的写回口:只用于「结案追加」的结果落盘。"""
|
|
169
114
|
write_text_atomic(path, text)
|
|
170
115
|
|
|
171
116
|
|
|
@@ -174,7 +119,7 @@ def _name_of(worklog: str | None) -> str | None:
|
|
|
174
119
|
|
|
175
120
|
|
|
176
121
|
def _fresh_table() -> str:
|
|
177
|
-
"""
|
|
122
|
+
"""当月第一次结案:按十一列表头起表(表头的真源只有 `history_md` 一处)。"""
|
|
178
123
|
return (
|
|
179
124
|
f"{history_md.HISTORY_TITLE}\n\n"
|
|
180
125
|
f"{history_md.HISTORY_HEADER}\n{history_md.HISTORY_SEP}\n"
|
|
@@ -32,7 +32,7 @@ class OwnersIndex:
|
|
|
32
32
|
|
|
33
33
|
def ensure(self, me: Identity) -> tuple[bool, dict[str, Any]]:
|
|
34
34
|
"""登记 / 补 `joined`。`name` 只在首次登记时写入,之后不回写(名字是给你做对照的被动字段,不该被任何逻辑读)。返回 (这次写了没有, 记录)。"""
|
|
35
|
-
key = normalize(me.
|
|
35
|
+
key = normalize(me.uuid)
|
|
36
36
|
with self._open() as session:
|
|
37
37
|
data = session.data
|
|
38
38
|
workers = data.setdefault("workers", {})
|
|
@@ -48,6 +48,21 @@ class OwnersIndex:
|
|
|
48
48
|
return True, dict(record)
|
|
49
49
|
return False, dict(record)
|
|
50
50
|
|
|
51
|
+
def keys_of_name(self, fragment: str) -> set[str]:
|
|
52
|
+
"""显示名模糊匹配(不分大小写的子串)⇒ uuid 集合。**只给查询筛选**,不参与归属判定。
|
|
53
|
+
|
|
54
|
+
比对的是名册里的名字(`workers[<uuid>].name`),不是每条任务上那份冗余的
|
|
55
|
+
`owner_name`:改名后名册先变,筛选结果跟着变,任务里的旧名字不会干扰。
|
|
56
|
+
"""
|
|
57
|
+
needle = normalize(fragment)
|
|
58
|
+
if not needle:
|
|
59
|
+
return set()
|
|
60
|
+
return {
|
|
61
|
+
key
|
|
62
|
+
for key, record in self.all().items()
|
|
63
|
+
if needle in normalize(str((record or {}).get("name") or ""))
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
def shards(self) -> dict[str, Path]:
|
|
52
67
|
"""身份 → 分片绝对路径(只信索引)。"""
|
|
53
68
|
out: dict[str, Path] = {}
|
|
@@ -110,5 +125,3 @@ class _IndexSession:
|
|
|
110
125
|
def __exit__(self, *exc) -> None:
|
|
111
126
|
if self.changed:
|
|
112
127
|
self.store._flush(self.data) # 同一个包内的受控写回口
|
|
113
|
-
|
|
114
|
-
|
|
@@ -22,7 +22,7 @@ class TaskStore(JSONStore):
|
|
|
22
22
|
self.identity = identity
|
|
23
23
|
|
|
24
24
|
def _flush(self, data: dict[str, Any]) -> None:
|
|
25
|
-
"""写盘时带上分片顶部的 `identity
|
|
25
|
+
"""写盘时带上分片顶部的 `identity`(只存 uuid;显示名在索引里)。"""
|
|
26
26
|
if self.identity is not None:
|
|
27
|
-
data["identity"] = {"
|
|
27
|
+
data["identity"] = {"uuid": self.identity.uuid}
|
|
28
28
|
super()._flush(data)
|