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.
- package/.agents/skills/spec-ask/SKILL.md +10 -0
- package/.agents/skills/speccore-router/SKILL.md +10 -0
- package/README.md +30 -8
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +8 -1
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze.d.ts +3 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +120 -21
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +12 -5
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/change.d.ts +5 -0
- package/dist/commands/change.d.ts.map +1 -1
- package/dist/commands/change.js +588 -22
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/code-index.d.ts +18 -0
- package/dist/commands/code-index.d.ts.map +1 -0
- package/dist/commands/code-index.js +126 -0
- package/dist/commands/code-index.js.map +1 -0
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +10 -40
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +133 -17
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doc2spec.js +4 -4
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/execute.d.ts +2 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +243 -51
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +17 -7
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/create.js +38 -6
- package/dist/commands/iteration/create.js.map +1 -1
- package/dist/commands/iteration/split.js +70 -12
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/lifecycle.d.ts.map +1 -1
- package/dist/commands/lifecycle.js +47 -23
- package/dist/commands/lifecycle.js.map +1 -1
- package/dist/commands/plan.d.ts +20 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +54 -39
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/retro.d.ts +9 -0
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +61 -15
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/status-panel.js +33 -6
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +25 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +22 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +158 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +5 -9
- package/dist/commands/welcome.js.map +1 -1
- package/dist/core/ai-context-generator.js +2 -2
- package/dist/core/ai-context-generator.js.map +1 -1
- package/dist/core/analyze-engine.d.ts +19 -0
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +225 -13
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +100 -10
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/capabilities.js +3 -3
- package/dist/core/code-index-markdown.d.ts +7 -0
- package/dist/core/code-index-markdown.d.ts.map +1 -0
- package/dist/core/code-index-markdown.js +160 -0
- package/dist/core/code-index-markdown.js.map +1 -0
- package/dist/core/code-scanner.d.ts +58 -3
- package/dist/core/code-scanner.d.ts.map +1 -1
- package/dist/core/code-scanner.js +406 -24
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/git-integration.d.ts +10 -0
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +99 -2
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/inbox.d.ts +120 -0
- package/dist/core/inbox.d.ts.map +1 -0
- package/dist/core/inbox.js +377 -0
- package/dist/core/inbox.js.map +1 -0
- package/dist/core/intent-cache.d.ts.map +1 -1
- package/dist/core/intent-cache.js +8 -2
- package/dist/core/intent-cache.js.map +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +4 -3
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/next-steps.js +1 -1
- package/dist/core/next-steps.js.map +1 -1
- package/dist/core/plan-html.js +3 -3
- package/dist/core/prompt-builder.d.ts +7 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +52 -9
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/resolver.d.ts +52 -0
- package/dist/core/resolver.d.ts.map +1 -0
- package/dist/core/resolver.js +257 -0
- package/dist/core/resolver.js.map +1 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +46 -14
- package/dist/core/state.js.map +1 -1
- package/dist/core/verify-engine.d.ts +64 -0
- package/dist/core/verify-engine.d.ts.map +1 -0
- package/dist/core/verify-engine.js +560 -0
- package/dist/core/verify-engine.js.map +1 -0
- package/package.json +1 -1
package/dist/commands/execute.js
CHANGED
|
@@ -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
|
-
//
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
1338
|
+
const taskDir = await resolveTaskDir(await (0, context_1.getIterationDir)(iteration), task);
|
|
1150
1339
|
// ── 前置检查:任务必须有有效需求或分析内容 ──
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
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 /
|
|
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'],
|