speccore 8.3.15 → 8.3.17
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 +127 -12
- package/dist/commands/analyze.js.map +1 -1
- package/dist/core/spec-paths.d.ts +12 -0
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +65 -0
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/spec-skeleton.d.ts +9 -0
- package/dist/core/spec-skeleton.d.ts.map +1 -1
- package/dist/core/spec-skeleton.js +100 -0
- package/dist/core/spec-skeleton.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAoEA,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC;IACxC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/analyze.js
CHANGED
|
@@ -461,17 +461,35 @@ async function analyzeCommand(options) {
|
|
|
461
461
|
return;
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
|
-
//
|
|
464
|
+
// v8.3.16+: 模糊匹配端名,支持多段指定(逗号分隔)
|
|
465
465
|
if (options.platform) {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
466
|
+
const allPlatforms = await (0, spec_paths_1.parsePlatformList)();
|
|
467
|
+
const inputPlatforms = options.platform.split(',').map(p => p.trim()).filter(Boolean);
|
|
468
|
+
const resolvedPlatforms = [];
|
|
469
|
+
for (const input of inputPlatforms) {
|
|
470
|
+
const resolved = await (0, platform_registry_1.resolvePlatform)(input);
|
|
471
|
+
if (resolved.error) {
|
|
472
|
+
logger_1.logger.error(`❌ ${resolved.error}`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (!resolved.exact) {
|
|
476
|
+
logger_1.logger.info(`📍 --platform ${input} → 匹配 ${resolved.resolved}`);
|
|
477
|
+
}
|
|
478
|
+
if (resolved.resolved) {
|
|
479
|
+
resolvedPlatforms.push(resolved.resolved);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// 过滤只保留在 CONSTITUTION.md 端列表中的端
|
|
483
|
+
const validPlatforms = resolvedPlatforms.filter(p => allPlatforms.includes(p));
|
|
484
|
+
if (validPlatforms.length === 0) {
|
|
485
|
+
logger_1.logger.error(`❌ 指定的端均不在 CONSTITUTION.md 端列表中。可用端: ${allPlatforms.join(', ')}`);
|
|
469
486
|
return;
|
|
470
487
|
}
|
|
471
|
-
if (
|
|
472
|
-
|
|
488
|
+
if (validPlatforms.length < resolvedPlatforms.length) {
|
|
489
|
+
const skipped = resolvedPlatforms.filter(p => !allPlatforms.includes(p));
|
|
490
|
+
logger_1.logger.warn(`⚠️ 跳过不在端列表中的端: ${skipped.join(', ')}`);
|
|
473
491
|
}
|
|
474
|
-
options.platform =
|
|
492
|
+
options.platform = validPlatforms.join(',');
|
|
475
493
|
}
|
|
476
494
|
// 命令前索引新鲜度检查(非阻塞)
|
|
477
495
|
// v6.97.0+ 修复:全局分析时不检查迭代级索引,避免误导用户
|
|
@@ -656,13 +674,44 @@ async function analyzeCommand(options) {
|
|
|
656
674
|
// v8.0.0+: 骨架优先架构 — CLI 预创建所有文件,AI 只覆盖内容
|
|
657
675
|
const iterDirForSkeleton = await (0, context_1.getIterationDir)(iterForDirs);
|
|
658
676
|
const specDir = (0, path_1.join)(iterDirForSkeleton, '020-specs');
|
|
659
|
-
|
|
677
|
+
let platforms = await (0, spec_paths_1.parsePlatformList)();
|
|
660
678
|
const iterName = iterForDirs;
|
|
679
|
+
// v8.3.16+: 如果指定了 --platform,只创建指定端的骨架
|
|
680
|
+
if (options.platform) {
|
|
681
|
+
const specifiedPlatforms = options.platform.split(',').map(p => p.trim()).filter(Boolean);
|
|
682
|
+
const filtered = platforms.filter(p => specifiedPlatforms.includes(p));
|
|
683
|
+
if (filtered.length > 0) {
|
|
684
|
+
platforms = filtered;
|
|
685
|
+
logger_1.logger.info(`📍 按 --platform 过滤: 只为 ${platforms.join(', ')} 端创建骨架`);
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
logger_1.logger.warn(`⚠️ --platform 指定的端不在 CONSTITUTION.md 列表中,将使用全部端`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
661
691
|
// v7.4.3+ 兼容:先运行一次旧版 sanitize 迁移遗留文件(一次性)
|
|
662
692
|
await sanitizeSpecDirectories(iterDirForSkeleton);
|
|
663
|
-
// v8.
|
|
693
|
+
// v8.3.17+: 检测新旧结构,自动选择 manifest 生成方式
|
|
664
694
|
const phase = options.phase;
|
|
665
|
-
const
|
|
695
|
+
const isLegacy = await (0, spec_paths_1.isLegacySpecDir)(specDir);
|
|
696
|
+
let manifest;
|
|
697
|
+
if (isLegacy) {
|
|
698
|
+
// 旧结构:端平铺(020-specs/{端}/)
|
|
699
|
+
manifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, phase, iterName);
|
|
700
|
+
logger_1.logger.info(`📂 使用旧结构(端平铺): 020-specs/{端}/`);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
// 新结构:按需求文档名组织(020-specs/{需求名}/)
|
|
704
|
+
const features = await (0, spec_paths_1.parseFeatureList)(iterDirForSkeleton);
|
|
705
|
+
if (features.length > 0) {
|
|
706
|
+
manifest = (0, spec_skeleton_1.computeFeatureBasedAnalyzeManifest)(features, platforms, phase, iterName);
|
|
707
|
+
logger_1.logger.info(`📂 使用新结构(按需求文档组织): ${features.length} 个功能模块`);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
// 未检测到需求文档,回退到旧结构
|
|
711
|
+
manifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, phase, iterName);
|
|
712
|
+
logger_1.logger.info(`📂 未检测到需求文档,回退到旧结构(端平铺)`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
666
715
|
const skeletonResult = await (0, spec_skeleton_1.generateSkeleton)(specDir, manifest);
|
|
667
716
|
if (skeletonResult.created.length > 0) {
|
|
668
717
|
logger_1.logger.info(`🦴 已创建 ${skeletonResult.created.length} 个文件骨架: ${skeletonResult.created.join(', ')}`);
|
|
@@ -1049,6 +1098,12 @@ async function analyzeCommand(options) {
|
|
|
1049
1098
|
await (0, fs_extra_1.ensureDir)(specDir);
|
|
1050
1099
|
const globalSet = new Set(spec_paths_1.GLOBAL_SPEC_FILES);
|
|
1051
1100
|
const validPlatforms = new Set([spec_paths_1.GLOBAL_SPECS_DIR, ...(await (0, spec_paths_1.parsePlatformList)())]);
|
|
1101
|
+
// v8.3.17+: 检测是否为新结构(按需求文档组织)
|
|
1102
|
+
const isFeatureBased = !await (0, spec_paths_1.isLegacySpecDir)(specDir);
|
|
1103
|
+
const features = isFeatureBased ? await (0, spec_paths_1.parseFeatureList)(iterDir) : [];
|
|
1104
|
+
if (isFeatureBased && features.length > 0) {
|
|
1105
|
+
logger_1.logger.info(` 📂 新结构检测到 ${features.length} 个功能模块,按需求文档名路由文档`);
|
|
1106
|
+
}
|
|
1052
1107
|
let skippedCount = 0;
|
|
1053
1108
|
for (const [filename, content] of Object.entries(docs)) {
|
|
1054
1109
|
// v8.2.0+: 需求文档写入黄金需求目录 020-specs/requirements/
|
|
@@ -1105,6 +1160,50 @@ async function analyzeCommand(options) {
|
|
|
1105
1160
|
if (cleanFilename.startsWith(`${spec_paths_1.GLOBAL_SPECS_DIR}/`)) {
|
|
1106
1161
|
cleanFilename = cleanFilename.slice(`${spec_paths_1.GLOBAL_SPECS_DIR}/`.length);
|
|
1107
1162
|
}
|
|
1163
|
+
// v8.3.17+: 新结构路由 — 按需求文档名组织的目录结构
|
|
1164
|
+
if (isFeatureBased && cleanFilename.includes('/')) {
|
|
1165
|
+
const parts = cleanFilename.split('/');
|
|
1166
|
+
const firstPart = parts[0];
|
|
1167
|
+
// 全局文档:overview/xxx → 写入 020-specs/overview/
|
|
1168
|
+
if (firstPart === spec_paths_1.GLOBAL_SPECS_DIR) {
|
|
1169
|
+
const targetDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
|
|
1170
|
+
await (0, fs_extra_1.ensureDir)(targetDir);
|
|
1171
|
+
const fp = (0, path_1.join)(targetDir, parts.slice(1).join('/'));
|
|
1172
|
+
if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
|
|
1173
|
+
logger_1.logger.info(` ⏭️ 跳过: ${filename}`);
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
const bk = await (0, task_utils_1.backupWithTimestamp)(fp);
|
|
1177
|
+
if (bk) {
|
|
1178
|
+
backups.push(bk);
|
|
1179
|
+
logger_1.logger.info(` 📦 ${filename} 旧版已备份: ${bk.split('/').pop()}`);
|
|
1180
|
+
}
|
|
1181
|
+
await (0, fs_extra_1.writeFile)(fp, content);
|
|
1182
|
+
count++;
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
// 功能模块路径:验证第二级是 overview/ 或合法端名
|
|
1186
|
+
if (!validPlatforms.has(firstPart) && parts.length >= 2) {
|
|
1187
|
+
const secondPart = parts[1];
|
|
1188
|
+
if (secondPart === 'overview' || validPlatforms.has(secondPart)) {
|
|
1189
|
+
const targetDir = (0, path_1.join)(specDir, ...parts.slice(0, -1));
|
|
1190
|
+
await (0, fs_extra_1.ensureDir)(targetDir);
|
|
1191
|
+
const fp = (0, path_1.join)(targetDir, parts[parts.length - 1]);
|
|
1192
|
+
if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
|
|
1193
|
+
logger_1.logger.info(` ⏭️ 跳过: ${filename}`);
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
const bk = await (0, task_utils_1.backupWithTimestamp)(fp);
|
|
1197
|
+
if (bk) {
|
|
1198
|
+
backups.push(bk);
|
|
1199
|
+
logger_1.logger.info(` 📦 ${filename} 旧版已备份: ${bk.split('/').pop()}`);
|
|
1200
|
+
}
|
|
1201
|
+
await (0, fs_extra_1.writeFile)(fp, content);
|
|
1202
|
+
count++;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1108
1207
|
// v7.4.0+: 如果 AI 携带了其他未知目录前缀(非端名),剥离前缀只保留文件名
|
|
1109
1208
|
if (cleanFilename.includes('/')) {
|
|
1110
1209
|
const prefix = cleanFilename.split('/')[0];
|
|
@@ -1123,9 +1222,10 @@ async function analyzeCommand(options) {
|
|
|
1123
1222
|
continue;
|
|
1124
1223
|
}
|
|
1125
1224
|
// 综合文档写入 overview/ 子目录,端专属文档写入 {端}/ 子目录
|
|
1225
|
+
// v8.3.16+: 多段指定时(如 api,web),不额外加目录前缀,由 AI 返回的路径决定
|
|
1126
1226
|
const targetDir = globalSet.has(cleanFilename)
|
|
1127
1227
|
? (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR)
|
|
1128
|
-
: options.platform ? (0, path_1.join)(specDir, options.platform) : specDir;
|
|
1228
|
+
: (options.platform && !options.platform.includes(',')) ? (0, path_1.join)(specDir, options.platform) : specDir;
|
|
1129
1229
|
await (0, fs_extra_1.ensureDir)(targetDir);
|
|
1130
1230
|
const fp = (0, path_1.join)(targetDir, cleanFilename);
|
|
1131
1231
|
if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
|
|
@@ -1449,9 +1549,10 @@ async function analyzeCommand(options) {
|
|
|
1449
1549
|
skippedCount++;
|
|
1450
1550
|
continue;
|
|
1451
1551
|
}
|
|
1552
|
+
// v8.3.16+: 多段指定时(如 api,web),不额外加目录前缀,由 AI 返回的路径决定
|
|
1452
1553
|
const targetDir = globalSet.has(cleanFilename)
|
|
1453
1554
|
? (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR)
|
|
1454
|
-
: options.platform ? (0, path_1.join)(specDir, options.platform) : specDir;
|
|
1555
|
+
: (options.platform && !options.platform.includes(',')) ? (0, path_1.join)(specDir, options.platform) : specDir;
|
|
1455
1556
|
await (0, fs_extra_1.ensureDir)(targetDir);
|
|
1456
1557
|
const fp = (0, path_1.join)(targetDir, cleanFilename);
|
|
1457
1558
|
if (!(await (0, task_utils_1.shouldOverwrite)(fp, !!options.interactive))) {
|
|
@@ -1846,6 +1947,20 @@ async function sanitizeSpecDirectories(iterDir) {
|
|
|
1846
1947
|
}
|
|
1847
1948
|
// 白名单校验:非 overview/ 且非端名的目录 → 非法
|
|
1848
1949
|
if (!validDirs.has(entry)) {
|
|
1950
|
+
// v8.3.17+: 新结构下允许功能模块目录存在
|
|
1951
|
+
const isLegacy = await (0, spec_paths_1.isLegacySpecDir)(specDir);
|
|
1952
|
+
if (!isLegacy) {
|
|
1953
|
+
// 新结构:只归档明显非法的目录名(纯数字、特殊符号等)
|
|
1954
|
+
if (/^\d+$/.test(entry) || /^[\.\-\*_\s]+$/.test(entry)) {
|
|
1955
|
+
const archivedName = `${entry}.invalid-${Date.now()}`;
|
|
1956
|
+
await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, archivedName));
|
|
1957
|
+
logger_1.logger.warn(`⚠️ 非法目录已归档: 020-specs/${entry}/ → ${archivedName}/`);
|
|
1958
|
+
illegalDirCount++;
|
|
1959
|
+
}
|
|
1960
|
+
// 其他目录允许存在(功能模块目录)
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
// 旧结构:非 overview/ 且非端名的目录 → 非法
|
|
1849
1964
|
const archivedName = `${entry}.invalid-${Date.now()}`;
|
|
1850
1965
|
await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, archivedName));
|
|
1851
1966
|
logger_1.logger.warn(`⚠️ 非法目录已归档: 020-specs/${entry}/ → ${archivedName}/`);
|