task-ledger 1.0.0 → 1.0.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/SKILL.md CHANGED
@@ -82,7 +82,7 @@ python3 <本 SKILL.md 所在目录>/scripts/progress.py <子命令> --repo <当
82
82
  | `set` | 改状态 / 换负责人 | `--id`(必填)、`--status`、`--owner` |
83
83
  | `close` | 结案:移出状态 + 追加历史 + 封卷过程文档 | `--id`、`--summary`(都必填)、`--finished`(可选,结案时间)、`--category` / `--tags`(可选,结案时覆盖修正一次) |
84
84
  | `list` | 查询在办任务 | `--mine`、`--owner`、`--status`、`--since`、`--until`、`--category`(精确)、`--tag`(包含)、`--json` |
85
- | `history` | 查已结案任务(只读) | `--owner`、`--mine`、`--since`、`--until`、`--limit`、`--category`(精确)、`--tag`(包含)、`--json` |
85
+ | `history` | 查已结案任务(只读) | `--owner`、`--mine`、`--since`、`--until`、`--limit`、`--category`(精确)、`--tag`(包含)、`--json`;另:`--count-only`(只数行数、封顶 1 万输出 `{count, capped}`,显示级统计)、`--months N`(只读最近 N 个月文件,不含存量平铺文件) |
86
86
  | `show` | 单任务详情 | `--id`(必填) |
87
87
  | `tags` | 分类/标签巡检与归并 | `--all`(全量频次表)、`--rename 旧 新`(归并在办任务的同值) |
88
88
  | `validate` | 校验状态表与过程文档是否一致 | — |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/progress.json, 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",
@@ -736,6 +736,47 @@ def history_sources(repo: Path) -> list[Path]:
736
736
  return paths
737
737
 
738
738
 
739
+ # 数行计数的封顶值(2026-10-17 为 dsh-task-ledger 看板统计卡新增):显示级统计封顶,
740
+ # 超出即 capped,精确值走 history --json / 列表。
741
+ COUNT_CAP = 10000
742
+
743
+
744
+ def history_row_count(
745
+ sources: list[Path], since: str | None, until: str | None, cap: int = COUNT_CAP
746
+ ) -> dict[str, Any]:
747
+ """数行计数(只读;2026-10-17 新增,服务看板「本周 / 近一年完成」统计卡)。
748
+
749
+ 只数数据行(`| 2` 开头 = 结案时间以年份开头),到 cap 封顶返回 capped=True。
750
+ 给了 since/until 时按创建时间列(第 2 列)粗筛——取日期部分做字符串比较
751
+ (ISO 日期字典序即时间序),不建对象、不判格式,比 parse_history 便宜一个量级。
752
+ ⚠️ 显示级统计:坏行 / 旧格式行可能多数少数,精确数以 history --json 为准。
753
+ """
754
+ since_d = (since or "")[:10]
755
+ until_d = (until or "")[:10]
756
+ total = 0
757
+ for hp in sources:
758
+ try:
759
+ text = hp.read_text(encoding="utf-8-sig")
760
+ except (OSError, UnicodeDecodeError):
761
+ continue
762
+ for line in text.splitlines():
763
+ if not line.startswith("| 2"):
764
+ continue
765
+ if since_d or until_d:
766
+ cells = line.split("|")
767
+ if len(cells) < 4:
768
+ continue
769
+ created = cells[2].strip()[:10]
770
+ if since_d and created < since_d:
771
+ continue
772
+ if until_d and created > until_d:
773
+ continue
774
+ total += 1
775
+ if total >= cap:
776
+ return {"count": total, "capped": True}
777
+ return {"count": total, "capped": False}
778
+
779
+
739
780
  def worklog_link(log_path: Path, history_path: Path) -> str:
