@longzai-intelligence-issues/cli 0.0.1 → 0.0.3
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 +7 -0
- package/.turbo/turbo-lint.log +7 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +27 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.d.ts +4 -0
- package/dist/main.js +2 -0
- package/lzi-builder.config.ts +6 -2
- package/package.json +14 -17
- package/src/__tests__/commands.known-flags.test.ts +243 -0
- package/src/commands/close.commands.ts +175 -32
- package/src/commands/context.utils.ts +13 -3
- package/src/commands/gate.commands.ts +257 -44
- package/src/commands/index.ts +3 -0
- package/src/commands/issue.commands.ts +102 -38
- package/src/commands/migrate.commands.ts +113 -0
- package/src/commands/scaffold.commands.ts +57 -17
- package/src/main.ts +19 -0
- package/tsconfig/.cache/build.tsbuildinfo +1 -1
- package/tsconfig/.cache/node.tsbuildinfo +1 -1
- package/tsconfig/.cache/test.tsbuildinfo +1 -1
|
@@ -7,10 +7,19 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
7
7
|
import { join, relative } from 'node:path';
|
|
8
8
|
import * as z from 'zod';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
fail,
|
|
12
|
+
info,
|
|
13
|
+
loadConfigOrExit,
|
|
14
|
+
ok,
|
|
15
|
+
repoRoot,
|
|
16
|
+
resolveScopeFromCwd,
|
|
17
|
+
} from './context.utils.js';
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* create / list / show 命令组:建档与查询
|
|
21
|
+
*
|
|
22
|
+
* @param program - CLI 程序实例
|
|
14
23
|
*/
|
|
15
24
|
export function registerIssueCommands(program: Command): void {
|
|
16
25
|
defineCommand(program, {
|
|
@@ -26,38 +35,47 @@ export function registerIssueCommands(program: Command): void {
|
|
|
26
35
|
{ flags: '--dry-run', description: '只呈现不落盘' },
|
|
27
36
|
{ flags: '--config <path>', description: '显式配置路径' },
|
|
28
37
|
],
|
|
29
|
-
schema: z
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
})
|
|
40
|
-
.passthrough(),
|
|
38
|
+
schema: z.looseObject({
|
|
39
|
+
title: z.string(),
|
|
40
|
+
slug: z.string().optional(),
|
|
41
|
+
priority: z.enum(['P1', 'P2', 'P3']).optional(),
|
|
42
|
+
type: z.string().optional(),
|
|
43
|
+
scope: z.string().optional(),
|
|
44
|
+
source: z.string().optional(),
|
|
45
|
+
dryRun: z.boolean().optional(),
|
|
46
|
+
config: z.string().optional(),
|
|
47
|
+
}),
|
|
41
48
|
action: async (opts) => {
|
|
42
49
|
/**
|
|
43
50
|
* 仓库根与配置
|
|
44
51
|
*/
|
|
45
52
|
const root = repoRoot();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 解析后配置
|
|
56
|
+
*/
|
|
46
57
|
const { config } = await loadConfigOrExit(opts.config);
|
|
47
58
|
|
|
48
59
|
/**
|
|
49
60
|
* scope key(显式 > cwd 最长前缀 > 全部列出退出)
|
|
50
61
|
*/
|
|
51
62
|
const cwdRel = relative(root, process.cwd());
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 生效 scope key(显式 --scope 优先,缺省按 cwd 解析)
|
|
66
|
+
*/
|
|
52
67
|
const scopeKey =
|
|
53
68
|
opts.scope ??
|
|
54
69
|
resolveScopeFromCwd(config, cwdRel) ??
|
|
55
|
-
fail(
|
|
70
|
+
fail(
|
|
71
|
+
`cwd「${cwdRel}」未命中任何 scope 覆盖面——候选:${config.scopes.map((s) => s.key).join('、')}(或显式 --scope)`,
|
|
72
|
+
);
|
|
56
73
|
|
|
57
74
|
/**
|
|
58
75
|
* 目标 scope
|
|
59
76
|
*/
|
|
60
|
-
const scope =
|
|
77
|
+
const scope =
|
|
78
|
+
config.scopes.find((s) => s.key === scopeKey) ?? fail(`scope「${scopeKey}」未登记`);
|
|
61
79
|
|
|
62
80
|
/**
|
|
63
81
|
* 写入时刻取号(落盘前现算最新值)
|
|
@@ -83,9 +101,14 @@ export function registerIssueCommands(program: Command): void {
|
|
|
83
101
|
.replace(/-+/g, '-')
|
|
84
102
|
.replace(/^-|-$/g, '');
|
|
85
103
|
|
|
104
|
+
/**
|
|
105
|
+
* slug 合法性门禁(纯 ASCII kebab;中文标题派生为空即拒绝并要求显式 --slug)
|
|
106
|
+
*/
|
|
86
107
|
const slug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(derivedSlug)
|
|
87
108
|
? derivedSlug
|
|
88
|
-
: fail(
|
|
109
|
+
: fail(
|
|
110
|
+
'slug 须为纯 ASCII kebab(小写字母/数字/连字符,禁中文)——中文标题请显式提供 --slug <slug>',
|
|
111
|
+
);
|
|
89
112
|
|
|
90
113
|
/**
|
|
91
114
|
* 标准模板落盘(instance 渲染)
|
|
@@ -94,7 +117,7 @@ export function registerIssueCommands(program: Command): void {
|
|
|
94
117
|
number: next.ok ? next.number : '0001',
|
|
95
118
|
slug,
|
|
96
119
|
title: opts.title,
|
|
97
|
-
priority:
|
|
120
|
+
priority: opts.priority ?? 'P2',
|
|
98
121
|
scopeTitle: scope.title,
|
|
99
122
|
date: new Date().toISOString().slice(0, 10),
|
|
100
123
|
source: opts.source ?? 'CLI 建档',
|
|
@@ -104,6 +127,10 @@ export function registerIssueCommands(program: Command): void {
|
|
|
104
127
|
* 文件路径(注册表目录缺席自创建——init 的轻量等价)
|
|
105
128
|
*/
|
|
106
129
|
const registryAbs = join(root, scope.registryDir);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 建档目标文件路径(编号门禁取号 + slug)
|
|
133
|
+
*/
|
|
107
134
|
const filePath = join(registryAbs, `${next.ok ? next.number : '0001'}-${slug}.md`);
|
|
108
135
|
|
|
109
136
|
if (opts.dryRun === true) {
|
|
@@ -126,7 +153,9 @@ export function registerIssueCommands(program: Command): void {
|
|
|
126
153
|
*/
|
|
127
154
|
const check = runNumberingNext({ root, config, scopeKey });
|
|
128
155
|
|
|
129
|
-
info(
|
|
156
|
+
info(
|
|
157
|
+
`编号自检:下一可用编号 ${check.ok ? check.number : '—'}(${scope.registryDir} 目录口径)`,
|
|
158
|
+
);
|
|
130
159
|
|
|
131
160
|
/**
|
|
132
161
|
* 编号撞号即让号提示
|
|
@@ -141,12 +170,26 @@ export function registerIssueCommands(program: Command): void {
|
|
|
141
170
|
description: '列出注册表 issue(按 scope 分组)',
|
|
142
171
|
options: [
|
|
143
172
|
{ flags: '--scope <scope>', description: '限定 scope' },
|
|
144
|
-
{
|
|
173
|
+
{
|
|
174
|
+
flags: '--status <status>',
|
|
175
|
+
description: '限定状态灯位(red/yellow/green/frozen/canceled)',
|
|
176
|
+
},
|
|
145
177
|
{ flags: '--config <path>', description: '显式配置路径' },
|
|
146
178
|
],
|
|
147
|
-
schema: z.
|
|
179
|
+
schema: z.looseObject({
|
|
180
|
+
scope: z.string().optional(),
|
|
181
|
+
status: z.string().optional(),
|
|
182
|
+
config: z.string().optional(),
|
|
183
|
+
}),
|
|
148
184
|
action: async (opts) => {
|
|
185
|
+
/**
|
|
186
|
+
* 仓库根
|
|
187
|
+
*/
|
|
149
188
|
const root = repoRoot();
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 解析后配置
|
|
192
|
+
*/
|
|
150
193
|
const { config } = await loadConfigOrExit(opts.config);
|
|
151
194
|
|
|
152
195
|
for (const scope of config.scopes) {
|
|
@@ -162,7 +205,8 @@ export function registerIssueCommands(program: Command): void {
|
|
|
162
205
|
/**
|
|
163
206
|
* 状态过滤
|
|
164
207
|
*/
|
|
165
|
-
const filtered =
|
|
208
|
+
const filtered =
|
|
209
|
+
opts.status === undefined ? records : records.filter((r) => r.status === opts.status);
|
|
166
210
|
|
|
167
211
|
process.stdout.write(`# ${scope.key}(${scope.title})— ${filtered.length} 项\n`);
|
|
168
212
|
|
|
@@ -178,19 +222,21 @@ export function registerIssueCommands(program: Command): void {
|
|
|
178
222
|
description: '展示单份 issue(ref 支持 编号 / 编号-slug / 不完整前缀;多义列候选)',
|
|
179
223
|
arguments: [{ signature: '<ref>', description: 'issue 引用' }],
|
|
180
224
|
options: [{ flags: '--config <path>', description: '显式配置路径' }],
|
|
181
|
-
schema: z.
|
|
182
|
-
action: async (
|
|
225
|
+
schema: z.looseObject({ config: z.string().optional() }),
|
|
226
|
+
action: async (opts, positionals) => {
|
|
183
227
|
/**
|
|
184
|
-
*
|
|
228
|
+
* ref 取值(defineCommand 位置参数第二通道;<ref> 必填由 commander 拦截,此处窄化兜底)
|
|
185
229
|
*/
|
|
186
|
-
const
|
|
230
|
+
const ref = positionals[0] ?? fail('缺少 issue 引用(show <ref>)');
|
|
187
231
|
|
|
188
232
|
/**
|
|
189
|
-
*
|
|
233
|
+
* 仓库根
|
|
190
234
|
*/
|
|
191
|
-
const ref = opts.ref ?? opts._?.[0] ?? fail('缺少 issue 引用(show <ref>)');
|
|
192
|
-
|
|
193
235
|
const root = repoRoot();
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 解析后配置
|
|
239
|
+
*/
|
|
194
240
|
const { config } = await loadConfigOrExit(opts.config);
|
|
195
241
|
|
|
196
242
|
/**
|
|
@@ -205,12 +251,24 @@ export function registerIssueCommands(program: Command): void {
|
|
|
205
251
|
/**
|
|
206
252
|
* 全 scope 候选收集
|
|
207
253
|
*/
|
|
208
|
-
const candidates: Array<{
|
|
254
|
+
const candidates: Array<{
|
|
255
|
+
scopeKey: string;
|
|
256
|
+
file: string;
|
|
257
|
+
slug: string;
|
|
258
|
+
title: string;
|
|
259
|
+
record: ReturnType<typeof scanRegistryDir>[number];
|
|
260
|
+
}> = [];
|
|
209
261
|
|
|
210
262
|
for (const scope of config.scopes) {
|
|
211
263
|
for (const record of scanRegistryDir(join(root, scope.registryDir))) {
|
|
212
264
|
if (record.id === match[1] && (match[2] === undefined || record.slug === match[2])) {
|
|
213
|
-
candidates.push({
|
|
265
|
+
candidates.push({
|
|
266
|
+
scopeKey: scope.key,
|
|
267
|
+
file: `${scope.registryDir}/${record.file}`,
|
|
268
|
+
slug: record.slug,
|
|
269
|
+
title: record.title,
|
|
270
|
+
record,
|
|
271
|
+
});
|
|
214
272
|
}
|
|
215
273
|
}
|
|
216
274
|
}
|
|
@@ -221,17 +279,23 @@ export function registerIssueCommands(program: Command): void {
|
|
|
221
279
|
*/
|
|
222
280
|
for (const scope of config.scopes) {
|
|
223
281
|
for (const record of scanRegistryDir(join(root, scope.registryDir))) {
|
|
224
|
-
if (
|
|
225
|
-
|
|
282
|
+
if (
|
|
283
|
+
record.id === match[1] &&
|
|
284
|
+
match[2] !== undefined &&
|
|
285
|
+
record.slug.startsWith(match[2])
|
|
286
|
+
) {
|
|
287
|
+
candidates.push({
|
|
288
|
+
scopeKey: scope.key,
|
|
289
|
+
file: `${scope.registryDir}/${record.file}`,
|
|
290
|
+
slug: record.slug,
|
|
291
|
+
title: record.title,
|
|
292
|
+
record,
|
|
293
|
+
});
|
|
226
294
|
}
|
|
227
295
|
}
|
|
228
296
|
}
|
|
229
297
|
}
|
|
230
298
|
|
|
231
|
-
if (candidates.length === 0) {
|
|
232
|
-
fail(`引用「${ref}」无命中`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
299
|
if (candidates.length > 1) {
|
|
236
300
|
process.stderr.write(`错误: 引用「${ref}」命中不唯一——候选:\n`);
|
|
237
301
|
for (const c of candidates) {
|
|
@@ -241,9 +305,9 @@ export function registerIssueCommands(program: Command): void {
|
|
|
241
305
|
}
|
|
242
306
|
|
|
243
307
|
/**
|
|
244
|
-
*
|
|
308
|
+
* 唯一命中输出全文(多义已列候选退出;零命在此窄化拦截)
|
|
245
309
|
*/
|
|
246
|
-
const hit = candidates[0]
|
|
310
|
+
const hit = candidates[0] ?? fail(`引用「${ref}」无命中`);
|
|
247
311
|
|
|
248
312
|
info(`命中:${hit.scopeKey} ${hit.file}`);
|
|
249
313
|
process.stdout.write(hit.record.rawContent);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Command } from '@longzai-intelligence-cli/core';
|
|
2
|
+
|
|
3
|
+
import { defineCommand } from '@longzai-intelligence-cli/core';
|
|
4
|
+
import { migrateLegacyFile, scanRegistryDir } from '@longzai-intelligence-issues/ledger';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import * as z from 'zod';
|
|
7
|
+
|
|
8
|
+
import { fail, info, loadConfigOrExit, ok, repoRoot, violation } from './context.utils.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 存量清偿命令组:migrate-legacy
|
|
12
|
+
*
|
|
13
|
+
* 判据口径:exit 0 过(含 skipped 交人工案,逐条列出)/ exit 1 守卫失败。
|
|
14
|
+
*
|
|
15
|
+
* @param program - CLI 程序实例
|
|
16
|
+
*/
|
|
17
|
+
export function registerMigrateCommands(program: Command): void {
|
|
18
|
+
defineCommand(program, {
|
|
19
|
+
name: 'migrate-legacy',
|
|
20
|
+
description: '存量注册表清偿(五方言解析→状态词规范化→处置段对齐→必填回填;零丢失守卫;幂等)',
|
|
21
|
+
options: [
|
|
22
|
+
{ flags: '--issue <issue>', description: 'issue 文件路径(可重复,与 --dir 互斥)' },
|
|
23
|
+
{ flags: '--dir <dir>', description: '注册表目录批量(与 --issue 互斥)' },
|
|
24
|
+
{ flags: '--default-priority <priority>', description: '优先级缺省回填 P1|P2|P3' },
|
|
25
|
+
{ flags: '--default-source <source>', description: '来源缺省回填文案' },
|
|
26
|
+
{ flags: '--default-filing-date <date>', description: '立案日期缺省回填(YYYY-MM-DD)' },
|
|
27
|
+
{
|
|
28
|
+
flags: '--status <status>',
|
|
29
|
+
description: '无状态行历史文档的显式灯位 red|green(人工定档,引擎承接机械迁移)',
|
|
30
|
+
},
|
|
31
|
+
{ flags: '--dry-run', description: '只呈现不落盘' },
|
|
32
|
+
{ flags: '--config <path>', description: '显式配置路径' },
|
|
33
|
+
],
|
|
34
|
+
schema: z.looseObject({
|
|
35
|
+
issue: z.union([z.string(), z.array(z.string())]).optional(),
|
|
36
|
+
dir: z.string().optional(),
|
|
37
|
+
defaultPriority: z.enum(['P1', 'P2', 'P3']).optional(),
|
|
38
|
+
defaultSource: z.string().optional(),
|
|
39
|
+
defaultFilingDate: z.string().optional(),
|
|
40
|
+
status: z.enum(['red', 'green']).optional(),
|
|
41
|
+
dryRun: z.boolean().optional(),
|
|
42
|
+
config: z.string().optional(),
|
|
43
|
+
}),
|
|
44
|
+
action: async (opts) => {
|
|
45
|
+
/**
|
|
46
|
+
* 仓库根
|
|
47
|
+
*/
|
|
48
|
+
const root = repoRoot();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 解析后配置
|
|
52
|
+
*/
|
|
53
|
+
const { config } = await loadConfigOrExit(opts.config);
|
|
54
|
+
|
|
55
|
+
if (opts.issue !== undefined && opts.dir !== undefined) {
|
|
56
|
+
fail('--issue 与 --dir 互斥(单文件指定或目录批量二选一)');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 目标文件清单(--dir 展开为注册表编号文件)
|
|
61
|
+
*/
|
|
62
|
+
const files =
|
|
63
|
+
opts.dir !== undefined
|
|
64
|
+
? scanRegistryDir(join(root, opts.dir)).map((record) => `${opts.dir}/${record.file}`)
|
|
65
|
+
: opts.issue === undefined
|
|
66
|
+
? []
|
|
67
|
+
: Array.isArray(opts.issue)
|
|
68
|
+
? opts.issue
|
|
69
|
+
: [opts.issue];
|
|
70
|
+
|
|
71
|
+
if (files.length === 0) {
|
|
72
|
+
fail('缺少 --issue <路径>(可重复)或 --dir <注册表目录>');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 形态计数器
|
|
77
|
+
*/
|
|
78
|
+
const counts = { rewritten: 0, unchanged: 0, skipped: 0, failed: 0 };
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
/**
|
|
82
|
+
* 单文件迁移结果
|
|
83
|
+
*/
|
|
84
|
+
const result = migrateLegacyFile({
|
|
85
|
+
root,
|
|
86
|
+
file,
|
|
87
|
+
config,
|
|
88
|
+
defaultPriority: opts.defaultPriority,
|
|
89
|
+
defaultSource: opts.defaultSource,
|
|
90
|
+
defaultFilingDate: opts.defaultFilingDate,
|
|
91
|
+
status: opts.status,
|
|
92
|
+
dryRun: opts.dryRun === true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
counts[result.outcome] += 1;
|
|
96
|
+
|
|
97
|
+
violation(
|
|
98
|
+
`${file}: ${result.outcome}(${result.message};变换 ${result.transformations.length} 项${opts.dryRun === true && result.outcome === 'rewritten' ? ',dry-run 未落盘' : ''})`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
info(
|
|
103
|
+
`migrate-legacy 汇总:rewritten ${counts.rewritten}|unchanged ${counts.unchanged}|skipped(交人工)${counts.skipped}|failed ${counts.failed}`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (counts.failed > 0) {
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ok('清偿批次完成(failed=0;skipped 案逐条列出待人工处置)');
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import type { Command } from '@longzai-intelligence-cli/core';
|
|
2
2
|
|
|
3
3
|
import { defineCommand } from '@longzai-intelligence-cli/core';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
appendScopeToConfigText,
|
|
6
|
+
renderConfigScaffoldFile,
|
|
7
|
+
} from '@longzai-intelligence-issues/config';
|
|
8
|
+
import {
|
|
9
|
+
renderCriteriaBlockExample,
|
|
10
|
+
renderIssueTemplateSkeleton,
|
|
11
|
+
renderRegistryReadmeStub,
|
|
12
|
+
TEMPLATE_FIELDS,
|
|
13
|
+
} from '@longzai-intelligence-issues/ledger';
|
|
6
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
15
|
import { join } from 'node:path';
|
|
8
16
|
import * as z from 'zod';
|
|
9
17
|
|
|
10
18
|
import { fail, info, loadConfigOrExit, ok, repoRoot } from './context.utils.js';
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* package.json 最小内省 schema(init 推导 scope key 用——仅认 name 字段)
|
|
22
|
+
*/
|
|
23
|
+
const packageJsonSchema = z.looseObject({ name: z.string().optional() });
|
|
24
|
+
|
|
12
25
|
/**
|
|
13
26
|
* init / template 命令组:标准格式的「提供 + 初始化」入口
|
|
27
|
+
*
|
|
28
|
+
* @param program - CLI 程序实例
|
|
14
29
|
*/
|
|
15
30
|
export function registerScaffoldCommands(program: Command): void {
|
|
16
31
|
defineCommand(program, {
|
|
@@ -22,14 +37,12 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
22
37
|
{ flags: '--registry-dir <dir>', description: '注册表目录(缺省 docs/issues)' },
|
|
23
38
|
{ flags: '--paths <paths...>', description: '覆盖路径 glob(缺省 .)' },
|
|
24
39
|
],
|
|
25
|
-
schema: z
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
32
|
-
.passthrough(),
|
|
40
|
+
schema: z.looseObject({
|
|
41
|
+
key: z.string().optional(),
|
|
42
|
+
title: z.string().optional(),
|
|
43
|
+
registryDir: z.string().optional(),
|
|
44
|
+
paths: z.array(z.string()).optional(),
|
|
45
|
+
}),
|
|
33
46
|
action: async (opts) => {
|
|
34
47
|
/**
|
|
35
48
|
* 仓库根
|
|
@@ -50,11 +63,15 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
50
63
|
? (() => {
|
|
51
64
|
try {
|
|
52
65
|
/**
|
|
53
|
-
* package.json
|
|
66
|
+
* package.json 名(zod safeParse 守卫——形态异常兜底 root)
|
|
54
67
|
*/
|
|
55
|
-
const pkg =
|
|
68
|
+
const pkg = packageJsonSchema.safeParse(
|
|
69
|
+
JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')),
|
|
70
|
+
);
|
|
56
71
|
|
|
57
|
-
return (pkg.
|
|
72
|
+
return ((pkg.success ? pkg.data.name : undefined) ?? 'root')
|
|
73
|
+
.replace(/^@[^/]+\//, '')
|
|
74
|
+
.replace(/[^a-z0-9-]/g, '-');
|
|
58
75
|
} catch {
|
|
59
76
|
return 'root';
|
|
60
77
|
}
|
|
@@ -76,6 +93,9 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
76
93
|
* 追加模式(不动其他节)
|
|
77
94
|
*/
|
|
78
95
|
try {
|
|
96
|
+
/**
|
|
97
|
+
* 追加 scope 后的配置全文
|
|
98
|
+
*/
|
|
79
99
|
const appended = appendScopeToConfigText(readFileSync(configPath, 'utf8'), scope);
|
|
80
100
|
writeFileSync(configPath, appended);
|
|
81
101
|
info(`已追加 scope「${key}」到 ${configPath}`);
|
|
@@ -91,13 +111,31 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
91
111
|
* 注册表目录 + README 存根
|
|
92
112
|
*/
|
|
93
113
|
const registryAbs = join(root, scope.registryDir);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* README 存根路径(幂等不覆盖)
|
|
117
|
+
*/
|
|
94
118
|
const readmePath = join(registryAbs, 'README.md');
|
|
95
119
|
|
|
120
|
+
/**
|
|
121
|
+
* 指南候选相对路径(按注册表目录深度回溯到仓库根;采用仓不携带指南文件时回退 null)
|
|
122
|
+
*/
|
|
123
|
+
const guideRepoRel = `${'../'.repeat(scope.registryDir.split('/').length)}docs/guides/issue-file-format.md`;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 存根指南链接(本仓指南在场才落链接,否则 null 触发机器真源回退)
|
|
127
|
+
*/
|
|
128
|
+
const guideRelPath = existsSync(join(root, 'docs', 'guides', 'issue-file-format.md'))
|
|
129
|
+
? guideRepoRel
|
|
130
|
+
: null;
|
|
131
|
+
|
|
96
132
|
mkdirSync(registryAbs, { recursive: true });
|
|
97
133
|
|
|
98
134
|
if (!existsSync(readmePath)) {
|
|
99
|
-
writeFileSync(readmePath, renderRegistryReadmeStub(scope.title,
|
|
100
|
-
ok(
|
|
135
|
+
writeFileSync(readmePath, renderRegistryReadmeStub(scope.title, guideRelPath));
|
|
136
|
+
ok(
|
|
137
|
+
`已创建注册表目录 ${scope.registryDir} 并落格式存根 README.md${guideRelPath === null ? '(本仓无指南文件,存根指向 lzi-issues template 机器真源)' : ''}`,
|
|
138
|
+
);
|
|
101
139
|
} else {
|
|
102
140
|
info(`注册表 README 已存在,跳过(幂等不覆盖)`);
|
|
103
141
|
}
|
|
@@ -113,7 +151,7 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
113
151
|
{ flags: '--with-criteria', description: '附 criteria 判据块示例' },
|
|
114
152
|
{ flags: '--format <format>', description: 'text | json(json 返回字段契约)' },
|
|
115
153
|
],
|
|
116
|
-
schema: z.
|
|
154
|
+
schema: z.looseObject({ withCriteria: z.boolean().optional(), format: z.string().optional() }),
|
|
117
155
|
action: async (opts) => {
|
|
118
156
|
/**
|
|
119
157
|
* 骨架全文(渲染真源产物)
|
|
@@ -121,7 +159,9 @@ export function registerScaffoldCommands(program: Command): void {
|
|
|
121
159
|
const skeleton = renderIssueTemplateSkeleton();
|
|
122
160
|
|
|
123
161
|
if (opts.format === 'json') {
|
|
124
|
-
process.stdout.write(
|
|
162
|
+
process.stdout.write(
|
|
163
|
+
`${JSON.stringify({ template: skeleton, fields: TEMPLATE_FIELDS }, null, 2)}\n`,
|
|
164
|
+
);
|
|
125
165
|
return;
|
|
126
166
|
}
|
|
127
167
|
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 库入口(采用仓单依赖接入面;package.json main / exports ".")
|
|
3
|
+
*
|
|
4
|
+
* `lzi-issues init` 生成的 `lzi-issues.config.ts` 从本包导入 defineConfig——
|
|
5
|
+
* 采用仓只安装 `@longzai-intelligence-issues/cli` 即可加载配置,无需显式安装
|
|
6
|
+
* config 包(其为本包运行时依赖,由包管理器解析)。bin 入口(dist/index.js,
|
|
7
|
+
* package.json bin / exports "./bin")与本入口分路径构建发布,import 本模块
|
|
8
|
+
* 不会触发 CLI 命令面。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
parseLziIssuesConfig,
|
|
13
|
+
type LintBaselineEntry,
|
|
14
|
+
type LziIssuesConfig,
|
|
15
|
+
type LziIssuesConfigInput,
|
|
16
|
+
type ScopeEntry,
|
|
17
|
+
type ScopeEntryInput,
|
|
18
|
+
defineConfig,
|
|
19
|
+
} from '@longzai-intelligence-issues/config';
|