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.
Files changed (43) hide show
  1. package/dist/client.js +50739 -1529
  2. package/dist/client.js.map +1 -1
  3. package/dist/host/errors.d.ts +2 -2
  4. package/dist/host/repositories/cache.d.ts +2 -3
  5. package/dist/host/repositories/cache.js +1 -2
  6. package/dist/host/repositories/identity.repository.d.ts +8 -2
  7. package/dist/host/repositories/identity.repository.js +9 -29
  8. package/dist/host/repositories/ledger.repository.d.ts +12 -22
  9. package/dist/host/repositories/ledger.repository.js +39 -58
  10. package/dist/host/repositories/records.d.ts +1 -11
  11. package/dist/host/repositories/records.js +16 -42
  12. package/dist/host/repositories/runner.d.ts +0 -30
  13. package/dist/host/repositories/runner.js +0 -23
  14. package/dist/host/repositories/stats.repository.d.ts +36 -0
  15. package/dist/host/repositories/stats.repository.js +86 -0
  16. package/dist/host/routes.d.ts +4 -25
  17. package/dist/host/routes.js +7 -384
  18. package/dist/host/services/dates.d.ts +3 -24
  19. package/dist/host/services/dates.js +3 -63
  20. package/dist/host/services/stats.service.d.ts +7 -0
  21. package/dist/host/services/stats.service.js +134 -0
  22. package/dist/host/skill/registration.d.ts +0 -3
  23. package/dist/host/skill/registration.js +0 -3
  24. package/dist/host/types.d.ts +70 -126
  25. package/dist/host/workspace.d.ts +3 -4
  26. package/dist/host/workspace.js +5 -5
  27. package/dist/index.js +2 -33
  28. package/package.json +2 -2
  29. package/skill/SKILL.md +1 -0
  30. package/dist/host/board.d.ts +0 -20
  31. package/dist/host/board.js +0 -29
  32. package/dist/host/services/history.service.d.ts +0 -27
  33. package/dist/host/services/history.service.js +0 -72
  34. package/dist/host/services/home.service.d.ts +0 -23
  35. package/dist/host/services/home.service.js +0 -91
  36. package/dist/host/services/ledger-write.service.d.ts +0 -10
  37. package/dist/host/services/ledger-write.service.js +0 -32
  38. package/dist/host/services/session.service.d.ts +0 -110
  39. package/dist/host/services/session.service.js +0 -155
  40. package/dist/host/services/trend.service.d.ts +0 -36
  41. package/dist/host/services/trend.service.js +0 -162
  42. package/dist/host/services/uuid.d.ts +0 -11
  43. 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
+ }
@@ -1,6 +1,3 @@
1
- /** 台账入口绝对路径(提示词里直接给「原样执行这一条」的命令用;与注册共用同一份目录知识)。
2
- * 它是按平台选二进制的薄包装,不需要系统装任何运行时。 */
3
- export declare const SKILL_SCRIPT: string;
4
1
  /** 宿主 ctx.skills.register 的最小面(外部事实文档的结构面)。 */
5
2
  export interface SkillRegistration {
6
3
  name: string;
@@ -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);
@@ -4,7 +4,8 @@
4
4
  * 不 import 官方包(参考插件同款纪律:宿主 rc 接口会无声变,结构化声明 + external/ 文档坐标
5
5
  * 比绑死官方类型更稳)。
6
6
  */