740
781
  """worklog 相对历史文件所在目录的 posix 链接。
741
782
 
@@ -1691,11 +1732,48 @@ def cmd_history(args: argparse.Namespace) -> None:
1691
1732
 
1692
1733
  repo = find_repo(args.repo)
1693
1734
  sources = history_sources(repo)
1735
+ want_owner = (
1736
+ args.owner.strip().lstrip("@") if args.owner else None
1737
+ ) # 容忍「@名字」写法
1738
+ me = git_user() if args.mine else None
1739
+ filter_echo = {
1740
+ "owner": want_owner,
1741
+ "mine": args.mine,
1742
+ "since": since,
1743
+ "until": until,
1744
+ "limit": args.limit,
1745
+ "category": args.category.strip() if args.category else None,
1746
+ "tag": args.tag.strip() if args.tag else None,
1747
+ }
1694
1748
  if not sources:
1749
+ # JSON / 计数模式必须**始终**机器可读:空台账也给 JSON,人话只走 stderr。
1750
+ # (2026-10-17 真机教训:这里原先直接 print 人话后 return,--json 调用方
1751
+ # 拿到非 JSON 输出 ⇒ dsh-task-ledger 看板 /board 整条 500。)
1752
+ if args.count_only:
1753
+ print(json.dumps({"count": 0, "capped": False}, ensure_ascii=False))
1754
+ return
1755
+ if args.json:
1756
+ print(
1757
+ json.dumps(
1758
+ {"count": 0, "filters": filter_echo, "records": []},
1759
+ ensure_ascii=False,
1760
+ indent=2,
1761
+ )
1762
+ )
1763
+ return
1695
1764
  print(
1696
1765
  f"没有已结案记录:ledger/history/ 还没有文件(存量 {HISTORY_REL} 也没有)。"
1697
1766
  )
1698
1767
  return
1768
+ if args.months is not None:
1769
+ if args.months < 1:
1770
+ raise Fail("--months 必须是正整数")
1771
+ # 只取按月归档的文件(存量平铺文件不进了——它是改造前的历史,窗口模式用不到)
1772
+ monthly = [p for p in sources if p.parent.name == HISTORY_DIR.name]
1773
+ sources = monthly[-args.months :]
1774
+ if args.count_only:
1775
+ print(json.dumps(history_row_count(sources, since, until), ensure_ascii=False))
1776
+ return
1699
1777
  records: list[dict[str, Any]] = []
1700
1778
  warnings: list[str] = []
1701
1779
  for hp in sources:
@@ -1713,10 +1791,6 @@ def cmd_history(args: argparse.Namespace) -> None:
1713
1791
  records.extend(recs)
1714
1792
  warnings.extend(f"{rel} {w}" for w in warns)
1715
1793
 
1716
- me = git_user() if args.mine else None
1717
- want_owner = (
1718
- args.owner.strip().lstrip("@") if args.owner else None
1719
- ) # 容忍「@名字」写法
1720
1794
  rows = []
1721
1795
  undated = 0
1722
1796
  for r in records:
@@ -1757,15 +1831,7 @@ def cmd_history(args: argparse.Namespace) -> None:
1757
1831
  json.dumps(
1758
1832
  {
1759
1833
  "count": len(rows),
1760
- "filters": {
1761
- "owner": want_owner,
1762
- "mine": args.mine,
1763
- "since": since,
1764
- "until": until,
1765
- "limit": args.limit,
1766
- "category": args.category.strip() if args.category else None,
1767
- "tag": args.tag.strip() if args.tag else None,
1768
- },
1834
+ "filters": filter_echo,
1769
1835
  "records": rows,
1770
1836
  },
1771
1837
  ensure_ascii=False,
@@ -1914,6 +1980,16 @@ def build_parser() -> argparse.ArgumentParser:
1914
1980
  )
1915
1981
  p.add_argument("--limit", type=int, help="过滤 + 倒序后取前 N 条(最近 N 条)")
1916
1982
  p.add_argument("--json", action="store_true", help="输出结构化 JSON(含摘要)")
1983
+ p.add_argument(
1984
+ "--count-only",
1985
+ action="store_true",
1986
+ help="只数行数(封顶 1 万,输出 {count, capped});显示级统计,精确数以 --json 为准",
1987
+ )
1988
+ p.add_argument(
1989
+ "--months",
1990
+ type=int,
1991
+ help="只读最近 N 个月的历史文件(ledger/history/*.md 按文件名取末 N 个;不含存量平铺文件)",
1992
+ )
1917
1993
  add_repo(p)
1918
1994
  p.set_defaults(func=cmd_history)
1919
1995