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
@@ -83,9 +83,101 @@ const agents_1 = require("../core/agents");
83
83
  const code_graph_1 = require("../core/code-graph");
84
84
  // v7.2.0+: 结构化代码数据提取
85
85
  const structured_extractor_1 = require("../core/structured-extractor");
86
+ /**
87
+ * 按 Markdown 标题合并文档内容
88
+ * v8.3.122+: 新内容中存在的章节替换旧章节,新章节追加,旧文档中未变动的章节保留
89
+ */
90
+ function mergeDocumentContent(existing, newContent) {
91
+ if (!existing.trim())
92
+ return newContent;
93
+ // 解析文档为标题块
94
+ function parseSections(text) {
95
+ const sections = new Map();
96
+ const lines = text.split('\n');
97
+ let currentHeading = '';
98
+ let currentLevel = 0;
99
+ let currentBody = [];
100
+ let headerLines = []; // 文档开头无标题的内容
101
+ for (const line of lines) {
102
+ const hm = line.match(/^(#{1,3})\s+(.+)/);
103
+ if (hm) {
104
+ if (currentHeading) {
105
+ sections.set(currentHeading, {
106
+ heading: currentHeading,
107
+ level: currentLevel,
108
+ body: currentBody.join('\n'),
109
+ });
110
+ }
111
+ else if (currentBody.length > 0) {
112
+ headerLines = [...currentBody];
113
+ }
114
+ currentHeading = hm[2].trim();
115
+ currentLevel = hm[1].length;
116
+ currentBody = [line];
117
+ }
118
+ else {
119
+ currentBody.push(line);
120
+ }
121
+ }
122
+ if (currentHeading) {
123
+ sections.set(currentHeading, {
124
+ heading: currentHeading,
125
+ level: currentLevel,
126
+ body: currentBody.join('\n'),
127
+ });
128
+ }
129
+ // 保存文档头部内容(如 frontmatter、标题等)
130
+ if (headerLines.length > 0) {
131
+ sections.set('__HEADER__', { heading: '__HEADER__', level: 0, body: headerLines.join('\n') });
132
+ }
133
+ return sections;
134
+ }
135
+ const oldSections = parseSections(existing);
136
+ const newSections = parseSections(newContent);
137
+ // 构建合并结果:保留旧文档的头部,按旧文档顺序输出
138
+ // 对于每个旧章节:如果新文档有同名章节,用新内容替换;否则保留旧内容
139
+ // 新文档中有但旧文档中没有的章节,追加到最后
140
+ const result = [];
141
+ // 先输出头部
142
+ const header = oldSections.get('__HEADER__');
143
+ if (header) {
144
+ result.push(header.body);
145
+ oldSections.delete('__HEADER__');
146
+ }
147
+ newSections.delete('__HEADER__');
148
+ // 收集旧文档中存在的章节标题顺序
149
+ const oldOrder = [...oldSections.keys()];
150
+ const addedNew = new Set();
151
+ for (const heading of oldOrder) {
152
+ if (newSections.has(heading)) {
153
+ result.push(newSections.get(heading).body);
154
+ addedNew.add(heading);
155
+ }
156
+ else {
157
+ result.push(oldSections.get(heading).body);
158
+ }
159
+ }
160
+ // 追加新文档中旧文档没有的章节
161
+ for (const [heading, section] of newSections) {
162
+ if (!addedNew.has(heading)) {
163
+ result.push(section.body);
164
+ }
165
+ }
166
+ return result.join('\n\n');
167
+ }
168
+ /** 写入文件,支持 --merge 模式(与现有内容按标题合并) */
169
+ async function writeFileMerged(fp, content, merge) {
170
+ if (merge && await (0, fs_extra_1.pathExists)(fp)) {
171
+ const existing = await (0, fs_extra_2.readFile)(fp, 'utf-8');
172
+ content = mergeDocumentContent(existing, content);
173
+ }
174
+ await (0, fs_extra_1.writeFile)(fp, content);
175
+ }
86
176
  async function analyzeCommand(options) {
87
177
  // v6.97.0+ 修复:全局分析时统一设置 iteration 为 'GLOBAL',避免任何 fallback 到 getDefaultIteration()
88
178
  // 这是根治方案:后面所有 "options.iteration || await getDefaultIteration()" 都会命中 options.iteration
179
+ // v8.3.102+: 向上查找项目根目录(支持在子目录执行)
180
+ const _projectRoot = (0, task_utils_1.findProjectRoot)() || process.cwd();
89
181
  if (options.scope === 'global' && !options.iteration) {
90
182
  options.iteration = 'GLOBAL';
91
183
  }
@@ -321,7 +413,7 @@ async function analyzeCommand(options) {
321
413
  // 自动刷新知识图谱
322
414
  try {
323
415
  const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
324
- await refreshKnowledgeGraph(process.cwd(), iter);
416
+ await refreshKnowledgeGraph(_projectRoot, iter);
325
417
  logger_1.logger.info('🧠 知识图谱已刷新');
326
418
  }
327
419
  catch { }
@@ -355,7 +447,7 @@ async function analyzeCommand(options) {
355
447
  // 自动刷新知识图谱
356
448
  try {
357
449
  const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
358
- await refreshKnowledgeGraph(process.cwd(), iter);
450
+ await refreshKnowledgeGraph(_projectRoot, iter);
359
451
  logger_1.logger.info('🧠 知识图谱已刷新');
360
452
  }
361
453
  catch { }
@@ -375,7 +467,7 @@ async function analyzeCommand(options) {
375
467
  const iterDir = await (0, context_1.getIterationDir)(iter);
376
468
  // Step 1: 提取功能单元清单
377
469
  let units = [];
378
- const unitsCachePath = (0, path_1.join)(process.cwd(), exports.UNITS_CACHE_DIR, iter, 'units.json');
470
+ const unitsCachePath = (0, path_1.join)(_projectRoot, exports.UNITS_CACHE_DIR, iter, 'units.json');
379
471
  if (await (0, fs_extra_1.pathExists)(unitsCachePath)) {
380
472
  try {
381
473
  units = JSON.parse(await (0, fs_extra_2.readFile)(unitsCachePath, 'utf-8'));
@@ -495,7 +587,7 @@ async function analyzeCommand(options) {
495
587
  // 命令前索引新鲜度检查(非阻塞)
496
588
  // v6.97.0+ 修复:全局分析时不检查迭代级索引,避免误导用户
497
589
  if (options.scope !== 'global') {
498
- await (0, index_guard_1.warnIfIndexStale)(process.cwd(), 'analyze', options.iteration);
590
+ await (0, index_guard_1.warnIfIndexStale)(_projectRoot, 'analyze', options.iteration);
499
591
  }
500
592
  // v8.3.0+: 需求澄清检测状态(跨代码块共享)
501
593
  let needsClarify = false;
@@ -658,7 +750,7 @@ async function analyzeCommand(options) {
658
750
  // 全局分析:预创建 .speccore/GLOBAL/ 目录结构,不写迭代目录
659
751
  // v6.81.0+: 需求文档单独放在 requirements/ 下,技术文档放在 platforms/ 下
660
752
  // v7.2.0+: 全局技术文档统一放在 overview/ 子目录下,与迭代层 020-specs/overview/ 命名一致
661
- const globalDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
753
+ const globalDir = (0, path_1.join)(_projectRoot, '.speccore', 'GLOBAL');
662
754
  await (0, fs_extra_1.ensureDir)(globalDir);
663
755
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(globalDir, 'overview'));
664
756
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(globalDir, 'platforms'));
@@ -736,7 +828,7 @@ async function analyzeCommand(options) {
736
828
  let platformOrder;
737
829
  if (!isGlobalScope && iter) {
738
830
  // 变更感知:检测 Git 变更影响的端
739
- affectedPlatforms = await (0, change_detection_1.detectAffectedPlatforms)(process.cwd());
831
+ affectedPlatforms = await (0, change_detection_1.detectAffectedPlatforms)(_projectRoot);
740
832
  // 关键路径优先:按任务优先级排序端
741
833
  platformOrder = await (0, change_detection_1.detectPlatformPriorityOrder)(iter);
742
834
  }
@@ -750,7 +842,7 @@ async function analyzeCommand(options) {
750
842
  }
751
843
  else {
752
844
  // 迭代层:使用 createAnalyzePipeline(支持契约先行 + 逐端推进 + 变更感知 + 关键路径优先 + 需求澄清)
753
- const result = await (0, pipeline_engine_1.createAnalyzePipeline)(iter, process.cwd(), {
845
+ const result = await (0, pipeline_engine_1.createAnalyzePipeline)(iter, _projectRoot, {
754
846
  affectedPlatforms: affectedPlatforms && affectedPlatforms.length > 0 ? affectedPlatforms : undefined,
755
847
  platformOrder: platformOrder && platformOrder.length > 0 ? platformOrder : undefined,
756
848
  skipClarify: options.skipClarify,
@@ -762,10 +854,10 @@ async function analyzeCommand(options) {
762
854
  initStep = options.skipClarify ? 'phase1-prompt' : 'clarify-prompt';
763
855
  }
764
856
  // 检查是否有活跃的 Pipeline(恢复模式)
765
- const hasActive = await pipeline_engine_1.PipelineEngine.hasActivePipeline(process.cwd(), pipelineKey);
857
+ const hasActive = await pipeline_engine_1.PipelineEngine.hasActivePipeline(_projectRoot, pipelineKey);
766
858
  let currentStep;
767
859
  if (hasActive) {
768
- const existingState = await pipeline_engine_1.PipelineEngine.loadExistingState(process.cwd(), pipelineKey);
860
+ const existingState = await pipeline_engine_1.PipelineEngine.loadExistingState(_projectRoot, pipelineKey);
769
861
  currentStep = existingState?.currentStep || initStep;
770
862
  logger_1.logger.info(`🔄 恢复 Pipeline: ${currentStep}`);
771
863
  }
@@ -967,7 +1059,7 @@ ${singlePrompt}`);
967
1059
  const fp = (0, path_1.join)(goldenDir, (0, path_1.basename)(filename));
968
1060
  await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(fp));
969
1061
  await (0, fs_extra_1.writeFile)(fp, finalContent);
970
- logger_1.logger.info(` ✅ 澄清文档已写入: ${(0, path_1.relative)(process.cwd(), fp)}`);
1062
+ logger_1.logger.info(` ✅ 澄清文档已写入: ${(0, path_1.relative)(_projectRoot, fp)}`);
971
1063
  }
972
1064
  logger_1.logger.info('');
973
1065
  }
@@ -1039,13 +1131,13 @@ ${singlePrompt}`);
1039
1131
  }
1040
1132
  else if (isGlobalScope) {
1041
1133
  // 全局级:写入 .speccore/GLOBAL/(与 platforms/ 同级,非迭代目录)
1042
- const globalBaseDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
1134
+ const globalBaseDir = (0, path_1.join)(_projectRoot, '.speccore', 'GLOBAL');
1043
1135
  await (0, fs_extra_1.ensureDir)(globalBaseDir);
1044
1136
  const globalSet = new Set(spec_paths_1.GLOBAL_SPEC_FILES);
1045
1137
  for (const [filename, content] of Object.entries(docs)) {
1046
1138
  // PATTERNS/ 文件特殊处理 → 写入 .speccore/PATTERNS/
1047
1139
  if (filename.startsWith('PATTERNS/')) {
1048
- const patternsDir = (0, path_1.join)(process.cwd(), '.speccore', 'PATTERNS');
1140
+ const patternsDir = (0, path_1.join)(_projectRoot, '.speccore', 'PATTERNS');
1049
1141
  await (0, fs_extra_1.ensureDir)(patternsDir);
1050
1142
  const patternFile = filename.slice('PATTERNS/'.length);
1051
1143
  const fp = (0, path_1.join)(patternsDir, patternFile);
@@ -1186,7 +1278,7 @@ ${singlePrompt}`);
1186
1278
  }
1187
1279
  // v6.72.0+: PATTERNS/ 文件特殊处理 → 写入 .speccore/PATTERNS/
1188
1280
  if (filename.startsWith('PATTERNS/')) {
1189
- const patternsDir = (0, path_1.join)(process.cwd(), '.speccore', 'PATTERNS');
1281
+ const patternsDir = (0, path_1.join)(_projectRoot, '.speccore', 'PATTERNS');
1190
1282
  await (0, fs_extra_1.ensureDir)(patternsDir);
1191
1283
  const patternFile = filename.slice('PATTERNS/'.length);
1192
1284
  const fp = (0, path_1.join)(patternsDir, patternFile);
@@ -1215,7 +1307,7 @@ ${singlePrompt}`);
1215
1307
  const targetDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
1216
1308
  await (0, fs_extra_1.ensureDir)(targetDir);
1217
1309
  const fp = (0, path_1.join)(targetDir, parts.slice(1).join('/'));
1218
- if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1310
+ if (!options.merge && !(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1219
1311
  logger_1.logger.info(` ⏭️ 跳过: ${filename}`);
1220
1312
  continue;
1221
1313
  }
@@ -1224,7 +1316,9 @@ ${singlePrompt}`);
1224
1316
  backups.push(bk);
1225
1317
  logger_1.logger.info(` 📦 ${filename} 旧版已备份: ${(0, path_1.basename)(bk)}`);
1226
1318
  }
1227
- await (0, fs_extra_1.writeFile)(fp, content);
1319
+ await writeFileMerged(fp, content, !!options.merge);
1320
+ if (options.merge)
1321
+ logger_1.logger.info(` 🔀 ${filename} 已合并`);
1228
1322
  count++;
1229
1323
  continue;
1230
1324
  }
@@ -1235,7 +1329,7 @@ ${singlePrompt}`);
1235
1329
  const targetDir = (0, path_1.join)(specDir, ...parts.slice(0, -1));
1236
1330
  await (0, fs_extra_1.ensureDir)(targetDir);
1237
1331
  const fp = (0, path_1.join)(targetDir, parts[parts.length - 1]);
1238
- if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1332
+ if (!options.merge && !(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1239
1333
  logger_1.logger.info(` ⏭️ 跳过: ${filename}`);
1240
1334
  continue;
1241
1335
  }
@@ -1244,7 +1338,9 @@ ${singlePrompt}`);
1244
1338
  backups.push(bk);
1245
1339
  logger_1.logger.info(` 📦 ${filename} 旧版已备份: ${(0, path_1.basename)(bk)}`);
1246
1340
  }
1247
- await (0, fs_extra_1.writeFile)(fp, content);
1341
+ await writeFileMerged(fp, content, !!options.merge);
1342
+ if (options.merge)
1343
+ logger_1.logger.info(` 🔀 ${filename} 已合并`);
1248
1344
  count++;
1249
1345
  continue;
1250
1346
  }
@@ -1282,7 +1378,7 @@ ${singlePrompt}`);
1282
1378
  }
1283
1379
  await (0, fs_extra_1.ensureDir)(targetDir);
1284
1380
  const fp = (0, path_1.join)(targetDir, cleanFilename);
1285
- if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1381
+ if (!options.merge && !(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
1286
1382
  logger_1.logger.info(` ⏭️ 跳过: ${filename}`);
1287
1383
  continue;
1288
1384
  }
@@ -1291,7 +1387,9 @@ ${singlePrompt}`);
1291
1387
  backups.push(bk);
1292
1388
  logger_1.logger.info(` 📦 ${filename} 旧版已备份: ${(0, path_1.basename)(bk)}`);
1293
1389
  }
1294
- await (0, fs_extra_1.writeFile)(fp, content);
1390
+ await writeFileMerged(fp, content, !!options.merge);
1391
+ if (options.merge)
1392
+ logger_1.logger.info(` 🔀 ${filename} 已合并`);
1295
1393
  count++;
1296
1394
  }
1297
1395
  if (skippedCount > 0) {
@@ -1356,7 +1454,7 @@ ${singlePrompt}`);
1356
1454
  await sanitizeSpecDirectories(iterDir);
1357
1455
  // v8.3.14+: analyze --apply 后自动清理归档文件和临时文件
1358
1456
  await (0, cleanup_1.cleanupByType)({
1359
- cwd: process.cwd(),
1457
+ cwd: _projectRoot,
1360
1458
  types: ['archiveFiles', 'archiveDirs', 'tempFiles'],
1361
1459
  days: 0, // 不限天数,清理所有归档和临时文件
1362
1460
  silent: true,
@@ -1476,12 +1574,12 @@ ${singlePrompt}`);
1476
1574
  logger_1.logger.success(`✅ ${count} 个 Spec 文档已写入 ${options.task}${platformLabel}/(任务级,迭代基线不变)`);
1477
1575
  }
1478
1576
  else if (isGlobalScope) {
1479
- const globalBaseDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
1577
+ const globalBaseDir = (0, path_1.join)(_projectRoot, '.speccore', 'GLOBAL');
1480
1578
  await (0, fs_extra_1.ensureDir)(globalBaseDir);
1481
1579
  const globalSet = new Set(spec_paths_1.GLOBAL_SPEC_FILES);
1482
1580
  for (const [filename, content] of Object.entries(docs)) {
1483
1581
  if (filename.startsWith('PATTERNS/')) {
1484
- const patternsDir = (0, path_1.join)(process.cwd(), '.speccore', 'PATTERNS');
1582
+ const patternsDir = (0, path_1.join)(_projectRoot, '.speccore', 'PATTERNS');
1485
1583
  await (0, fs_extra_1.ensureDir)(patternsDir);
1486
1584
  const patternFile = filename.slice('PATTERNS/'.length);
1487
1585
  const fp = (0, path_1.join)(patternsDir, patternFile);
@@ -1576,7 +1674,7 @@ ${singlePrompt}`);
1576
1674
  continue;
1577
1675
  }
1578
1676
  if (filename.startsWith('PATTERNS/')) {
1579
- const patternsDir = (0, path_1.join)(process.cwd(), '.speccore', 'PATTERNS');
1677
+ const patternsDir = (0, path_1.join)(_projectRoot, '.speccore', 'PATTERNS');
1580
1678
  await (0, fs_extra_1.ensureDir)(patternsDir);
1581
1679
  const patternFile = filename.slice('PATTERNS/'.length);
1582
1680
  const fp = (0, path_1.join)(patternsDir, patternFile);
@@ -1639,7 +1737,7 @@ ${singlePrompt}`);
1639
1737
  await sanitizeSpecDirectories(iterDir);
1640
1738
  // v8.3.14+: analyze --apply 后自动清理归档文件和临时文件
1641
1739
  await (0, cleanup_1.cleanupByType)({
1642
- cwd: process.cwd(),
1740
+ cwd: _projectRoot,
1643
1741
  types: ['archiveFiles', 'archiveDirs', 'tempFiles'],
1644
1742
  days: 0,
1645
1743
  silent: true,
@@ -1648,7 +1746,7 @@ ${singlePrompt}`);
1648
1746
  // v8.2.0+: 单元分析结果保存到缓存(供 --consolidate 汇总使用)
1649
1747
  if (options.unit && options.iteration) {
1650
1748
  try {
1651
- const unitsCachePath = (0, path_1.join)(process.cwd(), exports.UNITS_CACHE_DIR, options.iteration, 'units.json');
1749
+ const unitsCachePath = (0, path_1.join)(_projectRoot, exports.UNITS_CACHE_DIR, options.iteration, 'units.json');
1652
1750
  let unitName = options.unit;
1653
1751
  if (await (0, fs_extra_1.pathExists)(unitsCachePath)) {
1654
1752
  const allUnits = JSON.parse(await (0, fs_extra_2.readFile)(unitsCachePath, 'utf-8'));
@@ -1702,7 +1800,7 @@ ${singlePrompt}`);
1702
1800
  }
1703
1801
  else if (isGlobalScope) {
1704
1802
  // 全局级:写 .speccore/GLOBAL/ANALYSIS.md
1705
- const globalDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
1803
+ const globalDir = (0, path_1.join)(_projectRoot, '.speccore', 'GLOBAL');
1706
1804
  await (0, fs_extra_1.ensureDir)(globalDir);
1707
1805
  const globalAnalysisPath = (0, path_1.join)(globalDir, 'ANALYSIS.md');
1708
1806
  if (await (0, task_utils_1.shouldOverwrite)(globalAnalysisPath, !!options.interactive)) {
@@ -1762,7 +1860,7 @@ ${singlePrompt}`);
1762
1860
  if (options.iteration) {
1763
1861
  try {
1764
1862
  const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
1765
- await refreshKnowledgeGraph(process.cwd(), options.iteration);
1863
+ await refreshKnowledgeGraph(_projectRoot, options.iteration);
1766
1864
  logger_1.logger.info('🧠 知识图谱已刷新');
1767
1865
  }
1768
1866
  catch { }
@@ -1777,7 +1875,7 @@ ${singlePrompt}`);
1777
1875
  if (await (0, fs_extra_1.pathExists)(reqDir))
1778
1876
  dirs.push(reqDir);
1779
1877
  if (dirs.length > 0) {
1780
- await indexDirectoryDocuments(process.cwd(), dirs, `${options.iteration}_iteration_all`, `rag-index-${options.iteration}.json`);
1878
+ await indexDirectoryDocuments(_projectRoot, dirs, `${options.iteration}_iteration_all`, `rag-index-${options.iteration}.json`);
1781
1879
  logger_1.logger.info('🔍 迭代 RAG 索引已刷新');
1782
1880
  }
1783
1881
  }
@@ -1790,7 +1888,7 @@ ${singlePrompt}`);
1790
1888
  const pipelineKey = isGlobalScope ? 'GLOBAL' : options.iteration;
1791
1889
  if (!pipelineKey)
1792
1890
  return;
1793
- const hasPipeline = await pipeline_engine_1.PipelineEngine.hasActivePipeline(process.cwd(), pipelineKey);
1891
+ const hasPipeline = await pipeline_engine_1.PipelineEngine.hasActivePipeline(_projectRoot, pipelineKey);
1794
1892
  if (hasPipeline) {
1795
1893
  const { createAnalyzePipeline, createGlobalAnalyzePipeline } = await Promise.resolve().then(() => __importStar(require('../core/pipeline-engine')));
1796
1894
  // v6.69.0+: 推进时保持与初始化时相同的过滤和排序条件
@@ -1800,9 +1898,9 @@ ${singlePrompt}`);
1800
1898
  }
1801
1899
  else {
1802
1900
  const { detectAffectedPlatforms, detectPlatformPriorityOrder } = await Promise.resolve().then(() => __importStar(require('../core/change-detection')));
1803
- const affectedPlatforms = await detectAffectedPlatforms(process.cwd());
1901
+ const affectedPlatforms = await detectAffectedPlatforms(_projectRoot);
1804
1902
  const platformOrder = await detectPlatformPriorityOrder(options.iteration);
1805
- engine = (await createAnalyzePipeline(options.iteration, process.cwd(), {
1903
+ engine = (await createAnalyzePipeline(options.iteration, _projectRoot, {
1806
1904
  affectedPlatforms: affectedPlatforms.length > 0 ? affectedPlatforms : undefined,
1807
1905
  platformOrder: platformOrder.length > 0 ? platformOrder : undefined,
1808
1906
  })).engine;
@@ -2444,7 +2542,7 @@ async function injectGraphSummary(prompt) {
2444
2542
  if (_graphSummaryCache && Date.now() - _graphSummaryCacheTime < GRAPH_SUMMARY_TTL) {
2445
2543
  return prompt + _graphSummaryCache;
2446
2544
  }
2447
- const cg = await (0, code_graph_1.loadCodeGraph)(process.cwd());
2545
+ const cg = await (0, code_graph_1.loadCodeGraph)(((0, task_utils_1.findProjectRoot)() || process.cwd()));
2448
2546
  if (!cg)
2449
2547
  return prompt;
2450
2548
  const lines = [];
@@ -2495,7 +2593,7 @@ async function injectGraphSummary(prompt) {
2495
2593
  // ── v7.2.0+: 检测全局分析当前进度 ──
2496
2594
  // Layer 4 拆分为子层: 4a=产品文档, 4b=全局技术核心, 4c=全局技术扩展, 4d=各端技术
2497
2595
  async function detectGlobalLayerProgress() {
2498
- const globalDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
2596
+ const globalDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'GLOBAL');
2499
2597
  let completedLayer = 0;
2500
2598
  const missing = [];
2501
2599
  // Layer 1: 检查各端 _INDEX.md
@@ -2695,7 +2793,7 @@ async function buildGlobalAnalysisGuide(options) {
2695
2793
  // 下一步命令
2696
2794
  guide += '\n➡️ 下一步:\n';
2697
2795
  if (deepDoc && options?.iterative) {
2698
- const outlinePath = (0, path_1.join)(process.cwd(), '.speccore', 'cache', `deep-outline-${deepDoc.replace(/\//g, '-')}.md`);
2796
+ const outlinePath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'cache', `deep-outline-${deepDoc.replace(/\//g, '-')}.md`);
2699
2797
  const hasOutline = await (0, fs_extra_1.pathExists)(outlinePath);
2700
2798
  if (!hasOutline) {
2701
2799
  guide += ` 1. 将 AI 输出的大纲保存到: ${outlinePath}\n`;
@@ -2858,11 +2956,13 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
2858
2956
  const targetLayer = options?.layer || progress.nextLayer;
2859
2957
  const isLayered = !!options?.layer || progress.completedLayer < 4;
2860
2958
  // v7.2.0+: 结构化代码数据提取 — Layer 1 之前自动执行
2959
+ // v8.3.125+: 按迭代涉及的端筛选源码路径(避免全量扫描无关端)
2861
2960
  let structuredDataHint = '';
2862
2961
  if (targetLayer === 1 && ctx.withCode) {
2863
2962
  try {
2963
+ const projectRoot = (0, task_utils_1.findProjectRoot)() || process.cwd();
2864
2964
  // 读取 CONSTITUTION.md 获取源码路径
2865
- const constitutionPath = (0, path_1.join)(process.cwd(), '.speccore', 'CONSTITUTION.md');
2965
+ const constitutionPath = (0, path_1.join)(projectRoot, '.speccore', 'CONSTITUTION.md');
2866
2966
  let sourcePaths = ['src'];
2867
2967
  if (await (0, fs_extra_1.pathExists)(constitutionPath)) {
2868
2968
  const content = await (0, fs_extra_2.readFile)(constitutionPath, 'utf-8');
@@ -2871,7 +2971,54 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
2871
2971
  sourcePaths = match.map(m => m.replace(/.*-\s*`?/, '').replace(/`?$/, '').trim()).filter(Boolean);
2872
2972
  }
2873
2973
  }
2874
- await (0, structured_extractor_1.extractStructuredData)(process.cwd(), sourcePaths);
2974
+ // v8.3.125+: 按迭代端名筛选源码路径(宁可多扫不要漏扫)
2975
+ const iterDir = ctx.iteration ? await (0, context_1.getIterationDir)(ctx.iteration) : null;
2976
+ let filteredPaths = sourcePaths;
2977
+ if (iterDir && sourcePaths.length > 1) {
2978
+ try {
2979
+ // 1. 尝试从 PLATFORMS.md 读取端列表
2980
+ let platformNames = [];
2981
+ const platformsPath = (0, path_1.join)(iterDir, '020-specs', 'PLATFORMS.md');
2982
+ if (await (0, fs_extra_1.pathExists)(platformsPath)) {
2983
+ const pContent = await (0, fs_extra_2.readFile)(platformsPath, 'utf-8');
2984
+ const pMatch = pContent.match(/-\s*(\w+)/g);
2985
+ if (pMatch)
2986
+ platformNames = pMatch.map(m => m.replace(/^-\s*/, '').trim().toLowerCase());
2987
+ }
2988
+ // 2. fallback:从 020-specs/ 子目录推断端名
2989
+ if (platformNames.length === 0) {
2990
+ const specsDir = (0, path_1.join)(iterDir, '020-specs');
2991
+ if (await (0, fs_extra_1.pathExists)(specsDir)) {
2992
+ const entries = await (0, fs_extra_2.readdir)(specsDir, { withFileTypes: true });
2993
+ platformNames = entries
2994
+ .filter(e => e.isDirectory() && e.name !== 'overview' && e.name !== 'requirements')
2995
+ .map(e => e.name.toLowerCase());
2996
+ }
2997
+ }
2998
+ // 3. 筛选 sourcePaths(路径包含端名则保留)
2999
+ if (platformNames.length > 0) {
3000
+ const matched = sourcePaths.filter(sp => {
3001
+ const lower = sp.toLowerCase();
3002
+ return platformNames.some(pn => lower.includes(pn));
3003
+ });
3004
+ // 同时保留 shared/common/lib 等公共路径
3005
+ const sharedPaths = sourcePaths.filter(sp => /shared|common|lib|utils|pkg/.test(sp.toLowerCase()));
3006
+ const combined = [...new Set([...matched, ...sharedPaths])];
3007
+ // 宁可多扫不要漏扫:如果筛选后为空,回退全量
3008
+ if (combined.length > 0) {
3009
+ filteredPaths = combined;
3010
+ logger_1.logger.info(` 📂 结构化数据扫描已按端筛选: ${filteredPaths.join(', ')}(涉及端: ${platformNames.join(', ')})`);
3011
+ }
3012
+ else {
3013
+ logger_1.logger.info(` 📂 无法按端筛选,回退全量扫描: ${sourcePaths.join(', ')}`);
3014
+ }
3015
+ }
3016
+ }
3017
+ catch {
3018
+ // 筛选失败不阻断,回退全量
3019
+ }
3020
+ }
3021
+ await (0, structured_extractor_1.extractStructuredData)(projectRoot, filteredPaths);
2875
3022
  structuredDataHint = '\n> 📊 **结构化数据**: 已提取到 `.speccore/cache/structured-data.json`,包含 API/Entity/Route/Component 清单\n';
2876
3023
  }
2877
3024
  catch (e) {
@@ -2909,7 +3056,7 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
2909
3056
  prompt += `- **核心任务**: ${layerMeta.focus}\n`;
2910
3057
  prompt += `- **预期产出**: ${layerMeta.output}\n\n`;
2911
3058
  prompt += `## 要求\n`;
2912
- prompt += `1. **先读宪法**: Read .speccore/CONSTITUTION.md,获取工程名、源码路径、端列表。\n`;
3059
+ prompt += `1. **先读宪法**: Read .speccore/CONSTITUTION.md,获取工程标识、源码路径、端列表。\n`;
2913
3060
  prompt += `2. Read .speccore/GLOBAL/ 下已有文档(特别是前一层的产物)作为输入。\n`;
2914
3061
  prompt += `3. **禁止行为**: 不要打开浏览器、不要模拟用户操作、不要访问 URL。所有分析基于直接 Read 源码。\n`;
2915
3062
  if (ctx.withCode) {
@@ -3044,7 +3191,7 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
3044
3191
  prompt += `**每个功能模块涉及哪些端,就读取那些端的详细源码**:\n\n`;
3045
3192
  // v7.4.5+: CLI 预提取每个功能模块的代码上下文
3046
3193
  try {
3047
- const modulesCtx = await buildLayer3ModuleContext(process.cwd());
3194
+ const modulesCtx = await buildLayer3ModuleContext(((0, task_utils_1.findProjectRoot)() || process.cwd()));
3048
3195
  if (modulesCtx) {
3049
3196
  prompt += `## 📎 CLI 预提取的功能模块代码上下文(v7.4.5+)\n\n`;
3050
3197
  prompt += `> 以下内容由 CLI 从 structured-data.json 和 _MODULES.md 自动提取,你不需要自己搜索。\n`;
@@ -3468,7 +3615,7 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
3468
3615
  const subLayerTarget = progress.subLayer?.next || '4a';
3469
3616
  if (deepDoc) {
3470
3617
  // --deep 模式:单文档深度分析
3471
- const outlinePath = (0, path_1.join)(process.cwd(), '.speccore', 'cache', `deep-outline-${deepDoc.replace(/\//g, '-')}.md`);
3618
+ const outlinePath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'cache', `deep-outline-${deepDoc.replace(/\//g, '-')}.md`);
3472
3619
  const hasOutline = await (0, fs_extra_1.pathExists)(outlinePath);
3473
3620
  if (options?.iterative && !hasOutline) {
3474
3621
  // 迭代模式第一步:输出大纲
@@ -3534,7 +3681,7 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
3534
3681
  }
3535
3682
  else {
3536
3683
  // v7.5.0+: 子层内逐文档生成 — 检测当前子层中哪些文档已存在,每次只生成下一个缺失的
3537
- const globalDir = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL');
3684
+ const globalDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'GLOBAL');
3538
3685
  const overviewDir = (0, path_1.join)(globalDir, 'overview');
3539
3686
  const requirementsDir = (0, path_1.join)(globalDir, 'requirements');
3540
3687
  const platformsDir = (0, path_1.join)(globalDir, 'platforms');
@@ -3588,7 +3735,7 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
3588
3735
  const existingDocs = [];
3589
3736
  const missingDocs = [];
3590
3737
  for (const doc of expectedDocs) {
3591
- const fullPath = (0, path_1.join)(process.cwd(), '.speccore', 'GLOBAL', doc);
3738
+ const fullPath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'GLOBAL', doc);
3592
3739
  if (await (0, fs_extra_1.pathExists)(fullPath)) {
3593
3740
  existingDocs.push(doc);
3594
3741
  }
@@ -4309,9 +4456,9 @@ status: "clarified"
4309
4456
  if (!isGlobal && ctx.withCode && ctx.iteration && ctx.iteration !== 'GLOBAL') {
4310
4457
  try {
4311
4458
  // 1. 提取结构化数据(如果还没有)
4312
- const structuredDataPath = (0, path_1.join)(process.cwd(), '.speccore', 'cache', 'structured-data.json');
4459
+ const structuredDataPath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'cache', 'structured-data.json');
4313
4460
  if (!(await (0, fs_extra_1.pathExists)(structuredDataPath))) {
4314
- const constitutionPath = (0, path_1.join)(process.cwd(), '.speccore', 'CONSTITUTION.md');
4461
+ const constitutionPath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'CONSTITUTION.md');
4315
4462
  let sourcePaths = ['src'];
4316
4463
  if (await (0, fs_extra_1.pathExists)(constitutionPath)) {
4317
4464
  const content = await (0, fs_extra_2.readFile)(constitutionPath, 'utf-8');
@@ -4321,7 +4468,7 @@ status: "clarified"
4321
4468
  }
4322
4469
  }
4323
4470
  const { extractStructuredData } = await Promise.resolve().then(() => __importStar(require('../core/structured-extractor')));
4324
- await extractStructuredData(process.cwd(), sourcePaths);
4471
+ await extractStructuredData(((0, task_utils_1.findProjectRoot)() || process.cwd()), sourcePaths);
4325
4472
  }
4326
4473
  // 2. 注入结构化数据摘要到 Prompt
4327
4474
  if (await (0, fs_extra_1.pathExists)(structuredDataPath)) {
@@ -4342,7 +4489,7 @@ status: "clarified"
4342
4489
  if (options?.featureName) {
4343
4490
  const { buildFeatureContext, buildFeatureContextPrompt } = await Promise.resolve().then(() => __importStar(require('../core/semantic-locator')));
4344
4491
  const iterDir = await (0, context_1.getIterationDir)(ctx.iteration);
4345
- const featureCtx = await buildFeatureContext(process.cwd(), iterDir, options.featureName, options.docName);
4492
+ const featureCtx = await buildFeatureContext(((0, task_utils_1.findProjectRoot)() || process.cwd()), iterDir, options.featureName, options.docName);
4346
4493
  prompt += buildFeatureContextPrompt(featureCtx);
4347
4494
  }
4348
4495
  }
@@ -4663,7 +4810,7 @@ status: "clarified"
4663
4810
  prompt += `8. **端发现(重要)**:先确定项目有哪些端,再按端组织文档\n`;
4664
4811
  prompt += ` - 第 1 步:Read .speccore/CONSTITUTION.md\n`;
4665
4812
  prompt += ` - 第 2 步:从「## 端列表」章节提取端名(这是全局权威来源)\n`;
4666
- prompt += ` - 第 3 步:如果没有「端列表」章节,从「对应端」列提取\n`;
4813
+ prompt += ` - 第 3 步:如果没有「端列表」章节,从「对应需求端」列提取\n`;
4667
4814
  prompt += ` - 第 4 步:如果以上都无法确定,根据需求文档内容判断\n`;
4668
4815
  prompt += ` - 第 5 步:将发现的端列表写入 020-specs/PLATFORMS.md\n`;
4669
4816
  // v6.70.0+: REQUIREMENT.md 以产品视角撰写(不按端分章节)
@@ -4908,7 +5055,7 @@ status: "clarified"
4908
5055
  // ── v6.52.0+: 图谱 RAG 上下文注入(analyze 阶段也检索项目关联内容)──
4909
5056
  if (!isTask && ctx.phase !== '2') {
4910
5057
  try {
4911
- const ragResult = await (0, unified_retrieval_1.unifiedSearch)(process.cwd(), {
5058
+ const ragResult = await (0, unified_retrieval_1.unifiedSearch)(((0, task_utils_1.findProjectRoot)() || process.cwd()), {
4912
5059
  query: ctx.iteration || '',
4913
5060
  iteration: ctx.iteration,
4914
5061
  platform: ctx.platform,
@@ -5169,26 +5316,7 @@ async function getSubtaskDirs(taskDir) {
5169
5316
  }
5170
5317
  catch { /* ignore */ }
5171
5318
  }
5172
- // 回退: 旧结构 10-backend/ 和 20-frontend/
5173
- if (result.length === 0) {
5174
- for (const catDir of ['10-backend', '20-frontend']) {
5175
- const catPath = (0, path_1.join)(taskDir, catDir);
5176
- if (!(await (0, fs_extra_1.pathExists)(catPath)))
5177
- continue;
5178
- try {
5179
- for (const svc of await (0, fs_extra_2.readdir)(catPath, { withFileTypes: true })) {
5180
- if (!svc.isDirectory())
5181
- continue;
5182
- for (const sub of await (0, fs_extra_2.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
5183
- if (sub.isDirectory() && !sub.name.startsWith('.')) {
5184
- result.push((0, path_1.join)(catPath, svc.name, sub.name));
5185
- }
5186
- }
5187
- }
5188
- }
5189
- catch { /* ignore */ }
5190
- }
5191
- }
5319
+ // v8.3.121+: 已移除 10-backend/20-frontend 旧结构回退
5192
5320
  return result;
5193
5321
  }
5194
5322
  // ═══════════════════════════════════════════════════════════
@@ -5245,7 +5373,7 @@ async function buildClarifyPhasePrompt(iteration) {
5245
5373
  const hasMultipleUnits = extractedUnits.length >= 2;
5246
5374
  // v6.84.0+: 从 AGENTS 规范数据库动态加载角色
5247
5375
  let prompt = `\n# 任务: 需求专业化(Phase 0: 需求澄清,v6.84.0+)\n\n`;
5248
- const projectRoot = (0, task_utils_1.findProjectRoot)() || process.cwd();
5376
+ const projectRoot = (0, task_utils_1.findProjectRoot)() || ((0, task_utils_1.findProjectRoot)() || process.cwd());
5249
5377
  const agentContext = {
5250
5378
  iteration,
5251
5379
  iterationDir: iterDir,
@@ -5578,14 +5706,14 @@ async function extractIterationUnits(iterDir) {
5578
5706
  }
5579
5707
  /** 保存单元分析结果到缓存 */
5580
5708
  async function saveUnitAnalysis(iteration, analysis) {
5581
- const cacheDir = (0, path_1.join)(process.cwd(), exports.UNITS_CACHE_DIR, iteration);
5709
+ const cacheDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), exports.UNITS_CACHE_DIR, iteration);
5582
5710
  await (0, fs_extra_1.ensureDir)(cacheDir);
5583
5711
  const fp = (0, path_1.join)(cacheDir, `${analysis.id}.json`);
5584
5712
  await (0, fs_extra_1.writeFile)(fp, JSON.stringify(analysis, null, 2));
5585
5713
  }
5586
5714
  /** 读取已完成的单元分析 */
5587
5715
  async function loadUnitAnalyses(iteration) {
5588
- const cacheDir = (0, path_1.join)(process.cwd(), exports.UNITS_CACHE_DIR, iteration);
5716
+ const cacheDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), exports.UNITS_CACHE_DIR, iteration);
5589
5717
  if (!await (0, fs_extra_1.pathExists)(cacheDir))
5590
5718
  return [];
5591
5719
  const files = (await (0, fs_extra_2.readdir)(cacheDir)).filter(f => f.endsWith('.json'));