speccore 8.3.121 → 8.3.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/about.d.ts.map +1 -1
  4. package/dist/commands/about.js +16 -0
  5. package/dist/commands/about.js.map +1 -1
  6. package/dist/commands/analyze.d.ts +1 -0
  7. package/dist/commands/analyze.d.ts.map +1 -1
  8. package/dist/commands/analyze.js +198 -70
  9. package/dist/commands/analyze.js.map +1 -1
  10. package/dist/commands/change.js +14 -7
  11. package/dist/commands/change.js.map +1 -1
  12. package/dist/commands/context.js +25 -31
  13. package/dist/commands/context.js.map +1 -1
  14. package/dist/commands/deploy.d.ts.map +1 -1
  15. package/dist/commands/deploy.js +6 -1
  16. package/dist/commands/deploy.js.map +1 -1
  17. package/dist/commands/done.js +29 -28
  18. package/dist/commands/done.js.map +1 -1
  19. package/dist/commands/execute.d.ts.map +1 -1
  20. package/dist/commands/execute.js +281 -20
  21. package/dist/commands/execute.js.map +1 -1
  22. package/dist/commands/import.js +1 -1
  23. package/dist/commands/import.js.map +1 -1
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +512 -28
  26. package/dist/commands/init.js.map +1 -1
  27. package/dist/commands/iteration/create.js +1 -1
  28. package/dist/commands/iteration/split.d.ts.map +1 -1
  29. package/dist/commands/iteration/split.js +158 -70
  30. package/dist/commands/iteration/split.js.map +1 -1
  31. package/dist/commands/iteration-from-global.js +1 -2
  32. package/dist/commands/iteration-from-global.js.map +1 -1
  33. package/dist/commands/pattern.js +30 -16
  34. package/dist/commands/pattern.js.map +1 -1
  35. package/dist/commands/pipeline.d.ts.map +1 -1
  36. package/dist/commands/pipeline.js +15 -4
  37. package/dist/commands/pipeline.js.map +1 -1
  38. package/dist/commands/retro.d.ts.map +1 -1
  39. package/dist/commands/retro.js +15 -8
  40. package/dist/commands/retro.js.map +1 -1
  41. package/dist/commands/status-panel.js +1 -1
  42. package/dist/commands/status-panel.js.map +1 -1
  43. package/dist/commands/sync.js +15 -8
  44. package/dist/commands/sync.js.map +1 -1
  45. package/dist/commands/trace.js +27 -24
  46. package/dist/commands/trace.js.map +1 -1
  47. package/dist/commands/update-env-configs.d.ts.map +1 -1
  48. package/dist/commands/update-env-configs.js +33 -5
  49. package/dist/commands/update-env-configs.js.map +1 -1
  50. package/dist/commands/update.d.ts.map +1 -1
  51. package/dist/commands/update.js +29 -0
  52. package/dist/commands/update.js.map +1 -1
  53. package/dist/commands/verify.d.ts +1 -1
  54. package/dist/commands/verify.d.ts.map +1 -1
  55. package/dist/commands/verify.js +25 -0
  56. package/dist/commands/verify.js.map +1 -1
  57. package/dist/core/ai-context-generator.js +35 -22
  58. package/dist/core/ai-context-generator.js.map +1 -1
  59. package/dist/core/ai-impact-analyzer.d.ts.map +1 -1
  60. package/dist/core/ai-impact-analyzer.js +1 -1
  61. package/dist/core/ai-impact-analyzer.js.map +1 -1
  62. package/dist/core/analyze-engine.d.ts +2 -1
  63. package/dist/core/analyze-engine.d.ts.map +1 -1
  64. package/dist/core/analyze-engine.js +217 -53
  65. package/dist/core/analyze-engine.js.map +1 -1
  66. package/dist/core/ask-engine.d.ts.map +1 -1
  67. package/dist/core/ask-engine.js +31 -13
  68. package/dist/core/ask-engine.js.map +1 -1
  69. package/dist/core/change-detection.d.ts.map +1 -1
  70. package/dist/core/change-detection.js +21 -5
  71. package/dist/core/change-detection.js.map +1 -1
  72. package/dist/core/code-scanner.d.ts +12 -3
  73. package/dist/core/code-scanner.d.ts.map +1 -1
  74. package/dist/core/code-scanner.js +376 -10
  75. package/dist/core/code-scanner.js.map +1 -1
  76. package/dist/core/context-builder.d.ts +2 -1
  77. package/dist/core/context-builder.d.ts.map +1 -1
  78. package/dist/core/context-builder.js +104 -25
  79. package/dist/core/context-builder.js.map +1 -1
  80. package/dist/core/deploy/engine.d.ts.map +1 -1
  81. package/dist/core/deploy/engine.js +21 -6
  82. package/dist/core/deploy/engine.js.map +1 -1
  83. package/dist/core/git-integration.d.ts +5 -1
  84. package/dist/core/git-integration.d.ts.map +1 -1
  85. package/dist/core/git-integration.js +52 -15
  86. package/dist/core/git-integration.js.map +1 -1
  87. package/dist/core/knowledge-graph.d.ts +2 -0
  88. package/dist/core/knowledge-graph.d.ts.map +1 -1
  89. package/dist/core/knowledge-graph.js +246 -232
  90. package/dist/core/knowledge-graph.js.map +1 -1
  91. package/dist/core/name-validator.js +1 -1
  92. package/dist/core/name-validator.js.map +1 -1
  93. package/dist/core/pipeline-test.d.ts.map +1 -1
  94. package/dist/core/pipeline-test.js +7 -4
  95. package/dist/core/pipeline-test.js.map +1 -1
  96. package/dist/core/prompt-builder.d.ts +3 -0
  97. package/dist/core/prompt-builder.d.ts.map +1 -1
  98. package/dist/core/prompt-builder.js +629 -77
  99. package/dist/core/prompt-builder.js.map +1 -1
  100. package/dist/core/rag-engine.d.ts +10 -5
  101. package/dist/core/rag-engine.d.ts.map +1 -1
  102. package/dist/core/rag-engine.js +134 -16
  103. package/dist/core/rag-engine.js.map +1 -1
  104. package/dist/core/spec-paths.d.ts +42 -2
  105. package/dist/core/spec-paths.d.ts.map +1 -1
  106. package/dist/core/spec-paths.js +207 -11
  107. package/dist/core/spec-paths.js.map +1 -1
  108. package/dist/core/structured-extractor.d.ts +67 -0
  109. package/dist/core/structured-extractor.d.ts.map +1 -1
  110. package/dist/core/structured-extractor.js +1298 -23
  111. package/dist/core/structured-extractor.js.map +1 -1
  112. package/dist/core/test-config.d.ts +1 -1
  113. package/dist/core/test-config.d.ts.map +1 -1
  114. package/dist/core/test-config.js +85 -65
  115. package/dist/core/test-config.js.map +1 -1
  116. package/dist/core/ui-verify/report-generator.d.ts.map +1 -1
  117. package/dist/core/ui-verify/report-generator.js +7 -3
  118. package/dist/core/ui-verify/report-generator.js.map +1 -1
  119. package/dist/core/ui-verify/smoke-engine.js +7 -1
  120. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  121. package/dist/core/unified-config.d.ts +43 -3
  122. package/dist/core/unified-config.d.ts.map +1 -1
  123. package/dist/core/unified-config.js +133 -13
  124. package/dist/core/unified-config.js.map +1 -1
  125. package/dist/core/unified-retrieval.d.ts.map +1 -1
  126. package/dist/core/unified-retrieval.js +41 -15
  127. package/dist/core/unified-retrieval.js.map +1 -1
  128. package/dist/core/unit-context-assembler.js +2 -2
  129. package/dist/core/unit-context-assembler.js.map +1 -1
  130. package/dist/core/validator.d.ts.map +1 -1
  131. package/dist/core/validator.js +59 -44
  132. package/dist/core/validator.js.map +1 -1
  133. package/dist/core/verify-engine.d.ts +9 -2
  134. package/dist/core/verify-engine.d.ts.map +1 -1
  135. package/dist/core/verify-engine.js +274 -108
  136. package/dist/core/verify-engine.js.map +1 -1
  137. package/dist/core/verify-report-aggregator.js +9 -20
  138. package/dist/core/verify-report-aggregator.js.map +1 -1
  139. package/dist/utils/synonyms.d.ts +24 -0
  140. package/dist/utils/synonyms.d.ts.map +1 -0
  141. package/dist/utils/synonyms.js +114 -0
  142. package/dist/utils/synonyms.js.map +1 -0
  143. package/package.json +1 -1
