speccore 5.19.2 → 5.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.en.md +63 -210
  2. package/README.md +108 -252
  3. package/dist/cli.js +224 -300
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analyze.d.ts +5 -0
  6. package/dist/commands/analyze.d.ts.map +1 -1
  7. package/dist/commands/analyze.js +235 -543
  8. package/dist/commands/analyze.js.map +1 -1
  9. package/dist/commands/ask.d.ts +8 -0
  10. package/dist/commands/ask.d.ts.map +1 -0
  11. package/dist/commands/ask.js +293 -0
  12. package/dist/commands/ask.js.map +1 -0
  13. package/dist/commands/bugfix.d.ts +4 -0
  14. package/dist/commands/bugfix.d.ts.map +1 -1
  15. package/dist/commands/bugfix.js +122 -2
  16. package/dist/commands/bugfix.js.map +1 -1
  17. package/dist/commands/change.d.ts +2 -0
  18. package/dist/commands/change.d.ts.map +1 -1
  19. package/dist/commands/change.js +37 -2
  20. package/dist/commands/change.js.map +1 -1
  21. package/dist/commands/completion.js +1 -1
  22. package/dist/commands/completion.js.map +1 -1
  23. package/dist/commands/dev.d.ts +2 -0
  24. package/dist/commands/dev.d.ts.map +1 -1
  25. package/dist/commands/dev.js +175 -1
  26. package/dist/commands/dev.js.map +1 -1
  27. package/dist/commands/doc2spec.d.ts +10 -0
  28. package/dist/commands/doc2spec.d.ts.map +1 -0
  29. package/dist/commands/{word2spec.js → doc2spec.js} +55 -19
  30. package/dist/commands/doc2spec.js.map +1 -0
  31. package/dist/commands/done.d.ts.map +1 -1
  32. package/dist/commands/done.js +47 -0
  33. package/dist/commands/done.js.map +1 -1
  34. package/dist/commands/execute.d.ts +1 -0
  35. package/dist/commands/execute.d.ts.map +1 -1
  36. package/dist/commands/execute.js +118 -11
  37. package/dist/commands/execute.js.map +1 -1
  38. package/dist/commands/help-quick.js +2 -2
  39. package/dist/commands/help-quick.js.map +1 -1
  40. package/dist/commands/help.d.ts +3 -2
  41. package/dist/commands/help.d.ts.map +1 -1
  42. package/dist/commands/help.js +372 -51
  43. package/dist/commands/help.js.map +1 -1
  44. package/dist/commands/import.d.ts +1 -0
  45. package/dist/commands/import.d.ts.map +1 -1
  46. package/dist/commands/import.js +289 -15
  47. package/dist/commands/import.js.map +1 -1
  48. package/dist/commands/init.d.ts +0 -1
  49. package/dist/commands/init.d.ts.map +1 -1
  50. package/dist/commands/init.js +177 -283
  51. package/dist/commands/init.js.map +1 -1
  52. package/dist/commands/iteration/create.d.ts +1 -0
  53. package/dist/commands/iteration/create.d.ts.map +1 -1
  54. package/dist/commands/iteration/create.js +1 -0
  55. package/dist/commands/iteration/create.js.map +1 -1
  56. package/dist/commands/iteration/split.d.ts +1 -0
  57. package/dist/commands/iteration/split.d.ts.map +1 -1
  58. package/dist/commands/iteration/split.js +240 -7
  59. package/dist/commands/iteration/split.js.map +1 -1
  60. package/dist/commands/plan.d.ts +1 -0
  61. package/dist/commands/plan.d.ts.map +1 -1
  62. package/dist/commands/plan.js +25 -1
  63. package/dist/commands/plan.js.map +1 -1
  64. package/dist/commands/pr.d.ts +1 -0
  65. package/dist/commands/pr.d.ts.map +1 -1
  66. package/dist/commands/pr.js +94 -22
  67. package/dist/commands/pr.js.map +1 -1
  68. package/dist/commands/schedule.d.ts +27 -0
  69. package/dist/commands/schedule.d.ts.map +1 -0
  70. package/dist/commands/schedule.js +203 -0
  71. package/dist/commands/schedule.js.map +1 -0
  72. package/dist/commands/status-panel.d.ts +8 -1
  73. package/dist/commands/status-panel.d.ts.map +1 -1
  74. package/dist/commands/status-panel.js +1095 -5
  75. package/dist/commands/status-panel.js.map +1 -1
  76. package/dist/commands/task/new.d.ts +4 -0
  77. package/dist/commands/task/new.d.ts.map +1 -1
  78. package/dist/commands/task/new.js +87 -0
  79. package/dist/commands/task/new.js.map +1 -1
  80. package/dist/core/analyze-engine.d.ts +32 -0
  81. package/dist/core/analyze-engine.d.ts.map +1 -0
  82. package/dist/core/analyze-engine.js +935 -0
  83. package/dist/core/analyze-engine.js.map +1 -0
  84. package/dist/core/capabilities.d.ts +12 -0
  85. package/dist/core/capabilities.d.ts.map +1 -0
  86. package/dist/core/capabilities.js +58 -0
  87. package/dist/core/capabilities.js.map +1 -0
  88. package/dist/core/code-scanner.d.ts.map +1 -1
  89. package/dist/core/code-scanner.js +4 -1
  90. package/dist/core/code-scanner.js.map +1 -1
  91. package/dist/core/intent-recognition.d.ts +1 -1
  92. package/dist/core/intent-recognition.d.ts.map +1 -1
  93. package/dist/core/intent-recognition.js +5 -5
  94. package/dist/core/intent-recognition.js.map +1 -1
  95. package/dist/core/next-steps.js +1 -1
  96. package/dist/core/next-steps.js.map +1 -1
  97. package/dist/core/schedule-engine.d.ts +25 -0
  98. package/dist/core/schedule-engine.d.ts.map +1 -0
  99. package/dist/core/schedule-engine.js +179 -0
  100. package/dist/core/schedule-engine.js.map +1 -0
  101. package/dist/core/schedule-store.d.ts +104 -0
  102. package/dist/core/schedule-store.d.ts.map +1 -0
  103. package/dist/core/schedule-store.js +188 -0
  104. package/dist/core/schedule-store.js.map +1 -0
  105. package/dist/core/schemas/iteration.schema.d.ts +4 -4
  106. package/dist/core/schemas/task.schema.d.ts +2 -2
  107. package/package.json +7 -13
  108. package/dist/commands/demo.d.ts +0 -10
  109. package/dist/commands/demo.d.ts.map +0 -1
  110. package/dist/commands/demo.js +0 -151
  111. package/dist/commands/demo.js.map +0 -1
  112. package/dist/commands/goal.d.ts +0 -15
  113. package/dist/commands/goal.d.ts.map +0 -1
  114. package/dist/commands/goal.js +0 -232
  115. package/dist/commands/goal.js.map +0 -1
  116. package/dist/commands/new-task.d.ts +0 -15
  117. package/dist/commands/new-task.d.ts.map +0 -1
  118. package/dist/commands/new-task.js +0 -208
  119. package/dist/commands/new-task.js.map +0 -1
  120. package/dist/commands/platform-add.d.ts +0 -12
  121. package/dist/commands/platform-add.d.ts.map +0 -1
  122. package/dist/commands/platform-add.js +0 -111
  123. package/dist/commands/platform-add.js.map +0 -1
  124. package/dist/commands/retro.d.ts +0 -10
  125. package/dist/commands/retro.d.ts.map +0 -1
  126. package/dist/commands/retro.js +0 -131
  127. package/dist/commands/retro.js.map +0 -1
  128. package/dist/commands/spec.d.ts +0 -8
  129. package/dist/commands/spec.d.ts.map +0 -1
  130. package/dist/commands/spec.js +0 -159
  131. package/dist/commands/spec.js.map +0 -1
  132. package/dist/commands/template-add.d.ts +0 -12
  133. package/dist/commands/template-add.d.ts.map +0 -1
  134. package/dist/commands/template-add.js +0 -65
  135. package/dist/commands/template-add.js.map +0 -1
  136. package/dist/commands/word2spec.d.ts +0 -8
  137. package/dist/commands/word2spec.d.ts.map +0 -1
  138. package/dist/commands/word2spec.js.map +0 -1
