speccore 8.3.3 → 8.3.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAsIA,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"}
1
+ {"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAuIA,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;AAoCD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+0BzF"}
@@ -51,6 +51,7 @@ const questions_1 = require("../../core/questions");
51
51
  const pipeline_engine_1 = require("../../core/pipeline-engine");
52
52
  const code_scanner_1 = require("../../core/code-scanner");
53
53
  const knowledge_graph_1 = require("../../core/knowledge-graph");
54
+ const git_integration_1 = require("../../core/git-integration");
54
55
  /**
55
56
  * 将 AI 返回的 scope 简写映射到 CONSTITUTION.md 标准端名
56
57
  * v6.69.3+: 解决中文简写(如 "后端"、"admin")导致目录名错误的问题
@@ -162,16 +163,28 @@ async function detectPlatforms(iterationDir, specified) {
162
163
  const platforms = await (0, spec_paths_1.parsePlatformList)();
163
164
  if (platforms.length > 0)
164
165
  return platforms;
165
- // 2. 回退:扫描 020-specs/ 子目录(排除 global/ 等非端目录)
166
+ // 2. 回退:扫描 020-specs/ 子目录(排除 global/ 等非端目录 + 常见 AI 简写垃圾目录)
166
167
  const specsDir = (0, path_1.join)(iterationDir, '020-specs');
167
168
  if (await (0, fs_extra_1.pathExists)(specsDir)) {
168
169
  const entries = await (0, fs_extra_1.readdir)(specsDir, { withFileTypes: true });
169
- const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', spec_paths_1.GLOBAL_SPECS_DIR]);
170
- const platforms = entries
170
+ // v8.3.4+: 增加常见 AI 简写过滤,避免 api/web 等错误目录被当成端名
171
+ const knownNonPlatformDirs = new Set([
172
+ 'sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research',
173
+ 'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR,
174
+ // 常见 AI 简写垃圾目录(必须从标准端名映射,不能直接用)
175
+ 'api', 'web', 'backend', 'frontend', 'server', 'mobile', 'admin', 'h5', 'pc', 'app',
176
+ ]);
177
+ const rawPlatforms = entries
171
178
  .filter((e) => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name))
172
179
  .map((e) => e.name);
173
- if (platforms.length > 0)
174
- return platforms;
180
+ // 二次过滤:用 normalizeScopePlatforms 排除任何残留的非标准端名
181
+ if (rawPlatforms.length > 0) {
182
+ const standardPlatforms = await (0, spec_paths_1.parsePlatformList)();
183
+ if (standardPlatforms.length > 0) {
184
+ return normalizeScopePlatforms(rawPlatforms, standardPlatforms);
185
+ }
186
+ return rawPlatforms;
187
+ }
175
188
  }
176
189
  return ['web']; // 默认
177
190
  }
@@ -278,15 +291,15 @@ async function iterationSplitCommand(options) {
278
291
  return;
279
292
  }
280
293
  // 非 Pipeline 模式的 Response 处理
281
- const iterDir = (0, path_1.join)('Iteration-' + iter, '030-tasks');
294
+ // v8.3.4+ 修复:使用 getIterationDir 解析正确路径,避免 Iteration- 前缀重复
295
+ const iterDirFull = await (0, context_1.getIterationDir)(iter);
296
+ const iterDir = (0, path_1.join)(iterDirFull, '030-tasks');
282
297
  await (0, fs_extra_1.ensureDir)(iterDir);
283
298
  const backups = [];
284
299
  // 尝试解析 AI 返回的 JSON Task 列表
285
300
  try {
286
301
  const tasks = JSON.parse(options.response);
287
302
  if (Array.isArray(tasks)) {
288
- // 获取迭代根目录(createTaskFromSection 需要迭代根路径)
289
- const iterDirFull = await (0, context_1.getIterationDir)(iter);
290
303
  const allPlatforms = await detectPlatforms(iterDirFull);
291
304
  const sections = [];
292
305
  for (let i = 0; i < tasks.length; i++) {
@@ -351,6 +364,57 @@ async function iterationSplitCommand(options) {
351
364
  section._scopePlatforms = taskScopePlatforms;
352
365
  sections.push(section);
353
366
  }
367
+ // v8.3.4+: 校验所有任务的 scope 是否只含标准端名
368
+ const invalidScopes = [];
369
+ for (const sec of sections) {
370
+ const scopes = sec._scopePlatforms || [];
371
+ for (const s of scopes) {
372
+ if (!allPlatforms.includes(s))
373
+ invalidScopes.push(`任务 "${sec.name}" 的 scope "${s}"`);
374
+ }
375
+ }
376
+ if (invalidScopes.length > 0) {
377
+ logger_1.logger.error(`❌ 发现 ${invalidScopes.length} 个非标准端名,拆分被拒绝:`);
378
+ for (const msg of invalidScopes)
379
+ logger_1.logger.error(` ${msg}`);
380
+ logger_1.logger.error(` 标准端名: ${allPlatforms.join(', ')}`);
381
+ logger_1.logger.info(` 💡 请告诉 AI:scope 必须使用 CONSTITUTION.md 标准端名,禁止 api/web/backend 等简写`);
382
+ return;
383
+ }
384
+ // v8.3.4+: 功能点覆盖校验 — 对比 FUNCTION_MAP.md 检查是否有遗漏
385
+ const funcMapPath = (0, path_1.join)(iterDirFull, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'FUNCTION_MAP.md');
386
+ if (await (0, fs_extra_1.pathExists)(funcMapPath)) {
387
+ try {
388
+ const fmContent = await (0, fs_extra_1.readFile)(funcMapPath, 'utf-8');
389
+ const funcUnits = parseFunctionMap(fmContent, allPlatforms);
390
+ if (funcUnits.length > 0) {
391
+ const aiUnits = new Set();
392
+ for (const t of tasks) {
393
+ const unit = t.functionalUnit || t.name;
394
+ if (unit)
395
+ aiUnits.add(unit.trim());
396
+ }
397
+ const missingUnits = [];
398
+ for (const u of funcUnits) {
399
+ // 模糊匹配:如果 AI 任务中没有功能单元名包含或被包含于 FUNCTION_MAP 中的名称
400
+ const matched = [...aiUnits].some(aiu => aiu.toLowerCase().includes(u.name.toLowerCase()) ||
401
+ u.name.toLowerCase().includes(aiu.toLowerCase()));
402
+ if (!matched)
403
+ missingUnits.push(u.name);
404
+ }
405
+ if (missingUnits.length > 0) {
406
+ logger_1.logger.warn(`\n ⚠️ 功能点覆盖警告:${missingUnits.length} 个功能单元可能被遗漏`);
407
+ for (const mu of missingUnits.slice(0, 5))
408
+ logger_1.logger.warn(` 📌 ${mu}`);
409
+ if (missingUnits.length > 5)
410
+ logger_1.logger.warn(` ... 还有 ${missingUnits.length - 5} 个`);
411
+ logger_1.logger.warn(` 💡 建议检查:这些功能点是否已合并到其他任务中?`);
412
+ logger_1.logger.warn(` 💡 如确需补充,重新执行 split 并告诉 AI:"补充以下功能点: ${missingUnits.slice(0, 3).join(', ')}"`);
413
+ }
414
+ }
415
+ }
416
+ catch { }
417
+ }
354
418
  // 检测已有任务 + 冲突处理
355
419
  const existingTasks = await detectExistingTasks(iterDirFull);
356
420
  if (existingTasks.length > 0 && !options.force) {
@@ -1029,9 +1093,50 @@ function filterTemplateNoise(sections) {
1029
1093
  return true;
1030
1094
  });
1031
1095
  }
1096
+ // v8.3.4+: 从迭代目录路径提取迭代名(如 Iteration-011-meeting-upgrade → 011-meeting-upgrade)
1097
+ function extractIterationName(iterationDir) {
1098
+ const base = iterationDir.split('/').pop() || '';
1099
+ return base.replace(/^Iteration-/, '');
1100
+ }
1101
+ /** 生成带实际配置值的 git-config 内容 */
1102
+ function buildGitConfigContent(config, platformLabel, taskType) {
1103
+ const branchType = taskType === 'bugfix' ? 'bugfix' : taskType === 'refactor' ? 'refactor' : taskType === 'research' ? 'research' : 'feature';
1104
+ const exampleName = 'example-task';
1105
+ const exampleHash = 'a1b2';
1106
+ const prefix = config.branchPrefix ? `${config.branchPrefix}-` : '';
1107
+ return `# 子任务级 Git 配置(${platformLabel})
1108
+ # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
1109
+ # 当前值已从迭代级/全局级配置自动填充,可直接使用或按需修改。
1110
+
1111
+ # === 当前生效配置 ===
1112
+ 源分支: ${config.defaultBranch}
1113
+ 分支前缀: ${config.branchPrefix || '(空)'}
1114
+ 分支格式: ${config.branchFormat}
1115
+ 自动拉取: ${config.autoPull ? 'true' : 'false'}
1116
+ 远程名称: ${config.remoteName}
1117
+
1118
+ # === 保护分支(禁止直接推送)===
1119
+ ${config.protectedBranches.map(b => `- ${b}`).join('\n')}
1120
+
1121
+ # === 分支命名示例 ===
1122
+ # 当前任务类型: ${branchType}
1123
+ # 命名格式: ${config.branchFormat}
1124
+ # 示例分支: ${branchType}/${prefix}${exampleName}-${exampleHash}
1125
+ # ({hash4} 为自动生成的 4 位随机字符)
1126
+
1127
+ # === 自定义配置区 ===
1128
+ # 如需覆盖上述值,取消下方注释并修改:
1129
+ # 源分支: ${config.defaultBranch}
1130
+ # 分支前缀:
1131
+ # 分支格式: {type}/{prefix}{name}-{hash4}
1132
+ # 自动拉取: false
1133
+ # 远程名称: origin
1134
+ `;
1135
+ }
1032
1136
  async function createTaskFromSection(iterationDir, taskId, section, allPlatforms, taskType = 'feature', allSections) {
1033
1137
  // taskId 已含 slug(nextTaskId 返回 Task-NNN-slug),直接用
1034
1138
  const taskDir = (0, path_1.join)(iterationDir, '030-tasks', taskType, taskId);
1139
+ const iterationName = extractIterationName(iterationDir);
1035
1140
  // 确定任务涉及的端:优先使用 AI 标注的 _scopePlatforms,否则从 020-specs/{端}/TECH.md 是否存在且有内容来推断
1036
1141
  let taskPlatforms;
1037
1142
  if (section._scopePlatforms && section._scopePlatforms.length > 0) {
@@ -1364,17 +1469,9 @@ ${section.content}
1364
1469
  // src/ + tests/ 目录(AI 执行时代码输出位置)
1365
1470
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'src'));
1366
1471
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'tests'));
1367
- // git-config
1368
- await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'git-config'), `# 子任务级 Git 配置(${platformLabel})
1369
- # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
1370
- # 修改时去掉注释符 #,填入具体值即可。
1371
-
1372
- # 源分支: 继承迭代配置
1373
- # 分支前缀: 继承迭代配置
1374
- # 分支格式: 继承迭代配置
1375
- # 自动拉取: 继承迭代配置
1376
- # 远程名称: 继承迭代配置
1377
- `);
1472
+ // v8.3.4+: git-config 自动填充 — 读取迭代级/全局级实际配置值写入
1473
+ const gitConfig = (0, git_integration_1.loadGitConfig)(iterationName);
1474
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'git-config'), buildGitConfigContent(gitConfig, platformLabel, section.type));
1378
1475
  // v6.70.0+: 从 section 提取接口/页面清单用于 TASK.md
