speccore 7.5.0 → 8.0.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.
@@ -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,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,CA4qC3E"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAiEA,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;CACtB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2rC3E"}
@@ -59,6 +59,7 @@ const questions_1 = require("../core/questions");
59
59
  const platform_registry_1 = require("../core/platform-registry");
60
60
  const index_guard_1 = require("../core/index-guard");
61
61
  const spec_paths_1 = require("../core/spec-paths");
62
+ const spec_skeleton_1 = require("../core/spec-skeleton");
62
63
  const unified_retrieval_1 = require("../core/unified-retrieval");
63
64
  const pipeline_engine_1 = require("../core/pipeline-engine");
64
65
  const change_detection_1 = require("../core/change-detection");
@@ -523,10 +524,23 @@ async function analyzeCommand(options) {
523
524
  else {
524
525
  const iterForDirs = options.iteration || await (0, context_1.getDefaultIteration)();
525
526
  if (iterForDirs) {
526
- // v7.4.3+: 先清理上一轮 AI 留下的垃圾目录(1001/、错误码/、工程标识/ 等)
527
- const iterDirForSanitize = await (0, context_1.getIterationDir)(iterForDirs);
528
- await sanitizeSpecDirectories(iterDirForSanitize);
529
- await preCreateSpecDirectories(iterForDirs);
527
+ // v8.0.0+: 骨架优先架构 — CLI 预创建所有文件,AI 只覆盖内容
528
+ const iterDirForSkeleton = await (0, context_1.getIterationDir)(iterForDirs);
529
+ const specDir = (0, path_1.join)(iterDirForSkeleton, '020-specs');
530
+ const platforms = await (0, spec_paths_1.parsePlatformList)();
531
+ const iterName = iterForDirs;
532
+ // v7.4.3+ 兼容:先运行一次旧版 sanitize 迁移遗留文件(一次性)
533
+ await sanitizeSpecDirectories(iterDirForSkeleton);
534
+ // v8.0.0+: 计算 manifest 并生成骨架
535
+ const phase = options.phase;
536
+ const manifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, phase, iterName);
537
+ const skeletonResult = await (0, spec_skeleton_1.generateSkeleton)(specDir, manifest);
538
+ if (skeletonResult.created.length > 0) {
539
+ logger_1.logger.info(`🦴 已创建 ${skeletonResult.created.length} 个文件骨架: ${skeletonResult.created.join(', ')}`);
540
+ }
541
+ if (skeletonResult.skipped.length > 0) {
542
+ logger_1.logger.info(`✅ 已有 ${skeletonResult.skipped.length} 个文件保留原内容`);
543
+ }
530
544
  }
531
545
  }
532
546
  }
@@ -3215,19 +3229,22 @@ sequenceDiagram
3215
3229
  else if (ctx.phase === '2') {
3216
3230
  taskDocs = taskDocs.filter(([n]) => PLATFORM_DOCS.includes(n));
3217
3231
  }
3218
- // v7.5.0+: 逐文档生成模式检测已有文档,每次只生成下一个缺失的
3219
- // 避免一次性生成 9+ 文档导致 AI 跳过或写错目录
3232
+ // v8.0.0+: 骨架进度检测每次只生成下一个未填充的文档
3233
+ // 替代 v7.5.0 detectIterationDocsStatus(基于文件是否存在)
3220
3234
  let perDocStatus = null;
3221
3235
  if (!isGlobal && !isTask && !ctx.phase && ctx.iteration && ctx.iteration !== 'GLOBAL') {
3222
3236
  try {
3223
3237
  const iterDir = await (0, context_1.getIterationDir)(ctx.iteration);
3224
3238
  if (iterDir) {
3225
- perDocStatus = await detectIterationDocsStatus(iterDir);
3226
- if (perDocStatus.missing.length > 0 && perDocStatus.nextDoc) {
3227
- const nextDocName = perDocStatus.nextDoc.split('/').pop();
3228
- const nextDocPlatform = perDocStatus.nextDoc.includes('/') ? perDocStatus.nextDoc.split('/')[0] : null;
3229
- // 过滤 taskDocs 只保留下一个缺失的文档
3230
- if (nextDocPlatform && nextDocPlatform !== spec_paths_1.GLOBAL_SPECS_DIR) {
3239
+ const specDir = (0, path_1.join)(iterDir, '020-specs');
3240
+ const platforms = await (0, spec_paths_1.parsePlatformList)();
3241
+ const manifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, undefined, ctx.iteration);
3242
+ perDocStatus = await (0, spec_skeleton_1.detectSkeletonProgress)(specDir, manifest);
3243
+ if (perDocStatus.nextUnfilled) {
3244
+ const nextDocName = perDocStatus.nextUnfilled.docName;
3245
+ const nextDocPlatform = perDocStatus.nextUnfilled.platform || null;
3246
+ // 过滤 taskDocs 只保留下一个未填充的文档
3247
+ if (nextDocPlatform && perDocStatus.nextUnfilled.category === 'platform') {
3231
3248
  // Phase 2 文档(端专属)
3232
3249
  taskDocs = taskDocs.filter(([n]) => n === nextDocName);
3233
3250
  ctx.platform = nextDocPlatform;
@@ -3236,11 +3253,15 @@ sequenceDiagram
3236
3253
  // Phase 1 文档(overview)
3237
3254
  taskDocs = taskDocs.filter(([n]) => n === nextDocName);
3238
3255
  }
3256
+ logger_1.logger.info(`🦴 骨架进度: ${perDocStatus.filledCount}/${perDocStatus.totalCount} 已填充,下一个: ${perDocStatus.nextUnfilled.relPath}`);
3257
+ }
3258
+ else if (perDocStatus.filledCount === perDocStatus.totalCount) {
3259
+ logger_1.logger.info(`🎉 所有 ${perDocStatus.totalCount} 个骨架文件均已填充`);
3239
3260
  }
3240
3261
  }
3241
3262
  }
3242
3263
  catch (e) {
3243
- logger_1.logger.debug(`逐文档检测失败(非关键): ${e.message}`);
3264
+ logger_1.logger.debug(`骨架进度检测失败(非关键): ${e.message}`);
3244
3265
  }
3245
3266
  }
3246
3267
  // ── 任务级文档覆盖:00-specs/ 使用任务级文档集(v6.44.0+) ──
@@ -3407,28 +3428,29 @@ sequenceDiagram
3407
3428
  prompt += `---\n\n`;
3408
3429
  }