@@ -0,0 +1,935 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAnalysis = runAnalysis;
4
+ /**
5
+ * analyze-engine — 统一分析引擎
6
+ *
7
+ * 支持三种输入模式 + 三种输出范围:
8
+ *
9
+ * 输入:
10
+ * - 纯需求: --req docs/a.md docs/b.md
11
+ * - 纯代码: --src backend/src frontend/src
12
+ * - 需求+代码: 两者都指定
13
+ *
14
+ * 输出范围 (scope):
15
+ * - global → .speccore/GLOBAL/ 全局架构/代码健康
16
+ * - iteration → 期次-XX/00-需求文档/ (默认)
17
+ * - task → 期次-XX/Task-NN/ 单任务深化
18
+ */
19
+ const fs_extra_1 = require("fs-extra");
20
+ const path_1 = require("path");
21
+ const logger_1 = require("../utils/logger");
22
+ const code_scanner_1 = require("./code-scanner");
23
+ // ================================================================
24
+ // 主入口
25
+ // ================================================================
26
+ async function runAnalysis(input) {
27
+ // 深拷贝输入,避免静默修改调用者的对象
28
+ let requirements = [...input.requirements];
29
+ let sources = [...input.sources];
30
+ // 校验: task scope 必须同时提供 iteration 和 taskId
31
+ if (input.scope === 'task' && (!input.taskId || !input.iteration)) {
32
+ throw new Error('--scope task 必须同时指定 --task <id> 和 -I <iteration>');
33
+ }
34
+ // 自动检测: 都没指定 → 用默认值
35
+ if (requirements.length === 0 && sources.length === 0) {
36
+ if (input.scope === 'iteration' && input.iteration) {
37
+ const defaultReq = (0, path_1.join)(`期次-${input.iteration}`, '00-需求文档', 'REQUIREMENT.md');
38
+ if (await (0, fs_extra_1.pathExists)(defaultReq)) {
39
+ requirements = [defaultReq];
40
+ }
41
+ }
42
+ // 默认源码: 扫描项目常见目录
43
+ const defaultSrcDirs = ['src', 'app', 'lib', 'services', 'controllers', 'models'];
44
+ sources = [];
45
+ for (const d of defaultSrcDirs) {
46
+ if (await (0, fs_extra_1.pathExists)(d))
47
+ sources.push(d);
48
+ }
49
+ }
50
+ // 构建实际使用的输入(不修改原对象)
51
+ const effectiveInput = {
52
+ ...input,
53
+ sources,
54
+ requirements,
55
+ };
56
+ // 判别分析类型(使用自动检测后的值)
57
+ const hasReqs = effectiveInput.requirements.length > 0;
58
+ const hasSrc = effectiveInput.sources.length > 0;
59
+ const type = hasReqs && hasSrc ? 'combined' : hasReqs ? 'req' : 'code';
60
+ switch (type) {
61
+ case 'req': return analyzeRequirements(effectiveInput);
62
+ case 'code': return analyzeCodebase(effectiveInput);
63
+ case 'combined': return analyzeCombined(effectiveInput);
64
+ default: throw new Error(`Unknown analysis type: ${type}`);
65
+ }
66
+ }
67
+ // ================================================================
68
+ // 1. 纯需求分析
69
+ // ================================================================
70
+ async function analyzeRequirements(input) {
71
+ const allContent = [];
72
+ for (const reqPath of input.requirements) {
73
+ if (await (0, fs_extra_1.pathExists)(reqPath)) {
74
+ const content = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
75
+ allContent.push(`## 来源: ${(0, path_1.basename)(reqPath)}\n\n${content}`);
76
+ }
77
+ }
78
+ if (allContent.length === 0) {
79
+ throw new Error('未找到有效的需求文档');
80
+ }
81
+ const fullContent = allContent.join('\n\n---\n\n');
82
+ const issues = scanCompleteness(fullContent);
83
+ const archImpact = await analyzeArchitectureImpact(fullContent);
84
+ let outputPath;
85
+ let report;
86
+ if (input.scope === 'global') {
87
+ outputPath = (0, path_1.join)('.speccore', 'GLOBAL', input.output || 'REQ_CONSISTENCY.md');
88
+ report = buildReqConsistencyReport(input, issues, archImpact);
89
+ }
90
+ else if (input.scope === 'task') {
91
+ // task scope — 已在入口校验
92
+ outputPath = (0, path_1.join)(`期次-${input.iteration}`, input.taskId, 'backend', input.output || 'ANALYSIS.md');
93
+ report = buildTaskReqReport(input, issues, archImpact);
94
+ }
95
+ else {
96
+ // iteration (default)
97
+ const iterDir = `期次-${input.iteration || 'current'}`;
98
+ outputPath = (0, path_1.join)(iterDir, '00-需求文档', input.output || 'ANALYSIS.md');
99
+ report = buildIterationReqReport(input, issues, archImpact);
100
+ }
101
+ return {
102
+ outputPath,
103
+ report,
104
+ summary: {
105
+ issues: issues.length,
106
+ blockers: issues.filter(i => i.severity === 'blocker').length,
107
+ filesAnalyzed: input.requirements.length,
108
+ apisFound: archImpact.apis?.length || 0,
109
+ risks: archImpact.risks?.length || 0,
110
+ },
111
+ };
112
+ }
113
+ // ================================================================
114
+ // 2. 纯代码分析
115
+ // ================================================================
116
+ async function analyzeCodebase(input) {
117
+ // 扫描源码目录
118
+ const fileStats = await scanSourceDirs(input.sources, input.depth);
119
+ const apiInventory = await buildApiInventory(input.sources);
120
+ const hotspots = await detectHotspots(input.sources);
121
+ const deps = input.depth !== 'quick' ? await analyzeDependencies(input.sources) : null;
122
+ let outputPath;
123
+ let report;
124
+ if (input.scope === 'global') {
125
+ outputPath = (0, path_1.join)('.speccore', 'GLOBAL', input.output || 'CODE_HEALTH.md');
126
+ report = buildCodeHealthReport(input, fileStats, apiInventory, hotspots, deps);
127
+ }
128
+ else if (input.scope === 'task') {
129
+ // task scope — 已在入口校验
130
+ outputPath = (0, path_1.join)(`期次-${input.iteration}`, input.taskId, 'backend', input.output || 'CODE_REVIEW.md');
131
+ report = buildTaskCodeReport(input, fileStats, apiInventory, hotspots);
132
+ }
133
+ else {
134
+ // iteration (default)
135
+ const iterDir = `期次-${input.iteration || 'current'}`;
136
+ outputPath = (0, path_1.join)(iterDir, '00-需求文档', input.output || 'CODE_ANALYSIS.md');
137
+ report = buildIterationCodeReport(input, fileStats, apiInventory, hotspots, deps);
138
+ }
139
+ return {
140
+ outputPath,
141
+ report,
142
+ summary: {
143
+ issues: hotspots.length,
144
+ blockers: hotspots.filter(h => h.severity === 'high').length,
145
+ filesAnalyzed: fileStats.totalFiles,
146
+ apisFound: apiInventory.length,
147
+ risks: deps?.cycles?.length || 0,
148
+ },
149
+ };
150
+ }
151
+ // ================================================================
152
+ // 3. 需求+代码联合分析
153
+ // ================================================================
154
+ async function analyzeCombined(input) {
155
+ // 先做需求分析
156
+ const allContent = [];
157
+ for (const reqPath of input.requirements) {
158
+ if (await (0, fs_extra_1.pathExists)(reqPath)) {
159
+ allContent.push(await (0, fs_extra_1.readFile)(reqPath, 'utf-8'));
160
+ }
161
+ }
162
+ const fullReqContent = allContent.join('\n\n---\n\n');
163
+ const issues = scanCompleteness(fullReqContent);
164
+ const archImpact = await analyzeArchitectureImpact(fullReqContent);
165
+ // 代码分析
166
+ const fileStats = await scanSourceDirs(input.sources, input.depth);
167
+ const apiInventory = await buildApiInventory(input.sources);
168
+ // 需求-代码对标
169
+ if (await (0, code_scanner_1.isIndexStale)()) {
170
+ await (0, code_scanner_1.buildCodeIndex)();
171
+ }
172
+ // 分别对每个源码目录和每个需求做对标
173
+ const codeMapEntries = [];
174
+ for (const content of allContent) {
175
+ const rawMatches = await (0, code_scanner_1.findRelevantCode)(content, 20);
176
+ for (const m of rawMatches) {
177
+ codeMapEntries.push({
178
+ sourceFile: m.file,
179
+ apis: m.apis,
180
+ exports: m.exports.slice(0, 5),
181
+ relevanceScore: m.score,
182
+ });
183
+ }
184
+ }
185
+ // 读取相关源码(deep 模式)
186
+ let sourceContents = {};
187
+ if (input.depth === 'deep') {
188
+ const rawMatches = await (0, code_scanner_1.findRelevantCode)(fullReqContent, 15);
189
+ sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, 80000);
190
+ }
191
+ let outputPath;
192
+ let report;
193
+ if (input.scope === 'global') {
194
+ outputPath = (0, path_1.join)('.speccore', 'GLOBAL', input.output || 'ARCH_IMPACT.md');
195
+ report = buildGlobalArchImpactReport(input, issues, archImpact, codeMapEntries, fileStats);
196
+ }
197
+ else if (input.scope === 'task') {
198
+ // task scope — 已在入口校验 taskId + iteration 必填
199
+ outputPath = (0, path_1.join)(`期次-${input.iteration}`, input.taskId, 'backend', input.output || 'ANALYSIS.md');
200
+ report = buildTaskCombinedReport(input, issues, archImpact, codeMapEntries, sourceContents);
201
+ }
202
+ else {
203
+ // iteration (default)
204
+ const iterDir = `期次-${input.iteration || 'current'}`;
205
+ outputPath = (0, path_1.join)(iterDir, '00-需求文档', input.output || 'ANALYSIS.md');
206
+ report = buildIterationCombinedReport(input, issues, archImpact, codeMapEntries, fileStats, apiInventory);
207
+ }
208
+ return {
209
+ outputPath,
210
+ report,
211
+ summary: {
212
+ issues: issues.length,
213
+ blockers: issues.filter(i => i.severity === 'blocker').length,
214
+ filesAnalyzed: fileStats.totalFiles,
215
+ apisFound: apiInventory.length,
216
+ risks: archImpact.risks?.length || 0,
217
+ },
218
+ };
219
+ }
220
+ // ================================================================
221
+ // 源码扫描
222
+ // ================================================================
223
+ /** 文件扩展名 → 语言名映射 (模块级常量, 避免重复创建) */
224
+ const LANG_MAP = {
225
+ ts: 'TypeScript', tsx: 'TSX', js: 'JavaScript', jsx: 'JSX',
226
+ py: 'Python', java: 'Java', go: 'Go', rs: 'Rust',
227
+ vue: 'Vue', sql: 'SQL', yaml: 'YAML', yml: 'YAML',
228
+ json: 'JSON', xml: 'XML', css: 'CSS', scss: 'SCSS',
229
+ html: 'HTML', sh: 'Shell', md: 'Markdown',
230
+ };
231
+ async function scanSourceDirs(sources, depth) {
232
+ const stats = {
233
+ totalFiles: 0,
234
+ totalLines: 0,
235
+ byLanguage: {},
236
+ byDir: {},
237
+ largestFiles: [],
238
+ };
239
+ const allFiles = [];
240
+ for (const srcDir of sources) {
241
+ if (!(await (0, fs_extra_1.pathExists)(srcDir)))
242
+ continue;
243
+ await walkDir(srcDir, srcDir, async (filePath) => {
244
+ const relPath = (0, path_1.relative)(process.cwd(), filePath);
245
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
246
+ const lang = LANG_MAP[ext];
247
+ if (!lang)
248
+ return;
249
+ try {
250
+ const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
251
+ const lines = content.split('\n').length;
252
+ const dir = srcDir.split('/').pop() || srcDir;
253
+ stats.totalFiles++;
254
+ stats.totalLines += lines;
255
+ stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
256
+ stats.byDir[dir] = (stats.byDir[dir] || 0) + 1;
257
+ allFiles.push({ path: relPath, lines, lang, dir });
258
+ }
259
+ catch {
260
+ // 跳过二进制
261
+ }
262
+ }, depth === 'quick' ? 2 : depth === 'deep' ? 8 : 5);
263
+ }
264
+ // 最大文件
265
+ stats.largestFiles = allFiles
266
+ .sort((a, b) => b.lines - a.lines)
267
+ .slice(0, 10)
268
+ .map(f => ({ path: f.path, lines: f.lines }));
269
+ return stats;
270
+ }
271
+ async function walkDir(root, dir, callback, maxDepth = 5, visited = new Set()) {
272
+ // 符号链接循环保护
273
+ const realPath = (0, path_1.join)(dir);
274
+ if (visited.has(realPath))
275
+ return;
276
+ visited.add(realPath);
277
+ try {
278
+ const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
279
+ for (const entry of entries) {
280
+ const fullPath = (0, path_1.join)(dir, entry.name);
281
+ if (entry.name.startsWith('.') || ['node_modules', 'dist', 'build', 'target', '__pycache__', '.git'].includes(entry.name))
282
+ continue;
283
+ if (entry.isDirectory()) {
284
+ // 使用 path.relative 计算深度,避免尾部斜杠不一致问题
285
+ const relPath = (0, path_1.relative)(root, fullPath);
286
+ const curDepth = relPath.split('/').filter(Boolean).length;
287
+ if (curDepth < maxDepth)
288
+ await walkDir(root, fullPath, callback, maxDepth, visited);
289
+ }
290
+ else {
291
+ await callback(fullPath);
292
+ }
293
+ }
294
+ }
295
+ catch (err) {
296
+ // 区分权限错误和严重错误
297
+ const code = err?.code || '';
298
+ if (code === 'EACCES' || code === 'EPERM') {
299
+ // 权限不足: 静默跳过
300
+ }
301
+ else if (code === 'ENOENT' || code === 'ELOOP') {
302
+ // 文件已删除 / 符号链接循环: 静默跳过
303
+ }
304
+ else {
305
+ // 其他严重错误: 记录但不中断
306
+ logger_1.logger.debug(`⚠️ walkDir 错误 (${dir}): ${err.message}`);
307
+ }
308
+ }
309
+ }
310
+ async function buildApiInventory(sources) {
311
+ const apis = [];
312
+ const seen = new Set();
313
+ for (const srcDir of sources) {
314
+ if (!(await (0, fs_extra_1.pathExists)(srcDir)))
315
+ continue;
316
+ await walkDir(srcDir, srcDir, async (filePath) => {
317
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
318
+ if (!['ts', 'tsx', 'js', 'jsx', 'java', 'py', 'go'].includes(ext))
319
+ return;
320
+ try {
321
+ const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
322
+ const relPath = (0, path_1.relative)(process.cwd(), filePath);
323
+ // Java: @XXXMapping("/api/...")
324
+ const javaApis = content.match(/@\w*Mapping\s*\(\s*"(\/[^"]+)"/g) || [];
325
+ for (const m of javaApis) {
326
+ const method = (m.match(/@(Get|Post|Put|Delete|Patch)/)?.[1] || 'Request').toUpperCase();
327
+ const path = m.match(/"(\/[^"]+)"/)?.[1] || '';
328
+ const key = `${method} ${path}`;
329
+ if (!seen.has(key) && path) {
330
+ seen.add(key);
331
+ apis.push({ method, path, file: relPath });
332
+ }
333
+ }
334
+ // TS/JS: router.method('/api/...')
335
+ const tsApis = content.match(/(?:router|app|this)\.(get|post|put|delete|patch)\s*\(\s*['"](\/[^'"]+)['"]/gi) || [];
336
+ for (const m of tsApis) {
337
+ const method = (m.match(/\.(get|post|put|delete|patch)/i)?.[1] || 'get').toUpperCase();
338
+ const path = m.match(/['"](\/[^'"]+)['"]/)?.[1] || '';
339
+ const key = `${method} ${path}`;
340
+ if (!seen.has(key) && path) {
341
+ seen.add(key);
342
+ apis.push({ method, path, file: relPath });
343
+ }
344
+ }
345
+ // Python: @app.route('/api/...', methods=[...])
346
+ const pyApis = content.match(/@\w+\.route\s*\(\s*['"](\/[^'"]+)['"]/g) || [];
347
+ for (const m of pyApis) {
348
+ const path = m.match(/['"](\/[^'"]+)['"]/)?.[1] || '';
349
+ const key = `ANY ${path}`;
350
+ if (!seen.has(key) && path) {
351
+ seen.add(key);
352
+ apis.push({ method: 'ANY', path, file: relPath });
353
+ }
354
+ }
355
+ }
356
+ catch { /* skip */ }
357
+ }, 6);
358
+ }
359
+ return apis;
360
+ }
361
+ async function detectHotspots(sources) {
362
+ const hotspots = [];
363
+ for (const srcDir of sources) {
364
+ if (!(await (0, fs_extra_1.pathExists)(srcDir)))
365
+ continue;
366
+ await walkDir(srcDir, srcDir, async (filePath) => {
367
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
368
+ const codeExts = ['ts', 'tsx', 'js', 'jsx', 'java', 'py', 'go', 'vue'];
369
+ if (!codeExts.includes(ext))
370
+ return;
371
+ try {
372
+ const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
373
+ const lines = content.split('\n');
374
+ const relPath = (0, path_1.relative)(process.cwd(), filePath);
375
+ if (lines.length > 800) {
376
+ hotspots.push({ file: relPath, severity: 'high', reason: `超大文件 (${lines.length} 行)`, lines: lines.length });
377
+ }
378
+ else if (lines.length > 400) {
379
+ hotspots.push({ file: relPath, severity: 'medium', reason: `大文件 (${lines.length} 行)`, lines: lines.length });
380
+ }
381
+ // 检查 TODO/FIXME
382
+ const todos = lines.filter(l => /TODO|FIXME|HACK|XXX/.test(l)).length;
383
+ if (todos > 10) {
384
+ hotspots.push({ file: relPath, severity: 'medium', reason: `${todos} 个 TODO/FIXME`, lines: lines.length });
385
+ }
386
+ // 检查深层嵌套 (deep 模式)
387
+ if (lines.length > 100) {
388
+ let maxIndent = 0;
389
+ for (const line of lines) {
390
+ const indent = line.search(/\S/);
391
+ if (indent > maxIndent)
392
+ maxIndent = indent;
393
+ }
394
+ if (maxIndent > 60) {
395
+ hotspots.push({ file: relPath, severity: 'low', reason: `深层嵌套 (最大缩进 ${maxIndent})`, lines: lines.length });
396
+ }
397
+ }
398
+ }
399
+ catch { /* skip */ }
400
+ }, 6);
401
+ }
402
+ return hotspots.sort((a, b) => {
403
+ const sevOrder = { high: 0, medium: 1, low: 2 };
404
+ return sevOrder[a.severity] - sevOrder[b.severity];
405
+ }).slice(0, 30);
406
+ }
407
+ async function analyzeDependencies(sources) {
408
+ const imports = [];
409
+ const graph = new Map();
410
+ const allFiles = [];
411
+ for (const srcDir of sources) {
412
+ if (!(await (0, fs_extra_1.pathExists)(srcDir)))
413
+ continue;
414
+ await walkDir(srcDir, srcDir, async (filePath) => {
415
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
416
+ const codeExts = ['ts', 'tsx', 'js', 'jsx', 'java', 'py', 'go'];
417
+ if (!codeExts.includes(ext))
418
+ return;
419
+ try {
420
+ const content = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
421
+ const relPath = (0, path_1.relative)(process.cwd(), filePath);
422
+ allFiles.push(relPath);
423
+ // TS/JS imports — 只匹配项目内部引用 (相对路径)
424
+ const importMatches = content.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g) || [];
425
+ const depSet = new Set();
426
+ for (const m of importMatches) {
427
+ const from = m.match(/from\s+['"]([^'"]+)['"]/)?.[1] || '';
428
+ // 正向匹配: 只保留以 ./ 或 ../ 开头的本地引用
429
+ if (from && (from.startsWith('./') || from.startsWith('../'))) {
430
+ depSet.add(from);
431
+ }
432
+ }
433
+ if (depSet.size > 0) {
434
+ imports.push({ from: relPath, imports: [...depSet] });
435
+ if (!graph.has(relPath))
436
+ graph.set(relPath, new Set());
437
+ for (const d of depSet)
438
+ graph.get(relPath).add(d);
439
+ }
440
+ }
441
+ catch { /* skip */ }
442
+ }, 6);
443
+ }
444
+ // 简单循环依赖检测 (深度优先 2 层)
445
+ const cycles = [];
446
+ for (const [file, deps] of graph) {
447
+ for (const dep of deps) {
448
+ const depDeps = graph.get(dep);
449
+ if (depDeps?.has(file)) {
450
+ cycles.push([file, dep]);
451
+ }
452
+ }
453
+ }
454
+ // 入口点(被 import 最多的文件)
455
+ const importCount = new Map();
456
+ for (const { imports: imps } of imports) {
457
+ for (const imp of imps) {
458
+ importCount.set(imp, (importCount.get(imp) || 0) + 1);
459
+ }
460
+ }
461
+ const entryPoints = [...importCount.entries()]
462
+ .sort((a, b) => b[1] - a[1])
463
+ .slice(0, 5)
464
+ .map(([k, v]) => `${k} (被 ${v} 个文件引用)`);
465
+ return { imports, cycles, entryPoints };
466
+ }
467
+ function scanCompleteness(content) {
468
+ const issues = [];
469
+ const lines = content.split('\n');
470
+ let inTable = false;
471
+ let tableRowCount = 0;
472
+ const incompleteRows = [];
473
+ for (const line of lines) {
474
+ if (line.match(/^\|.*\|.*\|.*\|$/)) {
475
+ if (line.includes(':---')) {
476
+ inTable = true;
477
+ continue;
478
+ }
479
+ if (inTable) {
480
+ tableRowCount++;
481
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
482
+ if (cells.length < 3 || cells.some(c => !c)) {
483
+ incompleteRows.push(` - ${line.trim()}`);
484
+ }
485
+ }
486
+ }
487
+ else {
488
+ inTable = false;
489
+ }
490
+ }
491
+ if (tableRowCount === 0) {
492
+ issues.push({
493
+ severity: 'warning',
494
+ category: '接口定义',
495
+ message: '需求中未检测到接口表格。建议补充「方法 | 路径 | 说明」格式的接口定义。',
496
+ });
497
+ }
498
+ if (incompleteRows.length > 0) {
499
+ issues.push({
500
+ severity: 'blocker',
501
+ category: '接口定义',
502
+ message: `以下接口行缺少必填字段(方法/路径/说明):\n${incompleteRows.join('\n')}`,
503
+ });
504
+ }
505
+ const sections = content.match(/^(#{2,3})\s+(.+)$/gm) || [];
506
+ let prevIdx = 0;
507
+ for (let i = 0; i < sections.length; i++) {
508
+ const idx = content.indexOf(sections[i], prevIdx);
509
+ const nextIdx = i + 1 < sections.length
510
+ ? content.indexOf(sections[i + 1], idx + 1)
511
+ : content.length;
512
+ const sectionContent = content.substring(idx + sections[i].length, nextIdx).trim();
513
+ if (!sectionContent || sectionContent.length < 20) {
514
+ issues.push({
515
+ severity: 'warning',
516
+ category: '内容完整性',
517
+ message: `章节「${sections[i].replace(/^#+\s*/, '')}」内容过少(${sectionContent.length} 字符),可能需要补充`,
518
+ location: sections[i],
519
+ });
520
+ }
521
+ prevIdx = idx;
522
+ }
523
+ const hasInterfaces = content.includes('接口') || content.includes('API');
524
+ const hasDataModel = content.includes('数据模型') || content.includes('数据表') || content.includes('数据库');
525
+ if (!hasInterfaces) {
526
+ issues.push({ severity: 'warning', category: '内容完整性', message: '未找到接口定义章节。建议添加「接口定义」部分。' });
527
+ }
528
+ if (!hasDataModel) {
529
+ issues.push({ severity: 'info', category: '内容完整性', message: '未找到数据模型/数据表描述。如需数据库变更,建议补充。' });
530
+ }
531
+ return issues;
532
+ }
533
+ async function analyzeArchitectureImpact(content) {
534
+ const impact = { modules: [], newDependencies: [], risks: [], apis: [] };
535
+ const archPath = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL', 'ARCHITECTURE.md');
536
+ if (await (0, fs_extra_1.pathExists)(archPath)) {
537
+ const archContent = await (0, fs_extra_1.readFile)(archPath, 'utf-8');
538
+ if (archContent.match(/## 模块|## 系统边界|## 服务列表/)) {
539
+ impact.modules.push('需要对照 ARCHITECTURE.md 确认影响范围');
540
+ }
541
+ }
542
+ const apiCalls = content.match(/\/api\/[a-zA-Z0-9/-]+/g) || [];
543
+ impact.apis = [...new Set(apiCalls)];
544
+ // 新增依赖检测
545
+ if (/消息队列|MQ|Kafka/.test(content))
546
+ impact.newDependencies.push('消息队列');
547
+ if (/缓存|Redis|Cache/.test(content))
548
+ impact.newDependencies.push('缓存服务 (Redis)');
549
+ if (/OSS|对象存储|文件上传/.test(content))
550
+ impact.newDependencies.push('对象存储 (OSS/S3)');
551
+ if (/WebSocket|实时|推送/.test(content))
552
+ impact.newDependencies.push('WebSocket 实时通信');
553
+ if (/定时|调度|Cron/.test(content))
554
+ impact.newDependencies.push('定时任务调度');
555
+ if (/ES|全文检?索|Elasticsearch/.test(content))
556
+ impact.newDependencies.push('搜索引擎 (Elasticsearch)');
557
+ // 风险检测
558
+ if (/批量删除|批量操作/.test(content))
559
+ impact.risks.push('存在批量操作,需考虑事务一致性和性能');
560
+ if (/导出|报表/.test(content))
561
+ impact.risks.push('存在导出/报表功能,需考虑大数据量时的内存和超时');
562
+ if (/权限|角色|RBAC/.test(content))
563
+ impact.risks.push('涉及权限变更,需确认 RBAC 模型兼容性');
564
+ return impact;
565
+ }
566
+ // ================================================================
567
+ // 报告生成: 纯需求
568
+ // ================================================================
569
+ function buildReqConsistencyReport(input, issues, archImpact) {
570
+ const now = new Date().toISOString().split('T')[0];
571
+ let r = `# 全局需求一致性分析\n\n`;
572
+ r += `> 分析时间: ${now} | 需求文档: ${input.requirements.length} 个 | 模式: 全局\n\n`;
573
+ r += `---\n\n`;
574
+ r += `## 1. 需求覆盖\n\n`;
575
+ r += `| # | 文档 |\n| :--- | :--- |\n`;
576
+ for (let i = 0; i < input.requirements.length; i++) {
577
+ r += `| ${i + 1} | ${input.requirements[i]} |\n`;
578
+ }
579
+ r += `\n## 2. 完整性问题\n\n`;
580
+ r += issues.length === 0
581
+ ? `> ✅ 未发现完整性问题\n`
582
+ : `| 严重度 | 分类 | 问题 |\n| :--- | :--- | :--- |\n${issues.map(i => `| ${icon(i.severity)} ${i.severity} | ${i.category} | ${i.message.replace(/\n/g, '<br>')} |`).join('\n')}\n`;
583
+ r += `\n## 3. 跨需求一致性\n\n`;
584
+ r += `| 维度 | 状态 | 说明 |\n| :--- | :--- | :--- |\n`;
585
+ r += `| API 路径规范 | ⚠️ 待检查 | 请确认不同需求间 API 路径无冲突 |\n`;
586
+ r += `| 数据模型 | ⚠️ 待检查 | 请确认数据表/实体定义无冲突 |\n`;
587
+ r += `| 权限模型 | ⚠️ 待检查 | 请确认权限定义无冲突 |\n`;
588
+ r += `\n---\n\n## 4. 架构风险\n\n`;
589
+ for (const risk of archImpact.risks)
590
+ r += `- ⚠️ ${risk}\n`;
591
+ return r;
592
+ }
593
+ function buildIterationReqReport(input, issues, archImpact) {
594
+ const now = new Date().toISOString().split('T')[0];
595
+ const iter = input.iteration || 'current';
596
+ const blockerCount = issues.filter(i => i.severity === 'blocker').length;
597
+ let r = `# 需求分析报告\n\n`;
598
+ r += `> 期次: ${iter} | 分析时间: ${now} | 状态: ${blockerCount > 0 ? '🔴 有阻断' : '🟢 可拆分'}\n\n`;
599
+ r += `---\n\n`;
600
+ r += `## 1. 需求完整性检查\n\n`;
601
+ r += `| 严重度 | 分类 | 问题 |\n| :--- | :--- | :--- |\n`;
602
+ for (const issue of issues) {
603
+ r += `| ${icon(issue.severity)} ${issue.severity} | ${issue.category} | ${issue.message.replace(/\n/g, '<br>')} |\n`;
604
+ }
605
+ if (issues.length === 0)
606
+ r += `| ✅ | - | 未发现明显问题 |\n`;
607
+ r += `\n`;
608
+ r += `## 2. 架构影响\n\n`;
609
+ if (archImpact.apis.length > 0) {
610
+ r += `### 涉及接口\n`;
611
+ for (const a of archImpact.apis)
612
+ r += `- \`${a}\`\n`;
613
+ r += `\n`;
614
+ }
615
+ if (archImpact.newDependencies.length > 0) {
616
+ r += `### 新增依赖\n`;
617
+ for (const d of archImpact.newDependencies)
618
+ r += `- [ ] ${d}\n`;
619
+ r += `\n`;
620
+ }
621
+ if (archImpact.risks.length > 0) {
622
+ r += `### ⚠️ 风险提示\n`;
623
+ for (const rk of archImpact.risks)
624
+ r += `- ${rk}\n`;
625
+ r += `\n`;
626
+ }
627
+ r += `## 3. 待确认清单\n\n`;
628
+ for (const issue of issues.filter(i => i.severity !== 'info')) {
629
+ r += `- [ ] ${issue.message.replace(/\n/g, ' ').slice(0, 100)}\n`;
630
+ }
631
+ for (const rk of archImpact.risks)
632
+ r += `- [ ] ${rk}\n`;
633
+ if (archImpact.newDependencies.length > 0)
634
+ r += `- [ ] 确认新增依赖的引入方案和排期\n`;
635
+ r += `\n---\n\n## 4. 技术方案(待填写)\n\n`;
636
+ r += `| 模块 | 技术方案 | 负责人 | 预计工时 |\n| :--- | :--- | :--- | :--- |\n| | | | |\n\n`;
637
+ r += `### 数据库变更\n| 表名 | 变更类型 | 说明 |\n| :--- | :--- | :--- |\n| | | |\n\n`;
638
+ r += `### 接口依赖\n| 调用方 | 被调用方 | 接口 | 说明 |\n| :--- | :--- | :--- | :--- |\n| | | | |\n\n`;
639
+ r += `### 确认签字\n`;
640
+ r += `- [ ] 需求确认无遗漏\n- [ ] 技术方案评审通过\n- [ ] 工时评估合理\n- [ ] 可以开始拆分任务\n`;
641
+ return r;
642
+ }
643
+ function buildTaskReqReport(input, issues, archImpact) {
644
+ const now = new Date().toISOString().split('T')[0];
645
+ let r = `# 任务需求分析\n\n`;
646
+ r += `> 期次: ${input.iteration} | 任务: ${input.taskId} | 分析: ${now}\n\n---\n\n`;
647
+ r += `## 问题清单\n`;
648
+ if (issues.length === 0)
649
+ r += `> ✅ 无问题\n`;
650
+ else
651
+ for (const i of issues)
652
+ r += `- [ ] ${i.message.replace(/\n/g, ' ')}\n`;
653
+ if (archImpact.risks.length > 0) {
654
+ r += `\n## 风险\n`;
655
+ for (const rk of archImpact.risks)
656
+ r += `- ⚠️ ${rk}\n`;
657
+ }
658
+ return r;
659
+ }
660
+ // ================================================================
661
+ // 报告生成: 纯代码
662
+ // ================================================================
663
+ function buildCodeHealthReport(input, stats, apis, hotspots, deps) {
664
+ const now = new Date().toISOString().split('T')[0];
665
+ let r = `# 代码健康报告\n\n`;
666
+ r += `> 分析时间: ${now} | 目录: ${input.sources.join(', ')} | 深度: ${input.depth}\n\n`;
667
+ r += `---\n\n`;
668
+ // 概览
669
+ r += `## 1. 概览\n\n`;
670
+ r += `| 指标 | 数值 |\n| :--- | :--- |\n`;
671
+ r += `| 总文件数 | ${stats.totalFiles} |\n`;
672
+ r += `| 总行数 | ${stats.totalLines.toLocaleString()} |\n`;
673
+ r += `| API 接口数 | ${apis.length} |\n`;
674
+ r += `| 复杂度热点 | ${hotspots.length} 个 |\n`;
675
+ if (deps?.cycles?.length)
676
+ r += `| 循环依赖 | ${deps.cycles.length} 处 |\n`;
677
+ r += `\n`;
678
+ // 语言分布
679
+ r += `## 2. 语言分布\n\n`;
680
+ r += `| 语言 | 文件数 | 占比 |\n| :--- | :--- | :--- |\n`;
681
+ for (const [lang, count] of Object.entries(stats.byLanguage).sort((a, b) => b[1] - a[1])) {
682
+ const pct = ((count / stats.totalFiles) * 100).toFixed(1);
683
+ r += `| ${lang} | ${count} | ${pct}% |\n`;
684
+ }
685
+ r += `\n`;
686
+ // 目录分布
687
+ r += `## 3. 目录分布\n\n`;
688
+ r += `| 目录 | 文件数 |\n| :--- | :--- |\n`;
689
+ for (const [dir, count] of Object.entries(stats.byDir).sort((a, b) => b[1] - a[1])) {
690
+ r += `| ${dir}/ | ${count} |\n`;
691
+ }
692
+ r += `\n`;
693
+ // 复杂度热点
694
+ r += `## 4. 复杂度热点\n\n`;
695
+ if (hotspots.length === 0) {
696
+ r += `> ✅ 未发现明显的复杂度问题\n`;
697
+ }
698
+ else {
699
+ r += `| 文件 | 严重度 | 行数 | 原因 |\n| :--- | :--- | :--- | :--- |\n`;
700
+ for (const h of hotspots.slice(0, 15)) {
701
+ const icon = h.severity === 'high' ? '🔴' : h.severity === 'medium' ? '🟡' : '⚪';
702
+ r += `| ${h.file} | ${icon} ${h.severity} | ${h.lines} | ${h.reason} |\n`;
703
+ }
704
+ }
705
+ r += `\n`;
706
+ // API 清单
707
+ r += `## 5. API 清单 (${apis.length})\n\n`;
708
+ if (apis.length > 0) {
709
+ r += `| 方法 | 路径 | 文件 |\n| :--- | :--- | :--- |\n`;
710
+ for (const a of apis.slice(0, 40)) {
711
+ r += `| ${a.method} | \`${a.path}\` | ${a.file} |\n`;
712
+ }
713
+ if (apis.length > 40)
714
+ r += `| ... | 还有 ${apis.length - 40} 个接口 | |\n`;
715
+ }
716
+ r += `\n`;
717
+ // 最大文件
718
+ r += `## 6. 最大文件 TOP 10\n\n`;
719
+ r += `| # | 文件 | 行数 |\n| :--- | :--- | :--- |\n`;
720
+ for (let i = 0; i < stats.largestFiles.length; i++) {
721
+ r += `| ${i + 1} | ${stats.largestFiles[i].path} | ${stats.largestFiles[i].lines.toLocaleString()} |\n`;
722
+ }
723
+ r += `\n`;
724
+ // 依赖分析
725
+ if (deps) {
726
+ r += `## 7. 依赖分析\n\n`;
727
+ if (deps.cycles.length > 0) {
728
+ r += `### ⚠️ 循环依赖 (${deps.cycles.length} 处)\n`;
729
+ for (const c of deps.cycles)
730
+ r += `- ${c[0]} ↔ ${c[1]}\n`;
731
+ r += `\n`;
732
+ }
733
+ if (deps.entryPoints.length > 0) {
734
+ r += `### 核心模块 (被引用最多)\n`;
735
+ for (const e of deps.entryPoints)
736
+ r += `- ${e}\n`;
737
+ r += `\n`;
738
+ }
739
+ }
740
+ r += `---\n\n`;
741
+ r += `## 改进建议\n\n`;
742
+ if (hotspots.filter(h => h.severity === 'high').length > 0) {
743
+ r += `- 🔴 存在 ${hotspots.filter(h => h.severity === 'high').length} 个高严重度热点,建议拆分大文件\n`;
744
+ }
745
+ if (deps?.cycles?.length) {
746
+ r += `- ⚠️ 检测到 ${deps.cycles.length} 处循环依赖,建议重构解除耦合\n`;
747
+ }
748
+ r += `- 💡 定期运行此命令追踪代码健康趋势\n`;
749
+ return r;
750
+ }
751
+ function buildIterationCodeReport(input, stats, apis, hotspots, deps) {
752
+ const now = new Date().toISOString().split('T')[0];
753
+ let r = `# 代码分析报告\n\n`;
754
+ r += `> 期次: ${input.iteration || 'current'} | 分析: ${now} | 目录: ${input.sources.join(', ')}\n\n`;
755
+ r += `---\n\n`;
756
+ r += `## 统计\n\n`;
757
+ r += `| 指标 | 值 |\n| :--- | :--- |\n`;
758
+ r += `| 文件数 | ${stats.totalFiles} |\n| 行数 | ${stats.totalLines.toLocaleString()} |\n| API | ${apis.length} |\n| 热点 | ${hotspots.length} |\n\n`;
759
+ if (hotspots.length > 0) {
760
+ r += `## 代码热点\n\n`;
761
+ r += `| 文件 | 行数 | 原因 |\n| :--- | :--- | :--- |\n`;
762
+ for (const h of hotspots.slice(0, 10))
763
+ r += `| ${h.file} | ${h.lines} | ${h.severity} — ${h.reason} |\n`;
764
+ r += `\n`;
765
+ }
766
+ if (apis.length > 0) {
767
+ r += `## API 变更影响\n\n`;
768
+ r += `| 方法 | 路径 |\n| :--- | :--- |\n`;
769
+ for (const a of apis.slice(0, 20))
770
+ r += `| ${a.method} | \`${a.path}\` |\n`;
771
+ r += `\n`;
772
+ }
773
+ return r;
774
+ }
775
+ function buildTaskCodeReport(input, stats, apis, hotspots) {
776
+ const now = new Date().toISOString().split('T')[0];
777
+ let r = `# 任务代码审查\n\n`;
778
+ r += `> 期次: ${input.iteration} | 任务: ${input.taskId} | ${now}\n\n---\n\n`;
779
+ r += `## 变更文件 (${stats.totalFiles})\n\n`;
780
+ r += `| 文件 | 行数 |\n| :--- | :--- |\n`;
781
+ for (const f of stats.largestFiles.slice(0, 15))
782
+ r += `| ${f.path} | ${f.lines} |\n`;
783
+ r += `\n`;
784
+ if (hotspots.length > 0) {
785
+ r += `## 关注点\n\n`;
786
+ for (const h of hotspots)
787
+ r += `- ${h.severity === 'high' ? '🔴' : '🟡'} ${h.file}: ${h.reason}\n`;
788
+ r += `\n`;
789
+ }
790
+ r += `## 检查清单\n\n`;
791
+ r += `- [ ] 代码风格一致\n- [ ] 无硬编码密钥\n- [ ] 错误处理完善\n- [ ] 日志覆盖关键路径\n`;
792
+ if (apis.length > 0)
793
+ r += `- [ ] API 文档已更新 (${apis.length} 个接口)\n`;
794
+ return r;
795
+ }
796
+ // ================================================================
797
+ // 报告生成: 需求+代码联合
798
+ // ================================================================
799
+ function buildGlobalArchImpactReport(input, issues, archImpact, codeMap, stats) {
800
+ const now = new Date().toISOString().split('T')[0];
801
+ let r = `# 全局架构影响分析\n\n`;
802
+ r += `> 分析时间: ${now} | 需求: ${input.requirements.length} 个 | 源码目录: ${input.sources.join(', ')}\n\n`;
803
+ r += `---\n\n`;
804
+ r += `## 1. 概览\n\n`;
805
+ r += `| 指标 | 值 |\n| :--- | :--- |\n`;
806
+ r += `| 需求文档 | ${input.requirements.length} |\n`;
807
+ r += `| 扫描文件 | ${stats.totalFiles} |\n`;
808
+ r += `| 匹配代码 | ${codeMap.length} |\n`;
809
+ r += `| 问题 | ${issues.length} |\n`;
810
+ r += `| 风险 | ${archImpact.risks.length} |\n`;
811
+ r += `\n`;
812
+ r += `## 2. 需求完整性\n\n`;
813
+ for (const i of issues)
814
+ r += `- ${icon(i.severity)} ${i.message.replace(/\n/g, ' ')}\n`;
815
+ r += `\n`;
816
+ r += `## 3. 代码对标 (${codeMap.length} 个匹配)\n\n`;
817
+ if (codeMap.length > 0) {
818
+ r += `| 源码文件 | 关联分 | 匹配API |\n| :--- | :--- | :--- |\n`;
819
+ for (const m of codeMap.slice(0, 20)) {
820
+ r += `| ${m.sourceFile} | ${m.relevanceScore} | ${m.apis.slice(0, 3).join(', ')} |\n`;
821
+ }
822
+ }
823
+ r += `\n`;
824
+ r += `## 4. 架构影响\n\n`;
825
+ for (const d of archImpact.newDependencies)
826
+ r += `- [ ] ${d}\n`;
827
+ for (const rk of archImpact.risks)
828
+ r += `- ⚠️ ${rk}\n`;
829
+ return r;
830
+ }
831
+ function buildIterationCombinedReport(input, issues, archImpact, codeMap, stats, apis) {
832
+ const now = new Date().toISOString().split('T')[0];
833
+ const iter = input.iteration || 'current';
834
+ const blockerCount = issues.filter(i => i.severity === 'blocker').length;
835
+ let r = `# 需求分析报告\n\n`;
836
+ r += `> 期次: ${iter} | 分析时间: ${now} | 状态: ${blockerCount > 0 ? '🔴 有阻断' : '🟢 可拆分'}\n`;
837
+ r += `> 需求文档: ${input.requirements.length} 个 | 源码目录: ${input.sources.join(', ')}\n\n`;
838
+ r += `---\n\n`;
839
+ if (codeMap.length > 0) {
840
+ r += `## 0. 分析概览\n\n`;
841
+ r += `| 需求文档 | 扫描文件 | 匹配代码 | API 接口 | 问题 |\n`;
842
+ r += `| :--- | :--- | :--- | :--- | :--- |\n`;
843
+ r += `| ${input.requirements.length} | ${stats.totalFiles} | ${codeMap.length} | ${apis.length} | ${issues.length} |\n\n`;
844
+ }
845
+ // 1. 完整性检查
846
+ r += `## 1. 需求完整性检查\n\n`;
847
+ r += `| 严重度 | 分类 | 问题 |\n| :--- | :--- | :--- |\n`;
848
+ for (const issue of issues) {
849
+ r += `| ${icon(issue.severity)} ${issue.severity} | ${issue.category} | ${issue.message.replace(/\n/g, '<br>')} |\n`;
850
+ }
851
+ if (issues.length === 0)
852
+ r += `| ✅ | - | 未发现明显问题 |\n`;
853
+ r += `\n`;
854
+ // 2. 源码对标
855
+ r += `## 2. 源码对标\n\n`;
856
+ if (codeMap.length > 0) {
857
+ r += `| 源码文件 | 关联度 | API 匹配 |\n| :--- | :--- | :--- |\n`;
858
+ for (const m of codeMap.slice(0, 15)) {
859
+ const relevance = m.relevanceScore >= 30 ? 'high' : m.relevanceScore >= 15 ? 'medium' : 'low';
860
+ r += `| ${m.sourceFile} | ${relevance} (${m.relevanceScore}) | ${m.apis.slice(0, 3).join(', ') || '—'} |\n`;
861
+ }
862
+ }
863
+ else {
864
+ r += `> ⚠️ 未匹配到相关源码。请确认源码目录是否正确。\n`;
865
+ }
866
+ r += `\n`;
867
+ // 3. 架构影响
868
+ r += `## 3. 架构影响\n\n`;
869
+ if (archImpact.apis.length > 0) {
870
+ r += `### 涉及接口\n`;
871
+ for (const a of archImpact.apis.slice(0, 20))
872
+ r += `- \`${a}\`\n`;
873
+ r += `\n`;
874
+ }
875
+ if (archImpact.newDependencies.length > 0) {
876
+ r += `### 新增依赖\n`;
877
+ for (const d of archImpact.newDependencies)
878
+ r += `- [ ] ${d}\n`;
879
+ r += `\n`;
880
+ }
881
+ if (archImpact.risks.length > 0) {
882
+ r += `### ⚠️ 风险提示\n`;
883
+ for (const rk of archImpact.risks)
884
+ r += `- ${rk}\n`;
885
+ r += `\n`;
886
+ }
887
+ // 4. 待确认清单
888
+ r += `## 4. 待确认清单\n\n`;
889
+ for (const issue of issues.filter(i => i.severity !== 'info')) {
890
+ r += `- [ ] ${issue.message.replace(/\n/g, ' ').slice(0, 100)}\n`;
891
+ }
892
+ for (const rk of archImpact.risks)
893
+ r += `- [ ] ${rk}\n`;
894
+ r += `\n---\n\n## 5. 技术方案(待填写)\n\n`;
895
+ r += `| 模块 | 技术方案 | 负责人 | 预计工时 |\n| :--- | :--- | :--- | :--- |\n| | | | |\n\n`;
896
+ r += `### 数据库变更\n| 表名 | 变更类型 | 说明 |\n| :--- | :--- | :--- |\n| | | |\n\n`;
897
+ r += `### 接口依赖\n| 调用方 | 被调用方 | 接口 | 说明 |\n| :--- | :--- | :--- | :--- |\n| | | | |\n\n`;
898
+ r += `### 确认签字\n`;
899
+ r += `- [ ] 需求确认无遗漏\n- [ ] 技术方案评审通过\n- [ ] 工时评估合理\n- [ ] 可以开始拆分任务\n`;
900
+ return r;
901
+ }
902
+ function buildTaskCombinedReport(input, issues, archImpact, codeMap, sourceContents) {
903
+ const now = new Date().toISOString().split('T')[0];
904
+ let r = `# 任务综合分析\n\n`;
905
+ r += `> 期次: ${input.iteration} | 任务: ${input.taskId} | ${now}\n\n---\n\n`;
906
+ r += `## 问题清单\n`;
907
+ for (const i of issues)
908
+ r += `- ${icon(i.severity)} ${i.message.replace(/\n/g, ' ')}\n`;
909
+ r += `\n`;
910
+ if (codeMap.length > 0) {
911
+ r += `## 代码对标\n`;
912
+ r += `| 文件 | 关联分 |\n| :--- | :--- |\n`;
913
+ for (const m of codeMap.slice(0, 10))
914
+ r += `| ${m.sourceFile} | ${m.relevanceScore} |\n`;
915
+ r += `\n`;
916
+ }
917
+ if (archImpact.risks.length > 0) {
918
+ r += `## 风险\n`;
919
+ for (const rk of archImpact.risks)
920
+ r += `- ${rk}\n`;
921
+ }
922
+ return r;
923
+ }
924
+ // ================================================================
925
+ // 工具函数
926
+ // ================================================================
927
+ function icon(severity) {
928
+ switch (severity) {
929
+ case 'blocker': return '🔴';
930
+ case 'warning': return '🟡';
931
+ case 'info': return 'ℹ️';
932
+ default: return '⚪';
933
+ }
934
+ }
935
+ //# sourceMappingURL=analyze-engine.js.map