task-ledger 1.3.1 → 1.3.3

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 CHANGED
@@ -31,7 +31,7 @@
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
37
  子命令:`new` `claim` `set` `log` `close` `list` `history` `show` `tags` `validate`。命令参考(参数 / 一句话 / 举例)、任务生命周期、纪律(数据完整性 / 人类授权 / 故障处置)全在 `SKILL.md`;参数细节看 `<子命令> --help`(不要凭记忆编)。
package/SKILL.md CHANGED
@@ -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 <目录>` 给了就用;不给就从当前目录逐级向上找第一个含 `ledger/` 的目录。
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` 为准。
@@ -193,7 +193,7 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当
193
193
 
194
194
  ### 人类授权(跑之前问不问)
195
195
 
196
- 项目没有 `ledger/` ⇒ 先问「要不要建个台账记这活?」,不许自建。用户同意 ⇒ 直接跑第一条写命令,且**必须带 `--repo <项目根>`**:不带时脚本只从当前目录逐级往上找第一个含 `ledger/` 的目录,一个都没有就退 1 报「找不到台账」,绝不就地建;台账由这条写命令顺带建出来,没有 `init` 命令。之后可以不带 `--repo`,但查找是**逐级往上**的 —— 在本项目的子目录里跑可能命中上层项目的台账,拿不准就一直带 `--repo <项目根>`。
196
+ 项目没有 `ledger/` ⇒ 先问「要不要建个台账记这活?」,不许自建。用户同意 ⇒ 直接跑第一条写命令,且**必须带 `--repo <工作区根>`**(根是直接含有 `ledger/` 的那个目录,`ledger/` 本身不是根、指到它会被拒绝):不带时脚本只从当前目录逐级往上找第一个含 `ledger/` 的目录,一个都没有就退 1 报「找不到台账」,绝不就地建;台账由这条写命令顺带建出来,没有 `init` 命令。之后可以不带 `--repo`,但查找是**逐级往上**的 —— 在本项目的子目录里跑可能命中上层项目的台账,拿不准就一直带 `--repo <工作区根>`。
197
197
 
198
198
  | 命令 | 档位 | 要求 |
199
199
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",
@@ -35,7 +35,7 @@ close → 历史层 + 封卷 + 移出自己名下
35
35
 
36
36
  - 主键 = **git 邮箱**(`git var GIT_COMMITTER_IDENT` 里的 email,退回 `git config user.email`),归一化(去空白 + 小写)后比对。
37
37
  - 读不到邮箱**不报错**:走兜底链(`USER`/`LOGNAME` → `getpass` → 主机名 → 字面 `default`)并出一条提示,命令照常跑完。看到提示要告诉用户配好 git,否则任务记到兜底身份名下。
38
- - `owners.json` 里的 `aliases` 会并进身份比对;`list --owner` / `history --owner` 是归一化后的**精确匹配**,没有 `me` 缩写,「自己」用 `--mine`。
38
+ - 全站只认 git 邮箱这一个身份键;`list --owner` / `history --owner` 是归一化后的**精确匹配**,没有 `me` 缩写,「自己」用 `--mine`。
39
39
  - `close` / `log` 只对自己名下的任务生效,不在名下直接拒绝;`set --owner ""` 对已在公共池里的任务是幂等空转(退 0);`set --owner <自己>` 会把公共池里的任务认领到自己名下。
40
40
 
41
41
  ## 历史层
