dsh-task-ledger 0.0.114 → 0.0.116
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/dist/client.js +155 -24
- package/dist/client.js.map +1 -1
- package/dist/host/errors.d.ts +2 -2
- package/dist/host/repositories/trend.repository.d.ts +33 -0
- package/dist/host/repositories/trend.repository.js +92 -0
- package/dist/host/services/stats.service.js +6 -2
- package/dist/host/services/trend.service.d.ts +13 -0
- package/dist/host/services/trend.service.js +82 -0
- package/dist/host/types.d.ts +28 -0
- package/package.json +1 -1
- package/skill/SKILL.md +39 -1
- package/skill/package.json +1 -1
- package/skill/scripts/bin/ledger-darwin-arm64 +0 -0
- package/skill/scripts/bin/ledger-linux-amd64 +0 -0
- package/skill/scripts/bin/ledger-windows-amd64.exe +0 -0
package/dist/host/errors.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export declare class LedgerDataError extends Error {
|
|
|
19
19
|
export interface DegradedInfo {
|
|
20
20
|
readonly workspaceId: string;
|
|
21
21
|
readonly workspaceTitle: string;
|
|
22
|
-
/**
|
|
23
|
-
readonly op: "list" | "history" | "owners";
|
|
22
|
+
/** 哪个读挂了(skill 子命令;趋势走 `stats --trend`,单独一档便于区分)。 */
|
|
23
|
+
readonly op: "list" | "history" | "owners" | "trend";
|
|
24
24
|
readonly code: LedgerErrorCode;
|
|
25
25
|
/** 人话,可直接上界面(真源:脚本 stderr / records 校验文案)。 */
|
|
26
26
|
readonly message: string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LedgerWorkspace } from "../types.js";
|
|
2
|
+
/** 趋势图的窗口(周数):与首页右侧卡位一致(2026-10-17 定稿「近 8 周」)。 */
|
|
3
|
+
export declare const TREND_WEEKS = 8;
|
|
4
|
+
/** 一个人在一个周桶里的三个数(skill 口径:新增按 creator、完成 / 未结按 owner)。 */
|
|
5
|
+
export interface OwnerCounts {
|
|
6
|
+
readonly added: number;
|
|
7
|
+
readonly done: number;
|
|
8
|
+
readonly open: number;
|
|
9
|
+
}
|
|
10
|
+
/** 一个周桶;`from` / `to` 是**本机时区**的日期(含边界)。 */
|
|
11
|
+
export interface TrendBucket {
|
|
12
|
+
readonly from: string;
|
|
13
|
+
readonly to: string;
|
|
14
|
+
/** 该周新建。 */
|
|
15
|
+
readonly added: number;
|
|
16
|
+
/** 该周结案(全部人合计)。 */
|
|
17
|
+
readonly done: number;
|
|
18
|
+
/** 该周结束时仍未结案(全部人合计,含窗口前遗留)。 */
|
|
19
|
+
readonly open: number;
|
|
20
|
+
/** uuid → 按人明细(「只看我的」用;没要 / 该人该周没数据则没有这个 key)。 */
|
|
21
|
+
readonly owners?: Readonly<Record<string, OwnerCounts>>;
|
|
22
|
+
}
|
|
23
|
+
/** 单工作区的趋势。 */
|
|
24
|
+
export interface WorkspaceTrend {
|
|
25
|
+
readonly buckets: readonly TrendBucket[];
|
|
26
|
+
/** 实际有数据的第一天(早于窗口起点则等于窗口起点)。 */
|
|
27
|
+
readonly coverageFrom: string;
|
|
28
|
+
readonly coverageTo: string;
|
|
29
|
+
}
|
|
30
|
+
/** 单工作区趋势(skill `stats --trend week --last 8w --by-owner`;过 1.5s 读缓存)。 */
|
|
31
|
+
export declare function readTrend(workspace: LedgerWorkspace): Promise<WorkspaceTrend>;
|
|
32
|
+
/** `stats --trend` → {@link WorkspaceTrend}(容器级严格:缺字段即坏,不当 0)。 */
|
|
33
|
+
export declare function parseTrend(raw: unknown, where: string): WorkspaceTrend;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 趋势面取数:近 8 周的「完成 / 未结 / 新增」,**整段桶序列一次拿回**(skill `stats --trend`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不再拼现有方法:`history --count-only` 只给单窗口总数、且按**结案时间**过滤(新增取不到);
|
|
5
|
+
* 插件侧拉全量明细再聚合已被实测否决(20 万条 3s+、30MB 过进程边界)。
|
|
6
|
+
* 桶边界(周一起算 / 本机时区)与「未结 = 该周末仍未结案」的倒推算法全在 skill,
|
|
7
|
+
* 本文件只做形状校验 —— 与 stats 同一条纪律:数字真源只有 skill 一份。
|
|
8
|
+
*
|
|
9
|
+
* 形状不符一律抛 {@link LedgerDataError}(不拿 0 充数)。
|
|
10
|
+
*/
|
|
11
|
+
import { LedgerDataError } from "../errors.js";
|
|
12
|
+
import { cached, cacheKey } from "./cache.js";
|
|
13
|
+
import { runLedgerScript } from "./runner.js";
|
|
14
|
+
/** 趋势图的窗口(周数):与首页右侧卡位一致(2026-10-17 定稿「近 8 周」)。 */
|
|
15
|
+
export const TREND_WEEKS = 8;
|
|
16
|
+
const TREND_LAST = `${String(TREND_WEEKS)}w`;
|
|
17
|
+
function isPlainObject(value) {
|
|
18
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function num(source, key, where) {
|
|
21
|
+
const value = source[key];
|
|
22
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
23
|
+
throw new LedgerDataError(where, `${where}: stats --trend 输出缺少数字字段 ${key},格式可能已变`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function day(source, key, where) {
|
|
27
|
+
const value = source[key];
|
|
28
|
+
if (typeof value !== "string" || value === "")
|
|
29
|
+
throw new LedgerDataError(where, `${where}: stats --trend 输出缺少日期字段 ${key},格式可能已变`);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
/** 按人明细:没有这个字段(没要 --by-owner)返回 undefined;有就必须是合法的三数对象。 */
|
|
33
|
+
function parseOwners(raw, where) {
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (!isPlainObject(raw))
|
|
37
|
+
throw new LedgerDataError(where, `${where}: stats --trend 的 owners 不是对象,格式可能已变`);
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [uuid, cell] of Object.entries(raw)) {
|
|
40
|
+
if (!isPlainObject(cell))
|
|
41
|
+
throw new LedgerDataError(where, `${where}: stats --trend 里 ${uuid} 的按人明细不是对象`);
|
|
42
|
+
out[uuid] = {
|
|
43
|
+
added: num(cell, "added", where),
|
|
44
|
+
done: num(cell, "done", where),
|
|
45
|
+
open: num(cell, "open", where),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** 单工作区趋势(skill `stats --trend week --last 8w --by-owner`;过 1.5s 读缓存)。 */
|
|
51
|
+
export async function readTrend(workspace) {
|
|
52
|
+
// 缓存 op 仍记 "stats"(脚本子命令就是 stats),detail 里写清是哪个窗口的趋势
|
|
53
|
+
return await cached(cacheKey(workspace.id, "stats", `trend:${TREND_LAST}`), async () => {
|
|
54
|
+
// 趋势自带 JSON 输出,**不能**再传 --json(skill 那边是用法错)
|
|
55
|
+
const result = await runLedgerScript("stats",
|
|
56
|
+
// --by-owner:一次就把「全部」与「按人」两套数都拿回来 ⇒ 勾「只看我的」零请求
|
|
57
|
+
["--repo", workspace.path, "--trend", "week", "--last", TREND_LAST, "--by-owner"],
|
|
58
|
+
// 8 周窗口最坏要读 3 个历史文件,比单月读重:给到 20s 再判超时
|
|
59
|
+
{ timeoutMs: 20_000 });
|
|
60
|
+
if (!result.ok)
|
|
61
|
+
throw new Error(`${workspace.title}: ${result.error}`);
|
|
62
|
+
return parseTrend(result.data, workspace.title);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** `stats --trend` → {@link WorkspaceTrend}(容器级严格:缺字段即坏,不当 0)。 */
|
|
66
|
+
export function parseTrend(raw, where) {
|
|
67
|
+
if (!isPlainObject(raw))
|
|
68
|
+
throw new LedgerDataError(where, `${where}: stats --trend 输出不是对象,格式可能已变`);
|
|
69
|
+
const coverage = raw.coverage;
|
|
70
|
+
if (!isPlainObject(coverage))
|
|
71
|
+
throw new LedgerDataError(where, `${where}: stats --trend 输出缺 coverage,无法判断数据从哪天起有效`);
|
|
72
|
+
if (!Array.isArray(raw.buckets))
|
|
73
|
+
throw new LedgerDataError(where, `${where}: stats --trend 输出缺 buckets 数组,格式可能已变`);
|
|
74
|
+
const buckets = raw.buckets.map((item) => {
|
|
75
|
+
if (!isPlainObject(item))
|
|
76
|
+
throw new LedgerDataError(where, `${where}: stats --trend 的桶不是对象`);
|
|
77
|
+
const owners = parseOwners(item.owners, where);
|
|
78
|
+
return {
|
|
79
|
+
from: day(item, "from", where),
|
|
80
|
+
to: day(item, "to", where),
|
|
81
|
+
added: num(item, "added", where),
|
|
82
|
+
done: num(item, "done", where),
|
|
83
|
+
open: num(item, "open", where),
|
|
84
|
+
...(owners === undefined ? {} : { owners }),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
buckets,
|
|
89
|
+
coverageFrom: day(coverage, "from", where),
|
|
90
|
+
coverageTo: day(coverage, "to", where),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 数字真源全在 skill:每工作区跑 `stats --json`(在办各面)与两次
|
|
5
5
|
* `history --count-only`(本周 / 上周完成),本文件只做跨工作区求和与降级归集。
|
|
6
|
-
* 趋势图(近 8
|
|
6
|
+
* 趋势图(近 8 周)由 `trend.service.ts` 走 `stats --trend` 单独算,随首屏一起打包回来。
|
|
7
7
|
*/
|
|
8
8
|
import { codeOf, messageOf, settleWorkspaces, } from "../errors.js";
|
|
9
9
|
import { getCurrentIdentity } from "../repositories/identity.repository.js";
|
|
10
|
+
import { getBoardTrend } from "./trend.service.js";
|
|
10
11
|
import { readOwners, readTasks } from "../repositories/ledger.repository.js";
|
|
11
12
|
import { countClosed, readStats, } from "../repositories/stats.repository.js";
|
|
12
13
|
import { weekWindow } from "./dates.js";
|
|
@@ -60,10 +61,11 @@ function dedupe(rows) {
|
|
|
60
61
|
* 三条读并行(`stats` / `list` / `owners`),各自缓存;一个工作区坏只进 degraded,其余照常。
|
|
61
62
|
*/
|
|
62
63
|
export async function getBoardHome(workspaces, now = new Date()) {
|
|
63
|
-
const [statsPayload, taskScope, ownerScope] = await Promise.all([
|
|
64
|
+
const [statsPayload, taskScope, ownerScope, trend] = await Promise.all([
|
|
64
65
|
getBoardStats(workspaces, now),
|
|
65
66
|
settleWorkspaces(workspaces, "list", readTaskScope, (rows) => rows),
|
|
66
67
|
settleWorkspaces(workspaces, "owners", readOwners, (rows) => rows),
|
|
68
|
+
getBoardTrend(workspaces),
|
|
67
69
|
]);
|
|
68
70
|
const identities = {};
|
|
69
71
|
const tasks = [];
|
|
@@ -73,6 +75,7 @@ export async function getBoardHome(workspaces, now = new Date()) {
|
|
|
73
75
|
}
|
|
74
76
|
return {
|
|
75
77
|
...statsPayload,
|
|
78
|
+
trend: { buckets: trend.buckets, coverageFrom: trend.coverageFrom, coverageTo: trend.coverageTo },
|
|
76
79
|
workspaces,
|
|
77
80
|
tasks,
|
|
78
81
|
identities,
|
|
@@ -81,6 +84,7 @@ export async function getBoardHome(workspaces, now = new Date()) {
|
|
|
81
84
|
...statsPayload.degraded,
|
|
82
85
|
...taskScope.degraded,
|
|
83
86
|
...ownerScope.degraded,
|
|
87
|
+
...trend.degraded,
|
|
84
88
|
]),
|
|
85
89
|
};
|
|
86
90
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TrendBucket } from "../repositories/trend.repository.js";
|
|
2
|
+
import type { LedgerWorkspace, TrendPayload, TrendView } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 跨工作区求和:同一天起止的周桶相加。
|
|
5
|
+
* 工作区之间周界一致(同机同时区、都由 skill 的周一起算),所以直接按 `from` 归并。
|
|
6
|
+
*/
|
|
7
|
+
export declare function mergeTrend(rows: readonly {
|
|
8
|
+
buckets: readonly TrendBucket[];
|
|
9
|
+
coverageFrom: string;
|
|
10
|
+
coverageTo: string;
|
|
11
|
+
}[]): TrendView;
|
|
12
|
+
/** 趋势图数据:逐工作区读、坏的进 degraded,全挂给空桶(不抛)。 */
|
|
13
|
+
export declare function getBoardTrend(workspaces: readonly LedgerWorkspace[]): Promise<TrendPayload>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 首页趋势图的用例:跨工作区把「近 8 周」的桶**按周求和**。
|
|
3
|
+
*
|
|
4
|
+
* 数字真源全在 skill(每工作区 `stats --trend week --last 8w`),本文件只做两件事:
|
|
5
|
+
* 按 `from` 把各工作区同周的数加在一起、把读挂的工作区归进 `degraded`。
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ 这里**不抛**「全部失败」:趋势只是首页的一块图,不能因为它把 8 张卡和列表一起打成 500。
|
|
8
|
+
* 全挂时返回空桶 + inherited degraded,界面显示空态(真源口径见 errors.ts 的 settleWorkspaces 注释)。
|
|
9
|
+
*/
|
|
10
|
+
import { codeOf, messageOf } from "../errors.js";
|
|
11
|
+
import { readTrend } from "../repositories/trend.repository.js";
|
|
12
|
+
const byFrom = (a, b) => a.from < b.from ? -1 : a.from > b.from ? 1 : 0;
|
|
13
|
+
/**
|
|
14
|
+
* 跨工作区求和:同一天起止的周桶相加。
|
|
15
|
+
* 工作区之间周界一致(同机同时区、都由 skill 的周一起算),所以直接按 `from` 归并。
|
|
16
|
+
*/
|
|
17
|
+
export function mergeTrend(rows) {
|
|
18
|
+
const merged = new Map();
|
|
19
|
+
let coverageFrom = "";
|
|
20
|
+
let coverageTo = "";
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
for (const bucket of row.buckets) {
|
|
23
|
+
let current = merged.get(bucket.from);
|
|
24
|
+
if (current === undefined) {
|
|
25
|
+
current = {
|
|
26
|
+
from: bucket.from,
|
|
27
|
+
to: bucket.to,
|
|
28
|
+
added: 0,
|
|
29
|
+
done: 0,
|
|
30
|
+
open: 0,
|
|
31
|
+
owners: {},
|
|
32
|
+
};
|
|
33
|
+
merged.set(bucket.from, current);
|
|
34
|
+
}
|
|
35
|
+
// 已有对象就原地累加(同一份数据只在本函数里生成,不会被外部共享)
|
|
36
|
+
current.added += bucket.added;
|
|
37
|
+
current.done += bucket.done;
|
|
38
|
+
current.open += bucket.open;
|
|
39
|
+
for (const [uuid, cell] of Object.entries(bucket.owners ?? {})) {
|
|
40
|
+
const owner = current.owners[uuid] ?? { added: 0, done: 0, open: 0 };
|
|
41
|
+
current.owners[uuid] = {
|
|
42
|
+
added: owner.added + cell.added,
|
|
43
|
+
done: owner.done + cell.done,
|
|
44
|
+
open: owner.open + cell.open,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// 有数据的第一天取各工作区里最早的;最后一天取最晚的
|
|
49
|
+
if (row.coverageFrom !== "") {
|
|
50
|
+
if (coverageFrom === "" || row.coverageFrom < coverageFrom)
|
|
51
|
+
coverageFrom = row.coverageFrom;
|
|
52
|
+
}
|
|
53
|
+
if (row.coverageTo !== "" && row.coverageTo > coverageTo)
|
|
54
|
+
coverageTo = row.coverageTo;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
buckets: [...merged.values()].sort(byFrom),
|
|
58
|
+
coverageFrom,
|
|
59
|
+
coverageTo,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** 趋势图数据:逐工作区读、坏的进 degraded,全挂给空桶(不抛)。 */
|
|
63
|
+
export async function getBoardTrend(workspaces) {
|
|
64
|
+
const settled = await Promise.allSettled(workspaces.map((workspace) => readTrend(workspace)));
|
|
65
|
+
const rows = [];
|
|
66
|
+
const degraded = [];
|
|
67
|
+
settled.forEach((result, i) => {
|
|
68
|
+
if (result.status === "fulfilled") {
|
|
69
|
+
rows.push(result.value);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const workspace = workspaces[i];
|
|
73
|
+
degraded.push({
|
|
74
|
+
workspaceId: workspace.id,
|
|
75
|
+
workspaceTitle: workspace.title,
|
|
76
|
+
op: "trend",
|
|
77
|
+
code: codeOf(result.reason),
|
|
78
|
+
message: messageOf(result.reason),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return { ...mergeTrend(rows), degraded };
|
|
82
|
+
}
|
package/dist/host/types.d.ts
CHANGED
|
@@ -114,6 +114,32 @@ export interface StatsPayload {
|
|
|
114
114
|
readonly stats: BoardStatsView;
|
|
115
115
|
readonly degraded: readonly DegradedInfo[];
|
|
116
116
|
}
|
|
117
|
+
/** 一个人在一个周桶里的三个数(新增按 creator、完成 / 未结按 owner,都是 skill 的口径)。 */
|
|
118
|
+
export interface TrendOwnerCounts {
|
|
119
|
+
readonly added: number;
|
|
120
|
+
readonly done: number;
|
|
121
|
+
readonly open: number;
|
|
122
|
+
}
|
|
123
|
+
/** 趋势图的一根周桶(数字全由 skill `stats --trend --by-owner` 给,插件只跨工作区相加)。 */
|
|
124
|
+
export interface TrendBucketView {
|
|
125
|
+
readonly from: string;
|
|
126
|
+
readonly to: string;
|
|
127
|
+
readonly added: number;
|
|
128
|
+
readonly done: number;
|
|
129
|
+
readonly open: number;
|
|
130
|
+
/** uuid → 按人明细;「只看我的」直接取自己那条,勾选零请求。 */
|
|
131
|
+
readonly owners: Readonly<Record<string, TrendOwnerCounts>>;
|
|
132
|
+
}
|
|
133
|
+
/** 趋势图数据:近 8 周桶序列 + 实际有数据的第一天。 */
|
|
134
|
+
export interface TrendView {
|
|
135
|
+
readonly buckets: readonly TrendBucketView[];
|
|
136
|
+
readonly coverageFrom: string;
|
|
137
|
+
readonly coverageTo: string;
|
|
138
|
+
}
|
|
139
|
+
/** 趋势数据 + 降级(自带一条,免得拖垮整个快照)。 */
|
|
140
|
+
export interface TrendPayload extends TrendView {
|
|
141
|
+
readonly degraded: readonly DegradedInfo[];
|
|
142
|
+
}
|
|
117
143
|
/** 一个工作区的花名册(skill `owners --json`:含当前没任务的人)。 */
|
|
118
144
|
export interface WorkspaceOwners {
|
|
119
145
|
readonly workspaceId: string;
|
|
@@ -130,6 +156,8 @@ export interface WorkspaceOwners {
|
|
|
130
156
|
* 前端只筛不判。
|
|
131
157
|
*/
|
|
132
158
|
export interface BoardHomePayload extends StatsPayload {
|
|
159
|
+
/** 首页右侧趋势图(近 8 周)。 */
|
|
160
|
+
readonly trend: TrendView;
|
|
133
161
|
readonly workspaces: readonly LedgerWorkspace[];
|
|
134
162
|
readonly tasks: readonly TaskRow[];
|
|
135
163
|
readonly identities: Readonly<Record<string, string | null>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.116",
|
|
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
|
@@ -152,6 +152,44 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
152
152
|
|
|
153
153
|
举例:「我这有几条卡住了」→ `stats --mine`;「整体积压多严重」→ `stats --json`。
|
|
154
154
|
|
|
155
|
+
#### `stats --trend` — 按周期分桶的趋势
|
|
156
|
+
|
|
157
|
+
| 参数 | 含义 |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `--trend <粒度>` | `day` / `week` / `month` / `quarter` / `year`(桶边界:日 00:00、**周一**、自然月、自然季、自然年,全按本机时区) |
|
|
160
|
+
| `--last <N><d\|w\|m\|y>` | 最近 N 个日 / 周 / 月 / 年(与 `--since` / `--until` **二选一**;默认 `8w`) |
|
|
161
|
+
| `--since` / `--until <日期>` | 显式区间(与 `list` 同样的本地时区口径);首尾桶**按区间裁剪** —— 从周三起算,第一桶就只有周三起的数据 |
|
|
162
|
+
| `--mine` / `--owner <uuid>[,<uuid>]` | 只看自己 / 某几个人(`--owner` 可给多个 uuid,与 `--mine` 互斥) |
|
|
163
|
+
| `--by-owner` | 每个桶额外带按人明细(默认不返回,只给总数) |
|
|
164
|
+
|
|
165
|
+
干什么:一次遍历返回**整段桶序列**(不按桶多次调用),每桶三个数 —— `added`(该桶内新建)、`done`(该桶内结案)、`open`(该桶**结束时**仍未结案)。
|
|
166
|
+
|
|
167
|
+
口径:`added` 按 `created`、`done` 按 `finished`;`open` 由「当前在办 + 今天往前倒推」得出,天然含窗口之前遗留的基线。按人明细里 `added` 按 `creator`、`done` / `open` 按 `owner`(待认领不算任何人)。
|
|
168
|
+
|
|
169
|
+
按人的 `open` 只算**你名下**的任务,倒推时同时减去「你在那天之后新建的」(按 owner 计,与展示用的 creator 无关)—— 否则窗口之后才建、早已结案的任务会被当成那天就在办,越靠前的桶虚高得越离谱。负责人转交过的任务按**今天**的负责人归账。
|
|
170
|
+
|
|
171
|
+
**只出 JSON**(表格由界面渲染;给 `--json` 是用法错):
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"granularity": "week",
|
|
176
|
+
"coverage": { "from": "2026-09-01", "to": "2026-10-29" },
|
|
177
|
+
"buckets": [
|
|
178
|
+
{ "from": "2026-09-01", "to": "2026-09-06", "added": 3, "done": 5, "open": 12,
|
|
179
|
+
"owners": { "<uuid>": { "name": "某人", "added": 1, "done": 2, "open": 4 } } }
|
|
180
|
+
],
|
|
181
|
+
"warnings": []
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `from` / `to` 是**本地日期**(含边界);展示标签(`第 40 周` / `2026-10` …)由前端按粒度自己生成,脚本不回任何标签。
|
|
186
|
+
- `coverage.from` 是实际有数据的第一天;窗口里早于它的部分没有数据(前端显示「数据不足」,不画 0)。
|
|
187
|
+
- `owners` 只有加 `--by-owner` 才出现;人名实时取自名册,不进缓存。
|
|
188
|
+
- 上限按**时间跨度**定,不按读几个文件定:一次最多 10 年(3660 天);`day` 粒度另有 365 天上限(一天一桶没人看十年)。超了直接报错,不返回半截数据。缓存文件不计入任何上限(命中的月份根本不读源文件)。
|
|
189
|
+
- 已过完的本地月会落一份本机缓存(`~/.taskledger/cache/trend/`,不进 git、随时可删);当月永远实时算。缓存不改变**能查多久**(历史文件从不清理,10 年就是 120 个文件在盘上),只决定**查得多快**。
|
|
190
|
+
|
|
191
|
+
举例:「最近 8 周每周完成多少」→ `stats --trend week`;「今年每月按人看」→ `stats --trend month --last 12m --by-owner`。
|
|
192
|
+
|
|
155
193
|
### owners — 花名册
|
|
156
194
|
|
|
157
195
|
| 参数 | 含义 |
|
|
@@ -243,7 +281,7 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
243
281
|
|
|
244
282
|
- 定位项目:`--repo <工作区根>`(**直接含有 `ledger/` 的那个目录**)给了就校验后用;不给就从当前目录逐级向上找。`ledger/` 本身、以及它下面的任何子目录都不是根,指过去会被直接拒绝(判据只看位置,不看目录里有哪些文件 —— 项目根自带 `pool.json`、`history/`、`worklog/` 都不影响)。
|
|
245
283
|
- `--no-sync`(放在子命令**前**):用户明确要求「先别同步」时才用;之后自己 `git add ledger && commit && push`。
|
|
246
|
-
- `--json` 只对只读命令(`list` / `stats` / `owners` / `show` / `history` / `root` / `whoami` / `validate`)有意义;写命令不接收 `--json
|
|
284
|
+
- `--json` 只对只读命令(`list` / `stats` / `owners` / `show` / `history` / `root` / `whoami` / `validate`)有意义;写命令不接收 `--json`,传了是用法错。`stats --trend` 自带 JSON,不用也不接受 `--json`。
|
|
247
285
|
- **给界面喂数**:要看在办用 `list`、要各面计数用 `stats`、要人选列表用 `owners`、要判断某个目录有没有台账用 `root` —— 都是只读、都不改台账;多个 / 跨工作区自己扇出,脚本一次只服务一个工作区。
|
|
248
286
|
- **时间**:存盘一律 UTC,脚本对外**一律给本机时区**(人话表格与 `--json` 都是),拿到直接显示,不要再转一次;`--since` / `--until` / `--created` / `--finished` 也按本机时区给,脚本内部转 UTC 比较。过程文档正文里的时间是原文,脚本不碰。
|
|
249
287
|
- 命令回执只用来确认生效;任务状态以 `show` / `list` 为准。
|
package/skill/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.42",
|
|
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",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|