speccore 6.49.12 → 6.49.13
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/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +34 -7
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +34 -17
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +174 -0
- package/dist/commands/iteration/split.js.map +1 -1
- package/package.json +1 -1
|
@@ -46,6 +46,7 @@ const readline_1 = require("readline");
|
|
|
46
46
|
const prompt_builder_1 = require("../../core/prompt-builder");
|
|
47
47
|
const index_guard_1 = require("../../core/index-guard");
|
|
48
48
|
const spec_paths_1 = require("../../core/spec-paths");
|
|
49
|
+
const questions_1 = require("../../core/questions");
|
|
49
50
|
/** 将名称转为目录安全的短 slug(2-4 词) */
|
|
50
51
|
function slugify(name) {
|
|
51
52
|
const cleaned = name
|
|
@@ -463,6 +464,13 @@ async function iterationSplitCommand(options) {
|
|
|
463
464
|
return;
|
|
464
465
|
}
|
|
465
466
|
const iterationDir = await (0, context_1.getIterationDir)(iteration);
|
|
467
|
+
// ── v6.49.13+: 模块驱动拆分 — CLI 按功能模块×端创建任务目录,AI 只填充内容 ──
|
|
468
|
+
if (!options.prompt && !options.response) {
|
|
469
|
+
const moduleDrivenResult = await tryModuleDrivenSplit(iteration, iterationDir, options);
|
|
470
|
+
if (moduleDrivenResult) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
466
474
|
// ── 1. 检查 ANALYSIS.md + AI 智能拆分建议 ──
|
|
467
475
|
const analysisPath = (0, path_1.join)(iterationDir, '020-specs', 'ANALYSIS.md');
|
|
468
476
|
if (await (0, fs_extra_1.pathExists)(analysisPath)) {
|
|
@@ -2615,4 +2623,170 @@ speccore analyze --task ${taskId}${iterFlag}
|
|
|
2615
2623
|
process.stdout.write(md);
|
|
2616
2624
|
process.stdout.write('\n[/SPECCORE_NEXT_STEPS]\n');
|
|
2617
2625
|
}
|
|
2626
|
+
// ── v6.49.13+: 模块驱动拆分 — CLI 控制任务结构,AI 只填内容 ──
|
|
2627
|
+
/**
|
|
2628
|
+
* 尝试模块驱动拆分:从功能模块创建任务目录结构
|
|
2629
|
+
* 成功返回 true,无功能模块时返回 false(回退到传统流程)
|
|
2630
|
+
*/
|
|
2631
|
+
async function tryModuleDrivenSplit(iteration, iterationDir, options) {
|
|
2632
|
+
const reqDir = (0, path_1.join)(iterationDir, '010-requirements');
|
|
2633
|
+
const featuresDir = (0, path_1.join)(reqDir, 'features');
|
|
2634
|
+
// 收集功能模块
|
|
2635
|
+
const modules = [];
|
|
2636
|
+
// 1. 读取 features/*/README.md
|
|
2637
|
+
if (await (0, fs_extra_1.pathExists)(featuresDir)) {
|
|
2638
|
+
try {
|
|
2639
|
+
const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
|
|
2640
|
+
for (const entry of entries) {
|
|
2641
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
2642
|
+
const readmePath = (0, path_1.join)(featuresDir, entry.name, 'README.md');
|
|
2643
|
+
if (await (0, fs_extra_1.pathExists)(readmePath)) {
|
|
2644
|
+
modules.push({ name: entry.name, slug: slugify(entry.name), type: 'feature', sourceFile: `features/${entry.name}/README.md` });
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
catch { }
|
|
2650
|
+
}
|
|
2651
|
+
// 2. 读取类型文档(bugs/refactors/research)
|
|
2652
|
+
for (const typeDir of ['bugs', 'refactors', 'research']) {
|
|
2653
|
+
const typeDirPath = (0, path_1.join)(reqDir, typeDir);
|
|
2654
|
+
if (!(await (0, fs_extra_1.pathExists)(typeDirPath)))
|
|
2655
|
+
continue;
|
|
2656
|
+
try {
|
|
2657
|
+
const entries = await (0, fs_extra_1.readdir)(typeDirPath, { withFileTypes: true });
|
|
2658
|
+
for (const entry of entries) {
|
|
2659
|
+
if (entry.isFile() && entry.name.endsWith('.md') && !(0, task_utils_1.isTimestampBackup)(entry.name)) {
|
|
2660
|
+
const slug = slugify(entry.name.replace('.md', ''));
|
|
2661
|
+
const taskType = typeDir === 'bugs' ? 'bugfix' : typeDir;
|
|
2662
|
+
modules.push({ name: entry.name.replace('.md', ''), slug, type: taskType, sourceFile: `${typeDir}/${entry.name}` });
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
catch { }
|
|
2667
|
+
}
|
|
2668
|
+
if (modules.length === 0) {
|
|
2669
|
+
return false; // 无功能模块,回退到传统流程
|
|
2670
|
+
}
|
|
2671
|
+
// 检测已有任务
|
|
2672
|
+
const existingTasks = await detectExistingTasks(iterationDir);
|
|
2673
|
+
if (existingTasks.length > 0 && !options.force) {
|
|
2674
|
+
logger_1.logger.warn(` ⚠️ 已有 ${existingTasks.length} 个任务: ${existingTasks.slice(0, 5).join(', ')}...`);
|
|
2675
|
+
logger_1.logger.info(' 使用 --force 强制覆盖');
|
|
2676
|
+
return true; // 已处理,不继续传统流程
|
|
2677
|
+
}
|
|
2678
|
+
// --force 清理旧任务
|
|
2679
|
+
if (options.force) {
|
|
2680
|
+
const tasksRoot = (0, path_1.join)(iterationDir, '030-tasks');
|
|
2681
|
+
if (await (0, fs_extra_1.pathExists)(tasksRoot)) {
|
|
2682
|
+
const entries = await (0, fs_extra_1.readdir)(tasksRoot, { withFileTypes: true });
|
|
2683
|
+
for (const entry of entries) {
|
|
2684
|
+
if (entry.isDirectory()) {
|
|
2685
|
+
await (0, fs_extra_1.remove)((0, path_1.join)(tasksRoot, entry.name));
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
logger_1.logger.info(` 🗑️ 已清理旧任务目录`);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
const allPlatforms = await detectPlatforms(iterationDir);
|
|
2692
|
+
logger_1.logger.info(`\n📦 模块驱动拆分: ${modules.length} 个功能模块 × ${allPlatforms.length} 个端`);
|
|
2693
|
+
logger_1.logger.info(` 端列表: ${allPlatforms.join(', ')}`);
|
|
2694
|
+
// 逐模块创建任务目录
|
|
2695
|
+
const createdSections = [];
|
|
2696
|
+
for (const mod of modules) {
|
|
2697
|
+
const { id: taskId } = await (0, global_counters_1.nextTaskId)(mod.name, mod.slug);
|
|
2698
|
+
const section = {
|
|
2699
|
+
name: mod.name,
|
|
2700
|
+
content: '',
|
|
2701
|
+
level: 2,
|
|
2702
|
+
};
|
|
2703
|
+
section._topic = mod.slug;
|
|
2704
|
+
section._taskType = mod.type;
|
|
2705
|
+
section._sourceFile = mod.sourceFile;
|
|
2706
|
+
section._scopePlatforms = allPlatforms;
|
|
2707
|
+
section._complexity = {
|
|
2708
|
+
estimatedHours: 8,
|
|
2709
|
+
hoursByPlatform: {},
|
|
2710
|
+
priority: 'medium',
|
|
2711
|
+
complexity: 'medium',
|
|
2712
|
+
apiCount: 0,
|
|
2713
|
+
dbCount: 0,
|
|
2714
|
+
pageCount: 0,
|
|
2715
|
+
wordCount: 0,
|
|
2716
|
+
};
|
|
2717
|
+
section._owner = '未分配';
|
|
2718
|
+
section._taskId = taskId;
|
|
2719
|
+
section.functionalUnit = mod.name;
|
|
2720
|
+
await createTaskFromSection(iterationDir, taskId, section, allPlatforms, mod.type, []);
|
|
2721
|
+
createdSections.push(section);
|
|
2722
|
+
logger_1.logger.info(` ✅ 创建: ${taskId} [${mod.type}] — ${mod.name} (${allPlatforms.length} 个端子任务)`);
|
|
2723
|
+
}
|
|
2724
|
+
// 生成任务总览
|
|
2725
|
+
if (createdSections.length > 0) {
|
|
2726
|
+
await generateImpactGraph(iterationDir, createdSections, allPlatforms);
|
|
2727
|
+
logger_1.logger.info(`\n 📊 创建了 ${createdSections.length} 个任务(每端一个子任务)`);
|
|
2728
|
+
// 生成内容填充提示
|
|
2729
|
+
const fillPrompt = buildContentFillingPrompt(iteration, iterationDir, createdSections, allPlatforms);
|
|
2730
|
+
const promptsDir = (0, path_1.join)('.speccore', 'prompts');
|
|
2731
|
+
await (0, fs_extra_1.ensureDir)(promptsDir);
|
|
2732
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(promptsDir, `split-content-${iteration}.md`), fillPrompt);
|
|
2733
|
+
logger_1.logger.info(` 📝 内容填充提示 → .speccore/prompts/split-content-${iteration}.md`);
|
|
2734
|
+
}
|
|
2735
|
+
logger_1.logger.success(`✅ 模块驱动拆分完成: ${createdSections.length} 个任务`);
|
|
2736
|
+
// 自动刷新知识图谱
|
|
2737
|
+
try {
|
|
2738
|
+
const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../../core/knowledge-graph')));
|
|
2739
|
+
await refreshKnowledgeGraph(process.cwd(), iteration);
|
|
2740
|
+
logger_1.logger.info('🧠 知识图谱已刷新');
|
|
2741
|
+
}
|
|
2742
|
+
catch { }
|
|
2743
|
+
return true;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* 生成内容填充 Prompt — AI 为预创建的任务填充 REQ.md/TECH.md
|
|
2747
|
+
*/
|
|
2748
|
+
function buildContentFillingPrompt(iteration, iterationDir, sections, allPlatforms) {
|
|
2749
|
+
let p = `# 任务内容填充(模块驱动拆分)\n\n`;
|
|
2750
|
+
p += `> 迭代: ${iteration} | 任务数: ${sections.length} | 端: ${allPlatforms.join(', ')}\n\n`;
|
|
2751
|
+
p += `## 说明\n\n`;
|
|
2752
|
+
p += `CLI 已按功能模块×端创建了任务目录结构。每个任务目录下已有子任务目录(含 .meta/、TASK.md 等)。\n`;
|
|
2753
|
+
p += `你的任务是为每个子任务填充 REQ.md 和 TECH.md。\n\n`;
|
|
2754
|
+
p += `## 上下文\n\n`;
|
|
2755
|
+
p += `1. Read .speccore/CONSTITUTION.md — 项目配置\n`;
|
|
2756
|
+
p += `2. Read 020-specs/global/REQUIREMENT.md — 全局需求规格\n`;
|
|
2757
|
+
p += `3. Read 020-specs/global/ANALYSIS.md — 全局分析报告\n`;
|
|
2758
|
+
p += `4. Read 020-specs/global/TECH.md — 整体技术架构\n`;
|
|
2759
|
+
p += `5. Read 020-specs/{端}/TECH.md — 各端专属技术方案\n\n`;
|
|
2760
|
+
p += `## 任务清单\n\n`;
|
|
2761
|
+
for (const sec of sections) {
|
|
2762
|
+
const taskId = sec._taskId || sec.name;
|
|
2763
|
+
const sourceFile = sec._sourceFile || '';
|
|
2764
|
+
const featureName = sec.functionalUnit || sec.name;
|
|
2765
|
+
p += `### ${taskId} — ${sec.name}\n`;
|
|
2766
|
+
p += `- 功能单元: ${featureName}\n`;
|
|
2767
|
+
if (sourceFile)
|
|
2768
|
+
p += `- 来源: 010-requirements/${sourceFile}\n`;
|
|
2769
|
+
p += `- 子任务目录: ${allPlatforms.map(pl => `${pl}/`).join(', ')}\n`;
|
|
2770
|
+
p += `- 需要填充:\n`;
|
|
2771
|
+
for (const platform of allPlatforms) {
|
|
2772
|
+
p += ` - ${platform}/*/REQ.md — 子任务需求规格\n`;
|
|
2773
|
+
p += ` - ${platform}/*/TECH.md — 子任务技术方案\n`;
|
|
2774
|
+
}
|
|
2775
|
+
p += `\n`;
|
|
2776
|
+
}
|
|
2777
|
+
p += `## 填充规则\n\n`;
|
|
2778
|
+
p += `1. 先 Read 子任务目录下的 TASK.md(已有基本信息)和 .meta/feature(功能单元名)\n`;
|
|
2779
|
+
p += `2. REQ.md: 根据全局需求文档,撰写本子任务的需求规格(验收标准、业务规则、边界条件)\n`;
|
|
2780
|
+
p += `3. TECH.md: 根据全局 TECH.md,细化本子任务的技术方案(接口定义、数据模型、核心逻辑)\n`;
|
|
2781
|
+
p += `4. 用 Write 工具直接写入对应路径\n`;
|
|
2782
|
+
p += `5. 同一功能模块的各端子任务要保持 API 契约一致\n`;
|
|
2783
|
+
p += `6. 禁止产出垃圾内容——每个文件必须有实质性专业内容\n\n`;
|
|
2784
|
+
p += `## ⚠️ 绝对禁止\n\n`;
|
|
2785
|
+
p += `- 不要创建新目录 — 目录已由 CLI 创建\n`;
|
|
2786
|
+
p += `- 不要修改 .meta/ 下的文件\n`;
|
|
2787
|
+
p += `- 不要修改 TASK.md(已由 CLI 生成)\n`;
|
|
2788
|
+
p += `- 只写 REQ.md 和 TECH.md\n`;
|
|
2789
|
+
p += '\n' + (0, questions_1.buildAutoModeInstruction)('split', iteration) + '\n';
|
|
2790
|
+
return p;
|
|
2791
|
+
}
|
|
2618
2792
|
//# sourceMappingURL=split.js.map
|