@@ -58,6 +58,21 @@ const CACHE_PATH = (0, path_1.join)('.speccore', 'cache', 'structured-data.json'
58
58
  function isTsOrJs(filePath) {
59
59
  return /\.(ts|tsx|js|jsx|mts|cts)$/i.test(filePath);
60
60
  }
61
+ // v8.3.125+: 支持 Java / Python / Go
62
+ function isSupportedSource(filePath) {
63
+ return /\.(ts|tsx|js|jsx|mts|cts|java|py|go)$/i.test(filePath);
64
+ }
65
+ function detectLanguage(filePath) {
66
+ if (/\.(ts|tsx|js|jsx|mts|cts)$/i.test(filePath))
67
+ return 'typescript';
68
+ if (/\.java$/i.test(filePath))
69
+ return 'java';
70
+ if (/\.py$/i.test(filePath))
71
+ return 'python';
72
+ if (/\.go$/i.test(filePath))
73
+ return 'go';
74
+ return 'unknown';
75
+ }
61
76
  function getScriptKind(filePath) {
62
77
  if (/\.tsx$/i.test(filePath))
63
78
  return ts.ScriptKind.TSX;
@@ -81,8 +96,83 @@ function getJSDoc(node, sourceFile) {
81
96
  .slice(0, 200);
82
97
  }
83
98
  // ── 提取器实现 ──
99
+ /** v8.3.124+: 从参数节点提取详细信息(类型、装饰器、校验规则) */
100
+ function extractParameterDetail(param, sourceFile) {
101
+ const name = param.name.getText(sourceFile);
102
+ const type = param.type?.getText(sourceFile);
103
+ const required = !param.questionToken && !param.initializer;
104
+ const paramDecs = [];
105
+ const validations = [];
106
+ let dtoRef;
107
+ const decorators = ts.canHaveDecorators(param) ? ts.getDecorators(param) : undefined;
108
+ if (decorators) {
109
+ for (const d of decorators) {
110
+ const decText = d.getText(sourceFile);
111
+ paramDecs.push(decText);
112
+ // 校验规则: @IsString, @MinLength(6), @IsEmail, @IsOptional
113
+ if (/^@(Is|String|Min|Max|Length|Email|UUID|URL|Enum|Array|Optional|Validate)/.test(decText)) {
114
+ validations.push(decText);
115
+ }
116
+ // DTO 引用: @Body() dto: CreateUserDto
117
+ if (decText.includes('@Body') && param.type) {
118
+ const typeText = param.type.getText(sourceFile);
119
+ if (!/^string|^number|^boolean|^Date/.test(typeText)) {
120
+ dtoRef = typeText;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return { name, type, required, decorators: paramDecs, validationRules: validations.length > 0 ? validations : undefined };
126
+ }
127
+ /** v8.3.124+: 提取鉴权装饰器 */
128
+ function extractAuthDecorators(decorators, sourceFile) {
129
+ if (!decorators)
130
+ return [];
131
+ const authDecs = [];
132
+ for (const d of decorators) {
133
+ const text = d.getText(sourceFile);
134
+ if (/^@(Auth|Roles|Public|UseGuards|Permissions|Scope|Require)/.test(text)) {
135
+ authDecs.push(text.slice(0, 60));
136
+ }
137
+ }
138
+ return authDecs;
139
+ }
140
+ /** v8.3.125+: 从方法体提取 Service 调用链 */
141
+ function extractServiceCalls(body, sourceFile) {
142
+ const calls = [];
143
+ if (!body)
144
+ return calls;
145
+ function visitCall(n) {
146
+ if (ts.isCallExpression(n)) {
147
+ const expr = n.expression;
148
+ // 模式: this.userService.findById(...) / userService.create(...)
149
+ if (ts.isPropertyAccessExpression(expr)) {
150
+ const serviceName = expr.expression.getText(sourceFile);
151
+ const methodName = expr.name.getText(sourceFile);
152
+ // 过滤掉原生方法和内置对象
153
+ if (!/^(console|Math|Date|JSON|Object|Array|String|Number|Promise|process|require)$/.test(serviceName)) {
154
+ const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line + 1;
155
+ calls.push({ service: serviceName, method: methodName, line });
156
+ }
157
+ }
158
+ // 模式: await someAsyncCall(...)
159
+ if (ts.isIdentifier(expr)) {
160
+ const name = expr.getText(sourceFile);
161
+ if (/^(find|get|create|update|delete|save|remove|query|execute|call)/i.test(name)) {
162
+ const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line + 1;
163
+ calls.push({ service: 'unknown', method: name, line });
164
+ }
165
+ }
166
+ }
167
+ ts.forEachChild(n, visitCall);
168
+ }
169
+ visitCall(body);
170
+ return [...new Map(calls.map(c => [`${c.service}.${c.method}`, c])).values()]; // 去重
171
+ }
84
172
  /**
85
173
  * 从 TypeScript 源码提取 API 端点
174
+ * v8.3.124+: 增强参数详情、鉴权装饰器、DTO 引用
175
+ * v8.3.125+: 增加 Service 调用链
86
176
  */
87
177
  function extractApisFromFile(filePath, sourceFile, relPath) {
88
178
  const apis = [];
@@ -102,6 +192,25 @@ function extractApisFromFile(filePath, sourceFile, relPath) {
102
192
  const handlerName = node.name?.getText(sourceFile) || 'anonymous';
103
193
  const params = node.parameters.map(p => p.name.getText(sourceFile));
104
194
  const returnType = node.type?.getText(sourceFile);
195
+ // v8.3.124+: 提取详细参数信息
196
+ const paramDetails = node.parameters.map(p => extractParameterDetail(p, sourceFile));
197
+ const authDecs = extractAuthDecorators(decorators, sourceFile);
198
+ // v8.3.124+: 提取 DTO 引用
199
+ let dtoRef;
200
+ for (const pd of paramDetails) {
201
+ if (pd.decorators?.some(d => d.includes('@Body'))) {
202
+ const p = node.parameters.find(param => param.name.getText(sourceFile) === pd.name);
203
+ if (p && p.type) {
204
+ const typeText = p.type.getText(sourceFile);
205
+ if (!/^(string|number|boolean|Date|any|unknown)\b/.test(typeText)) {
206
+ dtoRef = typeText;
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ }
212
+ // v8.3.125+: 提取 Service 调用链
213
+ const svcCalls = extractServiceCalls(ts.isMethodDeclaration(node) ? node.body : undefined, sourceFile);
105
214
  apis.push({
106
215
  path: pathArg || '/',
107
216
  method: decName.toUpperCase(),
@@ -109,9 +218,13 @@ function extractApisFromFile(filePath, sourceFile, relPath) {
109
218
  filePath: relPath,
110
219
  line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
111
220
  parameters: params,
221
+ parameterDetails: paramDetails.length > 0 ? paramDetails : undefined,
112
222
  responseType: returnType,
113
223
  decorators: decorators.map(d => d.getText(sourceFile).slice(0, 50)),
224
+ authDecorators: authDecs.length > 0 ? authDecs : undefined,
114
225
  description: getJSDoc(node, sourceFile),
226
+ dtoRef,
227
+ serviceCalls: svcCalls.length > 0 ? svcCalls : undefined,
115
228
  });
116
229
  }
117
230
  }
@@ -142,8 +255,55 @@ function extractApisFromFile(filePath, sourceFile, relPath) {
142
255
  visit(sourceFile);
143
256
  return apis;
144
257
  }
258
+ /** v8.3.124+: 从装饰器参数提取数组(用于 @Index(['a','b'])) */
259
+ function extractArrayFromArg(arg, sourceFile) {
260
+ if (ts.isArrayLiteralExpression(arg)) {
261
+ return arg.elements
262
+ .filter(e => ts.isStringLiteral(e))
263
+ .map(e => e.text);
264
+ }
265
+ return [];
266
+ }
267
+ /** v8.3.124+: 从类装饰器提取索引和约束 */
268
+ function extractClassIndexesAndConstraints(decorators, sourceFile) {
269
+ const indexes = [];
270
+ const constraints = [];
271
+ if (!decorators)
272
+ return { indexes, constraints };
273
+ for (const d of decorators) {
274
+ const text = d.getText(sourceFile);
275
+ if (!ts.isCallExpression(d.expression))
276
+ continue;
277
+ const decName = d.expression.expression.getText(sourceFile);
278
+ // @Index(['email', 'phone'], { unique: true })
279
+ if (/^Index$/i.test(decName) && d.expression.arguments.length > 0) {
280
+ const fields = extractArrayFromArg(d.expression.arguments[0], sourceFile);
281
+ let unique = false;
282
+ if (d.expression.arguments.length > 1 && ts.isObjectLiteralExpression(d.expression.arguments[1])) {
283
+ const uniqueProp = d.expression.arguments[1].properties.find(p => ts.isPropertyAssignment(p) && p.name.getText(sourceFile) === 'unique');
284
+ if (uniqueProp && uniqueProp.initializer.getText(sourceFile) === 'true')
285
+ unique = true;
286
+ }
287
+ if (fields.length > 0)
288
+ indexes.push({ fields, unique });
289
+ }
290
+ // @Unique(['username'])
291
+ if (/^Unique$/i.test(decName) && d.expression.arguments.length > 0) {
292
+ const fields = extractArrayFromArg(d.expression.arguments[0], sourceFile);
293
+ if (fields.length > 0)
294
+ constraints.push({ type: 'UNIQUE', fields });
295
+ }
296
+ // @Check('age > 0')
297
+ if (/^Check$/i.test(decName) && d.expression.arguments.length > 0) {
298
+ const expr = d.expression.arguments[0].getText(sourceFile).replace(/['"]/g, '');
299
+ constraints.push({ type: 'CHECK', fields: [expr] });
300
+ }
301
+ }
302
+ return { indexes, constraints };
303
+ }
145
304
  /**
146
305
  * 从 TypeScript 源码提取 Entity/Model 定义
306
+ * v8.3.124+: 增强字段装饰器、索引、约束、默认值提取
147
307
  */
148
308
  function extractEntitiesFromFile(filePath, sourceFile, relPath) {
149
309
  const entities = [];
@@ -170,13 +330,18 @@ function extractEntitiesFromFile(filePath, sourceFile, relPath) {
170
330
  const fieldName = member.name.getText(sourceFile);
171
331
  const fieldType = member.type?.getText(sourceFile) || 'unknown';
172
332
  const isOptional = member.questionToken !== undefined;
173
- const isPrimary = decorators?.some(d => d.getText(sourceFile).includes('@PrimaryGeneratedColumn') ||
174
- d.getText(sourceFile).includes('@PrimaryColumn'));
175
- // 检测关系装饰器
333
+ // 字段装饰器
176
334
  const fieldDecs = ts.canHaveDecorators(member) ? ts.getDecorators(member) : undefined;
335
+ const fieldDecoratorTexts = [];
336
+ const fieldValidations = [];
337
+ let isPrimary = false;
338
+ let defaultValue;
339
+ let columnOptions = {};
177
340
  if (fieldDecs) {
178
341
  for (const d of fieldDecs) {
179
342
  const decText = d.getText(sourceFile);
343
+ fieldDecoratorTexts.push(decText.slice(0, 80));
344
+ // 关系装饰器
180
345
  const relationMatch = decText.match(/@(ManyToOne|OneToMany|ManyToMany|OneToOne)/);
181
346
  if (relationMatch) {
182
347
  const targetType = member.type?.getText(sourceFile) || 'unknown';
@@ -185,6 +350,39 @@ function extractEntitiesFromFile(filePath, sourceFile, relPath) {
185
350
  type: relationMatch[1],
186
351
  field: fieldName,
187
352
  });
353
+ continue;
354
+ }
355
+ // 主键
356
+ if (/@PrimaryGeneratedColumn|@PrimaryColumn/.test(decText))
357
+ isPrimary = true;
358
+ // 校验规则
359
+ if (/^@(Is|String|Min|Max|Length|Email|UUID|URL|Enum|Array|Optional|Validate|Matches)/.test(decText)) {
360
+ fieldValidations.push(decText.slice(0, 60));
361
+ }
362
+ // @Column 选项提取
363
+ if (/^@Column/.test(decText) && ts.isCallExpression(d.expression)) {
364
+ // 尝试提取 @Column({ length: 20, default: 'active' })
365
+ for (const arg of d.expression.arguments) {
366
+ if (ts.isObjectLiteralExpression(arg)) {
367
+ for (const prop of arg.properties) {
368
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
369
+ const key = prop.name.getText(sourceFile);
370
+ const valText = prop.initializer.getText(sourceFile);
371
+ if (key === 'default')
372
+ defaultValue = valText.replace(/['"]/g, '');
373
+ try {
374
+ columnOptions[key] = JSON.parse(valText);
375
+ }
376
+ catch {
377
+ columnOptions[key] = valText;
378
+ }
379
+ }
380
+ }
381
+ }
382
+ else if (ts.isStringLiteral(arg)) {
383
+ columnOptions.type = arg.text;
384
+ }
385
+ }
188
386
  }
189
387
  }
190
388
  }
@@ -193,7 +391,10 @@ function extractEntitiesFromFile(filePath, sourceFile, relPath) {
193
391
  type: fieldType,
194
392
  nullable: isOptional,
195
393
  isPrimaryKey: isPrimary,
394
+ defaultValue,
196
395
  description: getJSDoc(member, sourceFile),
396
+ columnOptions: Object.keys(columnOptions).length > 0 ? columnOptions : undefined,
397
+ validationRules: fieldValidations.length > 0 ? fieldValidations : undefined,
197
398
  });
198
399
  }
199
400
  }
@@ -209,6 +410,8 @@ function extractEntitiesFromFile(filePath, sourceFile, relPath) {
209
410
  }
210
411
  }
211
412
  }
413
+ // v8.3.124+: 提取类级索引和约束
414
+ const { indexes = [], constraints = [] } = extractClassIndexesAndConstraints(decorators, sourceFile);
212
415
  entities.push({
213
416
  name: className,
214
417
  tableName: tableName || className,
@@ -217,6 +420,8 @@ function extractEntitiesFromFile(filePath, sourceFile, relPath) {
217
420
  fields,
218
421
  relations: relations.length > 0 ? relations : undefined,
219
422
  description: getJSDoc(node, sourceFile),
423
+ indexes: indexes.length > 0 ? indexes : undefined,
424
+ constraints: constraints.length > 0 ? constraints : undefined,
220
425
  });
221
426
  }
222
427
  ts.forEachChild(node, visit);
@@ -319,8 +524,10 @@ async function extractStructuredData(projectRoot, sourcePaths) {
319
524
  generatedAt: new Date().toISOString(),
320
525
  projectRoot,
321
526
  endpoints: {},
527
+ dtos: [],
528
+ services: [],
322
529
  dependencies: [],
323
- stats: { totalApis: 0, totalEntities: 0, totalRoutes: 0, totalComponents: 0, totalFiles: 0 },
530
+ stats: { totalApis: 0, totalEntities: 0, totalRoutes: 0, totalComponents: 0, totalDtos: 0, totalServices: 0, totalFiles: 0 },
324
531
  };
325
532
  let totalFiles = 0;
326
533
  for (const srcPath of sourcePaths) {
@@ -332,23 +539,62 @@ async function extractStructuredData(projectRoot, sourcePaths) {
332
539
  if (!data.endpoints[platform]) {
333
540
  data.endpoints[platform] = { apis: [], entities: [], routes: [], components: [] };
334
541
  }
335
- const files = await collectTsFiles(absPath);
542
+ const files = await collectSourceFiles(absPath);
336
543
  for (const filePath of files) {
337
544
  totalFiles++;
338
545
  const relPath = (0, path_1.relative)(projectRoot, filePath);
339
546
  const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
340
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ES2020, true, getScriptKind(filePath));
341
- // 提取各类数据
342
- const apis = extractApisFromFile(filePath, sourceFile, relPath);
343
- const entities = extractEntitiesFromFile(filePath, sourceFile, relPath);
344
- const routes = extractRoutesFromFile(filePath, sourceFile, relPath);
345
- const components = extractComponentsFromFile(filePath, sourceFile, relPath);
547
+ const lang = detectLanguage(filePath);
548
+ let apis = [];
549
+ let entities = [];
550
+ let routes = [];
551
+ let components = [];
552
+ let fileDtos = []; // v8.3.126+
553
+ let fileServices = []; // v8.3.126+
554
+ if (lang === 'typescript') {
555
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ES2020, true, getScriptKind(filePath));
556
+ apis = extractApisFromFile(filePath, sourceFile, relPath);
557
+ entities = extractEntitiesFromFile(filePath, sourceFile, relPath);
558
+ routes = extractRoutesFromFile(filePath, sourceFile, relPath);
559
+ components = extractComponentsFromFile(filePath, sourceFile, relPath);
560
+ fileDtos = extractDtosFromTsFile(filePath, sourceFile, relPath); // v8.3.126+
561
+ fileServices = extractServicesFromTsFile(filePath, sourceFile, relPath); // v8.3.126+
562
+ }
563
+ else if (lang === 'java') {
564
+ const result = extractFromJavaFile(content, relPath);
565
+ apis = result.apis;
566
+ entities = result.entities;
567
+ routes = result.routes;
568
+ components = result.components;
569
+ fileDtos = result.dtos || []; // v8.3.126+
570
+ fileServices = result.services || []; // v8.3.126+
571
+ }
572
+ else if (lang === 'python') {
573
+ const result = extractFromPythonFile(content, relPath);
574
+ apis = result.apis;
575
+ entities = result.entities;
576
+ routes = result.routes;
577
+ components = result.components;
578
+ fileDtos = result.dtos || [];
579
+ fileServices = result.services || [];
580
+ }
581
+ else if (lang === 'go') {
582
+ const result = extractFromGoFile(content, relPath);
583
+ apis = result.apis;
584
+ entities = result.entities;
585
+ routes = result.routes;
586
+ components = result.components;
587
+ fileDtos = result.dtos || [];
588
+ fileServices = result.services || [];
589
+ }
346
590
  data.endpoints[platform].apis.push(...apis);
347
591
  data.endpoints[platform].entities.push(...entities);
348
592
  data.endpoints[platform].routes.push(...routes);
349
593
  data.endpoints[platform].components.push(...components);
350
- // 提取 import 依赖
351
- const imports = extractImports(content, relPath);
594
+ data.dtos.push(...fileDtos); // v8.3.126+
595
+ data.services.push(...fileServices); // v8.3.126+
596
+ // 提取 import 依赖(按语言适配)
597
+ const imports = extractImportsByLang(content, relPath, lang);
352
598
  data.dependencies.push(...imports);
353
599
  }
354
600
  }
@@ -360,12 +606,14 @@ async function extractStructuredData(projectRoot, sourcePaths) {
360
606
  data.stats.totalRoutes += ep.routes.length;
361
607
  data.stats.totalComponents += ep.components.length;
362
608
  }
609
+ data.stats.totalDtos = data.dtos.length; // v8.3.126+
610
+ data.stats.totalServices = data.services.length; // v8.3.126+
363
611
  data.stats.totalFiles = totalFiles;
364
612
  // 写入缓存
365
613
  await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(CACHE_PATH));
366
614
  await (0, fs_extra_1.writeFile)(CACHE_PATH, JSON.stringify(data, null, 2));
367
615
  logger_1.logger.info(`📊 结构化数据提取完成: ${CACHE_PATH}`);
368
- logger_1.logger.info(` API: ${data.stats.totalApis}, Entity: ${data.stats.totalEntities}, Route: ${data.stats.totalRoutes}, Component: ${data.stats.totalComponents}, Files: ${totalFiles}`);
616
+ logger_1.logger.info(` API: ${data.stats.totalApis}, Entity: ${data.stats.totalEntities}, Route: ${data.stats.totalRoutes}, Component: ${data.stats.totalComponents}, DTO: ${data.stats.totalDtos}, Service: ${data.stats.totalServices}, Files: ${totalFiles}`);
369
617
  return data;
370
618
  }
371
619
  /**
@@ -383,16 +631,16 @@ async function loadStructuredData() {
383
631
  }
384
632
  }
385
633
  // ── 内部辅助 ──
386
- async function collectTsFiles(dir) {
634
+ async function collectSourceFiles(dir) {
387
635
  const results = [];
388
636
  try {
389
637
  const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
390
638
  for (const entry of entries) {
391
639
  const fullPath = (0, path_1.join)(dir, entry.name);
392
- if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
393
- results.push(...await collectTsFiles(fullPath));
640
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== '__pycache__' && entry.name !== 'venv') {
641
+ results.push(...await collectSourceFiles(fullPath));
394
642
  }
395
- else if (entry.isFile() && isTsOrJs(fullPath)) {
643
+ else if (entry.isFile() && isSupportedSource(fullPath)) {
396
644
  results.push(fullPath);
397
645
  }
398
646
  }
@@ -414,15 +662,1042 @@ function inferPlatform(path) {
414
662
  return 'shared';
415
663
  return 'unknown';
416
664
  }
417
- function extractImports(content, fromPath) {
665
+ function extractImportsByLang(content, fromPath, lang) {
418
666
  const edges = [];
419
667
  const lines = content.split('\n');
420
- for (const line of lines) {
421
- const match = line.match(/import\s+.*?\s+from\s+['"](\.\/[^'"]+)['"]/);
422
- if (match) {
423
- edges.push({ from: fromPath, to: match[1], type: 'import' });
668
+ if (lang === 'typescript' || lang === 'java') {
669
+ for (const line of lines) {
670
+ const match = line.match(/import\s+.*?\s+from\s+['"](\.\/[^'"]+)['"]/);
671
+ if (match)
672
+ edges.push({ from: fromPath, to: match[1], type: 'import' });
673
+ }
674
+ }
675
+ else if (lang === 'python') {
676
+ for (const line of lines) {
677
+ const match = line.match(/from\s+([.\w]+)\s+import|import\s+([.\w]+)/);
678
+ if (match)
679
+ edges.push({ from: fromPath, to: match[1] || match[2], type: 'import' });
680
+ }
681
+ }
682
+ else if (lang === 'go') {
683
+ for (const line of lines) {
684
+ const match = line.match(/"([^"]+\/[^"]+)"/);
685
+ if (match)
686
+ edges.push({ from: fromPath, to: match[1], type: 'import' });
424
687
  }
425
688
  }
426
689
  return edges;
427
690
  }
691
+ // 保留旧函数兼容
692
+ function extractImports(content, fromPath) {
693
+ return extractImportsByLang(content, fromPath, 'typescript');
694
+ }
695
+ // ── Java (Spring Boot) ──
696
+ function extractFromJavaFile(content, relPath) {
697
+ const apis = [];
698
+ const entities = [];
699
+ const routes = [];
700
+ const components = [];
701
+ const lines = content.split('\n');
702
+ let currentClass = null;
703
+ let currentClassLine = 0;
704
+ let classAnnotations = [];
705
+ let inClass = false;
706
+ let braceDepth = 0;
707
+ for (let i = 0; i < lines.length; i++) {
708
+ const line = lines[i].trim();
709
+ // 类级注解收集
710
+ if (/^@\w+/.test(line)) {
711
+ classAnnotations.push(line);
712
+ continue;
713
+ }
714
+ // 类定义
715
+ const classMatch = line.match(/(?:public\s+)?(?:class|interface)\s+(\w+)/);
716
+ if (classMatch) {
717
+ currentClass = classMatch[1];
718
+ currentClassLine = i + 1;
719
+ inClass = true;
720
+ braceDepth = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
721
+ // @Entity 检测
722
+ const isEntity = classAnnotations.some(a => /@Entity/.test(a));
723
+ if (isEntity) {
724
+ const tableMatch = classAnnotations.find(a => /@Table\s*\(/.test(a));
725
+ let tableName;
726
+ if (tableMatch) {
727
+ const tm = tableMatch.match(/name\s*=\s*["'](\w+)["']/);
728
+ if (tm)
729
+ tableName = tm[1];
730
+ }
731
+ const entity = extractJavaEntity(currentClass, tableName, content, relPath, currentClassLine, i);
732
+ if (entity)
733
+ entities.push(entity);
734
+ }
735
+ classAnnotations = [];
736
+ continue;
737
+ }
738
+ if (inClass) {
739
+ braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
740
+ if (braceDepth <= 0) {
741
+ inClass = false;
742
+ currentClass = null;
743
+ }
744
+ // Spring Boot API 方法检测
745
+ const methodMatch = line.match(/@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RequestMapping)\s*\(([^)]*)\)/);
746
+ if (methodMatch && currentClass) {
747
+ const httpMethod = methodMatch[1].replace('Mapping', '').toUpperCase();
748
+ const methodArg = methodMatch[2];
749
+ let path = '/';
750
+ const pathMatch = methodArg.match(/["']([^"']+)["']/);
751
+ if (pathMatch)
752
+ path = pathMatch[1];
753
+ // 查找方法签名(下一行或当前行剩余部分)
754
+ let methodLine = i + 1;
755
+ let sig = '';
756
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
757
+ sig += lines[j];
758
+ if (lines[j].includes('{') || lines[j].includes(';'))
759
+ break;
760
+ }
761
+ const sigMatch = sig.match(/(?:public\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\(([^)]*)\)/);
762
+ const returnType = sigMatch ? sigMatch[1] : undefined;
763
+ const methodName = sigMatch ? sigMatch[2] : 'unknown';
764
+ // 参数解析
765
+ const params = [];
766
+ const paramDetails = [];
767
+ if (sigMatch && sigMatch[3]) {
768
+ const paramParts = sigMatch[3].split(',');
769
+ for (const pp of paramParts) {
770
+ const pm = pp.trim().match(/(?:@\w+(?:\([^)]*\))?\s+)*(\w+(?:<[^>]+>)?)\s+(\w+)/);
771
+ if (pm) {
772
+ params.push(pm[2]);
773
+ const pDecs = [];
774
+ const pVals = [];
775
+ const decMatch = pp.match(/@(\w+)(?:\([^)]*\))?/g);
776
+ if (decMatch) {
777
+ for (const d of decMatch) {
778
+ pDecs.push(d);
779
+ if (/RequestBody|Valid|Validated/.test(d))
780
+ pVals.push(d);
781
+ }
782
+ }
783
+ paramDetails.push({ name: pm[2], type: pm[1], decorators: pDecs, validationRules: pVals });
784
+ }
785
+ }
786
+ }
787
+ // 鉴权注解(方法级)
788
+ const authDecs = [];
789
+ for (let j = Math.max(0, i - 5); j < i; j++) {
790
+ if (/@(PreAuthorize|Secured|RolesAllowed)/.test(lines[j])) {
791
+ authDecs.push(lines[j].trim());
792
+ }
793
+ }
794
+ // v8.3.125+: 提取方法体内的 Service 调用
795
+ const svcCalls = extractServiceCallsFromLines(lines, i, 40, 'java');
796
+ apis.push({
797
+ path,
798
+ method: httpMethod === 'REQUEST' ? 'GET' : httpMethod,
799
+ handler: `${currentClass}.${methodName}`,
800
+ filePath: relPath,
801
+ line: methodLine,
802
+ parameters: params,
803
+ parameterDetails: paramDetails.length > 0 ? paramDetails : undefined,
804
+ responseType: returnType,
805
+ authDecorators: authDecs.length > 0 ? authDecs : undefined,
806
+ serviceCalls: svcCalls.length > 0 ? svcCalls : undefined,
807
+ });
808
+ }
809
+ }
810
+ }
811
+ const dtos = extractDtosFromJavaFile(content, relPath); // v8.3.126+
812
+ const services = extractServicesFromJavaFile(content, relPath); // v8.3.126+
813
+ return { apis, entities, routes, components, dtos, services };
814
+ }
815
+ function extractJavaEntity(className, tableName, content, relPath, classLine, startIdx) {
816
+ const lines = content.split('\n');
817
+ const fields = [];
818
+ let braceDepth = 0;
819
+ let inClass = false;
820
+ for (let i = startIdx; i < lines.length; i++) {
821
+ const line = lines[i];
822
+ if (!inClass) {
823
+ if (line.includes('{'))
824
+ inClass = true;
825
+ continue;
826
+ }
827
+ braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
828
+ if (braceDepth < 0)
829
+ break;
830
+ const trimmed = line.trim();
831
+ // 字段检测: private Type fieldName;
832
+ const fieldMatch = trimmed.match(/^private\s+(?:@\w+(?:\([^)]*\))?\s+)*(\w+(?:<[^>]+>)?)\s+(\w+)\s*;/);
833
+ if (fieldMatch) {
834
+ const fieldType = fieldMatch[1];
835
+ const fieldName = fieldMatch[2];
836
+ let isPrimary = false;
837
+ let isNullable = true;
838
+ let defaultValue;
839
+ const colOpts = {};
840
+ const validations = [];
841
+ // 检查前面几行的注解
842
+ for (let j = Math.max(0, i - 3); j < i; j++) {
843
+ const al = lines[j].trim();
844
+ if (/@Id|@PrimaryKey/.test(al))
845
+ isPrimary = true;
846
+ if (/@Column\s*\(/.test(al)) {
847
+ const nullableMatch = al.match(/nullable\s*=\s*(false|true)/);
848
+ if (nullableMatch)
849
+ isNullable = nullableMatch[1] === 'true';
850
+ const lenMatch = al.match(/length\s*=\s*(\d+)/);
851
+ if (lenMatch)
852
+ colOpts.length = parseInt(lenMatch[1]);
853
+ const defMatch = al.match(/columnDefinition\s*=\s*["']([^"']+)["']/);
854
+ if (defMatch)
855
+ colOpts.columnDefinition = defMatch[1];
856
+ }
857
+ if (/@NotNull/.test(al))
858
+ isNullable = false;
859
+ if (/^@\w+/.test(al) && !/@Column|@Id|@PrimaryKey/.test(al)) {
860
+ validations.push(al.slice(0, 60));
861
+ }
862
+ }
863
+ fields.push({
864
+ name: fieldName,
865
+ type: fieldType,
866
+ nullable: isNullable,
867
+ isPrimaryKey: isPrimary,
868
+ defaultValue,
869
+ columnOptions: Object.keys(colOpts).length > 0 ? colOpts : undefined,
870
+ validationRules: validations.length > 0 ? validations : undefined,
871
+ });
872
+ }
873
+ }
874
+ if (fields.length === 0)
875
+ return null;
876
+ return { name: className, tableName: tableName || className, filePath: relPath, line: classLine, fields };
877
+ }
878
+ // ── Python (FastAPI / Django / Flask) ──
879
+ function extractFromPythonFile(content, relPath) {
880
+ const apis = [];
881
+ const entities = [];
882
+ const routes = [];
883
+ const components = [];
884
+ const lines = content.split('\n');
885
+ for (let i = 0; i < lines.length; i++) {
886
+ const line = lines[i];
887
+ // FastAPI 路由: @app.get("/path") 或 @router.post("/path")
888
+ const apiMatch = line.match(/@(app|router|api)\.(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/);
889
+ if (apiMatch) {
890
+ const method = apiMatch[2].toUpperCase();
891
+ const path = apiMatch[3];
892
+ let handler = 'unknown';
893
+ // 查找下一行的 def
894
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
895
+ const defMatch = lines[j].match(/def\s+(\w+)\s*\(([^)]*)\)/);
896
+ if (defMatch) {
897
+ handler = defMatch[1];
898
+ const params = [];
899
+ const paramDetails = [];
900
+ const paramStr = defMatch[2];
901
+ // 解析参数(跳过 self, cls, 和依赖注入参数)
902
+ const paramParts = paramStr.split(',');
903
+ for (const pp of paramParts) {
904
+ const pt = pp.trim();
905
+ if (!pt || pt === 'self' || pt === 'cls')
906
+ continue;
907
+ // 支持 type hint: param: Type
908
+ const pm = pt.match(/(\w+)\s*:\s*(.+)/);
909
+ if (pm) {
910
+ params.push(pm[1]);
911
+ paramDetails.push({ name: pm[1], type: pm[2] });
912
+ }
913
+ else {
914
+ const simple = pt.match(/(\w+)/);
915
+ if (simple) {
916
+ params.push(simple[1]);
917
+ paramDetails.push({ name: simple[1] });
918
+ }
919
+ }
920
+ }
921
+ const pySvcCalls = extractServiceCallsFromLines(lines, j, 30, 'python');
922
+ apis.push({
923
+ path,
924
+ method,
925
+ handler,
926
+ filePath: relPath,
927
+ line: j + 1,
928
+ parameters: params,
929
+ parameterDetails: paramDetails.length > 0 ? paramDetails : undefined,
930
+ serviceCalls: pySvcCalls.length > 0 ? pySvcCalls : undefined,
931
+ });
932
+ break;
933
+ }
934
+ }
935
+ }
936
+ // Django 视图: def view_name(request): 带有 url 注释
937
+ const djangoUrlMatch = line.match(/#\s*url:\s*(\S+)/);
938
+ if (djangoUrlMatch) {
939
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
940
+ const defMatch = lines[j].match(/def\s+(\w+)\s*\(/);
941
+ if (defMatch) {
942
+ const djangoSvcCalls = extractServiceCallsFromLines(lines, j, 30, 'python');
943
+ apis.push({
944
+ path: djangoUrlMatch[1],
945
+ method: 'GET',
946
+ handler: defMatch[1],
947
+ filePath: relPath,
948
+ line: j + 1,
949
+ serviceCalls: djangoSvcCalls.length > 0 ? djangoSvcCalls : undefined,
950
+ });
951
+ break;
952
+ }
953
+ }
954
+ }
955
+ // Pydantic / Dataclass Entity
956
+ const classMatch = line.match(/class\s+(\w+)\s*\(([^)]*)\)/);
957
+ if (classMatch) {
958
+ const className = classMatch[1];
959
+ const bases = classMatch[2];
960
+ const isPydantic = /BaseModel| pydantic/.test(bases);
961
+ const isDataclass = /@dataclass/.test(bases) || lines.slice(Math.max(0, i - 2), i).some(l => l.includes('@dataclass'));
962
+ const isDjangoModel = /models\.Model/.test(bases);
963
+ if (isPydantic || isDataclass || isDjangoModel) {
964
+ const entity = extractPythonEntity(className, content, relPath, i + 1, i);
965
+ if (entity)
966
+ entities.push(entity);
967
+ }
968
+ }
969
+ }
970
+ const dtos = extractDtosFromPythonFile(content, relPath); // v8.3.126+
971
+ const services = extractServicesFromPythonFile(content, relPath); // v8.3.126+
972
+ return { apis, entities, routes, components, dtos, services };
973
+ }
974
+ function extractPythonEntity(className, content, relPath, classLine, startIdx) {
975
+ const lines = content.split('\n');
976
+ const fields = [];
977
+ for (let i = startIdx + 1; i < lines.length; i++) {
978
+ const line = lines[i];
979
+ // 类结束检测
980
+ if (line.trim() && !line.startsWith(' ') && !line.startsWith('\t') && !line.startsWith('#'))
981
+ break;
982
+ // 字段检测: name: Type = Field(...) 或 name = models.CharField(...)
983
+ const fieldMatch = line.match(/^(\s+)(\w+)\s*:\s*(\w+(?:\[[^\]]+\])?)\s*(?:=\s*(.+))?/);
984
+ if (fieldMatch) {
985
+ const fieldName = fieldMatch[2];
986
+ const fieldType = fieldMatch[3];
987
+ const defaultExpr = fieldMatch[4];
988
+ let defaultValue;
989
+ let isNullable = true;
990
+ const validations = [];
991
+ if (defaultExpr) {
992
+ if (/Field\s*\(/.test(defaultExpr)) {
993
+ const nullMatch = defaultExpr.match(/nullable\s*=\s*(True|False)/);
994
+ if (nullMatch)
995
+ isNullable = nullMatch[1] === 'True';
996
+ const defMatch = defaultExpr.match(/default\s*=\s*([^,)]+)/);
997
+ if (defMatch)
998
+ defaultValue = defMatch[1].trim();
999
+ }
1000
+ else {
1001
+ defaultValue = defaultExpr.trim();
1002
+ }
1003
+ }
1004
+ fields.push({
1005
+ name: fieldName,
1006
+ type: fieldType,
1007
+ nullable: isNullable,
1008
+ defaultValue,
1009
+ validationRules: validations.length > 0 ? validations : undefined,
1010
+ });
1011
+ }
1012
+ // Django ORM 字段: name = models.CharField(...)
1013
+ const djangoFieldMatch = line.match(/(\w+)\s*=\s*models\.(\w+)Field\s*\(([^)]*)\)/);
1014
+ if (djangoFieldMatch) {
1015
+ const fieldName = djangoFieldMatch[1];
1016
+ const fieldType = djangoFieldMatch[2];
1017
+ const args = djangoFieldMatch[3];
1018
+ let isPrimary = false;
1019
+ let isNullable = true;
1020
+ let defaultValue;
1021
+ const colOpts = {};
1022
+ if (/primary_key\s*=\s*True/.test(args))
1023
+ isPrimary = true;
1024
+ if (/null\s*=\s*False/.test(args))
1025
+ isNullable = false;
1026
+ const defMatch = args.match(/default\s*=\s*([^,)]+)/);
1027
+ if (defMatch)
1028
+ defaultValue = defMatch[1].trim();
1029
+ const maxMatch = args.match(/max_length\s*=\s*(\d+)/);
1030
+ if (maxMatch)
1031
+ colOpts.maxLength = parseInt(maxMatch[1]);
1032
+ fields.push({
1033
+ name: fieldName,
1034
+ type: fieldType,
1035
+ nullable: isNullable,
1036
+ isPrimaryKey: isPrimary,
1037
+ defaultValue,
1038
+ columnOptions: Object.keys(colOpts).length > 0 ? colOpts : undefined,
1039
+ });
1040
+ }
1041
+ }
1042
+ if (fields.length === 0)
1043
+ return null;
1044
+ return { name: className, filePath: relPath, line: classLine, fields };
1045
+ }
1046
+ // ── Go (Gin / Echo) ──
1047
+ function extractFromGoFile(content, relPath) {
1048
+ const apis = [];
1049
+ const entities = [];
1050
+ const routes = [];
1051
+ const components = [];
1052
+ const lines = content.split('\n');
1053
+ for (let i = 0; i < lines.length; i++) {
1054
+ const line = lines[i];
1055
+ // Gin 路由: r.GET("/path", handler) 或 router.POST("/path", handler)
1056
+ const ginMatch = line.match(/\b(r|router|g|engine|e)\.(GET|POST|PUT|DELETE|PATCH|OPTIONS)\s*\(\s*["']([^"']+)["']\s*,\s*(\w+)/);
1057
+ if (ginMatch) {
1058
+ const handlerLine = findGoHandlerLine(lines, ginMatch[4]);
1059
+ const goSvcCalls = handlerLine >= 0 ? extractServiceCallsFromLines(lines, handlerLine, 30, 'go') : [];
1060
+ apis.push({
1061
+ path: ginMatch[3],
1062
+ method: ginMatch[2],
1063
+ handler: ginMatch[4],
1064
+ filePath: relPath,
1065
+ line: i + 1,
1066
+ serviceCalls: goSvcCalls.length > 0 ? goSvcCalls : undefined,
1067
+ });
1068
+ }
1069
+ // Echo 路由: e.GET("/path", handler) 或 group.GET(...)
1070
+ const echoMatch = line.match(/\b(\w+)\.(GET|POST|PUT|DELETE|PATCH|OPTIONS)\s*\(\s*["']([^"']+)["']\s*,\s*(\w+)/);
1071
+ if (echoMatch && !ginMatch) {
1072
+ const handlerLine = findGoHandlerLine(lines, echoMatch[4]);
1073
+ const goSvcCalls = handlerLine >= 0 ? extractServiceCallsFromLines(lines, handlerLine, 30, 'go') : [];
1074
+ apis.push({
1075
+ path: echoMatch[3],
1076
+ method: echoMatch[2],
1077
+ handler: echoMatch[4],
1078
+ filePath: relPath,
1079
+ line: i + 1,
1080
+ serviceCalls: goSvcCalls.length > 0 ? goSvcCalls : undefined,
1081
+ });
1082
+ }
1083
+ // Go struct Entity(带 gorm 标签)
1084
+ const structMatch = line.match(/type\s+(\w+)\s+struct\s*\{/);
1085
+ if (structMatch) {
1086
+ const structName = structMatch[1];
1087
+ const entity = extractGoEntity(structName, content, relPath, i + 1, i);
1088
+ if (entity)
1089
+ entities.push(entity);
1090
+ }
1091
+ }
1092
+ const dtos = extractDtosFromGoFile(content, relPath); // v8.3.126+
1093
+ const services = extractServicesFromGoFile(content, relPath); // v8.3.126+
1094
+ return { apis, entities, routes, components, dtos, services };
1095
+ }
1096
+ function extractGoEntity(structName, content, relPath, structLine, startIdx) {
1097
+ const lines = content.split('\n');
1098
+ const fields = [];
1099
+ let inStruct = false;
1100
+ let braceDepth = 0;
1101
+ for (let i = startIdx; i < lines.length; i++) {
1102
+ const line = lines[i];
1103
+ if (!inStruct) {
1104
+ if (line.includes('{'))
1105
+ inStruct = true;
1106
+ continue;
1107
+ }
1108
+ braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
1109
+ if (braceDepth < 0)
1110
+ break;
1111
+ const trimmed = line.trim();
1112
+ // 字段: Name Type `gorm:"..." json:"..."`
1113
+ const fieldMatch = trimmed.match(/^(\w+)\s+(\S+(?:\[[^\]]+\])?)\s*(?:`([^`]+)`)?/);
1114
+ if (fieldMatch && !trimmed.startsWith('//')) {
1115
+ const fieldName = fieldMatch[1];
1116
+ const fieldType = fieldMatch[2];
1117
+ const tagStr = fieldMatch[3] || '';
1118
+ let isPrimary = false;
1119
+ let isNullable = true;
1120
+ let defaultValue;
1121
+ const colOpts = {};
1122
+ // 解析 gorm 标签
1123
+ const gormMatch = tagStr.match(/gorm:"([^"]+)"/);
1124
+ if (gormMatch) {
1125
+ const gormOpts = gormMatch[1];
1126
+ if (/primaryKey/.test(gormOpts))
1127
+ isPrimary = true;
1128
+ if (/autoIncrement/.test(gormOpts))
1129
+ colOpts.autoIncrement = true;
1130
+ if (/default:/.test(gormOpts)) {
1131
+ const defMatch = gormOpts.match(/default:([^;]+)/);
1132
+ if (defMatch)
1133
+ defaultValue = defMatch[1];
1134
+ }
1135
+ if (/size:/.test(gormOpts)) {
1136
+ const sizeMatch = gormOpts.match(/size:(\d+)/);
1137
+ if (sizeMatch)
1138
+ colOpts.size = parseInt(sizeMatch[1]);
1139
+ }
1140
+ if (/type:/.test(gormOpts)) {
1141
+ const typeMatch = gormOpts.match(/type:([^;]+)/);
1142
+ if (typeMatch)
1143
+ colOpts.type = typeMatch[1];
1144
+ }
1145
+ }
1146
+ // 解析 json 标签判断 omitempty
1147
+ const jsonMatch = tagStr.match(/json:"([^"]+)"/);
1148
+ if (jsonMatch && jsonMatch[1].includes('omitempty')) {
1149
+ isNullable = true;
1150
+ }
1151
+ fields.push({
1152
+ name: fieldName,
1153
+ type: fieldType,
1154
+ nullable: isNullable,
1155
+ isPrimaryKey: isPrimary,
1156
+ defaultValue,
1157
+ columnOptions: Object.keys(colOpts).length > 0 ? colOpts : undefined,
1158
+ });
1159
+ }
1160
+ }
1161
+ if (fields.length === 0)
1162
+ return null;
1163
+ return { name: structName, filePath: relPath, line: structLine, fields };
1164
+ }
1165
+ /** v8.3.125+: 在 Go 文件中查找 handler 函数定义行 */
1166
+ function findGoHandlerLine(lines, handlerName) {
1167
+ for (let i = 0; i < lines.length; i++) {
1168
+ if (new RegExp(`func\\s+(?:\\([^)]+\\)\\s+)?${handlerName}\\s*\\(`).test(lines[i])) {
1169
+ return i;
1170
+ }
1171
+ }
1172
+ return -1;
1173
+ }
1174
+ /** v8.3.125+: 通用 Service 调用提取(从代码行数组) */
1175
+ function extractServiceCallsFromLines(lines, startIdx, maxLines, lang) {
1176
+ const calls = [];
1177
+ const seen = new Set();
1178
+ const endIdx = Math.min(startIdx + maxLines, lines.length);
1179
+ for (let i = startIdx; i < endIdx; i++) {
1180
+ const line = lines[i].trim();
1181
+ if (line.startsWith('//') || line.startsWith('#') || line.startsWith('*'))
1182
+ continue;
1183
+ let matches = [];
1184
+ if (lang === 'java') {
1185
+ // this.userService.findById(...) 或 userService.create(...)
1186
+ const regex = /(\w+)\.(\w+)\s*\(/g;
1187
+ let m;
1188
+ while ((m = regex.exec(line)) !== null) {
1189
+ if (!/^(if|while|for|switch|return|new|assert|System|Math|String|Integer|Long|Boolean|Object|Arrays|Collections|Stream|Optional|logger|log)\b/.test(m[1])) {
1190
+ matches.push(m);
1191
+ }
1192
+ }
1193
+ }
1194
+ else if (lang === 'python') {
1195
+ // self.user_service.find_by_id(...) 或 user_service.create(...)
1196
+ const regex = /(\w+)\.(\w+)\s*\(/g;
1197
+ let m;
1198
+ while ((m = regex.exec(line)) !== null) {
1199
+ if (!/^(if|while|for|return|print|len|range|enumerate|zip|map|filter|super|self).__/.test(m[1] + '.' + m[2])) {
1200
+ matches.push(m);
1201
+ }
1202
+ }
1203
+ }
1204
+ else if (lang === 'go') {
1205
+ // svc.FindById(...) 或 service.Create(...)
1206
+ const regex = /(\w+)\.(\w+)\s*\(/g;
1207
+ let m;
1208
+ while ((m = regex.exec(line)) !== null) {
1209
+ if (!/^(if|for|switch|return|defer|go|make|new|append|copy|len|cap|panic|recover|print|println|fmt|log|strings|strconv|time|errors)\b/.test(m[1])) {
1210
+ matches.push(m);
1211
+ }
1212
+ }
1213
+ }
1214
+ for (const m of matches) {
1215
+ const key = `${m[1]}.${m[2]}`;
1216
+ if (!seen.has(key)) {
1217
+ seen.add(key);
1218
+ calls.push({ service: m[1], method: m[2], line: i + 1 });
1219
+ }
1220
+ }
1221
+ }
1222
+ return calls;
1223
+ }
1224
+ // ═══════════════════════════════════════════════════════════
1225
+ // v8.3.126+: DTO / Service 独立提取器
1226
+ // ═══════════════════════════════════════════════════════════
1227
+ /** 从 TypeScript 文件提取 DTO 定义(class/interface + class-validator 装饰器) */
1228
+ function extractDtosFromTsFile(filePath, sourceFile, relPath) {
1229
+ const dtos = [];
1230
+ function visit(node) {
1231
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
1232
+ const className = node.name?.getText(sourceFile);
1233
+ if (!className)
1234
+ return;
1235
+ // DTO 检测:类名含 DTO/Vo/Input/Output/Query/Command/Request/Response
1236
+ // 或包含 class-validator 装饰器
1237
+ const isDtoLike = /(Dto|VO|Input|Output|Query|Command|Request|Response|Create|Update|Delete|Filter|Search|Sort)$/i.test(className);
1238
+ const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) : undefined;
1239
+ const hasValidator = node.members.some(m => {
1240
+ const memberDecs = ts.canHaveDecorators(m) ? ts.getDecorators(m) : undefined;
1241
+ return memberDecs?.some(d => /^@(Is|String|Min|Max|Length|Email|UUID|URL|Enum|Array|Optional|Validate|Matches|Not)/.test(d.getText(sourceFile)));
1242
+ });
1243
+ if (!isDtoLike && !hasValidator)
1244
+ return;
1245
+ const fields = [];
1246
+ for (const member of node.members) {
1247
+ if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
1248
+ const fieldName = member.name.getText(sourceFile);
1249
+ const fieldType = member.type?.getText(sourceFile) || 'unknown';
1250
+ const isOptional = member.questionToken !== undefined;
1251
+ const fieldDecs = ts.canHaveDecorators(member) ? ts.getDecorators(member) : undefined;
1252
+ const validations = [];
1253
+ const decorators = [];
1254
+ if (fieldDecs) {
1255
+ for (const d of fieldDecs) {
1256
+ const dt = d.getText(sourceFile);
1257
+ decorators.push(dt.slice(0, 80));
1258
+ if (/^@(Is|String|Min|Max|Length|Email|UUID|URL|Enum|Array|Optional|Validate|Matches|Not)/.test(dt)) {
1259
+ validations.push(dt.slice(0, 60));
1260
+ }
1261
+ }
1262
+ }
1263
+ fields.push({
1264
+ name: fieldName,
1265
+ type: fieldType,
1266
+ required: !isOptional,
1267
+ decorators: decorators.length > 0 ? decorators : undefined,
1268
+ validationRules: validations.length > 0 ? validations : undefined,
1269
+ description: getJSDoc(member, sourceFile),
1270
+ });
1271
+ }
1272
+ }
1273
+ if (fields.length > 0) {
1274
+ dtos.push({
1275
+ name: className,
1276
+ filePath: relPath,
1277
+ line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
1278
+ fields,
1279
+ description: getJSDoc(node, sourceFile),
1280
+ });
1281
+ }
1282
+ }
1283
+ ts.forEachChild(node, visit);
1284
+ }
1285
+ visit(sourceFile);
1286
+ return dtos;
1287
+ }
1288
+ /** 从 TypeScript 文件提取 Service 定义 */
1289
+ function extractServicesFromTsFile(filePath, sourceFile, relPath) {
1290
+ const services = [];
1291
+ function visit(node) {
1292
+ if (ts.isClassDeclaration(node)) {
1293
+ const className = node.name?.getText(sourceFile);
1294
+ if (!className)
1295
+ return;
1296
+ // Service 检测:类名含 Service/Provider/Repository/Manager/Handler
1297
+ const isServiceLike = /(Service|Provider|Repository|Manager|Handler|Facade|Gateway)$/i.test(className);
1298
+ const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) : undefined;
1299
+ const hasInjectable = decorators?.some(d => /^@(Injectable|Service|Repository|Component)/.test(d.getText(sourceFile)));
1300
+ if (!isServiceLike && !hasInjectable)
1301
+ return;
1302
+ const methods = [];
1303
+ const injects = [];
1304
+ for (const member of node.members) {
1305
+ // 构造函数注入提取
1306
+ if (ts.isConstructorDeclaration(member)) {
1307
+ for (const param of member.parameters) {
1308
+ const paramType = param.type?.getText(sourceFile);
1309
+ if (paramType && /(Service|Repository|Manager|Provider|Client|Dao|Gateway)/i.test(paramType)) {
1310
+ injects.push(paramType);
1311
+ }
1312
+ }
1313
+ }
1314
+ // 方法提取
1315
+ if (ts.isMethodDeclaration(member)) {
1316
+ const methodName = member.name.getText(sourceFile);
1317
+ // 跳过私有方法和生命周期方法
1318
+ if (methodName.startsWith('_') || /^(constructor|onModuleInit|onModuleDestroy|onApplicationBootstrap|beforeApplicationShutdown)$/.test(methodName))
1319
+ continue;
1320
+ const returnType = member.type?.getText(sourceFile);
1321
+ const params = member.parameters.map(p => ({
1322
+ name: p.name.getText(sourceFile),
1323
+ type: p.type?.getText(sourceFile),
1324
+ }));
1325
+ const methodDecs = ts.canHaveDecorators(member) ? ts.getDecorators(member) : undefined;
1326
+ const decorators = methodDecs?.map(d => d.getText(sourceFile).slice(0, 50)) || [];
1327
+ methods.push({
1328
+ name: methodName,
1329
+ returnType,
1330
+ parameters: params,
1331
+ decorators: decorators.length > 0 ? decorators : undefined,
1332
+ description: getJSDoc(member, sourceFile),
1333
+ line: sourceFile.getLineAndCharacterOfPosition(member.getStart(sourceFile)).line + 1,
1334
+ });
1335
+ }
1336
+ }
1337
+ if (methods.length > 0) {
1338
+ services.push({
1339
+ name: className,
1340
+ filePath: relPath,
1341
+ line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
1342
+ methods,
1343
+ description: getJSDoc(node, sourceFile),
1344
+ injects: injects.length > 0 ? injects : undefined,
1345
+ });
1346
+ }
1347
+ }
1348
+ ts.forEachChild(node, visit);
1349
+ }
1350
+ visit(sourceFile);
1351
+ return services;
1352
+ }
1353
+ // ── Java DTO / Service 提取 ──
1354
+ function extractDtosFromJavaFile(content, relPath) {
1355
+ const dtos = [];
1356
+ const lines = content.split('\n');
1357
+ let currentClass = null;
1358
+ let classLine = 0;
1359
+ let classAnnotations = [];
1360
+ let inClass = false;
1361
+ let braceDepth = 0;
1362
+ for (let i = 0; i < lines.length; i++) {
1363
+ const line = lines[i].trim();
1364
+ if (/^@\w+/.test(line)) {
1365
+ classAnnotations.push(line);
1366
+ continue;
1367
+ }
1368
+ const classMatch = line.match(/(?:public\s+)?class\s+(\w+)/);
1369
+ if (classMatch) {
1370
+ currentClass = classMatch[1];
1371
+ classLine = i + 1;
1372
+ inClass = true;
1373
+ braceDepth = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
1374
+ const isDtoLike = /(DTO|VO|Request|Response|Input|Output|Query|Command|Form|Param)/i.test(currentClass);
1375
+ if (!isDtoLike) {
1376
+ currentClass = null;
1377
+ inClass = false;
1378
+ classAnnotations = [];
1379
+ continue;
1380
+ }
1381
+ classAnnotations = [];
1382
+ continue;
1383
+ }
1384
+ if (inClass && currentClass) {
1385
+ braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
1386
+ if (braceDepth <= 0) {
1387
+ inClass = false;
1388
+ currentClass = null;
1389
+ continue;
1390
+ }
1391
+ const fieldMatch = line.match(/(?:private\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*;/);
1392
+ if (fieldMatch) {
1393
+ const fieldType = fieldMatch[1];
1394
+ const fieldName = fieldMatch[2];
1395
+ let required = true;
1396
+ const validations = [];
1397
+ const decorators = [];
1398
+ for (let j = Math.max(0, i - 3); j < i; j++) {
1399
+ const al = lines[j].trim();
1400
+ if (/^@\w+/.test(al)) {
1401
+ decorators.push(al.slice(0, 80));
1402
+ if (/@(NotNull|NotBlank|NotEmpty|Valid)/.test(al))
1403
+ validations.push(al.slice(0, 60));
1404
+ if (/@(Nullable|Optional)/.test(al))
1405
+ required = false;
1406
+ }
1407
+ }
1408
+ // 查找对应的 getter 是否有 @NotNull
1409
+ for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
1410
+ if (lines[j].includes(`get${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}()`)) {
1411
+ for (let k = Math.max(0, j - 3); k < j; k++) {
1412
+ if (/^@\w+/.test(lines[k].trim())) {
1413
+ decorators.push(lines[k].trim().slice(0, 80));
1414
+ }
1415
+ }
1416
+ break;
1417
+ }
1418
+ }
1419
+ // 如果已经有这个 DTO,追加字段
1420
+ const existing = dtos.find(d => d.name === currentClass);
1421
+ if (existing) {
1422
+ existing.fields.push({ name: fieldName, type: fieldType, required, decorators: decorators.length > 0 ? decorators : undefined, validationRules: validations.length > 0 ? validations : undefined });
1423
+ }
1424
+ else {
1425
+ dtos.push({
1426
+ name: currentClass,
1427
+ filePath: relPath,
1428
+ line: classLine,
1429
+ fields: [{ name: fieldName, type: fieldType, required, decorators: decorators.length > 0 ? decorators : undefined, validationRules: validations.length > 0 ? validations : undefined }],
1430
+ });
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+ return dtos.filter(d => d.fields.length > 0);
1436
+ }
1437
+ function extractServicesFromJavaFile(content, relPath) {
1438
+ const services = [];
1439
+ const lines = content.split('\n');
1440
+ let currentClass = null;
1441
+ let classLine = 0;
1442
+ let inClass = false;
1443
+ let braceDepth = 0;
1444
+ let injects = [];
1445
+ for (let i = 0; i < lines.length; i++) {
1446
+ const line = lines[i].trim();
1447
+ const classMatch = line.match(/(?:public\s+)?class\s+(\w+)/);
1448
+ if (classMatch) {
1449
+ currentClass = classMatch[1];
1450
+ classLine = i + 1;
1451
+ inClass = true;
1452
+ braceDepth = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
1453
+ injects = [];
1454
+ const isServiceLike = /(Service|Repository|Manager|Provider|Handler|Facade|Gateway|Impl)/i.test(currentClass);
1455
+ if (!isServiceLike) {
1456
+ currentClass = null;
1457
+ inClass = false;
1458
+ continue;
1459
+ }
1460
+ continue;
1461
+ }
1462
+ if (inClass && currentClass) {
1463
+ braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
1464
+ if (braceDepth <= 0) {
1465
+ if (injects.length > 0 || services.find(s => s.name === currentClass)?.methods.length) {
1466
+ const svc = services.find(s => s.name === currentClass);
1467
+ if (svc)
1468
+ svc.injects = injects.length > 0 ? injects : undefined;
1469
+ }
1470
+ inClass = false;
1471
+ currentClass = null;
1472
+ injects = [];
1473
+ continue;
1474
+ }
1475
+ // 构造函数注入
1476
+ const ctorMatch = line.match(/public\s+\w+\s*\(([^)]*)\)/);
1477
+ if (ctorMatch) {
1478
+ const paramStr = ctorMatch[1];
1479
+ const parts = paramStr.split(',');
1480
+ for (const p of parts) {
1481
+ const pm = p.trim().match(/(\w+(?:<[^>]+>)?)\s+(\w+)/);
1482
+ if (pm && /(Service|Repository|Manager|Provider|Client|Dao|Gateway)/i.test(pm[1])) {
1483
+ injects.push(pm[1]);
1484
+ }
1485
+ }
1486
+ }
1487
+ // 方法提取
1488
+ const methodMatch = line.match(/(?:public|protected)\s+(?:\w+(?:<[^>]+>)?\s+)?(\w+)\s*\(([^)]*)\)/);
1489
+ if (methodMatch && !/^(if|while|for|switch|return|class|interface|enum)/.test(methodMatch[1])) {
1490
+ const methodName = methodMatch[1];
1491
+ const params = [];
1492
+ const paramStr = methodMatch[2];
1493
+ if (paramStr) {
1494
+ const parts = paramStr.split(',');
1495
+ for (const p of parts) {
1496
+ const pm = p.trim().match(/(?:@\w+(?:\([^)]*\))?\s+)*(\w+(?:<[^>]+>)?)\s+(\w+)/);
1497
+ if (pm)
1498
+ params.push({ name: pm[2], type: pm[1] });
1499
+ }
1500
+ }
1501
+ const existing = services.find(s => s.name === currentClass);
1502
+ if (existing) {
1503
+ existing.methods.push({ name: methodName, parameters: params, line: i + 1 });
1504
+ }
1505
+ else {
1506
+ services.push({
1507
+ name: currentClass,
1508
+ filePath: relPath,
1509
+ line: classLine,
1510
+ methods: [{ name: methodName, parameters: params, line: i + 1 }],
1511
+ injects: injects.length > 0 ? injects : undefined,
1512
+ });
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ return services.filter(s => s.methods.length > 0);
1518
+ }
1519
+ // ── Python DTO / Service 提取 ──
1520
+ function extractDtosFromPythonFile(content, relPath) {
1521
+ const dtos = [];
1522
+ const lines = content.split('\n');
1523
+ for (let i = 0; i < lines.length; i++) {
1524
+ const line = lines[i];
1525
+ const classMatch = line.match(/class\s+(\w+)\s*\(([^)]*)\)/);
1526
+ if (classMatch) {
1527
+ const className = classMatch[1];
1528
+ const bases = classMatch[2];
1529
+ const isDtoLike = /(BaseModel|Schema|Dto|Input|Output|Request|Response|Form)/i.test(bases);
1530
+ if (!isDtoLike)
1531
+ continue;
1532
+ const fields = [];
1533
+ for (let j = i + 1; j < lines.length; j++) {
1534
+ const l = lines[j];
1535
+ if (l.trim() && !l.startsWith(' ') && !l.startsWith('\t') && !l.startsWith('#'))
1536
+ break;
1537
+ const fieldMatch = l.match(/^\s+(\w+)\s*:\s*(\w+(?:\[[^\]]+\])?)\s*(?:=\s*(.+))?/);
1538
+ if (fieldMatch) {
1539
+ const fieldName = fieldMatch[1];
1540
+ const fieldType = fieldMatch[2];
1541
+ const defaultExpr = fieldMatch[3];
1542
+ let required = true;
1543
+ const validations = [];
1544
+ if (defaultExpr) {
1545
+ if (/Field\s*\(/.test(defaultExpr)) {
1546
+ if (/default\s*=/.test(defaultExpr))
1547
+ required = false;
1548
+ }
1549
+ else if (defaultExpr.trim() !== '...') {
1550
+ required = false;
1551
+ }
1552
+ }
1553
+ fields.push({ name: fieldName, type: fieldType, required, validationRules: validations.length > 0 ? validations : undefined });
1554
+ }
1555
+ }
1556
+ if (fields.length > 0) {
1557
+ dtos.push({ name: className, filePath: relPath, line: i + 1, fields });
1558
+ }
1559
+ }
1560
+ }
1561
+ return dtos;
1562
+ }
1563
+ function extractServicesFromPythonFile(content, relPath) {
1564
+ const services = [];
1565
+ const lines = content.split('\n');
1566
+ for (let i = 0; i < lines.length; i++) {
1567
+ const line = lines[i];
1568
+ const classMatch = line.match(/class\s+(\w+)\s*\(/);
1569
+ if (classMatch) {
1570
+ const className = classMatch[1];
1571
+ const isServiceLike = /(Service|Repository|Manager|Provider|Handler|Facade|Gateway)/i.test(className);
1572
+ if (!isServiceLike)
1573
+ continue;
1574
+ const methods = [];
1575
+ for (let j = i + 1; j < lines.length; j++) {
1576
+ const l = lines[j];
1577
+ if (l.trim() && !l.startsWith(' ') && !l.startsWith('\t') && !l.startsWith('#'))
1578
+ break;
1579
+ const methodMatch = l.match(/def\s+(\w+)\s*\(([^)]*)\)/);
1580
+ if (methodMatch) {
1581
+ const methodName = methodMatch[1];
1582
+ if (methodName.startsWith('_'))
1583
+ continue;
1584
+ const params = [];
1585
+ const paramStr = methodMatch[2];
1586
+ if (paramStr) {
1587
+ const parts = paramStr.split(',');
1588
+ for (const p of parts) {
1589
+ const pt = p.trim();
1590
+ if (!pt || pt === 'self' || pt === 'cls')
1591
+ continue;
1592
+ const pm = pt.match(/(\w+)\s*:\s*(.+)/);
1593
+ if (pm)
1594
+ params.push({ name: pm[1], type: pm[2] });
1595
+ else {
1596
+ const sm = pt.match(/(\w+)/);
1597
+ if (sm)
1598
+ params.push({ name: sm[1] });
1599
+ }
1600
+ }
1601
+ }
1602
+ methods.push({ name: methodName, parameters: params, line: j + 1 });
1603
+ }
1604
+ }
1605
+ if (methods.length > 0) {
1606
+ services.push({ name: className, filePath: relPath, line: i + 1, methods });
1607
+ }
1608
+ }
1609
+ }
1610
+ return services;
1611
+ }
1612
+ // ── Go DTO / Service 提取 ──
1613
+ function extractDtosFromGoFile(content, relPath) {
1614
+ const dtos = [];
1615
+ const lines = content.split('\n');
1616
+ for (let i = 0; i < lines.length; i++) {
1617
+ const line = lines[i];
1618
+ const structMatch = line.match(/type\s+(\w+)\s+struct\s*\{/);
1619
+ if (structMatch) {
1620
+ const structName = structMatch[1];
1621
+ const isDtoLike = /(DTO|VO|Request|Response|Input|Output|Query|Command|Form|Param)/i.test(structName);
1622
+ if (!isDtoLike)
1623
+ continue;
1624
+ const fields = [];
1625
+ let inStruct = false;
1626
+ let braceDepth = 0;
1627
+ for (let j = i; j < lines.length; j++) {
1628
+ const l = lines[j];
1629
+ if (!inStruct) {
1630
+ if (l.includes('{'))
1631
+ inStruct = true;
1632
+ continue;
1633
+ }
1634
+ braceDepth += (l.match(/\{/g) || []).length - (l.match(/\}/g) || []).length;
1635
+ if (braceDepth < 0)
1636
+ break;
1637
+ const trimmed = l.trim();
1638
+ const fieldMatch = trimmed.match(/^(\w+)\s+(\S+(?:\[[^\]]+\])?)\s*(?:`([^`]+)`)?/);
1639
+ if (fieldMatch && !trimmed.startsWith('//')) {
1640
+ const fieldName = fieldMatch[1];
1641
+ const fieldType = fieldMatch[2];
1642
+ const tagStr = fieldMatch[3] || '';
1643
+ let required = true;
1644
+ const jsonMatch = tagStr.match(/json:"([^"]+)"/);
1645
+ if (jsonMatch && jsonMatch[1].includes('omitempty'))
1646
+ required = false;
1647
+ if (jsonMatch && jsonMatch[1].startsWith('-'))
1648
+ continue; // 跳过不序列化的字段
1649
+ const validateMatch = tagStr.match(/validate:"([^"]+)"/);
1650
+ const validations = validateMatch ? validateMatch[1].split(',').map(s => s.trim()) : [];
1651
+ fields.push({ name: fieldName, type: fieldType, required, validationRules: validations.length > 0 ? validations : undefined });
1652
+ }
1653
+ }
1654
+ if (fields.length > 0) {
1655
+ dtos.push({ name: structName, filePath: relPath, line: i + 1, fields });
1656
+ }
1657
+ }
1658
+ }
1659
+ return dtos;
1660
+ }
1661
+ function extractServicesFromGoFile(content, relPath) {
1662
+ const services = [];
1663
+ const lines = content.split('\n');
1664
+ for (let i = 0; i < lines.length; i++) {
1665
+ const line = lines[i];
1666
+ const structMatch = line.match(/type\s+(\w+)\s+struct\s*\{/);
1667
+ if (structMatch) {
1668
+ const structName = structMatch[1];
1669
+ const isServiceLike = /(Service|Repository|Manager|Provider|Handler|Facade|Gateway|Impl)/i.test(structName);
1670
+ if (!isServiceLike)
1671
+ continue;
1672
+ const methods = [];
1673
+ for (let j = 0; j < lines.length; j++) {
1674
+ const l = lines[j];
1675
+ const methodMatch = l.match(/func\s+\(\s*\w+\s*\*?\s*\)?\s*\)?\s*(\w+)\s*\(([^)]*)\)/);
1676
+ if (methodMatch) {
1677
+ const methodName = methodMatch[1];
1678
+ if (methodName.startsWith('_'))
1679
+ continue;
1680
+ const params = [];
1681
+ const paramStr = methodMatch[2];
1682
+ if (paramStr) {
1683
+ const parts = paramStr.split(',');
1684
+ for (const p of parts) {
1685
+ const pt = p.trim();
1686
+ if (!pt)
1687
+ continue;
1688
+ const pm = pt.match(/(\w+)\s+(\S+)/);
1689
+ if (pm)
1690
+ params.push({ name: pm[1], type: pm[2] });
1691
+ }
1692
+ }
1693
+ methods.push({ name: methodName, parameters: params, line: j + 1 });
1694
+ }
1695
+ }
1696
+ if (methods.length > 0) {
1697
+ services.push({ name: structName, filePath: relPath, line: i + 1, methods });
1698
+ }
1699
+ }
1700
+ }
1701
+ return services;
1702
+ }
428
1703
  //# sourceMappingURL=structured-extractor.js.map