speccore 7.2.0 → 7.3.0

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.
@@ -75,13 +75,18 @@ disable-model-invocation: false
75
75
  - 步骤3-5: 同下
76
76
  - **全局级分析**(用户明确说"全局""全量""项目分析""所有端"等):
77
77
  - 步骤1: `speccore analyze --prompt --scope global --with-code` → 获取 SPECCORE_PROMPT
78
- - 步骤2: **Read .speccore/CONSTITUTION.md 获取工程源码路径**,从源码路径读取各端代码,**禁止读迭代目录**
79
- - 步骤3: Read .speccore/PATTERNS/TEMPLATES/specs/ 中的专业模板,参考其格式和结构
80
- - 步骤4: 对每个文档用专业格式自由撰写,禁止写"待填充"或复制模板空壳
81
- - 步骤5: `speccore analyze --apply '{"ANALYSIS.md":"...",...}' --scope global` 写入 `.speccore/GLOBAL/`
78
+ - 步骤2: **Read .speccore/CONSTITUTION.md 获取工程源码路径**,从源码路径读各端代码,**禁止读/写迭代目录**
79
+ - 步骤3: **严格按 prompt 中「当前执行层级 Layer N/4」执行,只做当前层**(v7.2.0+ 分层机制,禁止一次写全部):
80
+ - Layer 1 代码索引 → `platforms/{端}/_INDEX.md` + PATTERNS
81
+ - Layer 2 架构关联 `platforms/_shared/_ASSOCIATION.md` + `_MODULES.md`
82
+ - Layer 3 业务深入 → 各端功能模块文档
83
+ - Layer 4 全局汇总 → `overview/`(REQUIREMENT/ANALYSIS/DEPS/FUNCTION_MAP/INTERACTION_MAP/API_CONTRACT) + `requirements/` + `platforms/`
84
+ - 步骤4: 每层产物用 `speccore analyze --apply '{"<相对路径>":"...",...}' --scope global` 写入 `.speccore/GLOBAL/`
85
+ - 步骤5: 看到 [SPECCORE_PIPELINE_NEXT] 或 prompt 提示的下一层命令时**自动继续,不询问用户**,直到 Layer 4 完成
82
86
  - 步骤6: **用户提供参考文档时**: 如果用户附带了格式参考文档(如PDF/Word/截图),先按标准 Spec 模板生成默认文档,再按用户参考格式生成一份,**两边都要**
83
87
  - ❌ 禁止: 跳过文档填充直接写代码、生成重复空壳、用 "API=0" 跳过
84
- - ❌ 禁止: 全局分析时 fallback 到迭代目录读取需求文档
88
+ - ❌ 禁止: 全局分析时 fallback 到迭代目录读取需求文档,或把全局产物写到迭代目录
89
+ - ❌ 禁止: 只生成单个 ANALYSIS.md 就收尾、用 outputs/ 临时报告代替落盘 —— 必须按层级完成全部文档并写入 `.speccore/GLOBAL/`
85
90
  - ⚠️ 如果模板目录不存在或为空: 用你的专业知识自由撰写,绝不允许产出一行垃圾。你是一个专业的技术架构师和分析师,即使没有参考模板,也能产出高质量文档
86
91
  6. **交互式文档编辑**: 分析完成后用户可以对任意文档的任意部分进行修改。支持:
87
92
  - "修改 ANALYSIS.md 的接口清单部分,加上签到接口" → 只改指定章节,不改其他部分
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAgEA,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,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,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;CACtB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA+oC3E"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAgEA,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,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,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;CACtB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkpC3E"}
@@ -912,6 +912,8 @@ async function analyzeCommand(options) {
912
912
  logger_1.logger.warn(`⚠️ 共跳过 ${skippedCount} 个非法目录的文档,请检查 AI 输出是否包含非端名目录`);
913
913
  }
914
914
  logger_1.logger.success(`✅ ${count} 个 Spec 文档已写入 020-specs/`);
915
+ // v6.90.0+: 事后校验——检测并清理 AI 绕过 --apply 创建的非法目录/文件
916
+ await sanitizeSpecDirectories(iterDir);
915
917
  // v6.74.0+: 流式分析自动检查(回退检测 + 最终核对)
