speccore 5.70.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 (153) 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 +51 -15
  4. package/dist/cli.js +20 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/about.d.ts.map +1 -1
  7. package/dist/commands/about.js +11 -3
  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 +135 -31
  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 +15 -7
  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 +616 -27
  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/context.js +3 -3
  26. package/dist/commands/context.js.map +1 -1
  27. package/dist/commands/dashboard.d.ts.map +1 -1
  28. package/dist/commands/dashboard.js +12 -41
  29. package/dist/commands/dashboard.js.map +1 -1
  30. package/dist/commands/dev.d.ts +1 -0
  31. package/dist/commands/dev.d.ts.map +1 -1
  32. package/dist/commands/dev.js +137 -20
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/diff.js +2 -2
  35. package/dist/commands/diff.js.map +1 -1
  36. package/dist/commands/doc2spec.js +8 -8
  37. package/dist/commands/doc2spec.js.map +1 -1
  38. package/dist/commands/done.js +2 -2
  39. package/dist/commands/done.js.map +1 -1
  40. package/dist/commands/execute.d.ts +2 -0
  41. package/dist/commands/execute.d.ts.map +1 -1
  42. package/dist/commands/execute.js +271 -76
  43. package/dist/commands/execute.js.map +1 -1
  44. package/dist/commands/handover.js +1 -1
  45. package/dist/commands/handover.js.map +1 -1
  46. package/dist/commands/help.js +20 -9
  47. package/dist/commands/help.js.map +1 -1
  48. package/dist/commands/import.js +1 -1
  49. package/dist/commands/import.js.map +1 -1
  50. package/dist/commands/init.d.ts.map +1 -1
  51. package/dist/commands/init.js +105 -19
  52. package/dist/commands/init.js.map +1 -1
  53. package/dist/commands/iteration/create.d.ts.map +1 -1
  54. package/dist/commands/iteration/create.js +101 -4
  55. package/dist/commands/iteration/create.js.map +1 -1
  56. package/dist/commands/iteration/split.d.ts.map +1 -1
  57. package/dist/commands/iteration/split.js +179 -112
  58. package/dist/commands/iteration/split.js.map +1 -1
  59. package/dist/commands/iteration-from-global.js +8 -8
  60. package/dist/commands/iteration-from-global.js.map +1 -1
  61. package/dist/commands/lifecycle.d.ts.map +1 -1
  62. package/dist/commands/lifecycle.js +49 -25
  63. package/dist/commands/lifecycle.js.map +1 -1
  64. package/dist/commands/merge-check.js +1 -1
  65. package/dist/commands/merge-check.js.map +1 -1
  66. package/dist/commands/pattern.js +1 -1
  67. package/dist/commands/pattern.js.map +1 -1
  68. package/dist/commands/plan.d.ts +20 -0
  69. package/dist/commands/plan.d.ts.map +1 -1
  70. package/dist/commands/plan.js +56 -40
  71. package/dist/commands/plan.js.map +1 -1
  72. package/dist/commands/retro.d.ts +9 -0
  73. package/dist/commands/retro.d.ts.map +1 -1
  74. package/dist/commands/retro.js +63 -16
  75. package/dist/commands/retro.js.map +1 -1
  76. package/dist/commands/status-panel.d.ts.map +1 -1
  77. package/dist/commands/status-panel.js +47 -18
  78. package/dist/commands/status-panel.js.map +1 -1
  79. package/dist/commands/sync.js +6 -6
  80. package/dist/commands/sync.js.map +1 -1
  81. package/dist/commands/task/new.js +31 -16
  82. package/dist/commands/task/new.js.map +1 -1
  83. package/dist/commands/trace.js +3 -3
  84. package/dist/commands/trace.js.map +1 -1
  85. package/dist/commands/validate.d.ts.map +1 -1
  86. package/dist/commands/validate.js +25 -1
  87. package/dist/commands/validate.js.map +1 -1
  88. package/dist/commands/verify.d.ts +22 -0
  89. package/dist/commands/verify.d.ts.map +1 -0
  90. package/dist/commands/verify.js +158 -0
  91. package/dist/commands/verify.js.map +1 -0
  92. package/dist/commands/welcome.d.ts.map +1 -1
  93. package/dist/commands/welcome.js +6 -9
  94. package/dist/commands/welcome.js.map +1 -1
  95. package/dist/core/ai-context-generator.js +2 -2
  96. package/dist/core/ai-context-generator.js.map +1 -1
  97. package/dist/core/analyze-engine.d.ts +19 -0
  98. package/dist/core/analyze-engine.d.ts.map +1 -1
  99. package/dist/core/analyze-engine.js +230 -18
  100. package/dist/core/analyze-engine.js.map +1 -1
  101. package/dist/core/ask-engine.d.ts.map +1 -1
  102. package/dist/core/ask-engine.js +101 -11
  103. package/dist/core/ask-engine.js.map +1 -1
  104. package/dist/core/capabilities.js +3 -3
  105. package/dist/core/code-index-markdown.d.ts +7 -0
  106. package/dist/core/code-index-markdown.d.ts.map +1 -0
  107. package/dist/core/code-index-markdown.js +160 -0
  108. package/dist/core/code-index-markdown.js.map +1 -0
  109. package/dist/core/code-scanner.d.ts +58 -3
  110. package/dist/core/code-scanner.d.ts.map +1 -1
  111. package/dist/core/code-scanner.js +406 -24
  112. package/dist/core/code-scanner.js.map +1 -1
  113. package/dist/core/git-integration.d.ts +10 -0
  114. package/dist/core/git-integration.d.ts.map +1 -1
  115. package/dist/core/git-integration.js +99 -2
  116. package/dist/core/git-integration.js.map +1 -1
  117. package/dist/core/inbox.d.ts +120 -0
  118. package/dist/core/inbox.d.ts.map +1 -0
  119. package/dist/core/inbox.js +377 -0
  120. package/dist/core/inbox.js.map +1 -0
  121. package/dist/core/intent-cache.d.ts.map +1 -1
  122. package/dist/core/intent-cache.js +8 -2
  123. package/dist/core/intent-cache.js.map +1 -1
  124. package/dist/core/intent-recognition.d.ts.map +1 -1
  125. package/dist/core/intent-recognition.js +4 -3
  126. package/dist/core/intent-recognition.js.map +1 -1
  127. package/dist/core/name-validator.js +1 -1
  128. package/dist/core/name-validator.js.map +1 -1
  129. package/dist/core/next-steps.js +3 -3
  130. package/dist/core/next-steps.js.map +1 -1
  131. package/dist/core/plan-html.js +3 -3
  132. package/dist/core/prompt-builder.d.ts +7 -0
  133. package/dist/core/prompt-builder.d.ts.map +1 -1
  134. package/dist/core/prompt-builder.js +52 -9
  135. package/dist/core/prompt-builder.js.map +1 -1
  136. package/dist/core/resolver.d.ts +52 -0
  137. package/dist/core/resolver.d.ts.map +1 -0
  138. package/dist/core/resolver.js +257 -0
  139. package/dist/core/resolver.js.map +1 -0
  140. package/dist/core/reverse-sync.js +2 -2
  141. package/dist/core/reverse-sync.js.map +1 -1
  142. package/dist/core/risk-scorer.js +2 -2
  143. package/dist/core/risk-scorer.js.map +1 -1
  144. package/dist/core/state.d.ts.map +1 -1
  145. package/dist/core/state.js +47 -15
  146. package/dist/core/state.js.map +1 -1
  147. package/dist/core/validator.js +9 -9
  148. package/dist/core/validator.js.map +1 -1
  149. package/dist/core/verify-engine.d.ts +64 -0
  150. package/dist/core/verify-engine.d.ts.map +1 -0
  151. package/dist/core/verify-engine.js +560 -0
  152. package/dist/core/verify-engine.js.map +1 -0
  153. 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, 'backend', 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();
