dsh-task-ledger 0.0.8 → 0.0.9
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 +189 -70
- package/dist/client.js.map +1 -1
- package/dist/host/board.d.ts +38 -17
- package/dist/host/board.js +140 -57
- package/dist/host/routes.js +4 -5
- package/package.json +1 -1
package/dist/host/board.d.ts
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import type { HistoryRecord, LedgerFilter, LedgerWorkspace, TaskRow } from './types.js';
|
|
2
2
|
/** 停滞判定阈值:doing / blocked 且 created 至今超过这么多天(2026-10-17 拍板,显示级)。 */
|
|
3
3
|
export declare const STALE_DAYS = 14;
|
|
4
|
-
/** 趋势图桶数:近 8 周(2026-10-17
|
|
4
|
+
/** 趋势图桶数:近 8 周(2026-10-17 定稿)。 */
|
|
5
5
|
export declare const WEEKLY_WEEKS = 8;
|
|
6
|
-
/**
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
readonly
|
|
10
|
-
/**
|
|
11
|
-
readonly
|
|
6
|
+
/** 一个自然周期内的两个计数(核心永远是「完成」,新增只作对照)。 */
|
|
7
|
+
export interface PeriodCounts {
|
|
8
|
+
readonly done: number;
|
|
9
|
+
readonly created: number;
|
|
10
|
+
/** 周期起(YYYY-MM-DD,含)。 */
|
|
11
|
+
readonly from: string;
|
|
12
|
+
/** 周期止(YYYY-MM-DD,含)。 */
|
|
13
|
+
readonly to: string;
|
|
14
|
+
}
|
|
15
|
+
/** 统计台四个自然周期(用户 2026-10-17 定稿:本周 / 上周 / 本月 / 上月,不用 7/30 天)。 */
|
|
16
|
+
export interface TrendPeriods {
|
|
17
|
+
readonly thisWeek: PeriodCounts;
|
|
18
|
+
readonly lastWeek: PeriodCounts;
|
|
19
|
+
readonly thisMonth: PeriodCounts;
|
|
20
|
+
readonly lastMonth: PeriodCounts;
|
|
12
21
|
}
|
|
22
|
+
/** 趋势图一桶(一周):完成 + 新增。 */
|
|
13
23
|
export interface WeeklyBucket {
|
|
24
|
+
/** 该周周一(YYYY-MM-DD)。 */
|
|
14
25
|
readonly week: string;
|
|
15
|
-
readonly
|
|
26
|
+
readonly done: number;
|
|
27
|
+
readonly created: number;
|
|
28
|
+
}
|
|
29
|
+
/** 趋势图数据按范围各一份(切换零请求)。 */
|
|
30
|
+
export interface ClosedSummary {
|
|
31
|
+
readonly weekly: {
|
|
32
|
+
readonly all: readonly WeeklyBucket[];
|
|
33
|
+
readonly mine: readonly WeeklyBucket[];
|
|
34
|
+
};
|
|
16
35
|
}
|
|
17
36
|
/** 首页统计卡的六个面(阈值 / 天数全在 host 算,前端只渲染——单一真源)。 */
|
|
18
37
|
export interface BoardStats {
|
|
@@ -32,8 +51,6 @@ export interface BoardStats {
|
|
|
32
51
|
readonly staleDays: number;
|
|
33
52
|
/** 最老一条待认领躺了多少天(没有待认领 = null)。 */
|
|
34
53
|
readonly oldestOpenDays: number | null;
|
|
35
|
-
/** 卡住最久的 blocked 多少天(没有 blocked = null)。 */
|
|
36
|
-
readonly longestBlockedDays: number | null;
|
|
37
54
|
}
|
|
38
55
|
/** 列表行附加工资字段(host 算,前端不判阈值)。 */
|
|
39
56
|
export interface BoardTask extends TaskRow {
|
|
@@ -55,6 +72,12 @@ export interface BoardSnapshot {
|
|
|
55
72
|
*/
|
|
56
73
|
readonly identities: Readonly<Record<string, string | null>>;
|
|
57
74
|
}
|
|
75
|
+
/** 首页一次 `/board` 的全部聚合(**每个工作区只读一次** list + history + git 身份)。 */
|
|
76
|
+
export interface HomeAggregate {
|
|
77
|
+
readonly snapshot: BoardSnapshot;
|
|
78
|
+
readonly closed: ClosedSummary;
|
|
79
|
+
readonly trend: TrendPeriods;
|
|
80
|
+
}
|
|
58
81
|
/**
|
|
59
82
|
* 当前 git 身份(cwd = 该工作区)——「我的待办」判定源。
|
|
60
83
|
* 与 agent 在会话里跑 skill 时的取值环境一致;取不到回 null(非 git 目录 / 没配身份),
|
|
@@ -62,15 +85,13 @@ export interface BoardSnapshot {
|
|
|
62
85
|
*/
|
|
63
86
|
export declare function getCurrentIdentity(workspace: LedgerWorkspace): Promise<string | null>;
|
|
64
87
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
88
|
+
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
89
|
+
* + 四个自然周期计数。**每个工作区只读一次**(list + history + git 身份),
|
|
90
|
+
* 避免「快照读一遍、统计再读一遍」把 5 万行级工作区的 history 解析翻倍。
|
|
67
91
|
*/
|
|
92
|
+
export declare function getHomeAggregate(workspaces: readonly LedgerWorkspace[]): Promise<HomeAggregate>;
|
|
93
|
+
/** 只拿在办快照(薄封装:测试 / 其它调用方用;首页走 {@link getHomeAggregate} 一次读全)。 */
|
|
68
94
|
export declare function getBoardSnapshot(workspaces: readonly LedgerWorkspace[]): Promise<BoardSnapshot>;
|
|
69
|
-
/**
|
|
70
|
-
* 完成类统计 + 近 8 周趋势:读一次 13 个月窗口的 history,按**结案时间**落周桶。
|
|
71
|
-
* 任一工作区失败即抛(聚合数部分失败 = 错数,不兜)。
|
|
72
|
-
*/
|
|
73
|
-
export declare function getClosedSummary(workspaces: readonly LedgerWorkspace[]): Promise<ClosedSummary>;
|
|
74
95
|
/**
|
|
75
96
|
* 已结案列表:默认近 13 个月 + limit 500,跨工作区合并后按创建时间倒序。
|
|
76
97
|
* shortcut: 分页用「加大 limit 重取」的笨办法(skill 脚本无 offset);
|
package/dist/host/board.js
CHANGED
|
@@ -6,21 +6,57 @@ const RECENT_MONTHS = 13;
|
|
|
6
6
|
const DEFAULT_HISTORY_LIMIT = 500;
|
|
7
7
|
/** 停滞判定阈值:doing / blocked 且 created 至今超过这么多天(2026-10-17 拍板,显示级)。 */
|
|
8
8
|
export const STALE_DAYS = 14;
|
|
9
|
-
/** 趋势图桶数:近 8 周(2026-10-17
|
|
9
|
+
/** 趋势图桶数:近 8 周(2026-10-17 定稿)。 */
|
|
10
10
|
export const WEEKLY_WEEKS = 8;
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
/* ── 日期工具(本机时区;skill 脚本同口径:周一为一周之始)───────────────── */
|
|
12
|
+
function pad(value) {
|
|
13
|
+
return String(value).padStart(2, '0');
|
|
14
|
+
}
|
|
15
|
+
/** Date → `YYYY-MM-DD`。 */
|
|
16
|
+
function ymd(date) {
|
|
17
17
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
18
18
|
}
|
|
19
|
+
/** 时刻串(`YYYY-MM-DD HH:mm:ss` / `YYYY-MM-DD`)→ 日期部分;解析不了回 null。 */
|
|
20
|
+
function dayOf(stamp) {
|
|
21
|
+
if (stamp === null || stamp === undefined || stamp === '')
|
|
22
|
+
return null;
|
|
23
|
+
const day = String(stamp).slice(0, 10);
|
|
24
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(day) ? day : null;
|
|
25
|
+
}
|
|
26
|
+
/** 某日期所在周的周一(00:00)。 */
|
|
27
|
+
function mondayOf(day) {
|
|
28
|
+
const date = new Date(`${day}T00:00:00`);
|
|
29
|
+
date.setDate(date.getDate() - ((date.getDay() + 6) % 7));
|
|
30
|
+
return date;
|
|
31
|
+
}
|
|
32
|
+
/** 第 offset 周的起止(offset 0 = 本周,-1 = 上周;返回日期串,含边界)。
|
|
33
|
+
* ⚠️ 本周只到今天(周还没结束),上周才是完整周一~周日——否则「本周」会把明天之后的
|
|
34
|
+
* 日子也画进区间,tooltip 里的截止日撒谎。 */
|
|
35
|
+
function weekWindow(offset, now) {
|
|
36
|
+
const monday = mondayOf(ymd(now));
|
|
37
|
+
monday.setDate(monday.getDate() + offset * 7);
|
|
38
|
+
const sunday = new Date(monday);
|
|
39
|
+
sunday.setDate(sunday.getDate() + 6);
|
|
40
|
+
const last = offset === 0 ? now : sunday;
|
|
41
|
+
return { from: ymd(monday), to: ymd(last < sunday ? last : sunday) };
|
|
42
|
+
}
|
|
43
|
+
/** 第 offset 月的起止(offset 0 = 本月,-1 = 上月;含边界)。
|
|
44
|
+
* 同 {@link weekWindow}:本月只到今天,上月才是完整自然月。 */
|
|
45
|
+
function monthWindow(offset, now) {
|
|
46
|
+
const first = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
47
|
+
const last = new Date(now.getFullYear(), now.getMonth() + offset + 1, 0);
|
|
48
|
+
const end = offset === 0 ? now : last;
|
|
49
|
+
return { from: ymd(first), to: ymd(end < last ? end : last) };
|
|
50
|
+
}
|
|
51
|
+
/** 日期是否落在 [from, to](含边界,字符串比较即可——ISO 字典序即时间序)。 */
|
|
52
|
+
function inWindow(day, window) {
|
|
53
|
+
return day !== null && day >= window.from && day <= window.to;
|
|
54
|
+
}
|
|
19
55
|
/** 「YYYY-MM-DD HH:mm:ss」→ 距今天数(解析不了 / 未来都按 0;不编造)。 */
|
|
20
56
|
function daysSince(stamp, now) {
|
|
21
57
|
if (stamp === null || stamp === undefined || stamp === '')
|
|
22
58
|
return null;
|
|
23
|
-
const parsed = new Date(stamp.replace(' ', 'T'));
|
|
59
|
+
const parsed = new Date(String(stamp).replace(' ', 'T'));
|
|
24
60
|
if (Number.isNaN(parsed.getTime()))
|
|
25
61
|
return null;
|
|
26
62
|
const days = Math.floor((now.getTime() - parsed.getTime()) / 86_400_000);
|
|
@@ -43,36 +79,41 @@ export function getCurrentIdentity(workspace) {
|
|
|
43
79
|
});
|
|
44
80
|
});
|
|
45
81
|
}
|
|
82
|
+
/** 一个工作区读全(list + history + git 身份)——首页所有数字的共同原料。 */
|
|
83
|
+
async function readWorkspace(workspace) {
|
|
84
|
+
const [tasks, history, identity] = await Promise.all([
|
|
85
|
+
readTasks(workspace),
|
|
86
|
+
readHistory(workspace, { months: RECENT_MONTHS }),
|
|
87
|
+
getCurrentIdentity(workspace),
|
|
88
|
+
]);
|
|
89
|
+
return { workspace, tasks, history, identity };
|
|
90
|
+
}
|
|
91
|
+
/** 计数一批记录 / 任务(按范围过滤 owner;`mine` 时 identity 为 null 的一律不计)。 */
|
|
92
|
+
function countPeriod(entries, window) {
|
|
93
|
+
return entries.filter((entry) => inWindow(entry.day, window)).length;
|
|
94
|
+
}
|
|
46
95
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
96
|
+
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
97
|
+
* + 四个自然周期计数。**每个工作区只读一次**(list + history + git 身份),
|
|
98
|
+
* 避免「快照读一遍、统计再读一遍」把 5 万行级工作区的 history 解析翻倍。
|
|
49
99
|
*/
|
|
50
|
-
export async function
|
|
100
|
+
export async function getHomeAggregate(workspaces) {
|
|
51
101
|
const now = new Date();
|
|
52
|
-
const perWorkspace = await Promise.all(workspaces.map(
|
|
53
|
-
|
|
54
|
-
tasks: await readTasks(workspace),
|
|
55
|
-
identity: await getCurrentIdentity(workspace),
|
|
56
|
-
})));
|
|
102
|
+
const perWorkspace = await Promise.all(workspaces.map(readWorkspace));
|
|
103
|
+
// ── 快照(在办路径,与历史量无关)────────────────────────────────────
|
|
57
104
|
const withMeta = perWorkspace.flatMap((entry) => entry.tasks.map((task) => {
|
|
58
105
|
const working = task.status === 'doing' || task.status === 'blocked';
|
|
59
106
|
const ageDays = working ? daysSince(task.started ?? task.created, now) : null;
|
|
60
107
|
return { ...task, ageDays, stale: ageDays !== null && ageDays > STALE_DAYS };
|
|
61
108
|
}));
|
|
62
|
-
const myActive = perWorkspace.reduce((sum, entry) => sum + entry.tasks.filter((task) => task.owner === entry.identity && (task.status === 'doing' || task.status === 'blocked')).length, 0);
|
|
63
|
-
const myBlocked = perWorkspace.reduce((sum, entry) => sum + entry.tasks.filter((task) => task.owner === entry.identity && task.status === 'blocked').length, 0);
|
|
64
109
|
const openTasks = withMeta.filter((task) => task.status === 'open');
|
|
65
110
|
const blockedTasks = withMeta.filter((task) => task.status === 'blocked');
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
: openTasks.reduce((max, task) => Math.max(max, daysSince(task.created, now) ?? 0), 0);
|
|
69
|
-
const longestBlockedDays = blockedTasks.length === 0
|
|
70
|
-
? null
|
|
71
|
-
: blockedTasks.reduce((max, task) => Math.max(max, task.ageDays ?? 0), 0);
|
|
111
|
+
const myActive = perWorkspace.reduce((sum, entry) => sum + entry.tasks.filter((task) => task.owner === entry.identity && (task.status === 'doing' || task.status === 'blocked')).length, 0);
|
|
112
|
+
const myBlocked = perWorkspace.reduce((sum, entry) => sum + entry.tasks.filter((task) => task.owner === entry.identity && task.status === 'blocked').length, 0);
|
|
72
113
|
const identities = {};
|
|
73
114
|
for (const entry of perWorkspace)
|
|
74
115
|
identities[entry.workspace.id] = entry.identity;
|
|
75
|
-
|
|
116
|
+
const snapshot = {
|
|
76
117
|
at: now.toISOString(),
|
|
77
118
|
workspaces,
|
|
78
119
|
tasks: withMeta,
|
|
@@ -84,45 +125,87 @@ export async function getBoardSnapshot(workspaces) {
|
|
|
84
125
|
blocked: blockedTasks.length,
|
|
85
126
|
stale: withMeta.filter((task) => task.stale).length,
|
|
86
127
|
staleDays: STALE_DAYS,
|
|
87
|
-
oldestOpenDays
|
|
88
|
-
|
|
128
|
+
oldestOpenDays: openTasks.length === 0
|
|
129
|
+
? null
|
|
130
|
+
: openTasks.reduce((max, task) => Math.max(max, daysSince(task.created, now) ?? 0), 0),
|
|
89
131
|
},
|
|
90
132
|
identities,
|
|
91
133
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const value = record.time;
|
|
96
|
-
if (value === undefined || value === null || value === '')
|
|
97
|
-
return null;
|
|
98
|
-
return String(value).slice(0, 10);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 完成类统计 + 近 8 周趋势:读一次 13 个月窗口的 history,按**结案时间**落周桶。
|
|
102
|
-
* 任一工作区失败即抛(聚合数部分失败 = 错数,不兜)。
|
|
103
|
-
*/
|
|
104
|
-
export async function getClosedSummary(workspaces) {
|
|
105
|
-
const now = new Date();
|
|
106
|
-
const thisMonday = mondayDateString(now);
|
|
134
|
+
// ── 趋势:近 8 周(完成按结案时间、新增按创建时间)× 所有 / 我的 ────────
|
|
135
|
+
// 新增必须**合并两个来源**:已结案的任务被 close 移出 progress.json,只在 history 里;
|
|
136
|
+
// 在办的只在 progress.json 里。两者不相交(close 是移动),合并即全集、不会重复计。
|
|
107
137
|
const weeks = [];
|
|
108
|
-
for (let offset = WEEKLY_WEEKS - 1; offset >= 0; offset -= 1)
|
|
109
|
-
weeks.push(
|
|
110
|
-
}
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const date = closedDateOf(record);
|
|
115
|
-
if (date === null)
|
|
116
|
-
continue;
|
|
117
|
-
const monday = mondayDateString(new Date(`${date}T00:00:00`));
|
|
118
|
-
const bucket = counts.get(monday);
|
|
138
|
+
for (let offset = WEEKLY_WEEKS - 1; offset >= 0; offset -= 1)
|
|
139
|
+
weeks.push(weekWindow(-offset, now).from);
|
|
140
|
+
const bucketsAll = new Map(weeks.map((week) => [week, { done: 0, created: 0 }]));
|
|
141
|
+
const bucketsMine = new Map(weeks.map((week) => [week, { done: 0, created: 0 }]));
|
|
142
|
+
const bump = (map, week, field) => {
|
|
143
|
+
const bucket = map.get(week);
|
|
119
144
|
if (bucket !== undefined)
|
|
120
|
-
|
|
145
|
+
map.set(week, { ...bucket, [field]: bucket[field] + 1 });
|
|
146
|
+
};
|
|
147
|
+
for (const entry of perWorkspace) {
|
|
148
|
+
for (const task of entry.tasks) {
|
|
149
|
+
const created = dayOf(task.created);
|
|
150
|
+
if (created === null)
|
|
151
|
+
continue;
|
|
152
|
+
const week = ymd(mondayOf(created));
|
|
153
|
+
bump(bucketsAll, week, 'created');
|
|
154
|
+
if (entry.identity !== null && task.owner === entry.identity)
|
|
155
|
+
bump(bucketsMine, week, 'created');
|
|
156
|
+
}
|
|
157
|
+
for (const record of entry.history) {
|
|
158
|
+
const closed = dayOf(record.time);
|
|
159
|
+
if (closed !== null) {
|
|
160
|
+
const week = ymd(mondayOf(closed));
|
|
161
|
+
bump(bucketsAll, week, 'done');
|
|
162
|
+
if (entry.identity !== null && record.owner === entry.identity)
|
|
163
|
+
bump(bucketsMine, week, 'done');
|
|
164
|
+
}
|
|
165
|
+
const created = dayOf(record.created);
|
|
166
|
+
if (created !== null) {
|
|
167
|
+
const week = ymd(mondayOf(created));
|
|
168
|
+
bump(bucketsAll, week, 'created');
|
|
169
|
+
if (entry.identity !== null && record.owner === entry.identity)
|
|
170
|
+
bump(bucketsMine, week, 'created');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
121
173
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
174
|
+
const closed = {
|
|
175
|
+
weekly: {
|
|
176
|
+
all: weeks.map((week) => ({ week, ...bucketsAll.get(week) ?? { done: 0, created: 0 } })),
|
|
177
|
+
mine: weeks.map((week) => ({ week, ...bucketsMine.get(week) ?? { done: 0, created: 0 } })),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
// ── 统计台:四个自然周期(只算「全部工作区」范围;图表切换不带动卡片)────
|
|
181
|
+
const windows = {
|
|
182
|
+
thisWeek: weekWindow(0, now),
|
|
183
|
+
lastWeek: weekWindow(-1, now),
|
|
184
|
+
thisMonth: monthWindow(0, now),
|
|
185
|
+
lastMonth: monthWindow(-1, now),
|
|
125
186
|
};
|
|
187
|
+
const doneEntries = perWorkspace.flatMap((entry) => entry.history.map((record) => ({ day: dayOf(record.time), owner: record.owner ?? null })));
|
|
188
|
+
const createdEntries = perWorkspace.flatMap((entry) => [
|
|
189
|
+
...entry.tasks.map((task) => ({ day: dayOf(task.created), owner: task.owner ?? null })),
|
|
190
|
+
...entry.history.map((record) => ({ day: dayOf(record.created), owner: record.owner ?? null })),
|
|
191
|
+
]);
|
|
192
|
+
const period = (window) => ({
|
|
193
|
+
done: countPeriod(doneEntries, window),
|
|
194
|
+
created: countPeriod(createdEntries, window),
|
|
195
|
+
from: window.from,
|
|
196
|
+
to: window.to,
|
|
197
|
+
});
|
|
198
|
+
const trend = {
|
|
199
|
+
thisWeek: period(windows.thisWeek),
|
|
200
|
+
lastWeek: period(windows.lastWeek),
|
|
201
|
+
thisMonth: period(windows.thisMonth),
|
|
202
|
+
lastMonth: period(windows.lastMonth),
|
|
203
|
+
};
|
|
204
|
+
return { snapshot, closed, trend };
|
|
205
|
+
}
|
|
206
|
+
/** 只拿在办快照(薄封装:测试 / 其它调用方用;首页走 {@link getHomeAggregate} 一次读全)。 */
|
|
207
|
+
export async function getBoardSnapshot(workspaces) {
|
|
208
|
+
return (await getHomeAggregate(workspaces)).snapshot;
|
|
126
209
|
}
|
|
127
210
|
/**
|
|
128
211
|
* 已结案列表:默认近 13 个月 + limit 500,跨工作区合并后按创建时间倒序。
|
package/dist/host/routes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getHomeAggregate, getFilterOptions, queryClosed } from './board.js';
|
|
2
2
|
import { getTask, readTasks, readWorklog } from './ledger-read.js';
|
|
3
3
|
import { resolveWorkspaces } from './workspace.js';
|
|
4
4
|
/** API 前缀(唯一真源就在这一处)。 */
|
|
@@ -57,15 +57,14 @@ export function registerRoutes(webServer, deps) {
|
|
|
57
57
|
path: `${API_PREFIX}/workspaces`,
|
|
58
58
|
handler: (_req, res) => { sendJson(res, 200, { workspaces: workspacesOf(deps) }); },
|
|
59
59
|
});
|
|
60
|
-
//
|
|
60
|
+
// 首页一次拿全:在办快照 + 近 8 周趋势 + 四个自然周期计数(host 每个工作区只读一次)。
|
|
61
61
|
webServer.register({
|
|
62
62
|
kind: 'exact',
|
|
63
63
|
path: `${API_PREFIX}/board`,
|
|
64
64
|
handler: async (_req, res) => {
|
|
65
65
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
sendJson(res, 200, { ...snapshot, closed });
|
|
66
|
+
const home = await getHomeAggregate(workspacesOf(deps));
|
|
67
|
+
sendJson(res, 200, { ...home.snapshot, closed: home.closed, trend: home.trend });
|
|
69
68
|
}
|
|
70
69
|
catch (error) {
|
|
71
70
|
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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",
|