1379
1476
  const apiLines = section.content.split('\n').filter(l => l.includes('| GET') || l.includes('| POST') || l.includes('| PUT') || l.includes('| DELETE') || l.includes('| PATCH'));
1380
1477
  const apiList = apiLines.length > 0
@@ -1536,9 +1633,9 @@ ${isBk ? apiList : pageList}
1536
1633
  // 功能单元标识(v6.49.2+)
1537
1634
  const featureName = section.functionalUnit || section.name || '未分类';
1538
1635
  await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'feature'), featureName);
1539
- await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'git-config'), `# 子任务级 Git 配置(后端)
1540
- # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
1541
- `);
1636
+ // v8.3.4+: git-config 自动填充
1637
+ const gitConfig = (0, git_integration_1.loadGitConfig)(iterationName);
1638
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'git-config'), buildGitConfigContent(gitConfig, '后端', section.type));
1542
1639
  await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'TASK.md'), `# ${section.name} — ${fallbackBackend}(自动补充)
1543
1640
 
1544
1641
  ## 子任务信息
@@ -2261,7 +2358,8 @@ async function loadSpecContents(iterationDir) {
2261
2358
  }
2262
2359
  // 2. 读取各端子目录文档(如 admin/TECH.md、h5/TECH.md 等)
2263
2360
  const entries = await (0, fs_extra_1.readdir)(specDir, { withFileTypes: true });
2264
- const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', spec_paths_1.GLOBAL_SPECS_DIR]);
2361
+ // v8.3.4+: 增加 overview 过滤,防止 analyze 全局文档目录被误当成端目录
2362
+ const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR]);
2265
2363
  for (const e of entries) {
2266
2364
  if (e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name)) {
2267
2365
  const platform = e.name;
@@ -2710,6 +2808,12 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
2710
2808
  p += `> 同一模块/领域的任务填相同的值,用于粒度校验和任务分组\n`;
2711
2809
  p += `> **topic** 必须是英文短横线格式(如 \`user-authentication\`、\`product-crud\`),用于生成任务目录名 Task-NNN-{topic}\n`;
2712
2810
  p += `> **sourceFile** 必须填写:该任务对应的 020-specs 源文档路径(如 \`bugs/login-timeout.md\`、\`features/user-auth.md\`、\`refactors/db-pool.md\`),用于在 CONTEXT.md 中生成来源追溯\n`;
2811
+ p += `> **scope 强制约束(违反则拆分无效)**:\n`;
2812
+ p += `> - scope 数组中的每个值必须是上面「项目端列表」中的标准端名之一\n`;
2813
+ p += `> - ⛔ 禁止出现 api、web、backend、frontend、server、admin、h5、mobile、pc、app 等非标准名称\n`;
2814
+ p += `> - ⛔ 禁止出现中文端名(如"后端"、"前端"、"移动端")\n`;
2815
+ p += `> - 每个任务按 scope 中的端创建子任务目录,目录名 = 标准端名\n`;
2816
+ p += `> - 如果 scope 含非标准端名,整个 JSON 视为不合格,必须重新生成\n`;
2713
2817
  p += `> **reqContent 质量要求(必填,禁止模板化)**:\n`;
2714
2818
  p += `> - 必须是**具体的、可执行的需求描述**,不是"待补充"或"参考全局文档"\n`;
2715
2819
  p += `> - 包含:业务规则(含边界条件)、数据模型(字段/类型/约束)、接口清单(方法/路径/参数/响应)\n`;
@@ -2721,6 +2825,13 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
2721
2825
  p += `> - 从 020-specs/overview/TECH.md 和对应端 TECH.md 中提取本任务相关的技术细节\n`;
2722
2826
  p += `> - 直接写入 00-specs/TECH.md,执行时 AI 据此直接开发\n`;
2723
2827
  p += `> **质量红线**:如果 reqContent/techContent 只有标题和占位符(如 "<!-- AI-FILL -->"),视为不合格,必须重新生成\n\n`;
2828
+ // v8.3.4+: 强制约束专节(类似 analyze Phase 2)
2829
+ p += `### ⛔ 强制约束(违反则拆分无效)\n\n`;
2830
+ p += `- **禁止跳过任何功能模块** — 即使某个模块信息不足,也必须基于已有信息拆分并填充 reqContent/techContent,标注「基于现有信息推断」而非留空\n`;
2831
+ p += `- **禁止输出空内容** — reqContent、techContent、devGuideContent 每个字段长度必须 ≥200 字符,不允许写「待补充」「TODO」「参考全局文档」\n`;
2832
+ p += `- **禁止省略字段** — JSON 中必须包含 reqContent、techContent、devGuideContent 三个字段,缺一不可\n`;
2833
+ p += `- **内容必须从 analyze 产物提取** — reqContent 从 REQUIREMENT.md 提取,techContent 从 TECH.md 提取,devGuideContent 从 DEV_GUIDE.md 提取\n`;
2834
+ p += `- **如果 token 不足**:优先保证 reqContent 完整,其次是 techContent,最后是 devGuideContent\n\n`;
2724
2835
  p += `### ⚠️ 拆分规则(重要)\n\n`;
2725
2836
  p += `- **按功能单元拆分,不按技术层拆分**:每个任务必须对应一个业务功能(如「预订管理」「签到」「审批」),不要按技术层分组(如「后端改进」「前端修复」)\n`;
2726
2837
  p += `- ❌ 错误示例:Task-129-backend-improve、Task-130-admin-fix(按技术层分组)\n`;
@@ -2754,7 +2865,7 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
2754
2865
  - 你的判断
2755
2866
  - 建议后续动作\`
2756
2867
  `;
2757
- p += `3. **遇阻断就跳过**如果某个功能模块信息不足无法拆分,跳过它并在疑问清单中记录\n`;
2868
+ p += `3. **禁止跳过任何模块**即使某个功能模块信息不足,也必须基于已有信息拆分并填充内容,宁可标注「基于现有信息推断:需要补充 xxx」也不许留空或省略\n`;
2758
2869
  p += `4. **输出 JSON** — 直接输出拆分结果的 JSON 数组,不要输出其他内容\n`;
2759
2870
  // 持久指令(用户调整时 AI 可回读此文件)
2760
2871
  p += `\n## 🔄 调整指令(持久有效)\n\n`;