speccore 8.3.221 → 8.3.223

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 (55) hide show
  1. package/dist/cli.js +29 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/analyze.d.ts.map +1 -1
  4. package/dist/commands/analyze.js +491 -238
  5. package/dist/commands/analyze.js.map +1 -1
  6. package/dist/commands/change.js +1 -0
  7. package/dist/commands/change.js.map +1 -1
  8. package/dist/commands/doc2spec.d.ts.map +1 -1
  9. package/dist/commands/doc2spec.js +205 -95
  10. package/dist/commands/doc2spec.js.map +1 -1
  11. package/dist/commands/execute.js +4 -4
  12. package/dist/commands/execute.js.map +1 -1
  13. package/dist/commands/iteration/split.d.ts.map +1 -1
  14. package/dist/commands/iteration/split.js +29 -182
  15. package/dist/commands/iteration/split.js.map +1 -1
  16. package/dist/commands/plan.d.ts.map +1 -1
  17. package/dist/commands/plan.js +67 -11
  18. package/dist/commands/plan.js.map +1 -1
  19. package/dist/commands/req-doc.d.ts.map +1 -1
  20. package/dist/commands/req-doc.js +122 -14
  21. package/dist/commands/req-doc.js.map +1 -1
  22. package/dist/commands/retro.d.ts +1 -1
  23. package/dist/commands/retro.d.ts.map +1 -1
  24. package/dist/commands/retro.js +87 -8
  25. package/dist/commands/retro.js.map +1 -1
  26. package/dist/commands/run.d.ts +1 -0
  27. package/dist/commands/run.d.ts.map +1 -1
  28. package/dist/commands/run.js +182 -14
  29. package/dist/commands/run.js.map +1 -1
  30. package/dist/core/agent-adapter.d.ts.map +1 -1
  31. package/dist/core/agent-adapter.js +13 -6
  32. package/dist/core/agent-adapter.js.map +1 -1
  33. package/dist/core/context-budget.d.ts +3 -2
  34. package/dist/core/context-budget.d.ts.map +1 -1
  35. package/dist/core/context-budget.js +16 -3
  36. package/dist/core/context-budget.js.map +1 -1
  37. package/dist/core/dispatch-loop.d.ts +81 -0
  38. package/dist/core/dispatch-loop.d.ts.map +1 -0
  39. package/dist/core/dispatch-loop.js +152 -0
  40. package/dist/core/dispatch-loop.js.map +1 -0
  41. package/dist/core/prompt-builder.d.ts.map +1 -1
  42. package/dist/core/prompt-builder.js +20 -0
  43. package/dist/core/prompt-builder.js.map +1 -1
  44. package/dist/core/requirement-clarifier.d.ts.map +1 -1
  45. package/dist/core/requirement-clarifier.js +36 -0
  46. package/dist/core/requirement-clarifier.js.map +1 -1
  47. package/dist/core/state.d.ts +3 -0
  48. package/dist/core/state.d.ts.map +1 -1
  49. package/dist/core/state.js +19 -1
  50. package/dist/core/state.js.map +1 -1
  51. package/dist/core/unit-context-assembler.d.ts +5 -0
  52. package/dist/core/unit-context-assembler.d.ts.map +1 -1
  53. package/dist/core/unit-context-assembler.js +225 -9
  54. package/dist/core/unit-context-assembler.js.map +1 -1
  55. package/package.json +2 -2
@@ -76,6 +76,8 @@ const spec_paths_1 = require("../core/spec-paths");
76
76
  const spec_skeleton_1 = require("../core/spec-skeleton");
77
77
  const unified_retrieval_1 = require("../core/unified-retrieval");
78
78
  const unit_context_assembler_1 = require("../core/unit-context-assembler");
79
+ // v8.3.225+: 调度循环状态机(方式 Y 宿主会话内闭环)
80
+ const dispatch_loop_1 = require("../core/dispatch-loop");
79
81
  const pipeline_engine_1 = require("../core/pipeline-engine");
80
82
  const change_detection_1 = require("../core/change-detection");
81
83
  const streaming_analyzer_1 = require("../core/streaming-analyzer");
@@ -190,6 +192,25 @@ async function resolveClarifyMode(options) {
190
192
  catch { /* 配置读取失败时回退默认 */ }
191
193
  return 'auto';
192
194
  }
195
+ /**
196
+ * v8.3.224+: 收集新路径单元级澄清文档 020-specs/{feature}/overview/CLARIFY.md
197
+ * (feature 名与 010-requirements/features/ 切分目录一一对应,是澄清产物收敛后的标准位置)
198
+ */
199
+ async function collectUnitClarifyDocs(specDir) {
200
+ const docs = [];
201
+ try {
202
+ const entries = await (0, fs_extra_2.readdir)(specDir, { withFileTypes: true });
203
+ for (const e of entries) {
204
+ if (!e.isDirectory() || e.name === 'overview' || e.name === 'requirements')
205
+ continue;
206
+ const clarify = (0, path_1.join)(specDir, e.name, 'overview', 'CLARIFY.md');
207
+ if (await (0, fs_extra_1.pathExists)(clarify))
208
+ docs.push(clarify);
209
+ }
210
+ }
211
+ catch { }
212
+ return docs;
213
+ }
193
214
  /** 写入文件,支持 --merge 模式(与现有内容按标题合并) */
