speccore 7.4.0 → 7.4.2
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/cli.js +0 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +0 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +16 -14
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.d.ts +0 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +171 -59
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +1 -4
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +0 -4
- package/dist/core/intent-recognition.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,6 @@ export interface IterationSplitOptions {
|
|
|
16
16
|
prune?: boolean;
|
|
17
17
|
modules?: string;
|
|
18
18
|
ignoreSpecsUpdate?: boolean;
|
|
19
|
-
devGuide?: boolean;
|
|
20
19
|
}
|
|
21
20
|
export declare function iterationSplitCommand(options: IterationSplitOptions): Promise<void>;
|
|
22
21
|
//# sourceMappingURL=split.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAmIA,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAuBD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+xBzF"}
|
|
@@ -68,7 +68,7 @@ function normalizeScopePlatforms(scopeArr, standardPlatforms) {
|
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
70
|
// 2. 后端类简写 → 匹配第一个后端端名
|
|
71
|
-
if (/后端|backend|服务端|server/i.test(s)) {
|
|
71
|
+
if (/后端|backend|服务端|server|api/i.test(s)) {
|
|
72
72
|
const backendPlatform = standardPlatforms.find(p => /-(service|api|server|backend)$/i.test(p) || p.startsWith('后台'));
|
|
73
73
|
if (backendPlatform && !used.has(backendPlatform)) {
|
|
74
74
|
result.push(backendPlatform);
|
|
@@ -95,6 +95,7 @@ function normalizeScopePlatforms(scopeArr, standardPlatforms) {
|
|
|
95
95
|
'小程序': ['miniapp', 'mini-app', '小程序'],
|
|
96
96
|
'admin': ['admin-web', 'admin', '后台管理'],
|
|
97
97
|
'web': ['admin-web', 'web', 'h5-mobile'],
|
|
98
|
+
'frontend': ['admin-web', 'h5-mobile', 'frontend'],
|
|
98
99
|
'app': ['app', 'android', 'ios'],
|
|
99
100
|
'安卓': ['android'],
|
|
100
101
|
'ios': ['ios'],
|
|
@@ -108,11 +109,9 @@ function normalizeScopePlatforms(scopeArr, standardPlatforms) {
|
|
|
108
109
|
continue;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
|
-
// 5.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
used.add(s);
|
|
115
|
-
}
|
|
112
|
+
// 5. v7.4.2+: 无法匹配时警告并跳过,不保留非标准端名
|
|
113
|
+
logger_1.logger.warn(` ⚠️ 非标准端名 "${s}",无法映射到 CONSTITUTION.md 标准端名 ${standardPlatforms.join('/')}`);
|
|
114
|
+
logger_1.logger.warn(` 已跳过该端,请检查 AI 输出的 scope 字段`);
|
|
116
115
|
}
|
|
117
116
|
return result.length > 0 ? result : standardPlatforms;
|
|
118
117
|
}
|
|
@@ -703,7 +702,7 @@ async function iterationSplitCommand(options) {
|
|
|
703
702
|
// v6.76.0+: 传入过滤条件,限制拆分范围;传入已有 Task 结构,支持增量拆分
|
|
704
703
|
const modulesFilter = options.modules ? options.modules.split(',').map(m => m.trim()).filter(Boolean) : undefined;
|
|
705
704
|
const platformsFilter = options.platforms ? options.platforms.split(',').map(p => p.trim()).filter(Boolean) : undefined;
|
|
706
|
-
let splitPrompt = buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, allPlatforms, modulesFilter, platformsFilter, existingTaskStructure
|
|
705
|
+
let splitPrompt = buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, allPlatforms, modulesFilter, platformsFilter, existingTaskStructure);
|
|
707
706
|
// 注入全局上下文(INDEX + TOC 目录,AI 自主读取)
|
|
708
707
|
const { loadGlobalContext, formatGlobalContext } = await Promise.resolve().then(() => __importStar(require('../../core/prompt-builder')));
|
|
709
708
|
const globalCtx = await loadGlobalContext(process.cwd(), 'split');
|
|
@@ -2446,7 +2445,7 @@ ${isH5 ? '移动端优先,适配 375/414/768' :
|
|
|
2446
2445
|
/**
|
|
2447
2446
|
* 构建完整的 AI 智能拆分 Prompt(含 SpecCore 理念 + 粒度规则 + 完整上下文)
|
|
2448
2447
|
*/
|
|
2449
|
-
function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, standardPlatforms, modulesFilter, platformsFilter, existingTasks
|
|
2448
|
+
function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, standardPlatforms, modulesFilter, platformsFilter, existingTasks) {
|
|
2450
2449
|
let p = `# SpecCore AI 智能拆分\n\n`;
|
|
2451
2450
|
p += `> 迭代: ${iteration} | 生成: ${new Date().toISOString().split('T')[0]}\n\n`;
|
|
2452
2451
|
// v6.76.0+: 拆分范围限制
|
|
@@ -2490,21 +2489,12 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
|
|
|
2490
2489
|
}
|
|
2491
2490
|
// v6.69.3+: 注入标准端名列表
|
|
2492
2491
|
if (standardPlatforms.length > 0) {
|
|
2493
|
-
p += `## 🖥️
|
|
2492
|
+
p += `## 🖥️ 项目端列表(标准端名 — 权威来源)\n\n`;
|
|
2494
2493
|
p += `本项目已配置以下端,所有 scope 必须使用这些**标准端名**,禁止使用简写或中文:\n\n`;
|
|
2495
2494
|
p += `\`${standardPlatforms.join('`, `')}\`\n\n`;
|
|
2496
|
-
p += `-
|
|
2497
|
-
p += `-
|
|
2498
|
-
p += `-
|
|
2499
|
-
}
|
|
2500
|
-
// v6.69.3+: 注入标准端名列表
|
|
2501
|
-
if (standardPlatforms.length > 0) {
|
|
2502
|
-
p += `## 🖥️ 项目端列表(标准端名)\n\n`;
|
|
2503
|
-
p += `本项目已配置以下端,所有 scope 必须使用这些**标准端名**,禁止使用简写或中文:\n\n`;
|
|
2504
|
-
p += `\`${standardPlatforms.join('`, `')}\`\n\n`;
|
|
2505
|
-
p += `- 后端端名示例: \`booking-service\`, \`room-service\`(不是 "后端"、"backend")\n`;
|
|
2506
|
-
p += `- 前端端名示例: \`admin-web\`, \`h5-mobile\`(不是 "web"、"admin"、"h5")\n`;
|
|
2507
|
-
p += `- **scope 数组必须使用上述标准端名**,否则会导致目录结构错误\n\n`;
|
|
2495
|
+
p += `- ⛔ **禁止使用以下非标准端名**: \`api\`、\`web\`、\`backend\`、\`frontend\`、\`admin\`、\`h5\`、\"后端\"、\"前端\"\n`;
|
|
2496
|
+
p += `- ✅ **必须使用 CONSTITUTION.md 中的标准端名**作为子任务目录名\n`;
|
|
2497
|
+
p += `- scope 数组和子任务目录名必须完全匹配上述标准端名\n\n`;
|
|
2508
2498
|
}
|
|
2509
2499
|
// SpecCore 拆分原则
|
|
2510
2500
|
p += `## ⚙️ SpecCore 拆分原则\n\n`;
|
|
@@ -2592,7 +2582,8 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
|
|
|
2592
2582
|
p += ` "owner": "建议负责人",\n`;
|
|
2593
2583
|
p += ` "sourceFile": "来源文档路径(如 bugs/login-timeout.md、features/user-auth.md)",\n`;
|
|
2594
2584
|
p += ` "reqContent": "需求描述内容(Markdown 格式,写入 REQ.md)",\n`;
|
|
2595
|
-
p += ` "techContent": "技术方案内容(Markdown 格式,写入 TECH.md)"
|
|
2585
|
+
p += ` "techContent": "技术方案内容(Markdown 格式,写入 TECH.md)",\n`;
|
|
2586
|
+
p += ` "devGuideContent": "开发者实现指南(Markdown 格式,写入 DEV_GUIDE.md):实现步骤、关键代码示例、与存量功能集成、注意事项"\n`;
|
|
2596
2587
|
p += ` }\n]\n`;
|
|
2597
2588
|
p += '```\n\n';
|
|
2598
2589
|
p += `> **functionalUnit 必须填写**:根据任务类型语义不同:\n`;
|
|
@@ -2617,14 +2608,19 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
|
|
|
2617
2608
|
p += `> - 直接写入 00-specs/TECH.md,执行时 AI 据此直接开发\n`;
|
|
2618
2609
|
p += `> **质量红线**:如果 reqContent/techContent 只有标题和占位符(如 "<!-- AI-FILL -->"),视为不合格,必须重新生成\n\n`;
|
|
2619
2610
|
p += `### ⚠️ 拆分规则(重要)\n\n`;
|
|
2611
|
+
p += `- **按功能单元拆分,不按技术层拆分**:每个任务必须对应一个业务功能(如「预订管理」「签到」「审批」),不要按技术层分组(如「后端改进」「前端修复」)\n`;
|
|
2612
|
+
p += `- ❌ 错误示例:Task-129-backend-improve、Task-130-admin-fix(按技术层分组)\n`;
|
|
2613
|
+
p += `- ✅ 正确示例:Task-129-booking-crud、Task-130-checkin-flow、Task-131-approval-management(按功能单元分组)\n`;
|
|
2614
|
+
p += `- **topic 必须反映功能单元**:每个任务的 topic(目录名后缀)必须唯一且反映业务功能,禁止所有任务都用同一个后缀(如 -impl)\n`;
|
|
2615
|
+
p += `- **scope 必须使用标准端名**:禁止使用 api/web/backend/frontend 等非标准名称\n`;
|
|
2620
2616
|
p += `- **一个功能单元默认 1 个任务**,最多拆成 3 个\n`;
|
|
2621
|
-
p += `- **按 scope 涉及的端创建子任务**:每个端 1 个子任务目录\n`;
|
|
2622
|
-
p += `- **不要按端拆分任务**:同一功能的前后端各端工作在一个任务里,按端拆分子任务\n`;
|
|
2623
|
-
p += `- **用户不满意时可手动拆分**:CLI 提供简单的默认拆分,复杂拆分由用户手动调整\n\n`;
|
|
2617
|
+
p += `- **按 scope 涉及的端创建子任务**:每个端 1 个子任务目录\n\n`;
|
|
2624
2618
|
// 质量自检
|
|
2625
2619
|
p += `## ✅ 质量自检(必须全部通过)\n\n`;
|
|
2626
2620
|
p += `□ 每个任务都满足原子任务定义?\n`;
|
|
2627
|
-
p += `□
|
|
2621
|
+
p += `□ **每个任务按功能单元命名,不是按技术层**?(禁止 backend-improve/admin-fix 这类名称)\n`;
|
|
2622
|
+
p += `□ **每个 topic 唯一且反映业务功能**?(禁止全部用 -impl)\n`;
|
|
2623
|
+
p += `□ **scope 只包含 CONSTITUTION.md 标准端名**?(禁止 api/web/backend/frontend)\n`;
|
|
2628
2624
|
p += `□ 没有循环依赖?\n`;
|
|
2629
2625
|
p += `□ 基础模块排在前面?\n`;
|
|
2630
2626
|
p += `□ 同功能单元内的任务没被过度拆分?(每个功能单元 ≤ 3 个任务)\n`;
|
|
@@ -2911,38 +2907,46 @@ speccore analyze --task ${taskId}${iterFlag}
|
|
|
2911
2907
|
// ── v6.49.14+: 模块驱动拆分 — 从 overview/REQUIREMENT.md 读取涉及端 ──
|
|
2912
2908
|
/**
|
|
2913
2909
|
* 从 overview/REQUIREMENT.md 解析功能模块清单的「涉及端」列
|
|
2910
|
+
* 支持两种格式:
|
|
2911
|
+
* 1. 单表格含「涉及端」列(功能模块 | 涉及端 | ...)
|
|
2912
|
+
* 2. 按端分节(### X.X ... — {platform} 后跟表格)
|
|
2914
2913
|
* 返回模块列表及其涉及的标准端名
|
|
2915
2914
|
*/
|
|
2916
2915
|
function parseModulePlatforms(content, allPlatforms) {
|
|
2916
|
+
// v7.4.2+: 先尝试原始格式(单表格含「涉及端」列)
|
|
2917
|
+
const tableResult = parseModulePlatformsTable(content, allPlatforms);
|
|
2918
|
+
if (tableResult.length > 0)
|
|
2919
|
+
return tableResult;
|
|
2920
|
+
// v7.4.2+: 回退到按端分节格式
|
|
2921
|
+
return parseModulePlatformsBySection(content, allPlatforms);
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* v7.4.2+: 解析单表格含「涉及端」列的格式
|
|
2925
|
+
*/
|
|
2926
|
+
function parseModulePlatformsTable(content, allPlatforms) {
|
|
2917
2927
|
const modules = [];
|
|
2918
2928
|
const lines = content.split('\n');
|
|
2919
2929
|
let inFeatureTable = false;
|
|
2920
2930
|
let platformColIdx = -1;
|
|
2921
2931
|
for (const line of lines) {
|
|
2922
|
-
// 检测功能模块清单表格开始
|
|
2923
2932
|
if (line.includes('功能模块清单')) {
|
|
2924
2933
|
inFeatureTable = true;
|
|
2925
2934
|
continue;
|
|
2926
2935
|
}
|
|
2927
2936
|
if (!inFeatureTable)
|
|
2928
2937
|
continue;
|
|
2929
|
-
|
|
2930
|
-
if (line.startsWith('## ') && !line.includes('功能模块清单')) {
|
|
2938
|
+
if (line.startsWith('## ') && !line.includes('功能模块清单'))
|
|
2931
2939
|
break;
|
|
2932
|
-
}
|
|
2933
2940
|
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
2934
2941
|
if (cells.length < 2)
|
|
2935
2942
|
continue;
|
|
2936
|
-
// 表头行 → 找到「涉及端」列索引
|
|
2937
2943
|
if (cells.some(c => c.includes('涉及端'))) {
|
|
2938
2944
|
platformColIdx = cells.findIndex(c => c.includes('涉及端'));
|
|
2939
2945
|
continue;
|
|
2940
2946
|
}
|
|
2941
|
-
// 分隔行跳过
|
|
2942
2947
|
if (cells.every(c => /^[-:]+$/.test(c)))
|
|
2943
2948
|
continue;
|
|
2944
|
-
|
|
2945
|
-
const moduleName = cells[1]; // 第2列通常是模块名
|
|
2949
|
+
const moduleName = cells[1];
|
|
2946
2950
|
if (!moduleName || moduleName === '#' || moduleName === '模块')
|
|
2947
2951
|
continue;
|
|
2948
2952
|
if (platformColIdx >= 0 && platformColIdx < cells.length) {
|
|
@@ -2957,16 +2961,93 @@ function parseModulePlatforms(content, allPlatforms) {
|
|
|
2957
2961
|
}
|
|
2958
2962
|
}
|
|
2959
2963
|
}
|
|
2960
|
-
// 涉及端为空或无法解析 → 回退全端
|
|
2961
2964
|
modules.push({ name: moduleName, platforms: [...allPlatforms] });
|
|
2962
2965
|
}
|
|
2963
2966
|
return modules;
|
|
2964
2967
|
}
|
|
2968
|
+
/**
|
|
2969
|
+
* v7.4.2+: 解析按端分节的 REQUIREMENT.md 格式
|
|
2970
|
+
* 匹配: ### 2.1 后端 — booking-service 或 ### 2.1 前端 — admin-web
|
|
2971
|
+
* 从标题提取端名,从后续表格提取模块名,建立模块→端的映射
|
|
2972
|
+
*/
|
|
2973
|
+
function parseModulePlatformsBySection(content, allPlatforms) {
|
|
2974
|
+
const lines = content.split('\n');
|
|
2975
|
+
// 模块名 → 涉及端列表 的映射
|
|
2976
|
+
const modulePlatformMap = new Map();
|
|
2977
|
+
let currentPlatform = '';
|
|
2978
|
+
let inTable = false;
|
|
2979
|
+
for (const line of lines) {
|
|
2980
|
+
// 检测按端分节标题: ### 2.1 后端 — booking-service
|
|
2981
|
+
const sectionMatch = line.match(/^###\s+[\d.]+\s+.*[—\-–]\s*(.+)$/);
|
|
2982
|
+
if (sectionMatch) {
|
|
2983
|
+
const rawPlatform = sectionMatch[1].trim();
|
|
2984
|
+
// 匹配标准端名
|
|
2985
|
+
currentPlatform = allPlatforms.find(p => rawPlatform.includes(p)) || '';
|
|
2986
|
+
inTable = false;
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
2989
|
+
// 非当前端标题下的 ## 标题 → 重置
|
|
2990
|
+
if (line.startsWith('## ') && !line.startsWith('### ')) {
|
|
2991
|
+
currentPlatform = '';
|
|
2992
|
+
inTable = false;
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
if (!currentPlatform)
|
|
2996
|
+
continue;
|
|
2997
|
+
// 检测表格开始(表头行含「功能模块」)
|
|
2998
|
+
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
2999
|
+
if (cells.length >= 2 && cells.some(c => c.includes('功能模块'))) {
|
|
3000
|
+
inTable = true;
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (!inTable)
|
|
3004
|
+
continue;
|
|
3005
|
+
// 分隔行跳过
|
|
3006
|
+
if (cells.every(c => /^[-:]+$/.test(c)))
|
|
3007
|
+
continue;
|
|
3008
|
+
// 表头行跳过
|
|
3009
|
+
if (cells.some(c => c === '功能模块' || c === '模块'))
|
|
3010
|
+
continue;
|
|
3011
|
+
// 数据行:第1列或第2列是模块名
|
|
3012
|
+
const moduleName = cells[0] && cells[0] !== '功能模块' ? cells[0] : (cells[1] || '');
|
|
3013
|
+
if (!moduleName || moduleName.length < 2)
|
|
3014
|
+
continue;
|
|
3015
|
+
// 跳过状态标记行
|
|
3016
|
+
if (moduleName.startsWith('#'))
|
|
3017
|
+
continue;
|
|
3018
|
+
// 添加到映射
|
|
3019
|
+
const existing = modulePlatformMap.get(moduleName) || [];
|
|
3020
|
+
if (!existing.includes(currentPlatform)) {
|
|
3021
|
+
existing.push(currentPlatform);
|
|
3022
|
+
}
|
|
3023
|
+
modulePlatformMap.set(moduleName, existing);
|
|
3024
|
+
}
|
|
3025
|
+
// 转换为结果数组
|
|
3026
|
+
const modules = [];
|
|
3027
|
+
for (const [name, platforms] of modulePlatformMap) {
|
|
3028
|
+
modules.push({ name, platforms: platforms.length > 0 ? platforms : [...allPlatforms] });
|
|
3029
|
+
}
|
|
3030
|
+
return modules;
|
|
3031
|
+
}
|
|
2965
3032
|
/**
|
|
2966
3033
|
* v6.70.0+: 解析 FUNCTION_MAP.md 跨端功能映射表
|
|
3034
|
+
* 支持两种格式:
|
|
3035
|
+
* 1. Markdown 表格(功能单元 | 涉及端 | ...)
|
|
3036
|
+
* 2. 树形格式(├── 模块名 ... platform1 ✅ | platform2 ✅)
|
|
2967
3037
|
* 返回功能单元列表,含涉及端、共享能力、依赖关系
|
|
2968
3038
|
*/
|
|
2969
3039
|
function parseFunctionMap(content, allPlatforms) {
|
|
3040
|
+
// v7.4.2+: 先尝试表格格式
|
|
3041
|
+
const tableResult = parseFunctionMapTable(content, allPlatforms);
|
|
3042
|
+
if (tableResult.length > 0)
|
|
3043
|
+
return tableResult;
|
|
3044
|
+
// v7.4.2+: 回退到树形格式解析
|
|
3045
|
+
return parseFunctionMapTree(content, allPlatforms);
|
|
3046
|
+
}
|
|
3047
|
+
/**
|
|
3048
|
+
* v7.4.2+: 解析 FUNCTION_MAP.md 表格格式
|
|
3049
|
+
*/
|
|
3050
|
+
function parseFunctionMapTable(content, allPlatforms) {
|
|
2970
3051
|
const units = [];
|
|
2971
3052
|
const lines = content.split('\n');
|
|
2972
3053
|
let inTable = false;
|
|
@@ -2975,10 +3056,8 @@ function parseFunctionMap(content, allPlatforms) {
|
|
|
2975
3056
|
let dependsOnColIdx = -1;
|
|
2976
3057
|
let descColIdx = -1;
|
|
2977
3058
|
for (const line of lines) {
|
|
2978
|
-
// 检测映射表开始(通过表头特征)
|
|
2979
3059
|
if (line.includes('功能单元') && line.includes('涉及端')) {
|
|
2980
3060
|
inTable = true;
|
|
2981
|
-
// 解析表头,找到各列索引
|
|
2982
3061
|
const headerCells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
2983
3062
|
platformColIdx = headerCells.findIndex(c => c.includes('涉及端'));
|
|
2984
3063
|
sharedCapColIdx = headerCells.findIndex(c => c.includes('共享能力'));
|
|
@@ -2988,47 +3067,33 @@ function parseFunctionMap(content, allPlatforms) {
|
|
|
2988
3067
|
}
|
|
2989
3068
|
if (!inTable)
|
|
2990
3069
|
continue;
|
|
2991
|
-
|
|
2992
|
-
if (line.startsWith('## ') && !line.includes('功能映射')) {
|
|
3070
|
+
if (line.startsWith('## ') && !line.includes('功能映射'))
|
|
2993
3071
|
break;
|
|
2994
|
-
}
|
|
2995
3072
|
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
2996
3073
|
if (cells.length < 3)
|
|
2997
3074
|
continue;
|
|
2998
|
-
// 分隔行跳过
|
|
2999
3075
|
if (cells.every(c => /^[-:]+$/.test(c)))
|
|
3000
3076
|
continue;
|
|
3001
|
-
|
|
3002
|
-
if (cells.some(c => c.includes('功能单元')) || cells.some(c => c.includes('涉及端'))) {
|
|
3077
|
+
if (cells.some(c => c.includes('功能单元')) || cells.some(c => c.includes('涉及端')))
|
|
3003
3078
|
continue;
|
|
3004
|
-
}
|
|
3005
|
-
// 数据行:第2列是功能单元名
|
|
3006
3079
|
const unitName = cells[1];
|
|
3007
3080
|
if (!unitName || unitName === '#' || unitName === '功能单元')
|
|
3008
3081
|
continue;
|
|
3009
|
-
// 解析涉及端
|
|
3010
3082
|
let platforms = [];
|
|
3011
3083
|
if (platformColIdx >= 0 && platformColIdx < cells.length) {
|
|
3012
3084
|
const raw = cells[platformColIdx];
|
|
3013
3085
|
if (raw && raw !== '无' && raw !== '—' && raw !== '-') {
|
|
3014
|
-
platforms = raw.split(/[,,]/)
|
|
3015
|
-
.map(p => p.trim())
|
|
3016
|
-
.filter(p => p && allPlatforms.includes(p));
|
|
3086
|
+
platforms = raw.split(/[,,]/).map(p => p.trim()).filter(p => p && allPlatforms.includes(p));
|
|
3017
3087
|
}
|
|
3018
3088
|
}
|
|
3019
|
-
|
|
3020
|
-
if (platforms.length === 0) {
|
|
3089
|
+
if (platforms.length === 0)
|
|
3021
3090
|
platforms = [...allPlatforms];
|
|
3022
|
-
}
|
|
3023
|
-
// 解析共享能力
|
|
3024
3091
|
let sharedCapability = '无';
|
|
3025
3092
|
if (sharedCapColIdx >= 0 && sharedCapColIdx < cells.length) {
|
|
3026
3093
|
const raw = cells[sharedCapColIdx];
|
|
3027
|
-
if (raw && raw !== '无' && raw !== '—' && raw !== '-')
|
|
3094
|
+
if (raw && raw !== '无' && raw !== '—' && raw !== '-')
|
|
3028
3095
|
sharedCapability = raw;
|
|
3029
|
-
}
|
|
3030
3096
|
}
|
|
3031
|
-
// 解析依赖任务
|
|
3032
3097
|
let dependsOn = [];
|
|
3033
3098
|
if (dependsOnColIdx >= 0 && dependsOnColIdx < cells.length) {
|
|
3034
3099
|
const raw = cells[dependsOnColIdx];
|
|
@@ -3036,15 +3101,62 @@ function parseFunctionMap(content, allPlatforms) {
|
|
|
3036
3101
|
dependsOn = raw.split(/[,,]/).map(p => p.trim()).filter(Boolean);
|
|
3037
3102
|
}
|
|
3038
3103
|
}
|
|
3039
|
-
// 解析说明
|
|
3040
3104
|
let description = '';
|
|
3041
|
-
if (descColIdx >= 0 && descColIdx < cells.length)
|
|
3105
|
+
if (descColIdx >= 0 && descColIdx < cells.length)
|
|
3042
3106
|
description = cells[descColIdx];
|
|
3043
|
-
}
|
|
3044
3107
|
units.push({ name: unitName, platforms, sharedCapability, dependsOn, description });
|
|
3045
3108
|
}
|
|
3046
3109
|
return units;
|
|
3047
3110
|
}
|
|
3111
|
+
/**
|
|
3112
|
+
* v7.4.2+: 解析 FUNCTION_MAP.md 树形格式
|
|
3113
|
+
* 匹配: ├── 会议室 CRUD .............. room-service ✅ | admin-web ✅
|
|
3114
|
+
* 提取: 模块名="会议室 CRUD", 涉及端=[room-service, admin-web]
|
|
3115
|
+
*/
|
|
3116
|
+
function parseFunctionMapTree(content, allPlatforms) {
|
|
3117
|
+
const units = [];
|
|
3118
|
+
const lines = content.split('\n');
|
|
3119
|
+
// 树形节点正则:匹配 ├── 或 └── 开头的行
|
|
3120
|
+
const treeNodeRe = /[├└]──\s*/;
|
|
3121
|
+
// 分隔点正则:匹配连续 .... 或 ———
|
|
3122
|
+
const separatorRe = /\s*[.·]{3,}\s*/;
|
|
3123
|
+
for (const line of lines) {
|
|
3124
|
+
if (!treeNodeRe.test(line))
|
|
3125
|
+
continue;
|
|
3126
|
+
// 提取树节点后的文本
|
|
3127
|
+
const afterTree = line.replace(/.*[├└]──\s*/, '').trim();
|
|
3128
|
+
if (!afterTree)
|
|
3129
|
+
continue;
|
|
3130
|
+
// 跳过分类型节点(如 ├── 🏢 会议室管理),这些是父级分类
|
|
3131
|
+
// 父级节点通常没有 ... 分隔符和平台标记
|
|
3132
|
+
if (!separatorRe.test(afterTree) && !afterTree.includes('✅') && !afterTree.includes('❌') && !afterTree.includes('⚠️')) {
|
|
3133
|
+
continue; // 这是分类父节点,跳过
|
|
3134
|
+
}
|
|
3135
|
+
// 分割模块名和平台部分
|
|
3136
|
+
const parts = afterTree.split(separatorRe);
|
|
3137
|
+
if (parts.length < 2)
|
|
3138
|
+
continue;
|
|
3139
|
+
// 模块名:去除 emoji 和空格
|
|
3140
|
+
let unitName = parts[0].replace(/[🏢📅✅💰🔔👥📊⚙️🔑🛡️]/g, '').trim();
|
|
3141
|
+
if (!unitName || unitName.length < 2)
|
|
3142
|
+
continue;
|
|
3143
|
+
// 平台部分:提取所有标准端名
|
|
3144
|
+
const platformPart = parts.slice(1).join(' ');
|
|
3145
|
+
const platforms = [];
|
|
3146
|
+
for (const p of allPlatforms) {
|
|
3147
|
+
if (platformPart.includes(p)) {
|
|
3148
|
+
platforms.push(p);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
// 如果没匹配到标准端名,尝试匹配常见缩写
|
|
3152
|
+
if (platforms.length === 0) {
|
|
3153
|
+
// 回退全端
|
|
3154
|
+
platforms.push(...allPlatforms);
|
|
3155
|
+
}
|
|
3156
|
+
units.push({ name: unitName, platforms, sharedCapability: '无', dependsOn: [], description: '' });
|
|
3157
|
+
}
|
|
3158
|
+
return units;
|
|
3159
|
+
}
|
|
3048
3160
|
/**
|
|
3049
3161
|
* 尝试模块驱动拆分:从功能模块创建任务目录结构
|
|
3050
3162
|
* 成功返回 true,无功能模块时返回 false(回退到传统流程)
|