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.
- package/dist/adapters/command.schemas.js +18 -0
- package/dist/application/analysis.app.service.js +264 -0
- package/dist/application/bootstrap.js +21 -0
- package/dist/application/services/llm.analysis.service.js +170 -0
- package/dist/common/config.js +213 -0
- package/dist/common/constants.js +11 -0
- package/dist/common/errors.js +37 -0
- package/dist/common/logger.js +77 -0
- package/dist/common/types.js +2 -0
- package/dist/common/ui.js +201 -0
- package/dist/common/utils.js +117 -0
- package/dist/domain/index.js +17 -0
- package/dist/domain/interfaces.js +2 -0
- package/dist/domain/services/analysis.service.js +696 -0
- package/dist/domain/services/incremental.service.js +81 -0
- package/dist/infrastructure/blacklist.service.js +71 -0
- package/dist/infrastructure/cache/file.hash.cache.js +140 -0
- package/dist/infrastructure/git/git.service.js +159 -0
- package/dist/infrastructure/git.service.js +157 -0
- package/dist/infrastructure/index.service.js +108 -0
- package/dist/infrastructure/llm/llm.usage.tracker.js +58 -0
- package/dist/infrastructure/llm/openai.client.js +141 -0
- package/{src/infrastructure/llm/prompt.template.ts → dist/infrastructure/llm/prompt.template.js} +31 -36
- package/dist/infrastructure/llm.service.js +61 -0
- package/dist/infrastructure/skill/skill.generator.js +83 -0
- package/{src/infrastructure/skill/templates/resolve.script.ts → dist/infrastructure/skill/templates/resolve.script.js} +18 -15
- package/dist/infrastructure/skill/templates/skill.md.template.js +47 -0
- package/dist/infrastructure/splitter/code.splitter.js +137 -0
- package/dist/infrastructure/storage.service.js +409 -0
- package/dist/infrastructure/worker-pool/parse.worker.impl.js +137 -0
- package/dist/infrastructure/worker-pool/parse.worker.js +43 -0
- package/dist/infrastructure/worker-pool/worker-pool.service.js +171 -0
- package/package.json +5 -1
- package/jest.config.js +0 -27
- package/src/adapters/command.schemas.ts +0 -21
- package/src/application/analysis.app.service.ts +0 -272
- package/src/application/bootstrap.ts +0 -35
- package/src/application/services/llm.analysis.service.ts +0 -237
- package/src/cli.ts +0 -297
- package/src/common/config.ts +0 -209
- package/src/common/constants.ts +0 -8
- package/src/common/errors.ts +0 -34
- package/src/common/logger.ts +0 -82
- package/src/common/types.ts +0 -385
- package/src/common/ui.ts +0 -228
- package/src/common/utils.ts +0 -81
- package/src/domain/index.ts +0 -1
- package/src/domain/interfaces.ts +0 -188
- package/src/domain/services/analysis.service.ts +0 -735
- package/src/domain/services/incremental.service.ts +0 -50
- package/src/index.ts +0 -6
- package/src/infrastructure/blacklist.service.ts +0 -37
- package/src/infrastructure/cache/file.hash.cache.ts +0 -119
- package/src/infrastructure/git/git.service.ts +0 -120
- package/src/infrastructure/git.service.ts +0 -121
- package/src/infrastructure/index.service.ts +0 -94
- package/src/infrastructure/llm/llm.usage.tracker.ts +0 -65
- package/src/infrastructure/llm/openai.client.ts +0 -162
- package/src/infrastructure/llm.service.ts +0 -70
- package/src/infrastructure/skill/skill.generator.ts +0 -53
- package/src/infrastructure/skill/templates/skill.md.template.ts +0 -45
- package/src/infrastructure/splitter/code.splitter.ts +0 -176
- package/src/infrastructure/storage.service.ts +0 -413
- package/src/infrastructure/worker-pool/parse.worker.impl.ts +0 -135
- package/src/infrastructure/worker-pool/parse.worker.ts +0 -9
- package/src/infrastructure/worker-pool/worker-pool.service.ts +0 -173
- package/tsconfig.json +0 -24
- package/tsconfig.test.json +0 -5
package/src/common/types.ts
DELETED
|
@@ -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
|
-
}
|
package/src/common/utils.ts
DELETED
|
@@ -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
|
-
}
|
package/src/domain/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './interfaces'
|