@longzai-intelligence-workflow/core 0.0.2 → 0.0.5

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 (38) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-lint.log +1305 -0
  3. package/.turbo/turbo-typecheck.log +12 -0
  4. package/CHANGELOG.md +18 -0
  5. package/coverage/.lcov.info.0e095d78ac9192b0.tmp +329 -0
  6. package/coverage/.lcov.info.8f2985e5b3ad22cc.tmp +329 -0
  7. package/coverage/.lcov.info.a6ce2b7c3df2438b.tmp +228 -0
  8. package/coverage/.lcov.info.cd0e8c028b2d5097.tmp +329 -0
  9. package/coverage/.lcov.info.fbb8af17757faa2d.tmp +329 -0
  10. package/coverage/lcov.info +28 -28
  11. package/dist/index.d.ts +1 -0
  12. package/docs/overview.md +1 -1
  13. package/package.json +1 -1
  14. package/src/__tests__/unit/constants.test.ts +75 -30
  15. package/src/__tests__/unit/utils.test.ts +110 -38
  16. package/src/__tests__/work-item-rules.test.ts +18 -5
  17. package/src/index.ts +19 -5
  18. package/src/ports/action-executor.port.ts +2 -1
  19. package/src/ports/feed-event.port.ts +1 -1
  20. package/src/ports/index.ts +1 -0
  21. package/src/schemas/action-executor.schema.ts +2 -1
  22. package/src/schemas/definition.schema.ts +1 -1
  23. package/src/schemas/feed-event-trigger.schema.ts +1 -1
  24. package/src/schemas/index.ts +7 -0
  25. package/src/schemas/instance.schema.ts +1 -1
  26. package/src/schemas/monitoring.schema.ts +1 -1
  27. package/src/schemas/trigger.schema.ts +1 -1
  28. package/src/schemas/workflow-doc.schema.ts +4 -2
  29. package/src/types/action-executor.ts +1 -1
  30. package/src/types/feed-event-trigger.ts +1 -1
  31. package/src/types/index.ts +5 -0
  32. package/src/utils/index.ts +3 -1
  33. package/src/utils/work-item-rules.utils.ts +25 -6
  34. package/tsconfig/.cache/build.tsbuildinfo +1 -1
  35. package/tsconfig/.cache/node.tsbuildinfo +1 -1
  36. package/tsconfig/.cache/test.tsbuildinfo +1 -1
  37. package/tsconfig/node.json +1 -1
  38. package/tsconfig.json +9 -1
@@ -1,69 +1,99 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
2
 
3
- import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../../constants/workflow';
4
- import { formatDate, getPagination, getTotalPages } from '../../utils/index';
3
+ import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '@/constants/workflow';
4
+ import { formatDate, getPagination, getTotalPages } from '@/utils/index';
5
5
 
