speccore 8.3.189 → 8.3.191

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 (73) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +2 -2
  3. package/dist/cli.js +21 -24
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analyze.d.ts +1 -0
  6. package/dist/commands/analyze.d.ts.map +1 -1
  7. package/dist/commands/analyze.js +87 -24
  8. package/dist/commands/analyze.js.map +1 -1
  9. package/dist/commands/dev.js +38 -14
  10. package/dist/commands/dev.js.map +1 -1
  11. package/dist/commands/doc2spec.d.ts +5 -0
  12. package/dist/commands/doc2spec.d.ts.map +1 -1
  13. package/dist/commands/doc2spec.js +2 -0
  14. package/dist/commands/doc2spec.js.map +1 -1
  15. package/dist/commands/help.js +91 -15
  16. package/dist/commands/help.js.map +1 -1
  17. package/dist/commands/next.d.ts +5 -0
  18. package/dist/commands/next.d.ts.map +1 -0
  19. package/dist/commands/next.js +152 -0
  20. package/dist/commands/next.js.map +1 -0
  21. package/dist/commands/pipeline.d.ts.map +1 -1
  22. package/dist/commands/pipeline.js +3 -1
  23. package/dist/commands/pipeline.js.map +1 -1
  24. package/dist/commands/req-doc.d.ts +17 -0
  25. package/dist/commands/req-doc.d.ts.map +1 -0
  26. package/dist/commands/req-doc.js +388 -0
  27. package/dist/commands/req-doc.js.map +1 -0
  28. package/dist/commands/run.d.ts +13 -0
  29. package/dist/commands/run.d.ts.map +1 -0
  30. package/dist/commands/run.js +619 -0
  31. package/dist/commands/run.js.map +1 -0
  32. package/dist/commands/status-panel.js +3 -3
  33. package/dist/commands/verify.d.ts +8 -0
  34. package/dist/commands/verify.d.ts.map +1 -1
  35. package/dist/commands/verify.js +8 -0
  36. package/dist/commands/verify.js.map +1 -1
  37. package/dist/commands/welcome.js +2 -2
  38. package/dist/commands/welcome.js.map +1 -1
  39. package/dist/commands/workspace.js +2 -2
  40. package/dist/core/agent-adapter.d.ts.map +1 -1
  41. package/dist/core/agent-adapter.js +18 -0
  42. package/dist/core/agent-adapter.js.map +1 -1
  43. package/dist/core/agents/defaults/_INDEX.md +68 -0
  44. package/dist/core/ask-engine.d.ts.map +1 -1
  45. package/dist/core/ask-engine.js +120 -26
  46. package/dist/core/ask-engine.js.map +1 -1
  47. package/dist/core/handoff-generator.d.ts +1 -1
  48. package/dist/core/handoff-generator.d.ts.map +1 -1
  49. package/dist/core/handoff-generator.js +10 -10
  50. package/dist/core/handoff-generator.js.map +1 -1
  51. package/dist/core/help-panel.d.ts +1 -1
  52. package/dist/core/help-panel.d.ts.map +1 -1
  53. package/dist/core/help-panel.js +2 -1
  54. package/dist/core/help-panel.js.map +1 -1
  55. package/dist/core/intent-recognition.d.ts +1 -1
  56. package/dist/core/intent-recognition.d.ts.map +1 -1
  57. package/dist/core/intent-recognition.js +98 -15
  58. package/dist/core/intent-recognition.js.map +1 -1
  59. package/dist/core/requirement-clarifier.d.ts +7 -1
  60. package/dist/core/requirement-clarifier.d.ts.map +1 -1
  61. package/dist/core/requirement-clarifier.js +42 -10
  62. package/dist/core/requirement-clarifier.js.map +1 -1
  63. package/dist/core/spec-paths.d.ts +6 -0
  64. package/dist/core/spec-paths.d.ts.map +1 -1
  65. package/dist/core/spec-paths.js +66 -0
  66. package/dist/core/spec-paths.js.map +1 -1
  67. package/dist/core/spec-skeleton.js +2 -2
  68. package/dist/core/spec-skeleton.js.map +1 -1
  69. package/dist/core/unified-config.d.ts +16 -0
  70. package/dist/core/unified-config.d.ts.map +1 -1
  71. package/dist/core/unified-config.js +53 -94
  72. package/dist/core/unified-config.js.map +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,619 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCommand = runCommand;
