speccore 5.71.0 → 5.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.agents/skills/spec-ask/SKILL.md +10 -0
  2. package/.agents/skills/speccore-router/SKILL.md +10 -0
  3. package/README.md +30 -8
  4. package/dist/cli.js +18 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/about.d.ts.map +1 -1
  7. package/dist/commands/about.js +8 -1
  8. package/dist/commands/about.js.map +1 -1
  9. package/dist/commands/analyze.d.ts +3 -0
  10. package/dist/commands/analyze.d.ts.map +1 -1
  11. package/dist/commands/analyze.js +120 -21
  12. package/dist/commands/analyze.js.map +1 -1
  13. package/dist/commands/ask.d.ts +1 -1
  14. package/dist/commands/ask.d.ts.map +1 -1
  15. package/dist/commands/ask.js +12 -5
  16. package/dist/commands/ask.js.map +1 -1
  17. package/dist/commands/change.d.ts +5 -0
  18. package/dist/commands/change.d.ts.map +1 -1
  19. package/dist/commands/change.js +588 -22
  20. package/dist/commands/change.js.map +1 -1
  21. package/dist/commands/code-index.d.ts +18 -0
  22. package/dist/commands/code-index.d.ts.map +1 -0
  23. package/dist/commands/code-index.js +126 -0
  24. package/dist/commands/code-index.js.map +1 -0
  25. package/dist/commands/dashboard.d.ts.map +1 -1
  26. package/dist/commands/dashboard.js +10 -40
  27. package/dist/commands/dashboard.js.map +1 -1
  28. package/dist/commands/dev.d.ts +1 -0
  29. package/dist/commands/dev.d.ts.map +1 -1
  30. package/dist/commands/dev.js +133 -17
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/commands/doc2spec.js +4 -4
  33. package/dist/commands/doc2spec.js.map +1 -1
  34. package/dist/commands/execute.d.ts +2 -0
  35. package/dist/commands/execute.d.ts.map +1 -1
  36. package/dist/commands/execute.js +243 -51
  37. package/dist/commands/execute.js.map +1 -1
  38. package/dist/commands/help.js +17 -7
  39. package/dist/commands/help.js.map +1 -1
  40. package/dist/commands/init.d.ts.map +1 -1
  41. package/dist/commands/init.js +8 -1
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/commands/iteration/create.js +38 -6
  44. package/dist/commands/iteration/create.js.map +1 -1
  45. package/dist/commands/iteration/split.js +70 -12
  46. package/dist/commands/iteration/split.js.map +1 -1
  47. package/dist/commands/lifecycle.d.ts.map +1 -1
  48. package/dist/commands/lifecycle.js +47 -23
  49. package/dist/commands/lifecycle.js.map +1 -1
  50. package/dist/commands/plan.d.ts +20 -0
  51. package/dist/commands/plan.d.ts.map +1 -1
  52. package/dist/commands/plan.js +54 -39
  53. package/dist/commands/plan.js.map +1 -1
  54. package/dist/commands/retro.d.ts +9 -0
  55. package/dist/commands/retro.d.ts.map +1 -1
  56. package/dist/commands/retro.js +61 -15
  57. package/dist/commands/retro.js.map +1 -1
  58. package/dist/commands/status-panel.js +33 -6
  59. package/dist/commands/status-panel.js.map +1 -1
  60. package/dist/commands/validate.d.ts.map +1 -1
  61. package/dist/commands/validate.js +25 -1
  62. package/dist/commands/validate.js.map +1 -1
  63. package/dist/commands/verify.d.ts +22 -0
  64. package/dist/commands/verify.d.ts.map +1 -0
  65. package/dist/commands/verify.js +158 -0
  66. package/dist/commands/verify.js.map +1 -0
  67. package/dist/commands/welcome.d.ts.map +1 -1
  68. package/dist/commands/welcome.js +5 -9
  69. package/dist/commands/welcome.js.map +1 -1
  70. package/dist/core/ai-context-generator.js +2 -2
  71. package/dist/core/ai-context-generator.js.map +1 -1
  72. package/dist/core/analyze-engine.d.ts +19 -0
  73. package/dist/core/analyze-engine.d.ts.map +1 -1
  74. package/dist/core/analyze-engine.js +225 -13
  75. package/dist/core/analyze-engine.js.map +1 -1
  76. package/dist/core/ask-engine.d.ts.map +1 -1
  77. package/dist/core/ask-engine.js +100 -10
  78. package/dist/core/ask-engine.js.map +1 -1
  79. package/dist/core/capabilities.js +3 -3
  80. package/dist/core/code-index-markdown.d.ts +7 -0
  81. package/dist/core/code-index-markdown.d.ts.map +1 -0
  82. package/dist/core/code-index-markdown.js +160 -0
  83. package/dist/core/code-index-markdown.js.map +1 -0
  84. package/dist/core/code-scanner.d.ts +58 -3
  85. package/dist/core/code-scanner.d.ts.map +1 -1
  86. package/dist/core/code-scanner.js +406 -24
  87. package/dist/core/code-scanner.js.map +1 -1
  88. package/dist/core/git-integration.d.ts +10 -0
  89. package/dist/core/git-integration.d.ts.map +1 -1
  90. package/dist/core/git-integration.js +99 -2
  91. package/dist/core/git-integration.js.map +1 -1
  92. package/dist/core/inbox.d.ts +120 -0
  93. package/dist/core/inbox.d.ts.map +1 -0
  94. package/dist/core/inbox.js +377 -0
  95. package/dist/core/inbox.js.map +1 -0
  96. package/dist/core/intent-cache.d.ts.map +1 -1
  97. package/dist/core/intent-cache.js +8 -2
  98. package/dist/core/intent-cache.js.map +1 -1
  99. package/dist/core/intent-recognition.d.ts.map +1 -1
  100. package/dist/core/intent-recognition.js +4 -3
  101. package/dist/core/intent-recognition.js.map +1 -1
  102. package/dist/core/next-steps.js +1 -1
  103. package/dist/core/next-steps.js.map +1 -1
  104. package/dist/core/plan-html.js +3 -3
  105. package/dist/core/prompt-builder.d.ts +7 -0
  106. package/dist/core/prompt-builder.d.ts.map +1 -1
  107. package/dist/core/prompt-builder.js +52 -9
  108. package/dist/core/prompt-builder.js.map +1 -1
  109. package/dist/core/resolver.d.ts +52 -0
  110. package/dist/core/resolver.d.ts.map +1 -0
  111. package/dist/core/resolver.js +257 -0
  112. package/dist/core/resolver.js.map +1 -0
  113. package/dist/core/state.d.ts.map +1 -1
  114. package/dist/core/state.js +46 -14
  115. package/dist/core/state.js.map +1 -1
  116. package/dist/core/verify-engine.d.ts +64 -0
  117. package/dist/core/verify-engine.d.ts.map +1 -0
  118. package/dist/core/verify-engine.js +560 -0
  119. package/dist/core/verify-engine.js.map +1 -0
  120. package/package.json +1 -1
