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.
- package/.agents/skills/spec-ask/SKILL.md +10 -0
- package/.agents/skills/speccore-router/SKILL.md +10 -0
- package/README.md +51 -15
- package/dist/cli.js +20 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +11 -3
- 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 +135 -31
- 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 +15 -7
- 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 +616 -27
- 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/context.js +3 -3
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +12 -41
- 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 +137 -20
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.js +2 -2
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doc2spec.js +8 -8
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/done.js +2 -2
- package/dist/commands/done.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 +271 -76
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/handover.js +1 -1
- package/dist/commands/handover.js.map +1 -1
- package/dist/commands/help.js +20 -9
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +105 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/create.d.ts.map +1 -1
- package/dist/commands/iteration/create.js +101 -4
- package/dist/commands/iteration/create.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +179 -112
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/iteration-from-global.js +8 -8
- package/dist/commands/iteration-from-global.js.map +1 -1
- package/dist/commands/lifecycle.d.ts.map +1 -1
- package/dist/commands/lifecycle.js +49 -25
- package/dist/commands/lifecycle.js.map +1 -1
- package/dist/commands/merge-check.js +1 -1
- package/dist/commands/merge-check.js.map +1 -1
- package/dist/commands/pattern.js +1 -1
- package/dist/commands/pattern.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 +56 -40
- 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 +63 -16
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +47 -18
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/sync.js +6 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/task/new.js +31 -16
- package/dist/commands/task/new.js.map +1 -1
- package/dist/commands/trace.js +3 -3
- package/dist/commands/trace.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 +6 -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 +230 -18
- 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 +101 -11
- 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/name-validator.js +1 -1
- package/dist/core/name-validator.js.map +1 -1
- package/dist/core/next-steps.js +3 -3
- 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/reverse-sync.js +2 -2
- package/dist/core/reverse-sync.js.map +1 -1
- package/dist/core/risk-scorer.js +2 -2
- package/dist/core/risk-scorer.js.map +1 -1
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +47 -15
- package/dist/core/state.js.map +1 -1
- package/dist/core/validator.js +9 -9
- package/dist/core/validator.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, '
|
|
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();
|
|
@@ -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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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
|
|
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 =
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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}/
|
|
979
|
-
ctx += `2. \`Iteration-*/${task.id}/
|
|
980
|
-
ctx += `3. \`Iteration-*/${task.id}/
|
|
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 =
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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
|
|
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 =
|
|
1338
|
+
const taskDir = await resolveTaskDir(await (0, context_1.getIterationDir)(iteration), task);
|
|
1147
1339
|
// ── 前置检查:任务必须有有效需求或分析内容 ──
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
|
|
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 /
|
|
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'],
|