skill-any-code 1.0.0

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 (41) hide show
  1. package/README.md +48 -0
  2. package/dist/cli.js +319 -0
  3. package/dist/index.js +22 -0
  4. package/jest.config.js +27 -0
  5. package/package.json +59 -0
  6. package/src/adapters/command.schemas.ts +21 -0
  7. package/src/application/analysis.app.service.ts +272 -0
  8. package/src/application/bootstrap.ts +35 -0
  9. package/src/application/services/llm.analysis.service.ts +237 -0
  10. package/src/cli.ts +297 -0
  11. package/src/common/config.ts +209 -0
  12. package/src/common/constants.ts +8 -0
  13. package/src/common/errors.ts +34 -0
  14. package/src/common/logger.ts +82 -0
  15. package/src/common/types.ts +385 -0
  16. package/src/common/ui.ts +228 -0
  17. package/src/common/utils.ts +81 -0
  18. package/src/domain/index.ts +1 -0
  19. package/src/domain/interfaces.ts +188 -0
  20. package/src/domain/services/analysis.service.ts +735 -0
  21. package/src/domain/services/incremental.service.ts +50 -0
  22. package/src/index.ts +6 -0
  23. package/src/infrastructure/blacklist.service.ts +37 -0
  24. package/src/infrastructure/cache/file.hash.cache.ts +119 -0
  25. package/src/infrastructure/git/git.service.ts +120 -0
  26. package/src/infrastructure/git.service.ts +121 -0
  27. package/src/infrastructure/index.service.ts +94 -0
  28. package/src/infrastructure/llm/llm.usage.tracker.ts +65 -0
  29. package/src/infrastructure/llm/openai.client.ts +162 -0
  30. package/src/infrastructure/llm/prompt.template.ts +175 -0
  31. package/src/infrastructure/llm.service.ts +70 -0
  32. package/src/infrastructure/skill/skill.generator.ts +53 -0
  33. package/src/infrastructure/skill/templates/resolve.script.ts +97 -0
  34. package/src/infrastructure/skill/templates/skill.md.template.ts +45 -0
  35. package/src/infrastructure/splitter/code.splitter.ts +176 -0
  36. package/src/infrastructure/storage.service.ts +413 -0
  37. package/src/infrastructure/worker-pool/parse.worker.impl.ts +135 -0
  38. package/src/infrastructure/worker-pool/parse.worker.ts +9 -0
  39. package/src/infrastructure/worker-pool/worker-pool.service.ts +173 -0
  40. package/tsconfig.json +24 -0
  41. package/tsconfig.test.json +5 -0