37
+ /**
38
+ * run — 端到端流水线执行入口
39
+ * 唯一执行入口:13 阶段全自动流水线(--auto 默认,--from/--to 指定起止,--demo 放宽闸口 A/B)
40
+ */
41
+ const fs_extra_1 = require("fs-extra");
42
+ const path_1 = require("path");
43
+ const child_process_1 = require("child_process");
44
+ const logger_1 = require("../utils/logger");
45
+ const context_1 = require("../core/context");
46
+ const questions_1 = require("../core/questions");
47
+ const handoff_generator_1 = require("../core/handoff-generator");
48
+ const unified_config_1 = require("../core/unified-config");
49
+ async function hasFeatureDirs(iterDir) {
50
+ try {
51
+ const featuresDir = (0, path_1.join)(iterDir, '010-requirements', 'features');
52
+ const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
53
+ return entries.some(e => e.isDirectory() && !e.name.startsWith('.'));
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ async function runCommand(options) {
60
+ await autoPipeline(options);
61
+ }
62
+ async function autoPipeline(options) {
63
+ const PIPELINE_PHASES = [
64
+ // ── 需求入口(二选一:req-doc 一句话需求 / doc2spec 导入文档)──
65
+ { key: 'req-doc', cmd: 'speccore req-doc', layer: 'prepare' },
66
+ { key: 'doc2spec', cmd: 'speccore doc2spec', layer: 'prepare' },
67
+ // ── 结构化:切分功能单元(1→N,独立于澄清)──
68
+ { key: 'split-units', cmd: 'speccore doc2spec --split', layer: 'prepare' },
69
+ // ── 主流程(分析→校验→任务化→计划→执行→验证→部署→合并→归档→导出)──
70
+ { key: 'analyze', cmd: 'speccore analyze', layer: 'main' },
71
+ { key: 'validate', cmd: 'speccore validate', layer: 'main' },
72
+ { key: 'taskify', cmd: 'speccore iteration split', layer: 'main' },
73
+ { key: 'plan', cmd: 'speccore plan', layer: 'main' },
74
+ { key: 'execute', cmd: 'speccore execute', layer: 'main' },
75
+ { key: 'verify', cmd: 'speccore verify', layer: 'main' },
76
+ { key: 'pipeline', cmd: 'speccore pipeline', layer: 'main' },
77
+ { key: 'pr', cmd: 'speccore pr', layer: 'main' },
78
+ { key: 'done', cmd: 'speccore done', layer: 'main' },
79
+ { key: 'spec2doc', cmd: 'speccore spec2doc', layer: 'main' },
80
+ ];
81
+ // v8.3.190+: 内置预设流水线(起止点对,覆盖常见业务操作)
82
+ // 默认流水线为 develop(分析→验证,代码完成即止);pr/done/spec2doc 为独立收尾操作,
83
+ // 需「提交代码/归档/导出」时用 deliver/import/docs preset 或单独跑命令。
84
+ const PRESETS = {
85
+ full: { from: 'req-doc', to: 'spec2doc', desc: '全流程:需求→导出(含收尾)' },
86
+ import: { from: 'doc2spec', to: 'pr', desc: '导入需求→合并请求' },
87
+ analyze: { from: 'analyze', to: 'validate', desc: '只分析校验' },
88
+ spec: { from: 'doc2spec', to: 'taskify', desc: '导入需求→任务化(生成 Spec,看效果)' },
89
+ develop: { from: 'analyze', to: 'verify', desc: '分析→验证(默认,代码+验证完成)' },
90
+ deliver: { from: 'execute', to: 'pr', desc: '执行→提交代码' },
91
+ deploy: { from: 'analyze', to: 'pipeline', desc: '分析→部署验证' },
92
+ docs: { from: 'analyze', to: 'spec2doc', desc: '分析→文档导出' },
93
+ };
94
+ // v8.3.184+: review 闸口(功能开发质量底线)——阶段执行前触发,软停等待宿主 AI 确认
95
+ // A: taskify 前(Spec 定稿)、B: verify 前(代码 review)、C: pipeline 前(部署前确认,永不跳过)
96
+ const GATES = {
97
+ taskify: { id: 'A', title: 'Spec 定稿', hint: '请 review 生成的 Spec(功能单元/端覆盖/验收标准),确认后继续', resumeFrom: 'taskify' },
98
+ verify: { id: 'B', title: '代码 review', hint: '请 review 生成的代码(重点扫 AI 标记的低置信点 / .issues.md / .deviations),确认后继续', resumeFrom: 'verify' },
99
+ pipeline: { id: 'C', title: '部署前确认', hint: '即将部署到环境(destructive 操作),确认后才执行', resumeFrom: 'pipeline' },
100
+ };
101
+ const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
102
+ if (!iteration) {
103
+ logger_1.logger.info('🤖 Auto-pipeline: 未找到迭代,先初始化...');
104
+ (0, child_process_1.execSync)('speccore init', { stdio: 'inherit' });
105
+ return;
106
+ }
107
+ const iterDir = `Iteration-${iteration}`;
108
+ logger_1.logger.info(`\n🤖 Auto-pipeline: ${iteration}\n`);
109
+ // v8.3.190+: 读取 optional_steps(off 跳过)+ pipelines(命名流水线)配置
110
+ let optionalSteps = {};
111
+ let pipelinesConfig = {};
112
+ try {
113
+ const { config } = await (0, unified_config_1.loadConfigWithMeta)();
114
+ optionalSteps = (config.settings.optional_steps || {});
115
+ pipelinesConfig = (config.settings.pipelines || {});
116
+ }
117
+ catch { /* 配置读取失败用默认 */ }
118
+ const phaseResults = [];
119
+ const pipelineStart = Date.now();
120
+ // ── 解析流水线范围(优先级:--auto-steps > --from/--to > --preset/默认 > full)──
121
+ let startIdx = 0;
122
+ let endIdx = PIPELINE_PHASES.length - 1;
123
+ // 1. 解析预设/自定义流水线(--preset > 配置 default > 内置 full)
124
+ const presetName = options.preset || pipelinesConfig.default || 'full';
125
+ let presetFrom;
126
+ let presetTo;
127
+ if (PRESETS[presetName]) {
128
+ presetFrom = PRESETS[presetName].from;
129
+ presetTo = PRESETS[presetName].to;
130
+ if (options.preset)
131
+ logger_1.logger.info(`🎯 预设流水线 [${presetName}]: ${presetFrom} → ${presetTo}(${PRESETS[presetName].desc})\n`);
132
+ }
133
+ else if (pipelinesConfig.custom?.[presetName]) {
134
+ const steps = pipelinesConfig.custom[presetName].split(',').map(s => s.trim()).filter(Boolean);
135
+ if (steps.length > 0) {
136
+ presetFrom = steps[0];
137
+ presetTo = steps[steps.length - 1];
138
+ logger_1.logger.info(`🎯 自定义流水线 [${presetName}]: ${presetFrom} → ${presetTo}\n`);
139
+ }
140
+ }
141
+ else if (presetName !== 'full') {
142
+ logger_1.logger.warn(`⚠️ 未知流水线 "${presetName}",回退到 full`);
143
+ presetFrom = PRESETS.full.from;
144
+ presetTo = PRESETS.full.to;
145
+ }
146
+ // 2. 应用 preset 起止点(--from/--to 会覆盖)
147
+ if (presetFrom) {
148
+ const fi = PIPELINE_PHASES.findIndex(p => p.key === presetFrom);
149
+ if (fi >= 0)
150
+ startIdx = fi;
151
+ }
152
+ if (presetTo) {
153
+ const ti = PIPELINE_PHASES.findIndex(p => p.key === presetTo);
154
+ if (ti >= 0)
155
+ endIdx = ti;
156
+ }
157
+ // 3. --from/--to 显式覆盖
158
+ if (options.from) {
159
+ const fi = PIPELINE_PHASES.findIndex(p => p.key === options.from);
160
+ if (fi >= 0)
161
+ startIdx = fi;
162
+ else {
163
+ logger_1.logger.error(`未知阶段: ${options.from}`);
164
+ return;
165
+ }
166
+ }
167
+ if (options.to) {
168
+ const ti = PIPELINE_PHASES.findIndex(p => p.key === options.to);
169
+ if (ti >= 0)
170
+ endIdx = ti;
171
+ else {
172
+ logger_1.logger.error(`未知阶段: ${options.to}`);
173
+ return;
174
+ }
175
+ }
176
+ // 4. --auto-steps 最高优先级(指定连续步骤)
177
+ if (options.autoSteps) {
178
+ const stepKeys = options.autoSteps.split(',').map(s => s.trim());
179
+ const indices = stepKeys.map(k => PIPELINE_PHASES.findIndex(p => p.key === k));
180
+ if (indices.some(i => i < 0)) {
181
+ const unknown = stepKeys.filter(k => !PIPELINE_PHASES.find(p => p.key === k));
182
+ logger_1.logger.error(`未知阶段: ${unknown.join(', ')}`);
183
+ logger_1.logger.info(`可用: ${PIPELINE_PHASES.map(p => p.key).join(', ')}`);
184
+ return;
185
+ }
186
+ // 验证连续性
187
+ const sorted = [...indices].sort((a, b) => a - b);
188
+ for (let i = 1; i < sorted.length; i++) {
189
+ if (sorted[i] !== sorted[i - 1] + 1) {
190
+ logger_1.logger.error(`auto-steps 必须连续!${stepKeys.join(',')} 在流水线中不连续`);
191
+ logger_1.logger.info(`流水线顺序: ${PIPELINE_PHASES.map(p => p.key).join(' → ')}`);
192
+ return;
193
+ }
194
+ }
195
+ startIdx = sorted[0];
196
+ endIdx = sorted[sorted.length - 1];
197
+ logger_1.logger.info(`🎯 自动步骤: ${stepKeys.join(' → ')}\n`);
198
+ }
199
+ // ── 逐步执行 ──
200
+ let pausedGate = null;
201
+ let prevLayer = '';
202
+ for (let i = startIdx; i <= endIdx; i++) {
203
+ const phase = PIPELINE_PHASES[i];
204
+ // v8.3.189+: 分层展示(准备层 / 主流程)
205
+ if (phase.layer && phase.layer !== prevLayer) {
206
+ prevLayer = phase.layer;
207
+ logger_1.logger.info(phase.layer === 'prepare'
208
+ ? '\n────────── 🏗️ 准备层(初始化 + 需求入口)──────────'
209
+ : '\n────────── 🚀 主流程(分析→拆分→执行→验证→部署→归档→导出)──────────');
210
+ }
211
+ // v8.3.184+: review 闸口软停(功能开发质量底线)
212
+ const gate = GATES[phase.key];
213
+ if (gate) {
214
+ const skipGate = options.demo && gate.id !== 'C'; // demo 跳过 A/B,C 永不跳过
215
+ if (!skipGate) {
216
+ logger_1.logger.warn(`\n🛑 [SPECCORE_GATE_${gate.id}] ${gate.title}`);
217
+ logger_1.logger.warn(` ${gate.hint}`);
218
+ logger_1.logger.warn(` 确认后继续: speccore run --auto --from ${gate.resumeFrom} -I ${iteration}\n`);
219
+ pausedGate = gate.id;
220
+ break; // 软停,等宿主 AI 确认后 --from 继续
221
+ }
222
+ }
223
+ logger_1.logger.info(`━━━ [${i - startIdx + 1}/${endIdx - startIdx + 1}] ${phase.key} ━━━`);
224
+ const phaseStart = Date.now();
225
+ let phaseStatus = 'success';
226
+ let phaseMessage = '完成';
227
+ let phaseQuestions = [];
228
+ try {
229
+ switch (phase.key) {
230
+ case 'req-doc': {
231
+ // v8.3.184+: 一句话需求 → PRD(口语化需求入口,与 doc2spec 二选一)
232
+ const reqDoc = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
233
+ const hasFeatures = await hasFeatureDirs(iterDir);
234
+ if ((await (0, fs_extra_1.pathExists)(reqDoc)) || hasFeatures) {
235
+ logger_1.logger.info(' ✅ 需求已存在,跳过 req-doc');
236
+ }
237
+ else if (options.demand) {
238
+ (0, child_process_1.execSync)(`speccore req-doc "${options.demand}" --to ${iteration}`, { stdio: 'inherit' });
239
+ }
240
+ else {
241
+ logger_1.logger.info(' ℹ️ 未提供一句话需求,跳过 req-doc(走 doc2spec 导入文档)');
242
+ }
243
+ break;
244
+ }
245
+ case 'doc2spec': {
246
+ const reqDoc = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
247
+ const hasFeatures = await hasFeatureDirs(iterDir);
248
+ if (!(await (0, fs_extra_1.pathExists)(reqDoc)) && !hasFeatures) {
249
+ logger_1.logger.info(' ℹ️ 未找到需求文档,跳过(请手动 doc2spec)');
250
+ }
251
+ else {
252
+ // 检查需求文档是否有实质内容(不是空文件)
253
+ let hasContent = false;
254
+ try {
255
+ const { readFile: rf } = require('fs-extra');
256
+ const content = await rf(reqDoc, 'utf-8');
257
+ hasContent = content.replace(/^#+\s.*$/gm, '').replace(/\s/g, '').length > 50;
258
+ }
259
+ catch { }
260
+ if (hasContent) {
261
+ logger_1.logger.info(' ✅ 需求已导入且有实质内容,跳过');
262
+ }
263
+ else {
264
+ logger_1.logger.info(' ⚠️ 需求文档存在但内容为空,请手动 doc2spec 导入');
265
+ }
266
+ }
267
+ break;
268
+ }
269
+ case 'split-units': {
270
+ // v8.3.191+: 切分功能单元(结构化,独立于澄清)
271
+ // 已切分(features/ 非空)则跳过;否则对 converted/ 和 REQUIREMENT.md 文档切分
272
+ const featuresDir = (0, path_1.join)(iterDir, '010-requirements', 'features');
273
+ let hasFeatures = false;
274
+ try {
275
+ const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
276
+ hasFeatures = entries.some((e) => e.isDirectory() && !e.name.startsWith('.'));
277
+ }
278
+ catch { }
279
+ if (hasFeatures) {
280
+ logger_1.logger.info(' ✅ 功能单元已切分,跳过');
281
+ phaseStatus = 'skipped';
282
+ phaseMessage = '功能单元已切分';
283
+ break;
284
+ }
285
+ // 收集待切分的需求文档(converted/ + REQUIREMENT.md)
286
+ const docsToSplit = [];
287
+ const convDir = (0, path_1.join)(iterDir, '010-requirements', 'converted');
288
+ if (await (0, fs_extra_1.pathExists)(convDir)) {
289
+ const files = await (0, fs_extra_1.readdir)(convDir);
290
+ for (const f of files.filter((f) => f.endsWith('.md')))
291
+ docsToSplit.push((0, path_1.join)(convDir, f));
292
+ }
293
+ const reqRoot = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
294
+ if (await (0, fs_extra_1.pathExists)(reqRoot))
295
+ docsToSplit.push(reqRoot);
296
+ if (docsToSplit.length === 0) {
297
+ logger_1.logger.info(' ℹ️ 无需求文档可切分,跳过');
298
+ phaseStatus = 'skipped';
299
+ phaseMessage = '无需求文档可切分';
300
+ break;
301
+ }
302
+ const { splitDocumentToUnits } = await Promise.resolve().then(() => __importStar(require('./doc2spec')));
303
+ let written = 0;
304
+ for (const doc of docsToSplit) {
305
+ try {
306
+ await splitDocumentToUnits(iterDir, doc, '', (0, path_1.basename)(doc));
307
+ written++;
308
+ }
309
+ catch (e) {
310
+ logger_1.logger.warn(` ⚠️ 切分失败: ${(0, path_1.basename)(doc)} — ${e?.message || e}`);
311
+ }
312
+ }
313
+ logger_1.logger.info(` 🔪 切分功能单元完成: ${written}/${docsToSplit.length} 个文档`);
314
+ break;
315
+ }
316
+ case 'analyze': {
317
+ const analysis = (0, path_1.join)(iterDir, '020-specs', 'ANALYSIS.md');
318
+ const techSpec = (0, path_1.join)(iterDir, '020-specs', 'ANALYSIS.md');
319
+ let needsAnalysis = !(await (0, fs_extra_1.pathExists)(analysis));
320
+ let needsSpecs = false;
321
+ if (!needsAnalysis) {
322
+ try {
323
+ const { readFile: rf } = require('fs-extra');
324
+ const techContent = await rf(techSpec, 'utf-8');
325
+ const meaningful = techContent.replace(/_待填充_|_待补充_|\|\s*\|/g, '').replace(/\s/g, '');
326
+ needsSpecs = meaningful.length < 50;
327
+ }
328
+ catch {
329
+ needsSpecs = true;
330
+ }
331
+ }
332
+ if (needsAnalysis || needsSpecs) {
333
+ (0, child_process_1.execSync)(`speccore analyze --auto -I ${iteration}`, { stdio: 'inherit' });
334
+ }
335
+ else {
336
+ logger_1.logger.info(' ✅ 分析已完成,跳过');
337
+ phaseStatus = 'skipped';
338
+ phaseMessage = '分析已完成,跳过';
339
+ }
340
+ // v7.4.0+: CLARIFY_REPORT 质量检查
341
+ const clarifyPath = (0, path_1.join)(iterDir, '010-requirements', 'CLARIFY_REPORT.md');
342
+ if (await (0, fs_extra_1.pathExists)(clarifyPath)) {
343
+ try {
344
+ const clarifyContent = await (0, fs_extra_1.readFile)(clarifyPath, 'utf-8');
345
+ const lowScoreMatch = clarifyContent.match(/总分[::]\s*(\d+)/);
346
+ if (lowScoreMatch) {
347
+ const score = parseInt(lowScoreMatch[1]);
348
+ if (score < 60) {
349
+ logger_1.logger.warn(` ⚠️ CLARIFY_REPORT 质量评分偏低: ${score}/100`);
350
+ logger_1.logger.warn(` 建议重新澄清需求或手动补充后再继续`);
351
+ phaseStatus = 'warning';
352
+ phaseMessage = `CLARIFY_REPORT 质量评分 ${score}/100,建议重新澄清`;
353
+ phaseQuestions.push(`需求澄清质量评分 ${score}/100,低于 60 分基线`);
354
+ }
355
+ }
356
+ }
357
+ catch { }
358
+ }
359
+ break;
360
+ }
361
+ case 'validate': {
362
+ // v8.3.184+: Spec 质量门禁(analyze 后校验完整性/一致性,不通过不进入 split)
363
+ const analysis = (0, path_1.join)(iterDir, '020-specs', 'ANALYSIS.md');
364
+ if (await (0, fs_extra_1.pathExists)(analysis)) {
365
+ (0, child_process_1.execSync)(`speccore validate --iteration=${iteration}`, { stdio: 'inherit' });
366
+ }
367
+ else {
368
+ logger_1.logger.info(' ⏭️ 无 Spec 可校验,跳过 validate');
369
+ }
370
+ break;
371
+ }
372
+ case 'taskify': {
373
+ let hasTasks = false;
374
+ try {
375
+ const entries = await (0, fs_extra_1.readdir)((0, path_1.join)(iterDir, '030-tasks'), { withFileTypes: true });
376
+ hasTasks = entries.some((e) => e.isDirectory() && e.name.startsWith('Task-'));
377
+ }
378
+ catch { }
379
+ if (!hasTasks) {
380
+ (0, child_process_1.execSync)(`speccore iteration split -i ${iteration} --force`, { stdio: 'inherit' });
381
+ }
382
+ else {
383
+ logger_1.logger.info(' ✅ 任务已拆分,跳过');
384
+ }
385
+ break;
386
+ }
387
+ case 'plan': {
388
+ // v8.3.190+: 单任务跳过 plan(无需调度计划,直接 execute);多任务才列计划
389
+ let taskCount = 0;
390
+ try {
391
+ const { readdir: rd3 } = require('fs-extra');
392
+ const tasksDir = (0, path_1.join)(iterDir, '030-tasks');
393
+ if (await (0, fs_extra_1.pathExists)(tasksDir)) {
394
+ const entries = await rd3(tasksDir, { withFileTypes: true });
395
+ taskCount = entries.filter((e) => e.isDirectory() && e.name.startsWith('Task-')).length;
396
+ }
397
+ }
398
+ catch { }
399
+ if (taskCount <= 1) {
400
+ logger_1.logger.info(` ⏭️ 仅 ${taskCount} 个任务,跳过 plan(单任务无需调度计划)`);
401
+ phaseStatus = 'skipped';
402
+ phaseMessage = '单任务跳过 plan';
403
+ break;
404
+ }
405
+ // v6.91.1+: 检查是否已有计划,避免重复生成
406
+ const plansDir = (0, path_1.join)(iterDir, '000-overview', 'plans');
407
+ let hasPlan = false;
408
+ try {
409
+ const { readdir: rd2, pathExists: pe2 } = require('fs-extra');
410
+ if (await pe2(plansDir)) {
411
+ const entries = await rd2(plansDir);
412
+ hasPlan = entries.some((e) => e.endsWith('.md'));
413
+ }
414
+ }
415
+ catch { }
416
+ if (hasPlan) {
417
+ logger_1.logger.info(' ✅ 执行计划已存在,跳过');
418
+ }
419
+ else {
420
+ (0, child_process_1.execSync)(`speccore plan -i ${iteration} --force`, { stdio: 'inherit' });
421
+ }
422
+ break;
423
+ }
424
+ case 'execute': {
425
+ // v6.91.1+: 检查是否还有 pending 任务
426
+ let hasPending = false;
427
+ try {
428
+ const { scanTasks } = await Promise.resolve().then(() => __importStar(require('../core/state')));
429
+ const tasks = await scanTasks(iteration);
430
+ hasPending = tasks.some(t => t.status === 'pending');
431
+ }
432
+ catch { }
433
+ if (!hasPending) {
434
+ logger_1.logger.info(' ✅ 所有任务已完成,跳过 execute');
435
+ }
436
+ else {
437
+ (0, child_process_1.execSync)(`speccore execute --auto -i ${iteration}`, { stdio: 'inherit' });
438
+ }
439
+ break;
440
+ }
441
+ case 'verify': {
442
+ if (optionalSteps['verify'] === 'off') {
443
+ logger_1.logger.info(' ⏭️ 已配置跳过(optional_steps.verify = off)');
444
+ phaseStatus = 'skipped';
445
+ phaseMessage = '已配置跳过';
446
+ break;
447
+ }
448
+ // v8.3.184+: 验证(execute 后,编译+Lint+测试+ui 截图,只报告不阻断)
449
+ let hasPendingV = false;
450
+ try {
451
+ const { scanTasks } = await Promise.resolve().then(() => __importStar(require('../core/state')));
452
+ const tasks = await scanTasks(iteration);
453
+ hasPendingV = tasks.some(t => t.status === 'pending');
454
+ }
455
+ catch { }
456
+ if (hasPendingV) {
457
+ logger_1.logger.info(' ⏭️ 还有 pending 任务,跳过 verify');
458
+ }
459
+ else {
460
+ (0, child_process_1.execSync)(`speccore verify`, { stdio: 'inherit' });
461
+ }
462
+ break;
463
+ }
464
+ case 'pipeline': {
465
+ if (optionalSteps['pipeline'] === 'off') {
466
+ logger_1.logger.info(' ⏭️ 已配置跳过(optional_steps.pipeline = off)');
467
+ phaseStatus = 'skipped';
468
+ phaseMessage = '已配置跳过';
469
+ break;
470
+ }
471
+ // v8.3.184+: 部署(闸口 C 在阶段前已软停,确认后才到这)
472
+ (0, child_process_1.execSync)(`speccore pipeline --all`, { stdio: 'inherit' });
473
+ break;
474
+ }
475
+ case 'pr': {
476
+ if (optionalSteps['pr'] === 'off') {
477
+ logger_1.logger.info(' ⏭️ 已配置跳过(optional_steps.pr = off)');
478
+ phaseStatus = 'skipped';
479
+ phaseMessage = '已配置跳过';
480
+ break;
481
+ }
482
+ // v6.91.1+: 检查是否有未提交的代码变更
483
+ let hasChanges = false;
484
+ try {
485
+ const { execSync: es2 } = await Promise.resolve().then(() => __importStar(require('child_process')));
486
+ const gitStatus = es2('git status --short', { encoding: 'utf-8', cwd: process.cwd() });
487
+ hasChanges = gitStatus.trim().length > 0;
488
+ }
489
+ catch { }
490
+ if (!hasChanges) {
491
+ logger_1.logger.info(' ✅ 没有未提交的变更,跳过 pr');
492
+ }
493
+ else {
494
+ (0, child_process_1.execSync)(`speccore pr -i ${iteration} --force`, { stdio: 'inherit' });
495
+ }
496
+ break;
497
+ }
498
+ case 'done': {
499
+ if (optionalSteps['done'] === 'off') {
500
+ logger_1.logger.info(' ⏭️ 已配置跳过(optional_steps.done = off)');
501
+ phaseStatus = 'skipped';
502
+ phaseMessage = '已配置跳过';
503
+ break;
504
+ }
505
+ // v6.91.1+: 检查是否已归档
506
+ const retroPath = (0, path_1.join)(iterDir, '000-overview', 'RETRO.md');
507
+ if (await (0, fs_extra_1.pathExists)(retroPath)) {
508
+ logger_1.logger.info(' ✅ 已生成复盘报告,跳过 done');
509
+ }
510
+ else {
511
+ (0, child_process_1.execSync)(`speccore done --all -i ${iteration}`, { stdio: 'inherit' });
512
+ }
513
+ break;
514
+ }
515
+ case 'spec2doc': {
516
+ if (optionalSteps['spec2doc'] === 'off') {
517
+ logger_1.logger.info(' ⏭️ 已配置跳过(optional_steps.spec2doc = off)');
518
+ phaseStatus = 'skipped';
519
+ phaseMessage = '已配置跳过';
520
+ break;
521
+ }
522
+ (0, child_process_1.execSync)(`speccore spec2doc -i ${iteration}`, { stdio: 'inherit' });
523
+ break;
524
+ }
525
+ } // end switch
526
+ }
527
+ catch (err) {
528
+ phaseStatus = 'failed';
529
+ phaseMessage = err?.message?.slice(0, 100) || '执行失败';
530
+ logger_1.logger.error(` ❌ ${phase.key} 失败: ${phaseMessage}`);
531
+ }
532
+ // v7.4.0+: 每个阶段执行后收集疑问
533
+ try {
534
+ const qFiles = await (0, questions_1.collectQuestionFiles)();
535
+ for (const qf of qFiles) {
536
+ if (qf.includes(`-${phase.key}-`)) {
537
+ phaseQuestions.push(qf.split('/').pop() || qf);
538
+ }
539
+ }
540
+ }
541
+ catch { }
542
+ phaseResults.push({
543
+ key: phase.key,
544
+ status: phaseStatus,
545
+ message: phaseMessage,
546
+ questions: phaseQuestions,
547
+ duration: Math.round((Date.now() - phaseStart) / 1000),
548
+ });
549
+ // v8.3.187+: 阶段交接摘要(generateHandoff)——关键阶段成功后生成精简传递摘要,
550
+ // 完整产物存文件、传递摘要只给下一阶段 AI 看,控制跨阶段上下文大小。
551
+ // 13 阶段流水线 → 6 阶段 handoff 映射:verify 对应 review 闸口。
552
+ if (phaseStatus === 'success') {
553
+ const handoffMap = {
554
+ analyze: ['analyze', 'taskify'],
555
+ taskify: ['taskify', 'plan'],
556
+ plan: ['plan', 'execute'],
557
+ execute: ['execute', 'review'],
558
+ verify: ['review', 'done'],
559
+ };
560
+ const handoff = handoffMap[phase.key];
561
+ if (handoff) {
562
+ try {
563
+ const handoffPath = await (0, handoff_generator_1.generateHandoff)(handoff[0], handoff[1], iteration);
564
+ logger_1.logger.info(` 📋 阶段交接摘要: ${handoffPath}`);
565
+ }
566
+ catch (err) {
567
+ logger_1.logger.debug(` 交接摘要生成跳过: ${err.message}`);
568
+ }
569
+ }
570
+ }
571
+ logger_1.logger.info('');
572
+ }
573
+ // v7.4.0+: 生成 Pipeline 总结报告
574
+ const totalDuration = Math.round((Date.now() - pipelineStart) / 1000);
575
+ const reportDir = (0, path_1.join)(iterDir, '000-overview');
576
+ await (0, fs_extra_1.ensureDir)(reportDir);
577
+ const reportPath = (0, path_1.join)(reportDir, 'PIPELINE_REPORT.md');
578
+ const now = new Date().toLocaleString('zh-CN');
579
+ let report = `# Pipeline 执行报告\n\n`;
580
+ report += `> 迭代: ${iteration} | 执行时间: ${now} | 总耗时: ${totalDuration}s\n`;
581
+ report += `> 执行范围: ${PIPELINE_PHASES.slice(startIdx, endIdx + 1).map(p => p.key).join(' → ')}\n\n`;
582
+ report += `## 阶段执行结果\n\n`;
583
+ report += `| 阶段 | 状态 | 耗时 | 说明 |\n`;
584
+ report += `| :--- | :--- | :--- | :--- |\n`;
585
+ for (const pr of phaseResults) {
586
+ const icon = pr.status === 'success' ? '✅' : pr.status === 'skipped' ? '⏭️' : pr.status === 'warning' ? '⚠️' : '❌';
587
+ report += `| ${pr.key} | ${icon} ${pr.status} | ${pr.duration}s | ${pr.message} |\n`;
588
+ }
589
+ // 汇总所有疑问
590
+ const allQuestions = phaseResults.filter(pr => pr.questions.length > 0);
591
+ if (allQuestions.length > 0) {
592
+ report += `\n## 疑问清单\n\n`;
593
+ for (const pr of allQuestions) {
594
+ report += `### ${pr.key} 阶段\n\n`;
595
+ for (const q of pr.questions) {
596
+ report += `- ${q}\n`;
597
+ }
598
+ }
599
+ }
600
+ // 失败项汇总
601
+ const failedPhases = phaseResults.filter(pr => pr.status === 'failed');
602
+ if (failedPhases.length > 0) {
603
+ report += `\n## 失败项需要处理\n\n`;
604
+ for (const fp of failedPhases) {
605
+ report += `- **${fp.key}**: ${fp.message}\n`;
606
+ }
607
+ }
608
+ report += `\n---\n> 报告自动生成 by speccore run auto-pipeline\n`;
609
+ await (0, fs_extra_1.writeFile)(reportPath, report, 'utf-8');
610
+ if (pausedGate) {
611
+ logger_1.logger.warn(`\n🛑 Pipeline 已暂停在闸口 ${pausedGate}(完成至 ${phaseResults.map(p => p.key).join(' → ')})`);
612
+ logger_1.logger.warn(` 确认后继续: speccore run --auto --from ${GATES[Object.keys(GATES).find(k => GATES[k].id === pausedGate)].resumeFrom} -I ${iteration}\n`);
613
+ }
614
+ else {
615
+ logger_1.logger.success(`\n✅ Pipeline 完成: ${PIPELINE_PHASES.slice(startIdx, endIdx + 1).map(p => p.key).join(' → ')}`);
616
+ }
617
+ logger_1.logger.info(`📊 执行报告 → ${reportPath}\n`);
618
+ }
619
+ //# sourceMappingURL=run.js.map