194
215
  async function writeFileMerged(fp, content, merge) {
195
216
  if (merge && await (0, fs_extra_1.pathExists)(fp)) {
@@ -698,6 +719,19 @@ async function analyzeCommand(options) {
698
719
  targetUnitIds = [pending[0].id];
699
720
  logger_1.logger.info(`🔄 断点续跑: ${pending[0].id} ${pending[0].name}(剩余 ${pending.length} 个)`);
700
721
  }
722
+ // 3d. v8.3.225+: --auto-units / --resume-units 调度循环模式(方式 Y 宿主会话内闭环)
723
+ // CLI 每轮推进状态机:校验产出 → 已就绪自动标记并推进,未就绪输出该单元 prompt(exitCode 10)
724
+ // → 宿主 AI 落盘 → 再次调用本命令 → CLI 校验通过自动推进下一个单元,直到全部完成
725
+ if (options.autoUnits || options.resumeUnits) {
726
+ const loopResult = await runUnitDispatchLoop(iter, iterDir, units);
727
+ if (loopResult.finished) {
728
+ printUnitLoopSummary(iter, loopResult);
729
+ return;
730
+ }
731
+ process.stdout.write(`[SPECCORE_PROMPT]\n${loopResult.prompt}\n\n## 完成后继续\n\n落盘后运行以下命令,CLI 将校验产出并自动推进到下一个单元:\n\n\`\`\`bash\nspeccore analyze --resume-units -I ${iter}\n\`\`\`\n`);
732
+ process.exitCode = 10;
733
+ return;
734
+ }
701
735
  // 3c. --unit: 指定单元(支持逗号分隔多个,如 M-01,M-03)
702
736
  if (options.unit) {
703
737
  targetUnitIds = options.unit.split(',').map(s => s.trim()).filter(Boolean);
@@ -849,15 +883,71 @@ async function analyzeCommand(options) {
849
883
  const reqDir = (0, path_1.join)(iterDir, '010-requirements');
850
884
  const specDir = (0, path_1.join)(iterDir, '020-specs');
851
885
  await (0, fs_extra_1.ensureDir)(specDir);
852
- // 该目录存放经过 clarify 后的专业需求,作为分析和后续步骤的唯一依据
886
+ // 该目录存放经过 clarify 后的专业需求(黄金需求),作为分析和后续步骤的优先依据;
887
+ // 为空时回退到 010-requirements/ 原始需求(不澄清是官方支持的三态之一,非异常路径)
853
888
  const requirements = [];
854
889
  const goldenReqDir = (0, path_1.join)(specDir, 'requirements');
855
890
  const hasGoldenDocs = await (0, fs_extra_1.pathExists)(goldenReqDir);
856
891
  if (hasGoldenDocs) {
857
892
  try {
858
893
  const goldenFiles = await (0, fs_extra_2.readdir)(goldenReqDir);
894
+ // v8.3.224+: 黄金需求消费分层——单元级(unit-*.md)与文档级(*-clarified.md)分开收集:
895
+ // 单元级与切分目录一一对应,优先读入;文档级仅在单元级覆盖不足时兜底,避免内容重复注入
896
+ const unitLevel = [];
897
+ const docLevel = [];
859
898
  for (const f of goldenFiles.filter((f) => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))) {
860
- requirements.push((0, path_1.join)(goldenReqDir, f));
899
+ if (f.startsWith('unit-'))
900
+ unitLevel.push((0, path_1.join)(goldenReqDir, f));
901
+ else if (f.endsWith('-clarified.md'))
902
+ docLevel.push((0, path_1.join)(goldenReqDir, f));
903
+ else
904
+ requirements.push((0, path_1.join)(goldenReqDir, f));
905
+ }
906
+ // 新路径两级澄清产物({feature}/overview/CLARIFY.md)与切分目录一一对应,优先级最高
907
+ const clarifyUnitDocs = await collectUnitClarifyDocs(specDir);
908
+ const featureUnits = await (0, spec_paths_1.parseFeatureList)(iterDir);
909
+ if (featureUnits.length > 0 && (clarifyUnitDocs.length > 0 || unitLevel.length > 0)) {
910
+ // 有切分锚点:按单元优先读入一一对应的澄清文档,跳过冗余的文档级
911
+ const legacyUnitName = (f) => (0, path_1.basename)(f).replace(/^unit-/, '').replace(/\.md$/, '');
912
+ const newUnitNames = new Set(clarifyUnitDocs.map((p) => (0, path_1.basename)((0, path_1.dirname)((0, path_1.dirname)(p)))));
913
+ for (const p of clarifyUnitDocs)
914
+ requirements.push(p);
915
+ for (const f of unitLevel) {
916
+ // 新路径已覆盖该单元时跳过旧路径,避免同一单元内容双份注入
917
+ if (!newUnitNames.has(legacyUnitName(f)))
918
+ requirements.push(f);
919
+ }
920
+ const covered = featureUnits.filter((u) => newUnitNames.has(u) || unitLevel.some((f) => legacyUnitName(f) === u));
921
+ if (covered.length >= Math.ceil(featureUnits.length / 2)) {
922
+ logger_1.logger.info(` 📝 单元级澄清覆盖 ${covered.length}/${featureUnits.length} 个功能单元,跳过文档级澄清避免重复注入`);
923
+ }
924
+ else {
925
+ requirements.push(...docLevel);
926
+ }
927
+ }
928
+ else {
929
+ // 无切分锚点(老项目):维持原行为全量读入
930
+ requirements.push(...unitLevel, ...docLevel);
931
+ }
932
+ }
933
+ catch { }
934
+ // v8.3.223+: 黄金需求(旧路径平铺命名)与功能单元(features/ 目录)命名对齐校验——
935
+ // features/ 单元名决定 020-specs 骨架,黄金需求文件名决定内容来源,
936
+ // 两者不对齐时 taskify 无法把澄清内容挂接到对应单元
937
+ try {
938
+ const featureUnits = await (0, spec_paths_1.parseFeatureList)(iterDir);
939
+ if (featureUnits.length > 0) {
940
+ const goldenBases = (await (0, fs_extra_2.readdir)(goldenReqDir))
941
+ .filter((f) => f.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(f))
942
+ .map((f) => f.replace(/\.md$/, '').replace(/^unit-/, '').replace(/-clarified$/, '')
943
+ .trim().replace(/\s+/g, '-').replace(/[^\w\u4e00-\u9fa5\-]/g, ''));
944
+ const matched = featureUnits.filter((u) => goldenBases.some((g) => g && (u === g || u.startsWith(`${g}-`) || u.includes(g))));
945
+ if (matched.length === 0) {
946
+ logger_1.logger.warn(` ⚠️ 黄金需求(020-specs/requirements/)与功能单元(010-requirements/features/)命名不对齐:`);
947
+ logger_1.logger.warn(` 黄金需求: ${goldenBases.slice(0, 5).join(', ')}`);
948
+ logger_1.logger.warn(` 功能单元: ${featureUnits.slice(0, 5).join(', ')}`);
949
+ logger_1.logger.warn(` taskify 可能无法将澄清内容挂接到对应单元——建议使用新路径澄清(020-specs/{单元}/overview/CLARIFY.md)`);
950
+ }
861
951
  }
862
952
  }
863
953
  catch { }
@@ -918,6 +1008,16 @@ async function analyzeCommand(options) {
918
1008
  }
919
1009
  catch { }
920
1010
  }
1011
+ // v8.3.223+: 黄金需求目录为空时也要消费新路径两级澄清产物(v8.3.181+ 的 CLARIFY.md)——
1012
+ // 修复「用两级澄清产出的项目,黄金目录为空 → 回退原始需求 → 澄清白做」的问题
1013
+ const clarifyDocs = await collectUnitClarifyDocs(specDir);
1014
+ const overviewClarify = (0, path_1.join)(specDir, 'overview', 'CLARIFY.md');
1015
+ if (await (0, fs_extra_1.pathExists)(overviewClarify))
1016
+ clarifyDocs.unshift(overviewClarify);
1017
+ if (clarifyDocs.length > 0) {
1018
+ requirements.push(...clarifyDocs);
1019
+ logger_1.logger.info(` 📝 黄金需求目录为空,已附加新路径澄清文档 ${clarifyDocs.length} 个(020-specs/*/overview/CLARIFY.md)`);
1020
+ }
921
1021
  }
922
1022
  else {
923
1023
  logger_1.logger.info(` 📋 使用黄金需求目录: 020-specs/requirements/ (${requirements.length} 个文档)`);
@@ -1096,130 +1196,7 @@ async function analyzeCommand(options) {
1096
1196
  await engine.init(initStep);
1097
1197
  currentStep = initStep;
1098
1198
  }
1099
- // 根据当前步骤生成对应的 prompt
1100
- let prompt;
1101
- const platformMatch = currentStep.match(/^platform-(.+)-prompt$/);
1102
- // v8.3.167+: 检测功能模块步骤
1103
- const featureMatch = currentStep.match(/^feature-(.+)-prompt$/);
1104
- if (isGlobalScope) {
1105
- // 全局层 Pipeline 步骤映射
1106
- if (currentStep === 'init' || currentStep === 'discovery') {
1107
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1108
- iteration: iter || 'GLOBAL', task: options.task, platform: options.platform,
1109
- analyzeOptions: options,
1110
- }));
1111
- }
1112
- else if (currentStep === 'global-analysis') {
1113
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1114
- iteration: iter || 'GLOBAL', analyzeOptions: options,
1115
- }));
1116
- }
1117
- else if (currentStep === 'consistency-check') {
1118
- prompt = `\n# 任务: 全局一致性检查\n\n` +
1119
- `检查各迭代、各端之间的规格是否一致。\n` +
1120
- `重点检查:接口定义冲突、数据模型不一致、命名规范违规。\n`;
1121
- }
1122
- else if (currentStep === 'report-generation') {
1123
- prompt = `\n# 任务: 生成全局索引报告\n\n` +
1124
- `汇总所有分析结果,生成 .speccore/GLOBAL/INDEX.md。\n`;
1125
- }
1126
- else {
1127
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1128
- iteration: iter || 'GLOBAL', analyzeOptions: options,
1129
- }));
1130
- }
1131
- }
1132
- else if (currentStep === 'clarify-product' || currentStep === 'clarify-interaction' || currentStep === 'clarify-security') {
1133
- // v8.3.160+: Phase 0 需求澄清(拆分为 product / interaction / security 三个子步骤)
1134
- prompt = await buildClarifyPhasePrompt(iter);
1135
- }
1136
- else if (currentStep === 'confirm-check') {
1137
- prompt = await buildConfirmCheckPrompt(iter);
1138
- }
1139
- else if (currentStep === 'phase1-prompt') {
1140
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1141
- iteration: iter, task: options.task, platform: options.platform,
1142
- analyzeOptions: options,
1143
- }));
1144
- }
1145
- else if (featureMatch) {
1146
- // v8.3.167+: 功能模块级分析步骤
1147
- const featureName = featureMatch[1];
1148
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1149
- iteration: iter,
1150
- analyzeOptions: { ...options, feature: featureName, phase: '1' },
1151
- }));
1152
- }
1153
- else if (currentStep === 'contract-prompt') {
1154
- // 契约先行阶段:基于 Phase 1 文档生成跨端契约
1155
- prompt = await buildContractFirstPrompt(iter);
1156
- }
1157
- else if (platformMatch) {
1158
- // 逐端推进阶段:为指定端生成专属文档
1159
- const platform = platformMatch[1];
1160
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1161
- iteration: iter, platform,
1162
- analyzeOptions: { ...options, phase: '2' },
1163
- }));
1164
- }
1165
- else {
1166
- // 回退到默认 prompt
1167
- prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
1168
- iteration: iter, task: options.task, platform: options.platform,
1169
- analyzeOptions: options,
1170
- }));
1171
- }
1172
- // 添加 Pipeline 继续指令
1173
- const totalSteps = steps.filter(s => !s.id.endsWith('-done') && s.id !== 'done').length;
1174
- const currentStepIndex = steps.findIndex(s => s.id === currentStep);
1175
- let progressLabel;
1176
- if (isGlobalScope) {
1177
- progressLabel = currentStep === 'init' ? '全局分析: 初始化'
1178
- : currentStep === 'discovery' ? '全局分析: 发现'
1179
- : currentStep === 'global-analysis' ? '全局分析: 跨端分析'
1180
- : currentStep === 'consistency-check' ? '全局分析: 一致性检查'
1181
- : currentStep === 'report-generation' ? '全局分析: 报告生成'
1182
- : '全局 Pipeline';
1183
- }
1184
- else {
1185
- progressLabel = currentStep === 'clarify-product'
1186
- ? 'Phase 0a/3: 产品分析'
1187
- : currentStep === 'clarify-interaction'
1188
- ? 'Phase 0b/3: 交互设计'
1189
- : currentStep === 'clarify-security'
1190
- ? 'Phase 0c/3: 安全审查'
1191
- : currentStep === 'confirm-check'
1192
- ? 'Phase 0/3: 需求确认'
1193
- : currentStep === 'phase1-prompt'
1194
- ? 'Phase 1/3: 迭代综合文档'
1195
- : featureMatch
1196
- ? `Phase 1b/3: ${featureMatch[1]} 功能模块分析`
1197
- : currentStep === 'contract-prompt'
1198
- ? 'Phase 2/3: 契约定义'
1199
- : platformMatch
1200
- ? `Phase 2/3: ${platformMatch[1]} 端专属文档`
1201
- : 'Pipeline';
1202
- }
1203
- // v8.3.160+: 步骤隔离模式,每步完成后需新会话继续
1204
- const finalPrompt = prompt + `\n\n## ⚠️ Pipeline 模式:步骤隔离 (${progressLabel})\n\n` +
1205
- `当前是 Pipeline 模式(步骤 ${currentStepIndex + 1}/${totalSteps})。` +
1206
- `当你通过 --apply 写入文档后,CLI 会自动输出 [SPECCORE_STEP_DONE] 标记和下一步命令。\n\n` +
1207
- `**看到 [SPECCORE_STEP_DONE] 后,请在新会话中执行提示的命令**,继续下一步。\n`;
1208
- // v8.3.166+: Phase 2 端级步骤输出端级 subagent 标记
1209
- // v8.3.167+: 功能模块步骤输出功能模块级 subagent 标记
1210
- const platformSubagent = platformMatch ? `requirement-analyzer-${platformMatch[1]}` : undefined;
1211
- const featureSubagent = featureMatch ? `requirement-analyzer-feature` : undefined;
1212
- if (featureSubagent) {
1213
- process.stdout.write(`[SPECCORE_SESSION_AGENT: ${featureSubagent}]\n`);
1214
- process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 10000]\n`);
1215
- process.stdout.write(`[SPECCORE_CONTEXT_TYPE: full]\n\n`);
1216
- }
1217
- else if (platformSubagent) {
1218
- process.stdout.write(`[SPECCORE_SESSION_AGENT: ${platformSubagent}]\n`);
1219
- process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 8000]\n`);
1220
- process.stdout.write(`[SPECCORE_CONTEXT_TYPE: platform-only]\n\n`);
1221
- }
1222
- // v8.3.196+: 读取 AI provider
1199
+ // v8.3.225+: 读取 AI provider(循环共用,提前到循环外)
1223
1200
  // host = 仅宿主 AI(SDK 子 Agent / Prompt 模式)
