repowiki-core 0.1.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.
- package/dist/analyzer/api-analyzer.d.ts +22 -0
- package/dist/analyzer/api-analyzer.d.ts.map +1 -0
- package/dist/analyzer/api-analyzer.js +272 -0
- package/dist/analyzer/api-analyzer.js.map +1 -0
- package/dist/analyzer/config-analyzer.d.ts +18 -0
- package/dist/analyzer/config-analyzer.d.ts.map +1 -0
- package/dist/analyzer/config-analyzer.js +200 -0
- package/dist/analyzer/config-analyzer.js.map +1 -0
- package/dist/analyzer/database-analyzer.d.ts +24 -0
- package/dist/analyzer/database-analyzer.d.ts.map +1 -0
- package/dist/analyzer/database-analyzer.js +391 -0
- package/dist/analyzer/database-analyzer.js.map +1 -0
- package/dist/analyzer/index.d.ts +10 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +10 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/module-analyzer.d.ts +20 -0
- package/dist/analyzer/module-analyzer.d.ts.map +1 -0
- package/dist/analyzer/module-analyzer.js +252 -0
- package/dist/analyzer/module-analyzer.js.map +1 -0
- package/dist/analyzer/workflow-analyzer.d.ts +19 -0
- package/dist/analyzer/workflow-analyzer.d.ts.map +1 -0
- package/dist/analyzer/workflow-analyzer.js +165 -0
- package/dist/analyzer/workflow-analyzer.js.map +1 -0
- package/dist/detector/dependency-detector.d.ts +50 -0
- package/dist/detector/dependency-detector.d.ts.map +1 -0
- package/dist/detector/dependency-detector.js +326 -0
- package/dist/detector/dependency-detector.js.map +1 -0
- package/dist/detector/entrypoint-detector.d.ts +30 -0
- package/dist/detector/entrypoint-detector.d.ts.map +1 -0
- package/dist/detector/entrypoint-detector.js +240 -0
- package/dist/detector/entrypoint-detector.js.map +1 -0
- package/dist/detector/index.d.ts +10 -0
- package/dist/detector/index.d.ts.map +1 -0
- package/dist/detector/index.js +10 -0
- package/dist/detector/index.js.map +1 -0
- package/dist/detector/tech-stack-detector.d.ts +41 -0
- package/dist/detector/tech-stack-detector.d.ts.map +1 -0
- package/dist/detector/tech-stack-detector.js +300 -0
- package/dist/detector/tech-stack-detector.js.map +1 -0
- package/dist/generator/index.d.ts +9 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +9 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/markdown-generator.d.ts +71 -0
- package/dist/generator/markdown-generator.d.ts.map +1 -0
- package/dist/generator/markdown-generator.js +235 -0
- package/dist/generator/markdown-generator.js.map +1 -0
- package/dist/generator/mermaid-generator.d.ts +30 -0
- package/dist/generator/mermaid-generator.d.ts.map +1 -0
- package/dist/generator/mermaid-generator.js +297 -0
- package/dist/generator/mermaid-generator.js.map +1 -0
- package/dist/generator/sidebar-generator.d.ts +10 -0
- package/dist/generator/sidebar-generator.d.ts.map +1 -0
- package/dist/generator/sidebar-generator.js +120 -0
- package/dist/generator/sidebar-generator.js.map +1 -0
- package/dist/generator/wiki-generator.d.ts +45 -0
- package/dist/generator/wiki-generator.d.ts.map +1 -0
- package/dist/generator/wiki-generator.js +217 -0
- package/dist/generator/wiki-generator.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/auth-manager.d.ts +50 -0
- package/dist/llm/auth-manager.d.ts.map +1 -0
- package/dist/llm/auth-manager.js +172 -0
- package/dist/llm/auth-manager.js.map +1 -0
- package/dist/llm/index.d.ts +10 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +9 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm-client.d.ts +132 -0
- package/dist/llm/llm-client.d.ts.map +1 -0
- package/dist/llm/llm-client.js +308 -0
- package/dist/llm/llm-client.js.map +1 -0
- package/dist/llm/prompt-manager.d.ts +67 -0
- package/dist/llm/prompt-manager.d.ts.map +1 -0
- package/dist/llm/prompt-manager.js +283 -0
- package/dist/llm/prompt-manager.js.map +1 -0
- package/dist/models/analysis-result.d.ts +425 -0
- package/dist/models/analysis-result.d.ts.map +1 -0
- package/dist/models/analysis-result.js +34 -0
- package/dist/models/analysis-result.js.map +1 -0
- package/dist/models/analysis-types.d.ts +223 -0
- package/dist/models/analysis-types.d.ts.map +1 -0
- package/dist/models/analysis-types.js +95 -0
- package/dist/models/analysis-types.js.map +1 -0
- package/dist/models/file-reference.d.ts +62 -0
- package/dist/models/file-reference.d.ts.map +1 -0
- package/dist/models/file-reference.js +34 -0
- package/dist/models/file-reference.js.map +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +10 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/project-profile.d.ts +48 -0
- package/dist/models/project-profile.d.ts.map +1 -0
- package/dist/models/project-profile.js +26 -0
- package/dist/models/project-profile.js.map +1 -0
- package/dist/models/wiki-page.d.ts +57 -0
- package/dist/models/wiki-page.d.ts.map +1 -0
- package/dist/models/wiki-page.js +19 -0
- package/dist/models/wiki-page.js.map +1 -0
- package/dist/pipeline.d.ts +30 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +159 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/scanner/file-scanner.d.ts +27 -0
- package/dist/scanner/file-scanner.d.ts.map +1 -0
- package/dist/scanner/file-scanner.js +149 -0
- package/dist/scanner/file-scanner.js.map +1 -0
- package/dist/scanner/ignore-rules.d.ts +31 -0
- package/dist/scanner/ignore-rules.d.ts.map +1 -0
- package/dist/scanner/ignore-rules.js +98 -0
- package/dist/scanner/ignore-rules.js.map +1 -0
- package/dist/scanner/index.d.ts +8 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +8 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/tree-builder.d.ts +20 -0
- package/dist/scanner/tree-builder.d.ts.map +1 -0
- package/dist/scanner/tree-builder.js +118 -0
- package/dist/scanner/tree-builder.js.map +1 -0
- package/package.json +34 -0
- package/src/analyzer/api-analyzer.ts +324 -0
- package/src/analyzer/config-analyzer.ts +209 -0
- package/src/analyzer/database-analyzer.ts +468 -0
- package/src/analyzer/index.ts +26 -0
- package/src/analyzer/module-analyzer.ts +308 -0
- package/src/analyzer/workflow-analyzer.ts +190 -0
- package/src/detector/dependency-detector.ts +390 -0
- package/src/detector/entrypoint-detector.ts +270 -0
- package/src/detector/index.ts +21 -0
- package/src/detector/tech-stack-detector.ts +377 -0
- package/src/generator/index.ts +36 -0
- package/src/generator/markdown-generator.ts +277 -0
- package/src/generator/mermaid-generator.ts +340 -0
- package/src/generator/sidebar-generator.ts +134 -0
- package/src/generator/wiki-generator.ts +281 -0
- package/src/index.ts +12 -0
- package/src/llm/auth-manager.ts +207 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/llm-client.ts +417 -0
- package/src/llm/prompt-manager.ts +325 -0
- package/src/models/analysis-result.ts +44 -0
- package/src/models/analysis-types.ts +121 -0
- package/src/models/file-reference.ts +41 -0
- package/src/models/index.ts +44 -0
- package/src/models/project-profile.ts +29 -0
- package/src/models/wiki-page.ts +23 -0
- package/src/pipeline.ts +225 -0
- package/src/scanner/file-scanner.ts +192 -0
- package/src/scanner/ignore-rules.ts +112 -0
- package/src/scanner/index.ts +19 -0
- package/src/scanner/tree-builder.ts +156 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块聚类分析器
|
|
3
|
+
* 将扫描到的文件按目录结构聚类为逻辑模块,并可选通过 LLM 生成模块描述。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import type { FileNode, ModuleInfo, CoreComponent } from '../models/index.js';
|
|
8
|
+
import type { LLMClient } from '../llm/index.js';
|
|
9
|
+
import { buildModuleAnalysisPrompt } from '../llm/index.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 目录 → 分类标签映射
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/** 目录名关键词到分类标签的映射规则 */
|
|
16
|
+
const CATEGORY_RULES: Array<{ keywords: string[]; category: string }> = [
|
|
17
|
+
{ keywords: ['agent'], category: 'agents' },
|
|
18
|
+
{ keywords: ['model', 'entity', 'schema'], category: 'models' },
|
|
19
|
+
{ keywords: ['route', 'router', 'controller', 'endpoint'], category: 'api' },
|
|
20
|
+
{ keywords: ['test', '__tests__', 'spec'], category: 'tests' },
|
|
21
|
+
{ keywords: ['component', 'components'], category: 'components' },
|
|
22
|
+
{ keywords: ['view', 'views', 'page', 'pages'], category: 'views' },
|
|
23
|
+
{ keywords: ['hook', 'hooks'], category: 'hooks' },
|
|
24
|
+
{ keywords: ['util', 'utils', 'helper', 'helpers', 'lib'], category: 'utilities' },
|
|
25
|
+
{ keywords: ['service', 'services'], category: 'services' },
|
|
26
|
+
{ keywords: ['middleware', 'middlewares'], category: 'middleware' },
|
|
27
|
+
{ keywords: ['config', 'configuration'], category: 'config' },
|
|
28
|
+
{ keywords: ['store', 'stores', 'state'], category: 'state' },
|
|
29
|
+
{ keywords: ['workflow', 'workflows', 'pipeline'], category: 'workflows' },
|
|
30
|
+
{ keywords: ['migration', 'migrations'], category: 'migrations' },
|
|
31
|
+
{ keywords: ['static', 'public', 'assets'], category: 'assets' },
|
|
32
|
+
{ keywords: ['type', 'types', 'interface', 'interfaces'], category: 'types' },
|
|
33
|
+
{ keywords: ['llm', 'ai', 'prompt', 'prompts'], category: 'ai' },
|
|
34
|
+
{ keywords: ['auth', 'authentication', 'authorization'], category: 'auth' },
|
|
35
|
+
{ keywords: ['doc', 'docs', 'documentation'], category: 'docs' },
|
|
36
|
+
{ keywords: ['script', 'scripts', 'tool', 'tools'], category: 'tooling' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 根据目录路径推断分类标签
|
|
41
|
+
* @param dirPath - 目录的相对路径
|
|
42
|
+
* @returns 匹配的分类标签,无匹配时返回 'general'
|
|
43
|
+
*/
|
|
44
|
+
function inferCategory(dirPath: string): string {
|
|
45
|
+
const normalizedPath = dirPath.replace(/\\/g, '/').toLowerCase();
|
|
46
|
+
const segments = normalizedPath.split('/');
|
|
47
|
+
|
|
48
|
+
for (const rule of CATEGORY_RULES) {
|
|
49
|
+
for (const segment of segments) {
|
|
50
|
+
if (rule.keywords.some((kw) => segment.includes(kw))) {
|
|
51
|
+
return rule.category;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return 'general';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 从目录路径推导模块名称
|
|
60
|
+
* 取路径中最后一级有意义的目录名,如 'apps/api' → 'api','src/components' → 'components'
|
|
61
|
+
*
|
|
62
|
+
* @param dirPath - 目录的相对路径
|
|
63
|
+
* @returns 模块名称
|
|
64
|
+
*/
|
|
65
|
+
function deriveModuleName(dirPath: string): string {
|
|
66
|
+
const segments = dirPath
|
|
67
|
+
.replace(/\\/g, '/')
|
|
68
|
+
.split('/')
|
|
69
|
+
.filter((s) => s.length > 0);
|
|
70
|
+
|
|
71
|
+
// 跳过仅作命名空间的顶级目录(如 src)
|
|
72
|
+
const skipSegments = new Set(['src', 'source', 'lib', 'app']);
|
|
73
|
+
const meaningfulSegments = segments.filter((s) => !skipSegments.has(s.toLowerCase()));
|
|
74
|
+
|
|
75
|
+
if (meaningfulSegments.length > 0) {
|
|
76
|
+
return meaningfulSegments[meaningfulSegments.length - 1];
|
|
77
|
+
}
|
|
78
|
+
return segments[segments.length - 1] || 'root';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 提取文件的分组键:取前两级目录路径
|
|
83
|
+
* 例如 'apps/api/routers/user.py' → 'apps/api'
|
|
84
|
+
* 'src/components/Header.tsx' → 'src/components'
|
|
85
|
+
* 'main.py' → '.'
|
|
86
|
+
*
|
|
87
|
+
* @param relativePath - 文件相对路径
|
|
88
|
+
* @returns 分组键
|
|
89
|
+
*/
|
|
90
|
+
function getGroupKey(relativePath: string): string {
|
|
91
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
92
|
+
const parts = normalized.split('/');
|
|
93
|
+
|
|
94
|
+
if (parts.length <= 1) {
|
|
95
|
+
// 根目录下的文件
|
|
96
|
+
return '.';
|
|
97
|
+
}
|
|
98
|
+
if (parts.length === 2) {
|
|
99
|
+
// 只有一级目录
|
|
100
|
+
return parts[0];
|
|
101
|
+
}
|
|
102
|
+
// 取前两级目录
|
|
103
|
+
return `${parts[0]}/${parts[1]}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 基于文件名和结构生成基本模块描述(无 LLM 时的回退逻辑)
|
|
108
|
+
*
|
|
109
|
+
* @param moduleName - 模块名称
|
|
110
|
+
* @param directory - 目录路径
|
|
111
|
+
* @param files - 文件列表
|
|
112
|
+
* @param category - 分类标签
|
|
113
|
+
* @returns 模块描述文本
|
|
114
|
+
*/
|
|
115
|
+
function generateBasicSummary(
|
|
116
|
+
moduleName: string,
|
|
117
|
+
directory: string,
|
|
118
|
+
files: string[],
|
|
119
|
+
category: string,
|
|
120
|
+
): string {
|
|
121
|
+
const extCounts = new Map<string, number>();
|
|
122
|
+
for (const f of files) {
|
|
123
|
+
const ext = path.extname(f).toLowerCase();
|
|
124
|
+
if (ext) {
|
|
125
|
+
extCounts.set(ext, (extCounts.get(ext) || 0) + 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const extSummary = Array.from(extCounts.entries())
|
|
130
|
+
.sort((a, b) => b[1] - a[1])
|
|
131
|
+
.map(([ext, count]) => `${ext}(${count})`)
|
|
132
|
+
.join(', ');
|
|
133
|
+
|
|
134
|
+
const categoryDescriptions: Record<string, string> = {
|
|
135
|
+
agents: 'Agent 智能体相关逻辑',
|
|
136
|
+
models: '数据模型定义',
|
|
137
|
+
api: 'API 路由与控制器',
|
|
138
|
+
tests: '测试代码',
|
|
139
|
+
components: 'UI 组件',
|
|
140
|
+
views: '视图/页面',
|
|
141
|
+
hooks: 'React Hooks',
|
|
142
|
+
utilities: '工具函数与辅助库',
|
|
143
|
+
services: '业务服务层',
|
|
144
|
+
middleware: '中间件',
|
|
145
|
+
config: '配置管理',
|
|
146
|
+
state: '状态管理',
|
|
147
|
+
workflows: '工作流/流水线编排',
|
|
148
|
+
migrations: '数据库迁移脚本',
|
|
149
|
+
assets: '静态资源',
|
|
150
|
+
types: '类型定义',
|
|
151
|
+
ai: 'AI/LLM 集成',
|
|
152
|
+
auth: '认证与授权',
|
|
153
|
+
docs: '文档',
|
|
154
|
+
tooling: '脚本与工具',
|
|
155
|
+
general: '通用模块',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const categoryDesc = categoryDescriptions[category] || '通用模块';
|
|
159
|
+
return `${moduleName} 模块位于 ${directory},包含 ${files.length} 个文件(${extSummary}),主要负责${categoryDesc}。`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 基于文件列表推断核心组件(无 LLM 时的回退逻辑)
|
|
164
|
+
*
|
|
165
|
+
* @param files - 文件列表
|
|
166
|
+
* @returns 推断的核心组件列表
|
|
167
|
+
*/
|
|
168
|
+
function inferCoreComponents(files: string[]): CoreComponent[] {
|
|
169
|
+
const components: CoreComponent[] = [];
|
|
170
|
+
const seen = new Set<string>();
|
|
171
|
+
|
|
172
|
+
for (const filePath of files) {
|
|
173
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
174
|
+
|
|
175
|
+
// 跳过 index 文件和常见工具文件
|
|
176
|
+
if (['index', 'types', 'constants', 'utils'].includes(basename.toLowerCase())) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 跳过测试文件
|
|
181
|
+
if (/\.(test|spec|e2e)$/i.test(basename)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (seen.has(basename)) continue;
|
|
186
|
+
seen.add(basename);
|
|
187
|
+
|
|
188
|
+
components.push({
|
|
189
|
+
name: basename,
|
|
190
|
+
description: `定义于 ${filePath}`,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// 最多保留 10 个核心组件
|
|
194
|
+
if (components.length >= 10) break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return components;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* LLM 模块分析响应类型
|
|
202
|
+
*/
|
|
203
|
+
interface ModuleAnalysisResponse {
|
|
204
|
+
summary: string;
|
|
205
|
+
core_components: Array<{ name: string; description: string }>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 将文件聚类为逻辑模块并生成描述
|
|
210
|
+
*
|
|
211
|
+
* 将扫描到的全部文件按顶级和次级目录分组,每个分组视为一个逻辑模块。
|
|
212
|
+
* 若提供 LLM 客户端,则通过 LLM 为每个模块生成专业描述;
|
|
213
|
+
* 否则基于文件结构启发式推断。
|
|
214
|
+
*
|
|
215
|
+
* @param rootPath - 项目根目录的绝对路径
|
|
216
|
+
* @param files - 扫描到的 FileNode 列表
|
|
217
|
+
* @param llmClient - 可选的 LLM 客户端实例
|
|
218
|
+
* @returns 分析得到的 ModuleInfo 数组
|
|
219
|
+
*/
|
|
220
|
+
export async function analyzeModules(
|
|
221
|
+
rootPath: string,
|
|
222
|
+
files: FileNode[],
|
|
223
|
+
llmClient?: LLMClient | null,
|
|
224
|
+
): Promise<ModuleInfo[]> {
|
|
225
|
+
// ------------------------------------------------------------------
|
|
226
|
+
// 第一步:按目录分组
|
|
227
|
+
// ------------------------------------------------------------------
|
|
228
|
+
const groups = new Map<string, string[]>();
|
|
229
|
+
|
|
230
|
+
for (const file of files) {
|
|
231
|
+
if (file.nodeType !== 'file') continue;
|
|
232
|
+
|
|
233
|
+
const key = getGroupKey(file.relativePath);
|
|
234
|
+
if (!groups.has(key)) {
|
|
235
|
+
groups.set(key, []);
|
|
236
|
+
}
|
|
237
|
+
groups.get(key)!.push(file.relativePath);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ------------------------------------------------------------------
|
|
241
|
+
// 第二步:为每个分组构建 ModuleInfo
|
|
242
|
+
// ------------------------------------------------------------------
|
|
243
|
+
const modules: ModuleInfo[] = [];
|
|
244
|
+
|
|
245
|
+
for (const [directory, moduleFiles] of groups) {
|
|
246
|
+
const moduleName = deriveModuleName(directory);
|
|
247
|
+
const category = inferCategory(directory);
|
|
248
|
+
|
|
249
|
+
const moduleInfo: ModuleInfo = {
|
|
250
|
+
moduleName,
|
|
251
|
+
directory,
|
|
252
|
+
files: moduleFiles.sort(),
|
|
253
|
+
summary: '',
|
|
254
|
+
category,
|
|
255
|
+
coreComponents: [],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
modules.push(moduleInfo);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ------------------------------------------------------------------
|
|
262
|
+
// 第三步:生成描述(LLM 或回退)
|
|
263
|
+
// ------------------------------------------------------------------
|
|
264
|
+
if (llmClient) {
|
|
265
|
+
// 使用 LLM 逐模块生成描述,串行调用以避免速率限制
|
|
266
|
+
for (const mod of modules) {
|
|
267
|
+
try {
|
|
268
|
+
const prompt = buildModuleAnalysisPrompt(mod.directory, mod.files.join('\n'), '');
|
|
269
|
+
const result = await llmClient.chatJSON<ModuleAnalysisResponse>(prompt);
|
|
270
|
+
|
|
271
|
+
if (result && typeof result.summary === 'string') {
|
|
272
|
+
mod.summary = result.summary;
|
|
273
|
+
}
|
|
274
|
+
if (result && Array.isArray(result.core_components)) {
|
|
275
|
+
mod.coreComponents = result.core_components.map((c) => ({
|
|
276
|
+
name: String(c.name || ''),
|
|
277
|
+
description: String(c.description || ''),
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
// LLM 调用失败时使用回退逻辑
|
|
282
|
+
mod.summary = generateBasicSummary(
|
|
283
|
+
mod.moduleName,
|
|
284
|
+
mod.directory,
|
|
285
|
+
mod.files,
|
|
286
|
+
mod.category,
|
|
287
|
+
);
|
|
288
|
+
mod.coreComponents = inferCoreComponents(mod.files);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
// 无 LLM,使用基于结构的启发式描述
|
|
293
|
+
for (const mod of modules) {
|
|
294
|
+
mod.summary = generateBasicSummary(
|
|
295
|
+
mod.moduleName,
|
|
296
|
+
mod.directory,
|
|
297
|
+
mod.files,
|
|
298
|
+
mod.category,
|
|
299
|
+
);
|
|
300
|
+
mod.coreComponents = inferCoreComponents(mod.files);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 按目录路径排序输出
|
|
305
|
+
modules.sort((a, b) => a.directory.localeCompare(b.directory));
|
|
306
|
+
|
|
307
|
+
return modules;
|
|
308
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import type { FileNode } from '../models/index.js';
|
|
4
|
+
|
|
5
|
+
export interface WorkflowInfo {
|
|
6
|
+
name: string;
|
|
7
|
+
type: 'langchain' | 'langgraph' | 'custom-agent' | 'celery' | 'temporal' | 'custom-workflow';
|
|
8
|
+
filePath: string;
|
|
9
|
+
description: string;
|
|
10
|
+
steps: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 根据导入和内容模式识别工作流/智能体类型
|
|
15
|
+
*/
|
|
16
|
+
function detectWorkflowType(content: string, relativePath: string): 'langchain' | 'langgraph' | 'custom-agent' | 'celery' | 'temporal' | 'custom-workflow' | null {
|
|
17
|
+
const lowerContent = content.toLowerCase();
|
|
18
|
+
const normalizedPath = relativePath.replace(/\\/g, '/').toLowerCase();
|
|
19
|
+
|
|
20
|
+
if (lowerContent.includes('langgraph') || lowerContent.includes('stategraph')) {
|
|
21
|
+
return 'langgraph';
|
|
22
|
+
}
|
|
23
|
+
if (lowerContent.includes('langchain') || lowerContent.includes('agentexecutor') || lowerContent.includes('create_react_agent')) {
|
|
24
|
+
return 'langchain';
|
|
25
|
+
}
|
|
26
|
+
if (lowerContent.includes('celery') || lowerContent.includes('@shared_task') || lowerContent.includes('@app.task')) {
|
|
27
|
+
return 'celery';
|
|
28
|
+
}
|
|
29
|
+
if (lowerContent.includes('temporalio') || lowerContent.includes('@workflow.defn') || lowerContent.includes('workflow.run')) {
|
|
30
|
+
return 'temporal';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 启发式路径规则
|
|
34
|
+
if (normalizedPath.includes('/agent/') || normalizedPath.includes('/agents/') || normalizedPath.includes('agent.py') || normalizedPath.includes('agent.ts')) {
|
|
35
|
+
return 'custom-agent';
|
|
36
|
+
}
|
|
37
|
+
if (normalizedPath.includes('/workflow/') || normalizedPath.includes('/workflows/') || normalizedPath.includes('/pipeline/') || normalizedPath.includes('/pipelines/')) {
|
|
38
|
+
return 'custom-workflow';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 提取工作流步骤或节点
|
|
46
|
+
*/
|
|
47
|
+
function extractSteps(content: string, type: string): string[] {
|
|
48
|
+
const steps: string[] = [];
|
|
49
|
+
|
|
50
|
+
switch (type) {
|
|
51
|
+
case 'langgraph': {
|
|
52
|
+
// 匹配 .add_node("name", ...) 或 .add_node('name', ...)
|
|
53
|
+
const nodePattern = /\.add_node\(\s*['"]([^'"]+)['"]/g;
|
|
54
|
+
let match: RegExpExecArray | null;
|
|
55
|
+
while ((match = nodePattern.exec(content)) !== null) {
|
|
56
|
+
steps.push(match[1]);
|
|
57
|
+
}
|
|
58
|
+
// 也可能匹配 .add_edge("nodeA", "nodeB")
|
|
59
|
+
const edgePattern = /\.add_edge\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
|
|
60
|
+
while ((match = edgePattern.exec(content)) !== null) {
|
|
61
|
+
const edgeStr = `${match[1]} -> ${match[2]}`;
|
|
62
|
+
if (!steps.includes(edgeStr)) {
|
|
63
|
+
steps.push(edgeStr);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'langchain': {
|
|
69
|
+
// 匹配 Tool 定义或 Agent 步骤
|
|
70
|
+
const toolPattern = /@tool\b|Tool\.from_function|class\s+(\w+Tool)\b/g;
|
|
71
|
+
let match: RegExpExecArray | null;
|
|
72
|
+
while ((match = toolPattern.exec(content)) !== null) {
|
|
73
|
+
steps.push(match[1] || 'Registered Tool');
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'celery': {
|
|
78
|
+
// 匹配任务函数
|
|
79
|
+
const taskPattern = /@(?:app|shared)_task\s*(?:\([^)]*\))?\s*\n\s*(?:async\s+)?def\s+(\w+)/g;
|
|
80
|
+
let match: RegExpExecArray | null;
|
|
81
|
+
while ((match = taskPattern.exec(content)) !== null) {
|
|
82
|
+
steps.push(match[1]);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'temporal': {
|
|
87
|
+
// 匹配活动或工作流定义
|
|
88
|
+
const temporalPattern = /@workflow\.run\b\s*\n\s*(?:async\s+)?def\s+(\w+)|@activity\.defn\b\s*\n\s*(?:async\s+)?def\s+(\w+)/g;
|
|
89
|
+
let match: RegExpExecArray | null;
|
|
90
|
+
while ((match = temporalPattern.exec(content)) !== null) {
|
|
91
|
+
steps.push(match[1] || match[2]);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'custom-agent':
|
|
96
|
+
case 'custom-workflow': {
|
|
97
|
+
// 匹配类中的主要方法名,如 run, execute, process, invoke, step
|
|
98
|
+
const methodPattern = /(?:async\s+)?def\s+(run|execute|process|invoke|step|start|next_step)\s*\(/g;
|
|
99
|
+
let match: RegExpExecArray | null;
|
|
100
|
+
while ((match = methodPattern.exec(content)) !== null) {
|
|
101
|
+
steps.push(match[1]);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Array.from(new Set(steps));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 猜测工作流名称
|
|
112
|
+
*/
|
|
113
|
+
function deriveWorkflowName(content: string, filePath: string): string {
|
|
114
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
115
|
+
|
|
116
|
+
// 尝试寻找类名定义,如 class Agent, class WritingWorkflow
|
|
117
|
+
const classMatch = /class\s+(\w+(?:Agent|Workflow|Graph|Pipeline|Chain))\b/.exec(content);
|
|
118
|
+
if (classMatch) {
|
|
119
|
+
return classMatch[1];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return basename.replace(/^./, (c) => c.toUpperCase());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 生成工作流简要描述
|
|
127
|
+
*/
|
|
128
|
+
function generateDescription(name: string, type: string, steps: string[]): string {
|
|
129
|
+
const typeNames: Record<string, string> = {
|
|
130
|
+
langgraph: 'LangGraph 状态图智能体工作流',
|
|
131
|
+
langchain: 'LangChain 链式/智能体代理执行器',
|
|
132
|
+
celery: 'Celery 分布式异步任务队列',
|
|
133
|
+
temporal: 'Temporal 强一致性持久工作流',
|
|
134
|
+
'custom-agent': '自定义 Agent 智能体逻辑',
|
|
135
|
+
'custom-workflow': '自定义业务流程/工作流',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const typeDesc = typeNames[type] || '业务工作流';
|
|
139
|
+
if (steps.length > 0) {
|
|
140
|
+
return `${name} 是一个基于 ${typeDesc},定义了包含 [${steps.join(', ')}] 等关键节点或步骤。`;
|
|
141
|
+
}
|
|
142
|
+
return `${name} 定义了项目中的 ${typeDesc} 流程。`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 分析项目中的智能体(Agent)和工作流(Workflow)设计模式
|
|
147
|
+
*
|
|
148
|
+
* 识别 LangGraph, LangChain, Celery, Temporal 以及自定义 Agent 结构,
|
|
149
|
+
* 提取工作流节点与流转步骤。
|
|
150
|
+
*
|
|
151
|
+
* @param rootPath - 项目根目录
|
|
152
|
+
* @param files - 扫描的文件节点
|
|
153
|
+
*/
|
|
154
|
+
export async function analyzeWorkflows(rootPath: string, files: FileNode[]): Promise<WorkflowInfo[]> {
|
|
155
|
+
const workflows: WorkflowInfo[] = [];
|
|
156
|
+
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
if (file.nodeType !== 'file') continue;
|
|
159
|
+
|
|
160
|
+
// 仅对文本源文件分析
|
|
161
|
+
const ext = path.extname(file.relativePath).toLowerCase();
|
|
162
|
+
if (!['.py', '.ts', '.js', '.tsx', '.jsx'].includes(ext)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const absolutePath = path.resolve(rootPath, file.relativePath);
|
|
168
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
169
|
+
|
|
170
|
+
const type = detectWorkflowType(content, file.relativePath);
|
|
171
|
+
if (!type) continue;
|
|
172
|
+
|
|
173
|
+
const name = deriveWorkflowName(content, file.relativePath);
|
|
174
|
+
const steps = extractSteps(content, type);
|
|
175
|
+
const description = generateDescription(name, type, steps);
|
|
176
|
+
|
|
177
|
+
workflows.push({
|
|
178
|
+
name,
|
|
179
|
+
type,
|
|
180
|
+
filePath: file.relativePath,
|
|
181
|
+
description,
|
|
182
|
+
steps,
|
|
183
|
+
});
|
|
184
|
+
} catch {
|
|
185
|
+
// 忽略读取错误
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return workflows.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
190
|
+
}
|