speccore 7.4.6 → 7.5.1

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.
@@ -2500,12 +2500,13 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
2500
2500
  if (iterationDir) {
2501
2501
  try {
2502
2502
  const funcMapPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'FUNCTION_MAP.md');
2503
+ let funcUnitsInjected = false;
2503
2504
  if (await (0, fs_extra_1.pathExists)(funcMapPath)) {
2504
2505
  const funcMapContent = await (0, fs_extra_1.readFile)(funcMapPath, 'utf-8');
2505
2506
  const funcUnits = parseFunctionMap(funcMapContent, standardPlatforms);
2506
2507
  if (funcUnits.length > 0) {
2507
- p += `## 🧩 功能单元上下文(CLI 从分析文档精准提取)\n\n`;
2508
- p += `> 以下是从 FUNCTION_MAP.md / REQUIREMENT.md / _INDEX.md / _MODULES.md 中为每个功能单元提取的相关上下文。\n`;
2508
+ p += `## 🧩 功能单元上下文(CLI 从 FUNCTION_MAP.md 精准提取)\n\n`;
2509
+ p += `> 以下是从 FUNCTION_MAP.md 中为每个功能单元提取的相关上下文。\n`;
2509
2510
  p += `> 拆分时必须基于这些功能单元,每个功能单元对应 1~3 个任务。\n\n`;
2510
2511
  for (const unit of funcUnits) {
2511
2512
  const unitCtx = await assembleUnitContext(iterationDir, unit.name, unit.platforms);
@@ -2519,6 +2520,30 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
2519
2520
  p += `- 说明: ${unit.description}\n`;
2520
2521
  p += `\n${unitCtx}\n\n---\n\n`;
2521
2522
  }
2523
+ funcUnitsInjected = true;
2524
+ }
2525
+ }
2526
+ // v7.5.0+: FUNCTION_MAP.md 不存在时,从 ANALYSIS.md 提取功能单元清单
2527
+ if (!funcUnitsInjected) {
2528
+ const analysisPath = (0, path_1.join)(iterationDir, '020-specs', 'ANALYSIS.md');
2529
+ if (await (0, fs_extra_1.pathExists)(analysisPath)) {
2530
+ const analysisContent = await (0, fs_extra_1.readFile)(analysisPath, 'utf-8');
2531
+ const analysisUnits = parseAnalysisFunctionalUnits(analysisContent, standardPlatforms);
2532
+ if (analysisUnits.length > 0) {
2533
+ p += `## 🧩 功能单元清单(CLI 从 ANALYSIS.md 提取,v7.5.0+)\n\n`;
2534
+ p += `> 以下是从 ANALYSIS.md 功能点清单表中提取的 **全部功能单元**(F-xx 编号)。\n`;
2535
+ p += `> ⚠️ **你必须为每个功能单元创建至少 1 个任务,不允许遗漏任何功能单元。**\n`;
2536
+ p += `> 可以按合并规则将相关功能单元合并为同一个任务,但必须确保所有功能单元都被覆盖。\n\n`;
2537
+ p += `| 编号 | 功能单元 | 涉及端 | 核心功能 |\n`;
2538
+ p += `|:---|:---|:---|:---|\n`;
2539
+ for (const u of analysisUnits) {
2540
+ p += `| ${u.id} | ${u.name} | ${u.platforms.join(', ')} | ${u.description || '-'} |\n`;
2541
+ }
2542
+ p += `\n**拆分要求**:\n`;
2543
+ p += `- 每个功能单元必须在某个任务的 \`functionalUnit\` 字段中被引用\n`;
2544
+ p += `- 合并时,在任务描述中列出所有合并的功能单元编号(如 F-02, F-03)\n`;
2545
+ p += `- 不允许将不同端的功能单元合并到同一个任务\n\n`;
2546
+ }
2522
2547
  }
2523
2548
  }
2524
2549
  }
