@longzai-intelligence-issues/ledger 0.0.1

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 (50) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +26 -0
  3. package/dist/index.d.ts +2123 -0
  4. package/dist/index.js +78 -0
  5. package/dist/rolldown-runtime-BqT_7tdF.js +1 -0
  6. package/lzi-builder.config.ts +15 -0
  7. package/lzi-bun.config.ts +8 -0
  8. package/oxlint.config.ts +3 -0
  9. package/package.json +39 -0
  10. package/src/__tests__/close/verify-close.commands.test.ts +560 -0
  11. package/src/__tests__/docs-refs/docs-refs.commands.test.ts +213 -0
  12. package/src/__tests__/fs/mini-glob.utils.test.ts +71 -0
  13. package/src/__tests__/fs/walk-surface.utils.test.ts +170 -0
  14. package/src/__tests__/health/health.commands.test.ts +131 -0
  15. package/src/__tests__/lint/lint.core.test.ts +332 -0
  16. package/src/__tests__/numbering/numbering.commands.test.ts +194 -0
  17. package/src/__tests__/numbering/numbering.core.test.ts +318 -0
  18. package/src/__tests__/parser/guide-fixture.utils.test.ts +45 -0
  19. package/src/__tests__/parser/registry.parser.test.ts +316 -0
  20. package/src/__tests__/prompts/prompts.commands.test.ts +47 -0
  21. package/src/__tests__/template/issue-template.renderer.test.ts +133 -0
  22. package/src/close/evidence-reader.utils.ts +201 -0
  23. package/src/close/green-flip.commands.ts +199 -0
  24. package/src/close/verify-close.commands.ts +676 -0
  25. package/src/docs-refs/docs-refs.commands.ts +693 -0
  26. package/src/freeze/freeze.commands.ts +261 -0
  27. package/src/fs/mini-glob.utils.ts +216 -0
  28. package/src/fs/walk-surface.utils.ts +298 -0
  29. package/src/health/health.commands.ts +327 -0
  30. package/src/index.ts +46 -0
  31. package/src/lint/lint-baseline.commands.ts +147 -0
  32. package/src/lint/lint.core.ts +584 -0
  33. package/src/normalize/normalize-header.commands.ts +361 -0
  34. package/src/numbering/numbering.commands.ts +375 -0
  35. package/src/numbering/numbering.core.ts +685 -0
  36. package/src/parser/format.utils.ts +279 -0
  37. package/src/parser/guide-fixture.utils.ts +117 -0
  38. package/src/parser/registry.parser.ts +679 -0
  39. package/src/prompts/prompts.commands.ts +211 -0
  40. package/src/template/issue-template.renderer.ts +351 -0
  41. package/src/triage/triage.classify.ts +93 -0
  42. package/src/vault/vault.commands.ts +434 -0
  43. package/tsconfig/.cache/build.tsbuildinfo +1 -0
  44. package/tsconfig/.cache/node.tsbuildinfo +1 -0
  45. package/tsconfig/.cache/test.tsbuildinfo +1 -0
  46. package/tsconfig/app.json +13 -0
  47. package/tsconfig/build.json +15 -0
  48. package/tsconfig/node.json +12 -0
  49. package/tsconfig/test.json +15 -0
  50. package/tsconfig.json +23 -0
