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,413 +0,0 @@
1
- import * as fs from 'fs-extra'
2
- import * as path from 'path'
3
- import { createHash } from 'crypto'
4
- import { IStorageService } from '../domain/interfaces'
5
- import { FileAnalysis, DirectoryAnalysis, AnalysisCheckpoint } from '../common/types'
6
- import { AppError, ErrorCode } from '../common/errors'
7
- import { getStoragePath, getFileOutputPath, getDirOutputPath } from '../common/utils'
8
-
9
- export class LocalStorageService implements IStorageService {
10
- private projectRoot: string;
11
- private customOutputDir?: string;
12
-
13
- constructor(projectRoot: string = process.cwd(), customOutputDir?: string) {
14
- this.projectRoot = projectRoot;
15
- this.customOutputDir = customOutputDir;
16
- }
17
-
18
- private getStorageRoot(): string {
19
- return getStoragePath(this.projectRoot, this.customOutputDir);
20
- }
21
-
22
- private normalizeNewlines(input: string): string {
23
- return input.replace(/\r\n/g, '\n')
24
- }
25
-
26
- private extractSection(markdown: string, title: string): string {
27
- const md = this.normalizeNewlines(markdown)
28
- const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
29
- const re = new RegExp(`\\n##\\s+${escaped}\\n([\\s\\S]*?)(?=\\n##\\s+|\\n#\\s+|$)`, 'm')
30
- const m = md.match(re)
31
- return (m?.[1] ?? '').trim()
32
- }
33
-
34
- private extractSectionAny(markdown: string, titles: string[]): string {
35
- for (const t of titles) {
36
- const v = this.extractSection(markdown, t)
37
- if (v) return v
38
- }
39
- return ''
40
- }
41
-
42
- private parseBasicInfo(markdown: string): Record<string, string> {
43
- const basic = this.extractSectionAny(markdown, ['Basic Information', '基本信息'])
44
- const lines = basic.split('\n').map(l => l.trim()).filter(Boolean)
45
- const map: Record<string, string> = {}
46
- for (const line of lines) {
47
- const cleaned = line.replace(/^-+\s*/, '').trim()
48
- const idx = cleaned.indexOf(':') >= 0 ? cleaned.indexOf(':') : cleaned.indexOf(':')
49
- if (idx <= 0) continue
50
- const k = cleaned.slice(0, idx).trim()
51
- const v = cleaned.slice(idx + 1).trim()
52
- if (k) map[k] = v
53
- }
54
- return map
55
- }
56
-
57
- private getBasicValue(basic: Record<string, string>, keys: string[]): string | undefined {
58
- for (const k of keys) {
59
- const v = basic[k]
60
- if (v !== undefined) return v
61
- }
62
- return undefined
63
- }
64
-
65
- private parseFileMarkdownToAnalysis(markdown: string, filePath: string): FileAnalysis | null {
66
- const md = this.normalizeNewlines(markdown)
67
- const firstLine = md.split('\n')[0]?.trim() ?? ''
68
- const name = firstLine.startsWith('# ') ? firstLine.slice(2).trim() : path.basename(filePath)
69
- const basic = this.parseBasicInfo(md)
70
- const summary = this.extractSectionAny(md, ['Summary', '概述'])
71
- const description = this.extractSectionAny(md, ['Description', '功能描述'])
72
-
73
- const language = this.getBasicValue(basic, ['Language', '语言']) ?? 'unknown'
74
- const loc = Number(this.getBasicValue(basic, ['Lines of Code', '代码行数']) ?? NaN)
75
- const lastAnalyzedAt = this.getBasicValue(basic, ['Last Analyzed At', '最后解析时间']) ?? new Date(0).toISOString()
76
- const fileGitCommitId = this.getBasicValue(basic, ['file_git_commit_id'])
77
- const isDirtyWhenAnalyzedRaw = this.getBasicValue(basic, ['is_dirty_when_analyzed'])
78
- const fileHashWhenAnalyzed = this.getBasicValue(basic, ['file_hash_when_analyzed'])
79
-
80
- const isDirtyWhenAnalyzed =
81
- isDirtyWhenAnalyzedRaw === undefined
82
- ? undefined
83
- : ['true', '1', 'yes'].includes(isDirtyWhenAnalyzedRaw.toLowerCase())
84
-
85
- return {
86
- type: 'file',
87
- path: filePath,
88
- name,
89
- language,
90
- linesOfCode: Number.isFinite(loc) ? loc : 0,
91
- dependencies: [],
92
- fileGitCommitId: fileGitCommitId && fileGitCommitId !== 'N/A' ? fileGitCommitId : undefined,
93
- isDirtyWhenAnalyzed,
94
- fileHashWhenAnalyzed: fileHashWhenAnalyzed || undefined,
95
- description: description || undefined,
96
- summary: summary || '',
97
- classes: [],
98
- functions: [],
99
- lastAnalyzedAt,
100
- commitHash: '',
101
- }
102
- }
103
-
104
- private parseDirectoryMarkdownToAnalysis(markdown: string, dirPath: string): DirectoryAnalysis | null {
105
- const md = this.normalizeNewlines(markdown)
106
- const firstLine = md.split('\n')[0]?.trim() ?? ''
107
- const rawName = firstLine.startsWith('# ') ? firstLine.slice(2).trim() : path.basename(dirPath)
108
- const name = rawName.replace(/\s*(Directory|目录)\s*$/, '').trim() || path.basename(dirPath)
109
- const summary = this.extractSectionAny(md, ['Summary', '概述'])
110
- const description = this.extractSectionAny(md, ['Description', '功能描述']) || summary
111
- const basic = this.parseBasicInfo(md)
112
- const lastAnalyzedAt = this.getBasicValue(basic, ['Last Analyzed At', '最后解析时间']) ?? new Date(0).toISOString()
113
- return {
114
- type: 'directory',
115
- path: dirPath,
116
- name,
117
- description: description || '',
118
- summary: summary || '',
119
- childrenDirsCount: 0,
120
- childrenFilesCount: 0,
121
- structure: [],
122
- lastAnalyzedAt,
123
- commitHash: '',
124
- }
125
- }
126
-
127
- /**
128
- * 仅更新结果 Markdown 中「基本信息」段的部分字段,避免依赖任何内部 JSON 状态。
129
- */
130
- private async updateFileMarkdownBasicInfo(
131
- outputPath: string,
132
- updates: Partial<Pick<FileAnalysis, 'fileGitCommitId' | 'isDirtyWhenAnalyzed' | 'fileHashWhenAnalyzed' | 'lastAnalyzedAt'>>,
133
- ): Promise<void> {
134
- const raw = await fs.readFile(outputPath, 'utf-8')
135
- const md = this.normalizeNewlines(raw)
136
- const basic = this.extractSectionAny(md, ['Basic Information', '基本信息'])
137
- if (!basic) {
138
- // 如果没有“基本信息”段,直接回退为重写全文件(风险较大);这里选择保守:不改动
139
- return
140
- }
141
-
142
- const lines = basic.split('\n')
143
- const patchKV = (key: string, value: string) => {
144
- const re = new RegExp(`^\\s*-\\s*${key}\\s*[::].*$`, 'm')
145
- const replacement = `- ${key}: ${value}`
146
- const joined = lines.join('\n')
147
- if (re.test(joined)) {
148
- const next = joined.replace(re, replacement)
149
- lines.splice(0, lines.length, ...next.split('\n'))
150
- } else {
151
- lines.push(replacement)
152
- }
153
- }
154
-
155
- if (updates.lastAnalyzedAt) {
156
- patchKV('Last Analyzed At', updates.lastAnalyzedAt)
157
- patchKV('最后解析时间', updates.lastAnalyzedAt) // backward-compat for old markdown
158
- }
159
- if (updates.fileGitCommitId !== undefined) patchKV('file_git_commit_id', updates.fileGitCommitId || 'N/A')
160
- if (updates.isDirtyWhenAnalyzed !== undefined) patchKV('is_dirty_when_analyzed', String(!!updates.isDirtyWhenAnalyzed))
161
- if (updates.fileHashWhenAnalyzed !== undefined) patchKV('file_hash_when_analyzed', updates.fileHashWhenAnalyzed || '')
162
-
163
- const newBasic = lines.join('\n').trim() + '\n'
164
- const escapedEn = 'Basic Information'.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
165
- const escapedZh = '基本信息'.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
166
- const reSection = new RegExp(`(\\n##\\s+(?:${escapedEn}|${escapedZh})\\n)([\\s\\S]*?)(?=\\n##\\s+|\\n#\\s+|$)`, 'm')
167
- const nextMd = md.replace(reSection, `$1${newBasic}`)
168
- await fs.writeFile(outputPath, nextMd, 'utf-8')
169
- }
170
-
171
- async saveFileAnalysis(projectSlug: string, filePath: string, data: FileAnalysis): Promise<void> {
172
- try {
173
- const storageRoot = this.getStorageRoot()
174
- const outputPath = getFileOutputPath(storageRoot, filePath)
175
- await fs.ensureDir(path.dirname(outputPath))
176
-
177
- const relativePath = path.relative(this.projectRoot, filePath) || data.path
178
- const fileGitCommitId = data.fileGitCommitId ?? 'N/A'
179
- const isDirty = data.isDirtyWhenAnalyzed ?? false
180
- let fileHash = data.fileHashWhenAnalyzed ?? ''
181
- // 防御:某些路径/worker 回传异常场景下 fileHashWhenAnalyzed 可能丢失。
182
- // 这里尝试根据源码文件内容补齐,保证基本信息段可用于增量与回归测试。
183
- if (!fileHash) {
184
- try {
185
- const abs =
186
- path.isAbsolute(filePath) ? filePath : path.resolve(this.projectRoot, filePath)
187
- if (await fs.pathExists(abs)) {
188
- const content = await fs.readFile(abs, 'utf-8')
189
- fileHash = createHash('sha256').update(content).digest('hex')
190
- }
191
- } catch {
192
- // ignore:保持空字符串写入,避免影响主流程
193
- }
194
- }
195
-
196
- let content = `# ${data.name}\n\n`
197
-
198
- // 基本信息段(设计文档第 13.2.4)
199
- content += '## Basic Information\n'
200
- content += `- Path: ${relativePath}\n`
201
- content += `- Language: ${data.language}\n`
202
- content += `- Lines of Code: ${data.linesOfCode}\n`
203
- content += `- Last Analyzed At: ${data.lastAnalyzedAt}\n`
204
- content += `- file_git_commit_id: ${fileGitCommitId}\n`
205
- content += `- is_dirty_when_analyzed: ${isDirty}\n`
206
- content += `- file_hash_when_analyzed: ${fileHash}\n\n`
207
-
208
- // 概述与功能描述
209
- const summary = data.summary || ''
210
- const description = data.description || ''
211
- content += `## Summary\n${summary}\n\n`
212
- content += `## Description\n${description || summary}\n\n`
213
-
214
- // 类定义
215
- if (data.classes.length > 0) {
216
- content += '## Classes\n'
217
- for (const cls of data.classes) {
218
- content += `### ${cls.name}\n`
219
- if (cls.extends) content += `- Extends: ${cls.extends}\n`
220
- if (cls.implements && cls.implements.length > 0) content += `- Implements: ${cls.implements.join(', ')}\n`
221
-
222
- if (cls.properties.length > 0) {
223
- content += `- Properties:\n`
224
- for (const prop of cls.properties) {
225
- content += ` - ${prop.visibility} ${prop.name}: ${prop.type} - ${prop.description}\n`
226
- }
227
- }
228
-
229
- if (cls.methods.length > 0) {
230
- content += `- Methods:\n`
231
- for (const method of cls.methods) {
232
- content += ` - ${method.visibility} ${method.signature} - ${method.description}\n`
233
- }
234
- }
235
-
236
- content += '\n'
237
- }
238
- }
239
-
240
- // 全局函数
241
- if (data.functions.length > 0) {
242
- content += '## Global Functions\n'
243
- for (const func of data.functions) {
244
- content += `- ${func.signature} - ${func.description}\n`
245
- }
246
- content += '\n'
247
- }
248
-
249
- await fs.writeFile(outputPath, content, 'utf-8')
250
- } catch (e) {
251
- throw new AppError(ErrorCode.STORAGE_WRITE_FAILED, 'Failed to save file analysis result', e)
252
- }
253
- }
254
-
255
- async saveDirectoryAnalysis(projectSlug: string, dirPath: string, data: DirectoryAnalysis): Promise<void> {
256
- try {
257
- const storageRoot = this.getStorageRoot()
258
- const outputPath = getDirOutputPath(storageRoot, dirPath)
259
- await fs.ensureDir(path.dirname(outputPath))
260
-
261
- const relativePath = path.relative(this.projectRoot, dirPath) || data.path
262
- const childrenDirs = data.structure.filter(item => item.type === 'directory')
263
- const childrenFiles = data.structure.filter(item => item.type === 'file')
264
-
265
- let content = `# ${data.name} Directory\n\n`
266
-
267
- // 基本信息
268
- content += '## Basic Information\n'
269
- content += `- Path: ${relativePath}\n`
270
- content += `- Child Directories: ${childrenDirs.length}\n`
271
- content += `- Child Files: ${childrenFiles.length}\n`
272
- content += `- Last Analyzed At: ${data.lastAnalyzedAt}\n\n`
273
-
274
- let description = (data as any).description ?? data.summary
275
- const summary = data.summary
276
-
277
- // If LLM description is too short or purely statistical, add a deterministic English fallback.
278
- const asciiWordCount = (description || '').trim().split(/\s+/).filter(Boolean).length
279
- const looksLikeStatOnly = /\b\d+\b/.test(description || '') && /(files?|directories?)/i.test(description || '')
280
- if (!description || asciiWordCount < 12 || looksLikeStatOnly) {
281
- const fileNames = childrenFiles.map(f => f.name).join(', ')
282
- const dirNames = childrenDirs.map(d => d.name).join(', ')
283
- const extraKeywords = relativePath.includes('SenseVoice')
284
- ? ' It also appears to contain SenseVoice examples/demos.'
285
- : ''
286
- description =
287
- `The "${data.name}" directory at "${relativePath || '.'}" contains ${childrenFiles.length} file(s) and ${childrenDirs.length} subdirectory(ies)` +
288
- (fileNames ? ` (e.g., ${fileNames})` : '') +
289
- (dirNames ? ` and subdirectories such as: ${dirNames}` : '') +
290
- `. It organizes source code and related artifacts for this area of the project.${extraKeywords}`
291
- }
292
-
293
- content += `## Description\n${description}\n\n`
294
- content += `## Summary\n${summary}\n\n`
295
-
296
- if (childrenDirs.length > 0) {
297
- content += '## Subdirectories\n'
298
- for (const item of childrenDirs) {
299
- content += `- ${item.name}: ${item.description}\n`
300
- }
301
- content += '\n'
302
- }
303
-
304
- if (childrenFiles.length > 0) {
305
- content += '## Files\n'
306
- for (const item of childrenFiles) {
307
- content += `- ${item.name}: ${item.description}\n`
308
- }
309
- content += '\n'
310
- }
311
-
312
- await fs.writeFile(outputPath, content, 'utf-8')
313
- } catch (e) {
314
- throw new AppError(ErrorCode.STORAGE_WRITE_FAILED, 'Failed to save directory analysis result', e)
315
- }
316
- }
317
-
318
- async getFileAnalysis(projectSlug: string, filePath: string, type: 'summary' | 'full' | 'diagram'): Promise<FileAnalysis | null> {
319
- try {
320
- const storageRoot = this.getStorageRoot()
321
- const mdPath = getFileOutputPath(storageRoot, filePath)
322
- if (!(await fs.pathExists(mdPath))) {
323
- return null
324
- }
325
- const markdown = await fs.readFile(mdPath, 'utf-8')
326
- const parsed = this.parseFileMarkdownToAnalysis(markdown, filePath)
327
- return parsed
328
- } catch {
329
- return null
330
- }
331
- }
332
-
333
- async getDirectoryAnalysis(projectSlug: string, dirPath: string, type: 'summary' | 'full' | 'diagram'): Promise<DirectoryAnalysis | null> {
334
- try {
335
- const storageRoot = this.getStorageRoot()
336
- const mdPath = getDirOutputPath(storageRoot, dirPath)
337
- if (!(await fs.pathExists(mdPath))) {
338
- return null
339
- }
340
- const markdown = await fs.readFile(mdPath, 'utf-8')
341
- const parsed = this.parseDirectoryMarkdownToAnalysis(markdown, dirPath)
342
- return parsed
343
- } catch {
344
- return null
345
- }
346
- }
347
-
348
- /**
349
- * 增量模式 meta-only:仅在已有 Markdown 结果上更新基础字段,不依赖内部 JSON。
350
- */
351
- async patchFileResultMarkdown(
352
- filePath: string,
353
- updates: Partial<Pick<FileAnalysis, 'fileGitCommitId' | 'isDirtyWhenAnalyzed' | 'fileHashWhenAnalyzed' | 'lastAnalyzedAt'>>,
354
- ): Promise<void> {
355
- const storageRoot = this.getStorageRoot()
356
- const outputPath = getFileOutputPath(storageRoot, filePath)
357
- if (!(await fs.pathExists(outputPath))) {
358
- return
359
- }
360
- await this.updateFileMarkdownBasicInfo(outputPath, updates)
361
- }
362
-
363
- /**
364
- * 判断当前存储目录下是否已经存在任意解析结果。
365
- * 规则:
366
- * - 在存储根目录下递归查找任意 .md 结果文件(如 index.md 或文件级解析结果)。
367
- */
368
- async hasAnyResult(projectSlug: string): Promise<boolean> {
369
- try {
370
- const storageRoot = this.getStorageRoot()
371
- if (!(await fs.pathExists(storageRoot))) {
372
- return false
373
- }
374
-
375
- const entries = await fs.readdir(storageRoot, { withFileTypes: true })
376
- const stack: string[] = []
377
- for (const entry of entries) {
378
- const full = path.join(storageRoot, entry.name)
379
- stack.push(full)
380
- }
381
-
382
- while (stack.length > 0) {
383
- const current = stack.pop() as string
384
- const stat = await fs.stat(current)
385
- if (stat.isDirectory()) {
386
- const children = await fs.readdir(current, { withFileTypes: true })
387
- for (const child of children) {
388
- stack.push(path.join(current, child.name))
389
- }
390
- } else if (stat.isFile() && current.toLowerCase().endsWith('.md')) {
391
- return true
392
- }
393
- }
394
-
395
- return false
396
- } catch {
397
- return false
398
- }
399
- }
400
-
401
- async getCheckpoint(projectSlug: string): Promise<AnalysisCheckpoint | null> {
402
- // TODO: 实现断点查询逻辑
403
- return null
404
- }
405
-
406
- async saveCheckpoint(projectSlug: string, checkpoint: AnalysisCheckpoint): Promise<void> {
407
- // TODO: 实现断点保存逻辑
408
- }
409
-
410
- getStoragePath(projectSlug: string): string {
411
- return this.getStorageRoot()
412
- }
413
- }
@@ -1,135 +0,0 @@
1
- import * as path from 'path'
2
- import { DirectoryAnalysis, FileAnalysis, LLMConfig, ModificationLog } from '../../common/types'
3
- import { OpenAIClient } from '../llm/openai.client'
4
- import { LLMUsageTracker } from '../llm/llm.usage.tracker'
5
- import { CodeSplitter } from '../splitter/code.splitter'
6
- import { FileHashCache } from '../cache/file.hash.cache'
7
- import { LLMAnalysisService } from '../../application/services/llm.analysis.service'
8
- import * as crypto from 'crypto'
9
-
10
- export type WorkerUsageDelta = {
11
- totalPromptTokens: number
12
- totalCompletionTokens: number
13
- totalTokens: number
14
- totalCalls: number
15
- }
16
-
17
- export async function parseFile(
18
- filePath: string,
19
- fileContent: string,
20
- fileHash: string,
21
- language?: string,
22
- llmConfig?: LLMConfig
23
- ): Promise<{ analysis: FileAnalysis; usage: WorkerUsageDelta }> {
24
- if (!llmConfig) {
25
- throw new Error('LLM config is required for file parsing')
26
- }
27
-
28
- // 初始化LLM相关服务
29
- const tracker = new LLMUsageTracker()
30
- const llmClient = new OpenAIClient(llmConfig, tracker)
31
- const fileSplitter = new CodeSplitter(llmClient)
32
- const cache = new FileHashCache({
33
- cacheDir: llmConfig.cache_dir,
34
- maxSizeMb: llmConfig.cache_max_size_mb,
35
- })
36
- const llmAnalysisService = new LLMAnalysisService(llmClient, fileSplitter, cache, llmConfig)
37
-
38
- const result = await llmAnalysisService.analyzeFile(filePath, fileContent, fileHash)
39
- return { analysis: result, usage: tracker.getStats() }
40
- }
41
-
42
- export async function aggregateDirectory(
43
- dirPath: string,
44
- payload: {
45
- childrenDirs: Array<{ name: string; summary: string; description?: string }>
46
- childrenFiles: Array<{ name: string; summary: string; description?: string }>
47
- },
48
- llmConfig?: LLMConfig
49
- ): Promise<{ description: string; summary: string; usage: WorkerUsageDelta }> {
50
- const name = path.basename(dirPath)
51
- const childrenDirsPayload = (payload?.childrenDirs ?? []).map(d => ({
52
- name: d.name,
53
- summary: d.summary,
54
- description: d.description ?? d.summary,
55
- }))
56
- const childrenFilesPayload = (payload?.childrenFiles ?? []).map(f => ({
57
- name: f.name,
58
- summary: f.summary,
59
- description: f.description ?? f.summary,
60
- }))
61
-
62
- // LLM 不可用时回退到可解释的聚合文案
63
- if (!llmConfig || !llmConfig.base_url || !llmConfig.model) {
64
- const fileCount = childrenFilesPayload.length
65
- const dirCount = childrenDirsPayload.length
66
- const fallback = `The "${name}" directory contains ${fileCount} file(s) and ${dirCount} subdirectory(ies) and helps organize related source code and modules.`
67
- return { description: fallback, summary: fallback, usage: { totalPromptTokens: 0, totalCompletionTokens: 0, totalTokens: 0, totalCalls: 0 } }
68
- }
69
-
70
- // 初始化LLM相关服务(worker 内做本任务 token 统计,返回给主线程聚合)
71
- const tracker = new LLMUsageTracker()
72
- const llmClient = new OpenAIClient(llmConfig, tracker)
73
- const fileSplitter = new CodeSplitter(llmClient)
74
- const cache = new FileHashCache({
75
- cacheDir: llmConfig.cache_dir,
76
- maxSizeMb: llmConfig.cache_max_size_mb,
77
- })
78
- const llmAnalysisService = new LLMAnalysisService(llmClient, fileSplitter, cache, llmConfig)
79
-
80
- try {
81
- const dirResultFromLLM = await llmAnalysisService.analyzeDirectory(childrenDirsPayload, childrenFilesPayload)
82
- return {
83
- description: dirResultFromLLM.description,
84
- summary: dirResultFromLLM.summary,
85
- usage: tracker.getStats(),
86
- }
87
- } catch {
88
- const fileCount = childrenFilesPayload.length
89
- const dirCount = childrenDirsPayload.length
90
- const fallback = `The "${name}" directory contains ${fileCount} file(s) and ${dirCount} subdirectory(ies) and helps organize related source code and modules.`
91
- return { description: fallback, summary: fallback, usage: tracker.getStats() }
92
- }
93
- }
94
-
95
- export async function validateResult(parentResult: DirectoryAnalysis, childResult: FileAnalysis | DirectoryAnalysis): Promise<{
96
- valid: boolean
97
- corrections?: Partial<FileAnalysis | DirectoryAnalysis>
98
- log?: ModificationLog
99
- }> {
100
- // 简单的校验逻辑:检查子项是否在父项的结构中存在
101
- const existsInStructure = parentResult.structure.some(item => item.name === childResult.name)
102
- const corrections: Partial<FileAnalysis | DirectoryAnalysis> = {}
103
- let log: ModificationLog | undefined
104
-
105
- if (!existsInStructure) {
106
- // 修正:将子项添加到父项的结构中
107
- ;(corrections as Partial<DirectoryAnalysis>).structure = [
108
- ...parentResult.structure,
109
- {
110
- name: childResult.name,
111
- type: childResult.type,
112
- description: childResult.summary.substring(0, 100),
113
- },
114
- ]
115
-
116
- log = {
117
- id: crypto.randomUUID(),
118
- timestamp: new Date().toISOString(),
119
- path: parentResult.path,
120
- type: 'omission',
121
- originalContent: JSON.stringify(parentResult.structure),
122
- correctedContent: JSON.stringify((corrections as Partial<DirectoryAnalysis>).structure),
123
- reason: `Child item ${childResult.name} missing from directory structure`,
124
- }
125
-
126
- return {
127
- valid: false,
128
- corrections,
129
- log,
130
- }
131
- }
132
-
133
- return { valid: true }
134
- }
135
-
@@ -1,9 +0,0 @@
1
- import * as workerpool from 'workerpool'
2
- import { aggregateDirectory, parseFile, validateResult } from './parse.worker.impl'
3
-
4
- // 注册Worker方法
5
- workerpool.worker({
6
- parseFile,
7
- aggregateDirectory,
8
- validateResult
9
- })