dsh-task-ledger 0.0.113 → 0.0.114
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 +50739 -1529
- package/dist/client.js.map +1 -1
- package/dist/host/errors.d.ts +2 -2
- package/dist/host/repositories/cache.d.ts +2 -3
- package/dist/host/repositories/cache.js +1 -2
- package/dist/host/repositories/identity.repository.d.ts +8 -2
- package/dist/host/repositories/identity.repository.js +9 -29
- package/dist/host/repositories/ledger.repository.d.ts +12 -22
- package/dist/host/repositories/ledger.repository.js +39 -58
- package/dist/host/repositories/records.d.ts +1 -11
- package/dist/host/repositories/records.js +16 -42
- package/dist/host/repositories/runner.d.ts +0 -30
- package/dist/host/repositories/runner.js +0 -23
- package/dist/host/repositories/stats.repository.d.ts +36 -0
- package/dist/host/repositories/stats.repository.js +86 -0
- package/dist/host/routes.d.ts +4 -25
- package/dist/host/routes.js +7 -384
- package/dist/host/services/dates.d.ts +3 -24
- package/dist/host/services/dates.js +3 -63
- package/dist/host/services/stats.service.d.ts +7 -0
- package/dist/host/services/stats.service.js +134 -0
- package/dist/host/skill/registration.d.ts +0 -3
- package/dist/host/skill/registration.js +0 -3
- package/dist/host/types.d.ts +70 -126
- package/dist/host/workspace.d.ts +3 -4
- package/dist/host/workspace.js +5 -5
- package/dist/index.js +2 -33
- package/package.json +2 -2
- package/skill/SKILL.md +1 -0
- package/dist/host/board.d.ts +0 -20
- package/dist/host/board.js +0 -29
- package/dist/host/services/history.service.d.ts +0 -27
- package/dist/host/services/history.service.js +0 -72
- package/dist/host/services/home.service.d.ts +0 -23
- package/dist/host/services/home.service.js +0 -91
- package/dist/host/services/ledger-write.service.d.ts +0 -10
- package/dist/host/services/ledger-write.service.js +0 -32
- package/dist/host/services/session.service.d.ts +0 -110
- package/dist/host/services/session.service.js +0 -155
- package/dist/host/services/trend.service.d.ts +0 -36
- package/dist/host/services/trend.service.js +0 -162
- package/dist/host/services/uuid.d.ts +0 -11
- package/dist/host/services/uuid.js +0 -15
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8 个统计卡的用例(首屏唯一端点 `/board/stats`)。
|
|
3
|
+
*
|
|
4
|
+
* 数字真源全在 skill:每工作区跑 `stats --json`(在办各面)与两次
|
|
5
|
+
* `history --count-only`(本周 / 上周完成),本文件只做跨工作区求和与降级归集。
|
|
6
|
+
* 趋势图(近 8 周)没有 skill 方法,本轮不提供数据。
|
|
7
|
+
*/
|
|
8
|
+
import { codeOf, messageOf, settleWorkspaces, } from "../errors.js";
|
|
9
|
+
import { getCurrentIdentity } from "../repositories/identity.repository.js";
|
|
10
|
+
import { readOwners, readTasks } from "../repositories/ledger.repository.js";
|
|
11
|
+
import { countClosed, readStats, } from "../repositories/stats.repository.js";
|
|
12
|
+
import { weekWindow } from "./dates.js";
|
|
13
|
+
/**
|
|
14
|
+
* 一个自然周的结案数(跨工作区求和)。
|
|
15
|
+
* 部分工作区失败 ⇒ 数字偏小,degraded 会带出去(界面标红,不装没看见)。
|
|
16
|
+
*/
|
|
17
|
+
async function countWeek(workspaces, window) {
|
|
18
|
+
const settled = await Promise.allSettled(workspaces.map((workspace) => countClosed(workspace, window)));
|
|
19
|
+
let done = 0;
|
|
20
|
+
let ok = 0;
|
|
21
|
+
const degraded = [];
|
|
22
|
+
settled.forEach((result, i) => {
|
|
23
|
+
if (result.status === "fulfilled") {
|
|
24
|
+
done += result.value.count;
|
|
25
|
+
ok += 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const workspace = workspaces[i];
|
|
29
|
+
degraded.push({
|
|
30
|
+
workspaceId: workspace.id,
|
|
31
|
+
workspaceTitle: workspace.title,
|
|
32
|
+
op: "history",
|
|
33
|
+
code: codeOf(result.reason),
|
|
34
|
+
message: messageOf(result.reason),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return { done: ok === 0 && workspaces.length > 0 ? null : done, degraded };
|
|
38
|
+
}
|
|
39
|
+
/** 单工作区在办列表 + 本机身份(都经 skill:`list --json` / `whoami --json`)。 */
|
|
40
|
+
async function readTaskScope(workspace) {
|
|
41
|
+
const [tasks, identity] = await Promise.all([
|
|
42
|
+
readTasks(workspace),
|
|
43
|
+
getCurrentIdentity(workspace),
|
|
44
|
+
]);
|
|
45
|
+
return { workspace, tasks, identity };
|
|
46
|
+
}
|
|
47
|
+
/** 降级条目去重:stats 与 list 都挂时同一个工作区会报两遍。 */
|
|
48
|
+
function dedupe(rows) {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
return rows.filter((row) => {
|
|
51
|
+
const key = `${row.workspaceId}\u0001${row.code}\u0001${row.message}`;
|
|
52
|
+
if (seen.has(key))
|
|
53
|
+
return false;
|
|
54
|
+
seen.add(key);
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 首屏一次拿全:8 卡 + 在办列表 + 工作区 + 身份 + 各工作区花名册。
|
|
60
|
+
* 三条读并行(`stats` / `list` / `owners`),各自缓存;一个工作区坏只进 degraded,其余照常。
|
|
61
|
+
*/
|
|
62
|
+
export async function getBoardHome(workspaces, now = new Date()) {
|
|
63
|
+
const [statsPayload, taskScope, ownerScope] = await Promise.all([
|
|
64
|
+
getBoardStats(workspaces, now),
|
|
65
|
+
settleWorkspaces(workspaces, "list", readTaskScope, (rows) => rows),
|
|
66
|
+
settleWorkspaces(workspaces, "owners", readOwners, (rows) => rows),
|
|
67
|
+
]);
|
|
68
|
+
const identities = {};
|
|
69
|
+
const tasks = [];
|
|
70
|
+
for (const scope of taskScope.data) {
|
|
71
|
+
identities[scope.workspace.id] = scope.identity;
|
|
72
|
+
tasks.push(...scope.tasks);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...statsPayload,
|
|
76
|
+
workspaces,
|
|
77
|
+
tasks,
|
|
78
|
+
identities,
|
|
79
|
+
workspaceOwners: ownerScope.data,
|
|
80
|
+
degraded: dedupe([
|
|
81
|
+
...statsPayload.degraded,
|
|
82
|
+
...taskScope.degraded,
|
|
83
|
+
...ownerScope.degraded,
|
|
84
|
+
]),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export async function getBoardStats(workspaces, now = new Date()) {
|
|
88
|
+
const thisWeek = weekWindow(0, now);
|
|
89
|
+
const lastWeek = weekWindow(-1, now);
|
|
90
|
+
const [perWorkspace, thisWeekCounts, lastWeekCounts] = await Promise.all([
|
|
91
|
+
settleWorkspaces(workspaces, "list", readStats, (rows) => rows),
|
|
92
|
+
countWeek(workspaces, thisWeek),
|
|
93
|
+
countWeek(workspaces, lastWeek),
|
|
94
|
+
]);
|
|
95
|
+
const sum = (pick) => perWorkspace.data.reduce((total, stats) => total + pick(stats), 0);
|
|
96
|
+
// 阈值由 skill 回显;没传过覆盖值 ⇒ 各工作区一致,取首个即可。
|
|
97
|
+
// 一个工作区都没有时给 0——那时所有计数也是 0,阈值文案没有实际含义。
|
|
98
|
+
const first = perWorkspace.data[0];
|
|
99
|
+
const stats = {
|
|
100
|
+
myActive: sum((item) => item.myDoing),
|
|
101
|
+
myOverdue: sum((item) => item.myOverdue),
|
|
102
|
+
myStale: sum((item) => item.myStale),
|
|
103
|
+
open: sum((item) => item.open),
|
|
104
|
+
doing: sum((item) => item.doing),
|
|
105
|
+
overdue: sum((item) => item.overdue),
|
|
106
|
+
stale: sum((item) => item.stale),
|
|
107
|
+
backlog: sum((item) => item.backlog),
|
|
108
|
+
staleDays: first?.staleDays ?? 0,
|
|
109
|
+
overdueDays: first?.overdueDays ?? 0,
|
|
110
|
+
backlogDays: first?.backlogDays ?? 0,
|
|
111
|
+
oldestOpenDays: perWorkspace.data.length === 0
|
|
112
|
+
? null
|
|
113
|
+
: perWorkspace.data.reduce((max, item) => item.oldestOpenDays === null
|
|
114
|
+
? max
|
|
115
|
+
: max === null
|
|
116
|
+
? item.oldestOpenDays
|
|
117
|
+
: Math.max(max, item.oldestOpenDays), null),
|
|
118
|
+
weekDone: thisWeekCounts.done,
|
|
119
|
+
lastWeekDone: lastWeekCounts.done,
|
|
120
|
+
weekFrom: thisWeek.from,
|
|
121
|
+
weekTo: thisWeek.to,
|
|
122
|
+
lastWeekFrom: lastWeek.from,
|
|
123
|
+
lastWeekTo: lastWeek.to,
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
at: now.toISOString(),
|
|
127
|
+
stats,
|
|
128
|
+
degraded: [
|
|
129
|
+
...perWorkspace.degraded,
|
|
130
|
+
...thisWeekCounts.degraded,
|
|
131
|
+
...lastWeekCounts.degraded,
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -9,9 +9,6 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
const SKILL_DIR = fileURLToPath(new URL("../../../skill", import.meta.url));
|
|
10
10
|
/** SKILL.md 里指示脚本位置的占位符(注册时替换为绝对路径,模型据此找到脚本)。 */
|
|
11
11
|
const SKILL_DIR_TOKEN = "<本 SKILL.md 所在目录>";
|
|
12
|
-
/** 台账入口绝对路径(提示词里直接给「原样执行这一条」的命令用;与注册共用同一份目录知识)。
|
|
13
|
-
* 它是按平台选二进制的薄包装,不需要系统装任何运行时。 */
|
|
14
|
-
export const SKILL_SCRIPT = `${SKILL_DIR}/scripts/progress`;
|
|
15
12
|
/** frontmatter 最小解析:只取 name / description(不引 YAML 库;值去首尾引号)。 */
|
|
16
13
|
function parseFrontmatter(text) {
|
|
17
14
|
const match = /^---\n([\s\S]*?)\n---/.exec(text);
|
package/dist/host/types.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* 不 import 官方包(参考插件同款纪律:宿主 rc 接口会无声变,结构化声明 + external/ 文档坐标
|
|
5
5
|
* 比绑死官方类型更稳)。
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import type { DegradedInfo } from "./errors.js";
|
|
8
|
+
/** 一个活跃台账工作区:宿主 registry 条目 + 本地探测(含 `ledger/`)。 */
|
|
8
9
|
export interface LedgerWorkspace {
|
|
9
10
|
/** 宿主工作区 id。 */
|
|
10
11
|
readonly id: string;
|
|
@@ -13,7 +14,7 @@ export interface LedgerWorkspace {
|
|
|
13
14
|
/** canonical 绝对路径(宿主已 realpath 归一;skill 脚本 --repo 直传它)。 */
|
|
14
15
|
readonly path: string;
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
+
/** 任务对象的原始字段(skill 脚本 list --json 的值形状;缺字段按可选处理)。 */
|
|
17
18
|
export interface TaskRecord {
|
|
18
19
|
title?: string;
|
|
19
20
|
status?: string;
|
|
@@ -33,40 +34,31 @@ export interface TaskRecord {
|
|
|
33
34
|
category?: string | null;
|
|
34
35
|
tags?: string[] | null;
|
|
35
36
|
worklog?: string | null;
|
|
37
|
+
age_days?: number | null;
|
|
38
|
+
idle_days?: number | null;
|
|
39
|
+
waiting_days?: number | null;
|
|
40
|
+
stale?: boolean;
|
|
41
|
+
overdue?: boolean;
|
|
42
|
+
backlog?: boolean;
|
|
43
|
+
/** 是不是本机身份名下的(skill 比 uuid 得出来的)。 */
|
|
44
|
+
mine?: boolean;
|
|
36
45
|
}
|
|
37
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* 一条在办任务 = 原始字段 + 任务号 + 归属工作区(读取时打上)
|
|
48
|
+
* + skill 算好的派生面(插件只搬字段,不重算)。
|
|
49
|
+
*/
|
|
38
50
|
export interface TaskRow extends TaskRecord {
|
|
39
51
|
readonly id: string;
|
|
40
52
|
readonly workspace: LedgerWorkspace;
|
|
53
|
+
readonly ageDays: number | null;
|
|
54
|
+
readonly idleDays: number | null;
|
|
55
|
+
readonly waitingDays: number | null;
|
|
56
|
+
readonly stale: boolean;
|
|
57
|
+
readonly overdue: boolean;
|
|
58
|
+
readonly backlog: boolean;
|
|
59
|
+
readonly mine: boolean;
|
|
41
60
|
}
|
|
42
|
-
/**
|
|
43
|
-
export interface HistoryRecord {
|
|
44
|
-
/** 结案时间(首列)。 */
|
|
45
|
-
time?: string;
|
|
46
|
-
created?: string | null;
|
|
47
|
-
workpackage?: string;
|
|
48
|
-
category?: string | null;
|
|
49
|
-
tags?: string[] | null;
|
|
50
|
-
/** 负责人 uuid(身份唯一键;`history --owner` 就比这个)。 */
|
|
51
|
-
owner?: string;
|
|
52
|
-
/** 发起人(creator):任务的「创建者」,与 owner 区分(owner 可被 claim 改走)。历史 11 列新增。 */
|
|
53
|
-
creator?: string | null;
|
|
54
|
-
/** 负责人 / 发起人的显示名:**落盘在历史行里**(结案那一刻的快照,改名不回写旧行),只用于显示 / 搜索;老行没有时才按名册兜底补。 */
|
|
55
|
-
owner_name?: string | null;
|
|
56
|
-
creator_name?: string | null;
|
|
57
|
-
/** 「完成了什么」列。 */
|
|
58
|
-
summary?: string;
|
|
59
|
-
/** 过程文档链接列。 */
|
|
60
|
-
log?: string | null;
|
|
61
|
-
/** 行所在历史文件(相对工作区,脚本打上)。 */
|
|
62
|
-
src?: string;
|
|
63
|
-
/** 行格式(脚本 `fmt`)与行号(脚本 `line`):排查坏历史行时用来定位。 */
|
|
64
|
-
fmt?: string;
|
|
65
|
-
line?: number;
|
|
66
|
-
/** 归属工作区(读取时打上)。 */
|
|
67
|
-
readonly workspace?: LedgerWorkspace;
|
|
68
|
-
}
|
|
69
|
-
/** list / history 共用的过滤面(与 skill 脚本参数一一对应)。 */
|
|
61
|
+
/** `list` 的过滤面(与 skill 脚本 `list` 参数一一对应)。 */
|
|
70
62
|
export interface LedgerFilter {
|
|
71
63
|
/** 状态(open/doing;旧数据可能残留 blocked,读侧当进行中处理)。 */
|
|
72
64
|
status?: string;
|
|
@@ -79,116 +71,68 @@ export interface LedgerFilter {
|
|
|
79
71
|
/** 创建时间起 / 止(YYYY-MM-DD [HH:mm[:ss]],脚本会补全)。 */
|
|
80
72
|
since?: string;
|
|
81
73
|
until?: string;
|
|
82
|
-
/** 列表分页大小(history 侧;script 的 --limit 在过滤 + 倒序后取前 N)。 */
|
|
83
|
-
limit?: number;
|
|
84
|
-
/** 只读最近 N 个月的历史文件(插件传 13;不传 = 脚本默认读全部)。 */
|
|
85
|
-
months?: number;
|
|
86
|
-
}
|
|
87
|
-
/** 一个自然周期内的两个计数(核心永远是「完成」,新增只作对照)。 */
|
|
88
|
-
export interface PeriodCounts {
|
|
89
|
-
readonly done: number;
|
|
90
|
-
readonly created: number;
|
|
91
|
-
/** 周期起(YYYY-MM-DD,含)。 */
|
|
92
|
-
readonly from: string;
|
|
93
|
-
/** 周期止(YYYY-MM-DD,含)。 */
|
|
94
|
-
readonly to: string;
|
|
95
|
-
}
|
|
96
|
-
/** 统计台四个自然周期(用户 2026-10-17 定稿:本周 / 上周 / 本月 / 上月,不用 7/30 天)。 */
|
|
97
|
-
export interface TrendPeriods {
|
|
98
|
-
readonly thisWeek: PeriodCounts;
|
|
99
|
-
readonly lastWeek: PeriodCounts;
|
|
100
|
-
readonly thisMonth: PeriodCounts;
|
|
101
|
-
readonly lastMonth: PeriodCounts;
|
|
102
|
-
}
|
|
103
|
-
/** 趋势图一桶(一周):完成 + 新增 + 截止时仍未完成。 */
|
|
104
|
-
export interface WeeklyBucket {
|
|
105
|
-
/** 该周周一(YYYY-MM-DD)。 */
|
|
106
|
-
readonly week: string;
|
|
107
|
-
/** 该周截止日(YYYY-MM-DD):历史周=当周周日,当前周=今天——图表 x 轴标它,因为数据是按截止日判的。 */
|
|
108
|
-
readonly end: string;
|
|
109
|
-
readonly done: number;
|
|
110
|
-
readonly created: number;
|
|
111
|
-
/** 该周截止时仍未完成(在办)的任务数:cumulative(创建 - 完成) 到该周末,含窗口前遗留。 */
|
|
112
|
-
readonly remaining: number;
|
|
113
|
-
}
|
|
114
|
-
/** 趋势图数据按范围各一份(切换零请求)。 */
|
|
115
|
-
export interface ClosedSummary {
|
|
116
|
-
readonly weekly: {
|
|
117
|
-
readonly all: readonly WeeklyBucket[];
|
|
118
|
-
readonly mine: readonly WeeklyBucket[];
|
|
119
|
-
};
|
|
120
74
|
}
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
|
|
75
|
+
/**
|
|
76
|
+
* 8 个统计卡的面。**数字全部来自 skill**(`stats --json` + `history --count-only`),
|
|
77
|
+
* 插件只做跨工作区求和与阈值透传——不自己判停滞 / 逾期 / 积压。
|
|
78
|
+
*/
|
|
79
|
+
export interface BoardStatsView {
|
|
80
|
+
/** 我的进行中(skill `my_doing`)。 */
|
|
124
81
|
readonly myActive: number;
|
|
125
|
-
/**
|
|
82
|
+
/** 我的逾期(skill `my_overdue`)。 */
|
|
126
83
|
readonly myOverdue: number;
|
|
127
|
-
/**
|
|
84
|
+
/** 我的停滞(skill `my_stale`)。 */
|
|
128
85
|
readonly myStale: number;
|
|
129
|
-
/** 待认领(open
|
|
86
|
+
/** 待认领(skill `open`)。 */
|
|
130
87
|
readonly open: number;
|
|
131
|
-
/**
|
|
88
|
+
/** 进行中(skill `doing`,含旧数据的 blocked)。 */
|
|
132
89
|
readonly doing: number;
|
|
133
|
-
/**
|
|
90
|
+
/** 逾期(skill `overdue`,按时间判定不是状态)。 */
|
|
134
91
|
readonly overdue: number;
|
|
135
|
-
/**
|
|
92
|
+
/** 停滞(skill `stale`)。 */
|
|
136
93
|
readonly stale: number;
|
|
137
|
-
/**
|
|
94
|
+
/** 积压(skill `backlog`)。 */
|
|
138
95
|
readonly backlog: number;
|
|
139
|
-
/** 停滞 / 逾期 /
|
|
96
|
+
/** 停滞 / 逾期 / 积压阈值(天)——skill 回显,界面文案不硬编码。 */
|
|
140
97
|
readonly staleDays: number;
|
|
141
98
|
readonly overdueDays: number;
|
|
142
99
|
readonly backlogDays: number;
|
|
143
100
|
/** 最老一条待认领躺了多少天(没有待认领 = null)。 */
|
|
144
101
|
readonly oldestOpenDays: number | null;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
readonly
|
|
150
|
-
|
|
151
|
-
readonly
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/** 逾期标记:接任务至今满 OVERDUE_DAYS 天还没做完(不看中途更新)。 */
|
|
157
|
-
readonly overdue: boolean;
|
|
158
|
-
/** 积压标记:待认领且创建至今满 BACKLOG_DAYS 天没人接。 */
|
|
159
|
-
readonly backlog: boolean;
|
|
160
|
-
}
|
|
161
|
-
export interface BoardSnapshot {
|
|
102
|
+
/** 本周 / 上周完成数;history 全读不到时为 null(界面留 —,不拿 0 充数)。 */
|
|
103
|
+
readonly weekDone: number | null;
|
|
104
|
+
readonly lastWeekDone: number | null;
|
|
105
|
+
/** 两周的区间(界面悬停用,含边界)。 */
|
|
106
|
+
readonly weekFrom: string;
|
|
107
|
+
readonly weekTo: string;
|
|
108
|
+
readonly lastWeekFrom: string;
|
|
109
|
+
readonly lastWeekTo: string;
|
|
110
|
+
}
|
|
111
|
+
/** 8 卡的全部数字 + 降级信息。 */
|
|
112
|
+
export interface StatsPayload {
|
|
162
113
|
readonly at: string;
|
|
163
|
-
readonly
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
export interface HomeAggregate {
|
|
176
|
-
readonly snapshot: BoardSnapshot;
|
|
177
|
-
readonly closed: ClosedSummary;
|
|
178
|
-
readonly trend: TrendPeriods;
|
|
114
|
+
readonly stats: BoardStatsView;
|
|
115
|
+
readonly degraded: readonly DegradedInfo[];
|
|
116
|
+
}
|
|
117
|
+
/** 一个工作区的花名册(skill `owners --json`:含当前没任务的人)。 */
|
|
118
|
+
export interface WorkspaceOwners {
|
|
119
|
+
readonly workspaceId: string;
|
|
120
|
+
readonly workspaceTitle: string;
|
|
121
|
+
readonly owners: readonly {
|
|
122
|
+
readonly uuid: string;
|
|
123
|
+
readonly name: string;
|
|
124
|
+
readonly mine: boolean;
|
|
125
|
+
}[];
|
|
179
126
|
}
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
127
|
+
/**
|
|
128
|
+
* `/board/snapshot` 响应:首屏一次拿全 —— 8 卡 + 在办列表 + 工作区 + 身份 + 各工作区花名册。
|
|
129
|
+
* 列表行直接就是 skill `list --json` 的形状(含 `mine` / 三个标记 / 三个天数),
|
|
130
|
+
* 前端只筛不判。
|
|
131
|
+
*/
|
|
132
|
+
export interface BoardHomePayload extends StatsPayload {
|
|
133
|
+
readonly workspaces: readonly LedgerWorkspace[];
|
|
183
134
|
readonly tasks: readonly TaskRow[];
|
|
184
|
-
readonly
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
export interface TrendScope extends TaskScope {
|
|
188
|
-
readonly history: readonly HistoryRecord[];
|
|
189
|
-
}
|
|
190
|
-
export interface FilterOptions {
|
|
191
|
-
readonly owners: string[];
|
|
192
|
-
readonly categories: string[];
|
|
193
|
-
readonly tags: string[];
|
|
135
|
+
readonly identities: Readonly<Record<string, string | null>>;
|
|
136
|
+
/** 每个工作区的成员名单(跨工作区去重 / 分组由前端做,见 client 的 ownerGroups)。 */
|
|
137
|
+
readonly workspaceOwners: readonly WorkspaceOwners[];
|
|
194
138
|
}
|
package/dist/host/workspace.d.ts
CHANGED
|
@@ -12,10 +12,9 @@ export interface RegistryWorkspace {
|
|
|
12
12
|
readonly attachSession?: (sessionId: string) => Promise<void>;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* 工作区发现:宿主 registry 全量 →
|
|
16
|
-
*
|
|
17
|
-
* 补充源(registry 未收录目录)暂不做——形态待拍板,见 architecture §五。
|
|
15
|
+
* 工作区发现:宿主 registry 全量 → 逐个问 skill `root`(这个目录是不是台账根),
|
|
16
|
+
* 只留是的。没有 `ledger/` 的目录后端不可见(2026-10-17 拍板)。
|
|
18
17
|
*
|
|
19
18
|
* 探测走 repository 的 hasLedger()——本文件不碰 node:fs(IO 唯一出口纪律)。
|
|
20
19
|
*/
|
|
21
|
-
export declare function resolveWorkspaces(registryList: readonly RegistryWorkspace[]): LedgerWorkspace[]
|
|
20
|
+
export declare function resolveWorkspaces(registryList: readonly RegistryWorkspace[]): Promise<LedgerWorkspace[]>;
|
package/dist/host/workspace.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { hasLedger } from "./repositories/ledger.repository.js";
|
|
2
2
|
/**
|
|
3
|
-
* 工作区发现:宿主 registry 全量 →
|
|
4
|
-
*
|
|
5
|
-
* 补充源(registry 未收录目录)暂不做——形态待拍板,见 architecture §五。
|
|
3
|
+
* 工作区发现:宿主 registry 全量 → 逐个问 skill `root`(这个目录是不是台账根),
|
|
4
|
+
* 只留是的。没有 `ledger/` 的目录后端不可见(2026-10-17 拍板)。
|
|
6
5
|
*
|
|
7
6
|
* 探测走 repository 的 hasLedger()——本文件不碰 node:fs(IO 唯一出口纪律)。
|
|
8
7
|
*/
|
|
9
|
-
export function resolveWorkspaces(registryList) {
|
|
8
|
+
export async function resolveWorkspaces(registryList) {
|
|
9
|
+
const flags = await Promise.all(registryList.map((workspace) => hasLedger(workspace.path)));
|
|
10
10
|
return registryList
|
|
11
|
-
.filter((
|
|
11
|
+
.filter((_, index) => flags[index])
|
|
12
12
|
.map((workspace) => ({
|
|
13
13
|
id: workspace.id,
|
|
14
14
|
title: workspace.title,
|
package/dist/index.js
CHANGED
|
@@ -9,22 +9,9 @@
|
|
|
9
9
|
* 满足不了的依赖会令 entry 一直 pending、卡死整个 dsh 启动);两条注入线
|
|
10
10
|
* 互相独立——skills 缺席不挡数据面,反之亦然。
|
|
11
11
|
*/
|
|
12
|
-
import { registerRoutes,
|
|
12
|
+
import { registerRoutes, } from "./host/routes.js";
|
|
13
13
|
import { buildSkillRegistration, } from "./host/skill/registration.js";
|
|
14
14
|
export const name = "dsh-task-ledger";
|
|
15
|
-
/**
|
|
16
|
-
* 惰性服务读取:cordis ctx 的属性是 getter,**没 inject 的服务读一下就 throw**
|
|
17
|
-
* (参考插件真机实证:`cannot get property "agentTeams" without inject`)。
|
|
18
|
-
* 探测语义是「有就用、没有就降级」,所以把 throw 归一成 undefined。
|
|
19
|
-
*/
|
|
20
|
-
function readService(services, name) {
|
|
21
|
-
try {
|
|
22
|
-
return services[name];
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
15
|
export function apply(ctx) {
|
|
29
16
|
// ── 数据面(任务 3) ──
|
|
30
17
|
ctx.inject(["workspaceRegistry", "webServer"], (services) => {
|
|
@@ -35,25 +22,7 @@ export function apply(ctx) {
|
|
|
35
22
|
return;
|
|
36
23
|
}
|
|
37
24
|
registerRoutes(webServer, { registry });
|
|
38
|
-
|
|
39
|
-
registerWriteRoutes(webServer, { registry });
|
|
40
|
-
console.info("[task-ledger] 数据面路由已注册(读 /api/task-ledger/* + 写 /claim /release)");
|
|
41
|
-
});
|
|
42
|
-
// ── 会话交接(按钮侧「结束任务」,2026-10-18 拍板 B 案:只有结束走会话) ──
|
|
43
|
-
// 单独一条注入线:agents 缺席只影响这一个端点,不该拖累数据面(上一条线只等 registry + webServer)。
|
|
44
|
-
ctx.inject(["agents", "workspaceRegistry", "webServer"], (services) => {
|
|
45
|
-
const registry = services.workspaceRegistry;
|
|
46
|
-
const webServer = services.webServer;
|
|
47
|
-
if (registry === undefined || webServer === undefined) {
|
|
48
|
-
console.warn("[task-ledger] workspaceRegistry / webServer 未就位,「结束任务」端点不注册");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
registerSessionRoutes(webServer, {
|
|
52
|
-
registry,
|
|
53
|
-
agents: services.agents,
|
|
54
|
-
getService: (name) => readService(services, name),
|
|
55
|
-
});
|
|
56
|
-
console.info("[task-ledger] 「结束任务」端点已注册(POST /api/task-ledger/close)");
|
|
25
|
+
console.info("[task-ledger] 数据面路由已注册(读 /api/task-ledger/workspaces、/board/snapshot)");
|
|
57
26
|
});
|
|
58
27
|
// ── skill 全局注册(任务 2) ──
|
|
59
28
|
// unscoped 注册落全局层,模型可 load;disposer 随插件 dispose 自动注销。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.114",
|
|
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",
|
|
@@ -57,6 +57,6 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"dsh-plugin-core": "^0.
|
|
60
|
+
"dsh-plugin-core": "^0.8.3"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -16,6 +16,7 @@ allowed-tools: Read,Edit,Bash,Grep
|
|
|
16
16
|
## 手写边界
|
|
17
17
|
|
|
18
18
|
- **唯一能手写的是任务的过程文档(worklog)前五节**(怎么写见「过程文档」节);末节「执行记录」由脚本维护,**禁止手写、禁止手工添加**。
|
|
19
|
+
- **改正文前先确认任务在当前用户名下、状态 doing**:open 的先问用户,同意再 `claim`;别人名下的不动。
|
|
19
20
|
- **其他所有文件一律不许手改**:修改 / 删除 / 任何操作都走命令;手改台账文件即数据损坏。
|
|
20
21
|
- 台账看着不对劲(任务凭空少了、状态怪、分片对不上)⇒ **不要动手修**:让用户跑 `validate`(老月份的历史也要查就跑 `validate --deep`),把清单交给用户。
|
|
21
22
|
|
package/dist/host/board.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 首页**组合入口**(`/board` 兼容全量端点用):一次读全(tasks + history + 本机身份),
|
|
3
|
-
* 喂快 / 慢两条纯计算——**读一次算两样,不重复 IO**。
|
|
4
|
-
*
|
|
5
|
-
* 拆端点后(host-data-plane §五 A)首屏走 `services/home.service.ts::getSnapshot()`(不碰 history),
|
|
6
|
-
* 本组合归「兼容期全量 `/board`」;新端点在 routes 层直接调 service,不过本文件。
|
|
7
|
-
*/
|
|
8
|
-
import { type DegradedInfo } from "./errors.js";
|
|
9
|
-
import type { HomeAggregate, LedgerWorkspace } from "./types.js";
|
|
10
|
-
/**
|
|
11
|
-
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
12
|
-
* + 四个自然周期计数。**每个工作区只读一次**(list + history + 本机身份),
|
|
13
|
-
* 避免「快照读一遍、统计再读一遍」把 5 万行级工作区的 history 解析翻倍。
|
|
14
|
-
* 单工作区坏 ⇒ 进 degraded(其余照常);全坏 ⇒ 抛(路由转 500 + 人话)。
|
|
15
|
-
*/
|
|
16
|
-
export declare function getHomeAggregate(workspaces: readonly LedgerWorkspace[]): Promise<HomeAggregate & {
|
|
17
|
-
degraded: readonly DegradedInfo[];
|
|
18
|
-
}>;
|
|
19
|
-
export { getFilterOptions, queryClosed } from "./services/history.service.js";
|
|
20
|
-
export { getSnapshot as getBoardSnapshot } from "./services/home.service.js";
|
package/dist/host/board.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 首页**组合入口**(`/board` 兼容全量端点用):一次读全(tasks + history + 本机身份),
|
|
3
|
-
* 喂快 / 慢两条纯计算——**读一次算两样,不重复 IO**。
|
|
4
|
-
*
|
|
5
|
-
* 拆端点后(host-data-plane §五 A)首屏走 `services/home.service.ts::getSnapshot()`(不碰 history),
|
|
6
|
-
* 本组合归「兼容期全量 `/board`」;新端点在 routes 层直接调 service,不过本文件。
|
|
7
|
-
*/
|
|
8
|
-
import { settleWorkspaces } from "./errors.js";
|
|
9
|
-
import { computeSnapshot } from "./services/home.service.js";
|
|
10
|
-
import { computeClosedSummary, computeTrendPeriods, readTrendScope, } from "./services/trend.service.js";
|
|
11
|
-
/**
|
|
12
|
-
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
13
|
-
* + 四个自然周期计数。**每个工作区只读一次**(list + history + 本机身份),
|
|
14
|
-
* 避免「快照读一遍、统计再读一遍」把 5 万行级工作区的 history 解析翻倍。
|
|
15
|
-
* 单工作区坏 ⇒ 进 degraded(其余照常);全坏 ⇒ 抛(路由转 500 + 人话)。
|
|
16
|
-
*/
|
|
17
|
-
export async function getHomeAggregate(workspaces) {
|
|
18
|
-
const now = new Date();
|
|
19
|
-
const { data: scopes, degraded } = await settleWorkspaces(workspaces, "history", readTrendScope, (rows) => rows);
|
|
20
|
-
return {
|
|
21
|
-
snapshot: computeSnapshot(scopes, now),
|
|
22
|
-
closed: computeClosedSummary(scopes, now),
|
|
23
|
-
trend: computeTrendPeriods(scopes, now),
|
|
24
|
-
degraded,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export { getFilterOptions, queryClosed } from "./services/history.service.js";
|
|
28
|
-
// 单路径用例直接透传 service(调用方按快 / 慢自己选,不必经过组合):
|
|
29
|
-
export { getSnapshot as getBoardSnapshot } from "./services/home.service.js";
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 已结案列表 / 筛选项候选用例(history tab 与筛选控件用;非首屏,按需调用)。
|
|
3
|
-
*/
|
|
4
|
-
import { type DegradedInfo } from "../errors.js";
|
|
5
|
-
import type { FilterOptions, HistoryRecord, LedgerFilter, LedgerWorkspace, TrendScope } from "../types.js";
|
|
6
|
-
/** 已结案列表默认分页大小(「加载更多」= 调用方加大 limit 重取)。 */
|
|
7
|
-
export declare const DEFAULT_HISTORY_LIMIT = 500;
|
|
8
|
-
/**
|
|
9
|
-
* 已结案列表:默认近 13 个月 + limit 500,跨工作区合并后按创建时间倒序。
|
|
10
|
-
* shortcut: 分页用「加大 limit 重取」的笨办法(skill 脚本无 offset);
|
|
11
|
-
* 单工作区内 limit 已生效,合并边界的全局精确排序暂不保证(500 条一页无感)。
|
|
12
|
-
* 要全量老数据:调用方显式传 months: undefined(不拼 --months,脚本读全部,按秒计)。
|
|
13
|
-
*/
|
|
14
|
-
export declare function queryClosed(workspaces: readonly LedgerWorkspace[], filter?: LedgerFilter): Promise<{
|
|
15
|
-
records: HistoryRecord[];
|
|
16
|
-
degraded: readonly DegradedInfo[];
|
|
17
|
-
}>;
|
|
18
|
-
/** 候选值收集(纯函数):owner/category/tag 去重 + 排序。 */
|
|
19
|
-
export declare function collectFilterOptions(scopes: readonly TrendScope[]): FilterOptions;
|
|
20
|
-
/**
|
|
21
|
-
* 筛选项候选:在办(全量)+ 近 13 个月历史的去重值(owner / category / tag)。
|
|
22
|
-
* 按需调用(/filters 路由),非首屏——历史侧在极端量级下是百毫秒级。单工作区坏 ⇒ 进 degraded。
|
|
23
|
-
*/
|
|
24
|
-
export declare function getFilterOptions(workspaces: readonly LedgerWorkspace[]): Promise<{
|
|
25
|
-
options: FilterOptions;
|
|
26
|
-
degraded: readonly DegradedInfo[];
|
|
27
|
-
}>;
|