speccore 7.4.2 → 7.4.4
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 +3 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.js +178 -15
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/spec-paths.d.ts +1 -0
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +82 -2
- package/dist/core/spec-paths.js.map +1 -1
- package/package.json +1 -1
|
@@ -702,7 +702,7 @@ async function iterationSplitCommand(options) {
|
|
|
702
702
|
// v6.76.0+: 传入过滤条件,限制拆分范围;传入已有 Task 结构,支持增量拆分
|
|
703
703
|
const modulesFilter = options.modules ? options.modules.split(',').map(m => m.trim()).filter(Boolean) : undefined;
|
|
704
704
|
const platformsFilter = options.platforms ? options.platforms.split(',').map(p => p.trim()).filter(Boolean) : undefined;
|
|
705
|
-
let splitPrompt = buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, allPlatforms, modulesFilter, platformsFilter, existingTaskStructure);
|
|
705
|
+
let splitPrompt = await buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, allPlatforms, modulesFilter, platformsFilter, existingTaskStructure, iterationDir);
|
|
706
706
|
// 注入全局上下文(INDEX + TOC 目录,AI 自主读取)
|
|
707
707
|
const { loadGlobalContext, formatGlobalContext } = await Promise.resolve().then(() => __importStar(require('../../core/prompt-builder')));
|
|
708
708
|
const globalCtx = await loadGlobalContext(process.cwd(), 'split');
|
|
@@ -2445,7 +2445,7 @@ ${isH5 ? '移动端优先,适配 375/414/768' :
|
|
|
2445
2445
|
/**
|
|
2446
2446
|
* 构建完整的 AI 智能拆分 Prompt(含 SpecCore 理念 + 粒度规则 + 完整上下文)
|
|
2447
2447
|
*/
|
|
2448
|
-
function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, standardPlatforms, modulesFilter, platformsFilter, existingTasks) {
|
|
2448
|
+
async function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, standardPlatforms, modulesFilter, platformsFilter, existingTasks, iterationDir) {
|
|
2449
2449
|
let p = `# SpecCore AI 智能拆分\n\n`;
|
|
2450
2450
|
p += `> 迭代: ${iteration} | 生成: ${new Date().toISOString().split('T')[0]}\n\n`;
|
|
2451
2451
|
// v6.76.0+: 拆分范围限制
|
|
@@ -2496,6 +2496,34 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
|
|
|
2496
2496
|
p += `- ✅ **必须使用 CONSTITUTION.md 中的标准端名**作为子任务目录名\n`;
|
|
2497
2497
|
p += `- scope 数组和子任务目录名必须完全匹配上述标准端名\n\n`;
|
|
2498
2498
|
}
|
|
2499
|
+
// v7.4.2+: 功能单元精准上下文(CLI 提取,AI 拆分)
|
|
2500
|
+
if (iterationDir) {
|
|
2501
|
+
try {
|
|
2502
|
+
const funcMapPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'FUNCTION_MAP.md');
|
|
2503
|
+
if (await (0, fs_extra_1.pathExists)(funcMapPath)) {
|
|
2504
|
+
const funcMapContent = await (0, fs_extra_1.readFile)(funcMapPath, 'utf-8');
|
|
2505
|
+
const funcUnits = parseFunctionMap(funcMapContent, standardPlatforms);
|
|
2506
|
+
if (funcUnits.length > 0) {
|
|
2507
|
+
p += `## 🧩 功能单元上下文(CLI 从分析文档精准提取)\n\n`;
|
|
2508
|
+
p += `> 以下是从 FUNCTION_MAP.md / REQUIREMENT.md / _INDEX.md / _MODULES.md 中为每个功能单元提取的相关上下文。\n`;
|
|
2509
|
+
p += `> 拆分时必须基于这些功能单元,每个功能单元对应 1~3 个任务。\n\n`;
|
|
2510
|
+
for (const unit of funcUnits) {
|
|
2511
|
+
const unitCtx = await assembleUnitContext(iterationDir, unit.name, unit.platforms);
|
|
2512
|
+
p += `### 📌 功能单元: ${unit.name}\n`;
|
|
2513
|
+
p += `- 涉及端: ${unit.platforms.join(', ')}\n`;
|
|
2514
|
+
if (unit.sharedCapability && unit.sharedCapability !== '无')
|
|
2515
|
+
p += `- 共享能力: ${unit.sharedCapability}\n`;
|
|
2516
|
+
if (unit.dependsOn && unit.dependsOn.length > 0)
|
|
2517
|
+
p += `- 依赖: ${unit.dependsOn.join(', ')}\n`;
|
|
2518
|
+
if (unit.description)
|
|
2519
|
+
p += `- 说明: ${unit.description}\n`;
|
|
2520
|
+
p += `\n${unitCtx}\n\n---\n\n`;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
catch { }
|
|
2526
|
+
}
|
|
2499
2527
|
// SpecCore 拆分原则
|
|
2500
2528
|
p += `## ⚙️ SpecCore 拆分原则\n\n`;
|
|
2501
2529
|
p += `SpecCore 核心理念: "Code by Spec, Not by Vibe" — 每个任务必须有对应的 Spec,AI 在 Spec 约束下工作。\n\n`;
|
|
@@ -3353,7 +3381,7 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
|
|
|
3353
3381
|
await generateImpactGraph(iterationDir, createdSections, allPlatforms);
|
|
3354
3382
|
logger_1.logger.info(`\n 📊 创建了 ${createdSections.length} 个任务(每端一个子任务)`);
|
|
3355
3383
|
// 生成内容填充提示
|
|
3356
|
-
const fillPrompt = buildContentFillingPrompt(iteration, iterationDir, createdSections, allPlatforms);
|
|
3384
|
+
const fillPrompt = await buildContentFillingPrompt(iteration, iterationDir, createdSections, allPlatforms);
|
|
3357
3385
|
const promptsDir = (0, path_1.join)('.speccore', 'prompts');
|
|
3358
3386
|
await (0, fs_extra_1.ensureDir)(promptsDir);
|
|
3359
3387
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(promptsDir, `split-content-${iteration}.md`), fillPrompt);
|
|
@@ -3369,22 +3397,152 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
|
|
|
3369
3397
|
catch { }
|
|
3370
3398
|
return true;
|
|
3371
3399
|
}
|
|
3400
|
+
/**
|
|
3401
|
+
* v7.4.2+: 按功能单元从各分析文档中精准提取相关上下文
|
|
3402
|
+
* CLI 做计算(提取),AI 做内容(填充)
|
|
3403
|
+
*/
|
|
3404
|
+
async function assembleUnitContext(iterationDir, unitName, platforms) {
|
|
3405
|
+
const specsBase = (0, path_1.join)(iterationDir, '020-specs');
|
|
3406
|
+
const overviewDir = (0, path_1.join)(specsBase, spec_paths_1.GLOBAL_SPECS_DIR);
|
|
3407
|
+
const ctx = [];
|
|
3408
|
+
// 关键词拆分:将功能单元名拆成多个搜索词(如「预订管理」→「预订」「管理」)
|
|
3409
|
+
const keywords = unitName.split(/(?<=[\u4e00-\u9fff])(?=[\u4e00-\u9fff])/).filter(k => k.length >= 1);
|
|
3410
|
+
// 也加入英文关键词(如 booking、checkin)
|
|
3411
|
+
const slugKeywords = slugify(unitName).split('-').filter(k => k.length >= 3);
|
|
3412
|
+
const allKeywords = [...keywords, ...slugKeywords];
|
|
3413
|
+
const matchLine = (line) => allKeywords.some(k => line.includes(k));
|
|
3414
|
+
// 1. 从 overview/REQUIREMENT.md 提取相关章节
|
|
3415
|
+
const reqPath = (0, path_1.join)(overviewDir, 'REQUIREMENT.md');
|
|
3416
|
+
if (await (0, fs_extra_1.pathExists)(reqPath)) {
|
|
3417
|
+
try {
|
|
3418
|
+
const content = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
|
|
3419
|
+
const lines = content.split('\n');
|
|
3420
|
+
const relevant = [];
|
|
3421
|
+
let inRelevantSection = false;
|
|
3422
|
+
for (const line of lines) {
|
|
3423
|
+
if (line.startsWith('## ') || line.startsWith('### ')) {
|
|
3424
|
+
inRelevantSection = matchLine(line);
|
|
3425
|
+
}
|
|
3426
|
+
if (inRelevantSection || (line.startsWith('|') && matchLine(line))) {
|
|
3427
|
+
relevant.push(line);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
if (relevant.length > 0) {
|
|
3431
|
+
ctx.push(`### 📋 需求规格(REQUIREMENT.md 相关片段)\n\`\`\`\n${relevant.slice(0, 60).join('\n')}\n\`\`\``);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
catch { }
|
|
3435
|
+
}
|
|
3436
|
+
// 2. 从 platforms/_shared/_MODULES.md 提取相关模块
|
|
3437
|
+
const modulesPath = (0, path_1.join)(specsBase, 'platforms', '_shared', '_MODULES.md');
|
|
3438
|
+
if (await (0, fs_extra_1.pathExists)(modulesPath)) {
|
|
3439
|
+
try {
|
|
3440
|
+
const content = await (0, fs_extra_1.readFile)(modulesPath, 'utf-8');
|
|
3441
|
+
const lines = content.split('\n');
|
|
3442
|
+
const relevant = [];
|
|
3443
|
+
let inRelevantBlock = false;
|
|
3444
|
+
let blockLines = [];
|
|
3445
|
+
for (const line of lines) {
|
|
3446
|
+
if (line.startsWith('## ')) {
|
|
3447
|
+
// 保存上一个块
|
|
3448
|
+
if (inRelevantBlock && blockLines.length > 0) {
|
|
3449
|
+
relevant.push(...blockLines);
|
|
3450
|
+
}
|
|
3451
|
+
inRelevantBlock = matchLine(line);
|
|
3452
|
+
blockLines = [line];
|
|
3453
|
+
}
|
|
3454
|
+
else if (inRelevantBlock) {
|
|
3455
|
+
blockLines.push(line);
|
|
3456
|
+
}
|
|
3457
|
+
else if (line.startsWith('|') && matchLine(line)) {
|
|
3458
|
+
relevant.push(line);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
if (inRelevantBlock && blockLines.length > 0)
|
|
3462
|
+
relevant.push(...blockLines);
|
|
3463
|
+
if (relevant.length > 0) {
|
|
3464
|
+
ctx.push(`### 🧩 功能模块(_MODULES.md 相关片段)\n\`\`\`\n${relevant.slice(0, 50).join('\n')}\n\`\`\``);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
catch { }
|
|
3468
|
+
}
|
|
3469
|
+
// 3. 从各端 _INDEX.md 提取相关接口/页面
|
|
3470
|
+
for (const platform of platforms) {
|
|
3471
|
+
const indexPath = (0, path_1.join)(specsBase, 'platforms', platform, '_INDEX.md');
|
|
3472
|
+
if (await (0, fs_extra_1.pathExists)(indexPath)) {
|
|
3473
|
+
try {
|
|
3474
|
+
const content = await (0, fs_extra_1.readFile)(indexPath, 'utf-8');
|
|
3475
|
+
const lines = content.split('\n');
|
|
3476
|
+
const relevant = [];
|
|
3477
|
+
let inRelevantSection = false;
|
|
3478
|
+
for (const line of lines) {
|
|
3479
|
+
if (line.startsWith('## ') || line.startsWith('### ')) {
|
|
3480
|
+
inRelevantSection = matchLine(line);
|
|
3481
|
+
}
|
|
3482
|
+
if (inRelevantSection || (line.startsWith('|') && matchLine(line))) {
|
|
3483
|
+
relevant.push(line);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
if (relevant.length > 0) {
|
|
3487
|
+
ctx.push(`### 🖥️ ${platform} 端相关(_INDEX.md 片段)\n\`\`\`\n${relevant.slice(0, 40).join('\n')}\n\`\`\``);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
catch { }
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
// 4. 从 overview/FUNCTION_MAP.md 提取本单元行 + 依赖单元
|
|
3494
|
+
const funcMapPath = (0, path_1.join)(overviewDir, 'FUNCTION_MAP.md');
|
|
3495
|
+
if (await (0, fs_extra_1.pathExists)(funcMapPath)) {
|
|
3496
|
+
try {
|
|
3497
|
+
const content = await (0, fs_extra_1.readFile)(funcMapPath, 'utf-8');
|
|
3498
|
+
const lines = content.split('\n');
|
|
3499
|
+
const matchedRows = lines.filter(l => l.startsWith('|') && matchLine(l));
|
|
3500
|
+
if (matchedRows.length > 0) {
|
|
3501
|
+
ctx.push(`### 🗺️ 功能映射(FUNCTION_MAP.md)\n\`\`\`\n${matchedRows.join('\n')}\n\`\`\``);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
catch { }
|
|
3505
|
+
}
|
|
3506
|
+
// 5. 从 overview/TECH.md 提取相关架构信息
|
|
3507
|
+
const techPath = (0, path_1.join)(overviewDir, 'TECH.md');
|
|
3508
|
+
if (await (0, fs_extra_1.pathExists)(techPath)) {
|
|
3509
|
+
try {
|
|
3510
|
+
const content = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
|
|
3511
|
+
const lines = content.split('\n');
|
|
3512
|
+
const relevant = [];
|
|
3513
|
+
let inRelevantSection = false;
|
|
3514
|
+
for (const line of lines) {
|
|
3515
|
+
if (line.startsWith('## ') || line.startsWith('### ')) {
|
|
3516
|
+
inRelevantSection = matchLine(line);
|
|
3517
|
+
}
|
|
3518
|
+
if (inRelevantSection)
|
|
3519
|
+
relevant.push(line);
|
|
3520
|
+
}
|
|
3521
|
+
if (relevant.length > 0) {
|
|
3522
|
+
ctx.push(`### 🏗️ 技术架构相关(TECH.md 片段)\n\`\`\`\n${relevant.slice(0, 40).join('\n')}\n\`\`\``);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
catch { }
|
|
3526
|
+
}
|
|
3527
|
+
if (ctx.length === 0) {
|
|
3528
|
+
return `> 功能单元「${unitName}」未在分析文档中找到直接相关内容,请根据全局文档推断。`;
|
|
3529
|
+
}
|
|
3530
|
+
return ctx.join('\n\n');
|
|
3531
|
+
}
|
|
3372
3532
|
/**
|
|
3373
3533
|
* 生成内容填充 Prompt — AI 为预创建的任务填充 REQ.md/TECH.md
|
|
3534
|
+
* v7.4.2+: 每个功能单元注入精准上下文,而非让 AI 自己从全量文档中查找
|
|
3374
3535
|
*/
|
|
3375
|
-
function buildContentFillingPrompt(iteration, iterationDir, sections, allPlatforms) {
|
|
3536
|
+
async function buildContentFillingPrompt(iteration, iterationDir, sections, allPlatforms) {
|
|
3376
3537
|
let p = `# 任务内容填充(模块驱动拆分)\n\n`;
|
|
3377
3538
|
p += `> 迭代: ${iteration} | 任务数: ${sections.length} | 端: ${allPlatforms.join(', ')}\n\n`;
|
|
3378
3539
|
p += `## 说明\n\n`;
|
|
3379
|
-
p += `CLI
|
|
3540
|
+
p += `CLI 已按功能单元×端创建了任务目录结构,并为每个功能单元从分析文档中提取了相关上下文。\n`;
|
|
3380
3541
|
p += `你的任务是为每个子任务填充 REQ.md 和 TECH.md。\n\n`;
|
|
3381
|
-
p += `##
|
|
3542
|
+
p += `## 全局上下文\n\n`;
|
|
3382
3543
|
p += `1. Read .speccore/CONSTITUTION.md — 项目配置\n`;
|
|
3383
|
-
p += `2. Read 020-specs/overview/
|
|
3384
|
-
p +=
|
|
3385
|
-
p += `4. Read 020-specs/overview/TECH.md — 迭代综合技术架构\n`;
|
|
3386
|
-
p += `5. Read 020-specs/{端}/TECH.md — 各端专属技术方案\n\n`;
|
|
3387
|
-
p += `## 任务清单\n\n`;
|
|
3544
|
+
p += `2. Read 020-specs/overview/ANALYSIS.md — 迭代综合分析报告\n\n`;
|
|
3545
|
+
p += `## 任务清单(含精准上下文)\n\n`;
|
|
3388
3546
|
for (const sec of sections) {
|
|
3389
3547
|
const taskId = sec._taskId || sec.name;
|
|
3390
3548
|
const sourceFile = sec._sourceFile || '';
|
|
@@ -3401,15 +3559,20 @@ function buildContentFillingPrompt(iteration, iterationDir, sections, allPlatfor
|
|
|
3401
3559
|
p += ` - ${platform}/*/REQ.md — 子任务需求规格\n`;
|
|
3402
3560
|
p += ` - ${platform}/*/TECH.md — 子任务技术方案\n`;
|
|
3403
3561
|
}
|
|
3562
|
+
// v7.4.2+: 注入该功能单元的精准上下文
|
|
3563
|
+
const unitCtx = await assembleUnitContext(iterationDir, featureName, modPlatforms);
|
|
3564
|
+
p += `\n#### 📎 功能单元「${featureName}」相关上下文\n\n${unitCtx}\n\n`;
|
|
3404
3565
|
p += `\n`;
|
|
3405
3566
|
}
|
|
3406
3567
|
p += `## 填充规则\n\n`;
|
|
3407
3568
|
p += `1. 先 Read 子任务目录下的 TASK.md(已有基本信息)和 .meta/feature(功能单元名)\n`;
|
|
3408
|
-
p += `2. REQ.md:
|
|
3409
|
-
p += `3. TECH.md:
|
|
3569
|
+
p += `2. REQ.md: 基于上方提供的「功能单元相关上下文」,撰写本子任务的需求规格(验收标准、业务规则、边界条件)\n`;
|
|
3570
|
+
p += `3. TECH.md: 基于上下文中的接口定义和数据模型,细化本子任务的技术方案\n`;
|
|
3410
3571
|
p += `4. 用 Write 工具直接写入对应路径\n`;
|
|
3411
|
-
p += `5. 同一功能模块的各端子任务要保持 API
|
|
3412
|
-
p += `6. 禁止产出垃圾内容——每个文件必须有实质性专业内容\n
|
|
3572
|
+
p += `5. 同一功能模块的各端子任务要保持 API 契约一致(前后端接口签名必须匹配)\n`;
|
|
3573
|
+
p += `6. 禁止产出垃圾内容——每个文件必须有实质性专业内容\n`;
|
|
3574
|
+
p += `7. REQ.md 必须包含:功能描述、验收标准(Given/When/Then)、业务规则、边界条件、异常场景\n`;
|
|
3575
|
+
p += `8. TECH.md 必须包含:接口定义(请求/响应结构体)、数据模型(字段/类型/约束)、核心算法/逻辑、依赖说明\n\n`;
|
|
3413
3576
|
p += `## ⚠️ 绝对禁止\n\n`;
|
|
3414
3577
|
p += `- 不要创建新目录 — 目录已由 CLI 创建\n`;
|
|
3415
3578
|
p += `- 不要修改 .meta/ 下的文件\n`;
|