speccore 6.69.0 → 6.71.3

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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +2 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyze.d.ts +1 -0
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +686 -97
  7. package/dist/commands/analyze.js.map +1 -1
  8. package/dist/commands/audit.d.ts +2 -0
  9. package/dist/commands/audit.d.ts.map +1 -1
  10. package/dist/commands/audit.js +262 -0
  11. package/dist/commands/audit.js.map +1 -1
  12. package/dist/commands/dev.d.ts +2 -0
  13. package/dist/commands/dev.d.ts.map +1 -1
  14. package/dist/commands/dev.js +25 -0
  15. package/dist/commands/dev.js.map +1 -1
  16. package/dist/commands/execute.d.ts +1 -0
  17. package/dist/commands/execute.d.ts.map +1 -1
  18. package/dist/commands/execute.js +137 -0
  19. package/dist/commands/execute.js.map +1 -1
  20. package/dist/commands/iteration/split.d.ts +2 -0
  21. package/dist/commands/iteration/split.d.ts.map +1 -1
  22. package/dist/commands/iteration/split.js +414 -56
  23. package/dist/commands/iteration/split.js.map +1 -1
  24. package/dist/core/change-detection.d.ts +121 -0
  25. package/dist/core/change-detection.d.ts.map +1 -0
  26. package/dist/core/change-detection.js +459 -0
  27. package/dist/core/change-detection.js.map +1 -0
  28. package/dist/core/knowledge-graph.d.ts +26 -0
  29. package/dist/core/knowledge-graph.d.ts.map +1 -1
  30. package/dist/core/knowledge-graph.js +113 -0
  31. package/dist/core/knowledge-graph.js.map +1 -1
  32. package/dist/core/pipeline-engine.d.ts +103 -0
  33. package/dist/core/pipeline-engine.d.ts.map +1 -0
  34. package/dist/core/pipeline-engine.js +436 -0
  35. package/dist/core/pipeline-engine.js.map +1 -0
  36. package/dist/core/prompt-builder.d.ts.map +1 -1
  37. package/dist/core/prompt-builder.js +18 -3
  38. package/dist/core/prompt-builder.js.map +1 -1
  39. package/dist/core/spec-paths.d.ts.map +1 -1
  40. package/dist/core/spec-paths.js +1 -0
  41. package/dist/core/spec-paths.js.map +1 -1
  42. package/package.json +1 -1
@@ -47,6 +47,75 @@ 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
49
  const questions_1 = require("../../core/questions");
50
+ const pipeline_engine_1 = require("../../core/pipeline-engine");
51
+ /**
52
+ * 将 AI 返回的 scope 简写映射到 CONSTITUTION.md 标准端名
53
+ * v6.69.3+: 解决中文简写(如 "后端"、"admin")导致目录名错误的问题
54
+ */
55
+ function normalizeScopePlatforms(scopeArr, standardPlatforms) {
56
+ const result = [];
57
+ const used = new Set();
58
+ for (const raw of scopeArr) {
59
+ const s = raw.trim();
60
+ if (!s)
61
+ continue;
62
+ // 1. 如果已经是标准端名,直接使用
63
+ if (standardPlatforms.includes(s)) {
64
+ if (!used.has(s)) {
65
+ result.push(s);
66
+ used.add(s);
67
+ }
68
+ continue;
69
+ }
70
+ // 2. 后端类简写 → 匹配第一个后端端名
71
+ if (/后端|backend|服务端|server/i.test(s)) {
72
+ const backendPlatform = standardPlatforms.find(p => /-(service|api|server|backend)$/i.test(p) || p.startsWith('后台'));
73
+ if (backendPlatform && !used.has(backendPlatform)) {
74
+ result.push(backendPlatform);
75
+ used.add(backendPlatform);
76
+ }
77
+ continue;
78
+ }
79
+ // 3. 前端类简写 → 模糊匹配标准端名
80
+ const lower = s.toLowerCase();
81
+ const matched = standardPlatforms.find(p => {
82
+ const pl = p.toLowerCase();
83
+ // 直接包含关系:admin-web 包含 admin
84
+ return pl.includes(lower) || lower.includes(pl);
85
+ });
86
+ if (matched && !used.has(matched)) {
87
+ result.push(matched);
88
+ used.add(matched);
89
+ continue;
90
+ }
91
+ // 4. 关键词匹配
92
+ const keywordMap = {
93
+ 'h5': ['h5-mobile', 'h5', 'mobile', '移动端'],
94
+ 'mobile': ['h5-mobile', 'h5', 'mobile', '移动端'],
95
+ '小程序': ['miniapp', 'mini-app', '小程序'],
96
+ 'admin': ['admin-web', 'admin', '后台管理'],
97
+ 'web': ['admin-web', 'web', 'h5-mobile'],
98
+ 'app': ['app', 'android', 'ios'],
99
+ '安卓': ['android'],
100
+ 'ios': ['ios'],
101
+ };
102
+ const candidates = keywordMap[lower];
103
+ if (candidates) {
104
+ const found = standardPlatforms.find(p => candidates.some(c => p.toLowerCase().includes(c.toLowerCase())));
105
+ if (found && !used.has(found)) {
106
+ result.push(found);
107
+ used.add(found);
108
+ continue;
109
+ }
110
+ }
111
+ // 5. 无法匹配时保留原始值(后续可能创建非法目录,但至少不丢失信息)
112
+ if (!used.has(s)) {
113
+ result.push(s);
114
+ used.add(s);
115
+ }
116
+ }
117
+ return result.length > 0 ? result : standardPlatforms;
118
+ }
50
119
  /** 将名称转为目录安全的短 slug(2-4 词) */