@@ -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` 指对项目根;第一条写命令会自动把 `ledger/` 建出来,不用先初始化。
57
+ - **「找不到台账:从当前目录往上都没有 ledger/ 目录」**:`--repo` 指对**工作区根**(直接含有 `ledger/` 的那个目录);第一条写命令会自动把 `ledger/` 建出来,不用先初始化。
58
+ - **「--repo 指到了台账目录本身」**:你把 `ledger/` 当成了根;它的上一级才是工作区根,按报错里给的路径改。
59
+ - **「--repo 指到了台账目录里面」**:你指到了 `ledger/` 下面的某个子目录(如 `ledger/worklog`);工作区根是那个 `ledger/` 的上一级,按报错里给的路径改。
60
+ - **「--repo 指向的不是一个目录」**:路径写错或不存在,要指到工作区根。
58
61
 
59
62
  ### 并发 / 锁(退 1 或提示)
60
63
 
@@ -1,27 +1,30 @@
1
1
  """开工前的自举:台账文件不存在就建,没登记过就把自己登记进去、建自己的分片。
2
2
 
3
3
  这三份由**外壳**单独一个 `atom` 包住,它自己不开事务、不拿锁。
4
- 返回 `Identity`(把索引里的 `aliases` 并进来),不参与「提交说明 / where / 打印」。
4
+ 不参与「提交说明 / where / 打印」。
5
5
  """
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
9
  from pathlib import Path
10
10
 
11
- from ..core.identity import Identity, with_aliases
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
16
17
 
17
18
 
18
19
  def bootstrap(repo: Path, me: Identity) -> Identity:
19
- """写序:`owners/` 目录 → 池 → 索引(登记自己)→ 我的分片。已登记且没变 ⇒ 零写。"""
20
- ensure_dir(repo / to_repo_rel(LEDGER_DIR))
20
+ """写序:`owners/` 目录 → 池 → 索引(登记自己)→ 我的分片。已登记 ⇒ 零写。"""
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)
24
- _, record = index.ensure(me)
28
+ _, _ = index.ensure(me) # 登记自己(已登记则零写)
25
29
  TaskStore(repo, shard_rel(me.email), identity=me).ensure_file()
26
- aliases = tuple(record.get("aliases") or ())
27
- return with_aliases(me, aliases) if aliases else me
30
+ return me
@@ -20,14 +20,10 @@ from .query import ListQuery
20
20
 
21
21
 
22
22
  def current_identity(repo: Path) -> Identity:
23
- """env 的本机身份 + `owners.json` 里这个身份的 `aliases`(不写盘、不登记)。"""
23
+ """env 的本机身份(不写盘、不登记)。全站只认邮箱这一个身份键。"""
24
24
  from .. import env
25
- from ..core.identity import with_aliases
26
25
 
27
- me = env.current(repo)
28
- record = OwnersIndex(repo).get(me.email) or {}
29
- aliases = tuple(record.get("aliases") or ())
30
- return with_aliases(me, aliases) if aliases else me
26
+ return env.current(repo)
31
27
 
32
28
 
33
29
  def require_task(tasks: Mapping[str, Any], tid: str, *, where: Path) -> dict[str, Any]:
@@ -72,7 +68,7 @@ def status_of(task: Mapping[str, Any]) -> str:
72
68
 
73
69
 
74
70
  def is_mine(task: Mapping[str, Any], me: Identity) -> bool:
75
- """这条任务是不是我的(含 aliases)。"""
71
+ """这条任务是不是我的(按邮箱判定)。"""
76
72
  return is_me(me, owner_of(task) or "")
77
73
 
78
74
 
@@ -127,8 +127,7 @@ def query_history(repo: Path, me: Identity, q: HistoryQuery) -> HistoryResult:
127
127
  records.append(row)
128
128
 
129
129
  if q.mine:
130
- wanted = {k for k in (me.email, *me.aliases) if k}
131
- wanted = {normalize(k) for k in wanted}
130
+ wanted = {normalize(me.email)}
132
131
  records = [r for r in records if normalize(r.get("owner") or "") in wanted]
133
132
  elif q.owner:
134
133
  key = normalize(q.owner)
@@ -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 LEGACY_HISTORY_REL, OWNERS_REL, POOL_REL, to_repo_rel
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),
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import dataclass, replace
5
+ from dataclasses import dataclass
6
6
 
7
7
 
8
8
  @dataclass(frozen=True)
@@ -11,7 +11,6 @@ class Identity:
11
11
 
12
12
  email: str
13
13
  name: str = ""
14
- aliases: tuple[str, ...] = ()
15
14
 
16
15
  def label(self) -> str:
17
16
  """显示名:没名字就用邮箱。"""
@@ -23,21 +22,6 @@ def normalize(who: str) -> str:
23
22
  return (who or "").strip().lower()
24
23
 
25
24
 
26
- def keys(identity: Identity) -> tuple[str, ...]:
27
- """这个身份的全部可比标识:自己 + aliases(去重、保持顺序)。"""
28
- out: list[str] = []
29
- for value in (identity.email, *identity.aliases):
30
- key = normalize(value)
31
- if key and key not in out:
32
- out.append(key)
33
- return tuple(out)
34
-
35
-
36
25
  def is_me(identity: Identity, who: str | None) -> bool:
37
- """`who`(可能是邮箱、名字或别名)是不是这个人。"""
38
- return normalize(who or "") in keys(identity)
39
-
40
-
41
- def with_aliases(identity: Identity, aliases: tuple[str, ...]) -> Identity:
42
- """换一个带 aliases 的同身份对象(索引里读到别名后由外壳装配)。"""
43
- return replace(identity, aliases=tuple(aliases))
26
+ """`who`(邮箱)是不是这个人。全站只认邮箱这一个身份键。"""
27
+ return normalize(who or "") == normalize(identity.email)
@@ -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
@@ -90,4 +90,4 @@ def current(repo: Path | None = None) -> Identity:
90
90
  "配好 git config user.email 再跑,任务才会记到正确的名下。"
91
91
  )
92
92
  name = git_name(repo) or email
93
- return Identity(email=normalize(email), name=name.strip(), aliases=())
93
+ return Identity(email=normalize(email), name=name.strip())
@@ -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.paths import LEDGER_DIR
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="台账所在仓库(默认按当前目录向上找含 ledger/ 的目录)"
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` 给了就用;没给就从 `start`(默认 cwd)往上找含 `ledger/` 的目录。
172
+ """`--repo` 给了就校验后用;没给就从 `start`(默认 cwd)往上找。
173
173
 
174
174
  找不到 ⇒ Fail:绝不在当前目录自作主张建台账(2026-10-22 设计定稿)。
175
175
  建台账是第一条写命令的事,不需要、也没有 `init` 子命令。
176
176
  """
