task-ledger 1.3.7 → 1.3.8

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/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: task-ledger
3
3
  description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)、给任务结案或退回(做完了 / 验收通过 / 不做了 / 退回待认领)、整理标签或检查台账一致性时加载;只是改代码、讨论方案时不要用。
4
- version: 1.3.7
4
+ version: 1.3.8
5
5
  allowed-tools: Read,Edit,Bash,Grep
6
6
  ---
7
7
 
@@ -45,7 +45,7 @@ allowed-tools: Read,Edit,Bash,Grep
45
45
  | `--claim <uuid>` | 认领名册里已有的 uuid(名字一起复制) |
46
46
  | `--new --name "…"` | 新建身份:生成 uuid + 名字 |
47
47
 
48
- 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。**写操作只能由用户在终端里执行**(无终端调用会被脚本拒绝);agent 只许裸跑 `init`(只读、把候选报给用户),不得代跑 `--claim` / `--new` / `--name`。名册为空时裸跑 `init` 会报「新建身份必须输入一个名字」,此时让用户自己 `init --new --name "<名字>"` 新建。
48
+ 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。在终端里**裸跑 `init` 即交互式**:读当前仓库名册——有候选就列出来让你**选编号认领**(或输 `n` 新建),名册为空就让你**输入用户名**;全程要真终端。`--claim <uuid>` / `--new --name "<名字>"` 也只接受真终端,agent 的无终端调用会被脚本拒绝。
49
49
 
50
50
  ### new — 建任务
51
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
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",
@@ -269,8 +269,28 @@ def _lock_dir(repo: Path) -> Path:
269
269
  return lock_dir
270
270
 
271
271
 
272
+ def _print_candidates(opts: list[dict]) -> None:
273
+ print("这台机器还没初始化。当前仓库里已有的用户:")
274
+ for i, opt in enumerate(opts, 1):
275
+ print(f" {i}. {opt['uuid']} {opt['name']}")
276
+
277
+
278
+ def _print_init_result(res: dict) -> None:
279
+ if res.get("already"):
280
+ print(
281
+ f"已经初始化过,未做改动:{res['name'] or res['uuid'][:8]}({res['uuid']})"
282
+ )
283
+ print(f"文件:{res['path']}")
284
+ return
285
+ print(f"已初始化:{res['name'] or res['uuid'][:8]}({res['uuid']})")
286
+ print(f"文件:{res['path']}")
287
+
288
+
272
289
  def _run_init(args: argparse.Namespace) -> int:
273
- """init 不进 runner、不解析台账、不碰 git:只写机器身份文件。"""
290
+ """init 不进 runner、不解析台账、不碰 git:只写机器身份文件。
291
+
292
+ 交互式:裸 init 在真终端读名册、问用户选 / 输名字;无终端(agent)只报候选、不写。
293
+ """
274
294
  from .. import env
275
295
  from ..actions.init_identity import init_identity
276
296
 
@@ -282,38 +302,54 @@ def _run_init(args: argparse.Namespace) -> int:
282
302
  repo = None
283
303
  claim = getattr(args, "claim", None)
284
304
  new = bool(getattr(args, "new_id", False))
285
- name = getattr(args, "name", "") or ""
305
+ name = (getattr(args, "name", "") or "").strip()
306
+ tty = sys.stdin.isatty()
307
+
286
308
  try:
287
- # 首次写身份只能由用户在真终端交互完成:agent 的 subprocess 没有 TTY,
288
- # 放行就等于让它替用户挑一个身份写进本机(2026-10-26 事故)。
289
- if (
290
- env.read_identity() is None
291
- and (claim or new or name.strip())
292
- and not sys.stdin.isatty()
293
- ):
294
- raise Fail(
295
- "写入身份必须由用户在终端里交互执行(当前没有终端)。\n"
296
- " 处理:请用户在终端运行 `python3 progress.py init --repo <工作区根>`,\n"
297
- " 它会列出已有身份供选择,或让 ta 输入一个新名字。"
298
- )
299
- res = init_identity(repo, claim=claim, new=new, name=name)
309
+ existing = env.read_identity()
310
+ if existing is not None:
311
+ # 已初始化:幂等空转(带不带 flag 都一样,零写)
312
+ res = init_identity(repo, claim=claim, new=new, name=name)
313
+ elif claim or new or name:
314
+ # flag 写操作:只接受真终端;agent 无终端调不动(2026-10-26 事故)
315
+ if not tty:
316
+ raise Fail(
317
+ "写入身份必须由用户在终端里交互执行(当前没有终端)。\n"
318
+ " 处理:请用户在终端运行 `python3 progress.py init --repo <工作区根>`,\n"
319
+ " 它会列出已有身份供选择,或让 ta 输入一个新名字。"
320
+ )
321
+ res = init_identity(repo, claim=claim, new=new, name=name)
322
+ else:
323
+ # 裸 init:先看名册
324
+ res = init_identity(repo, claim=None, new=False, name="")
325
+ if res.get("need_choose"):
326
+ opts = res.get("options", [])
327
+ if not tty:
328
+ _print_candidates(opts)
329
+ print(
330
+ "请重跑:init --claim=<uuid> 认领,或 init --new --name=<名字> 新建"
331
+ )
332
+ return 0
333
+ _print_candidates(opts)
334
+ choice = input("选编号认领,或输入 n 新建:").strip()
335
+ if choice.lower() == "n":
336
+ prompt_name = input("输入用户名:").strip()
337
+ res = init_identity(repo, claim=None, new=True, name=prompt_name)
338
+ else:
339
+ res = init_identity(
340
+ repo, claim=opts[int(choice) - 1]["uuid"], new=False, name=""
341
+ )
342
+ else:
343
+ # 名册空:交互输入名字;非终端交给用户去终端跑(init_identity 会报需要名字)
344
+ prompt_name = input("输入用户名:").strip() if tty else ""
345
+ res = init_identity(repo, claim=None, new=tty, name=prompt_name)
300
346
  except Fail as exc:
301
347
  render.fail_message(exc)
302
348
  return BUSINESS
303
- if res.get("need_choose"):
304
- print("这台机器还没初始化。当前仓库里已有的用户:")
305
- for opt in res.get("options", []):
306
- print(f" {opt['uuid']} {opt['name']}")
307
- print("请重跑:init --claim=<uuid> 认领,或 init --new --name=<名字> 新建")
308
- return 0
309
- if res.get("already"):
310
- print(
311
- f"已经初始化过,未做改动:{res['name'] or res['uuid'][:8]}({res['uuid']})"
312
- )
313
- print(f"文件:{res['path']}")
314
- return 0
315
- print(f"已初始化:{res['name'] or res['uuid'][:8]}({res['uuid']})")
316
- print(f"文件:{res['path']}")
349
+ except (ValueError, IndexError):
350
+ render.fail_message(Fail("输入无效,初始化已取消。"))
351
+ return BUSINESS
352
+ _print_init_result(res)
317
353
  return 0
318
354
 
319
355