speccore 8.3.19 → 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.
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +22 -42
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/execute.js +22 -70
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +78 -142
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +5 -4
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +34 -35
- package/dist/core/prompt-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
818
|
-
const reqPath2 =
|
|
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
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (
|
|
830
|
-
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
|
870
|
-
for (const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
|
887
|
-
specContents.push({ name:
|
|
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
|
|
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
|
-
//
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
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
|
-
//
|
|
2531
|
-
const
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
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
|
}
|