1224
1201
  // llm = 自配 LLM 优先,失败回退宿主
1225
1202
  // auto = 宿主优先,宿主失败回退自配 LLM
@@ -1229,66 +1206,132 @@ async function analyzeCommand(options) {
1229
1206
  aiProvider = config.settings.ai?.provider || 'host';
1230
1207
  }
1231
1208
  catch { /* 默认 host */ }
1232
- // llm 优先模式:先用自配 LLM,失败再走宿主
1233
- if (aiProvider === 'llm') {
1234
- if (await tryLlmGenerate(prompt, iter)) {
1235
- process.exitCode = 0;
1236
- return;
1209
+ // v8.3.225+: 方式 Y 调度循环 — SDK/llm 成功落盘后进程内 advance 继续下一步,直到 Pipeline 完成;
1210
+ // host 模式输出自包含 prompt 接力(exitCode 10,宿主会话内 --apply 后继续调用本命令即可断点续跑)
1211
+ const scopeFlag = isGlobalScope ? '--scope global' : `-I ${iter}`;
1212
+ const resumeCmd = `speccore analyze --prompt --pipeline ${scopeFlag}`;
1213
+ const maxRounds = steps.length * 2 + 2; // 防异常死循环保护
1214
+ let round = 0;
1215
+ while (round < maxRounds) {
1216
+ round++;
1217
+ // 根据当前步骤生成对应的 prompt(含进度标签;--apply 推进路径复用同一函数保证一致)
1218
+ const stepPrompt = await buildPipelineStepPrompt({
1219
+ currentStep,
1220
+ isGlobalScope,
1221
+ iter: iter || 'GLOBAL',
1222
+ stepIds: steps.map(s => s.id),
1223
+ options,
1224
+ });
1225
+ const { prompt, progressLabel, stepIndex, totalSteps, platformMatch, featureMatch } = stepPrompt;
1226
+ // v8.3.166+: Phase 2 端级步骤输出端级 subagent 标记
1227
+ // v8.3.167+: 功能模块步骤输出功能模块级 subagent 标记
1228
+ const platformSubagent = platformMatch ? `requirement-analyzer-${platformMatch[1]}` : undefined;
1229
+ const featureSubagent = featureMatch ? `requirement-analyzer-feature` : undefined;
1230
+ const activeSubagent = featureSubagent || platformSubagent;
1231
+ // v8.3.225+: 步骤说明(方式 Y:同一会话内接力,不再强制新对话)
1232
+ const finalPrompt = prompt + `\n\n## ⚠️ Pipeline 模式:当前步骤 (${progressLabel})\n\n` +
1233
+ `当前是 Pipeline 模式(步骤 ${stepIndex + 1}/${totalSteps})。` +
1234
+ `当你通过 --apply 写入文档后,CLI 会自动推进到下一步。\n\n` +
1235
+ `**看到 [SPECCORE_STEP_DONE] 后,请在同一会话继续执行**: \`${resumeCmd}\`\n`;
1236
+ // llm 优先模式:先用自配 LLM,失败再走宿主
1237
+ if (aiProvider === 'llm') {
1238
+ if (await tryLlmGenerate(prompt, iter)) {
1239
+ const advanced = await advanceToWorkStep(engine);
1240
+ if (advanced.isComplete) {
1241
+ logger_1.logger.success('🎉 Pipeline 完成!');
1242
+ return;
1243
+ }
1244
+ currentStep = advanced.nextStepId;
1245
+ logger_1.logger.info(`🔄 Pipeline 推进: ${currentStep}`);
1246
+ continue;
1247
+ }
1248
+ logger_1.logger.warn('⚠️ [llm] 自配 LLM 未生成有效内容,回退到宿主模式');
1237
1249
  }
1238
- logger_1.logger.warn('⚠️ [llm] 自配 LLM 未生成有效内容,回退到宿主模式');
1239
- }
1240
- // v8.3.169+: 尝试通过 Qoder SDK 直接调度子 Agent(Qoder 环境且非 TTY 模式)
1241
- const activeSubagent = featureSubagent || platformSubagent;
1242
- if (activeSubagent && !process.stdout.isTTY) {
1243
- try {
1244
- const agentCtx = {
1245
- subagent: activeSubagent,
1246
- iteration: iter || '',
1247
- platform: platformMatch?.[1],
1248
- contextBudget: featureSubagent ? 10000 : 8000,
1249
- contextType: featureSubagent ? 'full' : 'platform-only',
1250
- cwd: _projectRoot,
1251
- };
1252
- const dispatchResult = await (0, agent_adapter_1.dispatchSubagent)(agentCtx, prompt);
1253
- if (dispatchResult && dispatchResult.success) {
1254
- logger_1.logger.info(`[analyze] 子 Agent ${activeSubagent} SDK 调度成功`);
1255
- process.stdout.write(`\n[SPECCORE_STEP_DONE]\n`);
1256
- process.stdout.write(`[SPECCORE_RESULT] 子 Agent ${activeSubagent} 通过 Qoder SDK 完成执行\n`);
1257
- if (dispatchResult.filesChanged && dispatchResult.filesChanged.length > 0) {
1258
- process.stdout.write(`📄 文件变更: ${dispatchResult.filesChanged.join(', ')}\n`);
1250
+ // v8.3.169+: 尝试通过 Qoder SDK 直接调度子 Agent(Qoder 环境且非 TTY 模式)
1251
+ if (activeSubagent && !process.stdout.isTTY) {
1252
+ try {
1253
+ const agentCtx = {
1254
+ subagent: activeSubagent,
1255
+ iteration: iter || '',
1256
+ platform: platformMatch?.[1],
1257
+ contextBudget: featureSubagent ? 10000 : 8000,
1258
+ contextType: featureSubagent ? 'full' : 'platform-only',
1259
+ cwd: _projectRoot,
1260
+ };
1261
+ const dispatchResult = await (0, agent_adapter_1.dispatchSubagent)(agentCtx, prompt);
1262
+ if (dispatchResult && dispatchResult.success) {
1263
+ logger_1.logger.info(`[analyze] 子 Agent ${activeSubagent} SDK 调度成功`);
1264
+ process.stdout.write(`\n[SPECCORE_STEP_DONE]\n`);
1265
+ process.stdout.write(`[SPECCORE_RESULT] 子 Agent ${activeSubagent} 通过 Qoder SDK 完成执行\n`);
1266
+ if (dispatchResult.filesChanged && dispatchResult.filesChanged.length > 0) {
1267
+ process.stdout.write(`📄 文件变更: ${dispatchResult.filesChanged.join(', ')}\n`);
1268
+ }
1269
+ if (dispatchResult.content) {
1270
+ // v8.3.196+: 若子 Agent 返回 JSON 文档格式,自动落盘(修复「分析结果只打印不落盘」)
1271
+ const autoApplied = await applyDocContent(dispatchResult.content, iter);
1272
+ if (autoApplied > 0) {
1273
+ process.stdout.write(`✅ 已自动写入 ${autoApplied} 个文档到 020-specs/(宿主子 Agent 直接落盘)\n`);
1274
+ // v8.3.225+: SDK 落盘成功 → 进程内推进下一步(放宽步骤隔离,仅 Qoder 环境)
1275
+ const advanced = await advanceToWorkStep(engine);
1276
+ if (advanced.isComplete) {
1277
+ logger_1.logger.success('🎉 Pipeline 完成!');
1278
+ return;
1279
+ }
1280
+ currentStep = advanced.nextStepId;
1281
+ logger_1.logger.info(`🔄 Pipeline 推进: ${currentStep}`);
1282
+ continue;
1283
+ }
1284
+ process.stdout.write(`\n--- 执行结果 ---\n${dispatchResult.content.slice(0, 3000)}${dispatchResult.content.length > 3000 ? '\n... (截断)' : ''}\n`);
1285
+ }
1286
+ // 提示用户审查结果后手动 --apply(非 JSON 格式时)
1287
+ process.stdout.write(`\n> 请审查上述结果,确认后执行 speccore analyze --apply 写入文档\n`);
1288
+ process.exitCode = 0;
1289
+ return;
1259
1290
  }
1260
- if (dispatchResult.content) {
1261
- // v8.3.196+: 若子 Agent 返回 JSON 文档格式,自动落盘(修复「分析结果只打印不落盘」)
1262
- const autoApplied = await applyDocContent(dispatchResult.content, iter);
1263
- if (autoApplied > 0) {
1264
- process.stdout.write(`✅ 已自动写入 ${autoApplied} 个文档到 020-specs/(宿主子 Agent 直接落盘)\n`);
1265
- process.exitCode = 0;
1291
+ // 宿主 SDK 调度失败(success=false)→ auto 时回退自配 LLM
1292
+ if (aiProvider === 'auto' && await tryLlmGenerate(prompt, iter)) {
1293
+ const advanced = await advanceToWorkStep(engine);
1294
+ if (advanced.isComplete) {
1295
+ logger_1.logger.success('🎉 Pipeline 完成!');
1266
1296
  return;
1267
1297
  }
1268
- process.stdout.write(`\n--- 执行结果 ---\n${dispatchResult.content.slice(0, 3000)}${dispatchResult.content.length > 3000 ? '\n... (截断)' : ''}\n`);
1298
+ currentStep = advanced.nextStepId;
1299
+ logger_1.logger.info(`🔄 Pipeline 推进: ${currentStep}`);
1300
+ continue;
1269
1301
  }
1270
- // 提示用户审查结果后手动 --apply(非 JSON 格式时)
1271
- process.stdout.write(`\n> 请审查上述结果,确认后执行 speccore analyze --apply 写入文档\n`);
1272
- process.exitCode = 0;
1273
- return;
1274
1302
  }
1275
- // 宿主 SDK 调度失败(success=false)→ auto 时回退自配 LLM
1276
- if (aiProvider === 'auto' && await tryLlmGenerate(prompt, iter)) {
1277
- process.exitCode = 0;
1278
- return;
1303
+ catch (e) {
1304
+ logger_1.logger.debug(`[analyze] SDK 调度尝试失败: ${e.message}`);
1305
+ // 宿主异常 → auto 时回退自配 LLM
1306
+ if (aiProvider === 'auto' && await tryLlmGenerate(prompt, iter)) {
1307
+ const advanced = await advanceToWorkStep(engine);
1308
+ if (advanced.isComplete) {
1309
+ logger_1.logger.success('🎉 Pipeline 完成!');
1310
+ return;
1311
+ }
1312
+ currentStep = advanced.nextStepId;
1313
+ logger_1.logger.info(`🔄 Pipeline 推进: ${currentStep}`);
1314
+ continue;
1315
+ }
1279
1316
  }
1280
1317
  }
1281
- catch (e) {
1282
- logger_1.logger.debug(`[analyze] SDK 调度尝试失败: ${e.message}`);
1283
- // 宿主异常 → auto 时回退自配 LLM
1284
- if (aiProvider === 'auto' && await tryLlmGenerate(prompt, iter)) {
1285
- process.exitCode = 0;
1286
- return;
1287
- }
1318
+ // host 模式:输出 subagent 标记 + 自包含 prompt,宿主会话内落盘后继续调用本命令
1319
+ if (featureSubagent) {
1320
+ process.stdout.write(`[SPECCORE_SESSION_AGENT: ${featureSubagent}]\n`);
1321
+ process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 10000]\n`);
1322
+ process.stdout.write(`[SPECCORE_CONTEXT_TYPE: full]\n\n`);
1288
1323
  }
1324
+ else if (platformSubagent) {
1325
+ process.stdout.write(`[SPECCORE_SESSION_AGENT: ${platformSubagent}]\n`);
1326
+ process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 8000]\n`);
1327
+ process.stdout.write(`[SPECCORE_CONTEXT_TYPE: platform-only]\n\n`);
1328
+ }
1329
+ process.stdout.write(`[SPECCORE_PROMPT]\n${finalPrompt}`);
1330
+ process.exitCode = 10;
1331
+ return;
1289
1332
  }
1290
- process.stdout.write(`[SPECCORE_PROMPT]\n${finalPrompt}`);
1291
- process.exitCode = 10;
1333
+ // 理论不可达(防死循环保护触发)
1334
+ logger_1.logger.error('Pipeline 循环超过最大轮数,状态可能异常,请检查 .speccore/local/ 下的 Pipeline 状态文件');
1292
1335
  return;
1293
1336
  }
1294
1337
  // ── Prompt 模式 ──
@@ -2336,13 +2379,28 @@ ${singlePrompt}`);
2336
2379
  const { detectAffectedPlatforms, detectPlatformPriorityOrder } = await Promise.resolve().then(() => __importStar(require('../core/change-detection')));
2337
2380
  const affectedPlatforms = await detectAffectedPlatforms(_projectRoot);
2338
2381
  const platformOrder = await detectPlatformPriorityOrder(options.iteration);
2382
+ // v8.3.225+: 与初始化时保持一致的功能模块分批与澄清开关(否则步骤链不一致会跳错步骤)
2383
+ let features;
2384
+ try {
2385
+ const iterDirForFeatures = await (0, context_1.getIterationDir)(options.iteration);
2386
+ if (iterDirForFeatures) {
2387
+ const { parseFeatureList } = await Promise.resolve().then(() => __importStar(require('../core/spec-paths')));
2388
+ const featureList = await parseFeatureList(iterDirForFeatures);
2389
+ if (featureList.length > 2)
2390
+ features = featureList;
2391
+ }
2392
+ }
2393
+ catch { /* 忽略功能模块检测失败 */ }
2394
+ const skipClarifyStep = clarifyMode === 'off' || (clarifyMode === 'auto' && !needsClarify);
2339
2395
  engine = (await createAnalyzePipeline(options.iteration, _projectRoot, {
2340
2396
  affectedPlatforms: affectedPlatforms.length > 0 ? affectedPlatforms : undefined,
2341
2397
  platformOrder: platformOrder.length > 0 ? platformOrder : undefined,
2398
+ skipClarify: skipClarifyStep,
2399
+ features,
2342
2400
  })).engine;
2343
2401
  }
2344
2402
  // v8.3.160+: 默认步骤隔离模式
2345
- const stepResult = await engine.advance();
2403
+ const stepResult = await advanceToWorkStep(engine);
2346
2404
  const state = await engine.getState();
2347
2405
  if (state?.currentStep === 'done') {
2348
2406
  logger_1.logger.success('🎉 Pipeline 完成!');
@@ -2355,71 +2413,24 @@ ${singlePrompt}`);
2355
2413
  logger_1.logger.info('');
2356
2414
  logger_1.logger.info(`🔄 Pipeline 推进: ${state.currentStep}`);
2357
2415
  logger_1.logger.info('');
2358
- // 生成下一步 prompt(v6.69.0+: 适配全局/迭代层步骤)
2359
- let nextPrompt;
2360
- const nextPlatformMatch = state.currentStep.match(/^platform-(.+)-prompt$/);
2361
- if (isGlobalScope) {
2362
- // 全局层 Pipeline 步骤映射
2363
- if (state.currentStep === 'global-analysis') {
2364
- nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
2365
- iteration: options.iteration || 'GLOBAL', analyzeOptions: options,
2366
- }));
2367
- }
2368
- else if (state.currentStep === 'consistency-check') {
2369
- nextPrompt = `\n# 任务: 全局一致性检查\n\n` +
2370
- `检查各迭代、各端之间的规格是否一致。\n` +
2371
- `重点检查:接口定义冲突、数据模型不一致、命名规范违规。\n`;
2372
- }
2373
- else if (state.currentStep === 'report-generation') {
2374
- nextPrompt = `\n# 任务: 生成全局索引报告\n\n` +
2375
- `汇总所有分析结果,生成 .speccore/GLOBAL/INDEX.md。\n`;
2376
- }
2377
- else {
2378
- nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
2379
- iteration: options.iteration || 'GLOBAL', analyzeOptions: options,
2380
- }));
2381
- }
2382
- // v8.3.160+: clarify 阶段推进(拆分为 product / interaction / security)
2383
- }
2384
- else if (state.currentStep === 'clarify-product' || state.currentStep === 'clarify-interaction' || state.currentStep === 'clarify-security') {
2385
- nextPrompt = await buildClarifyPhasePrompt(options.iteration);
2386
- }
2387
- else if (state.currentStep === 'confirm-check') {
2388
- nextPrompt = await buildConfirmCheckPrompt(options.iteration);
2389
- }
2390
- else if (state.currentStep === 'contract-prompt') {
2391
- nextPrompt = await buildContractFirstPrompt(options.iteration);
2392
- }
2393
- else if (nextPlatformMatch) {
2394
- const platform = nextPlatformMatch[1];
2395
- nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
2396
- iteration: options.iteration, platform,
2397
- analyzeOptions: { ...options, phase: '2' },
2398
- }));
2399
- }
2400
- else if (state.currentStep === 'phase2-prompt') {
2401
- // Pipeline 步骤名映射
2402
- nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
2403
- iteration: options.iteration,
2404
- analyzeOptions: { ...options, phase: '2' },
2405
- }));
2406
- }
2407
- else {
2408
- nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
2409
- iteration: options.iteration,
2410
- analyzeOptions: options,
2411
- }));
2412
- }
2413
- // v8.3.160+: 步骤隔离模式输出标记 + 子 Agent 激活
2416
+ // v8.3.225+: 生成下一步 prompt(复用统一函数,与 --prompt --pipeline 路径保持一致)
2417
+ const stepPrompt = await buildPipelineStepPrompt({
2418
+ currentStep: state.currentStep,
2419
+ isGlobalScope,
2420
+ iter: options.iteration || 'GLOBAL',
2421
+ stepIds: state.steps || [state.currentStep],
2422
+ options,
2423
+ });
2424
+ // v8.3.225+: 方式 Y 输出标记 — 宿主会话内继续执行即可,不再强制新对话
2414
2425
  const scopeFlag = isGlobalScope ? '--scope global' : `-I ${options.iteration}`;
