task-ledger 1.3.1 → 1.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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
  ## 历史层
@@ -1,14 +1,14 @@
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
13
  from ..store.atomic import ensure_dir
14
14
  from ..store.owners_index import OwnersIndex
@@ -16,12 +16,11 @@ from ..store.task_store import TaskStore
16
16
 
17
17
 
18
18
  def bootstrap(repo: Path, me: Identity) -> Identity:
19
- """写序:`owners/` 目录 → 池 → 索引(登记自己)→ 我的分片。已登记且没变 ⇒ 零写。"""
19
+ """写序:`owners/` 目录 → 池 → 索引(登记自己)→ 我的分片。已登记 ⇒ 零写。"""
20
20
  ensure_dir(repo / to_repo_rel(LEDGER_DIR))
21
21
  ensure_dir(repo / to_repo_rel(OWNERS_DIR))
22
22
  TaskStore(repo, POOL_REL).ensure_file()
23
23
  index = OwnersIndex(repo)
24
- _, record = index.ensure(me)
24
+ _, _ = index.ensure(me) # 登记自己(已登记则零写)
25
25
  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
26
+ 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)
@@ -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)
@@ -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())
@@ -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)