@@ -0,0 +1,332 @@
1
+ import { parseLziIssuesConfig } from '@longzai-intelligence-issues/config';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ import { runWriteLintBaseline } from '@/lint/lint-baseline.commands';
7
+ import { registerLintRulePack, runLint, scanOrphanSidecars } from '@/lint/lint.core';
8
+ import { renderIssueTemplate } from '@/template/issue-template.renderer';
9
+
10
+ /**
11
+ * 测试夹具根(包内临时目录)
12
+ */
13
+ const FIXTURE_ROOT = join(import.meta.dir, '__tmp-lint__');
14
+
15
+ /**
16
+ * 注册表目录
17
+ */
18
+ const REGISTRY_REL = 'docs/issues';
19
+
20
+ /**
21
+ * 写入注册表文件
22
+ *
23
+ * @param name - 文件名
24
+ * @param content - 文档全文
25
+ */
26
+ function writeIssue(name: string, content: string): void {
27
+ writeFileSync(join(FIXTURE_ROOT, REGISTRY_REL, name), content);
28
+ }
29
+
30
+ /**
31
+ * 基准配置文本(scope root + slug 白名单 + strictLegacy 可变体)
32
+ */
33
+ const BASE_INPUT = {
34
+ schemaVersion: 1,
35
+ scopes: [
36
+ {
37
+ key: 'root',
38
+ title: '全局',
39
+ paths: ['.'],
40
+ registryDir: REGISTRY_REL,
41
+ },
42
+ ],
43
+ };
44
+
45
+ /**
46
+ * 解析配置(覆盖变体)
47
+ */
48
+ const parseConfig = (overrides: Record<string, unknown> = {}) =>
49
+ parseLziIssuesConfig({ ...BASE_INPUT, ...overrides });
50
+
51
+ /**
52
+ * 干净骨架样例(模板 instance 渲染——新案形态)
53
+ */
54
+ const CLEAN_INSTANCE = renderIssueTemplate({
55
+ number: '0100',
56
+ slug: 'clean-case',
57
+ title: '干净样例',
58
+ priority: 'P2',
59
+ scopeTitle: '全局',
60
+ date: '2026-09-14',
61
+ source: '测试夹具',
62
+ });
63
+
64
+ /**
65
+ * 写入夹具目录树(基线三态用例外的全部规则样例)
66
+ */
67
+ function writeFixture(): void {
68
+ rmSync(FIXTURE_ROOT, { recursive: true, force: true });
69
+ mkdirSync(join(FIXTURE_ROOT, REGISTRY_REL), { recursive: true });
70
+
71
+ writeIssue('0100-clean-case.md', CLEAN_INSTANCE);
72
+ writeIssue('0002-no-status.md', '# 0002 - 无状态行\n\n正文\n');
73
+ writeIssue('0003-yellow-noreason.md', '# 0003 - 黄灯无原因\n\n**状态**: 🟡 处理中\n');
74
+ writeIssue('0004-green-nodispo.md', '# 0004 - 绿灯缺处置段\n\n**状态**: 🟢 已处置(完成)\n');
75
+ writeIssue(
76
+ '0005-green-placeholder.md',
77
+ [
78
+ '# 0005 - 处置段占位',
79
+ '',
80
+ '**状态**: 🟢 已处置(完成)',
81
+ '',
82
+ '## 四、处置结果/验证口径',
83
+ '',
84
+ '(处置中)',
85
+ ].join('\n'),
86
+ );
87
+ writeIssue(
88
+ '0006-fake-flip.md',
89
+ [
90
+ '# 0006 - 手工翻绿',
91
+ '',
92
+ '**状态**: 🟢 已处置(完成——判据证据锚点见处置段)',
93
+ '',
94
+ '## 四、处置结果/验证口径',
95
+ '',
96
+ '已修复。',
97
+ ].join('\n'),
98
+ );
99
+ writeIssue('0007-frozen-bad.md', '# 0007 - 冻结不规范\n\n**状态**: 已冻结(临时搁置)\n');
100
+ writeIssue('0008-model-v2.md', '# 0008 - slug 含数字\n\n**状态**: 🔴 待处理\n');
101
+ writeIssue('0009-canceled-noreason.md', '# 0009 - 取消无原因\n\n**状态**: ⚫ 已取消\n');
102
+ writeIssue(
103
+ '0011-blocking-ghost.md',
104
+ '# 0011 - 阻塞引用缺失\n\n**状态**: 🔴 待处理\n\n**阻塞**: [0099](./0099-ghost.md)\n',
105
+ );
106
+ writeIssue('0012-legacy-yellow.md', '# 0012 - 旧格式黄灯\n\n**状态**: 🟡 某旧写法\n');
107
+ writeIssue('0013-中文-slug.md', '# 0013 - 中文 slug\n\n**状态**: 🔴 待处理\n');
108
+ }
109
+
110
+ /**
111
+ * 清理夹具
112
+ */
113
+ function cleanFixture(): void {
114
+ rmSync(FIXTURE_ROOT, { recursive: true, force: true });
115
+ }
116
+
117
+ beforeEach(writeFixture);
118
+
119
+ afterEach(cleanFixture);
120
+
121
+ describe('runLint 核心规则', () => {
122
+ test('新落盘骨架实跑 lint 零违规(模板零漂移机检承诺)', () => {
123
+ const result = runLint({ root: FIXTURE_ROOT, config: parseConfig() });
124
+
125
+ const cleanViolations = result.violations.filter((v) => v.file.includes('0100-clean-case'));
126
+
127
+ expect(cleanViolations).toEqual([]);
128
+ });
129
+
130
+ test('各规则正例逐一命中', () => {
131
+ const result = runLint({ root: FIXTURE_ROOT, config: parseConfig() });
132
+
133
+ /**
134
+ * 规则 → 命中文件断言表
135
+ */
136
+ const expected: Record<string, string> = {
137
+ 'status-line-missing': '0002-no-status.md',
138
+ 'yellow-no-pending-reason': '0003-yellow-noreason.md',
139
+ 'green-no-disposition-section': '0004-green-nodispo.md',
140
+ 'green-disposition-placeholder': '0005-green-placeholder.md',
141
+ 'green-canonical-no-evidence-block': '0006-fake-flip.md',
142
+ 'frozen-format': '0007-frozen-bad.md',
143
+ 'frozen-no-adjudication-note': '0007-frozen-bad.md',
144
+ 'issue-slug-contains-digit': '0008-model-v2.md',
145
+ 'issue-slug-non-ascii': '0013-中文-slug.md',
146
+ 'canceled-no-reason': '0009-canceled-noreason.md',
147
+ 'blocking-ref-unknown': '0011-blocking-ghost.md',
148
+ };
149
+
150
+ for (const [rule, filePart] of Object.entries(expected)) {
151
+ expect(
152
+ result.violations.some((v) => v.rule === rule && v.file.includes(filePart)),
153
+ `${rule} 应命中 ${filePart}`,
154
+ ).toBe(true);
155
+ }
156
+ });
157
+
158
+ test('legacy 变体仅 strictLegacy 计违规', () => {
159
+ const off = runLint({ root: FIXTURE_ROOT, config: parseConfig() });
160
+
161
+ expect(off.violations.some((v) => v.rule === 'status-line-legacy-format')).toBe(false);
162
+
163
+ const on = runLint({
164
+ root: FIXTURE_ROOT,
165
+ config: parseConfig({ lint: { strictLegacy: true } }),
166
+ });
167
+
168
+ expect(
169
+ on.violations.some(
170
+ (v) => v.rule === 'status-line-legacy-format' && v.file.includes('0012-legacy-yellow'),
171
+ ),
172
+ ).toBe(true);
173
+ });
174
+
175
+ test('slug 白名单豁免', () => {
176
+ const result = runLint({
177
+ root: FIXTURE_ROOT,
178
+ config: parseConfig({
179
+ slugDigitAllowlist: [{ glob: 'docs/issues/0008-model-v2.md', reason: '既有编号语义' }],
180
+ }),
181
+ });
182
+
183
+ expect(result.violations.some((v) => v.rule === 'issue-slug-contains-digit')).toBe(false);
184
+ });
185
+
186
+ test('orphan-sidecar:评论边车指向不存在 issue', () => {
187
+ mkdirSync(join(FIXTURE_ROOT, '.lzi/issues/comments/root'), { recursive: true });
188
+
189
+ writeFileSync(
190
+ join(FIXTURE_ROOT, '.lzi/issues/comments/root/0100.json'),
191
+ JSON.stringify({ items: [{ issueId: '0099' }] }),
192
+ );
193
+
194
+ const result = runLint({ root: FIXTURE_ROOT, config: parseConfig() });
195
+
196
+ expect(result.violations.some((v) => v.rule === 'orphan-sidecar')).toBe(true);
197
+ });
198
+
199
+ test('目录级孤儿边车扫描(注册表无对应文件)', () => {
200
+ mkdirSync(join(FIXTURE_ROOT, '.lzi/issues/comments/root'), { recursive: true });
201
+
202
+ writeFileSync(
203
+ join(FIXTURE_ROOT, '.lzi/issues/comments/root/0098.json'),
204
+ JSON.stringify({ items: [] }),
205
+ );
206
+
207
+ const orphans = scanOrphanSidecars(FIXTURE_ROOT, parseConfig());
208
+
209
+ expect(orphans).toHaveLength(1);
210
+ expect(orphans[0]?.rule).toBe('orphan-sidecar');
211
+ expect(orphans[0]?.message).toContain('0098');
212
+ });
213
+ });
214
+
215
+ describe('runLint 规则包扩展点', () => {
216
+ test('注册并启用的规则包生效;未启用不生效', () => {
217
+ registerLintRulePack('fixture-pack', [
218
+ {
219
+ name: 'fixture-custom-rule',
220
+ evaluate: (record) => (record.id === '0100' ? '夹具自定义规则命中' : null),
221
+ },
222
+ ]);
223
+
224
+ const enabled = runLint({
225
+ root: FIXTURE_ROOT,
226
+ config: parseConfig({ lint: { rulePacks: ['fixture-pack'] } }),
227
+ });
228
+
229
+ expect(enabled.violations.some((v) => v.rule === 'fixture-custom-rule')).toBe(true);
230
+
231
+ const disabled = runLint({ root: FIXTURE_ROOT, config: parseConfig() });
232
+
233
+ expect(disabled.violations.some((v) => v.rule === 'fixture-custom-rule')).toBe(false);
234
+ });
235
+ });
236
+
237
+ describe('runLint 棘轮基线三态', () => {
238
+ test('命中豁免 suppressed / 基线外 active / 陈旧条目 stale', () => {
239
+ /**
240
+ * 豁免一条现行违规(0003 黄灯无原因)+ 登记一条已修复存量(陈旧)
241
+ */
242
+ const config = parseConfig({
243
+ lint: {
244
+ baseline: [
245
+ {
246
+ file: `${REGISTRY_REL}/0003-yellow-noreason.md`,
247
+ rule: 'yellow-no-pending-reason',
248
+ issue: '0050',
249
+ },
250
+ {
251
+ file: `${REGISTRY_REL}/0888-fixed-legacy.md`,
252
+ rule: 'yellow-no-pending-reason',
253
+ issue: '0051',
254
+ },
255
+ ],
256
+ },
257
+ });
258
+
259
+ const result = runLint({ root: FIXTURE_ROOT, config });
260
+
261
+ expect(result.suppressed).toBe(1);
262
+ expect(result.baselineCount).toBe(2);
263
+
264
+ expect(
265
+ result.violations.some(
266
+ (v) => v.rule === 'yellow-no-pending-reason' && v.file.includes('0003'),
267
+ ),
268
+ ).toBe(false);
269
+
270
+ expect(result.violations.some((v) => v.rule === 'baseline-stale')).toBe(true);
271
+
272
+ expect(
273
+ result.violations.some((v) => v.rule === 'status-line-missing' && v.file.includes('0002')),
274
+ ).toBe(true);
275
+ });
276
+ });
277
+
278
+ describe('runWriteLintBaseline 拒绝扩基线', () => {
279
+ test('存在基线外新增违规拒绝再生', () => {
280
+ const config = parseConfig({
281
+ lint: {
282
+ baseline: [
283
+ {
284
+ file: `${REGISTRY_REL}/0003-yellow-noreason.md`,
285
+ rule: 'yellow-no-pending-reason',
286
+ issue: '0050',
287
+ },
288
+ ],
289
+ },
290
+ });
291
+
292
+ const result = runWriteLintBaseline({
293
+ root: FIXTURE_ROOT,
294
+ config,
295
+ issuePointer: '0060',
296
+ });
297
+
298
+ expect(result.ok).toBe(false);
299
+ expect(result.ok === false && result.error).toContain('拒绝再生');
300
+ });
301
+
302
+ test('无新增违规时再生(现行全集重建基线)', () => {
303
+ /**
304
+ * 基线覆盖全部现行违规的配置——用全集违规预生成
305
+ */
306
+ const allViolations = runLint({
307
+ root: FIXTURE_ROOT,
308
+ config: parseConfig(),
309
+ ignoreBaseline: true,
310
+ }).violations;
311
+
312
+ const config = parseConfig({
313
+ lint: {
314
+ baseline: allViolations.map((v) => ({
315
+ file: v.file,
316
+ rule: v.rule,
317
+ issue: '0050',
318
+ })),
319
+ },
320
+ });
321
+
322
+ const result = runWriteLintBaseline({
323
+ root: FIXTURE_ROOT,
324
+ config,
325
+ issuePointer: '0060',
326
+ });
327
+
328
+ expect(result.ok).toBe(true);
329
+ expect(result.ok === true && result.entryCount).toBe(allViolations.length);
330
+ expect(result.ok === true && result.content).toContain('棘轮只许缩短');
331
+ });
332
+ });
@@ -0,0 +1,194 @@
1
+ import type { LziIssuesConfig } from '@longzai-intelligence-issues/config';
2
+
3
+ import { parseLziIssuesConfig } from '@longzai-intelligence-issues/config';
4
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
5
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+
8
+ import {
9
+ runNumberingAudit,
10
+ runNumberingCheck,
11
+ runNumberingNext,
12
+ } from '@/numbering/numbering.commands';
13
+
14
+ /**
15
+ * 命令测试夹具根(包内临时目录)
16
+ */
17
+ const FIXTURE_ROOT = join(import.meta.dir, '__tmp-numbering__');
18
+
19
+ /**
20
+ * 解析后的测试配置
21
+ */
22
+ const CONFIG: LziIssuesConfig = parseLziIssuesConfig({
23
+ schemaVersion: 1,
24
+ scopes: [
25
+ {
26
+ key: 'root',
27
+ title: '全局',
28
+ paths: ['.'],
29
+ registryDir: 'docs/issues',
30
+ numbering: { reserved: [{ number: '0081', reason: '历史空洞不回收' }] },
31
+ slugDigitPolicy: 'forbid',
32
+ },
33
+ {
34
+ key: 'domains',
35
+ title: '领域层',
36
+ paths: ['domains/**'],
37
+ registryDir: 'docs/domain-issues',
38
+ },
39
+ ],
40
+ numbering: {
41
+ audit: {
42
+ roots: ['docs'],
43
+ numberishPattern: '^\\d{1,4}[-._]',
44
+ pending: [{ glob: 'docs/reports/*.md', reason: '报告族规划中' }],
45
+ },
46
+ },
47
+ slugDigitAllowlist: [],
48
+ });
49
+
50
+ /**
51
+ * 写入夹具目录树
52
+ */
53
+ function writeFixture(): void {
54
+ rmSync(FIXTURE_ROOT, { recursive: true, force: true });
55
+
56
+ for (const dir of ['docs/issues', 'docs/domain-issues', 'docs/reports', 'docs/decisions']) {
57
+ mkdirSync(join(FIXTURE_ROOT, dir), { recursive: true });
58
+ }
59
+
60
+ const files: Record<string, string> = {
61
+ 'docs/issues/0001-first-case.md': '',
62
+ 'docs/issues/0002-second-case.md': '',
63
+ 'docs/issues/README.md': '',
64
+ 'docs/domain-issues/0001-domain-case.md': '',
65
+ 'docs/reports/0003-report.md': '',
66
+ 'docs/decisions/0004-decision.md': '',
67
+ };
68
+
69
+ for (const [rel, content] of Object.entries(files)) {
70
+ writeFileSync(join(FIXTURE_ROOT, rel), content);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 清理夹具
76
+ */
77
+ function cleanFixture(): void {
78
+ rmSync(FIXTURE_ROOT, { recursive: true, force: true });
79
+ }
80
+
81
+ beforeEach(writeFixture);
82
+
83
+ afterEach(cleanFixture);
84
+
85
+ describe('runNumberingNext', () => {
86
+ test('scope 模式:max+1 顺延', () => {
87
+ const result = runNumberingNext({ root: FIXTURE_ROOT, config: CONFIG, scopeKey: 'root' });
88
+
89
+ expect(result.ok).toBe(true);
90
+ expect(result.ok === true && result.number).toBe('0003');
91
+ expect(result.ok === true && result.target).toBe('root');
92
+ });
93
+
94
+ test('dir 模式:目录即目标', () => {
95
+ const result = runNumberingNext({
96
+ root: FIXTURE_ROOT,
97
+ config: CONFIG,
98
+ dirRel: 'docs/domain-issues',
99
+ });
100
+
101
+ expect(result.ok === true && result.number).toBe('0002');
102
+ });
103
+
104
+ test('未登记 scope 报错', () => {
105
+ const result = runNumberingNext({ root: FIXTURE_ROOT, config: CONFIG, scopeKey: 'ghost' });
106
+
107
+ expect(result.ok).toBe(false);
108
+ expect(result.ok === false && result.error).toContain('未登记');
109
+ });
110
+
111
+ test('未登记目录报错', () => {
112
+ const result = runNumberingNext({ root: FIXTURE_ROOT, config: CONFIG, dirRel: 'docs/unknown' });
113
+
114
+ expect(result.ok).toBe(false);
115
+ });
116
+
117
+ test('互斥校验', () => {
118
+ const both = runNumberingNext({
119
+ root: FIXTURE_ROOT,
120
+ config: CONFIG,
121
+ scopeKey: 'root',
122
+ dirRel: 'docs/issues',
123
+ });
124
+
125
+ expect(both.ok).toBe(false);
126
+ });
127
+ });
128
+
129
+ describe('runNumberingCheck', () => {
130
+ test('全量档:干净夹具零违例', () => {
131
+ const result = runNumberingCheck({ root: FIXTURE_ROOT, config: CONFIG });
132
+
133
+ expect(result.findings).toEqual([]);
134
+ expect(result.scopeCount).toBe(2);
135
+ expect(result.scannedFiles).toBe(4);
136
+ });
137
+
138
+ test('增量档:单目录判定', () => {
139
+ const result = runNumberingCheck({
140
+ root: FIXTURE_ROOT,
141
+ config: CONFIG,
142
+ dirRel: 'docs/issues',
143
+ });
144
+
145
+ expect(result.scopeCount).toBe(1);
146
+ expect(result.scannedFiles).toBe(3);
147
+ });
148
+
149
+ test('保留号占用全量档报违规', () => {
150
+ writeFileSync(join(FIXTURE_ROOT, 'docs/issues/0081-hole.md'), '');
151
+
152
+ const result = runNumberingCheck({ root: FIXTURE_ROOT, config: CONFIG });
153
+
154
+ expect(result.findings.some((f) => f.kind === 'reservedOccupied')).toBe(true);
155
+ });
156
+
157
+ test('撞号在增量档同样拦截', () => {
158
+ writeFileSync(join(FIXTURE_ROOT, 'docs/domain-issues/0001-dup-a.md'), '');
159
+ writeFileSync(join(FIXTURE_ROOT, 'docs/domain-issues/0001-dup-b.md'), '');
160
+
161
+ const result = runNumberingCheck({
162
+ root: FIXTURE_ROOT,
163
+ config: CONFIG,
164
+ dirRel: 'docs/domain-issues',
165
+ });
166
+
167
+ expect(result.findings.some((f) => f.kind === 'duplicate')).toBe(true);
168
+ });
169
+ });
170
+
171
+ describe('runNumberingAudit', () => {
172
+ test('未覆盖编号区按目录聚合,pending 覆盖免报', () => {
173
+ const result = runNumberingAudit({ root: FIXTURE_ROOT, config: CONFIG });
174
+
175
+ expect(result.findings).toHaveLength(1);
176
+ expect(result.findings[0]?.kind).toBe('coverageGap');
177
+ expect(result.findings[0]?.message).toContain('docs/decisions');
178
+ expect(result.findings[0]?.message).not.toContain('docs/reports');
179
+ expect(result.scannedFiles).toBe(6);
180
+ });
181
+
182
+ test('审计根缺失抛错(防扫描面缩水)', () => {
183
+ expect(() =>
184
+ runNumberingAudit({
185
+ root: FIXTURE_ROOT,
186
+ config: parseLziIssuesConfig({
187
+ schemaVersion: 1,
188
+ scopes: CONFIG.scopes,
189
+ numbering: { audit: { roots: ['missing-root'] } },
190
+ }),
191
+ }),
192
+ ).toThrow('扫描面目录缺失');
193
+ });
194
+ });