916
918
  if (options.streamingPhase) {
917
919
  const phase = options.streamingPhase;
@@ -1249,7 +1251,7 @@ async function preCreateSpecDirectories(iteration) {
1249
1251
  const iterDir = await (0, context_1.getIterationDir)(iteration);
1250
1252
  const specDir = (0, path_1.join)(iterDir, '020-specs');
1251
1253
  await (0, fs_extra_1.ensureDir)(specDir);
1252
- // 预创建 global/ 子目录
1254
+ // 预创建 overview/ 子目录(v6.78.0+ 从 global/ 改名)
1253
1255
  const globalDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
1254
1256
  await (0, fs_extra_1.ensureDir)(globalDir);
1255
1257
  // 读取端列表并预创建各端目录
@@ -1258,10 +1260,103 @@ async function preCreateSpecDirectories(iteration) {
1258
1260
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(specDir, platform));
1259
1261
  }
1260
1262
  if (platforms.length > 0) {
1261
- logger_1.logger.info(`📁 已预创建 020-specs/ 目录结构: global/ + ${platforms.length} 个端目录 (${platforms.join(', ')})`);
1263
+ logger_1.logger.info(`📁 已预创建 020-specs/ 目录结构: ${spec_paths_1.GLOBAL_SPECS_DIR}/ + ${platforms.length} 个端目录 (${platforms.join(', ')})`);
1262
1264
  }
1263
1265
  else {
1264
- logger_1.logger.info(`📁 已预创建 020-specs/ 目录结构: global/`);
1266
+ logger_1.logger.info(`📁 已预创建 020-specs/ 目录结构: ${spec_paths_1.GLOBAL_SPECS_DIR}/`);
1267
+ }
1268
+ }
1269
+ /**
1270
+ * v6.90.0+: 事后校验——检测并清理 020-specs/ 下 AI 绕过 --apply 创建的非法目录和文件
1271
+ *
1272
+ * 解决的问题:AI 用 Write 工具直接写文件,绕过 CLI --apply,导致:
1273
+ * - 非法子目录(如 1001/、工程标识/、错误码/)
1274
+ * - 遗留的 global/ 旧目录(应迁移到 overview/)
1275
+ * - 根目录散落的 .md 文件(应在 overview/ 内)
1276
+ */
1277
+ async function sanitizeSpecDirectories(iterDir) {
1278
+ const specDir = (0, path_1.join)(iterDir, '020-specs');
1279
+ if (!await (0, fs_extra_1.pathExists)(specDir))
1280
+ return;
1281
+ const platforms = await (0, spec_paths_1.parsePlatformList)();
1282
+ const validDirs = new Set([spec_paths_1.GLOBAL_SPECS_DIR, ...platforms]);
1283
+ const globalSet = new Set(spec_paths_1.GLOBAL_SPEC_FILES);
1284
+ const entries = await (0, fs_extra_2.readdir)(specDir);
1285
+ let illegalDirCount = 0;
1286
+ let orphanFileCount = 0;
1287
+ let legacyGlobalMigrated = false;
1288
+ for (const entry of entries) {
1289
+ const entryPath = (0, path_1.join)(specDir, entry);
1290
+ const entryStat = await (0, fs_extra_1.stat)(entryPath);
1291
+ if (entryStat.isDirectory()) {
1292
+ // 检测遗留的 global/ 目录(v6.78.0+ 已改名为 overview/)
1293
+ if (entry === 'global') {
1294
+ logger_1.logger.warn(`⚠️ 检测到遗留 global/ 目录(v6.78.0+ 已改名为 ${spec_paths_1.GLOBAL_SPECS_DIR}/)`);
1295
+ // 将 global/ 中的文件迁移到 overview/
1296
+ const globalFiles = await (0, fs_extra_2.readdir)(entryPath);
1297
+ const overviewDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
1298
+ await (0, fs_extra_1.ensureDir)(overviewDir);
1299
+ for (const f of globalFiles) {
1300
+ const src = (0, path_1.join)(entryPath, f);
1301
+ const dest = (0, path_1.join)(overviewDir, f);
1302
+ if (!await (0, fs_extra_1.pathExists)(dest)) {
1303
+ await (0, fs_extra_1.rename)(src, dest);
1304
+ logger_1.logger.info(` 📦 迁移: global/${f} → ${spec_paths_1.GLOBAL_SPECS_DIR}/${f}`);
1305
+ }
1306
+ }
1307
+ // 删除空的 global/ 目录
1308
+ const remaining = await (0, fs_extra_2.readdir)(entryPath);
1309
+ if (remaining.length === 0) {
1310
+ await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, `global.migrated-${Date.now()}`));
1311
+ logger_1.logger.info(` 🗑️ global/ 已迁移并归档`);
1312
+ }
1313
+ else {
1314
+ logger_1.logger.warn(` ⚠️ global/ 仍有 ${remaining.length} 个文件未迁移,已重命名归档`);
1315
+ await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, `global.archived-${Date.now()}`));
1316
+ }
1317
+ legacyGlobalMigrated = true;
1318
+ continue;
1319
+ }
1320
+ // 白名单校验:非 overview/ 且非端名的目录 → 非法
1321
+ if (!validDirs.has(entry)) {
1322
+ const archivedName = `${entry}.invalid-${Date.now()}`;
1323
+ await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, archivedName));
1324
+ logger_1.logger.warn(`⚠️ 非法目录已归档: 020-specs/${entry}/ → ${archivedName}/`);
1325
+ illegalDirCount++;
1326
+ }
1327
+ }
1328
+ else if (entryStat.isFile()) {
1329
+ // 根目录散落的 .md 文件 → 检查是否应归入 overview/
1330
+ if (entry.endsWith('.md') && globalSet.has(entry)) {
1331
+ const overviewDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
1332
+ await (0, fs_extra_1.ensureDir)(overviewDir);
1333
+ const dest = (0, path_1.join)(overviewDir, entry);
1334
+ if (!await (0, fs_extra_1.pathExists)(dest)) {
1335
+ await (0, fs_extra_1.rename)(entryPath, dest);
1336
+ logger_1.logger.warn(`⚠️ 散落文件已归位: 020-specs/${entry} → ${spec_paths_1.GLOBAL_SPECS_DIR}/${entry}`);
1337
+ orphanFileCount++;
1338
+ }
1339
+ else {
1340
+ // overview/ 中已有同名文件,归档根目录版本
1341
+ const archivedName = `${entry}.orphan-${Date.now()}`;
1342
+ await (0, fs_extra_1.rename)(entryPath, (0, path_1.join)(specDir, archivedName));
1343
+ logger_1.logger.warn(`⚠️ 重复散落文件已归档: 020-specs/${entry} → ${archivedName}`);
1344
+ orphanFileCount++;
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ // 汇总报告
1350
+ if (illegalDirCount > 0 || orphanFileCount > 0 || legacyGlobalMigrated) {
1351
+ logger_1.logger.info('');
1352
+ logger_1.logger.info(`🔍 020-specs/ 目录校验报告:`);
1353
+ if (illegalDirCount > 0)
1354
+ logger_1.logger.warn(` ⚠️ ${illegalDirCount} 个非法目录已归档(AI 绕过 --apply 创建)`);
1355
+ if (orphanFileCount > 0)
1356
+ logger_1.logger.warn(` ⚠️ ${orphanFileCount} 个散落文件已归位到 ${spec_paths_1.GLOBAL_SPECS_DIR}/`);
1357
+ if (legacyGlobalMigrated)
1358
+ logger_1.logger.info(` 📦 遗留 global/ 已迁移到 ${spec_paths_1.GLOBAL_SPECS_DIR}/`);
1359
+ logger_1.logger.info(` ✅ 当前合法目录: ${Array.from(validDirs).join(', ')}`);
1265
1360
  }
1266
1361
  }
1267
1362
  /**
@@ -2974,7 +3069,7 @@ sequenceDiagram
2974
3069
  prompt += `## 🚨 最高优先级警告(违反将导致分析失败)\n\n`;
2975
3070
  prompt += `### ⛔ 绝对禁止创建任何额外目录\n`;
2976
3071
  prompt += `- ❌ **错误行为**:创建 020-specs/1001/、020-specs/1002/、020-specs/错误码/、020-specs/工程标识/ 等垃圾目录\n`;
2977
- prompt += `- ✅ **正确行为**:只使用 CLI 预创建的 global/ 和 {端名}/ 目录,不要手动 mkdir 或 Write 到不存在的目录\n`;
3072
+ prompt += `- ✅ **正确行为**:只使用 CLI 预创建的 overview/ 和 {端名}/ 目录,不要手动 mkdir 或 Write 到不存在的目录\n`;
2978
3073
  prompt += `- ⚠️ **后果**:如果创建额外目录,会导致后续 split/execute 命令找不到文件,整个工作流失败\n\n`;
2979
3074
  prompt += `### 绝对禁止直接用 Write 工具写文件\n`;
2980
3075
  prompt += `- ❌ **错误行为**:Write("020-specs/overview/ANALYSIS.md", content) 或直接 Write 到任何路径\n`;
@@ -2984,7 +3079,7 @@ sequenceDiagram
2984
3079
  prompt += `### ✅ 正确的目录结构\n`;
2985
3080
  prompt += `\`\`\`\n`;
2986
3081
  prompt += `020-specs/\n`;
2987
- prompt += `├── global/ ← REQUIREMENT.md, ANALYSIS.md, DEPS.md(跨端通用)\n`;
3082
+ prompt += `├── overview/ ← REQUIREMENT.md, ANALYSIS.md, DEPS.md(跨端通用)\n`;
2988
3083
  prompt += `├── admin-web/ ← TECH.md, TEST.md, UI_SPEC.md(Admin 端专属)\n`;
2989
3084
  prompt += `├── booking-service/ ← TECH.md, TEST.md(后端服务专属)\n`;
2990
3085
  prompt += `├── h5-mobile/ ← TECH.md, TEST.md, UI_SPEC.md(H5 端专属)\n`;
@@ -3229,7 +3324,7 @@ sequenceDiagram
3229
3324
  prompt += ` - MONITOR.md(监控指标)\n`;
3230
3325
  prompt += ` - **禁止**:不要创建 020-specs/ 下的任何额外子目录(如数字编号、中文名称等)\n`;
3231
3326
  prompt += ` - **禁止直接用 Write 工具写文件到 020-specs/**:必须通过 \`speccore analyze --apply '{"文件名":"内容"}' -I ${iter}\` 写入\n`;
3232
- prompt += ` - ⚠️ 直接 Write 会导致目录结构错误(所有文件扁平在根目录),必须走 --apply 让 CLI 自动路由到 global/ 或 {端名}/ 子目录\n`;
3327
+ prompt += ` - ⚠️ 直接 Write 会导致目录结构错误(所有文件扁平在根目录),必须走 --apply 让 CLI 自动路由到 overview/ 或 {端名}/ 子目录\n`;
3233
3328
  if (ctx.phase !== '1') {
3234
3329
  // 端专业性约束只在默认模式(全量)中输出
3235
3330
  prompt += `\n## ⚠️ 端专业性约束\n`;
@@ -3320,7 +3415,7 @@ sequenceDiagram
3320
3415
  prompt += `\n## ⚠️ 重要:Phase 1 完成后的下一步\n\n`;
3321
3416
  prompt += `当你通过 --apply 写入所有综合文档后,CLI 会检测到项目有多个端(≥2 个端)。\n`;
3322
3417
  prompt += `**此时你需要主动询问用户**:\n\n`;
3323
- prompt += `"✅ Phase 1 已完成,生成了 global/ 的综合文档。\n`;
3418
+ prompt += `"✅ Phase 1 已完成,生成了 overview/ 的综合文档。\n`;
3324
3419
  prompt += `检测到项目有 ${'{'}端列表{'}'} 个端,需要继续执行 Phase 2 生成各端专属文档吗?\n`;
3325
3420
  prompt += `请确认:输入 '继续' 或 'yes' 以执行 Phase 2"\n\n`;
3326
3421
  prompt += `**如果用户确认继续**,你需要执行:\n`;