speccore 8.3.20 → 8.3.21

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.
@@ -164,35 +164,10 @@ async function detectPlatforms(iterationDir, specified) {
164
164
  const platforms = await (0, spec_paths_1.parsePlatformList)();
165
165
  if (platforms.length > 0)
166
166
  return platforms;
167
- // 2. 回退:扫描 020-specs/ 子目录(仅旧结构下有效)
168
- // v8.3.17+: 新结构下 020-specs/ 子目录是功能模块名,不能当成端名扫描
167
+ // v8.3.21+: 020-specs/ 按功能模块组织,不再扫描子目录猜测端名
169
168
  const specsDir = (0, path_1.join)(iterationDir, '020-specs');
170
169
  if (await (0, fs_extra_1.pathExists)(specsDir)) {
171
- const isLegacy = await (0, spec_paths_1.isLegacySpecDir)(specsDir);
172
- if (isLegacy) {
173
- // 旧结构:子目录是端名,可以扫描
174
- const entries = await (0, fs_extra_1.readdir)(specsDir, { withFileTypes: true });
175
- // v8.3.4+: 增加常见 AI 简写过滤,避免 api/web 等错误目录被当成端名
176
- const knownNonPlatformDirs = new Set([
177
- 'sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research',
178
- 'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR,
179
- // 常见 AI 简写垃圾目录(必须从标准端名映射,不能直接用)
180
- 'api', 'web', 'backend', 'frontend', 'server', 'mobile', 'admin', 'h5', 'pc', 'app',
181
- ]);
182
- const rawPlatforms = entries
183
- .filter((e) => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name))
184
- .map((e) => e.name);
185
- // 二次过滤:用 normalizeScopePlatforms 排除任何残留的非标准端名
186
- if (rawPlatforms.length > 0) {
187
- const standardPlatforms = await (0, spec_paths_1.parsePlatformList)();
188
- if (standardPlatforms.length > 0) {
189
- return normalizeScopePlatforms(rawPlatforms, standardPlatforms);
190
- }
191
- return rawPlatforms;
192
- }
193
- }
194
- // 新结构:跳过扫描,提示用户配置端列表
195
- logger_1.logger.warn(`⚠️ CONSTITUTION.md 中未找到「端列表」,且 020-specs/ 为新结构(按功能模块组织)`);
170
+ logger_1.logger.warn(`⚠️ CONSTITUTION.md 中未找到「端列表」`);
196
171
  logger_1.logger.warn(` 请先在 CONSTITUTION.md 中配置端列表,否则无法正确拆分任务`);
197
172
  }
198
173
  return ['web']; // 默认
@@ -234,28 +209,25 @@ async function preSplitGate(iterationDir) {
234
209
  isSkeleton: reqIsSkeleton,
235
210
  level: 'warn',
236
211
  });
