@longzai-intelligence-issues/ledger 0.0.1 → 0.0.2
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/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-lint.log +7 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +317 -7
- package/dist/index.js +23 -22
- package/package.json +4 -8
- package/src/__tests__/close/verify-close.commands.test.ts +57 -0
- package/src/__tests__/lint/lint.core.test.ts +23 -0
- package/src/__tests__/migrate/migrate-legacy.commands.test.ts +519 -0
- package/src/__tests__/template/issue-template.renderer.test.ts +10 -0
- package/src/index.ts +4 -0
- package/src/lint/lint.core.ts +12 -1
- package/src/migrate/migrate-legacy.commands.ts +417 -0
- package/src/migrate/migrate-legacy.core.ts +812 -0
- package/src/normalize/normalize-header.commands.ts +99 -8
- package/src/numbering/numbering.core.ts +3 -3
- package/src/parser/format.utils.ts +76 -0
- package/src/template/issue-template.renderer.ts +15 -4
- package/tsconfig/.cache/build.tsbuildinfo +1 -1
- package/tsconfig/.cache/node.tsbuildinfo +1 -1
- package/tsconfig/.cache/test.tsbuildinfo +1 -1
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { parseLziIssuesConfig } from '@longzai-intelligence-issues/config';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { migrateLegacyFile } from '@/migrate/migrate-legacy.commands';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 测试夹具根(包内临时目录)
|
|
10
|
+
*/
|
|
11
|
+
const FIXTURE_ROOT = join(import.meta.dir, '__tmp-migrate__');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 注册表目录
|
|
15
|
+
*/
|
|
16
|
+
const REGISTRY_REL = 'docs/issues';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 基准配置(scope root)
|
|
20
|
+
*/
|
|
21
|
+
const config = parseLziIssuesConfig({
|
|
22
|
+
schemaVersion: 1,
|
|
23
|
+
scopes: [
|
|
24
|
+
{
|
|
25
|
+
key: 'root',
|
|
26
|
+
title: '项目全局',
|
|
27
|
+
paths: ['.'],
|
|
28
|
+
registryDir: REGISTRY_REL,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 写入注册表文件
|
|
35
|
+
*
|
|
36
|
+
* @param name - 文件名
|
|
37
|
+
* @param content - 文档全文
|
|
38
|
+
*/
|
|
39
|
+
function writeIssue(name: string, content: string): void {
|
|
40
|
+
mkdirSync(join(FIXTURE_ROOT, REGISTRY_REL), { recursive: true });
|
|
41
|
+
|
|
42
|
+
writeFileSync(join(FIXTURE_ROOT, REGISTRY_REL, name), content);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 基准迁移入参(dry-run 只读形态)
|
|
47
|
+
*
|
|
48
|
+
* @param file - 仓库相对文件路径
|
|
49
|
+
* @returns 命令入参
|
|
50
|
+
*/
|
|
51
|
+
function input(file: string) {
|
|
52
|
+
return {
|
|
53
|
+
root: FIXTURE_ROOT,
|
|
54
|
+
file,
|
|
55
|
+
config,
|
|
56
|
+
defaultPriority: 'P2' as const,
|
|
57
|
+
defaultSource: '格式清偿迁移回填(2026-09-14):原案未登记来源',
|
|
58
|
+
dryRun: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 列表方言(KTV 主流形态)样例
|
|
64
|
+
*/
|
|
65
|
+
const LIST_DIALECT_GREEN = [
|
|
66
|
+
'# 0400 - roles-matrix 观测两行 deny 错误码红于 deny-first 校验先行分叉族',
|
|
67
|
+
'',
|
|
68
|
+
'- 日期:2026-08-27',
|
|
69
|
+
'- 来源:docs/issues/0390 治理 sweep 复跑发现',
|
|
70
|
+
'- 状态:**已治理(2026-08-27 同日:守门与口径对齐完成)**',
|
|
71
|
+
'- 严重程度:中',
|
|
72
|
+
'- 影响包:packages/api',
|
|
73
|
+
'',
|
|
74
|
+
'## 现象',
|
|
75
|
+
'',
|
|
76
|
+
'正文。',
|
|
77
|
+
'',
|
|
78
|
+
'## 验证结果',
|
|
79
|
+
'',
|
|
80
|
+
'验证正文:lint 全绿。',
|
|
81
|
+
].join('\n');
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 表格方言(KTV 最新形态)样例
|
|
85
|
+
*/
|
|
86
|
+
const TABLE_DIALECT = [
|
|
87
|
+
'# 0549 check:testing-coverage 门禁存在 15 处既有违规',
|
|
88
|
+
'',
|
|
89
|
+
'| 元数据 | 内容 |',
|
|
90
|
+
'| --- | --- |',
|
|
91
|
+
'| Issue 编号 | 0549 |',
|
|
92
|
+
'| 日期 | 2026-09-14 |',
|
|
93
|
+
'| 状态 | 已处理 |',
|
|
94
|
+
'| 来源 | 门禁收尾时发现 |',
|
|
95
|
+
'| 配对决策 | 无(纯欠账清偿) |',
|
|
96
|
+
'',
|
|
97
|
+
'## 现象',
|
|
98
|
+
'',
|
|
99
|
+
'正文。',
|
|
100
|
+
'',
|
|
101
|
+
'## 治理记录',
|
|
102
|
+
'',
|
|
103
|
+
'治理正文。',
|
|
104
|
+
].join('\n');
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 粗体旧式(KTV 最老形态)样例
|
|
108
|
+
*/
|
|
109
|
+
const BOLD_DIALECT_RED = [
|
|
110
|
+
'# StreamableFile 与 typedResponse 类型不兼容问题',
|
|
111
|
+
'',
|
|
112
|
+
'**日期**: 2026-06-13',
|
|
113
|
+
'**状态**: 未处理',
|
|
114
|
+
'**涉及文件**: `apps/nest-server/src/x.ts`',
|
|
115
|
+
'',
|
|
116
|
+
'## 问题描述',
|
|
117
|
+
'',
|
|
118
|
+
'正文。',
|
|
119
|
+
].join('\n');
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
rmSync(FIXTURE_ROOT, { recursive: true, force: true });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
rmSync(FIXTURE_ROOT, { recursive: true, force: true });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('migrateLegacyFile 存量清偿(dry-run 只读)', () => {
|
|
130
|
+
test('列表方言:状态规范化 + 字段别名 + 处置段标题链 + 必填回填', () => {
|
|
131
|
+
writeIssue('0400-roles-matrix-deny-divergence.md', LIST_DIALECT_GREEN);
|
|
132
|
+
|
|
133
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0400-roles-matrix-deny-divergence.md`));
|
|
134
|
+
|
|
135
|
+
expect(result.outcome).toBe('rewritten');
|
|
136
|
+
expect(result.content).toContain(
|
|
137
|
+
'**状态**: 🟢 已处置(2026-08-27 同日:守门与口径对齐完成;原「已治理」)',
|
|
138
|
+
);
|
|
139
|
+
expect(result.content).toContain('**立案日期**: 2026-08-27');
|
|
140
|
+
expect(result.content).toContain('**优先级**: P2');
|
|
141
|
+
expect(result.content).toContain('**所属域**: 项目全局');
|
|
142
|
+
expect(result.content).toContain('**来源**: docs/issues/0390 治理 sweep 复跑发现');
|
|
143
|
+
expect(result.content).toContain('**严重程度**: 中');
|
|
144
|
+
expect(result.content).toContain('**影响包**: packages/api');
|
|
145
|
+
expect(result.content).toContain('## 处置结果/验证口径');
|
|
146
|
+
expect(result.content).toContain('验证正文:lint 全绿。');
|
|
147
|
+
expect(result.content).toContain('## 现象');
|
|
148
|
+
expect(result.content.startsWith('# 0400 - ')).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('表格方言:数据行提取为字段、脚手架丢弃、治理记录对齐处置段', () => {
|
|
152
|
+
writeIssue('0549-testing-coverage-gate.md', TABLE_DIALECT);
|
|
153
|
+
|
|
154
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0549-testing-coverage-gate.md`));
|
|
155
|
+
|
|
156
|
+
expect(result.outcome).toBe('rewritten');
|
|
157
|
+
expect(result.content).toContain('**状态**: 🟢 已处置(原「已处理」)');
|
|
158
|
+
expect(result.content).toContain('**Issue 编号**: 0549');
|
|
159
|
+
expect(result.content).toContain('**配对决策**: 无(纯欠账清偿)');
|
|
160
|
+
expect(result.content).toContain('## 处置结果/治理记录');
|
|
161
|
+
expect(result.content.includes('| --- | --- |')).toBe(false);
|
|
162
|
+
expect(result.content.includes('| 元数据 | 内容 |')).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('粗体旧式:红灯映射 + H1 补编号 + 无验证段红灯不追加处置段', () => {
|
|
166
|
+
writeIssue('0001-streamablefile-typed-response.md', BOLD_DIALECT_RED);
|
|
167
|
+
|
|
168
|
+
const result = migrateLegacyFile(
|
|
169
|
+
input(`${REGISTRY_REL}/0001-streamablefile-typed-response.md`),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect(result.outcome).toBe('rewritten');
|
|
173
|
+
expect(result.content).toContain('**状态**: 🔴 待处理(原「未处理」)');
|
|
174
|
+
expect(result.content).toContain('**立案日期**: 2026-06-13');
|
|
175
|
+
expect(result.content).toContain('**来源**: 格式清偿迁移回填(2026-09-14):原案未登记来源');
|
|
176
|
+
expect(result.content).toContain('**涉及文件**: `apps/nest-server/src/x.ts`');
|
|
177
|
+
expect(result.content.startsWith('# 0001 - StreamableFile')).toBe(true);
|
|
178
|
+
expect(result.content.includes('处置结果/')).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('绿灯但处置段占位:降级黄灯并给挂起原因', () => {
|
|
182
|
+
writeIssue(
|
|
183
|
+
'0300-green-placeholder.md',
|
|
184
|
+
[
|
|
185
|
+
'# 0300 - 样例',
|
|
186
|
+
'',
|
|
187
|
+
'- 日期:2026-08-01',
|
|
188
|
+
'- 状态:已治理',
|
|
189
|
+
'- 来源:会话记录',
|
|
190
|
+
'',
|
|
191
|
+
'## 验证结果',
|
|
192
|
+
'',
|
|
193
|
+
'(待回填)',
|
|
194
|
+
].join('\n'),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0300-green-placeholder.md`));
|
|
198
|
+
|
|
199
|
+
expect(result.outcome).toBe('rewritten');
|
|
200
|
+
expect(result.content).toContain(
|
|
201
|
+
'**状态**: 🟡 处理中(历史标记已处置但验证口径为占位,清偿时待核实;原「已治理」)',
|
|
202
|
+
);
|
|
203
|
+
expect(result.content).toContain('## 处置结果/验证口径');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('候选段词汇表:落地治理/治理记录(带日期)/验证(治理后)/修复 均命中改名链,计划性段名不误吞', () => {
|
|
207
|
+
/**
|
|
208
|
+
* 落地治理族样例(KTV 真实段名词汇)
|
|
209
|
+
*/
|
|
210
|
+
writeIssue(
|
|
211
|
+
'0320-landing-governance.md',
|
|
212
|
+
[
|
|
213
|
+
'# 0320 - 样例',
|
|
214
|
+
'',
|
|
215
|
+
'- 日期:2026-08-03',
|
|
216
|
+
'- 状态:已治理',
|
|
217
|
+
'- 来源:会话记录',
|
|
218
|
+
'',
|
|
219
|
+
'## 现象',
|
|
220
|
+
'',
|
|
221
|
+
'正文。',
|
|
222
|
+
'',
|
|
223
|
+
'## 落地治理',
|
|
224
|
+
'',
|
|
225
|
+
'治理正文。',
|
|
226
|
+
'',
|
|
227
|
+
'## 修复方向',
|
|
228
|
+
'',
|
|
229
|
+
'计划正文(不应被改名)。',
|
|
230
|
+
].join('\n'),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 治理记录带日期样例
|
|
235
|
+
*/
|
|
236
|
+
writeIssue(
|
|
237
|
+
'0321-dated-record.md',
|
|
238
|
+
[
|
|
239
|
+
'# 0321 - 样例',
|
|
240
|
+
'',
|
|
241
|
+
'- 日期:2026-08-04',
|
|
242
|
+
'- 状态:已治理',
|
|
243
|
+
'- 来源:会话记录',
|
|
244
|
+
'',
|
|
245
|
+
'## 现象',
|
|
246
|
+
'',
|
|
247
|
+
'正文。',
|
|
248
|
+
'',
|
|
249
|
+
'## 治理记录(2026-08-31)',
|
|
250
|
+
'',
|
|
251
|
+
'记录正文。',
|
|
252
|
+
].join('\n'),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const first = migrateLegacyFile(input(`${REGISTRY_REL}/0320-landing-governance.md`));
|
|
256
|
+
|
|
257
|
+
expect(first.outcome).toBe('rewritten');
|
|
258
|
+
expect(first.content).toContain('## 处置结果/治理记录');
|
|
259
|
+
expect(first.content).toContain('## 修复方向');
|
|
260
|
+
expect(first.content).toContain('计划正文(不应被改名)。');
|
|
261
|
+
expect(first.content.includes('处置结果/验证口径')).toBe(false);
|
|
262
|
+
|
|
263
|
+
const second = migrateLegacyFile(input(`${REGISTRY_REL}/0321-dated-record.md`));
|
|
264
|
+
|
|
265
|
+
expect(second.outcome).toBe('rewritten');
|
|
266
|
+
expect(second.content).toContain('## 处置结果/治理记录');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('绿灯无任何候选验证段:追加处置段与事实回填说明', () => {
|
|
270
|
+
writeIssue(
|
|
271
|
+
'0310-green-nosection.md',
|
|
272
|
+
[
|
|
273
|
+
'# 0310 - 样例',
|
|
274
|
+
'',
|
|
275
|
+
'- 日期:2026-08-02',
|
|
276
|
+
'- 状态:已解决',
|
|
277
|
+
'- 来源:会话记录',
|
|
278
|
+
'',
|
|
279
|
+
'## 解决方案',
|
|
280
|
+
'',
|
|
281
|
+
'改了 X。',
|
|
282
|
+
].join('\n'),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0310-green-nosection.md`));
|
|
286
|
+
|
|
287
|
+
expect(result.outcome).toBe('rewritten');
|
|
288
|
+
expect(result.content).toContain('## 处置结果/变更记录');
|
|
289
|
+
expect(result.content).toContain('## 处置结果/验证口径');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('无状态行且未注入灯位:skipped 交人工', () => {
|
|
293
|
+
writeIssue('0547-anomaly.md', '# 0547 · 无元信息异类\n\n## 现象\n\n正文。\n');
|
|
294
|
+
|
|
295
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0547-anomaly.md`));
|
|
296
|
+
|
|
297
|
+
expect(result.outcome).toBe('skipped');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('无状态行 + status 注入(green):规范标签落位且处置段对齐执行', () => {
|
|
301
|
+
writeIssue(
|
|
302
|
+
'0350-status-inject.md',
|
|
303
|
+
[
|
|
304
|
+
'# Git 换行符问题分析与处理',
|
|
305
|
+
'',
|
|
306
|
+
'## 问题描述',
|
|
307
|
+
'',
|
|
308
|
+
'正文。',
|
|
309
|
+
'',
|
|
310
|
+
'## 解决方案',
|
|
311
|
+
'',
|
|
312
|
+
'已落地 .gitattributes 统一换行。',
|
|
313
|
+
].join('\n'),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const result = migrateLegacyFile({
|
|
317
|
+
...input(`${REGISTRY_REL}/0350-status-inject.md`),
|
|
318
|
+
status: 'green',
|
|
319
|
+
defaultFilingDate: '2026-06-13',
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(result.outcome).toBe('rewritten');
|
|
323
|
+
expect(result.content).toContain(
|
|
324
|
+
'**状态**: 🟢 已处置(存量清偿定档:原案无状态行,依结案内容人工定档)',
|
|
325
|
+
);
|
|
326
|
+
expect(result.content).toContain('**立案日期**: 2026-06-13');
|
|
327
|
+
expect(result.content).toContain('## 处置结果/变更记录');
|
|
328
|
+
expect(result.content).toContain('## 处置结果/验证口径');
|
|
329
|
+
expect(result.content.startsWith('# 0350 - Git 换行符')).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('H1 编号句点分隔符变体(# 0279. 标题)规范为 # 0279 - 标题且守卫通过', () => {
|
|
333
|
+
writeIssue(
|
|
334
|
+
'0279-dot-separator.md',
|
|
335
|
+
[
|
|
336
|
+
'# 0279. 包厢预订配置页默认值全 0:分组缺 defaultValue 建模',
|
|
337
|
+
'',
|
|
338
|
+
'- 日期:2026-08-10',
|
|
339
|
+
'- 状态:已治理',
|
|
340
|
+
'- 来源:会话记录',
|
|
341
|
+
'',
|
|
342
|
+
'## 现象',
|
|
343
|
+
'',
|
|
344
|
+
'正文。',
|
|
345
|
+
'',
|
|
346
|
+
'## 验证结果',
|
|
347
|
+
'',
|
|
348
|
+
'验证正文。',
|
|
349
|
+
].join('\n'),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const result = migrateLegacyFile(input(`${REGISTRY_REL}/0279-dot-separator.md`));
|
|
353
|
+
|
|
354
|
+
expect(result.outcome).toBe('rewritten');
|
|
355
|
+
expect(result.content.startsWith('# 0279 - 包厢预订配置页默认值全 0')).toBe(true);
|
|
356
|
+
expect(result.content.includes('0279.')).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('状态值粗体形态三变体(全包裹/前导无尾随/词中包裹)均可命中词表', () => {
|
|
360
|
+
writeIssue(
|
|
361
|
+
'0330-bold-variants.md',
|
|
362
|
+
[
|
|
363
|
+
'# 0330 - 样例',
|
|
364
|
+
'',
|
|
365
|
+
'- 日期:2026-08-21',
|
|
366
|
+
'- 状态:**已治理(2026-08-21,commit 57a2d80ef9)',
|
|
367
|
+
'- 来源:会话记录',
|
|
368
|
+
'',
|
|
369
|
+
'## 现象',
|
|
370
|
+
'',
|
|
371
|
+
'正文。',
|
|
372
|
+
'',
|
|
373
|
+
'## 治理记录',
|
|
374
|
+
'',
|
|
375
|
+
'治理正文。',
|
|
376
|
+
].join('\n'),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
writeIssue(
|
|
380
|
+
'0331-bold-word-wrap.md',
|
|
381
|
+
[
|
|
382
|
+
'# 0331 - 样例',
|
|
383
|
+
'',
|
|
384
|
+
'- 日期:2026-08-25',
|
|
385
|
+
'- 状态:**已治理**(2026-08-25 同日收口)',
|
|
386
|
+
'- 来源:会话记录',
|
|
387
|
+
'',
|
|
388
|
+
'## 现象',
|
|
389
|
+
'',
|
|
390
|
+
'正文。',
|
|
391
|
+
'',
|
|
392
|
+
'## 验证结果',
|
|
393
|
+
'',
|
|
394
|
+
'验证正文。',
|
|
395
|
+
].join('\n'),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const first = migrateLegacyFile(input(`${REGISTRY_REL}/0330-bold-variants.md`));
|
|
399
|
+
|
|
400
|
+
expect(first.outcome).toBe('rewritten');
|
|
401
|
+
expect(first.content).toContain(
|
|
402
|
+
'**状态**: 🟢 已处置(2026-08-21,commit 57a2d80ef9;原「已治理」)',
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const second = migrateLegacyFile(input(`${REGISTRY_REL}/0331-bold-word-wrap.md`));
|
|
406
|
+
|
|
407
|
+
expect(second.outcome).toBe('rewritten');
|
|
408
|
+
expect(second.content).toContain('**状态**: 🟢 已处置(2026-08-25 同日收口;原「已治理」)');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('规范词无 emoji 形态(已处置/已完成/已闭合)命中绿系词表', () => {
|
|
412
|
+
writeIssue(
|
|
413
|
+
'0334-canonical-word-no-emoji.md',
|
|
414
|
+
[
|
|
415
|
+
'# 0334 - 样例',
|
|
416
|
+
'',
|
|
417
|
+
'| 元数据 | 内容 |',
|
|
418
|
+
'| --- | --- |',
|
|
419
|
+
'| 状态 | 已处置(单例化修复 + e2e 复绿) |',
|
|
420
|
+
'',
|
|
421
|
+
'## 现象',
|
|
422
|
+
'',
|
|
423
|
+
'正文。',
|
|
424
|
+
'',
|
|
425
|
+
'## 验证结果',
|
|
426
|
+
'',
|
|
427
|
+
'验证正文。',
|
|
428
|
+
].join('\n'),
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const result = migrateLegacyFile({
|
|
432
|
+
...input(`${REGISTRY_REL}/0334-canonical-word-no-emoji.md`),
|
|
433
|
+
defaultFilingDate: '2026-09-01',
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(result.outcome).toBe('rewritten');
|
|
437
|
+
expect(result.content).toContain('**状态**: 🟢 已处置(单例化修复 + e2e 复绿;原「已处置」)');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('扩展词表:待处理/未修复/已收口/已根治/终态达成 等命中', () => {
|
|
441
|
+
writeIssue(
|
|
442
|
+
'0332-vocab-extended.md',
|
|
443
|
+
[
|
|
444
|
+
'# 0332 - 样例',
|
|
445
|
+
'',
|
|
446
|
+
'- 日期:2026-08-22',
|
|
447
|
+
'- 状态:已收口(2026-08-19,D-3 维持现状)',
|
|
448
|
+
'- 来源:会话记录',
|
|
449
|
+
'',
|
|
450
|
+
'## 现象',
|
|
451
|
+
'',
|
|
452
|
+
'正文。',
|
|
453
|
+
].join('\n'),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
writeIssue(
|
|
457
|
+
'0333-vocab-red.md',
|
|
458
|
+
[
|
|
459
|
+
'# 0333 - 样例',
|
|
460
|
+
'',
|
|
461
|
+
'- 日期:2026-08-23',
|
|
462
|
+
'- 状态:未修复(本次质量检查发现并归档,待用户决策修复范围)',
|
|
463
|
+
'- 来源:会话记录',
|
|
464
|
+
'',
|
|
465
|
+
'## 现象',
|
|
466
|
+
'',
|
|
467
|
+
'正文。',
|
|
468
|
+
].join('\n'),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
const first = migrateLegacyFile(input(`${REGISTRY_REL}/0332-vocab-extended.md`));
|
|
472
|
+
|
|
473
|
+
expect(first.outcome).toBe('rewritten');
|
|
474
|
+
expect(first.content).toContain(
|
|
475
|
+
'**状态**: 🟢 已处置(2026-08-19,D-3 维持现状;原「已收口」)',
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const second = migrateLegacyFile(input(`${REGISTRY_REL}/0333-vocab-red.md`));
|
|
479
|
+
|
|
480
|
+
expect(second.outcome).toBe('rewritten');
|
|
481
|
+
expect(second.content).toContain(
|
|
482
|
+
'🔴 待处理(本次质量检查发现并归档,待用户决策修复范围;原「未修复」)',
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('幂等:对已规范产物重跑 unchanged', () => {
|
|
487
|
+
writeIssue('0400-roles-matrix-deny-divergence.md', LIST_DIALECT_GREEN);
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 第一轮落盘
|
|
491
|
+
*/
|
|
492
|
+
const first = migrateLegacyFile({
|
|
493
|
+
...input(`${REGISTRY_REL}/0400-roles-matrix-deny-divergence.md`),
|
|
494
|
+
dryRun: false,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(first.outcome).toBe('rewritten');
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 第二轮对产物重跑
|
|
501
|
+
*/
|
|
502
|
+
const second = migrateLegacyFile(input(`${REGISTRY_REL}/0400-roles-matrix-deny-divergence.md`));
|
|
503
|
+
|
|
504
|
+
expect(second.outcome).toBe('unchanged');
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('dry-run 不落盘', () => {
|
|
508
|
+
writeIssue('0001-streamablefile-typed-response.md', BOLD_DIALECT_RED);
|
|
509
|
+
|
|
510
|
+
migrateLegacyFile(input(`${REGISTRY_REL}/0001-streamablefile-typed-response.md`));
|
|
511
|
+
|
|
512
|
+
expect(
|
|
513
|
+
readFileSync(
|
|
514
|
+
join(FIXTURE_ROOT, REGISTRY_REL, '0001-streamablefile-typed-response.md'),
|
|
515
|
+
'utf8',
|
|
516
|
+
),
|
|
517
|
+
).toBe(BOLD_DIALECT_RED);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
@@ -130,4 +130,14 @@ describe('renderRegistryReadmeStub', () => {
|
|
|
130
130
|
expect(stub.includes('🔴 待处理')).toBe(true);
|
|
131
131
|
expect(stub.includes('⚪ 已冻结')).toBe(true);
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
test('指南不在采用仓时(null)回退机器真源提示且不含死链', () => {
|
|
135
|
+
const stub = renderRegistryReadmeStub('项目全局', null);
|
|
136
|
+
|
|
137
|
+
expect(stub.includes('# 项目全局 issue 注册表')).toBe(true);
|
|
138
|
+
expect(stub.includes('lzi-issues template')).toBe(true);
|
|
139
|
+
expect(stub.includes('docs/guides/issue-file-format.md')).toBe(true);
|
|
140
|
+
expect(stub.includes('](../')).toBe(false);
|
|
141
|
+
expect(stub.includes('](../../')).toBe(false);
|
|
142
|
+
});
|
|
133
143
|
});
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,10 @@ export * from './lint/lint.core.js';
|
|
|
29
29
|
|
|
30
30
|
export * from './normalize/normalize-header.commands.js';
|
|
31
31
|
|
|
32
|
+
export * from './migrate/migrate-legacy.commands.js';
|
|
33
|
+
|
|
34
|
+
export * from './migrate/migrate-legacy.core.js';
|
|
35
|
+
|
|
32
36
|
export * from './numbering/numbering.commands.js';
|
|
33
37
|
|
|
34
38
|
export * from './numbering/numbering.core.js';
|
package/src/lint/lint.core.ts
CHANGED
|
@@ -259,6 +259,17 @@ export const CORE_LINT_RULES: readonly LintRule[] = [
|
|
|
259
259
|
return null;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* 所属 scope 数字政策(allow 档位整域放行,缺省 forbid 走白名单例外通道)
|
|
264
|
+
*/
|
|
265
|
+
const scopePolicy = ctx.config.scopes.find(
|
|
266
|
+
(scope) => scope.key === ctx.scopeKey,
|
|
267
|
+
)?.slugDigitPolicy;
|
|
268
|
+
|
|
269
|
+
if (scopePolicy === 'allow') {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
262
273
|
/**
|
|
263
274
|
* 仓库相对路径(白名单匹配口径)
|
|
264
275
|
*/
|
|
@@ -273,7 +284,7 @@ export const CORE_LINT_RULES: readonly LintRule[] = [
|
|
|
273
284
|
|
|
274
285
|
return whitelisted
|
|
275
286
|
? null
|
|
276
|
-
: `slug
|
|
287
|
+
: `slug 段含数字(scope 政策 slugDigitPolicy 或白名单 slugDigitAllowlist 登记):${record.file}`;
|
|
277
288
|
},
|
|
278
289
|
},
|
|
279
290
|
{
|