skill-any-code 1.0.0 → 1.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 (68) hide show
  1. package/dist/adapters/command.schemas.js +18 -0
  2. package/dist/application/analysis.app.service.js +264 -0
  3. package/dist/application/bootstrap.js +21 -0
  4. package/dist/application/services/llm.analysis.service.js +170 -0
  5. package/dist/common/config.js +213 -0
  6. package/dist/common/constants.js +11 -0
  7. package/dist/common/errors.js +37 -0
  8. package/dist/common/logger.js +77 -0
  9. package/dist/common/types.js +2 -0
  10. package/dist/common/ui.js +201 -0
  11. package/dist/common/utils.js +117 -0
  12. package/dist/domain/index.js +17 -0
  13. package/dist/domain/interfaces.js +2 -0
  14. package/dist/domain/services/analysis.service.js +696 -0
  15. package/dist/domain/services/incremental.service.js +81 -0
  16. package/dist/infrastructure/blacklist.service.js +71 -0
  17. package/dist/infrastructure/cache/file.hash.cache.js +140 -0
  18. package/dist/infrastructure/git/git.service.js +159 -0
  19. package/dist/infrastructure/git.service.js +157 -0
  20. package/dist/infrastructure/index.service.js +108 -0
  21. package/dist/infrastructure/llm/llm.usage.tracker.js +58 -0
  22. package/dist/infrastructure/llm/openai.client.js +141 -0
  23. package/{src/infrastructure/llm/prompt.template.ts → dist/infrastructure/llm/prompt.template.js} +31 -36
  24. package/dist/infrastructure/llm.service.js +61 -0
  25. package/dist/infrastructure/skill/skill.generator.js +83 -0
  26. package/{src/infrastructure/skill/templates/resolve.script.ts → dist/infrastructure/skill/templates/resolve.script.js} +18 -15
  27. package/dist/infrastructure/skill/templates/skill.md.template.js +47 -0
  28. package/dist/infrastructure/splitter/code.splitter.js +137 -0
  29. package/dist/infrastructure/storage.service.js +409 -0
  30. package/dist/infrastructure/worker-pool/parse.worker.impl.js +137 -0
  31. package/dist/infrastructure/worker-pool/parse.worker.js +43 -0
  32. package/dist/infrastructure/worker-pool/worker-pool.service.js +171 -0
  33. package/package.json +5 -1
  34. package/jest.config.js +0 -27
  35. package/src/adapters/command.schemas.ts +0 -21
  36. package/src/application/analysis.app.service.ts +0 -272
  37. package/src/application/bootstrap.ts +0 -35
  38. package/src/application/services/llm.analysis.service.ts +0 -237
  39. package/src/cli.ts +0 -297
  40. package/src/common/config.ts +0 -209
  41. package/src/common/constants.ts +0 -8
  42. package/src/common/errors.ts +0 -34
  43. package/src/common/logger.ts +0 -82
  44. package/src/common/types.ts +0 -385
  45. package/src/common/ui.ts +0 -228
  46. package/src/common/utils.ts +0 -81
  47. package/src/domain/index.ts +0 -1
  48. package/src/domain/interfaces.ts +0 -188
  49. package/src/domain/services/analysis.service.ts +0 -735
  50. package/src/domain/services/incremental.service.ts +0 -50
  51. package/src/index.ts +0 -6
  52. package/src/infrastructure/blacklist.service.ts +0 -37
  53. package/src/infrastructure/cache/file.hash.cache.ts +0 -119
  54. package/src/infrastructure/git/git.service.ts +0 -120
  55. package/src/infrastructure/git.service.ts +0 -121
  56. package/src/infrastructure/index.service.ts +0 -94
  57. package/src/infrastructure/llm/llm.usage.tracker.ts +0 -65
  58. package/src/infrastructure/llm/openai.client.ts +0 -162
  59. package/src/infrastructure/llm.service.ts +0 -70
  60. package/src/infrastructure/skill/skill.generator.ts +0 -53
  61. package/src/infrastructure/skill/templates/skill.md.template.ts +0 -45
  62. package/src/infrastructure/splitter/code.splitter.ts +0 -176
  63. package/src/infrastructure/storage.service.ts +0 -413
  64. package/src/infrastructure/worker-pool/parse.worker.impl.ts +0 -135
  65. package/src/infrastructure/worker-pool/parse.worker.ts +0 -9
  66. package/src/infrastructure/worker-pool/worker-pool.service.ts +0 -173
  67. package/tsconfig.json +0 -24
  68. package/tsconfig.test.json +0 -5
