dsh-task-ledger 0.0.119 → 0.0.121

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.
@@ -20,7 +20,7 @@ export interface DegradedInfo {
20
20
  readonly workspaceId: string;
21
21
  readonly workspaceTitle: string;
22
22
  /** 哪个读挂了(skill 子命令;趋势走 `stats --trend`,单独一档便于区分)。 */
23
- readonly op: "list" | "history" | "owners" | "trend";
23
+ readonly op: "list" | "owners" | "trend";
24
24
  readonly code: LedgerErrorCode;
25
25
  /** 人话,可直接上界面(真源:脚本 stderr / records 校验文案)。 */
26
26
  readonly message: string;
@@ -36,7 +36,7 @@ export declare function messageOf(error: unknown): string;
36
36
  export declare function settleWorkspaces<W extends {
37
37
  id: string;
38
38
  title: string;
39
- }, T, R>(workspaces: readonly W[], op: "list" | "history" | "owners", read: (workspace: W) => Promise<T>, merge: (rows: readonly T[]) => R): Promise<{
39
+ }, T, R>(workspaces: readonly W[], op: "list" | "owners", read: (workspace: W) => Promise<T>, merge: (rows: readonly T[]) => R): Promise<{
40
40
  data: R;
41
41
  degraded: readonly DegradedInfo[];
42
42
  }>;
@@ -16,21 +16,7 @@ export interface WorkspaceStats {
16
16
  readonly overdueDays: number;
17
17
  readonly backlogDays: number;
18
18
  }
19
- /** 一个时间窗口内的结案数(`history --count-only`)。 */
20
- export interface ClosedCount {
21
- readonly count: number;
22
- /** count 到了 skill 的封顶(10000)⇒ 真实值只多不少。 */
23
- readonly capped: boolean;
24
- }
25
19
  /** 单工作区在办统计(skill `stats --json`;过 1.5s 读缓存,同屏多端点复用)。 */
26
20
  export declare function readStats(workspace: LedgerWorkspace): Promise<WorkspaceStats>;
27
21
  /** `stats --json` → {@link WorkspaceStats}(容器级严格:缺字段即坏,不当 0)。 */
28
22
  export declare function parseStats(raw: unknown, where: string): WorkspaceStats;
29
- /**
30
- * 一个时间窗口内结案了多少条(skill `history --count-only`)。
31
- * 区间写进缓存键:本周 / 上周两个窗口各算一次,别互相串味。
32
- */
33
- export declare function countClosed(workspace: LedgerWorkspace, window: {
34
- readonly from: string;
35
- readonly to: string;
36
- }): Promise<ClosedCount>;
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * - 在办各面(待认领 / 进行中 / 积压 / 逾期 / 停滞 / 我的)→ `stats --json`;
5
5
  * 阈值、mine 判定、最老待认领天数都由 skill 算好回显。
6
- * - 本周 / 上周完成 → `history --count-only`(结案时间落在区间内;区间界线由插件算,日期窗口归插件)。
6
+ * - 本周 / 上周完成 → **不再单独查 history**(2026-10-29):由趋势(`stats --trend week`)
7
+ * 的最后两个桶直接给出,少两次脚本调用、口径也跟图表完全一致。
7
8
  *
8
9
  * 形状不符一律抛 {@link LedgerDataError}(不拿 0 充数)。
9
10
  */
@@ -59,28 +60,3 @@ export function parseStats(raw, where) {
59
60
  backlogDays: num(thresholds, "backlog_days", where),
60
61
  };
61
62
  }