2415
- const nextCmd = `speccore analyze ${scopeFlag} --resume`;
2426
+ const nextCmd = `speccore analyze --prompt --pipeline ${scopeFlag}`;
2416
2427
  const output = [
2417
2428
  `[SPECCORE_STEP_DONE]`,
2418
2429
  `步骤 "${state.currentStep}" 已完成。`,
2419
2430
  ``,
2420
2431
  `[SPECCORE_NEXT_STEP]`,
2421
2432
  `下一步: ${state.currentStep}`,
2422
- `请在新的对话中执行: ${nextCmd}`,
2433
+ `请在同一会话继续执行: ${nextCmd}`,
2423
2434
  ``,
2424
2435
  `[SPECCORE_CONTEXT_SNAPSHOT]`,
2425
2436
  JSON.stringify(stepResult.contextSnapshot || {}),
@@ -2432,7 +2443,7 @@ ${singlePrompt}`);
2432
2443
  ``,
2433
2444
  ] : []),
2434
2445
  `--- 下一步 Prompt ---`,
2435
- nextPrompt,
2446
+ stepPrompt.prompt,
2436
2447
  ].join('\n');
2437
2448
  process.stdout.write(output);
2438
2449
  process.exitCode = 10;
@@ -6473,12 +6484,17 @@ async function loadUnitAnalyses(iteration) {
6473
6484
  const cacheDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), exports.UNITS_CACHE_DIR, iteration);
6474
6485
  if (!await (0, fs_extra_1.pathExists)(cacheDir))
6475
6486
  return [];
6476
- const files = (await (0, fs_extra_2.readdir)(cacheDir)).filter(f => f.endsWith('.json'));
6487
+ // v8.3.225+ 修复:排除 units.json 清单缓存(extractIterationUnits 产物,非单元分析结果),
6488
+ // 否则数组被 push 进 results 后 sort 时 a.id 为 undefined 崩溃
6489
+ const files = (await (0, fs_extra_2.readdir)(cacheDir)).filter(f => f.endsWith('.json') && f !== 'units.json');
6477
6490
  const results = [];
6478
6491
  for (const f of files) {
6479
6492
  try {
6480
6493
  const content = await (0, fs_extra_2.readFile)((0, path_1.join)(cacheDir, f), 'utf-8');
6481
- results.push(JSON.parse(content));
6494
+ const parsed = JSON.parse(content);
6495
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !parsed.id)
6496
+ continue;
6497
+ results.push(parsed);
6482
6498
  }
6483
6499
  catch { /* skip */ }
6484
6500
  }
@@ -6640,6 +6656,243 @@ ${ctxResult.context}
6640
6656
  `;
6641
6657
  return prompt.trim();
6642
6658
  }
