speccore 8.3.248 → 8.3.250

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":"AA0KA,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;AAmKD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAorCzF"}
1
+ {"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AA0KA,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;AAqLD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmsCzF"}
@@ -235,13 +235,33 @@ async function preSplitGate(iterationDir) {
235
235
  level: 'block',
236
236
  });
237
237
  // v8.3.221+: 检查全局 ANALYSIS.md(阻塞级——功能分析,骨架说明 analyze 未落盘)
238
+ // v8.3.248+: 模块级回退——v8.3.238 后 analyze 指示 AI 把 ANALYSIS.md 写在 {feature}/overview/ 下
239
+ // (全局 overview 只留索引类文档),若仍硬性要求全局文件,新流程将永远阻塞无法拆分。
240
+ // 全局缺失/骨架时,任一功能模块的 overview/ANALYSIS.md 非骨架即视为分析已落盘。
238
241
  const analysisGatePath = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR, 'ANALYSIS.md');
239
- const analysisGateExists = await (0, fs_extra_1.pathExists)(analysisGatePath);
242
+ let analysisGateExists = await (0, fs_extra_1.pathExists)(analysisGatePath);
240
243
  let analysisGateIsSkeleton = false;
241
244
  if (analysisGateExists) {
242
245
  const ac = await (0, fs_extra_1.readFile)(analysisGatePath, 'utf-8');
243
246
  analysisGateIsSkeleton = ac.includes('<!-- SPEC-SKELETON -->') || ac.trim().length < 100;
244
247
  }