62
- /**
63
- * 一个时间窗口内结案了多少条(skill `history --count-only`)。
64
- * 区间写进缓存键:本周 / 上周两个窗口各算一次,别互相串味。
65
- */
66
- export async function countClosed(workspace, window) {
67
- return await cached(cacheKey(workspace.id, "history", `count:${window.from}~${window.to}`), async () => {
68
- const result = await runLedgerScript("history", [
69
- "--repo",
70
- workspace.path,
71
- "--count-only",
72
- "--since",
73
- window.from,
74
- "--until",
75
- window.to,
76
- ]);
77
- if (!result.ok)
78
- throw new Error(`${workspace.title}: ${result.error}`);
79
- if (!isPlainObject(result.data))
80
- throw new LedgerDataError(workspace.title, `${workspace.title}: history --count-only 输出不是对象`);
81
- const count = result.data.count;
82
- if (typeof count !== "number" || !Number.isFinite(count))
83
- throw new LedgerDataError(workspace.title, `${workspace.title}: history --count-only 缺 count 数字`);
84
- return { count, capped: result.data.capped === true };
85
- });
86
- }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 趋势面取数:近 8 周的「完成 / 未结 / 新增」,**整段桶序列一次拿回**(skill `stats --trend`)。
3
3
  *
4
- * 为什么不再拼现有方法:`history --count-only` 只给单窗口总数、且按**结案时间**过滤(新增取不到);
4
+ * 为什么不再拼现有方法:`history` 只给明细、且按**结案时间**过滤(新增取不到);
5
5
  * 插件侧拉全量明细再聚合已被实测否决(20 万条 3s+、30MB 过进程边界)。
6
6
  * 桶边界(周一起算 / 本机时区)与「未结 = 该周末仍未结案」的倒推算法全在 skill,
7
7
  * 本文件只做形状校验 —— 与 stats 同一条纪律:数字真源只有 skill 一份。
@@ -37,7 +37,7 @@ export function registerRoutes(webServer, deps) {
37
37
  });
38
38
  }
39
39
  });