7
- /** 一个活跃台账工作区:宿主 registry 条目 + 本地探测(ledger/progress.json 存在)。 */
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
- /** progress.json 任务对象的原始字段(skill 脚本 list --json 的值形状;缺字段按可选处理)。 */
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
- /** history 一条已结案记录(脚本 history --json 的 record 形状;字段名以脚本为准)。 */
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
- /** 首页统计卡的六个面(阈值 / 天数全在 host 算,前端只渲染——单一真源)。 */
122
- export interface BoardStats {
123
- /** 我的 doing(旧数据里的 blocked 也算进行中)。 */
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
- /** 我的逾期(接任务至今满 OVERDUE_DAYS 天未结案)。 */
82
+ /** 我的逾期(skill `my_overdue`)。 */
126
83
  readonly myOverdue: number;
127
- /** 我的停滞(我的进行中里 updated 满 STALE_DAYS 天没更新的)。 */
84
+ /** 我的停滞(skill `my_stale`)。 */
128
85
  readonly myStale: number;
129
- /** 待认领(open)。 */
86
+ /** 待认领(skill `open`)。 */
130
87
  readonly open: number;
131
- /** 全部 doing(旧数据里的 blocked 也算进行中)。 */
88
+ /** 进行中(skill `doing`,含旧数据的 blocked)。 */
132
89
  readonly doing: number;
133
- /** 全部逾期——按时间判定,不是状态。 */
90
+ /** 逾期(skill `overdue`,按时间判定不是状态)。 */
134
91
  readonly overdue: number;
135
- /** 停滞 = 进行中且 updated 至今满 STALE_DAYS 天没更新。 */
92
+ /** 停滞(skill `stale`)。 */
136
93
  readonly stale: number;
137
- /** 积压 = 待认领且 created 至今满 BACKLOG_DAYS 天没人接。 */
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
- /** 列表行附加工资字段(host 算,前端不判阈值)。 */
147
- export interface BoardTask extends TaskRow {
148
- /** 认领后未动天数(进行中;open 与解析不了的时间为 null)。 */
149
- readonly ageDays: number | null;
150
- /** 最后更新至今的天数(进行中;open 与解析不了的时间为 null)——停滞提示用。 */
151
- readonly idleDays: number | null;
152
- /** 创建至今的天数(**只对待认领**有值;其余为 null)——积压提示用。 */
153
- readonly waitingDays: number | null;
154
- /** 停滞标记:updated 至今满 STALE_DAYS 天没更新(与 ageDays 无关)。 */
155
- readonly stale: boolean;
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 workspaces: readonly LedgerWorkspace[];
164
- /** 全部在办任务(UI 自己筛「我的 / 待认领」——筛选是前端的事)。 */
165
- readonly tasks: readonly BoardTask[];
166
- readonly stats: BoardStats;
167
- /**
168
- * 工作区 id → 本机身份 uuid(取不到为 null)。
169
- * 「我的待办」在前端按 `owner === identities[workspace.id]` 筛——身份只在 host 侧读
170
- * (`~/.taskledger/identity.json` 的 `uuid`,机器级,与工作区 / git 配置无关),UI 不碰文件。
171
- */
172
- readonly identities: Readonly<Record<string, string | null>>;
173
- }
174
- /** 首页一次 `/board` 的全部聚合(**每个工作区只读一次** list + history + 本机身份)。 */
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
- /** 快路径作用域:在办 + 身份。**没有 history 字段**——想从它算趋势,编译器直接拦住。 */
181
- export interface TaskScope {
182
- readonly workspace: LedgerWorkspace;
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 identity: string | null;
185
- }
186
- /** 慢路径作用域:快路径 + history。趋势 / 筛选候选的原料。 */
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
  }
@@ -12,10 +12,9 @@ export interface RegistryWorkspace {
12
12
  readonly attachSession?: (sessionId: string) => Promise<void>;
13
13
  }
14
14
  /**
15
- * 工作区发现:宿主 registry 全量 → 逐个查 <path>/ledger/progress.json,只返回有的。
16
- * 未初始化(有 ledger/ 无 progress.json)= 隐藏,后端无此概念(2026-10-17 拍板)。
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[]>;
@@ -1,14 +1,14 @@
1
1
  import { hasLedger } from "./repositories/ledger.repository.js";
2
2
  /**
3
- * 工作区发现:宿主 registry 全量 → 逐个查 <path>/ledger/progress.json,只返回有的。
4
- * 未初始化(有 ledger/ 无 progress.json)= 隐藏,后端无此概念(2026-10-17 拍板)。
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((workspace) => hasLedger(workspace.path))
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, registerSessionRoutes, registerWriteRoutes, } from "./host/routes.js";
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
- // 写端点(认领 / 释放):B 混合制下由 host 直跑脚本,不依赖 agents ⇒ 与数据面同一条注入线。
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.113",
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.5.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
 
@@ -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";
@@ -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
- }>;