3409
3430
  }
3410
- // ── v6.59.0+: 最强警告:禁止自创目录,必须走 --apply ──
3411
- prompt += `## 🚨 最高优先级警告(违反将导致分析失败)\n\n`;
3412
- prompt += `### 绝对禁止创建任何额外目录\n`;
3413
- prompt += `- ❌ **错误行为**:创建 020-specs/1001/、020-specs/1002/、020-specs/错误码/、020-specs/工程标识/ 等垃圾目录\n`;
3414
- prompt += `- ✅ **正确行为**:只使用 CLI 预创建的 overview/ 和 {端名}/ 目录,不要手动 mkdir 或 Write 到不存在的目录\n`;
3415
- prompt += `- ⚠️ **后果**:如果创建额外目录,会导致后续 split/execute 命令找不到文件,整个工作流失败\n\n`;
3416
- prompt += `### 绝对禁止直接用 Write 工具写文件\n`;
3417
- prompt += `- **错误行为**:Write("020-specs/overview/ANALYSIS.md", content) 或直接 Write 到任何路径\n`;
3418
- prompt += `- ✅ **正确行为**:必须通过 \`speccore analyze --apply '{"overview/ANALYSIS.md":"...","admin-web/TECH.md":"..."}' -I ${iter}\` 写入\n`;
3419
- prompt += `- 💡 **Windows 兼容**:如果 JSON 在命令行中转义困难,先将 JSON 写入文件(如 result.json),然后执行 \`speccore analyze --apply @result.json -I ${iter}\`\n`;
3420
- prompt += `- ⚠️ **原因**:--apply 会让 CLI 自动路由文件到正确的子目录,直接 Write 会绕过这个机制,导致所有文件扁平在根目录\n\n`;
3421
- prompt += `### 正确的目录结构\n`;
3422
- prompt += `\`\`\`\n`;
3423
- prompt += `020-specs/\n`;
3424
- prompt += `├── overview/ ← REQUIREMENT.md, ANALYSIS.md, DEPS.md(跨端通用)\n`;
3425
- prompt += `├── admin-web/ ← TECH.md, TEST.md, UI_SPEC.md(Admin 端专属)\n`;
3426
- prompt += `├── booking-service/ TECH.md, TEST.md(后端服务专属)\n`;
3427
- prompt += `├── h5-mobile/ TECH.md, TEST.md, UI_SPEC.md(H5 端专属)\n`;
3428
- prompt += `└── room-service/ ← TECH.md, TEST.md(后端服务专属)\n`;
3429
- prompt += `\`\`\`\n`;
3430
- prompt += `- 每个端目录下只有该端的专属文档,不要混放\n`;
3431
- prompt += `- 不要创建上述之外的任何子目录\n\n`;
3431
+ // ── v8.0.0+: 骨架优先架构 简洁的文件写入指令 ──
3432
+ prompt += `## 📝 文件写入方式(v8.0.0+ 骨架模式)\n\n`;
3433
+ prompt += `CLI 已预创建所有文件骨架(含占位内容)。请逐个用 Write 工具覆盖为专业分析内容:\n\n`;
3434
+ // 动态生成骨架文件列表
3435
+ try {
3436
+ const iterDir = await (0, context_1.getIterationDir)(iter);
3437
+ if (iterDir) {
3438
+ const platforms = await (0, spec_paths_1.parsePlatformList)();
3439
+ const phase = options?.phase;
3440
+ const manifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, phase, iter);
3441
+ const specDir = (0, path_1.join)(iterDir, '020-specs');
3442
+ prompt += (0, spec_skeleton_1.buildSkeletonFileList)(manifest, specDir);
3443
+ }
3444
+ }
3445
+ catch {
3446
+ // 回退:静态列表
3447
+ prompt += `- \`020-specs/overview/\` 下的 REQUIREMENT.md, ANALYSIS.md, TECH.md, DEPS.md, RISK.md, REVIEW.md, MONITOR.md, FUNCTION_MAP.md\n`;
3448
+ prompt += `- \`020-specs/{端名}/\` 下的 TECH.md, TEST.md, UI_SPEC.md\n\n`;
3449
+ }
3450
+ prompt += `\n**注意**:\n`;
3451
+ prompt += `- 只覆盖上述已存在的文件,不要创建新文件或新目录\n`;
3452
+ prompt += `- 每个文件写入正确路径(如 \`overview/ANALYSIS.md\`,不是根目录的 \`ANALYSIS.md\`)\n`;
3453
+ prompt += `- 写完后用 Read 验证文件内容已替换占位内容\n\n`;
3432
3454
  prompt += `## ⚠️ 迭代名称仅为目录标识(重要)\n\n`;