248
+ if (!analysisGateExists || analysisGateIsSkeleton) {
249
+ try {
250
+ const fList = await (0, spec_paths_1.parseFeatureList)(iterationDir);
251
+ for (const f of fList) {
252
+ const fAnalysisPath = (0, path_1.join)(specDir, f, 'overview', 'ANALYSIS.md');
253
+ if (!(await (0, fs_extra_1.pathExists)(fAnalysisPath)))
254
+ continue;
255
+ const c = await (0, fs_extra_1.readFile)(fAnalysisPath, 'utf-8');
256
+ if (!c.includes('<!-- SPEC-SKELETON -->') && c.trim().length >= 100) {
257
+ analysisGateExists = true;
258
+ analysisGateIsSkeleton = false;
259
+ break;
260
+ }
261
+ }
262
+ }
263
+ catch { /* 回退失败保持原判定 */ }
264
+ }
245
265
  items.push({
246
266
  path: analysisGatePath,
247
267
  name: 'ANALYSIS.md',
@@ -1109,6 +1129,9 @@ async function iterationSplitCommand(options) {
1109
1129
  }
1110
1130
  }
1111
1131
  // ── 1. 检查 ANALYSIS.md + AI 智能拆分建议 ──
1132
+ // v8.3.249+: reqContent2/reqContent2Aggregated 提升到块外声明,供下方实际拆分源复用
1133
+ let reqContent2 = '';
1134
+ let reqContent2Aggregated = false;
1112
1135
  const analysisPathNew = (0, path_1.join)(iterationDir, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'ANALYSIS.md');
1113
1136
  const analysisPathOld = (0, path_1.join)(iterationDir, '020-specs', 'ANALYSIS.md');
1114
1137
  const analysisPath = await (0, fs_extra_1.pathExists)(analysisPathNew) ? analysisPathNew : analysisPathOld;
@@ -1147,12 +1170,24 @@ async function iterationSplitCommand(options) {
1147
1170
  const specDir2 = (0, path_1.join)(iterationDir, '020-specs');
1148
1171
  // REQUIREMENT.md 从 overview/ 读取
1149
1172
  const reqPath2 = (0, path_1.join)(specDir2, spec_paths_1.GLOBAL_SPECS_DIR, 'REQUIREMENT.md');
1150
- let reqContent2 = '';
1151
1173
  if (await (0, fs_extra_1.pathExists)(reqPath2)) {
1152
1174
  reqContent2 = await (0, fs_extra_1.readFile)(reqPath2, 'utf-8');
1153
1175
  }
1154
1176
  // v8.3.160+: 如果全局 REQUIREMENT.md 为空/骨架,从各功能模块 overview/REQUIREMENT.md 聚合
1155
- if (!reqContent2 || reqContent2.trim().length < 200 || reqContent2.includes('<!-- SPEC-SKELETON -->')) {
1177
+ // v8.3.249+: 增加陈旧检测——聚合文档不含当前 features 单元名时视为过期,强制重新聚合。
1178
+ // 此前仅按空/骨架判断,旧聚合文档与新切分单元不同步时 taskify 用陈旧数据拆分,
1179
+ // 漏掉新单元(--modules 指定新单元也匹配不到任何 section,反而全量拆分)。
1180
+ let reqContent2IsStale = false;
1181
+ if (reqContent2 && reqContent2.trim().length >= 200 && !reqContent2.includes('<!-- SPEC-SKELETON -->')) {
1182
+ const featuresAll = await (0, spec_paths_1.parseFeatureList)(iterationDir);
1183
+ for (const feature of featuresAll) {
1184
+ if (!reqContent2.includes(feature)) {
1185
+ reqContent2IsStale = true;
1186
+ break;
1187
+ }
1188
+ }
1189
+ }
1190
+ if (!reqContent2 || reqContent2.trim().length < 200 || reqContent2.includes('<!-- SPEC-SKELETON -->') || reqContent2IsStale) {
1156
1191
  const featureReqParts = [];
1157
1192
  const features = await (0, spec_paths_1.parseFeatureList)(iterationDir);
1158
1193
  for (const feature of features) {
@@ -1166,6 +1201,7 @@ async function iterationSplitCommand(options) {
1166
1201
  }
1167
1202
  if (featureReqParts.length > 0) {
1168
1203
  reqContent2 = featureReqParts.join('\n\n---\n\n');
1204
+ reqContent2Aggregated = true;
1169
1205
  }
1170
1206
  }
1171
1207
  const specContents = [];
@@ -1253,7 +1289,9 @@ async function iterationSplitCommand(options) {
1253
1289
  return;
1254
1290
  }
1255
1291
  const content = await (0, fs_extra_1.readFile)(reqFile, 'utf-8');
1256
- let sections = extractSections(content, options.sections);
1292
+ // v8.3.249+: 聚合后的最新需求内容(含新切分单元)优先于陈旧聚合文档作为实际拆分源
1293
+ const finalContent = (reqContent2Aggregated && reqContent2) ? reqContent2 : content;
1294
+ let sections = extractSections(finalContent, options.sections);
1257
1295
  // v6.76.0+: 按功能模块过滤
1258
1296
  if (options.modules) {
1259
1297
  const moduleFilters = options.modules.split(',').map(m => m.trim()).filter(Boolean);
@@ -4378,7 +4416,9 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
4378
4416
  catch { }
4379
4417
  }
4380
4418
  }
4381
- // 3. 回退:读取 features/*/SOURCE.md(v8.3.226+,兼容旧 README.md)(无涉及端信息,使用全端)
4419
+ // 3. 回退:读取 features/*/SOURCE.md(v8.3.226+,兼容旧 README.md)
4420
+ // v8.3.249+: 优先读 _matrix.md 端声明(unitize 切分时写入),替代全端兜底——
4421
+ // 此前全部配全端,单端功能(如仅 booking-service)也会拆出 4 个子任务
4382
4422
  if (!modulePlatformsParsed) {
4383
4423
  const featuresDir = (0, path_1.join)(reqDir, 'features');
4384
4424
  if (await (0, fs_extra_1.pathExists)(featuresDir)) {
@@ -4388,10 +4428,23 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
4388
4428
  if (entry.isDirectory() && !entry.name.startsWith('.')) {
4389
4429
  const readmePath = await (0, feature_source_1.resolveUnitSourcePath)((0, path_1.join)(featuresDir, entry.name));
4390
4430
  if (await (0, fs_extra_1.pathExists)(readmePath)) {
4431
+ let unitPlatforms = [...allPlatforms];
4432
+ const matrixPath = (0, path_1.join)(featuresDir, entry.name, '_matrix.md');
4433
+ if (await (0, fs_extra_1.pathExists)(matrixPath)) {
4434
+ const mc = await (0, fs_extra_1.readFile)(matrixPath, 'utf-8');
4435
+ const m = mc.match(/^platforms:\s*\n((?:\s+-\s+\S+\s*\n?)+)/m);
4436
+ if (m) {
4437
+ const declared = m[1].split('\n')
4438
+ .map(l => l.replace(/^\s+-\s+/, '').trim()).filter(Boolean);
4439
+ if (declared.length > 0) {
4440
+ unitPlatforms = declared.filter(p => allPlatforms.includes(p));
4441
+ }
4442
+ }
4443
+ }
4391
4444
  modules.push({
4392
4445
  name: entry.name, slug: slugify(entry.name), type: 'feature',
4393
4446
  sourceFile: `features/${entry.name}/${(0, path_1.basename)(readmePath)}`,
4394
- platforms: [...allPlatforms],
4447
+ platforms: unitPlatforms,
4395
4448
  });
4396
4449
  }
4397
4450
  }
@@ -4425,6 +4478,20 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
4425
4478
  if (modules.length === 0) {
4426
4479
  return false; // 无功能模块,回退到传统流程
4427
4480
  }
4481
+ // v8.3.249+: --modules 过滤——模块驱动拆分此前完全忽略该选项,
4482
+ // 指定单个单元时仍全量拆分(FUNCTION_MAP 全部单元照拆)
4483
+ if (options.modules) {
4484
+ const moduleFilters = options.modules.split(',').map(m => m.trim()).filter(Boolean);
4485
+ const beforeCount = modules.length;
4486
+ const filtered = modules.filter(mod => moduleFilters.some(f => mod.name.includes(f) || mod.slug.includes(f)));
4487
+ if (filtered.length === 0) {
4488
+ logger_1.logger.warn(`没有匹配 "${options.modules}" 的功能模块(可用: ${modules.slice(0, 5).map(m => m.name).join(', ')}${modules.length > 5 ? '...' : ''}),回退传统流程`);
4489
+ return false;
4490
+ }
4491
+ modules.length = 0;
4492
+ modules.push(...filtered);
4493
+ logger_1.logger.info(` 🎯 模块过滤: ${beforeCount} → ${filtered.length}(只拆分: ${moduleFilters.join(', ')})`);
4494
+ }
4428
4495
  // 检测已有任务
4429
4496
  const existingTasks = await detectExistingTasks(iterationDir);
4430
4497
  if (existingTasks.length > 0 && !options.force && !options.prune) {
@@ -4481,9 +4548,22 @@ async function tryModuleDrivenSplit(iteration, iterationDir, options) {
4481
4548
  for (const mod of modules) {
4482
4549
  const { id: taskId } = await (0, global_counters_1.nextTaskId)(mod.name, mod.slug);
4483
4550
  const modPlatforms = mod.platforms.length > 0 ? mod.platforms : [...allPlatforms];
4551
+ // v8.3.249+: section.content 从 sourceFile(SOURCE.md 等)读取——此前硬编码空串,
4552
+ // 导致 REQ.md 四级回退全部落空(aiReq/analyzeReq/短 section.content 均不满足),
4553
+ // 最终写出「待补充」骨架,execute 阶段拿不到有价值的需求文档
4554
+ let sectionContent = '';
4555
+ if (mod.sourceFile) {
4556
+ try {
4557
+ const srcPath = (0, path_1.join)(iterationDir, '010-requirements', mod.sourceFile);
4558
+ if (await (0, fs_extra_1.pathExists)(srcPath)) {
4559
+ sectionContent = await (0, fs_extra_1.readFile)(srcPath, 'utf-8');
4560
+ }
4561
+ }
4562
+ catch { /* 读取失败保持空 */ }
4563
+ }
4484
4564
  const section = {
4485
4565
  name: mod.name,
4486
- content: '',
4566
+ content: sectionContent,
4487
4567
  level: 2,
4488
4568
  };
4489
4569
  section._topic = mod.slug;