@@ -3185,6 +3210,118 @@ function parseFunctionMapTree(content, allPlatforms) {
3185
3210
  }
3186
3211
  return units;
3187
3212
  }
3213
+ /**
3214
+ * v7.5.0+: 从 ANALYSIS.md 提取功能单元清单
3215
+ * 支持两种格式:
3216
+ * A) 按端分节: ### X.X 端名(platform-name)— N 个页面 + | F-01 | 功能名 | ...
3217
+ * B) 按优先级分节: ### P0 — 必修 + 表格含「涉及端」列(v7.5.1+ 新增)
3218
+ */
3219
+ function parseAnalysisFunctionalUnits(content, allPlatforms) {
3220
+ const units = [];
3221
+ const lines = content.split('\n');
3222
+ let currentPlatforms = [];
3223
+ let platformColIdx = -1; // 「涉及端」列索引,-1 表示未检测到
3224
+ for (let i = 0; i < lines.length; i++) {
3225
+ const line = lines[i];
3226
+ // 检测 ### 标题行,提取当前端名
3227
+ if (line.startsWith('### ')) {
3228
+ const prevPlatforms = [...currentPlatforms]; // 保留上一轮端信息
3229
+ currentPlatforms = [];
3230
+ // 匹配括号内的端名: ### 1.1 后台管理端(admin-web)
3231
+ const parenMatch = line.match(/[((]([a-zA-Z][\w-]*)[))]/);
3232
+ if (parenMatch) {
3233
+ const name = parenMatch[1];
3234
+ const matched = allPlatforms.filter(p => p === name || p.includes(name) || name.includes(p));
3235
+ if (matched.length > 0)
3236
+ currentPlatforms.push(...matched);
3237
+ }
3238
+ // 回退:从标题文本匹配端名
3239
+ if (currentPlatforms.length === 0) {
3240
+ for (const p of allPlatforms) {
3241
+ if (line.includes(p)) {
3242
+ currentPlatforms.push(p);
3243
+ }
3244
+ }
3245
+ }
3246
+ // 后端服务检测
3247
+ if (currentPlatforms.length === 0 && /后端|服务|backend|service/i.test(line)) {
3248
+ currentPlatforms.push(...allPlatforms.filter(p => /service|server|api|backend/i.test(p)));
3249
+ }
3250
+ // v7.5.1: 标题未匹配到端时,保留上一轮的 currentPlatforms(优先级分节场景)
3251
+ if (currentPlatforms.length === 0) {
3252
+ currentPlatforms = prevPlatforms;
3253
+ }
3254
+ // 遇到新章节,重置列索引(下一张表可能结构不同)
3255
+ platformColIdx = -1;
3256
+ continue;
3257
+ }
3258
+ // 解析表格行
3259
+ if (!line.startsWith('|'))
3260
+ continue;
3261
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
3262
+ if (cells.length < 2)
3263
+ continue;
3264
+ // 分隔行跳过
3265
+ if (line.match(/^\|\s*[-:]/))
3266
+ continue;
3267
+ // 检测表头行:查找「涉及端」/「端」列
3268
+ if (platformColIdx === -1) {
3269
+ const idx = cells.findIndex(c => c === '涉及端' || c === '端' || c.includes('涉及端') || c.includes('涉及工程'));
3270
+ if (idx >= 0) {
3271
+ platformColIdx = idx;
3272
+ continue; // 表头行本身不是数据
3273
+ }
3274
+ }
3275
+ // 检测 F-xx 编号行(支持 | F-01 | 或 | # | 格式)
3276
+ const idMatch = cells[0]?.match(/^F-(\d+)$/i);
3277
+ if (!idMatch)
3278
+ continue;
3279
+ const id = `F-${idMatch[1]}`;
3280
+ const name = cells[1]?.replace(/[*_`]/g, '').trim();
3281
+ if (!name || name.length < 2)
3282
+ continue;
3283
+ // 提取描述(核心功能列,通常是第3或第4列)
3284
+ const desc = cells.length >= 4 ? cells[3]?.trim() || '' : '';
3285
+ // v7.5.1+: 优先从「涉及端」列提取平台(格式 B)
3286
+ let rowPlatforms = [];
3287
+ if (platformColIdx >= 0 && cells[platformColIdx]) {
3288
+ const rawPlatform = cells[platformColIdx];
3289
+ // 按逗号、+、顿号、空格分割
3290
+ const parts = rawPlatform.split(/[,+、;\s]+/).map(s => s.trim()).filter(Boolean);
3291
+ for (const part of parts) {
3292
+ // 精确匹配
3293
+ const exact = allPlatforms.find(p => p === part);
3294
+ if (exact) {
3295
+ rowPlatforms.push(exact);
3296
+ continue;
3297
+ }
3298
+ // 模糊匹配(部分名包含)
3299
+ const fuzzy = allPlatforms.filter(p => p.includes(part) || part.includes(p));
3300
+ if (fuzzy.length > 0) {
3301
+ rowPlatforms.push(...fuzzy);
3302
+ continue;
3303
+ }
3304
+ // 中文端名映射(如 "后台管理端" → admin-web)
3305
+ // 尝试从 CONSTITUTION 的「对应需求端」列匹配(此处简化:跳过中文值)
3306
+ }
3307
+ // 去重
3308
+ rowPlatforms = [...new Set(rowPlatforms)];
3309
+ }
3310
+ // 回退优先级:涉及端列 → 章节标题 → 全部端
3311
+ const platforms = rowPlatforms.length > 0
3312
+ ? rowPlatforms
3313
+ : (currentPlatforms.length > 0 ? [...currentPlatforms] : [...allPlatforms]);
3314
+ units.push({ name, platforms, description: desc, id });
3315
+ }
3316
+ // 去重(按名称)
3317
+ const seen = new Set();
3318
+ return units.filter(u => {
3319
+ if (seen.has(u.name))
3320
+ return false;
3321
+ seen.add(u.name);
3322
+ return true;
3323
+ });
3324
+ }
3188
3325
  /**
3189
3326
  * 尝试模块驱动拆分:从功能模块创建任务目录结构
3190
3327
  * 成功返回 true,无功能模块时返回 false(回退到传统流程)
@@ -3246,6 +3383,31 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
3246
3383
  }
3247
3384
  catch { }
3248
3385
  }
3386
+ // 2.5 v7.5.0+: 回退:从 ANALYSIS.md 提取功能单元清单(按端分节的 F-xx 表格)
3387
+ if (!modulePlatformsParsed) {
3388
+ const analysisPath = (0, path_1.join)(iterationDir, '020-specs', 'ANALYSIS.md');
3389
+ if (await (0, fs_extra_1.pathExists)(analysisPath)) {
3390
+ try {
3391
+ const content = await (0, fs_extra_1.readFile)(analysisPath, 'utf-8');
3392
+ const parsed = parseAnalysisFunctionalUnits(content, allPlatforms);
3393
+ if (parsed.length > 0) {
3394
+ for (const u of parsed) {
3395
+ modules.push({
3396
+ name: u.name,
3397
+ slug: slugify(u.name),
3398
+ type: 'feature',
3399
+ sourceFile: 'ANALYSIS.md',
3400
+ platforms: u.platforms,
3401
+ description: u.description,
3402
+ });
3403
+ }
3404
+ modulePlatformsParsed = true;
3405
+ logger_1.logger.info(` 📋 从 ANALYSIS.md 提取到 ${parsed.length} 个功能单元(F-xx 编号表)`);
3406
+ }
3407
+ }
3408
+ catch { }
3409
+ }
3410
+ }
3249
3411
  // 3. 回退:读取 features/*/README.md(无涉及端信息,使用全端)
3250
3412
  if (!modulePlatformsParsed) {
3251
3413
  const featuresDir = (0, path_1.join)(reqDir, 'features');