6
6
  describe('getPagination', () => {
7
7
  it('默认参数返回 offset=0, limit=DEFAULT_PAGE_SIZE', () => {
8
- // Arrange & Act
8
+ /*
9
+ * 不传参直接调用 getPagination 取得分页结果
10
+ */
9
11
  const result = getPagination();
10
12
 
11
- // Assert
13
+ /*
14
+ * 断言默认偏移为 0 且条数取默认分页大小
15
+ */
12
16
  expect(result.offset).toBe(0);
13
17
  expect(result.limit).toBe(DEFAULT_PAGE_SIZE);
14
18
  });
15
19
 
16
20
  it('page=2, pageSize=10 返回 offset=10, limit=10', () => {
17
- // Arrange & Act
21
+ /*
22
+ * 以第二页与每页十条的参数调用 getPagination
23
+ */
18
24
  const result = getPagination(2, 10);
19
25
 
20
- // Assert
26
+ /*
27
+ * 断言偏移跳过第一页且条数原样返回
28
+ */
21
29
  expect(result.offset).toBe(10);
22
30
  expect(result.limit).toBe(10);
23
31
  });
24
32
 
25
33
  it('pageSize 超过 MAX_PAGE_SIZE 时限制为 MAX_PAGE_SIZE', () => {
26
- // Arrange
34
+ /*
35
+ * 构造超出 MAX_PAGE_SIZE 的每页条数
36
+ */
27
37
  const oversizedPageSize = MAX_PAGE_SIZE + 50;
28
38
 
29
- // Act
39
+ /*
40
+ * 将超限条数传入 getPagination
41
+ */
30
42
  const result = getPagination(1, oversizedPageSize);
31
43
 
32
- // Assert
44
+ /*
45
+ * 断言条数被收口到 MAX_PAGE_SIZE
46
+ */
33
47
  expect(result.limit).toBe(MAX_PAGE_SIZE);
34
48
  });
35
49
 
36
50
  it('page=0 时默认为 page=1', () => {
37
- // Arrange & Act
51
+ /*
52
+ * 以零页码调用 getPagination
53
+ */
38
54
  const result = getPagination(0, 10);
39
55
 
40
- // Assert
56
+ /*
57
+ * 断言零页码被归位到首页偏移
58
+ */
41
59
  expect(result.offset).toBe(0);
42
60
  expect(result.limit).toBe(10);
43
61
  });
44
62
 
45
63
  it('page 为负数时默认为 page=1', () => {
46
- // Arrange & Act
64
+ /*
65
+ * 以负数页码调用 getPagination
66
+ */
47
67
  const result = getPagination(-1, 10);
48
68
 
49
- // Assert
69
+ /*
70
+ * 断言负页码同样回落到首页偏移
71
+ */
50
72
  expect(result.offset).toBe(0);
51
73
  });
52
74
 
53
75
  it('仅传入 page 时 pageSize 使用默认值', () => {
54
- // Arrange & Act
76
+ /*
77
+ * 只传页码而省略每页条数调用 getPagination
78
+ */
55
79
  const result = getPagination(3);
56
80
 
57
- // Assert
81
+ /*
82
+ * 断言偏移按默认条数折算且条数取默认值
83
+ */
58
84
  expect(result.offset).toBe((3 - 1) * DEFAULT_PAGE_SIZE);
59
85
  expect(result.limit).toBe(DEFAULT_PAGE_SIZE);
60
86
  });
61
87
 
62
88
  it('仅传入 pageSize 时 page 默认为 1', () => {
63
- // Arrange & Act
89
+ /*
90
+ * 页码缺省而只传每页条数调用 getPagination
91
+ */
64
92
  const result = getPagination(undefined, 15);
65
93
 
66
- // Assert
94
+ /*
95
+ * 断言首页偏移为零且条数原样保留
96
+ */
67
97
  expect(result.offset).toBe(0);
68
98
  expect(result.limit).toBe(15);
69
99
  });
@@ -71,86 +101,128 @@ describe('getPagination', () => {
71
101
 
72
102
  describe('getTotalPages', () => {
73
103
  it('正确计算总页数', () => {
74
- // Arrange
104
+ /*
105
+ * 准备总数 45 与每页 10 条的输入
106
+ */
75
107
  const total = 45;
76
108
  const pageSize = 10;
77
109
 
78
- // Act
110
+ /*
111
+ * 用该输入计算总页数
112
+ */
79
113
  const result = getTotalPages(total, pageSize);
80
114
 
81
- // Assert
115
+ /*
116
+ * 断言有余数时向上取整为 5 页
117
+ */
82
118
  expect(result).toBe(5);
83
119
  });
84
120
 
85
121
  it('总数恰好整除时返回精确页数', () => {
86
- // Arrange
122
+ /*
123
+ * 准备总数 40 与每页 10 条的整除输入
124
+ */
87
125
  const total = 40;
88
126
  const pageSize = 10;
89
127
 
90
- // Act
128
+ /*
129
+ * 用该输入计算总页数
130
+ */
91
131
  const result = getTotalPages(total, pageSize);
92
132
 
93
- // Assert
133
+ /*
134
+ * 断言整除时返回精确的 4 页
135
+ */
94
136
  expect(result).toBe(4);
95
137
  });
96
138
 
97
139
  it('总数为 0 时返回 0', () => {
98
- // Arrange
140
+ /*
141
+ * 准备总数为 0 的空集输入
142
+ */
99
143
  const total = 0;
100
144
  const pageSize = 10;
101
145
 
102
- // Act
146
+ /*
147
+ * 用该输入计算总页数
148
+ */
103
149
  const result = getTotalPages(total, pageSize);
104
150
 
105
- // Assert
151
+ /*
152
+ * 断言空集时总页数为 0
153
+ */
106
154
  expect(result).toBe(0);
107
155
  });
108
156
 
109
157
  it('总数为 1 时返回 1', () => {
110
- // Arrange
158
+ /*
159
+ * 准备总数为 1 的单条输入
160
+ */
111
161
  const total = 1;
112
162
  const pageSize = 10;
113
163
 
114
- // Act
164
+ /*
165
+ * 用该输入计算总页数
166
+ */
115
167
  const result = getTotalPages(total, pageSize);
116
168
 
117
- // Assert
169
+ /*
170
+ * 断言单条数据占 1 页
171
+ */
118
172
  expect(result).toBe(1);
119
173
  });
120
174
  });
121
175
 
122
176
  describe('formatDate', () => {
123
177
  it('将 Date 对象转换为 ISO 字符串', () => {
124
- // Arrange
178
+ /*
179
+ * 构造固定时刻的日期对象
180
+ */
125
181
  const date = new Date('2025-01-15T10:30:00.000Z');
126
182
 
127
- // Act
183
+ /*
184
+ * 将日期对象交给 formatDate 转换
185
+ */
128
186
  const result = formatDate(date);
129
187
 
130
- // Assert
188
+ /*
189
+ * 断言输出与输入的 ISO 表示逐字符一致
190
+ */
131
191
  expect(result).toBe('2025-01-15T10:30:00.000Z');
132
192
  });
133
193
 
134
194
  it('将日期字符串转换为 ISO 字符串', () => {
135
- // Arrange
195
+ /*
196
+ * 准备纯日期字符串输入
197
+ */
136
198
  const dateStr = '2025-06-01';
137
199
 
138
- // Act
200
+ /*
201
+ * 把字符串交给 formatDate 转换
202
+ */
139
203
  const result = formatDate(dateStr);
140
204
 
141
- // Assert
205
+ /*
206
+ * 断言输出包含原日期且类型为字符串
207
+ */
142
208
  expect(result).toContain('2025-06-01');
143
209
  expect(typeof result).toBe('string');
144
210
  });
145
211
 
146
212
  it('返回值为有效的 ISO 格式', () => {
147
- // Arrange
213
+ /*
214
+ * 取当前时刻构造日期对象
215
+ */
148
216
  const date = new Date();
149
217
 
150
- // Act
218
+ /*
219
+ * 将当前日期交给 formatDate 转换
220
+ */
151
221
  const result = formatDate(date);
152
222
 
153
- // Assert
223
+ /*
224
+ * 断言输出经日期还原后与自身一致
225
+ */
154
226
  expect(new Date(result).toISOString()).toBe(result);
155
227
  });
156
228
  });
@@ -3,8 +3,14 @@
3
3
  */
4
4
  import { describe, expect, test } from 'bun:test';
5
5
 
6
- import { parseGateLine, parseItemLine, type WorkflowEdge, type WorkflowGate, type WorkflowItem } from '../schemas/workflow-doc.schema';
7
- import { checkStatusTransition, deriveWorkflowView } from '../utils/work-item-rules.utils';
6
+ import {
7
+ parseGateLine,
8
+ parseItemLine,
9
+ type WorkflowEdge,
10
+ type WorkflowGate,
11
+ type WorkflowItem,
12
+ } from '@/schemas/workflow-doc.schema';
13
+ import { checkStatusTransition, deriveWorkflowView } from '@/utils/work-item-rules.utils';
8
14
 
9
15
  describe('声明方言四段制解析', () => {
10
16
  test('工作项行:状态段执法', () => {
@@ -19,9 +25,13 @@ describe('声明方言四段制解析', () => {
19
25
  });
20
26
 
21
27
  test('裁定门行:裁定段四形态执法', () => {
22
- expect(parseGateLine('t-013 | ruling | 载体指示待裁 | pending')?.ruling).toEqual({ status: 'pending' });
28
+ expect(parseGateLine('t-013 | ruling | 载体指示待裁 | pending')?.ruling).toEqual({
29
+ status: 'pending',
30
+ });
23
31
  expect(parseGateLine('workflow | ruling | TE 悬置 | pending')?.at).toBe('workflow');
24
- expect(parseGateLine('t-013 | ruling | 载体指示待裁 | approved:user:decisions/019.md')?.ruling).toEqual({
32
+ expect(
33
+ parseGateLine('t-013 | ruling | 载体指示待裁 | approved:user:decisions/019.md')?.ruling,
34
+ ).toEqual({
25
35
  status: 'approved',
26
36
  decidedBy: 'user',
27
37
  evidence: 'decisions/019.md',
@@ -68,7 +78,10 @@ describe('视图派生(依赖门控与未决裁定)', () => {
68
78
  expect(byId.get('b')?.blockedOnPrerequisite).toBe(true);
69
79
  expect(byId.get('b')?.canStart).toBe(false);
70
80
  expect(byId.get('c')?.canStart).toBe(false);
71
- expect(view.pendingRulings.map((ruling) => ruling.key)).toEqual(['b:ruling', 'workflow:ruling']);
81
+ expect(view.pendingRulings.map((ruling) => ruling.key)).toEqual([
82
+ 'b:ruling',
83
+ 'workflow:ruling',
84
+ ]);
72
85
  });
73
86
 
74
87
  test('前置闭环解除阻塞;门批准解除门控;escalated 仍属未决', () => {
package/src/index.ts CHANGED
@@ -4,13 +4,27 @@
4
4
  * 提供共享基础类型、Schema、常量和工具函数。
5
5
  * 不依赖任何 domain 包。
6
6
  */
7
- // 类型导出
7
+ /*
8
+ * 类型导出
9
+ */
8
10
  export * from './types/index';
9
- // 常量导出
11
+
12
+ /*
13
+ * 常量导出
14
+ */
10
15
  export * from './constants/index';
11
- // 工具函数导出
16
+
17
+ /*
18
+ * 工具函数导出
19
+ */
12
20
  export * from './utils/index';
13
- // zod schema 导出
21
+
22
+ /*
23
+ * zod schema 导出
24
+ */
14
25
  export * from './schemas/index';
15
- // 端口契约导出(C1 合规,零框架依赖)
26
+
27
+ /*
28
+ * 端口契约导出(C1 合规,零框架依赖)
29
+ */
16
30
  export * from './ports/index';
@@ -6,6 +6,7 @@
6
6
  * 当工作流执行到 action 节点时,根据 action.type 调用对应 handler。
7
7
  *
8
8
  * 支持的 action 类型:
9
+ *
9
10
  * - feed.applyTags:调用 feed F-2 applyTags(Phase 2)
10
11
  * - feed.summarize:调用 feed F-3 summarize(Phase 2)
11
12
  * - llm.complete:调用 agent LlmCompletePort
@@ -16,7 +17,7 @@
16
17
  * C1 合规:本端口为零框架依赖的纯类型契约,由具体适配器层提供实现。
17
18
  */
18
19
 
19
- import type { Action, ActionContext, ActionResult, ActionType } from '../types';
20
+ import type { Action, ActionContext, ActionResult, ActionType } from '@/types';
20
21
 
21
22
  /**
22
23
  * 单个 action handler 函数签名
@@ -8,7 +8,7 @@
8
8
  * C1 合规:本端口为零框架依赖的纯类型契约,由 feed 项目的实现层提供具体实现。
9
9
  */
10
10
 
11
- import type { FeedEventFilter, FeedEventName, FeedEventPayload } from '../types';
11
+ import type { FeedEventFilter, FeedEventName, FeedEventPayload } from '@/types';
12
12
 
13
13
  /**
14
14
  * feed 事件订阅句柄
@@ -6,4 +6,5 @@
6
6
  */
7
7
 
8
8
  export * from './feed-event.port';
9
+
9
10
  export * from './action-executor.port';
@@ -5,6 +5,7 @@
5
5
  * 供 execution 子插件注册 action handler 时使用。
6
6
  *
7
7
  * 支持的 action 类型:
8
+ *
8
9
  * - feed.applyTags:调用 feed F-2 applyTags,为条目打标签
9
10
  * - feed.summarize:调用 feed F-3 summarize,生成摘要
10
11
  * - llm.complete:调用 agent LlmCompletePort,完成文本
@@ -15,7 +16,7 @@
15
16
 
16
17
  import { z } from 'zod';
17
18
 
18
- import { ACTION_TYPES } from '../constants/workflow';
19
+ import { ACTION_TYPES } from '@/constants/workflow';
19
20
 
20
21
  /**
21
22
  * action 类型 Schema
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import { NODE_TYPES } from '../constants/workflow';
3
+ import { NODE_TYPES } from '@/constants/workflow';
4
4
 
5
5
  export const workflowNodeDtoSchema = z.object({
6
6
  nodeId: z.string(),
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { z } from 'zod';
14
14
 
15
- import { FEED_EVENT_NAMES } from '../constants/workflow';
15
+ import { FEED_EVENT_NAMES } from '@/constants/workflow';
16
16
 
17
17
  /**
18
18
  * feed 事件名称 Schema
@@ -1,8 +1,15 @@
1
1
  export * from './definition.schema';
2
+
2
3
  export * from './workflow-doc.schema';
4
+
3
5
  export * from './instance.schema';
6
+
4
7
  export * from './trigger.schema';
8
+
5
9
  export * from './monitoring.schema';
10
+
6
11
  export * from './feed-event-trigger.schema';
12
+
7
13
  export * from './action-executor.schema';
14
+
8
15
  export * from './workflow-doc.schema';
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import { INSTANCE_STATUSES, NODE_EXECUTION_STATUSES } from '../constants/workflow';
3
+ import { INSTANCE_STATUSES, NODE_EXECUTION_STATUSES } from '@/constants/workflow';
4
4
 
5
5
  export const instanceStatusDtoSchema = z.enum(INSTANCE_STATUSES);
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import { ALERT_TYPES, METRIC_TYPES } from '../constants/workflow';
3
+ import { ALERT_TYPES, METRIC_TYPES } from '@/constants/workflow';
4
4
 
5
5
  export const alertTypeDtoSchema = z.enum(ALERT_TYPES);
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import { TRIGGER_TYPES } from '../constants/workflow';
3
+ import { TRIGGER_TYPES } from '@/constants/workflow';
4
4
 
5
5
  export const triggerTypeDtoSchema = z.enum(TRIGGER_TYPES);
6
6
 
@@ -26,7 +26,8 @@ const KEBAB_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26
26
  /**
27
27
  * 工作项指针备选:`task:<仓>/<slug>`(task 台账引用)或 `ref:<仓>/<仓内路径>`
28
28
  */
29
- const ITEM_REF_ALTERNATION = 'task:[a-z0-9][a-z0-9-]*\\/[a-z0-9\\/-]+|ref:[a-z0-9][a-z0-9-]*\\/[a-z0-9\\/. _-]+';
29
+ const ITEM_REF_ALTERNATION =
30
+ 'task:[a-z0-9][a-z0-9-]*\\/[a-z0-9\\/-]+|ref:[a-z0-9][a-z0-9-]*\\/[a-z0-9\\/. _-]+';
30
31
 
31
32
  /**
32
33
  * 工作项行:`<itemId> | <标题> | <指针> | <状态>`(四段制)
@@ -362,4 +363,5 @@ export function parseGateLine(line: string): WorkflowGate | null {
362
363
  /**
363
364
  * 家族引用(条目档 frontmatter 的 ref 字段值)
364
365
  */
365
- export const WORKFLOW_REF_PATTERN = /^workflow:(global|[a-z0-9][a-z0-9-]*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
366
+ export const WORKFLOW_REF_PATTERN =
367
+ /^workflow:(global|[a-z0-9][a-z0-9-]*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
@@ -14,7 +14,7 @@ import type {
14
14
  feedSummarizeActionSchema,
15
15
  llmCompleteActionSchema,
16
16
  webhookSendActionSchema,
17
- } from '../schemas';
17
+ } from '@/schemas';
18
18
  import type { z } from 'zod';
19
19
 
20
20
  /**
@@ -10,7 +10,7 @@ import type {
10
10
  feedEventNameSchema,
11
11
  feedEventPayloadSchema,
12
12
  feedEventTriggerSchema,
13
- } from '../schemas';
13
+ } from '@/schemas';
14
14
  import type { z } from 'zod';
15
15
 
16
16
  /**
@@ -1,6 +1,11 @@
1
1
  export * from './definition';
2
+
2
3
  export * from './instance';
4
+
3
5
  export * from './trigger';
6
+
4
7
  export * from './monitoring';
8
+
5
9
  export * from './feed-event-trigger';
10
+
6
11
  export * from './action-executor';
@@ -1,4 +1,4 @@
1
- import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../constants/workflow';
1
+ import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '@/constants/workflow';
2
2
 
3
3
  export function getPagination(page?: number, pageSize?: number): { offset: number; limit: number } {
4
4
  const limit = Math.min(pageSize ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
@@ -15,5 +15,7 @@ export function formatDate(date: string | Date): string {
15
15
  }
16
16
 
17
17
  export * from './declaration-frontmatter.utils';
18
+
18
19
  export * from './work-item-rules.utils';
20
+
19
21
  export * from './dag.utils';
@@ -1,3 +1,10 @@
1
+ import type {
2
+ WorkflowEdge,
3
+ WorkflowGate,
4
+ WorkflowItem,
5
+ WorkItemStatus,
6
+ } from '@/schemas/workflow-doc.schema';
7
+
1
8
  /**
2
9
  * 工作项判定核(纯函数、零 I/O——输入声明形态,输出视图与违规)
3
10
  *
@@ -6,12 +13,12 @@
6
13
  */
7
14
  import { dependenciesMet } from '@longzai-intelligence-dag/core';
8
15
 
9
- import type { WorkflowEdge, WorkflowGate, WorkflowItem, WorkItemStatus } from '../schemas/workflow-doc.schema';
10
-
11
16
  /**
12
17
  * 工作项合法状态转移表(状态机唯一注册点;供编辑工具与判定器共用的规则真源)
13
18
  */
14
- export const ALLOWED_STATUS_TRANSITIONS: Readonly<Record<WorkItemStatus, readonly WorkItemStatus[]>> = {
19
+ export const ALLOWED_STATUS_TRANSITIONS: Readonly<
20
+ Record<WorkItemStatus, readonly WorkItemStatus[]>
21
+ > = {
15
22
  todo: ['doing', 'blocked', 'dropped'],
16
23
  doing: ['blocked', 'done', 'dropped'],
17
24
  blocked: ['todo', 'doing', 'dropped'],
@@ -41,7 +48,10 @@ export type InvalidTransitionError = {
41
48
  * @param to - 目标状态
42
49
  * @returns 合法时为 null;非法时携带合法后继清单
43
50
  */
44
- export function checkStatusTransition(from: WorkItemStatus, to: WorkItemStatus): InvalidTransitionError | null {
51
+ export function checkStatusTransition(
52
+ from: WorkItemStatus,
53
+ to: WorkItemStatus,
54
+ ): InvalidTransitionError | null {
45
55
  /**
46
56
  * 注册表中的合法后继
47
57
  */
@@ -50,7 +60,10 @@ export function checkStatusTransition(from: WorkItemStatus, to: WorkItemStatus):
50
60
  return { code: 'INVALID_TRANSITION', message: `未知状态:${from}` };
51
61
  }
52
62
  if (!allowed.includes(to)) {
53
- return { code: 'INVALID_TRANSITION', message: `非法转移:${from} → ${to}(合法后继:${allowed.join(' / ')})` };
63
+ return {
64
+ code: 'INVALID_TRANSITION',
65
+ message: `非法转移:${from} → ${to}(合法后继:${allowed.join(' / ')})`,
66
+ };
54
67
  }
55
68
  return null;
56
69
  }
@@ -134,6 +147,7 @@ export type WorkflowView = {
134
147
  * 已闭环状态集合(依赖满足判定基准:done 或 dropped 均不再阻塞后继)
135
148
  *
136
149
  * @param status - 工作项状态
150
+ * @returns 状态为 done 或 dropped 时为 true,否则为 false
137
151
  */
138
152
  function isSettled(status: WorkItemStatus): boolean {
139
153
  return status === 'done' || status === 'dropped';
@@ -145,6 +159,7 @@ function isSettled(status: WorkItemStatus): boolean {
145
159
  * @param items - 工作项清单(声明档现状)
146
160
  * @param edges - 依赖边清单
147
161
  * @param gates - 裁定门清单(含裁定事实)
162
+ * @returns 工作流声明视图,含全部工作项视图与未决裁定清单
148
163
  */
149
164
  export function deriveWorkflowView(
150
165
  items: readonly WorkflowItem[],
@@ -159,7 +174,11 @@ export function deriveWorkflowView(
159
174
  /**
160
175
  * 各工作项挂靠的未批准门(ruling.status 非 approved 即阻塞)
161
176
  */
162
- const unapprovedGateAt = new Set(gates.filter((gate) => gate.ruling.status !== 'approved' && gate.at !== 'workflow').map((gate) => gate.at));
177
+ const unapprovedGateAt = new Set(
178
+ gates
179
+ .filter((gate) => gate.ruling.status !== 'approved' && gate.at !== 'workflow')
180
+ .map((gate) => gate.at),
181
+ );
163
182
 
164
183
  /**
165
184
  * 工作项视图累积