@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.
- package/CHANGELOG.md +18 -0
- package/README.md +26 -0
- package/dist/index.d.ts +2123 -0
- package/dist/index.js +78 -0
- package/dist/rolldown-runtime-BqT_7tdF.js +1 -0
- package/lzi-builder.config.ts +15 -0
- package/lzi-bun.config.ts +8 -0
- package/oxlint.config.ts +3 -0
- package/package.json +39 -0
- package/src/__tests__/close/verify-close.commands.test.ts +560 -0
- package/src/__tests__/docs-refs/docs-refs.commands.test.ts +213 -0
- package/src/__tests__/fs/mini-glob.utils.test.ts +71 -0
- package/src/__tests__/fs/walk-surface.utils.test.ts +170 -0
- package/src/__tests__/health/health.commands.test.ts +131 -0
- package/src/__tests__/lint/lint.core.test.ts +332 -0
- package/src/__tests__/numbering/numbering.commands.test.ts +194 -0
- package/src/__tests__/numbering/numbering.core.test.ts +318 -0
- package/src/__tests__/parser/guide-fixture.utils.test.ts +45 -0
- package/src/__tests__/parser/registry.parser.test.ts +316 -0
- package/src/__tests__/prompts/prompts.commands.test.ts +47 -0
- package/src/__tests__/template/issue-template.renderer.test.ts +133 -0
- package/src/close/evidence-reader.utils.ts +201 -0
- package/src/close/green-flip.commands.ts +199 -0
- package/src/close/verify-close.commands.ts +676 -0
- package/src/docs-refs/docs-refs.commands.ts +693 -0
- package/src/freeze/freeze.commands.ts +261 -0
- package/src/fs/mini-glob.utils.ts +216 -0
- package/src/fs/walk-surface.utils.ts +298 -0
- package/src/health/health.commands.ts +327 -0
- package/src/index.ts +46 -0
- package/src/lint/lint-baseline.commands.ts +147 -0
- package/src/lint/lint.core.ts +584 -0
- package/src/normalize/normalize-header.commands.ts +361 -0
- package/src/numbering/numbering.commands.ts +375 -0
- package/src/numbering/numbering.core.ts +685 -0
- package/src/parser/format.utils.ts +279 -0
- package/src/parser/guide-fixture.utils.ts +117 -0
- package/src/parser/registry.parser.ts +679 -0
- package/src/prompts/prompts.commands.ts +211 -0
- package/src/template/issue-template.renderer.ts +351 -0
- package/src/triage/triage.classify.ts +93 -0
- package/src/vault/vault.commands.ts +434 -0
- package/tsconfig/.cache/build.tsbuildinfo +1 -0
- package/tsconfig/.cache/node.tsbuildinfo +1 -0
- package/tsconfig/.cache/test.tsbuildinfo +1 -0
- package/tsconfig/app.json +13 -0
- package/tsconfig/build.json +15 -0
- package/tsconfig/node.json +12 -0
- package/tsconfig/test.json +15 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { parseLziIssuesConfig } from '@longzai-intelligence-issues/config';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { findPendingWord, readEvidence } from '@/close/evidence-reader.utils';
|
|
7
|
+
import { applyGreenFlip } from '@/close/green-flip.commands';
|
|
8
|
+
import {
|
|
9
|
+
evidenceCloseFlow,
|
|
10
|
+
rerunCloseFlow,
|
|
11
|
+
resolveCloseGates,
|
|
12
|
+
} from '@/close/verify-close.commands';
|
|
13
|
+
import { freezeIssue, unfreezeIssue } from '@/freeze/freeze.commands';
|
|
14
|
+
import { normalizeHeader } from '@/normalize/normalize-header.commands';
|
|
15
|
+
import { computeCriterionFingerprint, runCriterionViaVault } from '@/vault/vault.commands';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 测试夹具根(包内临时目录)
|
|
19
|
+
*/
|
|
20
|
+
const FIXTURE_ROOT = join(import.meta.dir, '__tmp-close__');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 注册表目录
|
|
24
|
+
*/
|
|
25
|
+
const REGISTRY_REL = 'docs/issues';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 解析后的测试配置(含两级门禁正则)
|
|
29
|
+
*/
|
|
30
|
+
const CONFIG = parseLziIssuesConfig({
|
|
31
|
+
schemaVersion: 1,
|
|
32
|
+
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: REGISTRY_REL }],
|
|
33
|
+
close: {
|
|
34
|
+
heavyPattern: 'bun run heavy',
|
|
35
|
+
fullPattern: 'bun run full-suite',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 写入注册表文件
|
|
41
|
+
*
|
|
42
|
+
* @param name - 文件名
|
|
43
|
+
* @param content - 文档全文
|
|
44
|
+
*/
|
|
45
|
+
function writeIssue(name: string, content: string): void {
|
|
46
|
+
writeFileSync(join(FIXTURE_ROOT, REGISTRY_REL, name), content);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 写入夹具
|
|
51
|
+
*/
|
|
52
|
+
function writeFixture(): void {
|
|
53
|
+
rmSync(FIXTURE_ROOT, { recursive: true, force: true });
|
|
54
|
+
mkdirSync(join(FIXTURE_ROOT, REGISTRY_REL), { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 清理夹具
|
|
59
|
+
*/
|
|
60
|
+
function cleanFixture(): void {
|
|
61
|
+
rmSync(FIXTURE_ROOT, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeEach(writeFixture);
|
|
65
|
+
|
|
66
|
+
afterEach(cleanFixture);
|
|
67
|
+
|
|
68
|
+
describe('readEvidence 锚点四级归类', () => {
|
|
69
|
+
test('围栏外四级锚点命中且优先级归类', () => {
|
|
70
|
+
/**
|
|
71
|
+
* 处置段样例(含围栏内判据行与围栏外四类锚点)
|
|
72
|
+
*/
|
|
73
|
+
const disposition = [
|
|
74
|
+
'## 四、处置结果/验证口径',
|
|
75
|
+
'',
|
|
76
|
+
'```criteria',
|
|
77
|
+
'summary: 判据全绿',
|
|
78
|
+
'单包测试|test|pkg|bun run test',
|
|
79
|
+
'```',
|
|
80
|
+
'',
|
|
81
|
+
'证书指纹 ' + 'a'.repeat(64),
|
|
82
|
+
'退出码 0',
|
|
83
|
+
'全部通过',
|
|
84
|
+
'用户亲自自验通过',
|
|
85
|
+
].join('\n');
|
|
86
|
+
|
|
87
|
+
const { anchors } = readEvidence(`**状态**: 🟡 处理中(待收口)\n\n${disposition}\n`);
|
|
88
|
+
|
|
89
|
+
expect(anchors.map((anchor) => anchor.kind)).toEqual([
|
|
90
|
+
'certificate',
|
|
91
|
+
'exit-zero',
|
|
92
|
+
'all-green',
|
|
93
|
+
'adjudicated',
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('围栏内行不判读锚点;处置段跨内嵌围栏', () => {
|
|
98
|
+
const { anchors, dispositionText } = readEvidence(
|
|
99
|
+
[
|
|
100
|
+
'状态: 🟢 已处置',
|
|
101
|
+
'## 四、处置结果/验证口径',
|
|
102
|
+
'',
|
|
103
|
+
'```criteria',
|
|
104
|
+
'a|b|c|d',
|
|
105
|
+
'退出码 0',
|
|
106
|
+
'```',
|
|
107
|
+
].join('\n'),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(anchors).toEqual([]);
|
|
111
|
+
expect(dispositionText.includes('criteria')).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('findPendingWord 挂起词拦截', () => {
|
|
116
|
+
test('命中任一挂起词', () => {
|
|
117
|
+
expect(findPendingWord('**状态**: 🟡 处理中(待补跑回归)', ['待补跑', '挂起'])).toBe('待补跑');
|
|
118
|
+
expect(findPendingWord('**状态**: 🟡 处理中(正常推进)', ['待补跑'])).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('applyGreenFlip 守卫分诊', () => {
|
|
123
|
+
test('翻绿保留前缀形态并回填证据块', () => {
|
|
124
|
+
/**
|
|
125
|
+
* 黄灯文档
|
|
126
|
+
*/
|
|
127
|
+
const raw = [
|
|
128
|
+
'# 0001 - 样例',
|
|
129
|
+
'',
|
|
130
|
+
'**状态**: 🟡 处理中(修复完成)',
|
|
131
|
+
'',
|
|
132
|
+
'## 四、处置结果/验证口径',
|
|
133
|
+
'',
|
|
134
|
+
'已修复,退出码 0。',
|
|
135
|
+
].join('\n');
|
|
136
|
+
|
|
137
|
+
const flip = applyGreenFlip({
|
|
138
|
+
raw,
|
|
139
|
+
summary: '修复完成',
|
|
140
|
+
evidenceBlock: '证据锚点(末次通过零执行收口):\n- 退出码 0',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(flip.idempotent).toBe(false);
|
|
144
|
+
expect(flip.content).toContain('**状态**: 🟢 已处置(修复完成——判据证据锚点见处置段)');
|
|
145
|
+
expect(flip.content).toContain('证据锚点(末次通过零执行收口):');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('幂等分诊:已绿且概要一致零变更', () => {
|
|
149
|
+
/**
|
|
150
|
+
* 已绿文档(目标形态)
|
|
151
|
+
*/
|
|
152
|
+
const raw = [
|
|
153
|
+
'# 0001 - 样例',
|
|
154
|
+
'',
|
|
155
|
+
'**状态**: 🟢 已处置(修复完成——判据证据锚点见处置段)',
|
|
156
|
+
'',
|
|
157
|
+
'## 四、处置结果/验证口径',
|
|
158
|
+
'',
|
|
159
|
+
'已修复,退出码 0。',
|
|
160
|
+
].join('\n');
|
|
161
|
+
|
|
162
|
+
const flip = applyGreenFlip({
|
|
163
|
+
raw,
|
|
164
|
+
summary: '修复完成',
|
|
165
|
+
evidenceBlock: '',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(flip.idempotent).toBe(true);
|
|
169
|
+
expect(flip.content).toBe(raw);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('零命中抛错拒绝(缺状态行防假绿)', () => {
|
|
173
|
+
expect(() =>
|
|
174
|
+
applyGreenFlip({ raw: '# 0001 - 无状态行\n\n正文', summary: 's', evidenceBlock: '' }),
|
|
175
|
+
).toThrow('零命中');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('evidenceCloseFlow 证据收口', () => {
|
|
180
|
+
test('零锚点拒收(不核验即翻绿红线)', () => {
|
|
181
|
+
writeIssue(
|
|
182
|
+
'0001-no-evidence.md',
|
|
183
|
+
'**状态**: 🟡 处理中(修复完成)\n\n## 四、处置结果/验证口径\n\n已修复。\n',
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const result = evidenceCloseFlow({
|
|
187
|
+
root: FIXTURE_ROOT,
|
|
188
|
+
config: CONFIG,
|
|
189
|
+
files: [`${REGISTRY_REL}/0001-no-evidence.md`],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(result.succeeded).toBe(0);
|
|
193
|
+
expect(result.cases[0]?.message).toContain('零绿证据锚点');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('挂起词拦截', () => {
|
|
197
|
+
writeIssue(
|
|
198
|
+
'0002-pending.md',
|
|
199
|
+
'**状态**: 🟡 处理中(待补跑回归)\n\n## 四、处置结果/验证口径\n\n退出码 0\n',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const result = evidenceCloseFlow({
|
|
203
|
+
root: FIXTURE_ROOT,
|
|
204
|
+
config: CONFIG,
|
|
205
|
+
files: [`${REGISTRY_REL}/0002-pending.md`],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result.succeeded).toBe(0);
|
|
209
|
+
expect(result.cases[0]?.message).toContain('挂起词');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('冻结案拒收', () => {
|
|
213
|
+
writeIssue('0003-frozen.md', '**状态**: ⚪ 已冻结(用户裁定暂停)\n');
|
|
214
|
+
|
|
215
|
+
const result = evidenceCloseFlow({
|
|
216
|
+
root: FIXTURE_ROOT,
|
|
217
|
+
config: CONFIG,
|
|
218
|
+
files: [`${REGISTRY_REL}/0003-frozen.md`],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result.succeeded).toBe(0);
|
|
222
|
+
expect(result.cases[0]?.message).toContain('冻结案拒收');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('阻塞未全绿拒收(blockingMustBeGreen)', () => {
|
|
226
|
+
writeIssue('0004-blocker.md', '**状态**: 🔴 待处理\n');
|
|
227
|
+
writeIssue(
|
|
228
|
+
'0005-blocked.md',
|
|
229
|
+
'**状态**: 🟡 处理中(修复完成)\n\n**阻塞**: [0004](./0004-blocker.md)\n\n## 四、处置结果/验证口径\n\n退出码 0\n',
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const result = evidenceCloseFlow({
|
|
233
|
+
root: FIXTURE_ROOT,
|
|
234
|
+
config: CONFIG,
|
|
235
|
+
files: [`${REGISTRY_REL}/0005-blocked.md`],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(result.succeeded).toBe(0);
|
|
239
|
+
expect(result.cases[0]?.message).toContain('未全 🟢');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('正常收口翻绿落盘 + 幂等复跑零变更', () => {
|
|
243
|
+
writeIssue(
|
|
244
|
+
'0006-ready.md',
|
|
245
|
+
'**状态**: 🟡 处理中(修复完成)\n\n## 四、处置结果/验证口径\n\n已修复,退出码 0。\n',
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const first = evidenceCloseFlow({
|
|
249
|
+
root: FIXTURE_ROOT,
|
|
250
|
+
config: CONFIG,
|
|
251
|
+
files: [`${REGISTRY_REL}/0006-ready.md`],
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(first.succeeded).toBe(1);
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 落盘产物已翻绿
|
|
258
|
+
*/
|
|
259
|
+
const flipped = readFileSync(join(FIXTURE_ROOT, REGISTRY_REL, '0006-ready.md'), 'utf8');
|
|
260
|
+
|
|
261
|
+
expect(flipped).toContain('🟢 已处置(末次通过证据收口(0006):1 条绿证据锚点');
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 幂等复跑
|
|
265
|
+
*/
|
|
266
|
+
const second = evidenceCloseFlow({
|
|
267
|
+
root: FIXTURE_ROOT,
|
|
268
|
+
config: CONFIG,
|
|
269
|
+
files: [`${REGISTRY_REL}/0006-ready.md`],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(second.cases[0]?.idempotent).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('批量串行一案失败不阻断', () => {
|
|
276
|
+
writeIssue('0007-bad.md', '正文无状态行\n');
|
|
277
|
+
writeIssue(
|
|
278
|
+
'0008-good.md',
|
|
279
|
+
'**状态**: 🟡 处理中(修复完成)\n\n## 四、处置结果/验证口径\n\n退出码 0\n',
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const result = evidenceCloseFlow({
|
|
283
|
+
root: FIXTURE_ROOT,
|
|
284
|
+
config: CONFIG,
|
|
285
|
+
files: [`${REGISTRY_REL}/0007-bad.md`, `${REGISTRY_REL}/0008-good.md`],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(result.succeeded).toBe(1);
|
|
289
|
+
expect(result.cases).toHaveLength(2);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('resolveCloseGates 两级门禁', () => {
|
|
294
|
+
/**
|
|
295
|
+
* 判据构造器
|
|
296
|
+
*/
|
|
297
|
+
const criterion = (command: string) => ({
|
|
298
|
+
label: '判据',
|
|
299
|
+
channel: 'test',
|
|
300
|
+
cwd: '.',
|
|
301
|
+
command,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('禁用级:全量判据未豁免拦截', () => {
|
|
305
|
+
const gate = resolveCloseGates(CONFIG, [criterion('bun run full-suite')], false, true);
|
|
306
|
+
|
|
307
|
+
expect(gate.blocked).toBe(true);
|
|
308
|
+
expect(gate.kind).toBe('full');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('确认级:重度命令未确认拦截', () => {
|
|
312
|
+
const gate = resolveCloseGates(CONFIG, [criterion('bun run heavy')], true, false);
|
|
313
|
+
|
|
314
|
+
expect(gate.blocked).toBe(true);
|
|
315
|
+
expect(gate.kind).toBe('heavy');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('定向判据 + 双豁免通过', () => {
|
|
319
|
+
const gate = resolveCloseGates(CONFIG, [criterion('bun run test')], false, false);
|
|
320
|
+
|
|
321
|
+
expect(gate.blocked).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('rerunCloseFlow 证书化真跑', () => {
|
|
326
|
+
test('门禁拦截零执行(gateBlocked = exit 2 语义)', () => {
|
|
327
|
+
writeIssue(
|
|
328
|
+
'0009-criteria.md',
|
|
329
|
+
'**状态**: 🟡 处理中(待收口)\n\n```criteria\nsummary: 判据\n重度判据|heavy|.|bun run heavy\n```\n',
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const result = rerunCloseFlow({
|
|
333
|
+
root: FIXTURE_ROOT,
|
|
334
|
+
config: CONFIG,
|
|
335
|
+
files: [`${REGISTRY_REL}/0009-criteria.md`],
|
|
336
|
+
criteriaFromIssue: true,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(result.gateBlocked).toBe(true);
|
|
340
|
+
expect(result.gateBlocked && result.gateMessage).toContain('confirm-heavy');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('判据真跑全绿收口 + 证书落盘 + 复用秒回', () => {
|
|
344
|
+
writeIssue(
|
|
345
|
+
'0010-rerun.md',
|
|
346
|
+
[
|
|
347
|
+
'**状态**: 🟡 处理中(待收口)',
|
|
348
|
+
'',
|
|
349
|
+
'```criteria',
|
|
350
|
+
'summary: 真跑判据全绿',
|
|
351
|
+
'恒真|trivial|.|true',
|
|
352
|
+
'```',
|
|
353
|
+
'',
|
|
354
|
+
'## 四、处置结果/验证口径',
|
|
355
|
+
'',
|
|
356
|
+
'待回填。',
|
|
357
|
+
].join('\n'),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const result = rerunCloseFlow({
|
|
361
|
+
root: FIXTURE_ROOT,
|
|
362
|
+
config: CONFIG,
|
|
363
|
+
files: [`${REGISTRY_REL}/0010-rerun.md`],
|
|
364
|
+
criteriaFromIssue: true,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(result.succeeded).toBe(1);
|
|
368
|
+
expect(result.cases[0]?.message).toContain('证书化收口完成');
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 落盘产物含证据块与 64 位指纹
|
|
372
|
+
*/
|
|
373
|
+
const flipped = readFileSync(join(FIXTURE_ROOT, REGISTRY_REL, '0010-rerun.md'), 'utf8');
|
|
374
|
+
|
|
375
|
+
expect(flipped).toContain('判据执行证据(证书化收口,issues:verify-close):');
|
|
376
|
+
expect(flipped).toMatch(/证书指纹 [0-9a-f]{64}/);
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 证书在册
|
|
380
|
+
*/
|
|
381
|
+
expect(existsSync(join(FIXTURE_ROOT, '.lzi/issues/vault/certificates/trivial'))).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('同指纹复用秒回(固定指纹两连跑)', () => {
|
|
385
|
+
/**
|
|
386
|
+
* 固定判据与指纹
|
|
387
|
+
*/
|
|
388
|
+
const criterion = { label: '恒真', channel: 'trivial2', cwd: '.', command: 'true' };
|
|
389
|
+
|
|
390
|
+
const fingerprint = computeCriterionFingerprint(FIXTURE_ROOT, criterion);
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 首跑(真实执行落证书)
|
|
394
|
+
*/
|
|
395
|
+
const first = runCriterionViaVault({ root: FIXTURE_ROOT, criterion, fingerprint });
|
|
396
|
+
|
|
397
|
+
expect(first.reused).toBe(false);
|
|
398
|
+
expect(first.green).toBe(true);
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* 复跑(同指纹在册秒回)
|
|
402
|
+
*/
|
|
403
|
+
const second = runCriterionViaVault({ root: FIXTURE_ROOT, criterion, fingerprint });
|
|
404
|
+
|
|
405
|
+
expect(second.reused).toBe(true);
|
|
406
|
+
expect(second.green).toBe(true);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('判据红(非零退出码)跳过翻绿并落红证书', () => {
|
|
410
|
+
writeIssue(
|
|
411
|
+
'0011-red.md',
|
|
412
|
+
'**状态**: 🟡 处理中(待收口)\n\n```criteria\nsummary: 判据\n恒假|trivial|.|false\n```\n',
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const result = rerunCloseFlow({
|
|
416
|
+
root: FIXTURE_ROOT,
|
|
417
|
+
config: CONFIG,
|
|
418
|
+
files: [`${REGISTRY_REL}/0011-red.md`],
|
|
419
|
+
criteriaFromIssue: true,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(result.succeeded).toBe(0);
|
|
423
|
+
expect(result.cases[0]?.message).toContain('未全绿');
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 原文未翻绿
|
|
427
|
+
*/
|
|
428
|
+
const raw = readFileSync(join(FIXTURE_ROOT, REGISTRY_REL, '0011-red.md'), 'utf8');
|
|
429
|
+
|
|
430
|
+
expect(raw).toContain('🟡 处理中');
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe('freeze / unfreeze', () => {
|
|
435
|
+
test('无裁定语拒绝', () => {
|
|
436
|
+
const result = freezeIssue({
|
|
437
|
+
root: FIXTURE_ROOT,
|
|
438
|
+
file: `${REGISTRY_REL}/any.md`,
|
|
439
|
+
reason: '随便冻一下',
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
expect(result.ok).toBe(false);
|
|
443
|
+
expect(result.message).toContain('用户裁定');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('冻结写入与解冻恢复', () => {
|
|
447
|
+
writeIssue('0012-freeze.md', '**状态**: 🔴 待处理\n');
|
|
448
|
+
|
|
449
|
+
const frozen = freezeIssue({
|
|
450
|
+
root: FIXTURE_ROOT,
|
|
451
|
+
file: `${REGISTRY_REL}/0012-freeze.md`,
|
|
452
|
+
reason: '用户裁定暂停推进',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
expect(frozen.ok).toBe(true);
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 冻结产物
|
|
459
|
+
*/
|
|
460
|
+
const frozenRaw = readFileSync(join(FIXTURE_ROOT, REGISTRY_REL, '0012-freeze.md'), 'utf8');
|
|
461
|
+
|
|
462
|
+
expect(frozenRaw).toContain('⚪ 已冻结(用户裁定暂停推进');
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 非冻结案解冻拒绝(对另一份红灯案)
|
|
466
|
+
*/
|
|
467
|
+
writeIssue('0013-red.md', '**状态**: 🔴 待处理\n');
|
|
468
|
+
|
|
469
|
+
const wrongUnfreeze = unfreezeIssue({
|
|
470
|
+
root: FIXTURE_ROOT,
|
|
471
|
+
file: `${REGISTRY_REL}/0013-red.md`,
|
|
472
|
+
note: '用户裁定解冻',
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
expect(wrongUnfreeze.ok).toBe(false);
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* 正常解冻恢复 🔴
|
|
479
|
+
*/
|
|
480
|
+
const unfrozen = unfreezeIssue({
|
|
481
|
+
root: FIXTURE_ROOT,
|
|
482
|
+
file: `${REGISTRY_REL}/0012-freeze.md`,
|
|
483
|
+
note: '用户裁定恢复推进',
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(unfrozen.ok).toBe(true);
|
|
487
|
+
|
|
488
|
+
const unfrozenRaw = readFileSync(join(FIXTURE_ROOT, REGISTRY_REL, '0012-freeze.md'), 'utf8');
|
|
489
|
+
|
|
490
|
+
expect(unfrozenRaw).toContain('🔴 待处理(用户裁定恢复推进');
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test('复合元信息行拒绝整行替换', () => {
|
|
494
|
+
writeIssue('0014-composite.md', '**状态**: 🟡 处理中(推进中)| 优先级 P2\n');
|
|
495
|
+
|
|
496
|
+
const result = freezeIssue({
|
|
497
|
+
root: FIXTURE_ROOT,
|
|
498
|
+
file: `${REGISTRY_REL}/0014-composite.md`,
|
|
499
|
+
reason: '用户裁定冻结',
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
expect(result.ok).toBe(false);
|
|
503
|
+
expect(result.message).toContain('复合元信息行');
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
describe('normalizeHeader 方言迁移', () => {
|
|
508
|
+
test('管道引用行方言迁移为规范序粗体标签', () => {
|
|
509
|
+
const raw = [
|
|
510
|
+
'# 0001 - 样例',
|
|
511
|
+
'',
|
|
512
|
+
'> 优先级: P2 | 状态: 🟡 处理中(推进中) | 立案日期: 2026-09-14',
|
|
513
|
+
'',
|
|
514
|
+
'## 一、问题动机',
|
|
515
|
+
'',
|
|
516
|
+
'正文',
|
|
517
|
+
].join('\n');
|
|
518
|
+
|
|
519
|
+
const { content, outcome } = normalizeHeader(raw);
|
|
520
|
+
|
|
521
|
+
expect(outcome).toBe('rewritten');
|
|
522
|
+
expect(content).toContain('**立案日期**: 2026-09-14');
|
|
523
|
+
expect(content.indexOf('**立案日期**')).toBeLessThan(content.indexOf('**优先级**'));
|
|
524
|
+
expect(content.indexOf('**优先级**')).toBeLessThan(content.indexOf('**状态**'));
|
|
525
|
+
expect(content).toContain('## 一、问题动机');
|
|
526
|
+
expect(content).toContain('正文');
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('列表式方言迁移', () => {
|
|
530
|
+
const raw = [
|
|
531
|
+
'# 0002 - 样例',
|
|
532
|
+
'',
|
|
533
|
+
'- 状态: 🔴 待处理',
|
|
534
|
+
'- 来源: 会话记录',
|
|
535
|
+
'',
|
|
536
|
+
'## 一、问题动机',
|
|
537
|
+
].join('\n');
|
|
538
|
+
|
|
539
|
+
const { outcome, content } = normalizeHeader(raw);
|
|
540
|
+
|
|
541
|
+
expect(outcome).toBe('rewritten');
|
|
542
|
+
expect(content).toContain('**状态**: 🔴 待处理');
|
|
543
|
+
expect(content).toContain('**来源**: 会话记录');
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test('无状态行 skipped(不猜灯)', () => {
|
|
547
|
+
const { outcome } = normalizeHeader('# 0003 - 样例\n\n- 优先级: P1\n');
|
|
548
|
+
|
|
549
|
+
expect(outcome).toBe('skipped');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('已规范形态 unchanged(幂等)', () => {
|
|
553
|
+
const raw = ['# 0004 - 样例', '', '**状态**: 🔴 待处理', '', '## 一、问题动机'].join('\n');
|
|
554
|
+
|
|
555
|
+
const { outcome, content } = normalizeHeader(raw);
|
|
556
|
+
|
|
557
|
+
expect(outcome).toBe('unchanged');
|
|
558
|
+
expect(content).toBe(raw);
|
|
559
|
+
});
|
|
560
|
+
});
|