6659
+ /**
6660
+ * v8.3.225+: 生成 Pipeline 当前步骤的分析 prompt(含进度标签)。
6661
+ * --prompt --pipeline 初始化/恢复路径与 --apply 后自动推进路径复用,保证两处 prompt 一致。
6662
+ */
6663
+ async function buildPipelineStepPrompt(args) {
6664
+ const { currentStep, isGlobalScope, iter, stepIds, options } = args;
6665
+ const platformMatch = currentStep.match(/^platform-(.+)-prompt$/);
6666
+ // v8.3.167+: 检测功能模块步骤
6667
+ const featureMatch = currentStep.match(/^feature-(.+)-prompt$/);
6668
+ let prompt;
6669
+ if (isGlobalScope) {
6670
+ // 全局层 Pipeline 步骤映射
6671
+ if (currentStep === 'init' || currentStep === 'discovery') {
6672
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6673
+ iteration: iter, task: options.task, platform: options.platform,
6674
+ analyzeOptions: options,
6675
+ }));
6676
+ }
6677
+ else if (currentStep === 'global-analysis') {
6678
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6679
+ iteration: iter, analyzeOptions: options,
6680
+ }));
6681
+ }
6682
+ else if (currentStep === 'consistency-check') {
6683
+ prompt = `\n# 任务: 全局一致性检查\n\n` +
6684
+ `检查各迭代、各端之间的规格是否一致。\n` +
6685
+ `重点检查:接口定义冲突、数据模型不一致、命名规范违规。\n`;
6686
+ }
6687
+ else if (currentStep === 'report-generation') {
6688
+ prompt = `\n# 任务: 生成全局索引报告\n\n` +
6689
+ `汇总所有分析结果,生成 .speccore/GLOBAL/INDEX.md。\n`;
6690
+ }
6691
+ else {
6692
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6693
+ iteration: iter, analyzeOptions: options,
6694
+ }));
6695
+ }
6696
+ }
6697
+ else if (currentStep === 'clarify-product' || currentStep === 'clarify-interaction' || currentStep === 'clarify-security') {
6698
+ // v8.3.160+: Phase 0 需求澄清(拆分为 product / interaction / security 三个子步骤)
6699
+ prompt = await buildClarifyPhasePrompt(iter);
6700
+ }
6701
+ else if (currentStep === 'confirm-check') {
6702
+ prompt = await buildConfirmCheckPrompt(iter);
6703
+ }
6704
+ else if (currentStep === 'phase1-prompt') {
6705
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6706
+ iteration: iter, task: options.task, platform: options.platform,
6707
+ analyzeOptions: options,
6708
+ }));
6709
+ }
6710
+ else if (featureMatch) {
6711
+ // v8.3.167+: 功能模块级分析步骤
6712
+ const featureName = featureMatch[1];
6713
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6714
+ iteration: iter,
6715
+ analyzeOptions: { ...options, feature: featureName, phase: '1' },
6716
+ }));
6717
+ }
6718
+ else if (currentStep === 'contract-prompt') {
6719
+ // 契约先行阶段:基于 Phase 1 文档生成跨端契约
6720
+ prompt = await buildContractFirstPrompt(iter);
6721
+ }
6722
+ else if (platformMatch) {
6723
+ // 逐端推进阶段:为指定端生成专属文档
6724
+ const platform = platformMatch[1];
6725
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6726
+ iteration: iter, platform,
6727
+ analyzeOptions: { ...options, phase: '2' },
6728
+ }));
6729
+ }
6730
+ else if (currentStep === 'phase2-prompt') {
6731
+ // Pipeline 步骤名映射(--apply 推进时兼容)
6732
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6733
+ iteration: iter,
6734
+ analyzeOptions: { ...options, phase: '2' },
6735
+ }));
6736
+ }
6737
+ else {
6738
+ // 回退到默认 prompt
6739
+ prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
6740
+ iteration: iter, task: options.task, platform: options.platform,
6741
+ analyzeOptions: options,
6742
+ }));
6743
+ }
6744
+ // 进度标签
6745
+ const totalSteps = stepIds.filter(s => !s.endsWith('-done') && s !== 'done').length;
6746
+ const stepIndex = stepIds.indexOf(currentStep);
6747
+ let progressLabel;
6748
+ if (isGlobalScope) {
6749
+ progressLabel = currentStep === 'init' ? '全局分析: 初始化'
6750
+ : currentStep === 'discovery' ? '全局分析: 发现'
6751
+ : currentStep === 'global-analysis' ? '全局分析: 跨端分析'
6752
+ : currentStep === 'consistency-check' ? '全局分析: 一致性检查'
6753
+ : currentStep === 'report-generation' ? '全局分析: 报告生成'
6754
+ : '全局 Pipeline';
6755
+ }
6756
+ else {
6757
+ progressLabel = currentStep === 'clarify-product'
6758
+ ? 'Phase 0a/3: 产品分析'
6759
+ : currentStep === 'clarify-interaction'
6760
+ ? 'Phase 0b/3: 交互设计'
6761
+ : currentStep === 'clarify-security'
6762
+ ? 'Phase 0c/3: 安全审查'
6763
+ : currentStep === 'confirm-check'
6764
+ ? 'Phase 0/3: 需求确认'
6765
+ : currentStep === 'phase1-prompt'
6766
+ ? 'Phase 1/3: 迭代综合文档'
6767
+ : featureMatch
6768
+ ? `Phase 1b/3: ${featureMatch[1]} 功能模块分析`
6769
+ : currentStep === 'contract-prompt'
6770
+ ? 'Phase 2/3: 契约定义'
6771
+ : platformMatch
6772
+ ? `Phase 2/3: ${platformMatch[1]} 端专属文档`
6773
+ : 'Pipeline';
6774
+ }
6775
+ return { prompt, progressLabel, stepIndex, totalSteps, platformMatch, featureMatch };
6776
+ }
6777
+ /**
6778
+ * v8.3.225+: 推进 Pipeline 并自动跳过纯检查步骤(*-done),直达下一个工作步骤。
6779
+ * *-done 步骤无专属 prompt(回退默认 prompt 是噪音),方式 Y 下自动跳过减少空转往返。
6780
+ */
6781
+ async function advanceToWorkStep(engine) {
6782
+ let advanced = await engine.advance();
6783
+ let guard = 0;
6784
+ while (!advanced.isComplete && advanced.nextStepId && advanced.nextStepId.endsWith('-done')) {
6785
+ if (++guard > 50)
6786
+ break; // 防死循环保护
6787
+ logger_1.logger.info(`⏭️ 跳过检查步骤: ${advanced.nextStepId}`);
6788
+ advanced = await engine.advance();
6789
+ }
6790
+ return advanced;
6791
+ }
6792
+ // ═══════════════════════════════════════════════════════════
6793
+ // 调度循环模式(v8.3.225+,方式 Y 宿主会话内闭环)
6794
+ // ═══════════════════════════════════════════════════════════
6795
+ /** 单元名 → 目录名安全化(兜底模式下单元名可能是标题文本) */
6796
+ function sanitizeUnitDirName(name) {
6797
+ return name.replace(/[\\/:*?"<>|\s]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'unit';
6798
+ }
6799
+ /** 单元规格产物目录(对齐切分目录名,与 req-doc 澄清路径一致) */
6800
+ function getUnitSpecDir(iterDir, unit) {
6801
+ return (0, path_1.join)(iterDir, '020-specs', sanitizeUnitDirName(unit.name), 'overview');
6802
+ }
6803
+ /**
6804
+ * 校验单元分析产物:020-specs/{单元名}/overview/ 下至少一份有效规格文档。
6805
+ * 通过时顺带同步旧版单元分析缓存(consolidate 依赖),保持幂等。
6806
+ */
6807
+ async function validateUnitSpec(iterDir, iter, unit) {
6808
+ const specDir = getUnitSpecDir(iterDir, unit);
6809
+ const candidates = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md'];
6810
+ const found = {};
6811
+ for (const f of candidates) {
6812
+ const fp = (0, path_1.join)(specDir, f);
6813
+ if (!(await (0, fs_extra_1.pathExists)(fp)))
6814
+ continue;
6815
+ const content = (await (0, fs_extra_2.readFile)(fp, 'utf-8')).trim();
6816
+ if (!content || content.includes('SPEC-SKELETON') || content.length < 200)
6817
+ continue;
6818
+ found[f] = content;
6819
+ }
6820
+ if (Object.keys(found).length === 0) {
6821
+ return `020-specs/${sanitizeUnitDirName(unit.name)}/overview/ 下缺少有效的规格文档(REQUIREMENT.md / ANALYSIS.md / TECH.md 至少一份,内容 ≥200 字符且非骨架模板)`;
6822
+ }
6823
+ // 同步旧版缓存,保证 --consolidate 可读
6824
+ try {
6825
+ await saveUnitAnalysis(iter, {
6826
+ id: unit.id,
6827
+ name: unit.name,
6828
+ req: found['REQUIREMENT.md'] || '',
6829
+ tech: found['TECH.md'] || found['ANALYSIS.md'] || '',
6830
+ test: '',
6831
+ timestamp: new Date().toISOString(),
6832
+ });
6833
+ }
6834
+ catch { /* 缓存同步失败不阻断主流程 */ }
6835
+ return null;
6836
+ }
6837
+ /** 构建单元分析循环 prompt:复用现有 prompt + 落盘约束 + 重试反馈 */
6838
+ async function buildUnitLoopPrompt(iterDir, iter, unit, allUnits, attempt, lastError) {
6839
+ const base = await buildUnitAnalysisPrompt(iterDir, iter, unit, allUnits);
6840
+ const unitDirName = sanitizeUnitDirName(unit.name);
6841
+ const outputDir = `020-specs/${unitDirName}/overview/`;
6842
+ let extra = `
6843
+
6844
+ ## 落盘方式(重要 — 请直接写文件)
6845
+
6846
+ 请使用 Write 工具将以下文档直接写入迭代目录(不要只在回复中输出内容):
6847
+
6848
+ - \`${outputDir}REQUIREMENT.md\` — 需求规格(功能描述/输入输出/业务规则/异常场景)
6849
+ - \`${outputDir}ANALYSIS.md\` — 需求分析(现状/差距/方案要点)
6850
+ - \`${outputDir}TECH.md\` — 技术方案(数据模型/接口设计/端分工/依赖)
6851
+
6852
+ 约束:
6853
+ - 每个文件内容不少于 200 字符,禁止输出空模板或骨架占位
6854
+ - 写入前先创建目录;写完后回复一行:\`DONE ${unit.name}\`
6855
+ `;
6856
+ if (attempt > 1 && lastError) {
6857
+ extra += `
6858
+ ## ⚠️ 上次尝试未通过校验(第 ${attempt - 1} 次尝试)
6859
+
6860
+ 失败原因:${lastError}
6861
+
6862
+ 请检查文件是否写入了正确路径(\`${outputDir}\`),并确保内容完整、非骨架模板。
6863
+ `;
6864
+ }
6865
+ return base + extra;
6866
+ }
6867
+ /** 单元分析调度循环:构建步骤清单并推进一轮 */
6868
+ async function runUnitDispatchLoop(iter, iterDir, units) {
6869
+ const steps = units.map((u) => ({
6870
+ id: `unit:${sanitizeUnitDirName(u.name)}`,
6871
+ name: u.name,
6872
+ validate: () => validateUnitSpec(iterDir, iter, u),
6873
+ buildPrompt: ({ attempt, lastError }) => buildUnitLoopPrompt(iterDir, iter, u, units, attempt, lastError),
6874
+ }));
6875
+ return (0, dispatch_loop_1.runDispatchLoopRound)(steps, {
6876
+ projectRoot: (0, task_utils_1.findProjectRoot)() || process.cwd(),
6877
+ scope: `analyze-units:${iter}`,
6878
+ maxRetries: 1,
6879
+ });
6880
+ }
6881
+ /** 输出调度循环完成汇总 */
6882
+ function printUnitLoopSummary(iter, result) {
6883
+ const s = result.summary;
6884
+ if (s.failed.length > 0) {
6885
+ logger_1.logger.warn(`⚠️ 功能单元分析循环完成: ${s.done.length} 完成 / ${s.failed.length} 失败`);
6886
+ for (const id of s.failed) {
6887
+ logger_1.logger.warn(` ❌ ${id}: ${s.failedErrors[id] || '未知原因'}`);
6888
+ }
6889
+ logger_1.logger.info(` 重跑失败单元: speccore analyze --unit "<单元名>" --prompt -I ${iter}`);
6890
+ }
6891
+ else {
6892
+ logger_1.logger.success(`🎉 所有 ${s.done.length} 个功能单元分析完成!`);
6893
+ }
6894
+ logger_1.logger.info(` 下一步: 汇总统一报告 speccore analyze --prompt -I ${iter} --consolidate`);
6895
+ }
6643
6896
  // ═══════════════════════════════════════════════════════════
6644
6897
  // 多子任务分析支持(v8.3.94+)
6645
6898
  // ═══════════════════════════════════════════════════════════