40
- // 首屏一次拿全:8 卡(skill `stats` + 两次 `history --count-only`)+ 在办列表(`list --json`)。
40
+ // 首屏一次拿全:8 卡(skill `stats`;本周/上周完成取趋势最后两个桶)+ 在办列表(`list --json`)+ 趋势。
41
41
  registerRead("/board/snapshot", async (_req, res) => {
42
42
  try {
43
43
  sendJson(res, 200, await getBoardHome(await workspacesOf(deps)));
@@ -2,7 +2,7 @@
2
2
  * 日期口径(纯函数层,零 IO):本机时区、周一为一周之始(与 skill 脚本同口径)。
3
3
  * 所有函数显式收 `now`——不自己读时钟,便于单测与跨时区复现。
4
4
  *
5
- * 现在只有「本周 / 上周结案数」用得到(`history --count-only` 的时间窗)。
5
+ * 现在只有「本周 / 上周」用得到:趋势请求的时间窗与兜底标签(数字本身取 `stats --trend` 的桶)。
6
6
  * 台账时刻的解析与天数派生**已全部删除**:那些是 skill 算好的(`age_days` / `idle_days` /
7
7
  * `waiting_days` 随 `list --json` 每行下发),插件不再保留第二套算法。
8
8
  */
@@ -2,7 +2,7 @@
2
2
  * 日期口径(纯函数层,零 IO):本机时区、周一为一周之始(与 skill 脚本同口径)。
3
3
  * 所有函数显式收 `now`——不自己读时钟,便于单测与跨时区复现。
4
4
  *
5
- * 现在只有「本周 / 上周结案数」用得到(`history --count-only` 的时间窗)。
5
+ * 现在只有「本周 / 上周」用得到:趋势请求的时间窗与兜底标签(数字本身取 `stats --trend` 的桶)。
6
6
  * 台账时刻的解析与天数派生**已全部删除**:那些是 skill 算好的(`age_days` / `idle_days` /
7
7
  * `waiting_days` 随 `list --json` 每行下发),插件不再保留第二套算法。
8
8
  */
@@ -1,4 +1,24 @@
1
- import type { BoardHomePayload, LedgerWorkspace, StatsPayload } from "../types.js";
1
+ import type { BoardHomePayload, LedgerWorkspace, StatsPayload, TrendView } from "../types.js";
2
+ /** 本周 / 上周完成 = 趋势最后两个桶的「结案」(后者为上周)。 */
3
+ export interface WeekDone {
4
+ readonly weekDone: number | null;
5
+ readonly lastWeekDone: number | null;
6
+ readonly weekFrom: string;
7
+ readonly weekTo: string;
8
+ readonly lastWeekFrom: string;
9
+ readonly lastWeekTo: string;
10
+ }
11
+ /**
12
+ * 从趋势桶里取「本周 / 上周完成」。
13
+ * 趋势为空(全挂)⇒ 两个数都给 null(界面留 —,不拿 0 充数)。
14
+ */
15
+ export declare function weekDoneFromTrend(trend: TrendView, fallback: {
16
+ readonly from: string;
17
+ readonly to: string;
18
+ }, fallbackLast: {
19
+ readonly from: string;
20
+ readonly to: string;
21
+ }): WeekDone;
2
22
  /**
3
23
  * 首屏一次拿全:8 卡 + 在办列表 + 工作区 + 身份 + 各工作区花名册。
4
24
  * 三条读并行(`stats` / `list` / `owners`),各自缓存;一个工作区坏只进 degraded,其余照常。
@@ -1,41 +1,32 @@
1
1
  /**
2
2
  * 8 个统计卡的用例(首屏唯一端点 `/board/stats`)。
3
3
  *
4
- * 数字真源全在 skill:每工作区跑 `stats --json`(在办各面)与两次
5
- * `history --count-only`(本周 / 上周完成),本文件只做跨工作区求和与降级归集。
6
- * 趋势图(近 8 周)由 `trend.service.ts` 走 `stats --trend` 单独算,随首屏一起打包回来。
4
+ * 数字真源全在 skill:每工作区跑 `stats --json`(在办各面),本文件只做跨工作区求和与降级归集。
5
+ * 「本周 / 上周完成」不再单独查 history —— 直接取趋势桶(`stats --trend week`)的最后两个,
6
+ * 少两次脚本调用,口径也跟图表一致(2026-10-29)。
7
7
  */
8
- import { codeOf, messageOf, settleWorkspaces, } from "../errors.js";
8
+ import { settleWorkspaces } from "../errors.js";
9
9
  import { getCurrentIdentity } from "../repositories/identity.repository.js";
10
10
  import { getBoardTrend } from "./trend.service.js";
11
11
  import { readOwners, readTasks } from "../repositories/ledger.repository.js";
12
- import { countClosed, readStats, } from "../repositories/stats.repository.js";
12
+ import { readStats } from "../repositories/stats.repository.js";
13
13
  import { weekWindow } from "./dates.js";
14
14
  /**
15
- * 一个自然周的结案数(跨工作区求和)。
16
- * 部分工作区失败 ⇒ 数字偏小,degraded 会带出去(界面标红,不装没看见)。
15
+ * 从趋势桶里取「本周 / 上周完成」。
16
+ * 趋势为空(全挂)⇒ 两个数都给 null(界面留 —,不拿 0 充数)。
17
17
  */
18
- async function countWeek(workspaces, window) {
19
- const settled = await Promise.allSettled(workspaces.map((workspace) => countClosed(workspace, window)));
20
- let done = 0;
21
- let ok = 0;
22
- const degraded = [];
23
- settled.forEach((result, i) => {
24
- if (result.status === "fulfilled") {
25
- done += result.value.count;
26
- ok += 1;
27
- return;
28
- }
29
- const workspace = workspaces[i];
30
- degraded.push({
31
- workspaceId: workspace.id,
32
- workspaceTitle: workspace.title,
33
- op: "history",
34
- code: codeOf(result.reason),
35
- message: messageOf(result.reason),
36
- });
37
- });
38
- return { done: ok === 0 && workspaces.length > 0 ? null : done, degraded };
18
+ export function weekDoneFromTrend(trend, fallback, fallbackLast) {
19
+ const buckets = trend.buckets;
20
+ const thisWeek = buckets[buckets.length - 1];
21
+ const lastWeek = buckets[buckets.length - 2];
22
+ return {
23
+ weekDone: thisWeek === undefined ? null : thisWeek.done,
24
+ lastWeekDone: lastWeek === undefined ? null : lastWeek.done,
25
+ weekFrom: thisWeek?.from ?? fallback.from,
26
+ weekTo: thisWeek?.to ?? fallback.to,
27
+ lastWeekFrom: lastWeek?.from ?? fallbackLast.from,
28
+ lastWeekTo: lastWeek?.to ?? fallbackLast.to,
29
+ };
39
30
  }
40
31
  /** 单工作区在办列表 + 本机身份(都经 skill:`list --json` / `whoami --json`)。 */
41
32
  async function readTaskScope(workspace) {
@@ -73,8 +64,10 @@ export async function getBoardHome(workspaces, now = new Date()) {
73
64
  identities[scope.workspace.id] = scope.identity;
74
65
  tasks.push(...scope.tasks);
75
66
  }
67
+ const weeks = weekDoneFromTrend(trend, weekWindow(0, now), weekWindow(-1, now));
76
68
  return {
77
69
  ...statsPayload,
70
+ stats: { ...statsPayload.stats, ...weeks },
78
71
  trend: { buckets: trend.buckets, coverageFrom: trend.coverageFrom, coverageTo: trend.coverageTo },
79
72
  workspaces,
80
73
  tasks,
@@ -91,11 +84,7 @@ export async function getBoardHome(workspaces, now = new Date()) {
91
84
  export async function getBoardStats(workspaces, now = new Date()) {
92
85
  const thisWeek = weekWindow(0, now);
93
86
  const lastWeek = weekWindow(-1, now);
94
- const [perWorkspace, thisWeekCounts, lastWeekCounts] = await Promise.all([
95
- settleWorkspaces(workspaces, "list", readStats, (rows) => rows),
96
- countWeek(workspaces, thisWeek),
97
- countWeek(workspaces, lastWeek),
98
- ]);
87
+ const perWorkspace = await settleWorkspaces(workspaces, "list", readStats, (rows) => rows);
99
88
  const sum = (pick) => perWorkspace.data.reduce((total, stats) => total + pick(stats), 0);
100
89
  // 阈值由 skill 回显;没传过覆盖值 ⇒ 各工作区一致,取首个即可。
101
90
  // 一个工作区都没有时给 0——那时所有计数也是 0,阈值文案没有实际含义。
@@ -119,8 +108,9 @@ export async function getBoardStats(workspaces, now = new Date()) {
119
108
  : max === null
120
109
  ? item.oldestOpenDays
121
110
  : Math.max(max, item.oldestOpenDays), null),
122
- weekDone: thisWeekCounts.done,
123
- lastWeekDone: lastWeekCounts.done,
111
+ // 这两格由 getBoardHome 从趋势桶回填(趋势全挂时保持 null ⇒ 界面留 —)
112
+ weekDone: null,
113
+ lastWeekDone: null,
124
114
  weekFrom: thisWeek.from,
125
115
  weekTo: thisWeek.to,
126
116
  lastWeekFrom: lastWeek.from,
@@ -129,10 +119,6 @@ export async function getBoardStats(workspaces, now = new Date()) {
129
119
  return {
130
120
  at: now.toISOString(),
131
121
  stats,
132
- degraded: [
133
- ...perWorkspace.degraded,
134
- ...thisWeekCounts.degraded,
135
- ...lastWeekCounts.degraded,
136
- ],
122
+ degraded: perWorkspace.degraded,
137
123
  };
138
124
  }
@@ -73,7 +73,7 @@ export interface LedgerFilter {
73
73
  until?: string;
74
74
  }
75
75
  /**
76
- * 8 个统计卡的面。**数字全部来自 skill**(`stats --json` + `history --count-only`),
76
+ * 8 个统计卡的面。**数字全部来自 skill**(在办各面取 `stats --json`,本周/上周完成取趋势桶),
77
77
  * 插件只做跨工作区求和与阈值透传——不自己判停滞 / 逾期 / 积压。
78
78
  */
79
79
  export interface BoardStatsView {
@@ -99,7 +99,7 @@ export interface BoardStatsView {
99
99
  readonly backlogDays: number;
100
100
  /** 最老一条待认领躺了多少天(没有待认领 = null)。 */
101
101
  readonly oldestOpenDays: number | null;
102
- /** 本周 / 上周完成数;history 全读不到时为 null(界面留 —,不拿 0 充数)。 */
102
+ /** 本周 / 上周完成数(= 趋势最后两个桶);趋势全读不到时为 null(界面留 —)。 */
103
103
  readonly weekDone: number | null;
104
104
  readonly lastWeekDone: number | null;
105
105
  /** 两周的区间(界面悬停用,含边界)。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-task-ledger",
3
- "version": "0.0.119",
3
+ "version": "0.0.121",
4
4
  "description": "Task ledger board for dsh (DeepSeek Harness): read-only board over task-ledger skill workspaces. The task-ledger skill is bundled — install the plugin, get the skill globally.",
5
5
  "keywords": [
6
6
  "deepseek",
package/skill/SKILL.md CHANGED
@@ -224,7 +224,6 @@ allowed-tools: Read,Edit,Bash,Grep
224
224
  | `--since` / `--until` | 按**结案时间**筛(只给日期含当天) |
225
225
  | `--months N` | 最近 N 个自然月(与 `--since` / `--until` **互斥**,同传直接报错) |
226
226
  | `--limit N` | 最近 N 条 |
227
- | `--count-only` | 只输出 `{"count","capped"}`(与 `--json`、`--limit` 都互斥) |
228
227
  | `--json` | JSON 输出;**每条记录带 `mine`**(是不是自己结的,只比 uuid) |
229
228
 
230
229
  干什么:按结案时间倒序排列,每条一行,只列结案时间、负责人、标题,其他都不在表格里;要摘要 / 分类 / 标签 / 完整时间加 `--json`。不给时间参数 = 最近 3 个自然月;最多 13 个月,超了直接报错。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.43",
3
+ "version": "1.3.44",
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",
@@ -24,7 +24,7 @@
24
24
  - **「--release 与 --owner 不能同时给」**:释放不带人、交接不带释放,分开跑两次。
25
25
  - **「--owner 只收身份 uuid(形如 …),不接受名字 / 邮箱:X」**:身份参数传了名字。改传 uuid;要按显示名找人就加到查询里:`list --owner-name <片段>` / `history --owner-name <片段>`。
26
26
  - **「--owner 不能和 --mine / --pool 一起用」**:范围只能选一种。要按某个人筛就 `--owner <uuid>`,要看自己的就 `--mine`。
27
- - **「--count-only 与 --limit 不能同时用」/「--limit 必须 ≥ 1」**:计数不截断、列表才截断;`--limit 0` 没有意义。
27
+ - **「--limit 必须 ≥ 1」**:`--limit 0` 没有意义。
28
28
  - **「时间区间反了:--since(…)晚于 --until(…)」**:两端换过来。
29
29
  - **「认领目标不是 uuid:X」**:`init --claim` 抄了老台账的邮箱 / 名字键。老台账先迁到 uuid 身份;这台机器先 `init --new --name=<名字>`。
30
30
  - **「名册里没有 uuid=X」**:`init --claim` 抄了一个不在 `owners.json` 里的 uuid。不带参数跑一次 `init` 看可选用户再选。