237
- // 检查功能模块级文档(警告级,仅新结构下)
238
- const isLegacy = await (0, spec_paths_1.isLegacySpecDir)(specDir);
239
- if (!isLegacy) {
240
- const features = await (0, spec_paths_1.parseFeatureList)(iterationDir);
241
- for (const feature of features) {
242
- const featureReqPath = (0, path_1.join)(specDir, feature, 'overview', 'REQUIREMENT.md');
243
- const featureReqExists = await (0, fs_extra_1.pathExists)(featureReqPath);
244
- let featureReqIsSkeleton = false;
245
- if (featureReqExists) {
246
- const content = await (0, fs_extra_1.readFile)(featureReqPath, 'utf-8');
247
- featureReqIsSkeleton = content.includes('<!-- SPEC-SKELETON -->') || content.trim().length < 100;
248
- }
249
- items.push({
250
- path: featureReqPath,
251
- name: `${feature}/overview/REQUIREMENT.md`,
252
- purpose: `功能模块「${feature}」的完整需求(合并各端视角)`,
253
- impact: `缺失时,execute 阶段 AI 读不到「${feature}」的详细需求,可能实现不完整`,
254
- exists: featureReqExists,
255
- isSkeleton: featureReqIsSkeleton,
256
- level: 'warn',
257
- });
258
- }
212
+ // 检查功能模块级文档(警告级)
213
+ const features = await (0, spec_paths_1.parseFeatureList)(iterationDir);
214
+ for (const feature of features) {
215
+ const featureReqPath = (0, path_1.join)(specDir, feature, 'overview', 'REQUIREMENT.md');
216
+ const featureReqExists = await (0, fs_extra_1.pathExists)(featureReqPath);
217
+ let featureReqIsSkeleton = false;
218
+ if (featureReqExists) {
219
+ const content = await (0, fs_extra_1.readFile)(featureReqPath, 'utf-8');
220
+ featureReqIsSkeleton = content.includes('<!-- SPEC-SKELETON -->') || content.trim().length < 100;
221
+ }
222
+ items.push({
223
+ path: featureReqPath,
224
+ name: `${feature}/overview/REQUIREMENT.md`,
225
+ purpose: `功能模块「${feature}」的完整需求(合并各端视角)`,
226
+ impact: `缺失时,execute 阶段 AI 读不到「${feature}」的详细需求,可能实现不完整`,
227
+ exists: featureReqExists,
228
+ isSkeleton: featureReqIsSkeleton,
229
+ level: 'warn',
230
+ });
259
231
  }
260
232
  const blockers = items.filter(i => i.level === 'block' && (!i.exists || i.isSkeleton));
261
233
  const warnings = items.filter(i => i.level === 'warn' && (!i.exists || i.isSkeleton));
@@ -814,77 +786,46 @@ async function iterationSplitCommand(options) {
814
786
  }
815
787
  // 读取 020-specs/ 全部文件(完整上下文)
816
788
  const specDir2 = (0, path_1.join)(iterationDir, '020-specs');
817
- // REQUIREMENT.md 优先从 global/ 读取(v6.41.0+),回退根目录
818
- const reqPath2 = await (0, spec_paths_1.resolveGlobalSpecPath)(specDir2, 'REQUIREMENT.md') || (0, path_1.join)(iterationDir, '020-specs', 'REQUIREMENT.md');
789
+ // REQUIREMENT.md overview/ 读取
790
+ const reqPath2 = (0, path_1.join)(specDir2, spec_paths_1.GLOBAL_SPECS_DIR, 'REQUIREMENT.md');
819
791
  let reqContent2 = '';
820
792
  if (await (0, fs_extra_1.pathExists)(reqPath2)) {
821
793
  reqContent2 = await (0, fs_extra_1.readFile)(reqPath2, 'utf-8');
822
794
  }
823
795
  const specContents = [];
824
- // 读取全局层文档(优先 global/ 子目录,回退根目录 — v6.41.0+ 向后兼容)
825
- for (const f of ['ANALYSIS.md', 'TECH.md', 'RISK.md', 'DEPS.md', 'REVIEW.md', 'MONITOR.md', 'REQUIREMENT.md']) {
826
- const resolved = await (0, spec_paths_1.resolveGlobalSpecPath)(specDir2, f);
827
- if (resolved) {
828
- const content = await (0, fs_extra_1.readFile)(resolved, 'utf-8');
829
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
830
- specContents.push({ name: f, content });
831
- }
832
- }
833
- }
834
- // 根目录下的 TEST.md、UI_SPEC.md(端无关模板/回退)
835
- for (const f of ['TEST.md', 'UI_SPEC.md']) {
836
- const fp = (0, path_1.join)(specDir2, f);
837
- if (await (0, fs_extra_1.pathExists)(fp)) {
796
+ // v8.3.21+: 读取全局层文档(overview/ 下)
797
+ const overviewDir = (0, path_1.join)(specDir2, spec_paths_1.GLOBAL_SPECS_DIR);
798
+ if (await (0, fs_extra_1.pathExists)(overviewDir)) {
799
+ const overviewFiles = await (0, fs_extra_1.readdir)(overviewDir, { withFileTypes: true });
800
+ for (const f of overviewFiles) {
801
+ if (!f.name.endsWith('.md'))
802
+ continue;
803
+ const fp = (0, path_1.join)(overviewDir, f.name);
838
804
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
839
805
  if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
840
- specContents.push({ name: f, content });
841
- }
842
- }
843
- }
844
- // 读取各端详情(020-specs/{端}/,兼容旧路径 020-specs/platforms/{端}/)
845
- const platformDirs = [];
846
- // 新路径:020-specs/{端}/
847
- const directPlatformEntries = await (0, fs_extra_1.readdir)(specDir2, { withFileTypes: true });
848
- for (const e of directPlatformEntries) {
849
- if (e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.')
850
- && !['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', spec_paths_1.GLOBAL_SPECS_DIR].includes(e.name)) {
851
- platformDirs.push(e.name);
852
- }
853
- }
854
- // 旧路径回退:020-specs/platforms/{端}/
855
- const iterPlatformsDir = (0, path_1.join)(specDir2, 'platforms');
856
- if (await (0, fs_extra_1.pathExists)(iterPlatformsDir)) {
857
- const platformEntries = await (0, fs_extra_1.readdir)(iterPlatformsDir, { withFileTypes: true });
858
- for (const pe of platformEntries) {
859
- if (pe.isDirectory() && !pe.name.startsWith('.') && !platformDirs.includes(pe.name)) {
860
- platformDirs.push(pe.name);
806
+ specContents.push({ name: f.name, content });
861
807
  }
862
808
  }
863
809
  }
864
- // 读取每个端目录下的文件
865
- for (const pName of platformDirs) {
866
- const pDir = (0, path_1.join)(specDir2, pName);
867
- if (!(await (0, fs_extra_1.pathExists)(pDir)))
810
+ // v8.3.21+: 读取各功能模块下的文档(overview/ + 各端/)
811
+ const features = await (0, spec_paths_1.parseFeatureList)(iterationDir);
812
+ for (const feature of features) {
813
+ const featureDir = (0, path_1.join)(specDir2, feature);
814
+ if (!await (0, fs_extra_1.pathExists)(featureDir))
868
815
  continue;
869
- const pFiles = await (0, fs_extra_1.readdir)(pDir);
870
- for (const pf of pFiles.filter((f) => f.endsWith('.md'))) {
871
- const fp = (0, path_1.join)(pDir, pf);
872
- const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
873
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*\u5f85\u586b\u5145|^<!--\s*AI-FILL/m)) {
874
- specContents.push({ name: `${pName}/${pf}`, content });
875
- }
876
- }
877
- }
878
- // 读取功能模块分析(020-specs/features/)
879
- const iterFeaturesDir = (0, path_1.join)(specDir2, 'features');
880
- if (await (0, fs_extra_1.pathExists)(iterFeaturesDir)) {
881
- const featureEntries = await (0, fs_extra_1.readdir)(iterFeaturesDir, { withFileTypes: true });
882
- for (const fe of featureEntries) {
883
- if (!fe.name.startsWith('.') && fe.name.endsWith('.md')) {
884
- const fp = (0, path_1.join)(iterFeaturesDir, fe.name);
816
+ const entries = await (0, fs_extra_1.readdir)(featureDir, { withFileTypes: true });
817
+ for (const entry of entries) {
818
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
819
+ continue;
820
+ const subDir = (0, path_1.join)(featureDir, entry.name);
821
+ const subFiles = await (0, fs_extra_1.readdir)(subDir, { withFileTypes: true });
822
+ for (const sf of subFiles) {
823
+ if (!sf.name.endsWith('.md'))
824
+ continue;
825
+ const fp = (0, path_1.join)(subDir, sf.name);
885
826
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
886
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*\u5f85\u586b\u5145|^<!--\s*AI-FILL/m)) {
887
- specContents.push({ name: `features/${fe.name}`, content });
827
+ if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
828
+ specContents.push({ name: `${feature}/${entry.name}/${sf.name}`, content });
888
829
  }
889
830
  }
890
831
  }
@@ -900,7 +841,7 @@ async function iterationSplitCommand(options) {
900
841
  continue;
901
842
  const fp = (0, path_1.join)(typeDirPath, te.name);
902
843
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
903
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*\u5f85\u586b\u5145|^<!--\s*AI-FILL/m)) {
844
+ if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
904
845
  specContents.push({ name: `${typeDir}/${te.name}`, content });
905
846
  }