@@ -42,15 +42,30 @@ const child_process_1 = require("child_process");
42
42
  const logger_1 = require("../utils/logger");
43
43
  const context_1 = require("../core/context");
44
44
  const state_1 = require("../core/state");
45
+ const resolver_1 = require("../core/resolver");
45
46
  const transaction_1 = require("../core/transaction");
46
47
  const spec_rules_1 = require("../core/spec-rules");
47
48
  const operation_log_1 = require("../core/operation-log");
48
49
  const next_steps_1 = require("../core/next-steps");
49
50
  const question_checklist_1 = require("../core/question-checklist");
50
51
  const plan_store_1 = require("../core/plan-store");
52
+ const plan_1 = require("./plan");
53
+ const plan_html_1 = require("../core/plan-html");
54
+ const retro_1 = require("./retro");
55
+ const package_json_1 = require("../../package.json");
51
56
  const execution_state_1 = require("../core/execution-state");
52
57
  const git_integration_1 = require("../core/git-integration");
53
58
  const prompt_builder_1 = require("../core/prompt-builder");
59
+ const verify_engine_1 = require("../core/verify-engine");
60
+ const unified_config_1 = require("../core/unified-config");
61
+ /**
62
+ * 解析任务目录路径:优先 030-tasks/,兼容旧布局(迭代根目录)
63
+ */
64
+ async function resolveTaskDir(iterDir, taskId) {
65
+ const tasksDir = (0, path_1.join)(iterDir, '030-tasks');
66
+ const base = (await (0, fs_extra_1.pathExists)(tasksDir)) ? tasksDir : iterDir;
67
+ return taskId ? (0, path_1.join)(base, taskId) : base;
68
+ }
54
69
  async function executeCommand(options) {
55
70
  try {
56
71
  const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
@@ -80,19 +95,24 @@ async function executeCommand(options) {
80
95
  }
81
96
  // Apply filters
82
97
  if (options.task) {
83
- // Support both exact match and prefix match (Task-001 → Task-001-用户登录)
84
- const filtered = tasks.filter(t => t.id === options.task);
85
- if (filtered.length > 0) {
86
- tasks = filtered;
98
+ // 使用统一 resolver 解析任务名(支持短名、关键词、前缀匹配)
99
+ const taskResult = await (0, resolver_1.resolveTask)(options.task, iteration);
100
+ if (taskResult.exact && taskResult.value) {
101
+ if (taskResult.matchType !== 'exact') {
102
+ const hint = (0, resolver_1.formatResolveResult)(taskResult, 'Task');
103
+ if (hint)
104
+ logger_1.logger.info(hint);
105
+ }
106
+ tasks = tasks.filter(t => t.id === taskResult.value.id);
107
+ }
108
+ else if (taskResult.candidates.length > 1) {
109
+ // 多候选时展示列表,让用户选择
110
+ logger_1.logger.warn(taskResult.hint || '找到多个匹配任务,请指定更精确的名称');
111
+ return;
87
112
  }
88
113
  else {
89
- const prefixMatch = tasks.filter(t => t.id && t.id.startsWith(options.task));
90
- if (prefixMatch.length > 0) {
91
- tasks = prefixMatch;
92
- }
93
- else {
94
- logger_1.logger.warn(`Task "${options.task}" not found. Available: ${tasks.map(t => t.id).join(', ')}`);
95
- }
114
+ logger_1.logger.warn(taskResult.hint || `Task "${options.task}" not found`);
115
+ return;
96
116
  }
97
117
  }
98
118
  if (options.type)
@@ -113,6 +133,14 @@ async function executeCommand(options) {
113
133
  logger_1.logger.warn('No tasks match the specified filters');
114
134
  return;
115
135
  }
136
+ // ── --auto: 未指定任务时自动选择第一个待执行任务 ──
137
+ if (options.auto && !options.task && !options.all) {
138
+ const pendingTask = tasks.find(t => t.status === 'pending');
139
+ if (pendingTask) {
140
+ logger_1.logger.info(`🤖 Auto 模式: 自动选择 ${pendingTask.id}`);
141
+ tasks = tasks.filter(t => t.id === pendingTask.id);
142
+ }
143
+ }
116
144
  let sortedTasks = (0, state_1.topologicalSort)(tasks);
117
145
  // 检测并警告循环依赖
118
146
  const cycles = detectCycles(sortedTasks);
@@ -126,7 +154,7 @@ async function executeCommand(options) {
126
154
  : `Execute-${iteration}-${new Date().toISOString().slice(0, 16).replace('T', ' ')}`;
127
155
  const planTasks = sortedTasks.map(t => t.id);
128
156
  if (planTasks.length > 0) {
129
- await (0, plan_store_1.savePlan)({
157
+ const saved = await (0, plan_store_1.savePlan)({
130
158
  name: planName,
131
159
  iteration,
132
160
  tasks: planTasks,
@@ -141,6 +169,31 @@ async function executeCommand(options) {
141
169
  frontend: options.frontend,
142
170
  },
143
171
  });
172
+ // ── 多任务时自动生成 PLAN.md + HTML 可视化 ──
173
+ if (sortedTasks.length > 1) {
174
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
175
+ const planDir = (0, path_1.join)(iterDir, '000-overview', 'plans');
176
+ await (0, fs_extra_1.ensureDir)(planDir);
177
+ const planEntries = (0, plan_1.generatePlan)(sortedTasks, parseInt(options.batchSize || '3', 10), 'auto');
178
+ const planMd = (0, plan_1.formatPlanMarkdown)(planEntries, iteration, sortedTasks);
179
+ // 写入 PLAN.md(最新版)
180
+ const latestPath = (0, path_1.join)(planDir, 'PLAN.md');
181
+ await (0, fs_extra_1.writeFile)(latestPath, planMd, 'utf-8');
182
+ // 写入带时间戳的版本
183
+ const ts = new Date().toISOString().replace(/T/, '-').replace(/:/g, '').slice(0, 17);
184
+ const versionedPath = (0, path_1.join)(planDir, `PLAN-${ts}-auto.md`);
185
+ await (0, fs_extra_1.writeFile)(versionedPath, planMd, 'utf-8');
186
+ // 生成 HTML 可视化
187
+ const htmlData = sortedTasks.map(t => ({
188
+ id: t.id, name: t.name, priority: t.priority, status: t.status,
189
+ owner: t.assignee || undefined, dependsOn: t.dependencies || [],
190
+ }));
191
+ const html = (0, plan_html_1.generatePlanHtml)(htmlData, { version: package_json_1.version, iteration, planName });
192
+ const htmlPath = (0, path_1.join)(planDir, 'speccore-plan.html');
193
+ await (0, fs_extra_1.writeFile)(htmlPath, html, 'utf-8');
194
+ logger_1.logger.info(`📝 计划已生成: ${planDir}/PLAN.md`);
195
+ logger_1.logger.info(`📊 可视化: ${htmlPath}`);
196
+ }
144
197
  }
145
198
  // === Interactive mode ===
146
199
  if (options.interactive) {
@@ -150,9 +203,9 @@ async function executeCommand(options) {
150
203
  // === 显示执行计划 ===
151
204
  const batchSize = parseInt(options.batchSize || '3', 10);
152
205
  printExecutionPreview(sortedTasks, iteration, batchSize);
153
- // === Preview (default, unless --force) ===
154
- if (!options.force) {
155
- logger_1.logger.info('💡 使用 --force 直接执行,或 --interactive 选择执行');
206
+ // === Preview (default, unless --force or --auto) ===
207
+ if (!options.force && !options.auto) {
208
+ logger_1.logger.info('💡 使用 --force 或 --auto 直接执行,或 --interactive 选择执行');
156
209
  return;
157
210
  }
158
211
  logger_1.logger.info('🚀 开始执行...\n');
@@ -303,29 +356,11 @@ async function executeWithProgress(tasks, iteration, base, skip, options) {
303
356
  logger_1.logger.info(' ✅ 没有需要执行的任务');
304
357
  return;
305
358
  }
306
- // Create branches for each task (dependency-aware)
307
- if (tasks.length > 0) {
308
- let defaultBase = base || (0, git_integration_1.detectDefaultBranch)(iteration);
309
- for (const task of tasks) {
310
- let taskBase = base;
311
- // Auto-detect dependency per task from IMPACT.md
312
- if (!taskBase) {
313
- taskBase = await detectDependencyBase(iteration, task.id);
314
- }
315
- const branch = (0, git_integration_1.createTaskBranch)(task.id, task.name || task.id, taskBase, iteration);
316
- if (branch) {
317
- const baseInfo = taskBase ? ` (from ${taskBase})` : ` (from ${defaultBase || 'HEAD'})`;
318
- logger_1.logger.info(`🌿 ${task.id}: ${branch}${baseInfo}`);
319
- }
320
- // 切回基础分支,避免下个任务从上个任务分支上拉代码
321
- if (defaultBase) {
322
- try {
323
- (0, child_process_1.execSync)(`git checkout "${defaultBase}"`, { stdio: 'pipe' });
324
- }
325
- catch { }
326
- }
327
- }
328
- }
359
+ // ── 分支策略:懒创建 + 依赖合并 ──
360
+ // 每个任务的分支在执行前才创建,确保依赖任务的代码已存在
361
+ // 有依赖的任务会 merge 依赖分支,拿到前序任务的代码
362
+ const defaultBase = base || (0, git_integration_1.detectDefaultBranch)(iteration);
363
+ const createdBranches = new Map(); // taskId → branchName
329
364
  // ── Agent mode: output optimized context for external AI ──
330
365
  if (options?.agent) {
331
366
  const agentCtx = buildAgentContext(tasks, options.agent);
@@ -348,6 +383,11 @@ async function executeWithProgress(tasks, iteration, base, skip, options) {
348
383
  // Report current batch
349
384
  logger_1.logger.info(`[${String(i + 1).padStart(2, '0')}/${total}] ${bar} ${progress}%`);
350
385
  logger_1.logger.info(` 🔄 ${task.id} ${task.name || ''} (${task.type || 'feature'})`);
386
+ // ── 懒创建分支 + 合并依赖 ──
387
+ const branchName = prepareTaskBranch(task, iteration, defaultBase, createdBranches);
388
+ if (branchName) {
389
+ logger_1.logger.info(` 🌿 ${task.id}: ${branchName}`);
390
+ }
351
391
  await generateTaskSkeleton(task, iteration);
352
392
  completed.push(`${task.id} - ${task.name || ''}`);
353
393
  logger_1.logger.info(` ✅ ${task.id} completed`);
@@ -377,6 +417,74 @@ async function executeWithProgress(tasks, iteration, base, skip, options) {
377
417
  const postQs = await (0, question_checklist_1.extractQuestions)(await (0, context_1.getIterationDir)(iteration));
378
418
  if (postQs.length > 0)
379
419
  (0, question_checklist_1.showQuestionChecklist)(postQs, '执行后审查');
420
+ // 自动生成任务回顾报告
421
+ const iterDirForRetro = await (0, context_1.getIterationDir)(iteration);
422
+ for (const task of tasks) {
423
+ try {
424
+ await (0, retro_1.generateReport)(task.id, iterDirForRetro);
425
+ logger_1.logger.info(` 📝 回顾报告已生成: ${task.id}/RETRO.md`);
426
+ }
427
+ catch { }
428
+ }
429
+ // ── 强制质量门禁:execute 后自动运行,不可跳过 ──
430
+ // 设计:编译失败时输出 [SPECCORE_EXEC] 标签,由外部 Skill 编排重新执行(最多 3 轮)
431
+ // 每轮独立运行门禁,轮次状态通过 .verify-state.json 持久化
432
+ {
433
+ const config = await (0, unified_config_1.loadConfig)();
434
+ const codePath = config.code_scope?.[0] || process.cwd();
435
+ const absCodePath = codePath.startsWith('/') ? codePath : (0, path_1.join)(process.cwd(), codePath);
436
+ for (const task of tasks) {
437
+ const taskDir = (0, path_1.join)(iterDirForRetro, '030-tasks', task.id);
438
+ const taskCodePath = (await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, 'code'))) ? (0, path_1.join)(taskDir, 'code') : absCodePath;
439
+ // 读取上一轮修复状态(如果有)
440
+ const statePath = (0, path_1.join)(taskDir, '99-artifacts', '.verify-state.json');
441
+ let currentRound = 1;
442
+ if (await (0, fs_extra_1.pathExists)(statePath)) {
443
+ try {
444
+ const state = JSON.parse(await (0, fs_extra_1.readFile)(statePath, 'utf-8'));
445
+ currentRound = (state.round || 0) + 1;
446
+ }
447
+ catch { }
448
+ }
449
+ const gate = await (0, verify_engine_1.runQualityGate)(task.id, taskCodePath, taskDir);
450
+ if (gate.passed) {
451
+ if (currentRound > 1)
452
+ logger_1.logger.info(` ✅ ${task.id}: 第 ${currentRound} 轮修复后通过`);
453
+ // 清除轮次状态
454
+ if (await (0, fs_extra_1.pathExists)(statePath)) {
455
+ const { unlink } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
456
+ try {
457
+ await unlink(statePath);
458
+ }
459
+ catch { }
460
+ }
461
+ }
462
+ else {
463
+ // 有阻塞性失败
464
+ logger_1.logger.warn(` ❌ ${task.id}: 第 ${currentRound} 轮质量门禁未通过`);
465
+ for (const f of gate.blockingFailed) {
466
+ logger_1.logger.warn(` ❌ ${f.name}: ${f.details}`);
467
+ }
468
+ const MAX_ROUNDS = 3;
469
+ if (currentRound < MAX_ROUNDS) {
470
+ // 保存轮次状态 + 输出 [SPECCORE_EXEC] 让 AI 修复
471
+ const { ensureDir } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
472
+ await ensureDir((0, path_1.join)(taskDir, '99-artifacts'));
473
+ await (0, fs_extra_1.writeFile)(statePath, JSON.stringify({ round: currentRound, taskId: task.id, timestamp: new Date().toISOString() }));
474
+ logger_1.logger.info(` 🤖 请求 AI 修复(第 ${currentRound}/${MAX_ROUNDS} 轮)...`);
475
+ (0, verify_engine_1.outputFixTag)(gate.report, taskDir, currentRound);
476
+ }
477
+ else {
478
+ // 3 轮都失败
479
+ logger_1.logger.error(` 💀 ${task.id}: ${MAX_ROUNDS} 轮修复后质量门禁仍未通过`);
480
+ logger_1.logger.info(` 📄 报告: ${(0, path_1.join)(taskDir, '99-artifacts', 'VERIFY_REPORT.md')}`);
481
+ logger_1.logger.info(` 💡 请人工检查并修复`);
482
+ }
483
+ }
484
+ // 输出非阻塞警告
485
+ // (质量门禁内部已经打印了,这里不需要重复)
486
+ }
487
+ }
380
488
  (0, operation_log_1.logOperation)('speccore execute done', `completed ${total} tasks in ${totalElapsed}s`);
381
489
  }
382
490
  // ============================================================
@@ -404,6 +512,17 @@ async function executeResume(iteration) {
404
512
  state = (0, execution_state_1.loadExecutionState)();
405
513
  }
406
514
  logger_1.logger.success('All batches completed!');
515
+ // 自动生成任务回顾报告
516
+ const iterDirForRetro = await (0, context_1.getIterationDir)(iteration);
517
+ for (const taskId of state.completedTasks) {
518
+ try {
519
+ await (0, retro_1.generateReport)(taskId, iterDirForRetro);
520
+ }
521
+ catch { }
522
+ }
523
+ if (state.completedTasks.length > 0) {
524
+ logger_1.logger.info(`📝 已生成 ${state.completedTasks.length} 份回顾报告`);
525
+ }
407
526
  (0, execution_state_1.clearExecutionState)();
408
527
  }
409
528
  // ============================================================
@@ -433,6 +552,15 @@ async function executeBatchMode(tasks, iteration, batchSize, options) {
433
552
  }
434
553
  logger_1.logger.success('All batches completed!');
435
554
  (0, operation_log_1.logOperation)('speccore execute --batch-size', `${tasks.length} tasks in ${state.totalBatches} batches`);
555
+ // 自动生成任务回顾报告
556
+ const iterDirForRetro = await (0, context_1.getIterationDir)(iteration);
557
+ for (const task of tasks) {
558
+ try {
559
+ await (0, retro_1.generateReport)(task.id, iterDirForRetro);
560
+ }
561
+ catch { }
562
+ }
563
+ logger_1.logger.info(`📝 已生成 ${tasks.length} 份回顾报告`);
436
564
  (0, execution_state_1.clearExecutionState)();
437
565
  }
438
566
  // ============================================================
@@ -457,7 +585,7 @@ async function processBatch(tasks, state, iteration) {
457
585
  for (const task of tasks) {
458
586
  logger_1.logger.info(` ${task.id}:`);
459
587
  for (const f of ['REQ.md', 'TECH.md', 'TASK.md']) {
460
- const p = (0, path_1.join)(iterDir, task.id, '00-specs', f);
588
+ const p = (0, path_1.join)(await resolveTaskDir(iterDir), task.id, '00-specs', f);
461
589
  if (await (0, fs_extra_1.pathExists)(p)) {
462
590
  const content = await (0, fs_extra_1.readFile)(p, 'utf-8');
463
591
  const summary = content.slice(0, 80).replace(/\n/g, ' ');
@@ -526,7 +654,7 @@ function printExecutionPreview(tasks, iteration, batchSize = 3) {
526
654
  // Task execution (transaction protected)
527
655
  // ============================================================
528
656
  async function generateTaskSkeleton(task, iteration) {
529
- const taskDir = (0, path_1.join)(await (0, context_1.getIterationDir)(iteration), task.id);
657
+ const taskDir = await resolveTaskDir(await (0, context_1.getIterationDir)(iteration), task.id);
530
658
  let filesUpdated = 0;
531
659
  if (await (0, fs_extra_1.pathExists)(taskDir)) {
532
660
  const tx = new transaction_1.FileTransaction();
@@ -786,7 +914,7 @@ async function filterByPlatform(tasks, iteration, platform) {
786
914
  const filtered = [];
787
915
  const iterDir = await (0, context_1.getIterationDir)(iteration);
788
916
  for (const task of tasks) {
789
- const platformDir = (0, path_1.join)(iterDir, task.id, 'frontend', platform);
917
+ const platformDir = (0, path_1.join)(await resolveTaskDir(iterDir), task.id, 'frontend', platform);
790
918
  if (await (0, fs_extra_1.pathExists)(platformDir))
791
919
  filtered.push(task);
792
920
  }
@@ -830,7 +958,7 @@ async function preFlightCheck(tasks, iteration, options) {
830
958
  logger_1.logger.info('╚══════════════════════════════════════════════╝\n');
831
959
  const approved = [];
832
960
  for (const task of tasks) {
833
- const taskDir = (0, path_1.join)(iterDir, '030-tasks', task.id);
961
+ const taskDir = await resolveTaskDir(iterDir, task.id);
834
962
  logger_1.logger.info(`\n── ${task.id} ──`);
835
963
  let issues = [];
836
964
  // 1. Requirement completeness
@@ -924,6 +1052,65 @@ async function preFlightCheck(tasks, iteration, options) {
924
1052
  logger_1.logger.info('\n✅ 开始执行...\n');
925
1053
  return approved;
926
1054
  }
1055
+ /**
1056
+ * 懒创建任务分支 + 合并依赖分支
1057
+ * 每个任务的分支在执行前才创建,确保依赖任务的代码已存在
1058
+ */
1059
+ function prepareTaskBranch(task, iteration, defaultBase, createdBranches) {
1060
+ const base = defaultBase || 'HEAD';
1061
+ // 1. 切回 base 分支(允许从保护分支拉分支,但 hook 会阻止直接 commit)
1062
+ if (defaultBase && (0, git_integration_1.isProtectedBranch)(defaultBase)) {
1063
+ logger_1.logger.info(` ℹ️ base 分支 '${defaultBase}' 受保护,任务分支将独立工作`);
1064
+ }
1065
+ try {
1066
+ (0, child_process_1.execSync)(`git checkout "${base}"`, { stdio: 'pipe' });
1067
+ }
1068
+ catch {
1069
+ // base 分支不存在时从 HEAD 创建
1070
+ try {
1071
+ (0, child_process_1.execSync)('git checkout -', { stdio: 'pipe' });
1072
+ }
1073
+ catch { }
1074
+ }
1075
+ // 2. 创建任务分支
1076
+ const branch = (0, git_integration_1.createTaskBranch)(task.id, task.name || task.id, undefined, iteration);
1077
+ if (!branch)
1078
+ return null;
1079
+ createdBranches.set(task.id, branch);
1080
+ // 3. 检测依赖并合并
1081
+ // 同步方式检测 IMPACT.md 中的依赖
1082
+ let depTaskIds = [];
1083
+ const impactPath = (0, path_1.join)(`Iteration-${iteration}`, 'IMPACT.md');
1084
+ try {
1085
+ if (require('fs').existsSync(impactPath)) {
1086
+ const impact = require('fs').readFileSync(impactPath, 'utf-8');
1087
+ for (const line of impact.split('\n')) {
1088
+ if (line.includes('→') && line.includes(task.id)) {
1089
+ const match = line.match(/→\s*\|\s*([^|]+)/);
1090
+ if (match) {
1091
+ const depId = match[1].trim().split(':')[0].trim();
1092
+ depTaskIds.push(depId);
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ catch { }
1099
+ // 合并已完成的依赖任务分支
1100
+ for (const depId of depTaskIds) {
1101
+ const depBranch = createdBranches.get(depId);
1102
+ if (depBranch) {
1103
+ try {
1104
+ (0, child_process_1.execSync)(`git merge "${depBranch}" --no-edit --no-ff`, { stdio: 'pipe' });
1105
+ logger_1.logger.info(` 🔗 合并依赖分支: ${depBranch}`);
1106
+ }
1107
+ catch (e) {
1108
+ logger_1.logger.warn(` ⚠️ 合并 ${depBranch} 冲突,需要手动解决`);
1109
+ }
1110
+ }
1111
+ }
1112
+ return branch;
1113
+ }
927
1114
  /**
928
1115
  * 从 IMPACT.md 检测任务依赖,返回应作为 base 的依赖任务 ID
929
1116
  */
@@ -1007,7 +1194,7 @@ async function executionVerifyLoop(tasks, iteration, options) {
1007
1194
  const iterDir = await (0, context_1.getIterationDir)(iteration);
1008
1195
  for (const task of tasks) {
1009
1196
  logger_1.logger.info(`\n🔍 验证 ${task.id}...`);
1010
- const taskDir = (0, path_1.join)(iterDir, '030-tasks', task.id);
1197
+ const taskDir = await resolveTaskDir(iterDir, task.id);
1011
1198
  let allPassed = true;
1012
1199
  for (let round = 1; round <= maxRounds; round++) {
1013
1200
  if (round > 1)
@@ -1130,7 +1317,9 @@ async function runPromptMode(iteration, options) {
1130
1317
  const task = options.task || '';
1131
1318
  // ── 缺参数检测 ──
1132
1319
  if (!task) {
1133
- const taskDir = (0, path_1.join)(await (0, context_1.getIterationDir)(iteration), '030-tasks');
1320
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
1321
+ const tasksDir30 = (0, path_1.join)(iterDir, '030-tasks');
1322
+ const taskDir = (await (0, fs_extra_1.pathExists)(tasksDir30)) ? tasksDir30 : iterDir;
1134
1323
  let availableTasks = [];
1135
1324
  try {
1136
1325
  const entries = await (0, fs_extra_1.readdir)(taskDir, { withFileTypes: true });
@@ -1146,15 +1335,18 @@ async function runPromptMode(iteration, options) {
1146
1335
  });
1147
1336
  return;
1148
1337
  }
1149
- const taskDir = (0, path_1.join)(await (0, context_1.getIterationDir)(iteration), '030-tasks', task);
1338
+ const taskDir = await resolveTaskDir(await (0, context_1.getIterationDir)(iteration), task);
1150
1339
  // ── 前置检查:任务必须有有效需求或分析内容 ──
1151
- const analysisFile = (0, path_1.join)(taskDir, 'ANALYSIS.md');
1152
- const requirementFile = (0, path_1.join)(taskDir, 'REQUIREMENT.md');
1153
- // 读取文件内容验证有效性(接受 ANALYSIS.md REQUIREMENT.md
1154
- // Bug 任务的 REQUIREMENT.md(问题描述)本身就可以作为AI生成代码的依据
1340
+ // 优先检查 00-specs/(新路径),兼容旧路径(Task 根目录)
1341
+ const analysisFile = (0, path_1.join)(taskDir, '00-specs', 'ANALYSIS.md');
1342
+ const requirementFile = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
1343
+ const legacyAnalysis = (0, path_1.join)(taskDir, 'ANALYSIS.md');
1344
+ const legacyRequirement = (0, path_1.join)(taskDir, 'REQUIREMENT.md');
1345
+ // 读取文件内容验证有效性(接受 ANALYSIS.md / REQ.md / REQUIREMENT.md)
1346
+ // Bug 任务的 REQ.md(问题描述)本身就可以作为AI生成代码的依据
1155
1347
  let effectiveAnalysis = false;
1156
1348
  let contentPreview = '';
1157
- for (const f of [analysisFile, requirementFile]) {
1349
+ for (const f of [analysisFile, requirementFile, legacyAnalysis, legacyRequirement]) {
1158
1350
  if (await (0, fs_extra_1.pathExists)(f)) {
1159
1351
  const content = (await (0, fs_extra_1.readFile)(f, 'utf-8')).trim();
1160
1352
  // 有效内容:>80字符 且 不是纯占位符
@@ -1168,7 +1360,7 @@ async function runPromptMode(iteration, options) {
1168
1360
  }
1169
1361
  }
1170
1362
  if (!effectiveAnalysis) {
1171
- const reason = `任务 ${task} 缺少有效的需求或分析内容\n(ANALYSIS.md / REQUIREMENT.md 不存在或内容无效)`;
1363
+ const reason = `任务 ${task} 缺少有效的需求或分析内容\n(00-specs/ANALYSIS.md 或 00-specs/REQ.md 不存在或内容无效)`;
1172
1364
  (0, prompt_builder_1.outputNeedsInfo)({
1173
1365
  command: 'execute',
1174
1366
  missing: ['analysis'],