177
177
  if arg:
178
- return Path(arg)
178
+ return as_root(arg, source="--repo")
179
179
  here = (start or Path.cwd()).resolve()
180
- for candidate in (*here.parents, here):
181
- if (candidate / LEDGER_DIR).is_dir():
182
- return candidate
183
- raise Fail(
184
- "找不到台账:从当前目录往上都没有 ledger/ 目录。\n"
185
- " 处理:回到项目根再跑;或用 --repo <项目根> 指定。\n"
186
- " 第一条写命令会自动把 ledger/ 建出来,不用先初始化。"
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
- return Path(repo)
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)
@@ -1,7 +1,6 @@
1
1
  """索引 `owners.json`:谁是登记过的人、各自的分片文件是哪个、新人的登记与改名。
2
2
 
3
3
  **分片只认索引,不扫目录** —— 唯一例外是全仓仅此一处的 `disk_shards()`,它只给 `validate` 用。
4
- `ensure` 是 `aliases` 的唯一写入点。
5
4
  """
6
5
 
7
6
  from __future__ import annotations
@@ -32,7 +31,7 @@ class OwnersIndex:
32
31
  return self._store.all()
33
32
 
34
33
  def ensure(self, me: Identity) -> tuple[bool, dict[str, Any]]:
35
- """登记 / 补 `joined` / 名字或 aliases 变了才写。返回 (这次写了没有, 记录)。"""
34
+ """登记 / 补 `joined`。`name` 只在首次登记时写入,之后不回写(名字是给你做对照的被动字段,不该被任何逻辑读)。返回 (这次写了没有, 记录)。"""
36
35
  key = normalize(me.email)
37
36
  with self._open() as session:
38
37
  data = session.data
@@ -43,23 +42,11 @@ class OwnersIndex:
43
42
  "file": shard_rel(key).name,
44
43
  "name": me.name,
45
44
  "joined": now(),
46
- "aliases": _merge_aliases([], me.aliases),
47
45
  }
48
46
  workers[key] = record
49
47
  session.mark()
50
48
  return True, dict(record)
51
- changed = False
52
- if me.name and record.get("name") != me.name:
53
- record["name"] = me.name
54
- changed = True
55
- known = list(record.get("aliases") or [])
56
- aliases = _merge_aliases(known, me.aliases) # 只增不删
57
- if aliases != known:
58
- record["aliases"] = aliases
59
- changed = True
60
- if changed:
61
- session.mark()
62
- return changed, dict(record)
49
+ return False, dict(record)
63
50
 
64
51
  def shards(self) -> dict[str, Path]:
65
52
  """身份 → 分片绝对路径(只信索引)。"""
@@ -125,11 +112,3 @@ class _IndexSession:
125
112
  self.store._flush(self.data) # 同一个包内的受控写回口
126
113
 
127
114
 
128
- def _merge_aliases(known: list, extra: tuple[str, ...] | list[str]) -> list[str]:
129
- """把新别名并进去,只增不删、去重保序。"""
130
- out = list(known)
131
- for alias in extra or ():
132
- key = normalize(alias)
133
- if key and key not in out:
134
- out.append(key)
135
- return out
@@ -22,10 +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`(只存邮箱;显示名在索引里)。"""
26
26
  if self.identity is not None:
27
- data["identity"] = {
28
- "email": self.identity.email,
29
- "name": self.identity.name,
30
- }
27
+ data["identity"] = {"email": self.identity.email}
31
28
  super()._flush(data)