@didnhdj/fnmap 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/README.md +307 -0
- package/README_CN.md +308 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# fnmap
|
|
2
|
+
|
|
3
|
+
> AI code indexing tool for analyzing JS/TS code structure and generating structured code maps
|
|
4
|
+
|
|
5
|
+
[中文文档](./README_CN.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Fast Analysis**: Quickly analyze JavaScript/TypeScript code structure using AST
|
|
10
|
+
- 📊 **Structured Output**: Generate `.fnmap` index files with imports, functions, classes, and constants
|
|
11
|
+
- 🔗 **Call Graph**: Track function call relationships and dependencies
|
|
12
|
+
- 📈 **Mermaid Diagrams**: Generate visual call graphs in Mermaid format
|
|
13
|
+
- 🎯 **Git Integration**: Process only changed files for efficient workflows
|
|
14
|
+
- ⚙️ **Flexible Configuration**: Support for multiple configuration methods
|
|
15
|
+
- 🔌 **Pre-commit Hook**: Integrate seamlessly with git hooks
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g fnmap
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or use in your project:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install --save-dev fnmap
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Initialize Configuration
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
fnmap --init
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This creates a `.fnmaprc` configuration file in your project root.
|
|
38
|
+
|
|
39
|
+
### Basic Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Process files based on configuration
|
|
43
|
+
fnmap
|
|
44
|
+
|
|
45
|
+
# Process specific directory
|
|
46
|
+
fnmap --dir src
|
|
47
|
+
|
|
48
|
+
# Process specific files
|
|
49
|
+
fnmap --files index.js,utils.js
|
|
50
|
+
|
|
51
|
+
# Process git changed files
|
|
52
|
+
fnmap --changed
|
|
53
|
+
|
|
54
|
+
# Process git staged files (for pre-commit hook)
|
|
55
|
+
fnmap --staged -q
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Generate Call Graphs
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Generate file-level Mermaid diagrams
|
|
62
|
+
fnmap --mermaid file --dir src
|
|
63
|
+
|
|
64
|
+
# Generate project-level Mermaid diagram
|
|
65
|
+
fnmap --mermaid project
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
fnmap supports multiple configuration methods (in priority order):
|
|
71
|
+
|
|
72
|
+
1. `.fnmaprc` - JSON configuration file
|
|
73
|
+
2. `.fnmaprc.json` - JSON configuration file
|
|
74
|
+
3. `package.json#fnmap` - fnmap field in package.json
|
|
75
|
+
|
|
76
|
+
### Configuration Example
|
|
77
|
+
|
|
78
|
+
**.fnmaprc**
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"enable": true,
|
|
82
|
+
"include": [
|
|
83
|
+
"src/**/*.js",
|
|
84
|
+
"src/**/*.ts",
|
|
85
|
+
"src/**/*.jsx",
|
|
86
|
+
"src/**/*.tsx"
|
|
87
|
+
],
|
|
88
|
+
"exclude": [
|
|
89
|
+
"node_modules",
|
|
90
|
+
"dist",
|
|
91
|
+
"build"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**package.json**
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"fnmap": {
|
|
100
|
+
"enable": true,
|
|
101
|
+
"include": ["src/**/*.js", "src/**/*.ts"],
|
|
102
|
+
"exclude": ["dist"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Output Files
|
|
108
|
+
|
|
109
|
+
### .fnmap Index File
|
|
110
|
+
|
|
111
|
+
The `.fnmap` file contains structured information about your code:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
@FNMAP src/
|
|
115
|
+
#utils.js Utility functions
|
|
116
|
+
<fs:readFileSync,writeFileSync
|
|
117
|
+
<path:join,resolve
|
|
118
|
+
readConfig(filePath) 10-25 Read configuration file
|
|
119
|
+
parseData(data) 27-40 Parse data →JSON.parse
|
|
120
|
+
saveFile(path,content) 42-50 Save file →fs.writeFileSync,path.join
|
|
121
|
+
@FNMAP
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Format Description:**
|
|
125
|
+
- `#filename` - File name and description
|
|
126
|
+
- `<module:members` - Imported modules and members
|
|
127
|
+
- `functionName(params) startLine-endLine description →calls` - Function information with call graph
|
|
128
|
+
- `ClassName:SuperClass startLine-endLine` - Class information
|
|
129
|
+
- ` .methodName(params) line description →calls` - Instance method
|
|
130
|
+
- ` +methodName(params) line description →calls` - Static method
|
|
131
|
+
- `CONSTANT_NAME line description` - Constant definition
|
|
132
|
+
|
|
133
|
+
### Mermaid Call Graph
|
|
134
|
+
|
|
135
|
+
When using `--mermaid` option, generates visual call graphs:
|
|
136
|
+
|
|
137
|
+
**File-level** (`filename.mermaid`):
|
|
138
|
+
```mermaid
|
|
139
|
+
flowchart TD
|
|
140
|
+
subgraph utils["utils"]
|
|
141
|
+
readConfig["readConfig"]
|
|
142
|
+
parseData["parseData"]
|
|
143
|
+
saveFile["saveFile"]
|
|
144
|
+
end
|
|
145
|
+
readConfig --> parseData
|
|
146
|
+
saveFile --> parseData
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Project-level** (`.fnmap.mermaid`):
|
|
150
|
+
Shows call relationships across all files in the project.
|
|
151
|
+
|
|
152
|
+
## CLI Options
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Usage: fnmap [options] [files...]
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
-v, --version Show version number
|
|
159
|
+
-f, --files <files> Process specific files (comma-separated)
|
|
160
|
+
-d, --dir <dir> Process all code files in directory
|
|
161
|
+
-p, --project <dir> Specify project root directory (default: current directory)
|
|
162
|
+
-c, --changed Process git changed files (staged + modified + untracked)
|
|
163
|
+
-s, --staged Process git staged files (for pre-commit hook)
|
|
164
|
+
-m, --mermaid [mode] Generate Mermaid call graph (file=file-level, project=project-level)
|
|
165
|
+
-q, --quiet Quiet mode (suppress output)
|
|
166
|
+
--init Create default configuration file .fnmaprc
|
|
167
|
+
-h, --help Display help information
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Use Cases
|
|
171
|
+
|
|
172
|
+
### 1. Pre-commit Hook
|
|
173
|
+
|
|
174
|
+
Add to `.husky/pre-commit` or `.git/hooks/pre-commit`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
#!/bin/sh
|
|
178
|
+
fnmap --staged -q
|
|
179
|
+
git add .fnmap
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
This automatically updates the `.fnmap` index when committing code.
|
|
183
|
+
|
|
184
|
+
### 2. CI/CD Integration
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
# .github/workflows/ci.yml
|
|
188
|
+
- name: Generate Code Index
|
|
189
|
+
run: |
|
|
190
|
+
npm install -g fnmap
|
|
191
|
+
fnmap --dir src
|
|
192
|
+
git diff --exit-code .fnmap || echo "Code index updated"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. Code Review
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Generate index for changed files
|
|
199
|
+
fnmap --changed
|
|
200
|
+
|
|
201
|
+
# Generate call graph for review
|
|
202
|
+
fnmap --mermaid file --changed
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 4. Documentation Generation
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Generate project-level call graph
|
|
209
|
+
fnmap --mermaid project
|
|
210
|
+
|
|
211
|
+
# Use the .fnmap.mermaid file in your documentation
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Supported File Types
|
|
215
|
+
|
|
216
|
+
- `.js` - JavaScript
|
|
217
|
+
- `.ts` - TypeScript
|
|
218
|
+
- `.jsx` - React JSX
|
|
219
|
+
- `.tsx` - React TypeScript
|
|
220
|
+
- `.mjs` - ES Modules
|
|
221
|
+
|
|
222
|
+
## Limitations
|
|
223
|
+
|
|
224
|
+
To ensure performance and safety, fnmap has the following default limits:
|
|
225
|
+
- **File Size**: Maximum 10MB per file
|
|
226
|
+
- **Directory Depth**: Maximum recursion depth of 50 levels
|
|
227
|
+
|
|
228
|
+
## How It Works
|
|
229
|
+
|
|
230
|
+
1. **AST Parsing**: Uses `@babel/parser` to parse code into Abstract Syntax Tree
|
|
231
|
+
2. **Structure Analysis**: Traverses AST to extract imports, functions, classes, constants
|
|
232
|
+
3. **Call Graph**: Tracks function call relationships and dependencies
|
|
233
|
+
4. **Index Generation**: Generates compact `.fnmap` files with structured information
|
|
234
|
+
5. **Visualization**: Optionally generates Mermaid diagrams for visual representation
|
|
235
|
+
|
|
236
|
+
## Examples
|
|
237
|
+
|
|
238
|
+
### Example 1: Analyze Single File
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
fnmap --files src/utils.js
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Output:
|
|
245
|
+
```
|
|
246
|
+
==================================================
|
|
247
|
+
fnmap - AI Code Indexing Tool
|
|
248
|
+
==================================================
|
|
249
|
+
|
|
250
|
+
Analyzing: src/utils.js
|
|
251
|
+
✓ Imports: 3, Functions: 5, Classes: 0, Constants: 2
|
|
252
|
+
|
|
253
|
+
Generating .fnmap index...
|
|
254
|
+
✓ src/.fnmap
|
|
255
|
+
|
|
256
|
+
==================================================
|
|
257
|
+
Complete! Analyzed: 1, Failed: 0
|
|
258
|
+
==================================================
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Example 2: Analyze Directory with Call Graph
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
fnmap --dir src --mermaid file
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Generates:
|
|
268
|
+
- `src/.fnmap` - Code index
|
|
269
|
+
- `src/utils.mermaid` - Call graph for utils.js
|
|
270
|
+
- `src/parser.mermaid` - Call graph for parser.js
|
|
271
|
+
- etc.
|
|
272
|
+
|
|
273
|
+
### Example 3: Git Workflow
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Make changes to code
|
|
277
|
+
git add .
|
|
278
|
+
|
|
279
|
+
# Generate index for staged files
|
|
280
|
+
fnmap --staged -q
|
|
281
|
+
|
|
282
|
+
# Add updated index
|
|
283
|
+
git add .fnmap
|
|
284
|
+
|
|
285
|
+
# Commit
|
|
286
|
+
git commit -m "Update feature"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Contributing
|
|
290
|
+
|
|
291
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT
|
|
296
|
+
|
|
297
|
+
## Related Projects
|
|
298
|
+
|
|
299
|
+
- [@babel/parser](https://babeljs.io/docs/en/babel-parser) - JavaScript parser
|
|
300
|
+
- [@babel/traverse](https://babeljs.io/docs/en/babel-traverse) - AST traversal
|
|
301
|
+
- [Mermaid](https://mermaid-js.github.io/) - Diagram generation
|
|
302
|
+
|
|
303
|
+
## Support
|
|
304
|
+
|
|
305
|
+
- 🐛 [Report Issues](https://github.com/gqfx/fnmap/issues)
|
|
306
|
+
- 💡 [Feature Requests](https://github.com/gqfx/fnmap/issues)
|
|
307
|
+
- 📖 [Documentation](https://github.com/gqfx/fnmap)
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# fnmap
|
|
2
|
+
|
|
3
|
+
> AI 代码索引生成工具,分析 JS/TS 代码结构,生成结构化代码映射
|
|
4
|
+
|
|
5
|
+
[English Documentation](./README.md)
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- 🚀 **快速分析**:使用 AST 快速分析 JavaScript/TypeScript 代码结构
|
|
10
|
+
- 📊 **结构化输出**:生成包含导入、函数、类、常量的 `.fnmap` 索引文件
|
|
11
|
+
- 🔗 **调用图谱**:追踪函数调用关系和依赖
|
|
12
|
+
- 📈 **Mermaid 图表**:生成 Mermaid 格式的可视化调用图
|
|
13
|
+
- 🎯 **Git 集成**:只处理改动的文件,提高工作效率
|
|
14
|
+
- ⚙️ **灵活配置**:支持多种配置方式
|
|
15
|
+
- 🔌 **Pre-commit Hook**:无缝集成 git hooks
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g fnmap
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
或在项目中使用:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install --save-dev fnmap
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
### 初始化配置
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
fnmap --init
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
这会在项目根目录创建 `.fnmaprc` 配置文件。
|
|
38
|
+
|
|
39
|
+
### 基本用法
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 根据配置文件处理
|
|
43
|
+
fnmap
|
|
44
|
+
|
|
45
|
+
# 处理指定目录
|
|
46
|
+
fnmap --dir src
|
|
47
|
+
|
|
48
|
+
# 处理指定文件
|
|
49
|
+
fnmap --files index.js,utils.js
|
|
50
|
+
|
|
51
|
+
# 处理 git 改动的文件
|
|
52
|
+
fnmap --changed
|
|
53
|
+
|
|
54
|
+
# 处理 git staged 文件(用于 pre-commit hook)
|
|
55
|
+
fnmap --staged -q
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 生成调用图
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 生成文件级 Mermaid 图表
|
|
62
|
+
fnmap --mermaid file --dir src
|
|
63
|
+
|
|
64
|
+
# 生成项目级 Mermaid 图表
|
|
65
|
+
fnmap --mermaid project
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 配置
|
|
69
|
+
|
|
70
|
+
fnmap 支持多种配置方式(按优先级排序):
|
|
71
|
+
|
|
72
|
+
1. `.fnmaprc` - JSON 配置文件
|
|
73
|
+
2. `.fnmaprc.json` - JSON 配置文件
|
|
74
|
+
3. `package.json#fnmap` - package.json 中的 fnmap 字段
|
|
75
|
+
|
|
76
|
+
### 配置示例
|
|
77
|
+
|
|
78
|
+
**.fnmaprc**
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"enable": true,
|
|
82
|
+
"include": [
|
|
83
|
+
"src/**/*.js",
|
|
84
|
+
"src/**/*.ts",
|
|
85
|
+
"src/**/*.jsx",
|
|
86
|
+
"src/**/*.tsx"
|
|
87
|
+
],
|
|
88
|
+
"exclude": [
|
|
89
|
+
"node_modules",
|
|
90
|
+
"dist",
|
|
91
|
+
"build"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**package.json**
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"fnmap": {
|
|
100
|
+
"enable": true,
|
|
101
|
+
"include": ["src/**/*.js", "src/**/*.ts"],
|
|
102
|
+
"exclude": ["dist"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 输出文件
|
|
108
|
+
|
|
109
|
+
### .fnmap 索引文件
|
|
110
|
+
|
|
111
|
+
`.fnmap` 文件包含代码的结构化信息:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
@FNMAP src/
|
|
115
|
+
#utils.js 工具函数
|
|
116
|
+
<fs:readFileSync,writeFileSync
|
|
117
|
+
<path:join,resolve
|
|
118
|
+
readConfig(filePath) 10-25 读取配置文件
|
|
119
|
+
parseData(data) 27-40 解析数据 →JSON.parse
|
|
120
|
+
saveFile(path,content) 42-50 保存文件 →fs.writeFileSync,path.join
|
|
121
|
+
@FNMAP
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**格式说明:**
|
|
125
|
+
- `#filename` - 文件名和描述
|
|
126
|
+
- `<module:members` - 导入的模块和成员
|
|
127
|
+
- `functionName(params) startLine-endLine description →calls` - 函数信息及调用图
|
|
128
|
+
- `ClassName:SuperClass startLine-endLine` - 类信息
|
|
129
|
+
- ` .methodName(params) line description →calls` - 实例方法
|
|
130
|
+
- ` +methodName(params) line description →calls` - 静态方法
|
|
131
|
+
- `CONSTANT_NAME line description` - 常量定义
|
|
132
|
+
|
|
133
|
+
### Mermaid 调用图
|
|
134
|
+
|
|
135
|
+
使用 `--mermaid` 选项时,生成可视化调用图:
|
|
136
|
+
|
|
137
|
+
**文件级** (`filename.mermaid`):
|
|
138
|
+
```mermaid
|
|
139
|
+
flowchart TD
|
|
140
|
+
subgraph utils["utils"]
|
|
141
|
+
readConfig["readConfig"]
|
|
142
|
+
parseData["parseData"]
|
|
143
|
+
saveFile["saveFile"]
|
|
144
|
+
end
|
|
145
|
+
readConfig --> parseData
|
|
146
|
+
saveFile --> parseData
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**项目级** (`.fnmap.mermaid`):
|
|
150
|
+
显示项目中所有文件的调用关系。
|
|
151
|
+
|
|
152
|
+
## CLI 选项
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
用法: fnmap [options] [files...]
|
|
156
|
+
|
|
157
|
+
选项:
|
|
158
|
+
-v, --version 显示版本号
|
|
159
|
+
-f, --files <files> 处理指定文件(逗号分隔)
|
|
160
|
+
-d, --dir <dir> 处理目录下所有代码文件
|
|
161
|
+
-p, --project <dir> 指定项目根目录(默认:当前目录)
|
|
162
|
+
-c, --changed 处理 git 改动的文件(staged + modified + untracked)
|
|
163
|
+
-s, --staged 处理 git staged 文件(用于 pre-commit hook)
|
|
164
|
+
-m, --mermaid [mode] 生成 Mermaid 调用图(file=文件级,project=项目级)
|
|
165
|
+
-q, --quiet 静默模式(不输出信息)
|
|
166
|
+
--init 创建默认配置文件 .fnmaprc
|
|
167
|
+
-h, --help 显示帮助信息
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 使用场景
|
|
171
|
+
|
|
172
|
+
### 1. Pre-commit Hook
|
|
173
|
+
|
|
174
|
+
添加到 `.husky/pre-commit` 或 `.git/hooks/pre-commit`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
#!/bin/sh
|
|
178
|
+
fnmap --staged -q
|
|
179
|
+
git add .fnmap
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
这样在提交代码时会自动更新 `.fnmap` 索引。
|
|
183
|
+
|
|
184
|
+
### 2. CI/CD 集成
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
# .github/workflows/ci.yml
|
|
188
|
+
- name: Generate Code Index
|
|
189
|
+
run: |
|
|
190
|
+
npm install -g fnmap
|
|
191
|
+
fnmap --dir src
|
|
192
|
+
git diff --exit-code .fnmap || echo "Code index updated"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. 代码审查
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 为改动的文件生成索引
|
|
199
|
+
fnmap --changed
|
|
200
|
+
|
|
201
|
+
# 生成调用图用于审查
|
|
202
|
+
fnmap --mermaid file --changed
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 4. 文档生成
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# 生成项目级调用图
|
|
209
|
+
fnmap --mermaid project
|
|
210
|
+
|
|
211
|
+
# 在文档中使用 .fnmap.mermaid 文件
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## 支持的文件类型
|
|
215
|
+
|
|
216
|
+
- `.js` - JavaScript
|
|
217
|
+
- `.ts` - TypeScript
|
|
218
|
+
- `.jsx` - React JSX
|
|
219
|
+
- `.tsx` - React TypeScript
|
|
220
|
+
- `.mjs` - ES Modules
|
|
221
|
+
|
|
222
|
+
## 限制
|
|
223
|
+
|
|
224
|
+
为了保证性能和安全,fnmap 有以下默认限制:
|
|
225
|
+
- **文件大小**:单个文件最大支持 10MB
|
|
226
|
+
- **目录深度**:最大递归深度为 50 层
|
|
227
|
+
- **超时**:目前没有硬性超时限制,但处理超大文件可能会较慢
|
|
228
|
+
|
|
229
|
+
## 工作原理
|
|
230
|
+
|
|
231
|
+
1. **AST 解析**:使用 `@babel/parser` 将代码解析为抽象语法树
|
|
232
|
+
2. **结构分析**:遍历 AST 提取导入、函数、类、常量
|
|
233
|
+
3. **调用图谱**:追踪函数调用关系和依赖
|
|
234
|
+
4. **索引生成**:生成紧凑的 `.fnmap` 文件,包含结构化信息
|
|
235
|
+
5. **可视化**:可选生成 Mermaid 图表进行可视化展示
|
|
236
|
+
|
|
237
|
+
## 示例
|
|
238
|
+
|
|
239
|
+
### 示例 1:分析单个文件
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
fnmap --files src/utils.js
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
输出:
|
|
246
|
+
```
|
|
247
|
+
==================================================
|
|
248
|
+
fnmap - AI 代码索引生成工具
|
|
249
|
+
==================================================
|
|
250
|
+
|
|
251
|
+
Analyzing: src/utils.js
|
|
252
|
+
✓ Imports: 3, Functions: 5, Classes: 0, Constants: 2
|
|
253
|
+
|
|
254
|
+
Generating .fnmap index...
|
|
255
|
+
✓ src/.fnmap
|
|
256
|
+
|
|
257
|
+
==================================================
|
|
258
|
+
Complete! Analyzed: 1, Failed: 0
|
|
259
|
+
==================================================
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 示例 2:分析目录并生成调用图
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
fnmap --dir src --mermaid file
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
生成:
|
|
269
|
+
- `src/.fnmap` - 代码索引
|
|
270
|
+
- `src/utils.mermaid` - utils.js 的调用图
|
|
271
|
+
- `src/parser.mermaid` - parser.js 的调用图
|
|
272
|
+
- 等等
|
|
273
|
+
|
|
274
|
+
### 示例 3:Git 工作流
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# 修改代码
|
|
278
|
+
git add .
|
|
279
|
+
|
|
280
|
+
# 为 staged 文件生成索引
|
|
281
|
+
fnmap --staged -q
|
|
282
|
+
|
|
283
|
+
# 添加更新的索引
|
|
284
|
+
git add .fnmap
|
|
285
|
+
|
|
286
|
+
# 提交
|
|
287
|
+
git commit -m "Update feature"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 贡献
|
|
291
|
+
|
|
292
|
+
欢迎贡献!请随时提交 Pull Request。
|
|
293
|
+
|
|
294
|
+
## 许可证
|
|
295
|
+
|
|
296
|
+
MIT
|
|
297
|
+
|
|
298
|
+
## 相关项目
|
|
299
|
+
|
|
300
|
+
- [@babel/parser](https://babeljs.io/docs/en/babel-parser) - JavaScript 解析器
|
|
301
|
+
- [@babel/traverse](https://babeljs.io/docs/en/babel-traverse) - AST 遍历
|
|
302
|
+
- [Mermaid](https://mermaid-js.github.io/) - 图表生成
|
|
303
|
+
|
|
304
|
+
## 支持
|
|
305
|
+
|
|
306
|
+
- 🐛 [报告问题](https://github.com/gqfx/fnmap/issues)
|
|
307
|
+
- 💡 [功能请求](https://github.com/gqfx/fnmap/issues)
|
|
308
|
+
- 📖 [文档](https://github.com/gqfx/fnmap)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("fs"),I=require("path"),Re=require("commander"),U=require("child_process"),Ie=require("@babel/parser"),B=require("@babel/traverse"),O={FILE_NOT_FOUND:"FILE_NOT_FOUND",FILE_READ_ERROR:"FILE_READ_ERROR",PARSE_ERROR:"PARSE_ERROR",CONFIG_ERROR:"CONFIG_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",PERMISSION_ERROR:"PERMISSION_ERROR",FILE_TOO_LARGE:"FILE_TOO_LARGE"};function oe(e){return"parseError"in e}function Se(e){return e.success===!0}function Fe(e){return e.success===!1}function _e(e){return e.valid===!0}function ve(e){return e.valid===!1}const C={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",gray:"\x1B[90m",bold:"\x1B[1m"},W=10*1024*1024,X=50,Z=[".js",".ts",".jsx",".tsx",".mjs"],K=["node_modules",".git","dist","build",".next","coverage","__pycache__",".cache"],H={enable:!0,include:["**/*.js","**/*.ts","**/*.jsx","**/*.tsx","**/*.mjs"],exclude:[]};function ie(e){if(!e||typeof e!="string")return{valid:!1,error:"File path is required and must be a string / 文件路径必须是字符串",errorType:O.VALIDATION_ERROR};if(!v.existsSync(e))return{valid:!1,error:`File not found: ${e} / 文件不存在: ${e}`,errorType:O.FILE_NOT_FOUND};try{const a=v.statSync(e);if(!a.isFile())return{valid:!1,error:`Path is not a file: ${e} / 路径不是文件: ${e}`,errorType:O.VALIDATION_ERROR};if(a.size>W)return{valid:!1,error:`File too large (${(a.size/1024/1024).toFixed(2)}MB > ${W/1024/1024}MB): ${e} / 文件过大`,errorType:O.FILE_TOO_LARGE}}catch(a){return{valid:!1,error:`Cannot access file: ${e}. Reason: ${a.message} / 无法访问文件`,errorType:O.PERMISSION_ERROR}}return{valid:!0}}function Q(e){if(!e||typeof e!="object")return{valid:!1,error:"Config must be an object / 配置必须是对象"};const a=e;return a.enable!==void 0&&typeof a.enable!="boolean"?{valid:!1,error:"Config.enable must be a boolean / enable 字段必须是布尔值"}:a.include!==void 0&&!Array.isArray(a.include)?{valid:!1,error:"Config.include must be an array / include 字段必须是数组"}:a.exclude!==void 0&&!Array.isArray(a.exclude)?{valid:!1,error:"Config.exclude must be an array / exclude 字段必须是数组"}:{valid:!0}}function q(e,a,o={}){const t=[a];return o.file&&t.push(`File: ${o.file}`),o.line!==void 0&&o.column!==void 0&&t.push(`Location: Line ${o.line}, Column ${o.column}`),o.suggestion&&t.push(`Suggestion: ${o.suggestion}`),t.join(`
|
|
3
|
+
`)}let j=!1,G=null;const E={error:e=>{j||console.error(`${C.red}✗${C.reset} ${e}`)},success:e=>{j||console.log(`${C.green}✓${C.reset} ${e}`)},info:e=>{j||console.log(e)},warn:e=>{j||console.warn(`${C.yellow}!${C.reset} ${e}`)},title:e=>{j||console.log(`${C.bold}${e}${C.reset}`)}};function ae(e){j=e}function Ce(){return j}function ce(){try{return require("../../package.json").version}catch{return"0.1.0"}}function Y(){return G=new Re.Command,G.name("fnmap").description("AI code indexing tool - Analyzes JS/TS code structure and generates structured code maps").version(ce(),"-v, --version","Show version number").option("-f, --files <files>","Process specified files (comma-separated)",e=>e.split(",").map(a=>a.trim()).filter(Boolean)).option("-d, --dir <dir>","Process all code files in directory").option("-p, --project <dir>","Specify project root directory",process.env.CLAUDE_PROJECT_DIR??process.cwd()).option("-c, --changed","Process only git changed files (staged + modified + untracked)").option("-s, --staged","Process only git staged files (for pre-commit hook)").option("-m, --mermaid [mode]","Generate Mermaid call graph (file=file-level, project=project-level)").option("-q, --quiet","Quiet mode").option("--init","Create default config file .fnmaprc").argument("[files...]","Directly specify file paths").allowUnknownOption(!1).addHelpText("after",`
|
|
4
|
+
Configuration files (by priority):
|
|
5
|
+
.fnmaprc JSON config file
|
|
6
|
+
.fnmaprc.json JSON config file
|
|
7
|
+
package.json#fnmap fnmap field in package.json
|
|
8
|
+
|
|
9
|
+
Output:
|
|
10
|
+
.fnmap Code index file (imports, functions, classes, constants, call graph)
|
|
11
|
+
*.mermaid Mermaid call graph (when using --mermaid file)
|
|
12
|
+
.fnmap.mermaid Project-level Mermaid call graph (when using --mermaid project)
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
$ fnmap --dir src Process src directory
|
|
16
|
+
$ fnmap --files a.js,b.js Process specified files
|
|
17
|
+
$ fnmap --changed Process git changed files
|
|
18
|
+
$ fnmap --staged -q For pre-commit hook usage
|
|
19
|
+
$ fnmap --mermaid file --dir src Generate file-level call graphs
|
|
20
|
+
$ fnmap --mermaid project Generate project-level call graph
|
|
21
|
+
$ fnmap --init Create config file
|
|
22
|
+
`),G}function P(){return G||Y()}const Oe={get opts(){return P().opts.bind(P())},get args(){return P().args},get parse(){return P().parse.bind(P())}};function le(e){const a=[".fnmaprc",".fnmaprc.json"];for(const t of a){const R=I.join(e,t);if(v.existsSync(R))try{const r=v.readFileSync(R,"utf-8");if(!r.trim()){E.warn(`Config file is empty: ${t}. Using default config / 配置文件为空,使用默认配置`);continue}let p;try{p=JSON.parse(r)}catch(u){const c=u,s=q(O.CONFIG_ERROR,`Failed to parse config file: ${t} / 配置文件解析失败`,{file:R,line:c.lineNumber,column:c.columnNumber,suggestion:"Check JSON syntax, ensure proper quotes and commas / 检查 JSON 语法,确保引号和逗号正确"});E.warn(s);continue}const l=Q(p);if(!l.valid){E.warn(`Invalid config in ${t}: ${l.error}`);continue}return{config:p,source:t}}catch(r){const p=r,l=q(O.FILE_READ_ERROR,`Failed to read config file: ${t} / 配置文件读取失败`,{file:R,suggestion:p.message});E.warn(l)}}const o=I.join(e,"package.json");if(v.existsSync(o))try{const t=JSON.parse(v.readFileSync(o,"utf-8"));if(t.fnmap){const R=Q(t.fnmap);if(!R.valid)E.warn(`Invalid fnmap config in package.json: ${R.error}`);else return{config:t.fnmap,source:"package.json#fnmap"}}}catch{}return{config:null,source:null}}function fe(e){return e?{...H,...e,exclude:[...e.exclude??[]]}:H}function de(e,a=!1){const o=[];try{let t;if(a)t=U.execSync("git diff --cached --name-only --diff-filter=ACMR",{cwd:e,encoding:"utf-8"});else{const p=U.execSync("git diff --cached --name-only --diff-filter=ACMR",{cwd:e,encoding:"utf-8"}),l=U.execSync("git diff --name-only --diff-filter=ACMR",{cwd:e,encoding:"utf-8"}),u=U.execSync("git ls-files --others --exclude-standard",{cwd:e,encoding:"utf-8"});t=`${p}
|
|
23
|
+
${l}
|
|
24
|
+
${u}`}const R=t.split(`
|
|
25
|
+
`).map(p=>p.trim()).filter(Boolean).filter(p=>{const l=I.extname(p);return Z.includes(l)}),r=[...new Set(R)];for(const p of r){const l=I.resolve(e,p);v.existsSync(l)&&o.push(l)}}catch{return[]}return o}function z(e,a=e,o=K,t=0,R=new Set){const r=[];if(!v.existsSync(e))return E.warn(`Directory does not exist: ${e} / 目录不存在`),r;if(t>X)return E.warn(`Max directory depth (${X}) exceeded: ${e} / 超过最大目录深度`),r;let p;try{p=v.realpathSync(e)}catch(u){const c=u;return E.warn(`Cannot resolve real path: ${e}. Reason: ${c.message} / 无法解析真实路径`),r}if(R.has(p))return E.warn(`Circular reference detected, skipping: ${e} / 检测到循环引用`),r;R.add(p);let l;try{l=v.readdirSync(e,{withFileTypes:!0})}catch(u){const c=u;return c.code==="EACCES"||c.code==="EPERM"?E.warn(`Permission denied: ${e} / 权限不足`):E.warn(`Failed to read directory: ${e}. Reason: ${c.message} / 读取目录失败`),r}for(const u of l)try{const c=I.join(e,u.name);if(u.isDirectory())o.includes(u.name)||r.push(...z(c,a,o,t+1,R));else if(u.isFile()){const s=I.extname(u.name);Z.includes(s)&&r.push(I.relative(a,c))}}catch(c){const s=c;E.warn(`Error processing entry: ${u.name}. Reason: ${s.message} / 处理条目出错`)}return r}function k(e){if(!e)return"";const o=e.value.split(`
|
|
26
|
+
`).map(t=>t.replace(/^\s*\*\s?/,"").trim());for(const t of o)if(t.startsWith("@description "))return t.slice(13).trim().slice(0,60);for(const t of o)if(t&&!t.startsWith("@")&&!t.startsWith("/"))return t.slice(0,60);return""}const J=typeof B=="function"?B:B.default;function pe(e,a){var d,F;if(e==null)return{parseError:"Code content is null or undefined / 代码内容为空",errorType:O.VALIDATION_ERROR};if(typeof e!="string")return{parseError:"Code must be a string / 代码必须是字符串类型",errorType:O.VALIDATION_ERROR};if(!e.trim())return{description:"",imports:[],functions:[],classes:[],constants:[],callGraph:{}};const o={description:"",imports:[],functions:[],classes:[],constants:[],callGraph:{}},t=e.match(/^\/\*\*[\s\S]*?\*\//);if(t){const n=t[0].split(`
|
|
27
|
+
`).map(i=>i.replace(/^\s*\*\s?/,"").trim()).filter(i=>i&&!i.startsWith("/")&&!i.startsWith("@ai"));for(const i of n)if(i.startsWith("@description ")){o.description=i.slice(13).trim();break}if(!o.description&&n.length>0){const i=n.find(g=>!g.startsWith("@"));i&&(o.description=i)}}let R;try{const f=a&&(a.endsWith(".ts")||a.endsWith(".tsx"));R=Ie.parse(e,{sourceType:"unambiguous",plugins:["jsx","classPrivateProperties","classPrivateMethods",...f?["typescript"]:[]]})}catch(f){const n=f;return{parseError:q(O.PARSE_ERROR,`Syntax error: ${n.message} / 语法错误`,{file:a??void 0,line:(d=n.loc)==null?void 0:d.line,column:(F=n.loc)==null?void 0:F.column,suggestion:"Check syntax errors in the file / 检查文件中的语法错误"}),loc:n.loc,errorType:O.PARSE_ERROR}}const r=new Map,p=new Map,l=new Map;function u(f){var i,g,S;let n=f;for(;n;){if(n.node.type==="FunctionDeclaration"){const h=n.node;if(h.id)return h.id.name}if(n.node.type==="ClassMethod"){const h=n.node,$=(i=n.parentPath)==null?void 0:i.parentPath,y=$==null?void 0:$.node,_=((g=y==null?void 0:y.id)==null?void 0:g.name)??"",b=((S=h.key)==null?void 0:S.name)??"";return _?`${_}.${b}`:b}if(n.node.type==="ArrowFunctionExpression"||n.node.type==="FunctionExpression"){const h=n.parent;if((h==null?void 0:h.type)==="VariableDeclarator"){const y=h.id;if(y!=null&&y.name)return y.name}}n=n.parentPath}return null}J(R,{VariableDeclarator(f){var i,g,S,h;const n=f.node;if(((i=n.init)==null?void 0:i.type)==="CallExpression"&&((g=n.init.callee)==null?void 0:g.type)==="Identifier"&&n.init.callee.name==="require"&&((h=(S=n.init.arguments)==null?void 0:S[0])==null?void 0:h.type)==="StringLiteral"){const $=n.init.arguments[0].value;if(r.has($)||r.set($,new Set),n.id.type==="Identifier"){const y=n.id.name;r.get($).add(y),p.set(y,$),l.set(y,new Set)}else if(n.id.type==="ObjectPattern"){for(const y of n.id.properties)if(y.type==="ObjectProperty"&&y.key.type==="Identifier"){const _=y.value.type==="Identifier"?y.value.name:y.key.name;r.get($).add(y.key.name),p.set(_,$),l.set(_,new Set)}}}},CallExpression(f){var i,g,S,h,$,y;const n=f.node;if(((i=n.callee)==null?void 0:i.type)==="Identifier"&&n.callee.name==="require"&&((S=(g=n.arguments)==null?void 0:g[0])==null?void 0:S.type)==="StringLiteral"){const _=f.parent;if((_==null?void 0:_.type)==="MemberExpression"&&((h=_.property)==null?void 0:h.type)==="Identifier"){const b=n.arguments[0].value;r.has(b)||r.set(b,new Set),r.get(b).add(_.property.name);const A=($=f.parentPath)==null?void 0:$.parent;if((A==null?void 0:A.type)==="VariableDeclarator"){const N=A;((y=N.id)==null?void 0:y.type)==="Identifier"&&(p.set(N.id.name,b),l.set(N.id.name,new Set))}}}},ImportDeclaration(f){const n=f.node,i=n.source.value;r.has(i)||r.set(i,new Set);for(const g of n.specifiers){let S,h;if(g.type==="ImportDefaultSpecifier")S="default",h=g.local.name;else if(g.type==="ImportNamespaceSpecifier")S="*",h=g.local.name;else if(g.type==="ImportSpecifier"){const $=g.imported;S=$.type==="Identifier"?$.name:$.value,h=g.local.name}S&&h&&(r.get(i).add(S),p.set(h,i),l.set(h,new Set))}}}),J(R,{Identifier(f){const n=f.node.name;if(l.has(n)){const i=f.parent;if((i==null?void 0:i.type)==="VariableDeclarator"&&i.id===f.node||(i==null?void 0:i.type)==="ImportSpecifier"||(i==null?void 0:i.type)==="ImportDefaultSpecifier")return;const g=u(f);g&&l.get(n).add(g)}},FunctionDeclaration(f){var _,b,A,N,w;const n=f.node,i=((_=n.id)==null?void 0:_.name)??"[anonymous]",g=n.params.map(x=>{var M,T;return x.type==="Identifier"?x.name:x.type==="AssignmentPattern"&&((M=x.left)==null?void 0:M.type)==="Identifier"?x.left.name+"?":x.type==="RestElement"&&((T=x.argument)==null?void 0:T.type)==="Identifier"?"..."+x.argument.name:"?"}),S=((A=(b=n.loc)==null?void 0:b.start)==null?void 0:A.line)??0,h=((w=(N=n.loc)==null?void 0:N.end)==null?void 0:w.line)??0;let $="";const y=n.leadingComments;y&&y.length>0&&($=k(y[y.length-1])),o.functions.push({name:i,params:g.join(","),startLine:S,endLine:h,description:$})},ClassDeclaration(f){var b,A,N,w,x,M,T,ee,ne,te;const n=f.node,i=((b=n.id)==null?void 0:b.name)??"[anonymous]",g=((N=(A=n.loc)==null?void 0:A.start)==null?void 0:N.line)??0,S=((x=(w=n.loc)==null?void 0:w.end)==null?void 0:x.line)??0,h=((M=n.superClass)==null?void 0:M.type)==="Identifier"?n.superClass.name:null;let $="";const y=n.leadingComments;y&&y.length>0&&($=k(y[y.length-1]));const _=[];if((T=n.body)!=null&&T.body){for(const L of n.body.body)if(L.type==="ClassMethod"){const ye=((ee=L.key)==null?void 0:ee.type)==="Identifier"?L.key.name:"[computed]",$e=L.params.map(D=>{var re;return D.type==="Identifier"?D.name:D.type==="AssignmentPattern"&&((re=D.left)==null?void 0:re.type)==="Identifier"?D.left.name+"?":"?"}),Ee=((te=(ne=L.loc)==null?void 0:ne.start)==null?void 0:te.line)??0;let se="";const V=L.leadingComments;V&&V.length>0&&(se=k(V[V.length-1])),_.push({name:ye,params:$e.join(","),line:Ee,static:L.static,kind:L.kind,description:se})}}o.classes.push({name:i,superClass:h,startLine:g,endLine:S,methods:_,description:$})},VariableDeclaration(f){var i,g,S;if(f.parent.type!=="Program")return;const n=f.node;if(n.kind==="const"){let h="";const $=n.leadingComments;$&&$.length>0&&(h=k($[$.length-1]));for(const y of n.declarations){const _=((i=y.id)==null?void 0:i.type)==="Identifier"?y.id.name:void 0;if(_&&_===_.toUpperCase()&&_.length>2){const b=((S=(g=n.loc)==null?void 0:g.start)==null?void 0:S.line)??0;o.constants.push({name:_,line:b,description:h})}}}}});for(const[f,n]of r){const i=new Set;for(const g of p.keys())if(p.get(g)===f&&l.has(g))for(const S of l.get(g))i.add(S);o.imports.push({module:f,members:Array.from(n),usedIn:Array.from(i)})}const c=new Set;for(const f of o.functions)c.add(f.name);for(const f of o.classes)for(const n of f.methods)c.add(n.name),c.add(`${f.name}.${n.name}`);const s=new Set(p.keys()),m=new Map;J(R,{CallExpression(f){var g,S;const n=f.node;let i=null;if(n.callee.type==="Identifier")i=n.callee.name;else if(n.callee.type==="MemberExpression"&&((g=n.callee.property)==null?void 0:g.type)==="Identifier"){const h=((S=n.callee.object)==null?void 0:S.type)==="Identifier"?n.callee.object.name:void 0,$=n.callee.property.name;h&&s.has(h)?i=`${h}.${$}`:i=$}if(i){const h=u(f);if(h&&h!==i){const $=c.has(i),y=s.has(i)||i.includes(".")&&s.has(i.split(".")[0]);($||y)&&(m.has(h)||m.set(h,new Set),m.get(h).add(i))}}}}),o.callGraph={};for(const[f,n]of m)o.callGraph[f]=Array.from(n);return o}function be(e,a){var R;const o=[];let t=`/*@AI ${a}`;e.description&&(t+=` - ${e.description.slice(0,50)}`),o.push(t);for(const r of e.imports){const p=r.members.join(",");let l=`<${r.module}:${p}`;((R=r.usedIn)==null?void 0:R.length)>0&&(l+=` ->${r.usedIn.join(",")}`),o.push(l)}for(const r of e.classes){let p=r.name;r.superClass&&(p+=`:${r.superClass}`),p+=` ${r.startLine}-${r.endLine}`,r.description&&(p+=` ${r.description}`),o.push(p);for(const l of r.methods){const u=l.static?" +":" .",c=l.kind==="get"?"get:":l.kind==="set"?"set:":"";let s=`${u}${c}${l.name}(${l.params}) ${l.line}`;l.description&&(s+=` ${l.description}`),o.push(s)}}for(const r of e.functions){let p=`${r.name}(${r.params}) ${r.startLine}-${r.endLine}`;r.description&&(p+=` ${r.description}`),o.push(p)}for(const r of e.constants){let p=`${r.name} ${r.line}`;r.description&&(p+=` ${r.description}`),o.push(p)}return o.push("@AI*/"),o.join(`
|
|
28
|
+
`)}function xe(e){let a=e;return a=a.replace(/\/\*@AI[\s\S]*?@AI\*\/\s*/g,""),a=a.replace(/\/\*\*[\s\S]*?@ai-context-end[\s\S]*?\*\/\s*/g,""),a=a.replace(/^\/\*\*[\s\S]*?\*\/\s*\n?/,""),a}function ue(e,a){var t,R,r;const o=[`@FNMAP ${I.basename(e)}/`];for(const{relativePath:p,info:l}of a){let c=`#${I.basename(p)}`;l.description&&(c+=` ${l.description.slice(0,50)}`),o.push(c);for(const s of l.imports){const m=s.members.join(",");o.push(` <${s.module}:${m}`)}for(const s of l.classes){let m=` ${s.name}`;s.superClass&&(m+=`:${s.superClass}`),m+=` ${s.startLine}-${s.endLine}`,s.description&&(m+=` ${s.description}`),o.push(m);for(const d of s.methods){const F=d.static?" +":" .",f=d.kind==="get"?"get:":d.kind==="set"?"set:":"";let n=`${F}${f}${d.name}(${d.params}) ${d.line}`;d.description&&(n+=` ${d.description}`);const i=`${s.name}.${d.name}`,g=((t=l.callGraph)==null?void 0:t[i])??((R=l.callGraph)==null?void 0:R[d.name]);g&&g.length>0&&(n+=` →${g.join(",")}`),o.push(n)}}for(const s of l.functions){let m=` ${s.name}(${s.params}) ${s.startLine}-${s.endLine}`;s.description&&(m+=` ${s.description}`);const d=(r=l.callGraph)==null?void 0:r[s.name];d&&d.length>0&&(m+=` →${d.join(",")}`),o.push(m)}for(const s of l.constants){let m=` ${s.name} ${s.line}`;s.description&&(m+=` ${s.description}`),o.push(m)}}return o.push("@FNMAP"),o.join(`
|
|
29
|
+
`)}function me(e,a){const o=["flowchart TD"],t=s=>"id_"+s.replace(/[^a-zA-Z0-9]/g,m=>`_${m.charCodeAt(0)}_`),R=s=>s.replace(/"/g,"#quot;"),r=a.functions.map(s=>s.name),p=[];for(const s of a.classes)for(const m of s.methods)p.push(`${s.name}.${m.name}`);const l=[...r,...p];if(l.length===0)return null;const u=I.basename(e,I.extname(e));o.push(` subgraph ${t(u)}["${u}"]`);for(const s of l)o.push(` ${t(s)}["${R(s)}"]`);o.push(" end");const c=a.callGraph??{};for(const[s,m]of Object.entries(c))for(const d of m)if(l.includes(d)||d.includes(".")){const F=l.includes(d)?d:d.split(".").pop();(l.includes(d)||l.some(f=>f.endsWith(F)))&&o.push(` ${t(s)} --> ${t(d)}`)}return o.join(`
|
|
30
|
+
`)}function ge(e,a){const o=["flowchart TD"],t=u=>"id_"+u.replace(/[^a-zA-Z0-9]/g,c=>`_${c.charCodeAt(0)}_`),R=u=>u.replace(/"/g,"#quot;"),r=new Map,p=[];for(const{relativePath:u,info:c}of a){const s=I.basename(u,I.extname(u)),m=c.functions.map(n=>n.name),d=[];for(const n of c.classes)for(const i of n.methods)d.push(`${n.name}.${i.name}`);const F=[...m,...d];r.set(u,{fileName:s,functions:F});const f=c.callGraph??{};for(const[n,i]of Object.entries(f))for(const g of i)p.push({file:u,fileName:s,caller:n,callee:g})}for(const[,{fileName:u,functions:c}]of r)if(c.length!==0){o.push(` subgraph ${t(u)}["${R(u)}"]`);for(const s of c)o.push(` ${t(u)}_${t(s)}["${R(s)}"]`);o.push(" end")}const l=new Set;for(const{fileName:u,caller:c,callee:s}of p){const m=`${t(u)}_${t(c)}`;let d=null;for(const[,{fileName:F,functions:f}]of r)if(f.includes(s)){d=`${t(F)}_${t(s)}`;break}if(!d){const F=[...r.keys()].find(f=>{var n;return((n=r.get(f))==null?void 0:n.fileName)===u});if(F){const f=r.get(F);f!=null&&f.functions.includes(s)&&(d=`${t(u)}_${t(s)}`)}}if(d){const F=`${m}-->${d}`;l.has(F)||(o.push(` ${m} --> ${d}`),l.add(F))}}return o.join(`
|
|
31
|
+
`)}function he(e){const a=ie(e);if(!a.valid)return{success:!1,error:a.error,errorType:a.errorType??O.VALIDATION_ERROR};try{const o=v.readFileSync(e,"utf-8"),t=pe(o,e);return t?oe(t)?{success:!1,error:t.parseError,errorType:t.errorType,loc:t.loc}:{success:!0,info:t}:{success:!1,error:"Analysis returned null / 分析返回空值",errorType:O.PARSE_ERROR}}catch(o){const t=o;return{success:!1,error:q(O.FILE_READ_ERROR,"Failed to read or process file / 读取或处理文件失败",{file:e,suggestion:t.message}),errorType:O.FILE_READ_ERROR}}}function Ae(){const e=Y();e.parse(process.argv);const a=e.opts(),o=e.args;a.quiet&&ae(!0);const t=I.resolve(a.project);if(a.init){const c=I.join(t,".fnmaprc");if(v.existsSync(c)){console.log(`${C.yellow}!${C.reset} Config file already exists: .fnmaprc`);return}const s={enable:!0,include:["src/**/*.js","src/**/*.ts","src/**/*.jsx","src/**/*.tsx"],exclude:["node_modules","dist","build",".next","coverage","__pycache__",".cache"]};v.writeFileSync(c,JSON.stringify(s,null,2)),console.log(`${C.green}✓${C.reset} Created config file: .fnmaprc`);return}const R=[...a.files??[],...o].filter(c=>v.existsSync(c));let r=[];if(a.changed||a.staged){if(r=de(t,a.staged),r.length===0){E.info("No git changed code files detected");return}}else if(R.length>0)r=R.map(c=>I.isAbsolute(c)?c:I.resolve(t,c));else if(a.dir){const c=I.resolve(t,a.dir);r=z(c,t).map(m=>I.join(t,m))}else{const{config:c,source:s}=le(t);if(c){if(E.info(`Using config: ${s}`),c.enable===!1){E.info("Config file has enable set to false, skipping processing");return}const m=fe(c),d=[...K,...m.exclude];if(m.include)for(const F of m.include){const f=F.replace(/\/\*\*\/.*$/,"").replace(/\*\*\/.*$/,""),n=f?I.resolve(t,f):t;if(v.existsSync(n)){const i=z(n,t,d);r.push(...i.map(g=>I.join(t,g)))}}}else{E.warn("No config file found. Use fnmap init to create config, or use --dir/--files to specify scope"),E.info(""),E.info("Supported config files: .fnmaprc, .fnmaprc.json, package.json#fnmap");return}}if(r.length===0){E.info("No files found to process");return}r=[...new Set(r)],E.info("=".repeat(50)),E.title("fnmap - AI Code Indexing Tool"),E.info("=".repeat(50));let p=0,l=0;const u=new Map;for(const c of r){const s=I.relative(t,c);E.info(`
|
|
32
|
+
Analyzing: ${s}`);const m=he(c);if(m.success){p++;const d=m.info;E.success(`Imports: ${d.imports.length}, Functions: ${d.functions.length}, Classes: ${d.classes.length}, Constants: ${d.constants.length}`);const F=I.dirname(c);u.has(F)||u.set(F,[]),u.get(F).push({relativePath:s,info:d})}else l++,E.error(m.error)}if(u.size>0){E.info(`
|
|
33
|
+
Generating .fnmap index...`);for(const[c,s]of u){const m=ue(c,s),d=I.join(c,".fnmap");v.writeFileSync(d,m),E.success(I.relative(t,d))}}if(a.mermaid&&u.size>0){if(E.info(`
|
|
34
|
+
Generating Mermaid call graphs...`),a.mermaid==="file"||a.mermaid===!0)for(const[c,s]of u)for(const{relativePath:m,info:d}of s){const F=me(m,d);if(F){const f=I.basename(m,I.extname(m)),n=I.join(c,`${f}.mermaid`);v.writeFileSync(n,F),E.success(I.relative(t,n))}}else if(a.mermaid==="project"){const c=[];for(const[,d]of u)c.push(...d);const s=ge(t,c),m=I.join(t,".fnmap.mermaid");v.writeFileSync(m,s),E.success(I.relative(t,m))}}E.info(`
|
|
35
|
+
`+"=".repeat(50)),E.info(`Complete! Analyzed: ${C.green}${p}${C.reset}, Failed: ${l>0?C.red:""}${l}${C.reset}`),E.info("=".repeat(50))}if(require.main===module){const{main:e}=require("./main");e()}exports.COLORS=C;exports.DEFAULT_CONFIG=H;exports.DEFAULT_EXCLUDES=K;exports.ErrorTypes=O;exports.MAX_DIR_DEPTH=X;exports.MAX_FILE_SIZE=W;exports.SUPPORTED_EXTENSIONS=Z;exports.analyzeFile=pe;exports.extractJSDocDescription=k;exports.formatError=q;exports.generateAiMap=ue;exports.generateFileMermaid=me;exports.generateHeader=be;exports.generateProjectMermaid=ge;exports.getGitChangedFiles=de;exports.getVersion=ce;exports.isParseError=oe;exports.isProcessFailure=Fe;exports.isProcessSuccess=Se;exports.isQuietMode=Ce;exports.isValidationFailure=ve;exports.isValidationSuccess=_e;exports.loadConfig=le;exports.logger=E;exports.main=Ae;exports.mergeConfig=fe;exports.processFile=he;exports.program=Oe;exports.removeExistingHeaders=xe;exports.scanDirectory=z;exports.setQuietMode=ae;exports.setupCLI=Y;exports.validateConfig=Q;exports.validateFilePath=ie;
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/types/index.ts","../src/constants/index.ts","../src/validation/index.ts","../src/cli/index.ts","../src/config/index.ts","../src/scanner/index.ts","../src/analyzer/jsdoc.ts","../src/analyzer/index.ts","../src/generator/header.ts","../src/generator/fnmap.ts","../src/generator/mermaid.ts","../src/processor/index.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["// ============== 错误类型 ==============\n\nexport const ErrorTypes = {\n FILE_NOT_FOUND: 'FILE_NOT_FOUND',\n FILE_READ_ERROR: 'FILE_READ_ERROR',\n PARSE_ERROR: 'PARSE_ERROR',\n CONFIG_ERROR: 'CONFIG_ERROR',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n PERMISSION_ERROR: 'PERMISSION_ERROR',\n FILE_TOO_LARGE: 'FILE_TOO_LARGE'\n} as const;\n\nexport type ErrorType = (typeof ErrorTypes)[keyof typeof ErrorTypes];\n\n// ============== 验证结果类型 ==============\n\nexport interface ValidationSuccess {\n valid: true;\n}\n\nexport interface ValidationFailure {\n valid: false;\n error: string;\n errorType?: ErrorType;\n}\n\nexport type ValidationResult = ValidationSuccess | ValidationFailure;\n\n// ============== 配置类型 ==============\n\nexport interface FnmapConfig {\n enable?: boolean;\n include?: string[];\n exclude?: string[];\n}\n\nexport interface LoadedConfig {\n config: FnmapConfig | null;\n source: string | null;\n}\n\n// ============== 代码分析结果类型 ==============\n\nexport interface ImportInfo {\n module: string;\n members: string[];\n usedIn: string[];\n}\n\nexport interface FunctionInfo {\n name: string;\n params: string;\n startLine: number;\n endLine: number;\n description: string;\n}\n\nexport interface MethodInfo {\n name: string;\n params: string;\n line: number;\n static: boolean;\n kind: 'method' | 'get' | 'set' | 'constructor';\n description: string;\n}\n\nexport interface ClassInfo {\n name: string;\n superClass: string | null;\n startLine: number;\n endLine: number;\n methods: MethodInfo[];\n description: string;\n}\n\nexport interface ConstantInfo {\n name: string;\n line: number;\n description: string;\n}\n\nexport type CallGraph = Record<string, string[]>;\n\nexport interface FileInfo {\n description: string;\n imports: ImportInfo[];\n functions: FunctionInfo[];\n classes: ClassInfo[];\n constants: ConstantInfo[];\n callGraph: CallGraph;\n}\n\nexport interface ParseErrorResult {\n parseError: string;\n errorType: ErrorType;\n loc?: { line: number; column: number };\n}\n\nexport type AnalyzeResult = FileInfo | ParseErrorResult;\n\n// ============== 处理结果类型 ==============\n\nexport interface ProcessSuccess {\n success: true;\n info: FileInfo;\n}\n\nexport interface ProcessFailure {\n success: false;\n error: string;\n errorType: ErrorType;\n loc?: { line: number; column: number };\n}\n\nexport type ProcessResult = ProcessSuccess | ProcessFailure;\n\n// ============== CLI 类型 ==============\n\nexport interface CLIOptions {\n files?: string[];\n dir?: string;\n project: string;\n changed?: boolean;\n staged?: boolean;\n mermaid?: boolean | 'file' | 'project';\n quiet?: boolean;\n init?: boolean;\n}\n\n// ============== 错误格式化上下文 ==============\n\nexport interface ErrorContext {\n file?: string;\n line?: number;\n column?: number;\n suggestion?: string;\n}\n\n// ============== 文件信息映射 ==============\n\nexport interface FileInfoEntry {\n relativePath: string;\n info: FileInfo;\n}\n\n// ============== 类型守卫 ==============\n\nexport function isParseError(result: AnalyzeResult): result is ParseErrorResult {\n return 'parseError' in result;\n}\n\nexport function isProcessSuccess(result: ProcessResult): result is ProcessSuccess {\n return result.success === true;\n}\n\nexport function isProcessFailure(result: ProcessResult): result is ProcessFailure {\n return result.success === false;\n}\n\nexport function isValidationSuccess(result: ValidationResult): result is ValidationSuccess {\n return result.valid === true;\n}\n\nexport function isValidationFailure(result: ValidationResult): result is ValidationFailure {\n return result.valid === false;\n}\n","import type { FnmapConfig } from '../types';\n\n// ANSI颜色常量\nexport const COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m'\n} as const;\n\n// 文件大小限制 (10MB)\nexport const MAX_FILE_SIZE = 10 * 1024 * 1024;\n\n// 最大目录深度限制\nexport const MAX_DIR_DEPTH = 50;\n\n// 默认支持的文件扩展名\nexport const SUPPORTED_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs'] as const;\nexport type SupportedExtension = (typeof SUPPORTED_EXTENSIONS)[number];\n\n// 默认排除的目录\nexport const DEFAULT_EXCLUDES = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n 'coverage',\n '__pycache__',\n '.cache'\n] as const;\n\n// 默认配置\nexport const DEFAULT_CONFIG: Required<FnmapConfig> = {\n enable: true,\n include: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.mjs'],\n exclude: []\n};\n","import fs from 'fs';\nimport type { ValidationResult, ErrorContext, FnmapConfig, ErrorType } from '../types';\nimport { ErrorTypes } from '../types';\nimport { MAX_FILE_SIZE } from '../constants';\n\n/**\n * 验证文件路径\n */\nexport function validateFilePath(filePath: unknown): ValidationResult {\n if (!filePath || typeof filePath !== 'string') {\n return {\n valid: false,\n error: 'File path is required and must be a string / 文件路径必须是字符串',\n errorType: ErrorTypes.VALIDATION_ERROR\n };\n }\n\n if (!fs.existsSync(filePath)) {\n return {\n valid: false,\n error: `File not found: ${filePath} / 文件不存在: ${filePath}`,\n errorType: ErrorTypes.FILE_NOT_FOUND\n };\n }\n\n try {\n const stats = fs.statSync(filePath);\n\n if (!stats.isFile()) {\n return {\n valid: false,\n error: `Path is not a file: ${filePath} / 路径不是文件: ${filePath}`,\n errorType: ErrorTypes.VALIDATION_ERROR\n };\n }\n\n if (stats.size > MAX_FILE_SIZE) {\n return {\n valid: false,\n error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB): ${filePath} / 文件过大`,\n errorType: ErrorTypes.FILE_TOO_LARGE\n };\n }\n } catch (e) {\n const error = e as Error;\n return {\n valid: false,\n error: `Cannot access file: ${filePath}. Reason: ${error.message} / 无法访问文件`,\n errorType: ErrorTypes.PERMISSION_ERROR\n };\n }\n\n return { valid: true };\n}\n\n/**\n * 验证配置对象\n */\nexport function validateConfig(config: unknown): ValidationResult {\n if (!config || typeof config !== 'object') {\n return {\n valid: false,\n error: 'Config must be an object / 配置必须是对象'\n };\n }\n\n const cfg = config as Partial<FnmapConfig>;\n\n if (cfg.enable !== undefined && typeof cfg.enable !== 'boolean') {\n return {\n valid: false,\n error: 'Config.enable must be a boolean / enable 字段必须是布尔值'\n };\n }\n\n if (cfg.include !== undefined && !Array.isArray(cfg.include)) {\n return {\n valid: false,\n error: 'Config.include must be an array / include 字段必须是数组'\n };\n }\n\n if (cfg.exclude !== undefined && !Array.isArray(cfg.exclude)) {\n return {\n valid: false,\n error: 'Config.exclude must be an array / exclude 字段必须是数组'\n };\n }\n\n return { valid: true };\n}\n\n/**\n * 格式化错误信息\n */\nexport function formatError(\n _errorType: ErrorType | string,\n message: string,\n context: ErrorContext = {}\n): string {\n const parts: string[] = [message];\n\n if (context.file) {\n parts.push(`File: ${context.file}`);\n }\n\n if (context.line !== undefined && context.column !== undefined) {\n parts.push(`Location: Line ${context.line}, Column ${context.column}`);\n }\n\n if (context.suggestion) {\n parts.push(`Suggestion: ${context.suggestion}`);\n }\n\n return parts.join('\\n ');\n}\n","import { Command } from 'commander';\nimport { COLORS } from '../constants';\n\n// 日志工具\nlet quietMode = false;\n\n// 使用模块级变量存储 program 实例\nlet _program: Command | null = null;\n\nexport const logger = {\n error: (msg: string): void => {\n if (!quietMode) console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`);\n },\n success: (msg: string): void => {\n if (!quietMode) console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`);\n },\n info: (msg: string): void => {\n if (!quietMode) console.log(msg);\n },\n warn: (msg: string): void => {\n if (!quietMode) console.warn(`${COLORS.yellow}!${COLORS.reset} ${msg}`);\n },\n title: (msg: string): void => {\n if (!quietMode) console.log(`${COLORS.bold}${msg}${COLORS.reset}`);\n }\n};\n\nexport function setQuietMode(quiet: boolean): void {\n quietMode = quiet;\n}\n\nexport function isQuietMode(): boolean {\n return quietMode;\n}\n\n/**\n * 获取package.json中的版本号\n */\nexport function getVersion(): string {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const pkg = require('../../package.json') as { version: string };\n return pkg.version;\n } catch {\n return '0.1.0';\n }\n}\n\n/**\n * 配置CLI命令\n */\nexport function setupCLI(): Command {\n // 每次创建新的 Command 实例以支持测试\n _program = new Command();\n\n _program\n .name('fnmap')\n .description('AI code indexing tool - Analyzes JS/TS code structure and generates structured code maps')\n .version(getVersion(), '-v, --version', 'Show version number')\n .option('-f, --files <files>', 'Process specified files (comma-separated)', (val: string) =>\n val\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean)\n )\n .option('-d, --dir <dir>', 'Process all code files in directory')\n .option('-p, --project <dir>', 'Specify project root directory', process.env.CLAUDE_PROJECT_DIR ?? process.cwd())\n .option('-c, --changed', 'Process only git changed files (staged + modified + untracked)')\n .option('-s, --staged', 'Process only git staged files (for pre-commit hook)')\n .option('-m, --mermaid [mode]', 'Generate Mermaid call graph (file=file-level, project=project-level)')\n .option('-q, --quiet', 'Quiet mode')\n .option('--init', 'Create default config file .fnmaprc')\n .argument('[files...]', 'Directly specify file paths')\n .allowUnknownOption(false)\n .addHelpText(\n 'after',\n `\nConfiguration files (by priority):\n .fnmaprc JSON config file\n .fnmaprc.json JSON config file\n package.json#fnmap fnmap field in package.json\n\nOutput:\n .fnmap Code index file (imports, functions, classes, constants, call graph)\n *.mermaid Mermaid call graph (when using --mermaid file)\n .fnmap.mermaid Project-level Mermaid call graph (when using --mermaid project)\n\nExamples:\n $ fnmap --dir src Process src directory\n $ fnmap --files a.js,b.js Process specified files\n $ fnmap --changed Process git changed files\n $ fnmap --staged -q For pre-commit hook usage\n $ fnmap --mermaid file --dir src Generate file-level call graphs\n $ fnmap --mermaid project Generate project-level call graph\n $ fnmap --init Create config file\n`\n );\n\n return _program;\n}\n\n// 导出 program getter\nexport function getProgram(): Command {\n if (!_program) {\n return setupCLI();\n }\n return _program;\n}\n\n// 为了兼容性,导出 program 为 getter\nexport const program = {\n get opts() {\n return getProgram().opts.bind(getProgram());\n },\n get args() {\n return getProgram().args;\n },\n get parse() {\n return getProgram().parse.bind(getProgram());\n }\n};\n","import fs from 'fs';\nimport path from 'path';\nimport type { FnmapConfig, LoadedConfig } from '../types';\nimport { ErrorTypes } from '../types';\nimport { DEFAULT_CONFIG } from '../constants';\nimport { validateConfig, formatError } from '../validation';\nimport { logger } from '../cli';\n\n/**\n * 加载配置文件\n * 优先级: .fnmaprc > .fnmaprc.json > package.json#fnmap > 默认配置\n */\nexport function loadConfig(projectDir: string): LoadedConfig {\n const configFiles = ['.fnmaprc', '.fnmaprc.json'];\n\n for (const file of configFiles) {\n const configPath = path.join(projectDir, file);\n if (fs.existsSync(configPath)) {\n try {\n const content = fs.readFileSync(configPath, 'utf-8');\n\n // 检查是否为空文件\n if (!content.trim()) {\n logger.warn(`Config file is empty: ${file}. Using default config / 配置文件为空,使用默认配置`);\n continue;\n }\n\n let config: unknown;\n try {\n config = JSON.parse(content);\n } catch (parseError) {\n const pe = parseError as Error & { lineNumber?: number; columnNumber?: number };\n const errorMsg = formatError(\n ErrorTypes.CONFIG_ERROR,\n `Failed to parse config file: ${file} / 配置文件解析失败`,\n {\n file: configPath,\n line: pe.lineNumber,\n column: pe.columnNumber,\n suggestion: 'Check JSON syntax, ensure proper quotes and commas / 检查 JSON 语法,确保引号和逗号正确'\n }\n );\n logger.warn(errorMsg);\n continue;\n }\n\n // 验证配置\n const validation = validateConfig(config);\n if (!validation.valid) {\n logger.warn(`Invalid config in ${file}: ${validation.error}`);\n continue;\n }\n\n return { config: config as FnmapConfig, source: file };\n } catch (e) {\n const error = e as Error;\n const errorMsg = formatError(\n ErrorTypes.FILE_READ_ERROR,\n `Failed to read config file: ${file} / 配置文件读取失败`,\n {\n file: configPath,\n suggestion: error.message\n }\n );\n logger.warn(errorMsg);\n }\n }\n }\n\n // 检查 package.json 中的 fnmap 字段\n const pkgPath = path.join(projectDir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { fnmap?: unknown };\n if (pkg.fnmap) {\n const validation = validateConfig(pkg.fnmap);\n if (!validation.valid) {\n logger.warn(`Invalid fnmap config in package.json: ${validation.error}`);\n } else {\n return { config: pkg.fnmap as FnmapConfig, source: 'package.json#fnmap' };\n }\n }\n } catch {\n // ignore package.json parse errors\n }\n }\n\n return { config: null, source: null };\n}\n\n/**\n * 合并配置\n */\nexport function mergeConfig(userConfig: FnmapConfig | null): Required<FnmapConfig> {\n if (!userConfig) return DEFAULT_CONFIG;\n return {\n ...DEFAULT_CONFIG,\n ...userConfig,\n exclude: [...(userConfig.exclude ?? [])]\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport child_process from 'child_process';\nimport { SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDES, MAX_DIR_DEPTH } from '../constants';\nimport { logger } from '../cli';\n\n/**\n * 获取 git 改动的文件列表\n */\nexport function getGitChangedFiles(projectDir: string, stagedOnly = false): string[] {\n const files: string[] = [];\n\n try {\n let output: string;\n if (stagedOnly) {\n // 只获取 staged 文件\n output = child_process.execSync('git diff --cached --name-only --diff-filter=ACMR', {\n cwd: projectDir,\n encoding: 'utf-8'\n });\n } else {\n // 获取所有改动文件(包括 staged、modified、untracked)\n const staged = child_process.execSync('git diff --cached --name-only --diff-filter=ACMR', {\n cwd: projectDir,\n encoding: 'utf-8'\n });\n const modified = child_process.execSync('git diff --name-only --diff-filter=ACMR', {\n cwd: projectDir,\n encoding: 'utf-8'\n });\n const untracked = child_process.execSync('git ls-files --others --exclude-standard', {\n cwd: projectDir,\n encoding: 'utf-8'\n });\n output = `${staged}\\n${modified}\\n${untracked}`;\n }\n\n const changedFiles = output\n .split('\\n')\n .map((f) => f.trim())\n .filter(Boolean)\n .filter((f) => {\n const ext = path.extname(f);\n return (SUPPORTED_EXTENSIONS as readonly string[]).includes(ext);\n });\n\n // 去重并转换为绝对路径\n const uniqueFiles = [...new Set(changedFiles)];\n for (const f of uniqueFiles) {\n const fullPath = path.resolve(projectDir, f);\n if (fs.existsSync(fullPath)) {\n files.push(fullPath);\n }\n }\n } catch {\n // 不是 git 仓库或其他错误\n return [];\n }\n\n return files;\n}\n\n/**\n * 递归扫描目录获取所有代码文件\n */\nexport function scanDirectory(\n dir: string,\n baseDir: string = dir,\n excludes: readonly string[] = DEFAULT_EXCLUDES,\n depth = 0,\n visited: Set<string> = new Set()\n): string[] {\n const files: string[] = [];\n\n if (!fs.existsSync(dir)) {\n logger.warn(`Directory does not exist: ${dir} / 目录不存在`);\n return files;\n }\n\n // 检查深度限制\n if (depth > MAX_DIR_DEPTH) {\n logger.warn(`Max directory depth (${MAX_DIR_DEPTH}) exceeded: ${dir} / 超过最大目录深度`);\n return files;\n }\n\n // 获取规范化的真实路径,处理符号链接\n let realPath: string;\n try {\n realPath = fs.realpathSync(dir);\n } catch (e) {\n const error = e as Error;\n logger.warn(`Cannot resolve real path: ${dir}. Reason: ${error.message} / 无法解析真实路径`);\n return files;\n }\n\n // 检测循环引用\n if (visited.has(realPath)) {\n logger.warn(`Circular reference detected, skipping: ${dir} / 检测到循环引用`);\n return files;\n }\n visited.add(realPath);\n\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch (e) {\n const error = e as NodeJS.ErrnoException;\n if (error.code === 'EACCES' || error.code === 'EPERM') {\n logger.warn(`Permission denied: ${dir} / 权限不足`);\n } else {\n logger.warn(`Failed to read directory: ${dir}. Reason: ${error.message} / 读取目录失败`);\n }\n return files;\n }\n\n for (const entry of entries) {\n try {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!excludes.includes(entry.name)) {\n files.push(...scanDirectory(fullPath, baseDir, excludes, depth + 1, visited));\n }\n } else if (entry.isFile()) {\n const ext = path.extname(entry.name);\n if ((SUPPORTED_EXTENSIONS as readonly string[]).includes(ext)) {\n files.push(path.relative(baseDir, fullPath));\n }\n }\n // 忽略符号链接、设备文件等其他类型\n } catch (e) {\n const error = e as Error;\n logger.warn(`Error processing entry: ${entry.name}. Reason: ${error.message} / 处理条目出错`);\n }\n }\n\n return files;\n}\n","import type { Comment } from '@babel/types';\n\n/**\n * 从JSDoc注释中提取描述\n */\nexport function extractJSDocDescription(comment: Comment | null | undefined): string {\n if (!comment) return '';\n\n const text = comment.value;\n const lines = text.split('\\n').map((l) => l.replace(/^\\s*\\*\\s?/, '').trim());\n\n // 优先查找 @description 标签\n for (const line of lines) {\n if (line.startsWith('@description ')) {\n return line.slice(13).trim().slice(0, 60);\n }\n }\n\n // 否则取第一行非空非标签内容\n for (const line of lines) {\n if (line && !line.startsWith('@') && !line.startsWith('/')) {\n return line.slice(0, 60);\n }\n }\n\n return '';\n}\n","import parser from '@babel/parser';\nimport _traverse from '@babel/traverse';\nimport type { NodePath } from '@babel/traverse';\nimport type * as t from '@babel/types';\nimport type {\n FileInfo,\n AnalyzeResult,\n ImportInfo,\n FunctionInfo,\n ClassInfo,\n MethodInfo,\n ConstantInfo\n} from '../types';\nimport { ErrorTypes } from '../types';\nimport { formatError } from '../validation';\nimport { extractJSDocDescription } from './jsdoc';\n\n// 处理 ESM/CJS 兼容性\nconst traverse = typeof _traverse === 'function' ? _traverse : (_traverse as { default: typeof _traverse }).default;\n\n/**\n * 分析JS/TS文件,提取结构信息\n */\nexport function analyzeFile(code: unknown, filePath: string | null): AnalyzeResult {\n // 输入验证\n if (code === null || code === undefined) {\n return {\n parseError: 'Code content is null or undefined / 代码内容为空',\n errorType: ErrorTypes.VALIDATION_ERROR\n };\n }\n\n if (typeof code !== 'string') {\n return {\n parseError: 'Code must be a string / 代码必须是字符串类型',\n errorType: ErrorTypes.VALIDATION_ERROR\n };\n }\n\n // 检查空文件\n if (!code.trim()) {\n return {\n description: '',\n imports: [],\n functions: [],\n classes: [],\n constants: [],\n callGraph: {}\n };\n }\n\n const info: FileInfo = {\n description: '',\n imports: [],\n functions: [],\n classes: [],\n constants: [],\n callGraph: {}\n };\n\n // 提取现有的文件描述注释\n const existingCommentMatch = code.match(/^\\/\\*\\*[\\s\\S]*?\\*\\//);\n if (existingCommentMatch) {\n const commentText = existingCommentMatch[0];\n const lines = commentText\n .split('\\n')\n .map((l) => l.replace(/^\\s*\\*\\s?/, '').trim())\n .filter((l) => l && !l.startsWith('/') && !l.startsWith('@ai'));\n\n for (const line of lines) {\n if (line.startsWith('@description ')) {\n info.description = line.slice(13).trim();\n break;\n }\n }\n if (!info.description && lines.length > 0) {\n const firstLine = lines.find((l) => !l.startsWith('@'));\n if (firstLine) info.description = firstLine;\n }\n }\n\n let ast: t.File;\n try {\n const isTS = filePath && (filePath.endsWith('.ts') || filePath.endsWith('.tsx'));\n ast = parser.parse(code, {\n sourceType: 'unambiguous',\n plugins: ['jsx', 'classPrivateProperties', 'classPrivateMethods', ...(isTS ? (['typescript'] as const) : [])]\n });\n } catch (e) {\n const error = e as Error & { loc?: { line: number; column: number } };\n const errorMsg = formatError(ErrorTypes.PARSE_ERROR, `Syntax error: ${error.message} / 语法错误`, {\n file: filePath ?? undefined,\n line: error.loc?.line,\n column: error.loc?.column,\n suggestion: 'Check syntax errors in the file / 检查文件中的语法错误'\n });\n return {\n parseError: errorMsg,\n loc: error.loc,\n errorType: ErrorTypes.PARSE_ERROR\n };\n }\n\n // 收集导入信息\n const importsMap = new Map<string, Set<string>>();\n const localToModule = new Map<string, string>();\n const usageMap = new Map<string, Set<string>>();\n\n // 辅助函数:获取当前所在的函数/方法名\n function getEnclosingFunctionName(nodePath: NodePath): string | null {\n let current: NodePath | null = nodePath;\n while (current) {\n if (current.node.type === 'FunctionDeclaration') {\n const funcNode = current.node as t.FunctionDeclaration;\n if (funcNode.id) {\n return funcNode.id.name;\n }\n }\n if (current.node.type === 'ClassMethod') {\n const methodNode = current.node as t.ClassMethod;\n const classPath = current.parentPath?.parentPath;\n const classNode = classPath?.node as t.ClassDeclaration | undefined;\n const className = classNode?.id?.name ?? '';\n const methodName = (methodNode.key as t.Identifier)?.name ?? '';\n return className ? `${className}.${methodName}` : methodName;\n }\n if (current.node.type === 'ArrowFunctionExpression' || current.node.type === 'FunctionExpression') {\n const parent = current.parent as t.Node;\n if (parent?.type === 'VariableDeclarator') {\n const varDecl = parent as t.VariableDeclarator;\n const id = varDecl.id as t.Identifier;\n if (id?.name) return id.name;\n }\n }\n current = current.parentPath;\n }\n return null;\n }\n\n // 第一遍:收集导入信息\n traverse(ast, {\n VariableDeclarator(nodePath: NodePath<t.VariableDeclarator>) {\n const node = nodePath.node;\n if (\n node.init?.type === 'CallExpression' &&\n node.init.callee?.type === 'Identifier' &&\n node.init.callee.name === 'require' &&\n node.init.arguments?.[0]?.type === 'StringLiteral'\n ) {\n const moduleName = node.init.arguments[0].value;\n if (!importsMap.has(moduleName)) {\n importsMap.set(moduleName, new Set());\n }\n\n if (node.id.type === 'Identifier') {\n const localName = node.id.name;\n importsMap.get(moduleName)!.add(localName);\n localToModule.set(localName, moduleName);\n usageMap.set(localName, new Set());\n } else if (node.id.type === 'ObjectPattern') {\n for (const prop of node.id.properties) {\n if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {\n const localName = prop.value.type === 'Identifier' ? prop.value.name : prop.key.name;\n importsMap.get(moduleName)!.add(prop.key.name);\n localToModule.set(localName, moduleName);\n usageMap.set(localName, new Set());\n }\n }\n }\n }\n },\n\n CallExpression(nodePath: NodePath<t.CallExpression>) {\n const node = nodePath.node;\n if (\n node.callee?.type === 'Identifier' &&\n node.callee.name === 'require' &&\n node.arguments?.[0]?.type === 'StringLiteral'\n ) {\n const parent = nodePath.parent;\n if (parent?.type === 'MemberExpression' && parent.property?.type === 'Identifier') {\n const moduleName = node.arguments[0].value;\n if (!importsMap.has(moduleName)) {\n importsMap.set(moduleName, new Set());\n }\n importsMap.get(moduleName)!.add(parent.property.name);\n const grandParent = nodePath.parentPath?.parent;\n if (grandParent?.type === 'VariableDeclarator') {\n const varDecl = grandParent as t.VariableDeclarator;\n if (varDecl.id?.type === 'Identifier') {\n localToModule.set(varDecl.id.name, moduleName);\n usageMap.set(varDecl.id.name, new Set());\n }\n }\n }\n }\n },\n\n ImportDeclaration(nodePath: NodePath<t.ImportDeclaration>) {\n const node = nodePath.node;\n const moduleName = node.source.value;\n if (!importsMap.has(moduleName)) {\n importsMap.set(moduleName, new Set());\n }\n\n for (const specifier of node.specifiers) {\n let importedName: string | undefined;\n let localName: string | undefined;\n if (specifier.type === 'ImportDefaultSpecifier') {\n importedName = 'default';\n localName = specifier.local.name;\n } else if (specifier.type === 'ImportNamespaceSpecifier') {\n importedName = '*';\n localName = specifier.local.name;\n } else if (specifier.type === 'ImportSpecifier') {\n const imported = specifier.imported;\n importedName = imported.type === 'Identifier' ? imported.name : imported.value;\n localName = specifier.local.name;\n }\n if (importedName && localName) {\n importsMap.get(moduleName)!.add(importedName);\n localToModule.set(localName, moduleName);\n usageMap.set(localName, new Set());\n }\n }\n }\n });\n\n // 第二遍:收集函数/类信息,并追踪导入使用\n traverse(ast, {\n Identifier(nodePath: NodePath<t.Identifier>) {\n const name = nodePath.node.name;\n if (usageMap.has(name)) {\n const parent = nodePath.parent;\n if (parent?.type === 'VariableDeclarator' && (parent as t.VariableDeclarator).id === nodePath.node) return;\n if (parent?.type === 'ImportSpecifier' || parent?.type === 'ImportDefaultSpecifier') return;\n const enclosing = getEnclosingFunctionName(nodePath);\n if (enclosing) {\n usageMap.get(name)!.add(enclosing);\n }\n }\n },\n\n FunctionDeclaration(nodePath: NodePath<t.FunctionDeclaration>) {\n const node = nodePath.node;\n const name = node.id?.name ?? '[anonymous]';\n const params = node.params.map((p) => {\n if (p.type === 'Identifier') return p.name;\n if (p.type === 'AssignmentPattern' && p.left?.type === 'Identifier') return p.left.name + '?';\n if (p.type === 'RestElement' && p.argument?.type === 'Identifier') return '...' + p.argument.name;\n return '?';\n });\n const startLine = node.loc?.start?.line ?? 0;\n const endLine = node.loc?.end?.line ?? 0;\n\n let desc = '';\n const comments = node.leadingComments;\n if (comments && comments.length > 0) {\n desc = extractJSDocDescription(comments[comments.length - 1]);\n }\n\n info.functions.push({\n name,\n params: params.join(','),\n startLine,\n endLine,\n description: desc\n } as FunctionInfo);\n },\n\n ClassDeclaration(nodePath: NodePath<t.ClassDeclaration>) {\n const node = nodePath.node;\n const name = node.id?.name ?? '[anonymous]';\n const startLine = node.loc?.start?.line ?? 0;\n const endLine = node.loc?.end?.line ?? 0;\n const superClass = node.superClass?.type === 'Identifier' ? node.superClass.name : null;\n\n let desc = '';\n const comments = node.leadingComments;\n if (comments && comments.length > 0) {\n desc = extractJSDocDescription(comments[comments.length - 1]);\n }\n\n const methods: MethodInfo[] = [];\n if (node.body?.body) {\n for (const member of node.body.body) {\n if (member.type === 'ClassMethod') {\n const methodName = member.key?.type === 'Identifier' ? member.key.name : '[computed]';\n const methodParams = member.params.map((p) => {\n if (p.type === 'Identifier') return p.name;\n if (p.type === 'AssignmentPattern' && p.left?.type === 'Identifier') return p.left.name + '?';\n return '?';\n });\n const methodLine = member.loc?.start?.line ?? 0;\n\n let methodDesc = '';\n const methodComments = member.leadingComments;\n if (methodComments && methodComments.length > 0) {\n methodDesc = extractJSDocDescription(methodComments[methodComments.length - 1]);\n }\n\n methods.push({\n name: methodName,\n params: methodParams.join(','),\n line: methodLine,\n static: member.static,\n kind: member.kind as MethodInfo['kind'],\n description: methodDesc\n });\n }\n }\n }\n\n info.classes.push({\n name,\n superClass,\n startLine,\n endLine,\n methods,\n description: desc\n } as ClassInfo);\n },\n\n VariableDeclaration(nodePath: NodePath<t.VariableDeclaration>) {\n if (nodePath.parent.type !== 'Program') return;\n\n const node = nodePath.node;\n if (node.kind === 'const') {\n let desc = '';\n const comments = node.leadingComments;\n if (comments && comments.length > 0) {\n desc = extractJSDocDescription(comments[comments.length - 1]);\n }\n\n for (const decl of node.declarations) {\n const name = decl.id?.type === 'Identifier' ? decl.id.name : undefined;\n if (name && name === name.toUpperCase() && name.length > 2) {\n const startLine = node.loc?.start?.line ?? 0;\n info.constants.push({\n name,\n line: startLine,\n description: desc\n } as ConstantInfo);\n }\n }\n }\n }\n });\n\n // 转换导入信息\n for (const [moduleName, members] of importsMap) {\n const usedIn = new Set<string>();\n for (const localName of localToModule.keys()) {\n if (localToModule.get(localName) === moduleName && usageMap.has(localName)) {\n for (const fn of usageMap.get(localName)!) {\n usedIn.add(fn);\n }\n }\n }\n info.imports.push({\n module: moduleName,\n members: Array.from(members),\n usedIn: Array.from(usedIn)\n } as ImportInfo);\n }\n\n // 第三遍:构建函数调用图\n const definedFunctions = new Set<string>();\n for (const fn of info.functions) {\n definedFunctions.add(fn.name);\n }\n for (const cls of info.classes) {\n for (const method of cls.methods) {\n definedFunctions.add(method.name);\n definedFunctions.add(`${cls.name}.${method.name}`);\n }\n }\n\n // 收集所有导入的标识符\n const importedNames = new Set(localToModule.keys());\n\n const callGraph = new Map<string, Set<string>>();\n\n traverse(ast, {\n CallExpression(nodePath: NodePath<t.CallExpression>) {\n const node = nodePath.node;\n let calleeName: string | null = null;\n\n // 直接函数调用: funcName()\n if (node.callee.type === 'Identifier') {\n calleeName = node.callee.name;\n }\n // 方法调用: this.method() 或 obj.method()\n else if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {\n const objName = node.callee.object?.type === 'Identifier' ? node.callee.object.name : undefined;\n const propName = node.callee.property.name;\n // 导入对象的方法调用: fs.readFileSync()\n if (objName && importedNames.has(objName)) {\n calleeName = `${objName}.${propName}`;\n } else {\n calleeName = propName;\n }\n }\n\n if (calleeName) {\n const caller = getEnclosingFunctionName(nodePath);\n if (caller && caller !== calleeName) {\n // 文件内定义的函数 或 导入的函数\n const isDefinedFunc = definedFunctions.has(calleeName);\n const isImportedFunc =\n importedNames.has(calleeName) ||\n (calleeName.includes('.') && importedNames.has(calleeName.split('.')[0]!));\n\n if (isDefinedFunc || isImportedFunc) {\n if (!callGraph.has(caller)) callGraph.set(caller, new Set());\n callGraph.get(caller)!.add(calleeName);\n }\n }\n }\n }\n });\n\n // 转换调用图为对象格式\n info.callGraph = {};\n for (const [caller, callees] of callGraph) {\n info.callGraph[caller] = Array.from(callees);\n }\n\n return info;\n}\n\nexport { extractJSDocDescription };\n","import type { FileInfo } from '../types';\n\n/**\n * 生成紧凑格式AI注释头\n */\nexport function generateHeader(info: FileInfo, fileName: string): string {\n const lines: string[] = [];\n\n let headerLine = `/*@AI ${fileName}`;\n if (info.description) {\n headerLine += ` - ${info.description.slice(0, 50)}`;\n }\n lines.push(headerLine);\n\n // 导入信息\n for (const imp of info.imports) {\n const members = imp.members.join(',');\n let line = `<${imp.module}:${members}`;\n if (imp.usedIn?.length > 0) {\n line += ` ->${imp.usedIn.join(',')}`;\n }\n lines.push(line);\n }\n\n // 类信息\n for (const cls of info.classes) {\n let clsLine = cls.name;\n if (cls.superClass) clsLine += `:${cls.superClass}`;\n clsLine += ` ${cls.startLine}-${cls.endLine}`;\n if (cls.description) clsLine += ` ${cls.description}`;\n lines.push(clsLine);\n\n for (const method of cls.methods) {\n const prefix = method.static ? ' +' : ' .';\n const kindMark = method.kind === 'get' ? 'get:' : method.kind === 'set' ? 'set:' : '';\n let methodLine = `${prefix}${kindMark}${method.name}(${method.params}) ${method.line}`;\n if (method.description) methodLine += ` ${method.description}`;\n lines.push(methodLine);\n }\n }\n\n // 函数信息\n for (const fn of info.functions) {\n let fnLine = `${fn.name}(${fn.params}) ${fn.startLine}-${fn.endLine}`;\n if (fn.description) fnLine += ` ${fn.description}`;\n lines.push(fnLine);\n }\n\n // 常量信息\n for (const c of info.constants) {\n let constLine = `${c.name} ${c.line}`;\n if (c.description) constLine += ` ${c.description}`;\n lines.push(constLine);\n }\n\n lines.push('@AI*/');\n return lines.join('\\n');\n}\n\n/**\n * 移除现有的AI注释头\n */\nexport function removeExistingHeaders(code: string): string {\n let result = code;\n result = result.replace(/\\/\\*@AI[\\s\\S]*?@AI\\*\\/\\s*/g, '');\n result = result.replace(/\\/\\*\\*[\\s\\S]*?@ai-context-end[\\s\\S]*?\\*\\/\\s*/g, '');\n result = result.replace(/^\\/\\*\\*[\\s\\S]*?\\*\\/\\s*\\n?/, '');\n return result;\n}\n","import path from 'path';\nimport type { FileInfoEntry } from '../types';\n\n/**\n * 生成目录级 .fnmap 索引文件(包含完整信息)\n */\nexport function generateAiMap(dirPath: string, filesInfo: FileInfoEntry[]): string {\n const lines: string[] = [`@FNMAP ${path.basename(dirPath)}/`];\n\n for (const { relativePath, info } of filesInfo) {\n const fileName = path.basename(relativePath);\n let fileLine = `#${fileName}`;\n if (info.description) {\n fileLine += ` ${info.description.slice(0, 50)}`;\n }\n lines.push(fileLine);\n\n // 添加导入信息(不含使用位置,由函数行的调用图提供)\n for (const imp of info.imports) {\n const members = imp.members.join(',');\n lines.push(` <${imp.module}:${members}`);\n }\n\n // 添加类信息\n for (const cls of info.classes) {\n let clsLine = ` ${cls.name}`;\n if (cls.superClass) clsLine += `:${cls.superClass}`;\n clsLine += ` ${cls.startLine}-${cls.endLine}`;\n if (cls.description) clsLine += ` ${cls.description}`;\n lines.push(clsLine);\n\n // 类方法\n for (const method of cls.methods) {\n const prefix = method.static ? ' +' : ' .';\n const kindMark = method.kind === 'get' ? 'get:' : method.kind === 'set' ? 'set:' : '';\n let methodLine = `${prefix}${kindMark}${method.name}(${method.params}) ${method.line}`;\n if (method.description) methodLine += ` ${method.description}`;\n // 追加调用信息\n const methodKey = `${cls.name}.${method.name}`;\n const calls = info.callGraph?.[methodKey] ?? info.callGraph?.[method.name];\n if (calls && calls.length > 0) methodLine += ` →${calls.join(',')}`;\n lines.push(methodLine);\n }\n }\n\n // 添加函数信息\n for (const fn of info.functions) {\n let fnLine = ` ${fn.name}(${fn.params}) ${fn.startLine}-${fn.endLine}`;\n if (fn.description) fnLine += ` ${fn.description}`;\n // 追加调用信息\n const calls = info.callGraph?.[fn.name];\n if (calls && calls.length > 0) fnLine += ` →${calls.join(',')}`;\n lines.push(fnLine);\n }\n\n // 添加常量信息\n for (const c of info.constants) {\n let constLine = ` ${c.name} ${c.line}`;\n if (c.description) constLine += ` ${c.description}`;\n lines.push(constLine);\n }\n }\n\n lines.push('@FNMAP');\n return lines.join('\\n');\n}\n","import path from 'path';\nimport type { FileInfo, FileInfoEntry } from '../types';\n\n/**\n * 生成单文件的mermaid调用图\n */\nexport function generateFileMermaid(fileName: string, info: FileInfo): string | null {\n const lines: string[] = ['flowchart TD'];\n // 使用更安全的ID生成策略以避免冲突\n const safeId = (name: string): string =>\n 'id_' +\n name.replace(/[^a-zA-Z0-9]/g, (c) => `_${c.charCodeAt(0)}_`);\n const escapeLabel = (text: string): string => text.replace(/\"/g, '#quot;');\n\n // 收集所有函数节点\n const functions = info.functions.map((fn) => fn.name);\n const classMethods: string[] = [];\n for (const cls of info.classes) {\n for (const method of cls.methods) {\n classMethods.push(`${cls.name}.${method.name}`);\n }\n }\n const allFunctions = [...functions, ...classMethods];\n\n if (allFunctions.length === 0) {\n return null; // 没有函数,跳过\n }\n\n // 添加子图标题\n const baseName = path.basename(fileName, path.extname(fileName));\n lines.push(` subgraph ${safeId(baseName)}[\"${baseName}\"]`);\n\n // 添加函数节点\n for (const fn of allFunctions) {\n lines.push(` ${safeId(fn)}[\"${escapeLabel(fn)}\"]`);\n }\n lines.push(' end');\n\n // 添加调用关系边\n const callGraph = info.callGraph ?? {};\n for (const [caller, callees] of Object.entries(callGraph)) {\n for (const callee of callees) {\n // 只显示文件内部的调用关系\n if (allFunctions.includes(callee) || callee.includes('.')) {\n const calleeName = allFunctions.includes(callee) ? callee : callee.split('.').pop()!;\n if (allFunctions.includes(callee) || allFunctions.some((f) => f.endsWith(calleeName))) {\n lines.push(` ${safeId(caller)} --> ${safeId(callee)}`);\n }\n }\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * 生成项目级mermaid调用图\n */\nexport function generateProjectMermaid(_projectDir: string, allFilesInfo: FileInfoEntry[]): string {\n const lines: string[] = ['flowchart TD'];\n\n const safeId = (name: string): string =>\n 'id_' +\n name.replace(/[^a-zA-Z0-9]/g, (c) => `_${c.charCodeAt(0)}_`);\n const escapeLabel = (text: string): string => text.replace(/\"/g, '#quot;');\n\n // 收集所有文件的函数和它们的调用关系\n const fileToFunctions = new Map<string, { fileName: string; functions: string[] }>();\n const allCallEdges: Array<{ file: string; fileName: string; caller: string; callee: string }> = [];\n\n for (const { relativePath, info } of allFilesInfo) {\n const fileName = path.basename(relativePath, path.extname(relativePath));\n const functions = info.functions.map((fn) => fn.name);\n const classMethods: string[] = [];\n for (const cls of info.classes) {\n for (const method of cls.methods) {\n classMethods.push(`${cls.name}.${method.name}`);\n }\n }\n const allFunctions = [...functions, ...classMethods];\n fileToFunctions.set(relativePath, { fileName, functions: allFunctions });\n\n // 收集调用边\n const callGraph = info.callGraph ?? {};\n for (const [caller, callees] of Object.entries(callGraph)) {\n for (const callee of callees) {\n allCallEdges.push({\n file: relativePath,\n fileName,\n caller,\n callee\n });\n }\n }\n }\n\n // 按文件生成子图\n for (const [, { fileName, functions }] of fileToFunctions) {\n if (functions.length === 0) continue;\n\n lines.push(` subgraph ${safeId(fileName)}[\"${escapeLabel(fileName)}\"]`);\n for (const fn of functions) {\n lines.push(` ${safeId(fileName)}_${safeId(fn)}[\"${escapeLabel(fn)}\"]`);\n }\n lines.push(' end');\n }\n\n // 添加调用关系边\n const addedEdges = new Set<string>();\n for (const { fileName, caller, callee } of allCallEdges) {\n const callerId = `${safeId(fileName)}_${safeId(caller)}`;\n\n // 查找callee所在的文件\n let calleeId: string | null = null;\n for (const [, { fileName: fn, functions }] of fileToFunctions) {\n if (functions.includes(callee)) {\n calleeId = `${safeId(fn)}_${safeId(callee)}`;\n break;\n }\n }\n\n // 如果没找到,可能是同文件内调用或外部调用\n if (!calleeId) {\n const matchingKey = [...fileToFunctions.keys()].find((k) => fileToFunctions.get(k)?.fileName === fileName);\n if (matchingKey) {\n const fileData = fileToFunctions.get(matchingKey);\n if (fileData?.functions.includes(callee)) {\n calleeId = `${safeId(fileName)}_${safeId(callee)}`;\n }\n }\n }\n\n if (calleeId) {\n const edgeKey = `${callerId}-->${calleeId}`;\n if (!addedEdges.has(edgeKey)) {\n lines.push(` ${callerId} --> ${calleeId}`);\n addedEdges.add(edgeKey);\n }\n }\n }\n\n return lines.join('\\n');\n}\n","import fs from 'fs';\nimport type { ProcessResult } from '../types';\nimport { ErrorTypes, isParseError } from '../types';\nimport { validateFilePath, formatError } from '../validation';\nimport { analyzeFile } from '../analyzer';\n\n/**\n * 处理单个文件(只分析,不修改文件)\n */\nexport function processFile(filePath: string): ProcessResult {\n // 使用验证函数\n const validation = validateFilePath(filePath);\n if (!validation.valid) {\n return {\n success: false,\n error: validation.error,\n errorType: validation.errorType ?? ErrorTypes.VALIDATION_ERROR\n };\n }\n\n try {\n const code = fs.readFileSync(filePath, 'utf-8');\n const result = analyzeFile(code, filePath);\n\n if (!result) {\n return {\n success: false,\n error: 'Analysis returned null / 分析返回空值',\n errorType: ErrorTypes.PARSE_ERROR\n };\n }\n\n // 检查是否有解析错误\n if (isParseError(result)) {\n return {\n success: false,\n error: result.parseError,\n errorType: result.errorType,\n loc: result.loc\n };\n }\n\n return { success: true, info: result };\n } catch (e) {\n const error = e as Error;\n // 捕获文件读取错误\n const errorMsg = formatError(ErrorTypes.FILE_READ_ERROR, `Failed to read or process file / 读取或处理文件失败`, {\n file: filePath,\n suggestion: error.message\n });\n return {\n success: false,\n error: errorMsg,\n errorType: ErrorTypes.FILE_READ_ERROR\n };\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { CLIOptions, FileInfoEntry } from './types';\nimport { COLORS, DEFAULT_EXCLUDES } from './constants';\nimport { logger, setupCLI, setQuietMode } from './cli';\nimport { loadConfig, mergeConfig } from './config';\nimport { scanDirectory, getGitChangedFiles } from './scanner';\nimport { processFile } from './processor';\nimport { generateAiMap, generateFileMermaid, generateProjectMermaid } from './generator';\n\n/**\n * 主函数\n */\nexport function main(): void {\n // 配置并解析CLI\n const program = setupCLI();\n program.parse(process.argv);\n\n const options = program.opts<CLIOptions>();\n const args = program.args;\n\n // 设置静默模式\n if (options.quiet) {\n setQuietMode(true);\n }\n\n const projectDir = path.resolve(options.project);\n\n // init命令:创建默认配置文件\n if (options.init) {\n const configPath = path.join(projectDir, '.fnmaprc');\n if (fs.existsSync(configPath)) {\n console.log(`${COLORS.yellow}!${COLORS.reset} Config file already exists: .fnmaprc`);\n return;\n }\n\n const defaultConfig = {\n enable: true,\n include: ['src/**/*.js', 'src/**/*.ts', 'src/**/*.jsx', 'src/**/*.tsx'],\n exclude: ['node_modules', 'dist', 'build', '.next', 'coverage', '__pycache__', '.cache']\n };\n\n fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));\n console.log(`${COLORS.green}✓${COLORS.reset} Created config file: .fnmaprc`);\n return;\n }\n\n // 合并文件参数\n const fileArgs = [...(options.files ?? []), ...args].filter((f) => fs.existsSync(f));\n\n // 确定要处理的文件列表\n let filesToProcess: string[] = [];\n\n if (options.changed || options.staged) {\n // 基于git改动\n filesToProcess = getGitChangedFiles(projectDir, options.staged);\n if (filesToProcess.length === 0) {\n logger.info('No git changed code files detected');\n return;\n }\n } else if (fileArgs.length > 0) {\n // 指定的文件\n filesToProcess = fileArgs.map((f) => (path.isAbsolute(f) ? f : path.resolve(projectDir, f)));\n } else if (options.dir) {\n // 扫描指定目录\n const targetDir = path.resolve(projectDir, options.dir);\n const relFiles = scanDirectory(targetDir, projectDir);\n filesToProcess = relFiles.map((f) => path.join(projectDir, f));\n } else {\n // 检查项目配置文件\n const { config, source } = loadConfig(projectDir);\n\n if (config) {\n logger.info(`Using config: ${source}`);\n\n // 检查是否启用\n if (config.enable === false) {\n logger.info('Config file has enable set to false, skipping processing');\n return;\n }\n\n // 合并配置\n const mergedConfig = mergeConfig(config);\n const excludes = [...DEFAULT_EXCLUDES, ...mergedConfig.exclude];\n\n if (mergedConfig.include) {\n for (const pattern of mergedConfig.include) {\n const dir = pattern.replace(/\\/\\*\\*\\/.*$/, '').replace(/\\*\\*\\/.*$/, '');\n const targetDir = dir ? path.resolve(projectDir, dir) : projectDir;\n if (fs.existsSync(targetDir)) {\n const relFiles = scanDirectory(targetDir, projectDir, excludes);\n filesToProcess.push(...relFiles.map((f) => path.join(projectDir, f)));\n }\n }\n }\n } else {\n logger.warn('No config file found. Use fnmap init to create config, or use --dir/--files to specify scope');\n logger.info('');\n logger.info('Supported config files: .fnmaprc, .fnmaprc.json, package.json#fnmap');\n return;\n }\n }\n\n if (filesToProcess.length === 0) {\n logger.info('No files found to process');\n return;\n }\n\n // 去重\n filesToProcess = [...new Set(filesToProcess)];\n\n logger.info('='.repeat(50));\n logger.title('fnmap - AI Code Indexing Tool');\n logger.info('='.repeat(50));\n\n let processed = 0;\n let failed = 0;\n const dirFilesMap = new Map<string, FileInfoEntry[]>();\n\n for (const filePath of filesToProcess) {\n const relativePath = path.relative(projectDir, filePath);\n logger.info(`\\nAnalyzing: ${relativePath}`);\n\n const result = processFile(filePath);\n\n if (result.success) {\n processed++;\n const info = result.info;\n logger.success(\n `Imports: ${info.imports.length}, Functions: ${info.functions.length}, Classes: ${info.classes.length}, Constants: ${info.constants.length}`\n );\n\n // 收集目录信息\n const dir = path.dirname(filePath);\n if (!dirFilesMap.has(dir)) {\n dirFilesMap.set(dir, []);\n }\n dirFilesMap.get(dir)!.push({ relativePath, info });\n } else {\n failed++;\n logger.error(result.error);\n }\n }\n\n // 生成.fnmap索引文件\n if (dirFilesMap.size > 0) {\n logger.info('\\nGenerating .fnmap index...');\n for (const [dir, filesInfo] of dirFilesMap) {\n const mapContent = generateAiMap(dir, filesInfo);\n const mapPath = path.join(dir, '.fnmap');\n fs.writeFileSync(mapPath, mapContent);\n logger.success(path.relative(projectDir, mapPath));\n }\n }\n\n // 生成Mermaid调用图\n if (options.mermaid && dirFilesMap.size > 0) {\n logger.info('\\nGenerating Mermaid call graphs...');\n\n if (options.mermaid === 'file' || options.mermaid === true) {\n // 文件级:每个文件生成一个mermaid图\n for (const [dir, filesInfo] of dirFilesMap) {\n for (const { relativePath, info } of filesInfo) {\n const mermaidContent = generateFileMermaid(relativePath, info);\n if (mermaidContent) {\n const baseName = path.basename(relativePath, path.extname(relativePath));\n const mermaidPath = path.join(dir, `${baseName}.mermaid`);\n fs.writeFileSync(mermaidPath, mermaidContent);\n logger.success(path.relative(projectDir, mermaidPath));\n }\n }\n }\n } else if (options.mermaid === 'project') {\n // 项目级:生成一个包含所有文件的mermaid图\n const allFilesInfo: FileInfoEntry[] = [];\n for (const [, filesInfo] of dirFilesMap) {\n allFilesInfo.push(...filesInfo);\n }\n const mermaidContent = generateProjectMermaid(projectDir, allFilesInfo);\n const mermaidPath = path.join(projectDir, '.fnmap.mermaid');\n fs.writeFileSync(mermaidPath, mermaidContent);\n logger.success(path.relative(projectDir, mermaidPath));\n }\n }\n\n logger.info('\\n' + '='.repeat(50));\n logger.info(\n `Complete! Analyzed: ${COLORS.green}${processed}${COLORS.reset}, Failed: ${failed > 0 ? COLORS.red : ''}${failed}${COLORS.reset}`\n );\n logger.info('='.repeat(50));\n}\n","#!/usr/bin/env node\n/**\n * fnmap - AI Code Indexing Tool\n * Analyzes JS/TS code structure and generates structured code maps to help AI understand code quickly\n */\n\n// 导出类型\nexport type {\n ErrorType,\n ValidationResult,\n ValidationSuccess,\n ValidationFailure,\n FnmapConfig,\n LoadedConfig,\n ImportInfo,\n FunctionInfo,\n MethodInfo,\n ClassInfo,\n ConstantInfo,\n CallGraph,\n FileInfo,\n ParseErrorResult,\n AnalyzeResult,\n ProcessResult,\n ProcessSuccess,\n ProcessFailure,\n CLIOptions,\n ErrorContext,\n FileInfoEntry\n} from './types';\n\n// 导出类型守卫\nexport { ErrorTypes, isParseError, isProcessSuccess, isProcessFailure, isValidationSuccess, isValidationFailure } from './types';\n\n// 导出常量\nexport { COLORS, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDES, DEFAULT_CONFIG, MAX_FILE_SIZE, MAX_DIR_DEPTH } from './constants';\n\n// 导出验证函数\nexport { validateFilePath, validateConfig, formatError } from './validation';\n\n// 导出配置函数\nexport { loadConfig, mergeConfig } from './config';\n\n// 导出 CLI\nexport { setupCLI, getVersion, logger, setQuietMode, isQuietMode, program } from './cli';\n\n// 导出扫描器\nexport { scanDirectory, getGitChangedFiles } from './scanner';\n\n// 导出分析器\nexport { analyzeFile, extractJSDocDescription } from './analyzer';\n\n// 导出生成器\nexport { generateHeader, removeExistingHeaders, generateAiMap, generateFileMermaid, generateProjectMermaid } from './generator';\n\n// 导出处理器\nexport { processFile } from './processor';\n\n// 导出主函数\nexport { main } from './main';\n\n// CLI 入口\nif (require.main === module) {\n const { main } = require('./main');\n main();\n}\n"],"names":["ErrorTypes","isParseError","result","isProcessSuccess","isProcessFailure","isValidationSuccess","isValidationFailure","COLORS","MAX_FILE_SIZE","MAX_DIR_DEPTH","SUPPORTED_EXTENSIONS","DEFAULT_EXCLUDES","DEFAULT_CONFIG","validateFilePath","filePath","fs","stats","e","validateConfig","config","cfg","formatError","_errorType","message","context","parts","quietMode","_program","logger","msg","setQuietMode","quiet","isQuietMode","getVersion","setupCLI","Command","val","f","getProgram","program","loadConfig","projectDir","configFiles","file","configPath","path","content","parseError","pe","errorMsg","validation","error","pkgPath","pkg","mergeConfig","userConfig","getGitChangedFiles","stagedOnly","files","output","child_process","staged","modified","untracked","changedFiles","ext","uniqueFiles","fullPath","scanDirectory","dir","baseDir","excludes","depth","visited","realPath","entries","entry","extractJSDocDescription","comment","lines","l","line","traverse","_traverse","analyzeFile","code","info","existingCommentMatch","firstLine","ast","isTS","parser","_a","_b","importsMap","localToModule","usageMap","getEnclosingFunctionName","nodePath","current","funcNode","methodNode","classPath","classNode","className","methodName","_c","parent","id","node","_d","moduleName","localName","prop","grandParent","_e","varDecl","_f","specifier","importedName","imported","name","enclosing","params","p","startLine","endLine","desc","comments","superClass","methods","_g","member","_h","methodParams","methodLine","_j","_i","methodDesc","methodComments","decl","members","usedIn","fn","definedFunctions","cls","method","importedNames","callGraph","calleeName","objName","propName","caller","isDefinedFunc","isImportedFunc","callees","generateHeader","fileName","headerLine","imp","clsLine","prefix","kindMark","fnLine","c","constLine","removeExistingHeaders","generateAiMap","dirPath","filesInfo","relativePath","fileLine","methodKey","calls","generateFileMermaid","safeId","escapeLabel","text","functions","classMethods","allFunctions","baseName","callee","generateProjectMermaid","_projectDir","allFilesInfo","fileToFunctions","allCallEdges","addedEdges","callerId","calleeId","matchingKey","k","fileData","edgeKey","processFile","main","options","args","defaultConfig","fileArgs","filesToProcess","targetDir","source","mergedConfig","pattern","relFiles","processed","failed","dirFilesMap","mapContent","mapPath","mermaidContent","mermaidPath"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@didnhdj/fnmap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI code indexing tool for analyzing JS/TS code structure and generating structured code maps to help AI understand code quickly",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"fnmap": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"README_CN.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && vite build",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"prepublishOnly": "npm test && npm run build",
|
|
24
|
+
"prepare": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ai",
|
|
28
|
+
"code-analysis",
|
|
29
|
+
"ast",
|
|
30
|
+
"javascript",
|
|
31
|
+
"typescript",
|
|
32
|
+
"code-index",
|
|
33
|
+
"code-map",
|
|
34
|
+
"fnmap",
|
|
35
|
+
"code-intelligence",
|
|
36
|
+
"static-analysis"
|
|
37
|
+
],
|
|
38
|
+
"author": "gqfx <didnhdj2@gmail.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/gqfx/fnmap.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/gqfx/fnmap/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/gqfx/fnmap#readme",
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=14.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@babel/generator": "^7.28.5",
|
|
53
|
+
"@babel/parser": "^7.28.5",
|
|
54
|
+
"@babel/traverse": "^7.28.5",
|
|
55
|
+
"@babel/types": "^7.28.5",
|
|
56
|
+
"commander": "^14.0.2",
|
|
57
|
+
"prettier": "^3.7.4"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/babel__traverse": "^7.28.0",
|
|
61
|
+
"@types/node": "^25.0.3",
|
|
62
|
+
"@vitest/coverage-v8": "^1.1.0",
|
|
63
|
+
"cross-env": "^10.1.0",
|
|
64
|
+
"esbuild": "^0.27.2",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"vite": "^5.0.0",
|
|
67
|
+
"vitest": "^1.1.0"
|
|
68
|
+
}
|
|
69
|
+
}
|