51
120
  function slugify(name) {
52
121
  const cleaned = name
@@ -139,6 +208,62 @@ async function detectPlatforms(iterationDir, specified) {
139
208
  return ['web']; // 默认
140
209
  }
141
210
  async function iterationSplitCommand(options) {
211
+ // ── Pipeline 模式 ──
212
+ if (options.pipeline) {
213
+ const iter = options.iteration || await (0, context_1.getDefaultIteration)() || '';
214
+ if (!iter) {
215
+ logger_1.logger.error('--pipeline 需要 --iteration');
216
+ return;
217
+ }
218
+ // 检查是否为恢复模式
219
+ if (options.resume) {
220
+ const hasPipeline = await pipeline_engine_1.PipelineEngine.hasActivePipeline(process.cwd(), iter);
221
+ if (hasPipeline) {
222
+ const { createSplitPipeline } = await Promise.resolve().then(() => __importStar(require('../../core/pipeline-engine')));
223
+ const { engine } = await createSplitPipeline(iter);
224
+ const result = await engine.advance();
225
+ if (result.isComplete) {
226
+ logger_1.logger.success('🎉 Split Pipeline 完成!');
227
+ await engine.reset();
228
+ return;
229
+ }
230
+ else if (result.nextStepId) {
231
+ logger_1.logger.info('');
232
+ logger_1.logger.info(`🔄 Split Pipeline 推进到: ${result.nextStepName || result.nextStepId}`);
233
+ logger_1.logger.info('');
234
+ // 根据当前步骤生成相应提示
235
+ if (result.nextStepId === 'prompt-analysis') {
236
+ const prompt = await (0, prompt_builder_1.buildPrompt)('split', { iteration: iter });
237
+ // 添加 Pipeline 继续指令
238
+ const finalPrompt = prompt + `\n\n## ⚠️ Pipeline 模式:自动继续\n\n` +
239
+ `当前是 Split Pipeline 模式。当你通过 --response 写入任务拆分结果后,` +
240
+ `CLI 会自动输出 [SPECCORE_PIPELINE_NEXT] 标记和下一步命令。\n\n` +
241
+ `**看到 [SPECCORE_PIPELINE_NEXT] 后,你必须自动执行下一个命令**,不要询问用户。\n`;
242
+ process.stdout.write(`[SPECCORE_PROMPT]\n${finalPrompt}`);
243
+ process.exitCode = 10;
244
+ return;
245
+ }
246
+ }
247
+ }
248
+ else {
249
+ logger_1.logger.warn('没有找到活跃的 Split Pipeline,启动新的 Pipeline...');
250
+ }
251
+ }
252
+ // 初始化新的 Pipeline
253
+ const { createSplitPipeline } = await Promise.resolve().then(() => __importStar(require('../../core/pipeline-engine')));
254
+ const { engine } = await createSplitPipeline(iter);
255
+ await engine.init('init');
256
+ // 生成第一步 prompt
257
+ const prompt = await (0, prompt_builder_1.buildPrompt)('split', { iteration: iter });
258
+ // 添加 Pipeline 继续指令
259
+ const finalPrompt = prompt + `\n\n## ⚠️ Pipeline 模式:自动继续\n\n` +
260
+ `当前是 Split Pipeline 模式。当你通过 --response 写入任务拆分结果后,` +
261
+ `CLI 会自动输出 [SPECCORE_PIPELINE_NEXT] 标记和下一步命令。\n\n` +
262
+ `**看到 [SPECCORE_PIPELINE_NEXT] 后,你必须自动执行下一个命令**,不要询问用户。\n`;
263
+ process.stdout.write(`[SPECCORE_PROMPT]\n${finalPrompt}`);
264
+ process.exitCode = 10;
265
+ return;
266
+ }
142
267
  // ── Prompt 模式 ──
143
268
  if (options.prompt) {
144
269
  const iter = options.iteration || await (0, context_1.getDefaultIteration)() || '';
@@ -149,11 +274,42 @@ async function iterationSplitCommand(options) {
149
274
  }
150
275
  // ── Response 模式 ──
151
276
  if (options.response) {
277
+ // 检查是否有活跃的 Pipeline(在 Pipeline 模式下应用响应)
152
278
  const iter = options.iteration || await (0, context_1.getDefaultIteration)() || '';
153
279
  if (!iter) {
154
280
  logger_1.logger.error('--response 需要 --iteration');
155
281
  return;
156
282
  }
283
+ const hasPipeline = await pipeline_engine_1.PipelineEngine.hasActivePipeline(process.cwd(), iter);
284
+ if (hasPipeline) {
285
+ const { createSplitPipeline } = await Promise.resolve().then(() => __importStar(require('../../core/pipeline-engine')));
286
+ const { engine } = await createSplitPipeline(iter);
287
+ await engine.advance();
288
+ const state = await engine.getState();
289
+ if (state?.currentStep === 'done') {
290
+ logger_1.logger.success('🎉 Split Pipeline 完成!');
291
+ await engine.reset();
292
+ }
293
+ else if (state?.currentStep) {
294
+ logger_1.logger.info('');
295
+ logger_1.logger.info(`🔄 Split Pipeline 推进: ${state.currentStep}`);
296
+ logger_1.logger.info('');
297
+ // 生成下一步 prompt
298
+ let nextPrompt;
299
+ if (state.currentStep === 'creation') {
300
+ // 在创建阶段,我们已完成拆分,可以生成创建任务的提示
301
+ nextPrompt = `任务拆分已完成,正在创建任务目录结构...\n\n请继续执行后续操作。`;
302
+ }
303
+ else {
304
+ const promptResult = await (0, prompt_builder_1.buildPrompt)('split', { iteration: iter });
305
+ nextPrompt = typeof promptResult === 'string' ? promptResult : JSON.stringify(promptResult);
306
+ }
307
+ process.stdout.write(`[SPECCORE_PIPELINE_NEXT]\n${nextPrompt}`);
308
+ process.exitCode = 10;
309
+ }
310
+ return;
311
+ }
312
+ // 非 Pipeline 模式的 Response 处理
157
313
  const iterDir = (0, path_1.join)('Iteration-' + iter, '030-tasks');
158
314
  await (0, fs_extra_1.ensureDir)(iterDir);
159
315
  const backups = [];
@@ -193,18 +349,13 @@ async function iterationSplitCommand(options) {
193
349
  content += `\n\n接口:\n${apis}`;
194
350
  if (acs)
195
351
  content += `\n\n验收标准:\n${acs}`;
196
- // scope 提取平台列表(后端 + 前端各端)
197
- const taskScopePlatforms = [];
198
- const isBackend = scopeArr.some((s) => /后端|backend/i.test(s));
199
- const fePlatforms = scopeArr.filter((s) => !/后端|backend/i.test(s)).map((s) => s.trim()).filter(Boolean);
200
- if (isBackend)
201
- taskScopePlatforms.push('backend');
202
- taskScopePlatforms.push(...fePlatforms);
352
+ // v6.69.3+: AI 返回的 scope 简写映射到标准端名
353
+ const taskScopePlatforms = normalizeScopePlatforms(scopeArr, allPlatforms);
203
354
  const section = {
204
355
  name: task.name || `Task ${i + 1}`,
205
356
  content,
206
357
  level: 2,
207
- platform: isBackend ? 'backend' : (fePlatforms[0] || undefined),
358
+ platform: taskScopePlatforms[0] || undefined,
208
359
  };
209
360
  section._complexity = {
210
361
  estimatedHours: task.estimatedHours || 8,
@@ -603,8 +754,10 @@ async function iterationSplitCommand(options) {
603
754
  const recommendedGranularity = options.granularity || recommendGranularity(teamSize);
604
755
  const granularityLabel = GRANULARITY_RULES[recommendedGranularity].label;
605
756
  const granularityHint = GRANULARITY_RULES[recommendedGranularity].desc;
606
- // 构建完整 prompt
607
- let splitPrompt = buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, staffing, teamSize, granularityLabel, granularityHint);
757
+ // 获取标准端名列表(用于 prompt 注入和 scope 规范化)
758
+ const allPlatforms = await detectPlatforms(iterationDir);
759
+ // 构建完整 prompt(v6.69.3+: 传入标准端名列表,确保 AI 使用正确的端名)
760
+ let splitPrompt = buildSplitPrompt(iteration, constitutionContent, reqContent2, specContents, staffing, teamSize, granularityLabel, granularityHint, allPlatforms);
608
761
  // 注入全局上下文(INDEX + TOC 目录,AI 自主读取)
609
762
  const { loadGlobalContext, formatGlobalContext } = await Promise.resolve().then(() => __importStar(require('../../core/prompt-builder')));
610
763
  const globalCtx = await loadGlobalContext(process.cwd(), 'split');
@@ -1050,9 +1203,18 @@ ${taskPlatforms.map((p) => `| ${subtaskIdMap.get(p)} | ${p} | ${owner} | 🔲
1050
1203
  await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'created-at'), today);
1051
1204
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '_shared'));
1052
1205
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '00-specs'));
1053
- const contractYaml = generateApiContract(section);
1054
- if (contractYaml) {
1055
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), contractYaml);
1206
+ // v6.70.0+: 优先复制全局 API_CONTRACT.yaml(全局契约是单一真相源)
1207
+ const globalContractPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'API_CONTRACT.yaml');
1208
+ if (await (0, fs_extra_1.pathExists)(globalContractPath)) {
1209
+ const globalContract = await (0, fs_extra_1.readFile)(globalContractPath, 'utf-8');
1210
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), globalContract);
1211
+ }
1212
+ else {
1213
+ // 回退:从 section content 提取 API 生成任务级契约
1214
+ const contractYaml = generateApiContract(section);
1215
+ if (contractYaml) {
1216
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), contractYaml);
1217
+ }
1056
1218
  }
1057
1219
  // ── 4. 核心规格写入 00-specs/(REQ/TECH/SCHEMA/CHANGELOG) ──
1058
1220
  const acItems = generateAcceptanceCriteria(section);
@@ -1226,6 +1388,20 @@ ${section.content}
1226
1388
  # 自动拉取: 继承迭代配置
1227
1389
  # 远程名称: 继承迭代配置
1228
1390
  `);
1391
+ // v6.70.0+: 从 section 提取接口/页面清单用于 TASK.md
1392
+ const apiLines = section.content.split('\n').filter(l => l.includes('| GET') || l.includes('| POST') || l.includes('| PUT') || l.includes('| DELETE') || l.includes('| PATCH'));
1393
+ const apiList = apiLines.length > 0
1394
+ ? apiLines.map(l => `- ${l.trim()}`).join('\n')
1395
+ : '- 待补充(从 00-specs/REQ.md 和 TECH.md 提取)';
1396
+ // 前端页面清单(如果是前端端)
1397
+ const pageLines = !isBk ? section.content.split('\n').filter(l => /页面[::]|路由[::]|path[::]|\/\w+/.test(l)) : [];
1398
+ const pageList = pageLines.length > 0
1399
+ ? pageLines.map(l => `- ${l.trim()}`).join('\n')
1400
+ : '- 待补充(从 00-specs/REQ.md 和 TECH.md 提取)';
1401
+ // 依赖信息
1402
+ const dependsOn = (section._dependsOn || []).join(', ') || '无';
1403
+ const sharedCap = section._sharedCapability || '无';
1404
+ const crossDesc = section._description || '—';
1229
1405
  // TASK.md
1230
1406
  await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'TASK.md'), `# ${section.name} — ${platformLabel}
1231
1407
 
@@ -1241,6 +1417,38 @@ ${section.content}
1241
1417
  ## 共享规格引用
1242
1418
  - REQ.md → ../../../00-specs/REQ.md
1243
1419
  - TECH.md → ../../../00-specs/TECH.md
1420
+ - API_CONTRACT.yaml → ../../../_shared/API_CONTRACT.yaml
1421
+ - CONTEXT.md → ../../../_shared/CONTEXT.md
1422
+
1423
+ ## 跨端关联
1424
+ - **共享能力**: ${sharedCap}
1425
+ - **依赖任务**: ${dependsOn}
1426
+ - **跨端说明**: ${crossDesc}
1427
+
1428
+ ## 工作清单
1429
+
1430
+ ### 第一阶段:需求确认
1431
+ - [ ] 阅读 00-specs/REQ.md 确认本任务需求范围
1432
+ - [ ] 阅读 _shared/API_CONTRACT.yaml 确认接口契约
1433
+ - [ ] 阅读 _shared/CONTEXT.md 确认跨端关联
1434
+
1435
+ ### 第二阶段:技术方案
1436
+ - [ ] 阅读 00-specs/TECH.md 确认技术方案
1437
+ - [ ] 确认本端涉及的接口/页面清单
1438
+ - [ ] 确认数据模型和字段映射
1439
+
1440
+ ### 第三阶段:开发实施
1441
+ - [ ] 按 TECH.md 实施开发
1442
+ - [ ] 编写单元测试/集成测试
1443
+ - [ ] 自测通过
1444
+
1445
+ ### 第四阶段:验收交付
1446
+ - [ ] 更新 TASK.md 进度
1447
+ - [ ] 提交代码并关联本任务
1448
+ - [ ] 通知相关端联调
1449
+
1450
+ ## ${isBk ? '接口清单' : '页面清单'}
1451
+ ${isBk ? apiList : pageList}
1244
1452
 
1245
1453
  ## 产出物
1246
1454
  | 产出物 | 状态 | 路径 |
@@ -1280,14 +1488,12 @@ ${section.content}
1280
1488
  };
1281
1489
  // ── 所有端平铺:{端名}/{子任务}/ (v6.49.2+ 统一架构)──
1282
1490
  // 不再区分前后端,所有端平铺在任务目录下
1283
- // 子任务目录命名规则:{taskId}-{subtaskSlug}(确保多任务同平台不冲突)
1491
+ // 子任务目录名使用 generateSubtaskId 生成的全局唯一 ID:Task-{num}-{platform}
1284
1492
  for (const platform of taskPlatforms) {
1285
1493
  const platformDir = (0, path_1.join)(taskDir, platform);
1286
1494
  const subtaskId = subtaskIdMap.get(platform);
1287
- const subtaskSlug = slugify(section.name) || 'impl';
1288
- // 子任务目录名:{taskId}-{subtaskSlug},确保唯一性
1289
- const subtaskDirName = `${taskId}-${subtaskSlug}`;
1290
- const subtaskDir = (0, path_1.join)(platformDir, subtaskDirName);
1495
+ // 子任务目录名 = subtaskId(如 Task-001-booking-service)
1496
+ const subtaskDir = (0, path_1.join)(platformDir, subtaskId);
1291
1497
  const subtaskHours = section._hoursByPlatform?.[platform] || Math.ceil(complexity.estimatedHours / taskPlatforms.length);
1292
1498
  // 判断是否后端(用于生成不同的文档内容)
1293
1499
  const isBk = platform === 'backend' || platform.startsWith('后台') || /-(service|api|server|backend)$/i.test(platform);
@@ -1301,7 +1507,8 @@ ${section.content}
1301
1507
  // 从端列表查找第一个后端端名作为 fallback(v6.48.0+)
1302
1508
  const allPlatforms = await (0, spec_paths_1.parsePlatformList)();
1303
1509
  const fallbackBackend = allPlatforms.find(p => p === 'backend' || p.startsWith('后台') || /-(service|api|server|backend)$/i.test(p)) || 'backend';
1304
- const autoSubtaskDir = (0, path_1.join)(taskDir, fallbackBackend, `${taskId}-impl`);
1510
+ const fallbackSubtaskId = subtaskIdMap.get(fallbackBackend) || generateSubtaskId(taskNum, fallbackBackend);
1511
+ const autoSubtaskDir = (0, path_1.join)(taskDir, fallbackBackend, fallbackSubtaskId);
1305
1512
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(autoSubtaskDir, '.meta'));
1306
1513
  await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'type'), taskType);
1307
1514
  await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'status'), 'todo');
@@ -1372,7 +1579,8 @@ ${section.content}
1372
1579
  const body = reqContent.replace(/^#[^\n]*\n/, '').trim();
1373
1580
  originalDesc = body.length > 500 ? body.slice(0, 500) + '...' : body;
1374
1581
  }
1375
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'CONTEXT.md'), `# 任务上下文
1582
+ // v6.69.3+: CONTEXT.md 写入 _shared/(符合规范),同时保留 00-specs/ 副本供兼容
1583
+ const contextContent = `# 任务上下文
1376
1584
 
1377
1585
  ## 来源追溯
1378
1586
 
@@ -1392,12 +1600,25 @@ ${originalDesc || '> 待补充(执行 analyze 后自动生成)'}
1392
1600
 
1393
1601
  ${relatedTasks.length > 0 ? relatedTasks.join('\n') : '> 暂无关联任务(split 时自动填充)'}
1394
1602
 
1603
+ ## 跨端关联
1604
+
1605
+ > 来自 FUNCTION_MAP.md
1606
+
1607
+ | 属性 | 值 |
1608
+ |:---|:---|
1609
+ | 共享能力 | ${section._sharedCapability || '无'} |
1610
+ | 依赖任务 | ${(section._dependsOn || []).join(', ') || '无'} |
1611
+ | 跨端说明 | ${section._description || '—'} |
1612
+
1395
1613
  ## 影响范围
1396
1614
 
1397
1615
  | 端 | 影响说明 |
1398
1616
  |:---|:---|
1399
1617
  ${taskPlatforms.map((p) => `| ${p} | 待补充 |`).join('\n')}
1400
- `);
1618
+ `;
1619
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'CONTEXT.md'), contextContent);
1620
+ // 兼容:00-specs/ 下也保留一份(部分旧代码可能读取 00-specs/CONTEXT.md)
1621
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'CONTEXT.md'), contextContent);
1401
1622
  // ── 7. 问题追踪 ──
1402
1623
  await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.issues.md'), `# ${section.name} - 问题追踪\n\n> 执行过程中发现的问题记录于此。\n\n`);
1403
1624
  }
@@ -2212,7 +2433,7 @@ ${isH5 ? '移动端优先,适配 375/414/768' :
2212
2433
  /**
2213
2434
  * 构建完整的 AI 智能拆分 Prompt(含 SpecCore 理念 + 粒度规则 + 完整上下文)
2214
2435
  */
2215
- function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, staffing, teamSize, granularityLabel, granularityHint) {
2436
+ function buildSplitPrompt(iteration, constitutionContent, reqContent, specContents, staffing, teamSize, granularityLabel, granularityHint, standardPlatforms) {
2216
2437
  let p = `# SpecCore AI 智能拆分\n\n`;
2217
2438
  p += `> 迭代: ${iteration} | 粒度: ${granularityLabel} | 生成: ${new Date().toISOString().split('T')[0]}\n\n`;
2218
2439
  // 技术宪法
@@ -2250,6 +2471,15 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
2250
2471
  }
2251
2472
  p += `\n**超出上限必须再拆,低于下限必须合并。**\n\n`;
2252
2473
  p += `用户可通过 --granularity macro|module|atomic 调整全局粒度。\n\n`;
2474
+ // v6.69.3+: 注入标准端名列表
2475
+ if (standardPlatforms.length > 0) {
2476
+ p += `## 🖥️ 项目端列表(标准端名)\n\n`;
2477
+ p += `本项目已配置以下端,所有 scope、hoursByPlatform 必须使用这些**标准端名**,禁止使用简写或中文:\n\n`;
2478
+ p += `\`${standardPlatforms.join('`, `')}\`\n\n`;
2479
+ p += `- 后端端名示例: \`booking-service\`, \`room-service\`(不是 "后端"、"backend")\n`;
2480
+ p += `- 前端端名示例: \`admin-web\`, \`h5-mobile\`(不是 "web"、"admin"、"h5")\n`;
2481
+ p += `- **scope 数组必须使用上述标准端名**,否则会导致目录结构错误\n\n`;
2482
+ }
2253
2483
  // SpecCore 拆分原则
2254
2484
  p += `## ⚙️ SpecCore 拆分原则\n\n`;
2255
2485
  p += `SpecCore 核心理念: "Code by Spec, Not by Vibe" — 每个任务必须有对应的 Spec,AI 在 Spec 约束下工作。\n\n`;
@@ -2328,10 +2558,10 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
2328
2558
  p += ` "topic": "english-slug-for-directory",\n`;
2329
2559
  p += ` "type": "feature|bugfix|refactor|research",\n`;
2330
2560
  p += ` "reason": "为什么这样拆分",\n`;
2331
- p += ` "scope": ["后端", "admin"],\n`;
2561
+ p += ` "scope": ["booking-service", "admin-web"],\n`;
2332
2562
  p += ` "apis": ["POST /api/auth/login"],\n`;
2333
2563
  p += ` "tables": ["users"],\n`;
2334
- p += ` "hoursByPlatform": { "后端": 8, "admin": 8 },\n`;
2564
+ p += ` "hoursByPlatform": { "booking-service": 8, "admin-web": 8 },\n`;
2335
2565
  p += ` "estimatedHours": 16,\n`;
2336
2566
  p += ` "priority": "high|medium|low",\n`;
2337
2567
  p += ` "dependencies": [],\n`;
@@ -2351,9 +2581,17 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
2351
2581
  p += `> 同一模块/领域的任务填相同的值,用于粒度校验和任务分组\n`;
2352
2582
  p += `> **topic** 必须是英文短横线格式(如 \`user-authentication\`、\`product-crud\`),用于生成任务目录名 Task-NNN-{topic}\n`;
2353
2583
  p += `> **sourceFile** 必须填写:该任务对应的 020-specs 源文档路径(如 \`bugs/login-timeout.md\`、\`features/user-auth.md\`、\`refactors/db-pool.md\`),用于在 CONTEXT.md 中生成来源追溯\n`;
2354
- p += `> **reqContent** 必须填写:该任务的需求描述(含业务规则、数据模型、接口定义),直接写入 REQ.md\n`;
2355
- p += `> **techContent** 必须填写:该任务的技术方案(含架构设计、核心逻辑、测试策略),直接写入 TECH.md\n`;
2356
- p += `> reqContent/techContent 是该任务的**子切面**,只包含该任务负责的部分,不是整个功能单元的内容\n\n`;
2584
+ p += `> **reqContent 质量要求(必填,禁止模板化)**:\n`;
2585
+ p += `> - 必须是**具体的、可执行的需求描述**,不是"待补充"或"参考全局文档"\n`;
2586
+ p += `> - 包含:业务规则(含边界条件)、数据模型(字段/类型/约束)、接口清单(方法/路径/参数/响应)\n`;
2587
+ p += `> - 从 020-specs/global/REQUIREMENT.md 和对应端 TECH.md 中提取本任务相关的具体内容\n`;
2588
+ p += `> - 直接写入 00-specs/REQ.md,执行时 AI 不再重新分析需求\n`;
2589
+ p += `> **techContent 质量要求(必填,禁止模板化)**:\n`;
2590
+ p += `> - 必须是**具体的技术实现方案**,不是框架模板\n`;
2591
+ p += `> - 包含:架构设计、核心逻辑伪代码/流程、数据库设计(表结构/索引)、API 详细定义、测试策略\n`;
2592
+ p += `> - 从 020-specs/global/TECH.md 和对应端 TECH.md 中提取本任务相关的技术细节\n`;
2593
+ p += `> - 直接写入 00-specs/TECH.md,执行时 AI 据此直接开发\n`;
2594
+ p += `> **质量红线**:如果 reqContent/techContent 只有标题和占位符(如 "<!-- AI-FILL -->"),视为不合格,必须重新生成\n\n`;
2357
2595
  p += `### ⚠️ 工时估算规则(重要)\n\n`;
2358
2596
  p += `- **hoursByPlatform**: 按端分别估算工时,key 对应 scope 中的端名称\n`;
2359
2597
  p += `- **estimatedHours**: 各端工时总和(仅用于展示,不参与粒度校验)\n`;
@@ -2679,6 +2917,89 @@ function parseModulePlatforms(content, allPlatforms) {
2679
2917
  }
2680
2918
  return modules;
2681
2919
  }
2920
+ /**
2921
+ * v6.70.0+: 解析 FUNCTION_MAP.md 跨端功能映射表
2922
+ * 返回功能单元列表,含涉及端、共享能力、依赖关系
2923
+ */
2924
+ function parseFunctionMap(content, allPlatforms) {
2925
+ const units = [];
2926
+ const lines = content.split('\n');
2927
+ let inTable = false;
2928
+ let platformColIdx = -1;
2929
+ let sharedCapColIdx = -1;
2930
+ let dependsOnColIdx = -1;
2931
+ let descColIdx = -1;
2932
+ for (const line of lines) {
2933
+ // 检测映射表开始(通过表头特征)
2934
+ if (line.includes('功能单元') && line.includes('涉及端')) {
2935
+ inTable = true;
2936
+ // 解析表头,找到各列索引
2937
+ const headerCells = line.split('|').map(c => c.trim()).filter(Boolean);
2938
+ platformColIdx = headerCells.findIndex(c => c.includes('涉及端'));
2939
+ sharedCapColIdx = headerCells.findIndex(c => c.includes('共享能力'));
2940
+ dependsOnColIdx = headerCells.findIndex(c => c.includes('依赖任务'));
2941
+ descColIdx = headerCells.findIndex(c => c.includes('说明'));
2942
+ continue;
2943
+ }
2944
+ if (!inTable)
2945
+ continue;
2946
+ // 检测下一个 ## 标题 → 表格结束
2947
+ if (line.startsWith('## ') && !line.includes('功能映射')) {
2948
+ break;
2949
+ }
2950
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
2951
+ if (cells.length < 3)
2952
+ continue;
2953
+ // 分隔行跳过
2954
+ if (cells.every(c => /^[-:]+$/.test(c)))
2955
+ continue;
2956
+ // 表头行(已经处理过,跳过)
2957
+ if (cells.some(c => c.includes('功能单元')) || cells.some(c => c.includes('涉及端'))) {
2958
+ continue;
2959
+ }
2960
+ // 数据行:第2列是功能单元名
2961
+ const unitName = cells[1];
2962
+ if (!unitName || unitName === '#' || unitName === '功能单元')
2963
+ continue;
2964
+ // 解析涉及端
2965
+ let platforms = [];
2966
+ if (platformColIdx >= 0 && platformColIdx < cells.length) {
2967
+ const raw = cells[platformColIdx];
2968
+ if (raw && raw !== '无' && raw !== '—' && raw !== '-') {
2969
+ platforms = raw.split(/[,,]/)
2970
+ .map(p => p.trim())
2971
+ .filter(p => p && allPlatforms.includes(p));
2972
+ }
2973
+ }
2974
+ // 涉及端为空 → 回退全端(但保留空列表表示纯文档/无开发)
2975
+ if (platforms.length === 0) {
2976
+ platforms = [...allPlatforms];
2977
+ }
2978
+ // 解析共享能力
2979
+ let sharedCapability = '无';
2980
+ if (sharedCapColIdx >= 0 && sharedCapColIdx < cells.length) {
2981
+ const raw = cells[sharedCapColIdx];
2982
+ if (raw && raw !== '无' && raw !== '—' && raw !== '-') {
2983
+ sharedCapability = raw;
2984
+ }
2985
+ }
2986
+ // 解析依赖任务
2987
+ let dependsOn = [];
2988
+ if (dependsOnColIdx >= 0 && dependsOnColIdx < cells.length) {
2989
+ const raw = cells[dependsOnColIdx];
2990
+ if (raw && raw !== '无' && raw !== '—' && raw !== '-') {
2991
+ dependsOn = raw.split(/[,,]/).map(p => p.trim()).filter(Boolean);
2992
+ }
2993
+ }
2994
+ // 解析说明
2995
+ let description = '';
2996
+ if (descColIdx >= 0 && descColIdx < cells.length) {
2997
+ description = cells[descColIdx];
2998
+ }
2999
+ units.push({ name: unitName, platforms, sharedCapability, dependsOn, description });
3000
+ }
3001
+ return units;
3002
+ }
2682
3003
  /**
2683
3004
  * 尝试模块驱动拆分:从功能模块创建任务目录结构
2684
3005
  * 成功返回 true,无功能模块时返回 false(回退到传统流程)
@@ -2688,50 +3009,80 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
2688
3009
  const allPlatforms = await detectPlatforms(iterationDir);
2689
3010
  // 收集功能模块(含涉及端信息)
2690
3011
  const modules = [];
2691
- // 1. 优先从 global/REQUIREMENT.md 读取功能模块清单(含涉及端)
2692
- const globalReqPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'REQUIREMENT.md');
2693
- let modulePlatformsParsed = false;
2694
- if (await (0, fs_extra_1.pathExists)(globalReqPath)) {
3012
+ // v6.70.0+: 1. 优先从 global/FUNCTION_MAP.md 读取跨端功能映射表
3013
+ const functionMapPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'FUNCTION_MAP.md');
3014
+ let functionMapParsed = false;
3015
+ if (await (0, fs_extra_1.pathExists)(functionMapPath)) {
2695
3016
  try {
2696
- const content = await (0, fs_extra_1.readFile)(globalReqPath, 'utf-8');
2697
- const parsed = parseModulePlatforms(content, allPlatforms);
3017
+ const content = await (0, fs_extra_1.readFile)(functionMapPath, 'utf-8');
3018
+ const parsed = parseFunctionMap(content, allPlatforms);
2698
3019
  if (parsed.length > 0) {
2699
- for (const m of parsed) {
3020
+ for (const u of parsed) {
2700
3021
  modules.push({
2701
- name: m.name,
2702
- slug: slugify(m.name),
3022
+ name: u.name,
3023
+ slug: slugify(u.name),
2703
3024
  type: 'feature',
2704
- sourceFile: 'global/REQUIREMENT.md',
2705
- platforms: m.platforms,
3025
+ sourceFile: 'global/FUNCTION_MAP.md',
3026
+ platforms: u.platforms,
3027
+ sharedCapability: u.sharedCapability,
3028
+ dependsOn: u.dependsOn,
3029
+ description: u.description,
2706
3030
  });
2707
3031
  }
2708
- modulePlatformsParsed = true;
2709
- logger_1.logger.info(` 📋 从 global/REQUIREMENT.md 读取到 ${parsed.length} 个功能模块(含涉及端)`);
3032
+ functionMapParsed = true;
3033
+ logger_1.logger.info(` 📋 从 global/FUNCTION_MAP.md 读取到 ${parsed.length} 个功能单元(严格按映射表拆分)`);
2710
3034
  }
2711
3035
  }
2712
- catch { }
3036
+ catch (e) {
3037
+ logger_1.logger.debug('解析 FUNCTION_MAP.md 失败:', e);
3038
+ }
2713
3039
  }
2714
- // 2. 回退:读取 features/*/README.md(无涉及端信息,使用全端)
2715
- if (!modulePlatformsParsed) {
2716
- const featuresDir = (0, path_1.join)(reqDir, 'features');
2717
- if (await (0, fs_extra_1.pathExists)(featuresDir)) {
3040
+ // 2. 回退:从 global/REQUIREMENT.md 读取功能模块清单(含涉及端)
3041
+ if (!functionMapParsed) {
3042
+ const globalReqPath = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'REQUIREMENT.md');
3043
+ let modulePlatformsParsed = false;
3044
+ if (await (0, fs_extra_1.pathExists)(globalReqPath)) {
2718
3045
  try {
2719
- const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
2720
- for (const entry of entries) {
2721
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
2722
- const readmePath = (0, path_1.join)(featuresDir, entry.name, 'README.md');
2723
- if (await (0, fs_extra_1.pathExists)(readmePath)) {
2724
- modules.push({
2725
- name: entry.name, slug: slugify(entry.name), type: 'feature',
2726
- sourceFile: `features/${entry.name}/README.md`,
2727
- platforms: [...allPlatforms],
2728
- });
2729
- }
3046
+ const content = await (0, fs_extra_1.readFile)(globalReqPath, 'utf-8');
3047
+ const parsed = parseModulePlatforms(content, allPlatforms);
3048
+ if (parsed.length > 0) {
3049
+ for (const m of parsed) {
3050
+ modules.push({
3051
+ name: m.name,
3052
+ slug: slugify(m.name),
3053
+ type: 'feature',
3054
+ sourceFile: 'global/REQUIREMENT.md',
3055
+ platforms: m.platforms,
3056
+ });
2730
3057
  }
3058
+ modulePlatformsParsed = true;
3059
+ logger_1.logger.info(` 📋 从 global/REQUIREMENT.md 读取到 ${parsed.length} 个功能模块(含涉及端)`);
2731
3060
  }
2732
3061
  }
2733
3062
  catch { }
2734
3063
  }
3064
+ // 3. 回退:读取 features/*/README.md(无涉及端信息,使用全端)
3065
+ if (!modulePlatformsParsed) {
3066
+ const featuresDir = (0, path_1.join)(reqDir, 'features');
3067
+ if (await (0, fs_extra_1.pathExists)(featuresDir)) {
3068
+ try {
3069
+ const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
3070
+ for (const entry of entries) {
3071
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
3072
+ const readmePath = (0, path_1.join)(featuresDir, entry.name, 'README.md');
3073
+ if (await (0, fs_extra_1.pathExists)(readmePath)) {
3074
+ modules.push({
3075
+ name: entry.name, slug: slugify(entry.name), type: 'feature',
3076
+ sourceFile: `features/${entry.name}/README.md`,
3077
+ platforms: [...allPlatforms],
3078
+ });
3079
+ }
3080
+ }
3081
+ }
3082
+ }
3083
+ catch { }
3084
+ }
3085
+ }
2735
3086
  }
2736
3087
  // 3. 读取类型文档(bugs/refactors/research)
2737
3088
  for (const typeDir of ['bugs', 'refactors', 'research']) {
@@ -2806,6 +3157,13 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
2806
3157
  section._owner = '未分配';
2807
3158
  section._taskId = taskId;
2808
3159
  section.functionalUnit = mod.name;
3160
+ // v6.70.0+: 传递 FUNCTION_MAP.md 中的扩展信息
3161
+ if (mod.sharedCapability)
3162
+ section._sharedCapability = mod.sharedCapability;
3163
+ if (mod.dependsOn)
3164
+ section._dependsOn = mod.dependsOn;
3165
+ if (mod.description)
3166
+ section._description = mod.description;
2809
3167
  await createTaskFromSection(iterationDir, taskId, section, modPlatforms, mod.type, []);
2810
3168
  createdSections.push(section);
2811
3169
  logger_1.logger.info(` ✅ 创建: ${taskId} [${mod.type}] — ${mod.name} (${modPlatforms.length} 个端: ${modPlatforms.join(', ')})`);