speccore 8.3.121 → 8.3.127

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 (144) 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 +1 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +513 -28
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/iteration/create.js +1 -1
  29. package/dist/commands/iteration/split.d.ts.map +1 -1
  30. package/dist/commands/iteration/split.js +158 -70
  31. package/dist/commands/iteration/split.js.map +1 -1
  32. package/dist/commands/iteration-from-global.js +1 -2
  33. package/dist/commands/iteration-from-global.js.map +1 -1
  34. package/dist/commands/pattern.js +30 -16
  35. package/dist/commands/pattern.js.map +1 -1
  36. package/dist/commands/pipeline.d.ts.map +1 -1
  37. package/dist/commands/pipeline.js +15 -4
  38. package/dist/commands/pipeline.js.map +1 -1
  39. package/dist/commands/retro.d.ts.map +1 -1
  40. package/dist/commands/retro.js +15 -8
  41. package/dist/commands/retro.js.map +1 -1
  42. package/dist/commands/status-panel.js +1 -1
  43. package/dist/commands/status-panel.js.map +1 -1
  44. package/dist/commands/sync.js +15 -8
  45. package/dist/commands/sync.js.map +1 -1
  46. package/dist/commands/trace.js +27 -24
  47. package/dist/commands/trace.js.map +1 -1
  48. package/dist/commands/update-env-configs.d.ts.map +1 -1
  49. package/dist/commands/update-env-configs.js +33 -5
  50. package/dist/commands/update-env-configs.js.map +1 -1
  51. package/dist/commands/update.d.ts.map +1 -1
  52. package/dist/commands/update.js +35 -0
  53. package/dist/commands/update.js.map +1 -1
  54. package/dist/commands/verify.d.ts +1 -1
  55. package/dist/commands/verify.d.ts.map +1 -1
  56. package/dist/commands/verify.js +25 -0
  57. package/dist/commands/verify.js.map +1 -1
  58. package/dist/core/ai-context-generator.js +35 -22
  59. package/dist/core/ai-context-generator.js.map +1 -1
  60. package/dist/core/ai-impact-analyzer.d.ts.map +1 -1
  61. package/dist/core/ai-impact-analyzer.js +1 -1
  62. package/dist/core/ai-impact-analyzer.js.map +1 -1
  63. package/dist/core/analyze-engine.d.ts +2 -1
  64. package/dist/core/analyze-engine.d.ts.map +1 -1
  65. package/dist/core/analyze-engine.js +217 -53
  66. package/dist/core/analyze-engine.js.map +1 -1
  67. package/dist/core/ask-engine.d.ts.map +1 -1
  68. package/dist/core/ask-engine.js +31 -13
  69. package/dist/core/ask-engine.js.map +1 -1
  70. package/dist/core/change-detection.d.ts.map +1 -1
  71. package/dist/core/change-detection.js +21 -5
  72. package/dist/core/change-detection.js.map +1 -1
  73. package/dist/core/code-scanner.d.ts +12 -3
  74. package/dist/core/code-scanner.d.ts.map +1 -1
  75. package/dist/core/code-scanner.js +376 -10
  76. package/dist/core/code-scanner.js.map +1 -1
  77. package/dist/core/context-builder.d.ts +2 -1
  78. package/dist/core/context-builder.d.ts.map +1 -1
  79. package/dist/core/context-builder.js +104 -25
  80. package/dist/core/context-builder.js.map +1 -1
  81. package/dist/core/deploy/engine.d.ts.map +1 -1
  82. package/dist/core/deploy/engine.js +21 -6
  83. package/dist/core/deploy/engine.js.map +1 -1
  84. package/dist/core/git-integration.d.ts +5 -1
  85. package/dist/core/git-integration.d.ts.map +1 -1
  86. package/dist/core/git-integration.js +52 -15
  87. package/dist/core/git-integration.js.map +1 -1
  88. package/dist/core/knowledge-graph.d.ts +2 -0
  89. package/dist/core/knowledge-graph.d.ts.map +1 -1
  90. package/dist/core/knowledge-graph.js +246 -232
  91. package/dist/core/knowledge-graph.js.map +1 -1
  92. package/dist/core/name-validator.js +1 -1
  93. package/dist/core/name-validator.js.map +1 -1
  94. package/dist/core/pipeline-test.d.ts.map +1 -1
  95. package/dist/core/pipeline-test.js +7 -4
  96. package/dist/core/pipeline-test.js.map +1 -1
  97. package/dist/core/prompt-builder.d.ts +3 -0
  98. package/dist/core/prompt-builder.d.ts.map +1 -1
  99. package/dist/core/prompt-builder.js +629 -77
  100. package/dist/core/prompt-builder.js.map +1 -1
  101. package/dist/core/rag-engine.d.ts +10 -5
  102. package/dist/core/rag-engine.d.ts.map +1 -1
  103. package/dist/core/rag-engine.js +134 -16
  104. package/dist/core/rag-engine.js.map +1 -1
  105. package/dist/core/spec-paths.d.ts +42 -2
  106. package/dist/core/spec-paths.d.ts.map +1 -1
  107. package/dist/core/spec-paths.js +207 -11
  108. package/dist/core/spec-paths.js.map +1 -1
  109. package/dist/core/structured-extractor.d.ts +67 -0
  110. package/dist/core/structured-extractor.d.ts.map +1 -1
  111. package/dist/core/structured-extractor.js +1298 -23
  112. package/dist/core/structured-extractor.js.map +1 -1
  113. package/dist/core/test-config.d.ts +1 -1
  114. package/dist/core/test-config.d.ts.map +1 -1
  115. package/dist/core/test-config.js +85 -65
  116. package/dist/core/test-config.js.map +1 -1
  117. package/dist/core/ui-verify/report-generator.d.ts.map +1 -1
  118. package/dist/core/ui-verify/report-generator.js +7 -3
  119. package/dist/core/ui-verify/report-generator.js.map +1 -1
  120. package/dist/core/ui-verify/smoke-engine.js +7 -1
  121. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  122. package/dist/core/unified-config.d.ts +43 -3
  123. package/dist/core/unified-config.d.ts.map +1 -1
  124. package/dist/core/unified-config.js +133 -13
  125. package/dist/core/unified-config.js.map +1 -1
  126. package/dist/core/unified-retrieval.d.ts.map +1 -1
  127. package/dist/core/unified-retrieval.js +41 -15
  128. package/dist/core/unified-retrieval.js.map +1 -1
  129. package/dist/core/unit-context-assembler.js +2 -2
  130. package/dist/core/unit-context-assembler.js.map +1 -1
  131. package/dist/core/validator.d.ts.map +1 -1
  132. package/dist/core/validator.js +59 -44
  133. package/dist/core/validator.js.map +1 -1
  134. package/dist/core/verify-engine.d.ts +9 -2
  135. package/dist/core/verify-engine.d.ts.map +1 -1
  136. package/dist/core/verify-engine.js +274 -108
  137. package/dist/core/verify-engine.js.map +1 -1
  138. package/dist/core/verify-report-aggregator.js +9 -20
  139. package/dist/core/verify-report-aggregator.js.map +1 -1
  140. package/dist/utils/synonyms.d.ts +24 -0
  141. package/dist/utils/synonyms.d.ts.map +1 -0
  142. package/dist/utils/synonyms.js +114 -0
  143. package/dist/utils/synonyms.js.map +1 -0
  144. package/package.json +1 -1