3433
3455
  prompt += `- 迭代名称("${iter}")仅为目录标识符,**不代表需求内容**\n`;
3434
3456
  prompt += `- **需求文档是唯一事实来源**:所有分析必须 100% 基于 010-requirements/ 下的文档内容\n`;
@@ -3771,21 +3793,23 @@ sequenceDiagram
3771
3793
  prompt += `请在生成综合文档后,**直接继续**生成各端专属文档(Phase 2)。\n`;
3772
3794
  prompt += `执行命令:\`speccore analyze --prompt -I ${iter} --phase 2\`\n\n`;
3773
3795
  }
3774
- // v7.5.0+: 逐文档自动链式推进
3775
- if (perDocStatus && perDocStatus.missing.length > 0) {
3776
- const remainingAfterThis = perDocStatus.missing.length - 1;
3777
- prompt += `\n## 🔗 逐文档自动链式推进(v7.5.0+)\n\n`;
3796
+ // v8.0.0+: 逐文档自动链式推进(基于骨架进度)
3797
+ if (perDocStatus && perDocStatus.filledCount < perDocStatus.totalCount) {
3798
+ const remainingAfterThis = perDocStatus.unfilled.length - 1;
3799
+ prompt += `\n## 🔗 逐文档自动链式推进(v8.0.0+ 骨架模式)\n\n`;
3778
3800
  prompt += `> 当前为**单文档模式**:本次只生成 1 个文档,写入后立即推进下一个。\n`;
3779
- prompt += `> 剩余缺失文档: ${perDocStatus.missing.length} 个(${perDocStatus.missing.slice(0, 5).join('、')}${perDocStatus.missing.length > 5 ? '...' : ''})\n\n`;
3801
+ prompt += `> 骨架进度: ${perDocStatus.filledCount}/${perDocStatus.totalCount} 已填充,剩余 ${perDocStatus.unfilled.length} 个\n`;
3802
+ prompt += `> 待填充: ${perDocStatus.unfilled.slice(0, 5).join('、')}${perDocStatus.unfilled.length > 5 ? '...' : ''}\n\n`;
3780
3803
  if (remainingAfterThis > 0) {
3781
3804
  prompt += `**当前文档写入完成后,立即输出以下标记推进下一个文档**:\n\n`;
3782
3805
  prompt += `[SPECCORE_EXEC: speccore analyze --prompt -I ${iter}${options?.auto ? ' --auto' : ''}${options?.withCode ? ' --with-code' : ''}]\n\n`;
3783
- prompt += `> 💡 CLI 会自动检测下一个缺失的文档并生成对应的 prompt。重复执行直到所有文档生成完毕。\n`;
3806
+ prompt += `> 💡 CLI 会自动检测下一个未填充的骨架文件并生成对应的 prompt。重复执行直到所有文件填充完毕。\n`;
3784
3807
  }
3785
3808
  else {
3786
- prompt += `> 🎉 这是最后一个缺失文档!写入后所有迭代级分析文档将完整。\n`;
3809
+ prompt += `> 🎉 这是最后一个骨架文件!填充后所有迭代级分析文档将完整。\n`;
3787
3810
  const platforms = await (0, spec_paths_1.parsePlatformList)();
3788
- if (platforms.length >= 2 && perDocStatus.phase2Complete === false) {
3811
+ const hasPlatformUnfilled = perDocStatus.unfilled.some(p => p.includes('/') && !p.startsWith('overview/'));
3812
+ if (platforms.length >= 2 && hasPlatformUnfilled) {
3789
3813
  prompt += `> 下一步: 生成各端专属文档 → \`speccore analyze --prompt -I ${iter} --phase 2\`\n`;
3790
3814
  }
3791
3815
  else {