@@ -0,0 +1,135 @@
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
+
@@ -0,0 +1,9 @@
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
+ })
@@ -0,0 +1,173 @@
1
+ import * as workerpool from 'workerpool'
2
+ import * as path from 'path'
3
+ import * as fs from 'fs'
4
+ import { IWorkerPoolService } from '../../domain/interfaces'
5
+ import { FileAnalysis, DirectoryAnalysis, ModificationLog, LLMConfig } from '../../common/types'
6
+ import { AppError, ErrorCode } from '../../common/errors'
7
+ import { DEFAULT_CONCURRENCY } from '../../common/constants'
8
+ import os from 'os'
9
+ import { aggregateDirectory, parseFile, validateResult } from './parse.worker.impl'
10
+
11
+ export interface DirectoryAggregationPayload {
12
+ childrenDirs: Array<{ name: string; summary: string; description?: string }>
13
+ childrenFiles: Array<{ name: string; summary: string; description?: string }>
14
+ }
15
+
16
+ export interface DirectoryAggregationLLMResult {
17
+ description: string
18
+ summary: string
19
+ }
20
+
21
+ type WorkerUsageDelta = {
22
+ totalPromptTokens: number
23
+ totalCompletionTokens: number
24
+ totalTokens: number
25
+ totalCalls: number
26
+ }
27
+
28
+ export class WorkerPoolService implements IWorkerPoolService {
29
+ private pool?: workerpool.Pool
30
+ private pendingTasks: Promise<any>[] = []
31
+ private currentConcurrency: number
32
+ private llmConfig: LLMConfig
33
+ private readonly localMode: boolean
34
+
35
+ constructor(llmConfig: LLMConfig, concurrency: number = DEFAULT_CONCURRENCY) {
36
+ this.currentConcurrency = concurrency
37
+ this.llmConfig = {
38
+ ...llmConfig,
39
+ cache_dir: WorkerPoolService.expandTilde(llmConfig.cache_dir),
40
+ }
41
+
42
+ const workerScript = path.join(__dirname, 'parse.worker.js')
43
+ this.localMode = !fs.existsSync(workerScript)
44
+
45
+ // Jest/ts-jest 直接执行 src 时,parse.worker.js 不存在。此时回退到“本进程执行”以保证核心逻辑可测。
46
+ // 生产构建(dist)与 CLI 运行时,parse.worker.js 存在,继续使用 worker threads 提升性能。
47
+ if (!this.localMode) {
48
+ this.pool = workerpool.pool(workerScript, {
49
+ maxWorkers: concurrency,
50
+ workerType: 'thread',
51
+ })
52
+ }
53
+ }
54
+
55
+ private static expandTilde(p: string): string {
56
+ if (!p) return p
57
+ if (p.startsWith('~') && (p.length === 1 || p[1] === '/' || p[1] === '\\')) {
58
+ return path.join(os.homedir(), p.slice(1))
59
+ }
60
+ return p
61
+ }
62
+
63
+ async submitFileAnalysisTask(
64
+ filePath: string,
65
+ fileContent: string,
66
+ fileHash: string,
67
+ language?: string
68
+ ): Promise<{ analysis: FileAnalysis; usage: WorkerUsageDelta }> {
69
+ if (this.localMode) {
70
+ const task = parseFile(filePath, fileContent, fileHash, language, this.llmConfig)
71
+ this.pendingTasks.push(task)
72
+ try {
73
+ return await task
74
+ } finally {
75
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
76
+ }
77
+ }
78
+ try {
79
+ const task = this.pool!.exec('parseFile', [filePath, fileContent, fileHash, language, this.llmConfig])
80
+ this.pendingTasks.push(task)
81
+
82
+ const result = await task
83
+ // 移除已完成的任务
84
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
85
+
86
+ return result
87
+ } catch (e) {
88
+ throw new AppError(ErrorCode.WORKER_SCHEDULE_FAILED, 'File analysis task failed', (e as Error).message)
89
+ }
90
+ }
91
+
92
+ async submitDirectoryAggregationTask(
93
+ dirPath: string,
94
+ payload: DirectoryAggregationPayload
95
+ ): Promise<DirectoryAggregationLLMResult & { usage: WorkerUsageDelta }> {
96
+ if (this.localMode) {
97
+ const task = aggregateDirectory(dirPath, payload, this.llmConfig)
98
+ this.pendingTasks.push(task)
99
+ try {
100
+ return await task
101
+ } finally {
102
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
103
+ }
104
+ }
105
+ try {
106
+ const task = this.pool!.exec('aggregateDirectory', [dirPath, payload, this.llmConfig])
107
+ this.pendingTasks.push(task)
108
+
109
+ const result = await task
110
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
111
+
112
+ return result
113
+ } catch (e) {
114
+ throw new AppError(ErrorCode.WORKER_SCHEDULE_FAILED, 'Directory aggregation task failed', (e as Error).message)
115
+ }
116
+ }
117
+
118
+ async submitValidationTask(parentResult: DirectoryAnalysis, childResult: FileAnalysis | DirectoryAnalysis): Promise<{
119
+ valid: boolean
120
+ corrections?: Partial<FileAnalysis | DirectoryAnalysis>
121
+ log?: ModificationLog
122
+ }> {
123
+ if (this.localMode) {
124
+ const task = validateResult(parentResult, childResult)
125
+ this.pendingTasks.push(task)
126
+ try {
127
+ return await task
128
+ } finally {
129
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
130
+ }
131
+ }
132
+ try {
133
+ const task = this.pool!.exec('validateResult', [parentResult, childResult])
134
+ this.pendingTasks.push(task)
135
+
136
+ const result = await task
137
+ this.pendingTasks = this.pendingTasks.filter(t => t !== task)
138
+
139
+ return result
140
+ } catch (e) {
141
+ throw new AppError(ErrorCode.WORKER_SCHEDULE_FAILED, 'Validation task failed', (e as Error).message)
142
+ }
143
+ }
144
+
145
+ setConcurrency(concurrency: number): void {
146
+ this.currentConcurrency = concurrency
147
+ if (this.localMode) {
148
+ return
149
+ }
150
+ // 立即终止旧 worker,避免遗留线程占用资源
151
+ void this.pool!.terminate(true).catch(() => {})
152
+ this.pool = workerpool.pool(path.join(__dirname, 'parse.worker.js'), {
153
+ maxWorkers: concurrency,
154
+ workerType: 'thread'
155
+ })
156
+ }
157
+
158
+ async waitAll(): Promise<void> {
159
+ await Promise.all(this.pendingTasks)
160
+ }
161
+
162
+ cancelAll(): void {
163
+ void this.pool?.terminate(true).catch(() => {})
164
+ this.pendingTasks = []
165
+ }
166
+
167
+ async terminate(force: boolean = true): Promise<void> {
168
+ if (this.pool) {
169
+ await this.pool.terminate(force)
170
+ }
171
+ this.pendingTasks = []
172
+ }
173
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "moduleResolution": "node",
14
+ "experimentalDecorators": true,
15
+ "emitDecoratorMetadata": true,
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "@/*": ["src/*"]
19
+ },
20
+ "types": ["node", "jest"]
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "coverage", "tests"]
24
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src/**/*", "tests/**/*"],
4
+ "exclude": ["node_modules", "dist", "coverage"]
5
+ }