@@ -12,7 +12,7 @@ exports.generateSpecsFromRequirements = generateSpecsFromRequirements;
12
12
  *
13
13
  * 输入:
14
14
  * - 纯需求: --req docs/a.md docs/b.md
15
- * - 纯代码: --src backend/src 20-frontend/src
15
+ * - 纯代码: --src backend/src frontend/src
16
16
  * - 需求+代码: 两者都指定
17
17
  *
18
18
  * 输出范围 (scope):
@@ -25,6 +25,8 @@ const path_1 = require("path");
25
25
  const logger_1 = require("../utils/logger");
26
26
  const task_utils_1 = require("../utils/task-utils");
27
27
  const code_scanner_1 = require("./code-scanner");
28
+ // v8.3.125+: 同义词扩展,解决中英文/缩写匹配失败问题
29
+ const synonyms_1 = require("../utils/synonyms");
28
30
  const ai_context_generator_1 = require("./ai-context-generator");
29
31
  const git_integration_1 = require("./git-integration");
30
32
  const rag_engine_1 = require("./rag-engine");
@@ -51,7 +53,7 @@ async function runAnalysis(input) {
51
53
  if (input.scope === 'iteration' && input.iteration) {
52
54
  const productReqDir = (0, path_1.join)(`Iteration-${input.iteration}`, '010-requirements');
53
55
  if (await (0, fs_extra_1.pathExists)(productReqDir)) {
54
- // 递归扫描子目录 (backend/ 20-frontend/Web/ 等)
56
+ // 递归扫描子目录 ({端名}/ 等)
55
57
  const scanDir = async (dir) => {
56
58
  const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
57
59
  for (const e of entries) {
@@ -160,9 +162,11 @@ async function runAnalysis(input) {
160
162
  logger_1.logger.debug('RAG 索引生成失败(非关键):', e);
161
163
  }
162
164
  // ── 统一刷新代码索引 + 知识图谱(所有 scope)──
165
+ // v8.3.125+: 按迭代端名筛选源码路径,减少增量刷新范围
163
166
  try {
164
167
  logger_1.logger.info(' 🔄 刷新代码索引...');
165
- await (0, code_scanner_1.buildCodeIndex)(undefined, true);
168
+ const codeIndexScope = await (0, code_scanner_1.resolveIterationScope)(effectiveInput.iteration);
169
+ await (0, code_scanner_1.buildCodeIndex)(codeIndexScope, true);
166
170
  logger_1.logger.info(' ✅ 代码索引已刷新');
167
171
  }
168
172
  catch (e) {
@@ -203,16 +207,18 @@ async function analyzeRequirements(input) {
203
207
  const issues = scanCompleteness(fullContent);
204
208
  const archImpact = await analyzeArchitectureImpact(fullContent);
205
209
  // ── 需求分析时默认读取关联代码(避免分析不接地气 + 减少重复 token 消耗) ──
210
+ // v8.3.122+: 增加文件数和字节配额,确保深入阅读业务逻辑
206
211
  let sourceContents = {};
207
212
  const shouldReadSource = input.readSource !== false;
208
213
  if (shouldReadSource && (input.iteration || input.taskId)) {
209
- const limit = input.depth === 'deep' ? 15 : (input.depth === 'quick' ? 3 : 8);
210
- const maxBytes = input.depth === 'deep' ? 80000 : (input.depth === 'quick' ? 20000 : 40000);
214
+ const limit = input.depth === 'deep' ? 25 : (input.depth === 'quick' ? 5 : 12);
215
+ const maxBytes = input.depth === 'deep' ? 150000 : (input.depth === 'quick' ? 30000 : 70000);
211
216
  const rawMatches = await (0, code_scanner_1.findRelevantCode)(fullContent, limit, input.sourceScope, input.iteration, input.taskId);
212
- sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes);
217
+ sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes, limit);
213
218
  if (Object.keys(sourceContents).length > 0) {
214
219
  const scopeHint = input.sourceScope ? ` (范围: ${input.sourceScope})` : '';
215
- logger_1.logger.info(` 📖 需求分析已关联 ${Object.keys(sourceContents).length} 个源码文件${scopeHint}`);
220
+ const totalChars = Object.values(sourceContents).reduce((a, c) => a + c.length, 0);
221
+ logger_1.logger.info(` 📖 需求分析已关联 ${Object.keys(sourceContents).length} 个源码文件 (${Math.round(totalChars / 1000)}K 字符)${scopeHint}`);
216
222
  }
217
223
  }
218
224
  let outputPath;
@@ -339,15 +345,17 @@ async function analyzeCombined(input) {
339
345
  // 代码结构分析
340
346
  const fileStats = await scanSourceDirs(input.sources, input.depth);
341
347
  const apiInventory = await buildApiInventory(input.sources);
342
- // 确保代码索引是最新的(解耦设计:总是全量索引,分析时按 scope 筛选)
343
- if (await (0, code_scanner_1.isIndexStale)()) {
344
- logger_1.logger.info(' 🔍 索引已过期,自动重建全量代码索引...');
345
- await (0, code_scanner_1.buildCodeIndex)(); // 总是全量建,不是按 scope
348
+ // 确保代码索引是最新的
349
+ // v8.3.125+: 按迭代端名筛选 scope + 增量刷新 + mtime 精确判断过期
350
+ const codeIndexScope = await (0, code_scanner_1.resolveIterationScope)(input.iteration);
351
+ if (await (0, code_scanner_1.isIndexStale)(codeIndexScope)) {
352
+ logger_1.logger.info(' 🔍 索引已过期,自动重建代码索引...');
353
+ await (0, code_scanner_1.buildCodeIndex)(codeIndexScope, true);
346
354
  logger_1.logger.info(' ✅ 代码索引已更新(包含多端识别 + 模块分组 + git 联动分析)');
347
355
  logger_1.logger.info(' 💡 提示:可手动运行 `speccore code-index --show` 查看索引摘要');
348
356
  }
349
357
  else {
350
- logger_1.logger.info(' 📚 使用缓存的代码索引(1 小时内已构建)');
358
+ logger_1.logger.info(' 📚 使用缓存的代码索引(源码未变更)');
351
359
  }
352
360
  // ── AI 上下文生成: 替代关键词匹配(传入已读取的需求内容,避免重复 readFile) ──
353
361
  const aiContext = await (0, ai_context_generator_1.generateAIContext)({
@@ -364,13 +372,14 @@ async function analyzeCombined(input) {
364
372
  logger_1.logger.info(` 🔗 API: ${aiContext.totalApis} 个`);
365
373
  // 默认读取相关源码内容注入分析(除非明确关闭)
366
374
  // 解耦设计:从完整索引中按 sourceScope 筛选,不是按 scope 建索引
375
+ // v8.3.122+: 增加配额,确保深入阅读业务逻辑和公共模块
367
376
  let sourceContents = {};
368
377
  const shouldReadSource = input.readSource !== false;
369
378
  if (shouldReadSource) {
370
- const limit = input.depth === 'deep' ? 20 : (input.depth === 'quick' ? 5 : 10);
371
- const maxBytes = input.depth === 'deep' ? 120000 : (input.depth === 'quick' ? 30000 : 60000);
379
+ const limit = input.depth === 'deep' ? 30 : (input.depth === 'quick' ? 8 : 15);
380
+ const maxBytes = input.depth === 'deep' ? 200000 : (input.depth === 'quick' ? 40000 : 100000);
372
381
  const rawMatches = await (0, code_scanner_1.findRelevantCode)(fullReqContent, limit, input.sourceScope, input.iteration, input.taskId);
373
- sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes);
382
+ sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes, limit);
374
383
  if (Object.keys(sourceContents).length > 0) {
375
384
  const scopeHint = input.sourceScope ? ` (范围: ${input.sourceScope})` : '';
376
385
  logger_1.logger.info(` 📖 已读取 ${Object.keys(sourceContents).length} 个源码文件${scopeHint} (${Object.values(sourceContents).reduce((a, c) => a + c.length, 0)} 字符)`);
@@ -835,12 +844,14 @@ function buildIterationReqReport(input, issues, archImpact, sourceContents) {
835
844
  if (archImpact.newDependencies.length > 0)
836
845
  r += `- [ ] 确认新增依赖的引入方案和排期\n`;
837
846
  // ── 代码关联分析(v6.8.0 新增:需求分析默认关联代码) ──
847
+ // v8.3.122+: 按深度动态调整代码展示配额,确保业务逻辑可见
838
848
  if (sourceContents && Object.keys(sourceContents).length > 0) {
839
849
  r += `\n---\n\n## 3.5 关联代码现状\n\n`;
840
850
  r += `> 以下源码文件与当前需求相关,供技术方案参考\n\n`;
851
+ const previewLimit = input.depth === 'deep' ? 3000 : (input.depth === 'quick' ? 800 : 1500);
841
852
  for (const [path, content] of Object.entries(sourceContents)) {
842
- const preview = content.slice(0, 600).replace(/\n/g, '\n ');
843
- r += `### \`${path}\`\n\n\`\`\`${path.split('.').pop() || 'ts'}\n${preview}${content.length > 600 ? '\n // ... 截断 ...' : ''}\n\`\`\`\n\n`;
853
+ const preview = content.slice(0, previewLimit).replace(/\n/g, '\n ');
854
+ r += `### \`${path}\`\n\n\`\`\`${path.split('.').pop() || 'ts'}\n${preview}${content.length > previewLimit ? '\n // ... 截断 ...' : ''}\n\`\`\`\n\n`;
844
855
  }
845
856
  }
846
857
  r += `\n---\n\n## 4. 技术方案(待填写)\n\n`;
@@ -1098,14 +1109,20 @@ async function buildAIEnhancedReport(input, scope, params) {
1098
1109
  if (readFiles.length > 0 || uncoveredFiles.length > 0) {
1099
1110
  r += `## 📚 源码分析清单\n\n`;
1100
1111
  // 已分析文件
1112
+ // v8.3.122+: 增加代码内容预览,确保 AI 能看到业务逻辑
1101
1113
  if (readFiles.length > 0) {
1102
1114
  r += `### ✅ 已分析(${readFiles.length} 个文件)\n\n`;
1115
+ const previewLimit = input.depth === 'deep' ? 3000 : (input.depth === 'quick' ? 800 : 1500);
1103
1116
  for (const [file, content] of Object.entries(sourceContents)) {
1104
1117
  const lines = content.split('\n').length;
1105
1118
  const size = (content.length / 1024).toFixed(1);
1106
1119
  r += `- \`${file}\` (${lines} 行, ${size}KB)\n`;
1120
+ // 展示代码预览
1121
+ if (content.length > 0) {
1122
+ const preview = content.slice(0, previewLimit).replace(/\n/g, '\n ');
1123
+ r += `\n \`\`\`${file.split('.').pop() || 'ts'}\n ${preview}${content.length > previewLimit ? '\n // ... 截断 ...' : ''}\n \`\`\`\n\n`;
1124
+ }
1107
1125
  }
1108
- r += `\n`;
1109
1126
  }
1110
1127
  // 未覆盖文件(按目录分组)
1111
1128
  if (uncoveredFiles.length > 0) {
@@ -1182,7 +1199,10 @@ function icon(severity) {
1182
1199
  default: return '⚪';
1183
1200
  }
1184
1201
  }
1185
- /** 按需求功能 × 端 分目录写入分析报告(按端提取差异化内容) */
1202
+ /** 按需求功能 × 端 分目录写入分析报告(按端提取差异化内容)
1203
+ * v8.3.122+: 修复路径 — 从 020-specs/{platform}/ 改为 020-specs/{feature}/{platform}/
1204
+ * 与 split 读取路径保持一致,确保端级分析文档能被正确关联
1205
+ */
1186
1206
  async function writePerPlatform(iterDir, report, filename) {
1187
1207
  const reqDir = (0, path_1.join)(iterDir, '010-requirements');
1188
1208
  const specsBase = (0, path_1.join)(iterDir, '020-specs');
@@ -1197,16 +1217,14 @@ async function writePerPlatform(iterDir, report, filename) {
1197
1217
  return;
1198
1218
  // 平台列表从 CONSTITUTION 获取(不再硬编码默认值)
1199
1219
  const platforms = await detectPlatformsFromConstitution();
1200
- for (const platform of platforms) {
1201
- const platformDir = (0, path_1.join)(specsBase, platform);
1202
- await (0, fs_extra_1.ensureDir)(platformDir);
1203
- // 按端提取差异化内容(而非写入同一份报告)
1204
- const platformReport = extractPlatformContent(report, platform);
1205
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, filename), platformReport);
1206
- for (const feature of features) {
1207
- // 每个 feature 文件也按端提取
1220
+ for (const feature of features) {
1221
+ for (const platform of platforms) {
1222
+ // v8.3.122+: 输出到 020-specs/{feature}/{platform}/ 与 split 读取路径一致
1223
+ // v8.3.125+: 使用增量合并,避免多次分析覆盖丢失
1224
+ const platformDir = (0, path_1.join)(specsBase, feature, platform);
1225
+ await (0, fs_extra_1.ensureDir)(platformDir);
1208
1226
  const featureContent = extractFeatureForPlatform(report, feature, platform);
1209
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, `${feature}.md`), featureContent);
1227
+ await writeMergedFile((0, path_1.join)(platformDir, filename), featureContent);
1210
1228
  }
1211
1229
  }
1212
1230
  }
@@ -1264,8 +1282,8 @@ function extractPlatformContent(report, platform) {
1264
1282
  }
1265
1283
  /** 从报告中提取特定功能 × 特定端的内容 */
1266
1284
  function extractFeatureForPlatform(report, feature, platform) {
1267
- // 先提取该功能的段落
1268
- const featureKeywords = [feature, feature.replace(/-/g, ' ')];
1285
+ // v8.3.125+: 使用同义词扩展,解决 "登录" 与 "auth" 等跨语言/缩写不匹配问题
1286
+ const featureKeywords = [...(0, synonyms_1.expandSynonyms)((0, synonyms_1.extractNormalizedKeywords)(feature))];
1269
1287
  const lines = report.split('\n');
1270
1288
  const sections = [];
1271
1289
  let current = { heading: '', content: [] };
@@ -1285,9 +1303,10 @@ function extractFeatureForPlatform(report, feature, platform) {
1285
1303
  sections.push({ heading: current.heading, content: current.content });
1286
1304
  }
1287
1305
  // 先按功能名匹配,再按端关键词过滤
1306
+ // v8.3.125+: 使用同义词扩展后的关键词集合匹配
1288
1307
  const featureSections = sections.filter(s => {
1289
1308
  const text = (s.heading + ' ' + s.content.join(' ')).toLowerCase();
1290
- return featureKeywords.some(kw => text.includes(kw.toLowerCase()));
1309
+ return featureKeywords.some(kw => text.includes(kw));
1291
1310
  });
1292
1311
  if (featureSections.length > 0) {
1293
1312
  const platformKws = PLATFORM_KEYWORDS[platform] || [platform];
@@ -1300,9 +1319,41 @@ function extractFeatureForPlatform(report, feature, platform) {
1300
1319
  }
1301
1320
  return `# ${feature} (${platform})\n\n_该功能在 ${platform} 端暂无独立分析内容,请参考完整报告。_\n`;
1302
1321
  }
1322
+ /** 从报告中提取特定功能模块的内容(不限制端,用于模块总览) */
1323
+ function extractFeatureContent(report, feature) {
1324
+ // v8.3.125+: 使用同义词扩展
1325
+ const featureKeywords = [...(0, synonyms_1.expandSynonyms)((0, synonyms_1.extractNormalizedKeywords)(feature))];
1326
+ const lines = report.split('\n');
1327
+ const sections = [];
1328
+ let current = { heading: '', content: [] };
1329
+ for (const line of lines) {
1330
+ const hm = line.match(/^(#{1,4})\s+(.+)/);
1331
+ if (hm) {
1332
+ if (current.heading || current.content.length > 0) {
1333
+ sections.push({ heading: current.heading, content: current.content });
1334
+ }
1335
+ current = { heading: line, content: [] };
1336
+ }
1337
+ else {
1338
+ current.content.push(line);
1339
+ }
1340
+ }
1341
+ if (current.heading || current.content.length > 0) {
1342
+ sections.push({ heading: current.heading, content: current.content });
1343
+ }
1344
+ const matched = sections.filter(s => {
1345
+ const text = (s.heading + ' ' + s.content.join(' ')).toLowerCase();
1346
+ return featureKeywords.some(kw => text.includes(kw));
1347
+ });
1348
+ if (matched.length > 0) {
1349
+ return matched.map(s => s.heading + '\n' + s.content.join('\n')).join('\n\n');
1350
+ }
1351
+ return '';
1352
+ }
1303
1353
  /**
1304
- * 局部分析:只分析单个功能模块,写入 020-specs/features/{feature}.md
1354
+ * 局部分析:只分析单个功能模块,写入 020-specs/{feature}/overview/
1305
1355
  * 用于 --feature 模式,避免全量重跑
1356
+ * v8.3.122+: 修复路径与规范一致
1306
1357
  */
1307
1358
  async function analyzeSingleFeature(iterDir, featureName) {
1308
1359
  const reqDir = (0, path_1.join)(iterDir, '010-requirements');
@@ -1346,10 +1397,10 @@ async function analyzeSingleFeature(iterDir, featureName) {
1346
1397
  r += `_未检测到明显架构影响_\n\n`;
1347
1398
  }
1348
1399
  r += `## 3. 需求原文\n\n${content}\n`;
1349
- // 写入 020-specs/features/
1350
- const featuresDir = (0, path_1.join)(iterDir, '020-specs', 'features');
1351
- await (0, fs_extra_1.ensureDir)(featuresDir);
1352
- const outputPath = (0, path_1.join)(featuresDir, `${featureName}.md`);
1400
+ // v8.3.122+: 写入 020-specs/{feature}/overview/ 与规范一致
1401
+ const overviewDir = (0, path_1.join)(iterDir, '020-specs', featureName, 'overview');
1402
+ await (0, fs_extra_1.ensureDir)(overviewDir);
1403
+ const outputPath = (0, path_1.join)(overviewDir, 'ANALYSIS.md');
1353
1404
  await (0, fs_extra_1.writeFile)(outputPath, r);
1354
1405
  return { outputPath, report: r };
1355
1406
  }
@@ -1402,10 +1453,13 @@ async function analyzeSingleTypedDoc(iterDir, docPath) {
1402
1453
  await (0, fs_extra_1.writeFile)(outputPath, r);
1403
1454
  return { outputPath, report: r };
1404
1455
  }
1405
- /** 按功能模块写入 020-specs/features/ */
1456
+ /** 按功能模块写入模块总览(提取该功能模块在所有端中的内容)
1457
+ * v8.3.122+: 修复路径 — 从 020-specs/features/{feature}.md 改为 020-specs/{feature}/overview/
1458
+ * 与 split 读取路径保持一致,避免整份报告冗余复制
1459
+ */
1406
1460
  async function writePerFeature(iterDir, report, filename) {
1407
1461
  const reqDir = (0, path_1.join)(iterDir, '010-requirements');
1408
- const featuresDir = (0, path_1.join)(iterDir, '020-specs', 'features');
1462
+ const specsBase = (0, path_1.join)(iterDir, '020-specs');
1409
1463
  // 非 feature 型目录的排除名单
1410
1464
  const EXCLUDED_DIRS = new Set(['sources', 'assets', 'converted', 'staging', 'bugs', 'refactors', 'research', 'prototypes']);
1411
1465
  try {
@@ -1416,16 +1470,18 @@ async function writePerFeature(iterDir, report, filename) {
1416
1470
  .map(e => e.name);
1417
1471
  if (features.length === 0)
1418
1472
  return;
1419
- await (0, fs_extra_1.ensureDir)(featuresDir);
1420
1473
  for (const feature of features) {
1421
- // 读取该 feature 的 README.md 作为头部,拼接完整报告
1422
- const featureReqPath = (0, path_1.join)(reqDir, feature, 'README.md');
1423
- let featureHeader = '';
1424
- if (await (0, fs_extra_1.pathExists)(featureReqPath)) {
1425
- featureHeader = `# ${feature} — 需求分析\n\n> 来源: 010-requirements/features/${feature}/README.md\n\n---\n\n`;
1474
+ // 提取该功能模块的专属内容(不限制端)
1475
+ let featureContent = extractFeatureContent(report, feature);
1476
+ if (featureContent.trim().length === 0) {
1477
+ // 无匹配内容时回退:写入提示说明
1478
+ featureContent = `# ${feature} — 模块总览\n\n_未从分析报告中提取到该模块的专属内容,请参考完整报告或检查需求文档命名。_\n`;
1426
1479
  }
1427
- const featureContent = featureHeader + report;
1428
- await (0, fs_extra_1.writeFile)((0, path_1.join)(featuresDir, `${feature}.md`), featureContent);
1480
+ // v8.3.122+: 输出到 020-specs/{feature}/overview/ 与 split 读取路径一致
1481
+ // v8.3.125+: 使用增量合并,避免多次分析覆盖丢失
1482
+ const overviewDir = (0, path_1.join)(specsBase, feature, 'overview');
1483
+ await (0, fs_extra_1.ensureDir)(overviewDir);
1484
+ await writeMergedFile((0, path_1.join)(overviewDir, filename), featureContent);
1429
1485
  }
1430
1486
  }
1431
1487
  catch { }
@@ -1716,17 +1772,21 @@ async function detectPlatformsFromConstitution() {
1716
1772
  const lines = content.split('\n');
1717
1773
  // ── Layer 1: 表格「对应端」/「对应需求端」列(旧版回退)──
1718
1774
  let headerRowIndex = -1;
1719
- let headerCells = [];
1775
+ let platformColIdx = -1;
1776
+ let identifierColIdx = -1;
1720
1777
  for (let i = 0; i < lines.length; i++) {
1721
1778
  const cells = lines[i].split('|').map((c) => c.trim()).filter(Boolean);
1722
- const platformColIdx = cells.findIndex((c) => c.includes('对应端') || c.includes('对应需求端'));
1723
- if (platformColIdx >= 0) {
1779
+ const pIdx = cells.findIndex((c) => c.includes('对应端') || c.includes('对应需求端'));
1780
+ if (pIdx >= 0) {
1724
1781
  headerRowIndex = i;
1725
- headerCells = cells;
1782
+ platformColIdx = pIdx;
1783
+ identifierColIdx = cells.findIndex((c) => c.includes('工程标识') || c.includes('工程'));
1784
+ if (identifierColIdx < 0)
1785
+ identifierColIdx = 0;
1726
1786
  break;
1727
1787
  }
1728
1788
  }
1729
- if (headerRowIndex >= 0) {
1789
+ if (headerRowIndex >= 0 && platformColIdx >= 0) {
1730
1790
  const platforms = [];
1731
1791
  const backendPlatforms = [];
1732
1792
  const seen = new Set();
@@ -1742,8 +1802,8 @@ async function detectPlatformsFromConstitution() {
1742
1802
  if (!line.startsWith('|'))
1743
1803
  break;
1744
1804
  const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
1745
- const projectName = cells[0] || '';
1746
- const platformChinese = cells[5] || cells[cells.length - 1] || '';
1805
+ const projectName = identifierColIdx >= 0 && cells.length > identifierColIdx ? cells[identifierColIdx] : '';
1806
+ const platformChinese = cells.length > platformColIdx ? cells[platformColIdx] : '';
1747
1807
  // 跳过空值和占位符(如「待填写」)
1748
1808
  if (!platformChinese || !projectName)
1749
1809
  continue;
@@ -3057,4 +3117,108 @@ function extractStatusEnumsFromContent(platformContent) {
3057
3117
  }
3058
3118
  return enums;
3059
3119
  }
3120
+ // ═══════════════════════════════════════════════
3121
+ // v8.3.125+: 增量合并 — 避免多次分析覆盖丢失
3122
+ // ═══════════════════════════════════════════════
3123
+ /**
3124
+ * 按 Markdown 标题合并文档内容
3125
+ * v8.3.122+: 新内容中存在的章节替换旧章节,新章节追加,旧文档中未变动的章节保留
3126
+ * v8.3.125+: 从 analyze.ts 迁移到 analyze-engine.ts,供 writePerPlatform/writePerFeature 使用
3127
+ */
3128
+ function mergeDocumentContent(existing, newContent) {
3129
+ if (!existing.trim())
3130
+ return newContent;
3131
+ // 解析文档为标题块
3132
+ function parseSections(text) {
3133
+ const sections = new Map();
3134
+ const lines = text.split('\n');
3135
+ let currentHeading = '';
3136
+ let currentLevel = 0;
3137
+ let currentBody = [];
3138
+ let headerLines = []; // 文档开头无标题的内容
3139
+ for (const line of lines) {
3140
+ const hm = line.match(/^(#{1,3})\s+(.+)/);
3141
+ if (hm) {
3142
+ if (currentHeading) {
3143
+ sections.set(currentHeading, {
3144
+ heading: currentHeading,
3145
+ level: currentLevel,
3146
+ body: currentBody.join('\n'),
3147
+ });
3148
+ }
3149
+ else if (currentBody.length > 0) {
3150
+ headerLines = [...currentBody];
3151
+ }
3152
+ currentHeading = hm[2].trim();
3153
+ currentLevel = hm[1].length;
3154
+ currentBody = [line];
3155
+ }
3156
+ else {
3157
+ currentBody.push(line);
3158
+ }
3159
+ }
3160
+ if (currentHeading) {
3161
+ sections.set(currentHeading, {
3162
+ heading: currentHeading,
3163
+ level: currentLevel,
3164
+ body: currentBody.join('\n'),
3165
+ });
3166
+ }
3167
+ // 保存文档头部内容(如 frontmatter、标题等)
3168
+ if (headerLines.length > 0) {
3169
+ sections.set('__HEADER__', { heading: '__HEADER__', level: 0, body: headerLines.join('\n') });
3170
+ }
3171
+ return sections;
3172
+ }
3173
+ const oldSections = parseSections(existing);
3174
+ const newSections = parseSections(newContent);
3175
+ // 构建合并结果:保留旧文档的头部,按旧文档顺序输出
3176
+ // 对于每个旧章节:如果新文档有同名章节,用新内容替换;否则保留旧内容
3177
+ // 新文档中有但旧文档中没有的章节,追加到最后
3178
+ const result = [];
3179
+ // 先输出头部
3180
+ const header = oldSections.get('__HEADER__');
3181
+ if (header) {
3182
+ result.push(header.body);
3183
+ oldSections.delete('__HEADER__');
3184
+ }
3185
+ newSections.delete('__HEADER__');
3186
+ // 收集旧文档中存在的章节标题顺序
3187
+ const oldOrder = [...oldSections.keys()];
3188
+ const addedNew = new Set();
3189
+ for (const heading of oldOrder) {
3190
+ if (newSections.has(heading)) {
3191
+ result.push(newSections.get(heading).body);
3192
+ addedNew.add(heading);
3193
+ }
3194
+ else {
3195
+ result.push(oldSections.get(heading).body);
3196
+ }
3197
+ }
3198
+ // 追加新文档中旧文档没有的章节
3199
+ for (const [heading, section] of newSections) {
3200
+ if (!addedNew.has(heading)) {
3201
+ result.push(section.body);
3202
+ }
3203
+ }
3204
+ return result.join('\n\n');
3205
+ }
3206
+ /**
3207
+ * 安全写入文件:如果文件已存在,先读取并与新内容按章节合并
3208
+ * v8.3.125+: 替代直接 writeFile,避免多次分析覆盖丢失
3209
+ */
3210
+ async function writeMergedFile(filePath, newContent) {
3211
+ try {
3212
+ if (await (0, fs_extra_1.pathExists)(filePath)) {
3213
+ const existing = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
3214
+ const merged = mergeDocumentContent(existing, newContent);
3215
+ await (0, fs_extra_1.writeFile)(filePath, merged);
3216
+ return;
3217
+ }
3218
+ }
3219
+ catch {
3220
+ // 读取或合并失败,回退到直接写入
3221
+ }
3222
+ await (0, fs_extra_1.writeFile)(filePath, newContent);
3223
+ }
3060
3224
  //# sourceMappingURL=analyze-engine.js.map