@@ -535,9 +663,9 @@ async function generateTaskSkeleton(task, iteration) {
535
663
  const techStack = await (0, spec_rules_1.loadTechStack)();
536
664
  logger_1.logger.info(` Tech Stack: ${techStack.backendFramework} + ${techStack.frontendFramework}`);
537
665
  // 读取后端 Spec 生成代码骨架
538
- const backendDir = (0, path_1.join)(taskDir, 'backend');
666
+ const backendDir = (0, path_1.join)(taskDir, '10-backend');
539
667
  if (await (0, fs_extra_1.pathExists)(backendDir)) {
540
- const reqPath = (0, path_1.join)(backendDir, 'REQ.md');
668
+ const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
541
669
  let className = convertToClassName(task.name || task.id);
542
670
  // 从 CONSTITUTION.md 读取包名,回退到默认值
543
671
  let packageName = `com.example.${className.toLowerCase()}`;
@@ -549,26 +677,28 @@ async function generateTaskSkeleton(task, iteration) {
549
677
  packageName = `${pkgMatch[1]}.${className.toLowerCase()}`;
550
678
  }
551
679
  }
680
+ const srcDir = (0, path_1.join)(backendDir, 'src');
681
+ await (0, fs_extra_1.ensureDir)(srcDir);
552
682
  // 生成 Controller 骨架
553
683
  if (await (0, fs_extra_1.pathExists)(reqPath)) {
554
684
  const req = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
555
685
  const controllerCode = generateJavaController(className, packageName, req, specRules);
556
- const ctrlPath = (0, path_1.join)(backendDir, `${className}Controller.java`);
686
+ const ctrlPath = (0, path_1.join)(srcDir, `${className}Controller.java`);
557
687
  tx.write(ctrlPath, controllerCode);
558
688
  filesUpdated++;
559
689
  }
560
690
  // 生成 Service 骨架
561
691
  const serviceCode = generateJavaService(className, packageName);
562
- const svcPath = (0, path_1.join)(backendDir, `${className}Service.java`);
692
+ const svcPath = (0, path_1.join)(srcDir, `${className}Service.java`);
563
693
  tx.write(svcPath, serviceCode);
564
694
  filesUpdated++;
565
695
  // 生成 Repository 骨架
566
696
  const repoCode = generateJavaRepository(className, packageName);
567
- const repoPath = (0, path_1.join)(backendDir, `${className}Repository.java`);
697
+ const repoPath = (0, path_1.join)(srcDir, `${className}Repository.java`);
568
698
  tx.write(repoPath, repoCode);
569
699
  filesUpdated++;
570
700
  // 更新 TASK.md 状态
571
- const taskMdPath = (0, path_1.join)(backendDir, 'TASK.md');
701
+ const taskMdPath = (0, path_1.join)(taskDir, '00-specs', 'TASK.md');
572
702
  if (await (0, fs_extra_1.pathExists)(taskMdPath)) {
573
703
  const content = await (0, fs_extra_1.readFile)(taskMdPath, 'utf-8');
574
704
  const updated = content.replace('状态: 🔲 待开发', '状态: 🔄 进行中');
@@ -577,7 +707,7 @@ async function generateTaskSkeleton(task, iteration) {
577
707
  }
578
708
  }
579
709
  // 前端各平台代码生成
580
- const frontendDir = (0, path_1.join)(taskDir, 'frontend');
710
+ const frontendDir = (0, path_1.join)(taskDir, '20-frontend');
581
711
  if (await (0, fs_extra_1.pathExists)(frontendDir)) {
582
712
  const { readdir: rd } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
583
713
  const platformDirs = await rd(frontendDir, { withFileTypes: true });
@@ -585,7 +715,9 @@ async function generateTaskSkeleton(task, iteration) {
585
715
  if (pd.isDirectory()) {
586
716
  const componentName = convertToClassName(task.name || task.id);
587
717
  const vueCode = generateVueComponent(componentName);
588
- const vuePath = (0, path_1.join)(frontendDir, pd.name, `${componentName}.vue`);
718
+ const srcDir = (0, path_1.join)(frontendDir, pd.name, 'src');
719
+ await (0, fs_extra_1.ensureDir)(srcDir);
720
+ const vuePath = (0, path_1.join)(srcDir, `${componentName}.vue`);
589
721
  tx.write(vuePath, vueCode);
590
722
  filesUpdated++;
591
723
  const ftaskPath = (0, path_1.join)(frontendDir, pd.name, 'TASK.md');
@@ -782,7 +914,7 @@ async function filterByPlatform(tasks, iteration, platform) {
782
914
  const filtered = [];
783
915
  const iterDir = await (0, context_1.getIterationDir)(iteration);
784
916
  for (const task of tasks) {
785
- 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);
786
918
  if (await (0, fs_extra_1.pathExists)(platformDir))
787
919
  filtered.push(task);
788
920
  }
@@ -826,11 +958,11 @@ async function preFlightCheck(tasks, iteration, options) {
826
958
  logger_1.logger.info('╚══════════════════════════════════════════════╝\n');
827
959
  const approved = [];
828
960
  for (const task of tasks) {
829
- const taskDir = (0, path_1.join)(iterDir, '030-tasks', task.id);
961
+ const taskDir = await resolveTaskDir(iterDir, task.id);
830
962
  logger_1.logger.info(`\n── ${task.id} ──`);
831
963
  let issues = [];
832
964
  // 1. Requirement completeness
833
- const reqPath = (0, path_1.join)(taskDir, 'backend', 'REQ.md');
965
+ const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
834
966
  if (await (0, fs_extra_1.pathExists)(reqPath)) {
835
967
  const req = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
836
968
  const sections = (req.match(/^###?\s+.+/gm) || []).length;
@@ -843,7 +975,7 @@ async function preFlightCheck(tasks, iteration, options) {
843
975
  issues.push('缺少 REQ.md');
844
976
  }
845
977
  // 2. Tech plan
846
- const techPath = (0, path_1.join)(taskDir, 'backend', 'TECH.md');
978
+ const techPath = (0, path_1.join)(taskDir, '00-specs', 'TECH.md');
847
979
  if (await (0, fs_extra_1.pathExists)(techPath)) {
848
980
  const tech = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
849
981
  const s = [tech.includes('数据库') && 'DB', tech.includes('Redis') && 'Redis', tech.includes('MQ') && 'MQ'].filter(Boolean).join('/');
@@ -853,18 +985,18 @@ async function preFlightCheck(tasks, iteration, options) {
853
985
  issues.push('缺少 TECH.md');
854
986
  }
855
987
  // 3. Test cases
856
- const testPath = (0, path_1.join)(taskDir, 'backend', 'TEST.md');
988
+ const testPath = (0, path_1.join)(taskDir, '99-artifacts', 'TEST.md');
857
989
  if (await (0, fs_extra_1.pathExists)(testPath)) {
858
990
  const test = await (0, fs_extra_1.readFile)(testPath, 'utf-8');
859
991
  const n = (test.match(/⬜|✅|❌/g) || []).length;
860
992
  logger_1.logger.info(` 3. 测试: ${n} 用例`);
861
993
  }
862
994
  // 4. Review
863
- logger_1.logger.info(` 4. 审查: ${await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, 'backend', 'REVIEW.md')) ? '✅' : '❌'}`);
995
+ logger_1.logger.info(` 4. 审查: ${await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md')) ? '✅' : '❌'}`);
864
996
  // 5. API
865
997
  logger_1.logger.info(` 5. 契约: ${await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml')) ? '✅' : '⚠️'}`);
866
998
  // 6. Platform
867
- const fd = (0, path_1.join)(taskDir, 'frontend');
999
+ const fd = (0, path_1.join)(taskDir, '20-frontend');
868
1000
  if (await (0, fs_extra_1.pathExists)(fd)) {
869
1001
  const pf = (0, fs_1.readdirSync)(fd, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
870
1002
  logger_1.logger.info(` 6. 端: ${pf.join(', ')}`);
@@ -920,6 +1052,65 @@ async function preFlightCheck(tasks, iteration, options) {
920
1052
  logger_1.logger.info('\n✅ 开始执行...\n');
921
1053
  return approved;
922
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
+ }
923
1114
  /**
924
1115
  * 从 IMPACT.md 检测任务依赖,返回应作为 base 的依赖任务 ID
925
1116
  */
@@ -975,17 +1166,17 @@ function buildAgentContext(tasks, agent) {
975
1166
  ctx += `Status: ${task.status} | Priority: ${task.priority} | Type: ${task.type}\n\n`;
976
1167
  // Progressive loading order
977
1168
  ctx += `**Read in this order (progressive disclosure):**\n`;
978
- ctx += `1. \`Iteration-*/${task.id}/backend/TASK.md\` — task overview + deliverables checklist\n`;
979
- ctx += `2. \`Iteration-*/${task.id}/backend/REQ.md\` — requirements + acceptance criteria\n`;
980
- ctx += `3. \`Iteration-*/${task.id}/backend/TECH.md\` — tech design + architecture\n`;
1169
+ ctx += `1. \`Iteration-*/${task.id}/00-specs/TASK.md\` — task overview + deliverables checklist\n`;
1170
+ ctx += `2. \`Iteration-*/${task.id}/00-specs/REQ.md\` — requirements + acceptance criteria\n`;
1171
+ ctx += `3. \`Iteration-*/${task.id}/00-specs/TECH.md\` — tech design + architecture\n`;
981
1172
  ctx += `4. \`Iteration-*/${task.id}/_shared/API_CONTRACT.yaml\` — API contract (if exists)\n\n`;
982
1173
  ctx += `**Supplementary (read only if needed):**\n`;
983
- ctx += `- TEST.md (test cases) | REVIEW.md (review checklist)\n`;
984
- ctx += `- SCHEMA.md (DB schema) | ADR.md (arch decisions)\n`;
985
- ctx += `- RISK.md (risks+rollback) | DEPS.md (dependencies) | MONITOR.md (monitoring)\n`;
986
- ctx += `- ERROR_CODES.md (error codes) | DEPLOY.md (deployment)\n\n`;
1174
+ ctx += `- 99-artifacts/TEST.md (test cases) | 99-artifacts/REVIEW.md (review checklist)\n`;
1175
+ ctx += `- 00-specs/SCHEMA.md (DB schema) | 99-artifacts/ADR.md (arch decisions)\n`;
1176
+ ctx += `- 99-artifacts/RISK.md (risks+rollback) | 99-artifacts/DEPS.md (dependencies) | 99-artifacts/MONITOR.md (monitoring)\n`;
1177
+ ctx += `- 99-artifacts/ERROR_CODES.md (error codes) | 99-artifacts/DEPLOY.md (deployment)\n\n`;
987
1178
  ctx += `**Frontend (if exists):**\n`;
988
- ctx += `- frontend/{platform}/COMPONENT_TREE.md | ROUTES.md | STATE.md | STYLE_GUIDE.md\n\n`;
1179
+ ctx += `- 20-20-frontend/{platform}/COMPONENT_TREE.md | ROUTES.md | STATE.md | STYLE_GUIDE.md\n\n`;
989
1180
  }
990
1181
  // 3. Global rules (load last, only if needed)
991
1182
  ctx += `### Step N: Global rules (load last)\n`;
@@ -1003,15 +1194,14 @@ async function executionVerifyLoop(tasks, iteration, options) {
1003
1194
  const iterDir = await (0, context_1.getIterationDir)(iteration);
1004
1195
  for (const task of tasks) {
1005
1196
  logger_1.logger.info(`\n🔍 验证 ${task.id}...`);
1006
- const taskDir = (0, path_1.join)(iterDir, '030-tasks', task.id);
1007
- const backendDir = (0, path_1.join)(taskDir, 'backend');
1197
+ const taskDir = await resolveTaskDir(iterDir, task.id);
1008
1198
  let allPassed = true;
1009
1199
  for (let round = 1; round <= maxRounds; round++) {
1010
1200
  if (round > 1)
1011
1201
  logger_1.logger.info(` 🔄 第 ${round} 轮修复...`);
1012
1202
  allPassed = true;
1013
1203
  // 1. 检查 TEST.md
1014
- const testPath = (0, path_1.join)(backendDir, 'TEST.md');
1204
+ const testPath = (0, path_1.join)(taskDir, '99-artifacts', 'TEST.md');
1015
1205
  if (await (0, fs_extra_1.pathExists)(testPath)) {
1016
1206
  const testContent = await (0, fs_extra_1.readFile)(testPath, 'utf-8');
1017
1207
  const total = (testContent.match(/\[[ x]\]/g) || []).length;
@@ -1028,7 +1218,7 @@ async function executionVerifyLoop(tasks, iteration, options) {
1028
1218
  }
1029
1219
  }
1030
1220
  // 2. 检查 REVIEW.md
1031
- const reviewPath = (0, path_1.join)(backendDir, 'REVIEW.md');
1221
+ const reviewPath = (0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md');
1032
1222
  if (await (0, fs_extra_1.pathExists)(reviewPath)) {
1033
1223
  const reviewContent = await (0, fs_extra_1.readFile)(reviewPath, 'utf-8');
1034
1224
  const total = (reviewContent.match(/\[[ x]\]/g) || []).length;
@@ -1045,7 +1235,7 @@ async function executionVerifyLoop(tasks, iteration, options) {
1045
1235
  }
1046
1236
  }
1047
1237
  // 3. 检查 DEPLOY.md
1048
- const deployPath = (0, path_1.join)(backendDir, 'DEPLOY.md');
1238
+ const deployPath = (0, path_1.join)(taskDir, '99-artifacts', 'DEPLOY.md');
1049
1239
  if (await (0, fs_extra_1.pathExists)(deployPath)) {
1050
1240
  const depContent = await (0, fs_extra_1.readFile)(deployPath, 'utf-8');
1051
1241
  const total = (depContent.match(/\[[ x]\]/g) || []).length;
@@ -1127,7 +1317,9 @@ async function runPromptMode(iteration, options) {
1127
1317
  const task = options.task || '';
1128
1318
  // ── 缺参数检测 ──
1129
1319
  if (!task) {
1130
- 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;
1131
1323
  let availableTasks = [];
1132
1324
  try {
1133
1325
  const entries = await (0, fs_extra_1.readdir)(taskDir, { withFileTypes: true });
@@ -1143,15 +1335,18 @@ async function runPromptMode(iteration, options) {
1143
1335
  });
1144
1336
  return;
1145
1337
  }
1146
- 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);
1147
1339
  // ── 前置检查:任务必须有有效需求或分析内容 ──
1148
- const analysisFile = (0, path_1.join)(taskDir, 'ANALYSIS.md');
1149
- const requirementFile = (0, path_1.join)(taskDir, 'REQUIREMENT.md');
1150
- // 读取文件内容验证有效性(接受 ANALYSIS.md REQUIREMENT.md
1151
- // 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生成代码的依据
1152
1347
  let effectiveAnalysis = false;
1153
1348
  let contentPreview = '';
1154
- for (const f of [analysisFile, requirementFile]) {
1349
+ for (const f of [analysisFile, requirementFile, legacyAnalysis, legacyRequirement]) {
1155
1350
  if (await (0, fs_extra_1.pathExists)(f)) {
1156
1351
  const content = (await (0, fs_extra_1.readFile)(f, 'utf-8')).trim();
1157
1352
  // 有效内容:>80字符 且 不是纯占位符
@@ -1165,7 +1360,7 @@ async function runPromptMode(iteration, options) {
1165
1360
  }
1166
1361
  }
1167
1362
  if (!effectiveAnalysis) {
1168
- const reason = `任务 ${task} 缺少有效的需求或分析内容\n(ANALYSIS.md / REQUIREMENT.md 不存在或内容无效)`;
1363
+ const reason = `任务 ${task} 缺少有效的需求或分析内容\n(00-specs/ANALYSIS.md 或 00-specs/REQ.md 不存在或内容无效)`;
1169
1364
  (0, prompt_builder_1.outputNeedsInfo)({
1170
1365
  command: 'execute',
1171
1366
  missing: ['analysis'],