@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
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
|
|
13
|
+
import { CANONICAL_STATUS_LABELS } from '@/parser/format.utils';
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* 字段规范序(排名值越小越靠前;未入表字段按首现序殿后)
|
|
15
17
|
*/
|
|
@@ -35,6 +37,12 @@ export type NormalizeHeaderFileInput = {
|
|
|
35
37
|
* issue 文件仓库相对路径
|
|
36
38
|
*/
|
|
37
39
|
file: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 无可识别状态行时的显式灯位(--status 语义;frozen 不在此通道——冻结唯一合法
|
|
43
|
+
* 写入路径是 freeze 命令,其裁定语硬门不可绕过)
|
|
44
|
+
*/
|
|
45
|
+
status?: 'red' | 'yellow' | 'green' | 'canceled';
|
|
38
46
|
};
|
|
39
47
|
|
|
40
48
|
/**
|
|
@@ -60,7 +68,7 @@ export type NormalizeHeaderResult = {
|
|
|
60
68
|
/**
|
|
61
69
|
* 头部解析产物
|
|
62
70
|
*/
|
|
63
|
-
type ParsedHeader = {
|
|
71
|
+
export type ParsedHeader = {
|
|
64
72
|
/**
|
|
65
73
|
* 字段键值对(保序)
|
|
66
74
|
*/
|
|
@@ -78,12 +86,12 @@ type ParsedHeader = {
|
|
|
78
86
|
};
|
|
79
87
|
|
|
80
88
|
/**
|
|
81
|
-
*
|
|
89
|
+
* 解析头部方言(五种形态逐行尝试:粗体标签 / 列表式 / 标题式 / 管道引用行 / 表格)
|
|
82
90
|
*
|
|
83
91
|
* @param lines - 文档行数组
|
|
84
92
|
* @returns 头部解析产物
|
|
85
93
|
*/
|
|
86
|
-
function parseHeaderDialect(lines: string[]): ParsedHeader {
|
|
94
|
+
export function parseHeaderDialect(lines: string[]): ParsedHeader {
|
|
87
95
|
/**
|
|
88
96
|
* 字段收集器
|
|
89
97
|
*/
|
|
@@ -149,6 +157,11 @@ function parseHeaderDialect(lines: string[]): ParsedHeader {
|
|
|
149
157
|
.split(/[||]/)
|
|
150
158
|
.map((segment) => segment.trim());
|
|
151
159
|
|
|
160
|
+
/**
|
|
161
|
+
* 未命中键值的段(零段命中时整行原样入 residual——保 `> ` 前缀原貌)
|
|
162
|
+
*/
|
|
163
|
+
const unmatched: string[] = [];
|
|
164
|
+
|
|
152
165
|
for (const segment of segments) {
|
|
153
166
|
/**
|
|
154
167
|
* 段内键值切分
|
|
@@ -158,10 +171,70 @@ function parseHeaderDialect(lines: string[]): ParsedHeader {
|
|
|
158
171
|
if (pairMatch !== null) {
|
|
159
172
|
fields.push({ key: pairMatch[1]?.trim() ?? '', value: pairMatch[2]?.trim() ?? '' });
|
|
160
173
|
} else {
|
|
161
|
-
|
|
174
|
+
unmatched.push(segment);
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
|
178
|
+
if (unmatched.length === segments.length) {
|
|
179
|
+
residuals.push(line);
|
|
180
|
+
} else {
|
|
181
|
+
residuals.push(...unmatched);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 形态五:表格方言(两列表 `| 键 | 值 |`;表头行与分隔行为表格脚手架,键值
|
|
189
|
+
* 提取后丢弃——脚手架无独立语义,保留会在规范头部产出破碎表格残片)
|
|
190
|
+
*/
|
|
191
|
+
const tableMatch = /^\|([^|]*)\|([^|]*)\|$/.exec(line.trim());
|
|
192
|
+
|
|
193
|
+
if (tableMatch !== null) {
|
|
194
|
+
/**
|
|
195
|
+
* 键单元格 / 值单元格
|
|
196
|
+
*/
|
|
197
|
+
const keyCell = (tableMatch[1] ?? '').trim();
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 值单元格(数据行切键值的值侧)
|
|
201
|
+
*/
|
|
202
|
+
const valueCell = (tableMatch[2] ?? '').trim();
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 分隔行形态(单元格仅 dashes/colons——`| --- | --- |`)
|
|
206
|
+
*/
|
|
207
|
+
const separatorCellPattern = /^:?-{2,}:?$/;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 本行是否分隔行
|
|
211
|
+
*/
|
|
212
|
+
const isSeparator =
|
|
213
|
+
separatorCellPattern.test(keyCell) && separatorCellPattern.test(valueCell);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 次行(表头行判定:分隔行紧前的一行是表头)
|
|
217
|
+
*/
|
|
218
|
+
const nextMatch = /^\|([^|]*)\|([^|]*)\|$/.exec((lines[index + 1] ?? '').trim());
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 次行是否表格分隔行(当前行为表头行的判定依据)
|
|
222
|
+
*/
|
|
223
|
+
const nextIsTableSeparator =
|
|
224
|
+
nextMatch !== null &&
|
|
225
|
+
separatorCellPattern.test((nextMatch[1] ?? '').trim()) &&
|
|
226
|
+
separatorCellPattern.test((nextMatch[2] ?? '').trim());
|
|
227
|
+
|
|
228
|
+
if (isSeparator || (nextIsTableSeparator && keyCell !== '')) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (keyCell !== '') {
|
|
233
|
+
fields.push({ key: keyCell, value: valueCell });
|
|
234
|
+
} else {
|
|
235
|
+
residuals.push(line);
|
|
236
|
+
}
|
|
237
|
+
|
|
165
238
|
continue;
|
|
166
239
|
}
|
|
167
240
|
|
|
@@ -187,6 +260,16 @@ function fieldSortKey(key: string, firstSeenRank: number): number {
|
|
|
187
260
|
*/
|
|
188
261
|
export type NormalizeOutcome = 'rewritten' | 'unchanged' | 'skipped';
|
|
189
262
|
|
|
263
|
+
/**
|
|
264
|
+
* normalizeHeader 迁移选项(无可识别状态行时的显式灯位注入)
|
|
265
|
+
*/
|
|
266
|
+
export type NormalizeHeaderOptions = {
|
|
267
|
+
/**
|
|
268
|
+
* 显式灯位(--status 语义;frozen 不在此通道——冻结唯一合法写入路径是 freeze)
|
|
269
|
+
*/
|
|
270
|
+
status?: NormalizeHeaderFileInput['status'];
|
|
271
|
+
};
|
|
272
|
+
|
|
190
273
|
/**
|
|
191
274
|
* 迁移产物
|
|
192
275
|
*/
|
|
@@ -206,9 +289,10 @@ export type NormalizeResult = {
|
|
|
206
289
|
* 迁移单份文档头部
|
|
207
290
|
*
|
|
208
291
|
* @param raw - 文档全文
|
|
292
|
+
* @param options - 迁移选项(无可识别状态行时的显式灯位注入)
|
|
209
293
|
* @returns 迁移产物
|
|
210
294
|
*/
|
|
211
|
-
export function normalizeHeader(raw: string): NormalizeResult {
|
|
295
|
+
export function normalizeHeader(raw: string, options?: NormalizeHeaderOptions): NormalizeResult {
|
|
212
296
|
/**
|
|
213
297
|
* 文档行数组
|
|
214
298
|
*/
|
|
@@ -220,12 +304,19 @@ export function normalizeHeader(raw: string): NormalizeResult {
|
|
|
220
304
|
const parsed = parseHeaderDialect(lines);
|
|
221
305
|
|
|
222
306
|
/**
|
|
223
|
-
*
|
|
307
|
+
* 「状态」字段定位(无状态行且未给 --status 则 skipped——灯位属语义判断,不猜灯)
|
|
224
308
|
*/
|
|
225
309
|
const hasStatus = parsed.fields.some((field) => field.key === '状态');
|
|
226
310
|
|
|
227
311
|
if (!hasStatus) {
|
|
228
|
-
|
|
312
|
+
if (options?.status === undefined) {
|
|
313
|
+
return { content: raw, outcome: 'skipped' };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 显式灯位注入(--status):以规范标签落状态行,排入 FIELD_RANK 规范序
|
|
318
|
+
*/
|
|
319
|
+
parsed.fields.push({ key: '状态', value: CANONICAL_STATUS_LABELS[options.status] });
|
|
229
320
|
}
|
|
230
321
|
|
|
231
322
|
/**
|
|
@@ -328,7 +419,7 @@ export function normalizeHeaderFile(input: NormalizeHeaderFileInput): NormalizeH
|
|
|
328
419
|
/**
|
|
329
420
|
* 迁移产物
|
|
330
421
|
*/
|
|
331
|
-
const normalized = normalizeHeader(raw);
|
|
422
|
+
const normalized = normalizeHeader(raw, { status: input.status });
|
|
332
423
|
|
|
333
424
|
if (normalized.outcome === 'skipped') {
|
|
334
425
|
return {
|
|
@@ -325,9 +325,9 @@ export type ScopeScanInput = {
|
|
|
325
325
|
allowlist: readonly NumberingAllowlistEntry[];
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
|
-
* slug 数字策略('forbid'
|
|
328
|
+
* slug 数字策略('forbid' 启用判定;'allow' 整域放行跳过判定)
|
|
329
329
|
*/
|
|
330
|
-
slugDigitPolicy?: 'forbid';
|
|
330
|
+
slugDigitPolicy?: 'forbid' | 'allow';
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
333
|
* slug 数字白名单 glob(仓库相对路径命中豁免)
|
|
@@ -503,7 +503,7 @@ export function scanScopeForFindings(input: ScopeScanInput): NumberingFinding[]
|
|
|
503
503
|
});
|
|
504
504
|
});
|
|
505
505
|
|
|
506
|
-
if (input.slugDigitPolicy
|
|
506
|
+
if (input.slugDigitPolicy !== 'allow') {
|
|
507
507
|
/**
|
|
508
508
|
* slug 白名单命中集合(全量档失效对账用)
|
|
509
509
|
*/
|
|
@@ -131,6 +131,82 @@ export const CANONICAL_STATUS_LABELS: Readonly<
|
|
|
131
131
|
canceled: '⚫ 已取消',
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* pre-emoji 存量状态词表(写侧迁移单一真源)
|
|
136
|
+
*
|
|
137
|
+
* 读侧归类(classifyStatusLine)不消费本词表——旧词表形态归 unknown 由 lint
|
|
138
|
+
* status-line-missing 拦截,迁移经由 migrate-legacy 写侧规范化后回归规范五态,
|
|
139
|
+
* 避免为读侧新增 legacy-red 等形态破坏 IssueStatus 契约面。
|
|
140
|
+
*/
|
|
141
|
+
export const LEGACY_STATUS_VOCABULARY: Readonly<{
|
|
142
|
+
/**
|
|
143
|
+
* 绿系词(历史已结案标记)
|
|
144
|
+
*/
|
|
145
|
+
green: readonly string[];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 红系词(历史待办标记)
|
|
149
|
+
*/
|
|
150
|
+
red: readonly string[];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 延期系词(已登记(延期)族——迁移默认回红灯并保留延期说明)
|
|
154
|
+
*/
|
|
155
|
+
deferred: readonly string[];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 取消系词
|
|
159
|
+
*/
|
|
160
|
+
canceled: readonly string[];
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 进行系词(黄灯候选)
|
|
164
|
+
*/
|
|
165
|
+
inProgress: readonly string[];
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 冻结系词(不迁移——冻结唯一合法写入路径是 freeze 命令)
|
|
169
|
+
*/
|
|
170
|
+
frozen: readonly string[];
|
|
171
|
+
}> = {
|
|
172
|
+
green: [
|
|
173
|
+
'已解决',
|
|
174
|
+
'已修复',
|
|
175
|
+
'已治理',
|
|
176
|
+
'已处理',
|
|
177
|
+
'已处置',
|
|
178
|
+
'已完成',
|
|
179
|
+
'已闭合',
|
|
180
|
+
'已消解',
|
|
181
|
+
'已闭环',
|
|
182
|
+
'已清治',
|
|
183
|
+
'已收口',
|
|
184
|
+
'已根治',
|
|
185
|
+
'已终态',
|
|
186
|
+
'已裁决',
|
|
187
|
+
'已决策',
|
|
188
|
+
'已验证',
|
|
189
|
+
'已全部清治',
|
|
190
|
+
'全量完成',
|
|
191
|
+
'终态达成',
|
|
192
|
+
],
|
|
193
|
+
red: [
|
|
194
|
+
'未处理',
|
|
195
|
+
'待处理',
|
|
196
|
+
'待治理',
|
|
197
|
+
'待修复',
|
|
198
|
+
'未修复',
|
|
199
|
+
'待验证',
|
|
200
|
+
'待评估',
|
|
201
|
+
'紧急',
|
|
202
|
+
'已立项待治理',
|
|
203
|
+
],
|
|
204
|
+
deferred: ['已登记'],
|
|
205
|
+
canceled: ['已取消'],
|
|
206
|
+
inProgress: ['处理中', '进行中'],
|
|
207
|
+
frozen: ['已冻结'],
|
|
208
|
+
};
|
|
209
|
+
|
|
134
210
|
/**
|
|
135
211
|
* issue 引用 token 形态
|
|
136
212
|
*
|
|
@@ -241,13 +241,24 @@ export function renderCriteriaBlockExample(): string {
|
|
|
241
241
|
/**
|
|
242
242
|
* 渲染注册表 README 格式存根(init 命令落盘)
|
|
243
243
|
*
|
|
244
|
-
*
|
|
244
|
+
* 一页式格式速览:建档入口、状态行五态、正文四段;完整规范指向格式真源指南
|
|
245
|
+
* (采用仓不携带指南文件时回退机器真源提示,不落死链)。
|
|
245
246
|
*
|
|
246
247
|
* @param scopeTitle - scope 人类可读名
|
|
247
|
-
* @param guideRelPath -
|
|
248
|
+
* @param guideRelPath - 格式指南相对本注册表目录的路径;指南不在采用仓时为 null
|
|
248
249
|
* @returns README 存根全文
|
|
249
250
|
*/
|
|
250
|
-
export function renderRegistryReadmeStub(scopeTitle: string, guideRelPath: string): string {
|
|
251
|
+
export function renderRegistryReadmeStub(scopeTitle: string, guideRelPath: string | null): string {
|
|
252
|
+
/**
|
|
253
|
+
* 指南指引行(链接形态或机器真源回退形态)
|
|
254
|
+
*/
|
|
255
|
+
const guideLine =
|
|
256
|
+
guideRelPath === null
|
|
257
|
+
? '- 标准模板:`lzi-issues template`(字段契约 `--format json`);完整格式规范见 lzi-issues 仓 docs/guides/issue-file-format.md(本仓未携带指南文件,不落链接)'
|
|
258
|
+
: '- 标准模板:`lzi-issues template`;完整格式规范:[issue-file-format](' +
|
|
259
|
+
guideRelPath +
|
|
260
|
+
')';
|
|
261
|
+
|
|
251
262
|
return [
|
|
252
263
|
`# ${scopeTitle} issue 注册表`,
|
|
253
264
|
'',
|
|
@@ -256,7 +267,7 @@ export function renderRegistryReadmeStub(scopeTitle: string, guideRelPath: strin
|
|
|
256
267
|
'## 建档',
|
|
257
268
|
'',
|
|
258
269
|
'- `lzi-issues create --title <标题> --scope <key>`(编号走门禁取号,禁止手工定号)',
|
|
259
|
-
|
|
270
|
+
guideLine,
|
|
260
271
|
'',
|
|
261
272
|
'## 状态行(处理情况第一入口)',
|
|
262
273
|
'',
|