906
847
  }
@@ -2506,44 +2447,39 @@ async function loadSpecContents(iterationDir) {
2506
2447
  const specDir = (0, path_1.join)(iterationDir, '020-specs');
2507
2448
  if (!(await (0, fs_extra_1.pathExists)(specDir)))
2508
2449
  return specs;
2509
- // 1. 读取全局文档(优先 global/ 子目录,回退根目录 — v6.41.0+ 向后兼容)
2510
- // v8.3.0+: 新增 DEV_GUIDE.md — analyze 生成的开发指南是 split 填充任务级文档的关键输入
2511
- for (const f of ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'DEV_GUIDE.md', 'RISK.md', 'DEPS.md', 'REVIEW.md', 'MONITOR.md']) {
2512
- const resolved = await (0, spec_paths_1.resolveGlobalSpecPath)(specDir, f);
2513
- if (resolved) {
2514
- const content = await (0, fs_extra_1.readFile)(resolved, 'utf-8');
2515
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
2516
- specs[f] = content;
2517
- }
2518
- }
2519
- }
2520
- // 根目录下的 TEST.md、UI_SPEC.md(端无关模板/回退)
2521
- for (const f of ['TEST.md', 'UI_SPEC.md']) {
2522
- const fp = (0, path_1.join)(specDir, f);
2523
- if (await (0, fs_extra_1.pathExists)(fp)) {
2450
+ // v8.3.21+: 读取全局文档(overview/ 下)
2451
+ const overviewDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
2452
+ if (await (0, fs_extra_1.pathExists)(overviewDir)) {
2453
+ const overviewFiles = await (0, fs_extra_1.readdir)(overviewDir, { withFileTypes: true });
2454
+ for (const f of overviewFiles) {
2455
+ if (!f.name.endsWith('.md'))
2456
+ continue;
2457
+ const fp = (0, path_1.join)(overviewDir, f.name);
2524
2458
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
2525
2459
  if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
2526
- specs[f] = content;
2460
+ specs[f.name] = content;
2527
2461
  }
2528
2462
  }
2529
2463
  }
2530
- // 2. 读取各端子目录文档(如 admin/TECH.md、h5/TECH.md 等)
2531
- const entries = await (0, fs_extra_1.readdir)(specDir, { withFileTypes: true });
2532
- // v8.3.4+: 增加 overview 过滤,防止 analyze 全局文档目录被误当成端目录
2533
- const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR]);
2534
- for (const e of entries) {
2535
- if (e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name)) {
2536
- const platform = e.name;
2537
- const platformDir = (0, path_1.join)(specDir, platform);
2538
- // 读取该端下的 TECH.md、TEST.md、UI_SPEC.md、DEV_GUIDE.md
2539
- for (const f of ['TECH.md', 'TEST.md', 'UI_SPEC.md', 'DEV_GUIDE.md']) {
2540
- const fp = (0, path_1.join)(platformDir, f);
2541
- if (await (0, fs_extra_1.pathExists)(fp)) {
2542
- const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
2543
- if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
2544
- // 用平台前缀区分:admin/TECH.md → 'admin/TECH.md'
2545
- specs[`${platform}/${f}`] = content;
2546
- }
2464
+ // v8.3.21+: 读取各功能模块下的文档
2465
+ const features = await (0, spec_paths_1.parseFeatureList)(iterationDir);
2466
+ for (const feature of features) {
2467
+ const featureDir = (0, path_1.join)(specDir, feature);
2468
+ if (!await (0, fs_extra_1.pathExists)(featureDir))
2469
+ continue;
2470
+ const entries = await (0, fs_extra_1.readdir)(featureDir, { withFileTypes: true });
2471
+ for (const entry of entries) {
2472
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
2473
+ continue;
2474
+ const subDir = (0, path_1.join)(featureDir, entry.name);
2475
+ const subFiles = await (0, fs_extra_1.readdir)(subDir, { withFileTypes: true });
2476
+ for (const sf of subFiles) {
2477
+ if (!sf.name.endsWith('.md'))
2478
+ continue;
2479
+ const fp = (0, path_1.join)(subDir, sf.name);
2480
+ const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
2481
+ if (content.trim().length > 50 && !content.trim().match(/^#+\s*待填充|^<!--\s*AI-FILL/m)) {
2482
+ specs[`${feature}/${entry.name}/${sf.name}`] = content;
2547
2483
  }
2548
2484
  }
2549
2485
  }