@@ -1,385 +0,0 @@
1
- // 元数据结构
2
- export interface AnalysisMetadata {
3
- projectRoot: string
4
- lastAnalyzedAt: string
5
- gitCommits: Array<{
6
- hash: string
7
- branch: string
8
- analyzedAt: string
9
- }>
10
- /**
11
- * 非 Git 项目增量:文件快照(用于两次运行间的变更检测)。
12
- * 仅记录 stat 级信息,避免全量读取文件内容。
13
- *
14
- * key 为相对 projectRoot 的文件路径(保持与运行时 path.relative 一致,可能含 \ 或 /)。
15
- */
16
- fileSnapshot?: Record<string, { mtimeMs: number; size: number }>
17
- analysisVersion: string
18
- analyzedFilesCount: number
19
- schemaVersion: string
20
- }
21
-
22
- // 文件分析结果
23
- export interface FileAnalysis {
24
- type: 'file'
25
- path: string
26
- name: string
27
- language: string
28
- linesOfCode: number
29
- dependencies: string[]
30
- /**
31
- * 文件级 git commit id(最近一次修改该文件的提交),对应设计文档 10.3.2 / 13.2.1
32
- * git 不可用或文件未提交时可为空
33
- */
34
- fileGitCommitId?: string
35
- /**
36
- * 解析时文件是否处于未提交(dirty)状态,对应设计文档 10.3.2 / 13.2.1
37
- */
38
- isDirtyWhenAnalyzed?: boolean
39
- /**
40
- * 解析时文件内容哈希,用于增量与缓存决策,对应设计文档 10.3.2 / 13.2.1
41
- */
42
- fileHashWhenAnalyzed?: string
43
- /** 文件功能描述(200字以内),由 LLM 第二步调用生成 */
44
- description?: string
45
- summary: string
46
- classes: Array<{
47
- name: string
48
- extends?: string
49
- implements?: string[]
50
- methods: Array<{
51
- name: string
52
- signature: string
53
- description: string
54
- visibility: 'public' | 'private' | 'protected'
55
- }>
56
- properties: Array<{
57
- name: string
58
- type: string
59
- description: string
60
- visibility: 'public' | 'private' | 'protected'
61
- }>
62
- }>
63
- functions: Array<{
64
- name: string
65
- signature: string
66
- description: string
67
- }>
68
- lastAnalyzedAt: string
69
- commitHash: string
70
- }
71
-
72
- // 目录分析结果
73
- export interface DirectoryAnalysis {
74
- type: 'directory'
75
- path: string
76
- name: string
77
- /** 目录功能描述(200 字以内) */
78
- description: string
79
- /** 目录概述(100 字以内) */
80
- summary: string
81
- /** 直接子目录数量 */
82
- childrenDirsCount: number
83
- /** 直接子文件数量(仅解析范围内的文本代码文件) */
84
- childrenFilesCount: number
85
- structure: Array<{
86
- name: string
87
- type: 'file' | 'directory'
88
- description: string
89
- }>
90
- lastAnalyzedAt: string
91
- commitHash: string
92
- }
93
-
94
- // 项目整体总结
95
- export interface ProjectSummary {
96
- projectName: string
97
- slug: string
98
- description: string
99
- techStack: string[]
100
- codeSize: {
101
- files: number
102
- lines: number
103
- languages: Record<string, number>
104
- }
105
- architecture: {
106
- layers: string[]
107
- modules: Array<{ name: string; description: string }>
108
- dependencies: Array<{ from: string; to: string }>
109
- }
110
- coreFlow: string
111
- architectureDiagram: string
112
- flowDiagram: string
113
- generatedAt: string
114
- commitHash: string
115
- }
116
-
117
- // 修正日志
118
- export interface ModificationLog {
119
- id: string
120
- timestamp: string
121
- path: string
122
- type: 'inconsistency' | 'omission' | 'error'
123
- originalContent: string
124
- correctedContent: string
125
- reason: string
126
- }
127
-
128
- // 解析断点
129
- export interface AnalysisCheckpoint {
130
- projectRoot: string
131
- commitHash: string
132
- mode: 'full' | 'incremental'
133
- completedFiles: string[]
134
- completedDirs: string[]
135
- pendingTasks: Array<{ type: 'file' | 'directory'; path: string }>
136
- createdAt: string
137
- }
138
-
139
- // ===== V2.3 索引相关类型 =====
140
- export interface IndexEntry {
141
- resultPath: string
142
- type: 'file' | 'directory'
143
- }
144
-
145
- export interface AnalysisIndex {
146
- version: string
147
- projectRoot: string
148
- storageRoot: string
149
- generatedAt: string
150
- entries: Record<string, IndexEntry>
151
- }
152
-
153
- // ===== V2.3 resolve-config.json =====
154
- export interface ResolveConfig {
155
- indexFilePath: string
156
- }
157
-
158
- export interface AnalysisObject {
159
- type: 'file' | 'directory'
160
- path: string
161
- }
162
-
163
- export interface ObjectResultMeta {
164
- status: 'parsed' | 'cached' | 'filtered' | 'skipped' | 'failed'
165
- reason?: string
166
- }
167
-
168
- // 进度回调:每完成一个对象(文件/目录)时调用(兼容旧接口)
169
- export type ProgressCallback = (done: number, total: number, current?: { path: string }) => void
170
-
171
- // 分析服务参数
172
- export interface FullAnalysisParams {
173
- projectRoot: string
174
- depth?: number
175
- concurrency: number
176
- /** 可选:每完成一个文件/目录时回调,用于进度条等 */
177
- onProgress?: ProgressCallback
178
- /** V2.4:对象级生命周期回调 */
179
- onObjectPlanned?: (obj: AnalysisObject) => void
180
- onObjectStarted?: (obj: AnalysisObject) => void
181
- onObjectCompleted?: (obj: AnalysisObject, meta: ObjectResultMeta) => void
182
- /** V2.6:扫描阶段进度回调,统计“将被解析的对象数(文件+目录)”,用于 CLI 单行实时展示 */
183
- onScanProgress?: (scannedFiles: number) => void
184
- }
185
-
186
- export interface IncrementalAnalysisParams {
187
- projectRoot: string
188
- baseCommit: string
189
- targetCommit: string
190
- changedFiles: string[]
191
- /**
192
- * V2.6:增量自修复 - 需要重新聚合的目录列表(例如目录结果缺失但文件未变更)
193
- */
194
- changedDirs?: string[]
195
- concurrency: number
196
- /** V2.4:对象级生命周期回调 */
197
- onObjectPlanned?: (obj: AnalysisObject) => void
198
- onObjectStarted?: (obj: AnalysisObject) => void
199
- onObjectCompleted?: (obj: AnalysisObject, meta: ObjectResultMeta) => void
200
- /** 扫描阶段进度回调,用于 CLI 单行实时展示增量待处理对象数(文件+目录) */
201
- onScanProgress?: (scannedObjects: number) => void
202
- }
203
-
204
- export interface ResumeAnalysisParams {
205
- projectRoot: string
206
- checkpoint: AnalysisCheckpoint
207
- }
208
-
209
- /**
210
- * 统一解析参数:全量与增量共享同一条管线,仅通过 fileFilter 区分过滤策略。
211
- */
212
- export interface AnalysisParams {
213
- projectRoot: string
214
- depth?: number
215
- concurrency: number
216
- mode: 'full' | 'incremental'
217
- commitHash: string
218
- /** 文件过滤器:返回 true 表示该文件需要(重新)解析 */
219
- fileFilter: (relPath: string, absPath: string) => Promise<boolean>
220
- /** 过滤完成后通知总对象数(用于 CLI 启动进度条) */
221
- onTotalKnown?: (total: number) => void
222
- onObjectPlanned?: (obj: AnalysisObject) => void
223
- onObjectStarted?: (obj: AnalysisObject) => void
224
- onObjectCompleted?: (obj: AnalysisObject, meta: ObjectResultMeta) => void
225
- onScanProgress?: (scannedFiles: number) => void
226
- }
227
-
228
- // 分析结果
229
- export interface AnalysisResult {
230
- success: boolean
231
- analyzedFilesCount: number
232
- analyzedDirsCount: number
233
- duration: number
234
- errors: Array<{ path: string; message: string }>
235
- projectSlug: string
236
- summaryPath: string
237
- /** V2.3:解析过程中收集的索引条目 */
238
- indexEntries: Array<{ sourcePath: string; resultPath: string; type: 'file' | 'directory' }>
239
- /** V2.3:增量解析中已删除的源码路径 */
240
- removedSourcePaths: string[]
241
- }
242
-
243
- // 命令参数类型
244
- export interface AnalyzeProjectCommandParams {
245
- path?: string
246
- mode?: 'full' | 'incremental' | 'auto'
247
- depth?: number
248
- concurrency?: number
249
- llmConfig?: LLMConfig
250
- /** V2.3:Skill 部署的 Provider 列表 */
251
- skillsProviders?: string[]
252
- /** V2.3:是否跳过 Skill 生成 */
253
- noSkills?: boolean
254
- /** V2.3:结果输出目录 */
255
- outputDir?: string
256
- /** 总对象数已知时回调(用于 CLI 启动进度条) */
257
- onTotalKnown?: (total: number) => void
258
- /** 进度更新回调(done, total, current) */
259
- onProgress?: ProgressCallback
260
- /** Token 使用快照回调:每次 LLM 调用后触发,用于 CLI UI 实时展示 Tokens 行 */
261
- onTokenUsageSnapshot?: (stats: TokenUsageStats) => void
262
- /** V2.6:扫描阶段进度回调,用于 CLI 单行显示“已扫描将被解析的对象数(文件+目录)” */
263
- onScanProgress?: (scannedFiles: number) => void
264
- }
265
-
266
- // 命令返回结果
267
- export interface AnalyzeProjectCommandResult {
268
- success: boolean
269
- code: number
270
- message: string
271
- data?: {
272
- projectName: string
273
- mode: 'full' | 'incremental'
274
- analyzedFilesCount: number
275
- duration: number
276
- summaryPath: string
277
- tokenUsage?: TokenUsageStats
278
- }
279
- errors?: Array<{
280
- path: string
281
- message: string
282
- }>
283
- }
284
-
285
- // LLM调用选项
286
- export interface LLMCallOptions {
287
- model?: string;
288
- temperature?: number;
289
- maxTokens?: number;
290
- timeout?: number;
291
- retries?: number;
292
- }
293
-
294
- // LLM响应结果
295
- export interface LLMResponse {
296
- content: string;
297
- usage: {
298
- promptTokens: number;
299
- completionTokens: number;
300
- totalTokens: number;
301
- };
302
- model: string;
303
- responseTime: number;
304
- }
305
-
306
- export interface TokenUsageStats {
307
- totalPromptTokens: number
308
- totalCompletionTokens: number
309
- totalTokens: number
310
- totalCalls: number
311
- }
312
-
313
- // 文件分片
314
- export interface FileChunk {
315
- id: number;
316
- content: string;
317
- startLine: number;
318
- endLine: number;
319
- context?: string;
320
- }
321
-
322
- // 分片解析结果
323
- export interface FileChunkAnalysis {
324
- chunkId: number;
325
- classes: Array<{
326
- name: string;
327
- extends?: string;
328
- implements?: string[];
329
- methods: Array<{
330
- name: string;
331
- signature: string;
332
- description: string;
333
- visibility: 'public' | 'private' | 'protected';
334
- }>;
335
- properties: Array<{
336
- name: string;
337
- type: string;
338
- description: string;
339
- visibility: 'public' | 'private' | 'protected';
340
- }>;
341
- }>;
342
- functions: Array<{
343
- name: string;
344
- signature: string;
345
- description: string;
346
- }>;
347
- }
348
-
349
- // LLM配置
350
- export interface LLMConfig {
351
- model: string;
352
- api_key: string;
353
- base_url?: string;
354
- temperature: number;
355
- max_tokens: number;
356
- timeout: number;
357
- proxy?: string;
358
- max_retries: number;
359
- retry_delay: number;
360
- context_window_size: number;
361
- cache_enabled: boolean;
362
- cache_dir: string;
363
- // V2.5:缓存容量上限(MB),0 表示禁用磁盘缓存
364
- cache_max_size_mb: number;
365
- }
366
-
367
- // 扩展CLI配置(与 Config 对齐,供类型引用)
368
- export interface CliConfig {
369
- global: {
370
- logLevel: 'debug' | 'info' | 'warn' | 'error';
371
- outputFormat: 'text' | 'json' | 'markdown';
372
- autoConfirm: boolean;
373
- outputDir: string;
374
- };
375
- llm: LLMConfig;
376
- commands: {
377
- analyze: {
378
- defaultDepth: number;
379
- defaultConcurrency: number;
380
- };
381
- skills: {
382
- defaultProviders: string[];
383
- };
384
- };
385
- }
package/src/common/ui.ts DELETED
@@ -1,228 +0,0 @@
1
- import inquirer from 'inquirer';
2
- import pc from 'picocolors';
3
- import type { TokenUsageStats } from './types';
4
-
5
- interface CliRenderState {
6
- total: number;
7
- done: number;
8
- currentObjects: string[];
9
- tokens?: TokenUsageStats;
10
- totalKnown: boolean;
11
- scannedFiles?: number;
12
- }
13
-
14
- /**
15
- * CLI 多行区域渲染器:统一负责
16
- * - 进度行:「解析进度 |████...| 37% | 已处理: x/y 对象」
17
- * - 当前对象块:
18
- * 当前对象:
19
- * [ foo ]
20
- * [ bar ]
21
- * - Tokens 行:「Tokens: in=... out=... total=...」
22
- *
23
- * 通过 ANSI 光标控制码在同一块区域内重绘,避免产生多份重复的块。
24
- */
25
- export class CliMultiSectionRenderer {
26
- private state: CliRenderState = {
27
- total: 0,
28
- done: 0,
29
- currentObjects: [],
30
- totalKnown: false,
31
- };
32
-
33
- // 最近一次渲染占用了多少行,用于回退光标和清空
34
- private renderedLines = 0;
35
- private readonly isInteractive: boolean;
36
-
37
- constructor() {
38
- this.isInteractive = !!process.stdout.isTTY && process.env.TERM !== 'dumb';
39
- }
40
-
41
- setTotal(total: number) {
42
- this.state.total = total;
43
- this.state.totalKnown = true;
44
- if (this.state.done > total) {
45
- this.state.done = total;
46
- }
47
- this.render();
48
- }
49
-
50
- updateProgress(done: number, total: number, currentPathsText?: string, maxLines?: number) {
51
- this.state.done = done;
52
- // 若 total 尚未通过 onTotalKnown 确认,则不接受外部传入的 total,避免占位魔法数字污染首帧。
53
- if (this.state.totalKnown) {
54
- this.state.total = total;
55
- }
56
- if (currentPathsText !== undefined) {
57
- let lines = currentPathsText
58
- .split('\n')
59
- .map((l) => l.trim())
60
- .filter((l) => l.length > 0);
61
- if (typeof maxLines === 'number' && maxLines > 0 && lines.length > maxLines) {
62
- lines = lines.slice(0, maxLines);
63
- }
64
- this.state.currentObjects = lines;
65
- }
66
- this.render();
67
- }
68
-
69
- updateTokens(tokens: TokenUsageStats) {
70
- this.state.tokens = { ...tokens };
71
- this.render();
72
- }
73
-
74
- /**
75
- * 扫描阶段:实时更新“已扫描将被解析的文件数”。
76
- * 该信息以单独一行展示,并在同一位置覆盖刷新。
77
- */
78
- updateScanProgress(scannedFiles: number) {
79
- this.state.scannedFiles = scannedFiles;
80
- this.render();
81
- }
82
-
83
- /**
84
- * 在进度区域下方追加一条日志。
85
- * 实现方式:先清空进度区域,将日志打印到 stdout,然后重新渲染进度区域。
86
- */
87
- logBelow(message: string) {
88
- if (this.isInteractive) {
89
- this.clearRenderedArea();
90
- process.stdout.write(message + '\n');
91
- this.render();
92
- } else {
93
- // 非交互终端下不做覆盖,仅顺序输出日志与最新快照,避免依赖 ANSI 光标控制。
94
- process.stdout.write(message + '\n');
95
- const lines = this.buildLines();
96
- for (const line of lines) {
97
- process.stdout.write(line + '\n');
98
- }
99
- }
100
- }
101
-
102
- private buildLines(): string[] {
103
- const { total, done, currentObjects, tokens, totalKnown, scannedFiles } = this.state;
104
-
105
- const safeTotal = totalKnown && total > 0 ? total : 1;
106
- const ratio = totalKnown ? Math.max(0, Math.min(1, done / safeTotal)) : 0;
107
- const percentage = totalKnown ? Math.floor(ratio * 100) : 0;
108
-
109
- const barWidth = 40;
110
- const completeCount = Math.round(barWidth * ratio);
111
- const incompleteCount = barWidth - completeCount;
112
- const bar =
113
- '█'.repeat(completeCount) +
114
- '░'.repeat(Math.max(0, incompleteCount));
115
-
116
- const lines: string[] = [];
117
-
118
- // 扫描阶段行:仅当有值时展示一行,实时覆盖刷新(对象=文件+目录)
119
- if (typeof scannedFiles === 'number') {
120
- lines.push(`Scanned objects to analyze: ${scannedFiles}`);
121
- }
122
-
123
- // 进度行(V2.4:将「已处理: x/y」拆为独立行,避免被“当前对象路径提取”误判为路径)
124
- const totalLabel = totalKnown ? String(total) : '?';
125
- lines.push(`${pc.blue('Progress')} |${pc.cyan(bar)}| ${percentage}%`);
126
- lines.push(`Processed: ${done}/${totalLabel}`);
127
-
128
- // 当前对象块
129
- // 需求:仅当存在 worker 正在处理的对象时才展示该块;无对象时整段不输出(不占用任何行)。
130
- if (currentObjects.length > 0) {
131
- lines.push('Current:');
132
- for (const obj of currentObjects) {
133
- lines.push(` [ ${obj} ]`);
134
- }
135
- }
136
-
137
- // Tokens 行(即使 tokens 尚未产生也显示 0,避免首帧缺行导致 UI 跳变)
138
- const t = tokens ?? { totalPromptTokens: 0, totalCompletionTokens: 0, totalTokens: 0, totalCalls: 0 };
139
- lines.push(
140
- `Tokens: in=${t.totalPromptTokens} out=${t.totalCompletionTokens} total=${t.totalTokens}`,
141
- );
142
-
143
- return lines;
144
- }
145
-
146
- private clearRenderedArea() {
147
- if (!this.isInteractive || this.renderedLines <= 0) return;
148
-
149
- // 将光标移动到渲染块的起始行
150
- process.stdout.write(`\u001b[${this.renderedLines}F`);
151
- for (let i = 0; i < this.renderedLines; i += 1) {
152
- // 清除当前行并换行到下一行
153
- process.stdout.write('\u001b[2K\r\n');
154
- }
155
- // 回到块的起始位置
156
- process.stdout.write(`\u001b[${this.renderedLines}F`);
157
- this.renderedLines = 0;
158
- }
159
-
160
- private render() {
161
- const lines = this.buildLines();
162
-
163
- if (this.isInteractive) {
164
- // 将光标移到当前块顶部并清空旧内容
165
- if (this.renderedLines > 0) {
166
- process.stdout.write(`\u001b[${this.renderedLines}F`);
167
- }
168
- for (let i = 0; i < this.renderedLines; i += 1) {
169
- process.stdout.write('\u001b[2K\r\n');
170
- }
171
- if (this.renderedLines > 0) {
172
- process.stdout.write(`\u001b[${this.renderedLines}F`);
173
- }
174
-
175
- // 写入新内容
176
- for (const line of lines) {
177
- process.stdout.write('\u001b[2K'); // 清当前行
178
- process.stdout.write(line + '\n');
179
- }
180
-
181
- this.renderedLines = lines.length;
182
- } else {
183
- // 非交互终端:不做覆盖,仅输出一次最新快照,避免光标控制符干扰日志采集。
184
- for (const line of lines) {
185
- process.stdout.write(line + '\n');
186
- }
187
- this.renderedLines = 0;
188
- }
189
- }
190
- }
191
-
192
- export const cliRenderer = new CliMultiSectionRenderer();
193
-
194
- export async function confirm(message: string, defaultAnswer: boolean = false): Promise<boolean> {
195
- const { answer } = await inquirer.prompt([
196
- {
197
- type: 'confirm',
198
- name: 'answer',
199
- message,
200
- default: defaultAnswer,
201
- },
202
- ]);
203
- return answer;
204
- }
205
-
206
- export async function select(message: string, choices: string[]): Promise<string> {
207
- const { selected } = await inquirer.prompt([
208
- {
209
- type: 'list',
210
- name: 'selected',
211
- message,
212
- choices,
213
- },
214
- ]);
215
- return selected;
216
- }
217
-
218
- export async function input(message: string, defaultValue?: string): Promise<string> {
219
- const { value } = await inquirer.prompt([
220
- {
221
- type: 'input',
222
- name: 'value',
223
- message,
224
- default: defaultValue,
225
- },
226
- ]);
227
- return value;
228
- }
@@ -1,81 +0,0 @@
1
- import * as crypto from 'crypto'
2
- import * as path from 'path'
3
- import { DEFAULT_OUTPUT_DIR } from './constants'
4
-
5
- /**
6
- * 规范化路径:统一使用正斜杠、移除尾部斜杠
7
- * 用于索引文件中的路径标准化和 resolve 查询时的路径匹配
8
- */
9
- export function normalizePath(inputPath: string): string {
10
- let normalized = inputPath.replace(/\\/g, '/')
11
- if (normalized.length > 1 && normalized.endsWith('/')) {
12
- normalized = normalized.slice(0, -1)
13
- }
14
- return normalized
15
- }
16
-
17
- export function generateProjectSlug(projectRoot: string, isGit: boolean, gitSlug?: string): string {
18
- if (isGit && gitSlug) {
19
- return gitSlug.replace('/', '-')
20
- }
21
- const dirName = path.basename(projectRoot)
22
- const pathHash = crypto.createHash('md5').update(projectRoot).digest('hex').slice(0, 8)
23
- return `${dirName}-${pathHash}`
24
- }
25
-
26
- export function getStoragePath(projectRoot: string, customOutputDir?: string): string {
27
- const outputDir = customOutputDir || DEFAULT_OUTPUT_DIR
28
- // 如果是相对路径,相对于项目根目录
29
- if (!path.isAbsolute(outputDir)) {
30
- return path.resolve(projectRoot, outputDir)
31
- }
32
- return outputDir
33
- }
34
-
35
- export function getFileOutputPath(storageRoot: string, filePath: string): string {
36
- const parsed = path.parse(filePath)
37
- // 特殊处理 index.xxx:为避免与目录级 index.md 冲突,文件结果命名为 index.xxx.md
38
- if (parsed.name === 'index' && parsed.ext) {
39
- return path.join(storageRoot, parsed.dir, `index${parsed.ext}.md`)
40
- }
41
- return path.join(storageRoot, parsed.dir, `${parsed.name}.md`)
42
- }
43
-
44
- export function getDirOutputPath(storageRoot: string, dirPath: string): string {
45
- return path.join(storageRoot, dirPath, 'index.md')
46
- }
47
-
48
- /**
49
- * 受控并发执行:同时最多 limit 个 fn 在运行,所有 items 执行完毕后 resolve。
50
- */
51
- export async function mapLimit<T>(items: T[], limit: number, fn: (item: T) => Promise<void>): Promise<void> {
52
- const concurrency = Math.max(1, Number(limit) || 1)
53
- let nextIndex = 0
54
- const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
55
- while (true) {
56
- const idx = nextIndex++
57
- if (idx >= items.length) return
58
- await fn(items[idx])
59
- }
60
- })
61
- await Promise.all(runners)
62
- }
63
-
64
- export function getLanguageFromExtension(ext: string): string {
65
- const map: Record<string, string> = {
66
- '.js': 'javascript',
67
- '.ts': 'typescript',
68
- '.jsx': 'javascript',
69
- '.tsx': 'typescript',
70
- '.py': 'python',
71
- '.java': 'java',
72
- '.go': 'go',
73
- '.cpp': 'cpp',
74
- '.c': 'c',
75
- '.cs': 'csharp',
76
- '.php': 'php',
77
- '.rb': 'ruby',
78
- '.rs': 'rust'
79
- }
80
- return map[ext.toLowerCase()] || 'unknown'
81
- }
@@ -1 +0,0 @@
1
- export * from './interfaces'