speccore 8.0.1 → 8.1.1

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.
@@ -1825,8 +1825,8 @@ async function injectGraphSummary(prompt) {
1825
1825
  async function detectIterationDocsStatus(iterDir) {
1826
1826
  const specsDir = (0, path_1.join)(iterDir, '020-specs');
1827
1827
  const overviewDir = (0, path_1.join)(specsDir, spec_paths_1.GLOBAL_SPECS_DIR);
1828
- const PHASE1_DOCS = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'DEPS.md', 'RISK.md', 'REVIEW.md', 'MONITOR.md', 'FUNCTION_MAP.md'];
1829
- const PLATFORM_DOCS = ['TECH.md', 'TEST.md', 'UI_SPEC.md'];
1828
+ const PHASE1_DOCS = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'DEPS.md', 'RISK.md', 'REVIEW.md', 'MONITOR.md', 'FUNCTION_MAP.md', 'INTERACTION_MAP.md', 'DEV_GUIDE.md'];
1829
+ const PLATFORM_DOCS = ['TECH.md', 'TEST.md', 'UI_SPEC.md', 'DEV_GUIDE.md'];
1830
1830
  const existing = [];
1831
1831
  const missing = [];
1832
1832
  // Phase 1: 检查 overview/ 下的文档
@@ -3222,8 +3222,8 @@ sequenceDiagram
3222
3222
  // ── v6.61.0+: 恢复 Phase 1/Phase 2 分步逻辑,但 CLI 自动触发 Phase 2 ──
3223
3223
  // Phase 1: 生成综合文档(overview/REQUIREMENT.md、ANALYSIS.md、DEPS.md 等)
3224
3224
  // Phase 2: 生成各端专属文档({端}/TECH.md、TEST.md、UI_SPEC.md 等)
3225
- const GLOBAL_DOCS = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'RISK.md', 'DEPS.md', 'REVIEW.md', 'MONITOR.md', 'FUNCTION_MAP.md', 'INTERACTION_MAP.md'];
3226
- const PLATFORM_DOCS = ['TECH.md', 'TEST.md', 'UI_SPEC.md'];
3225
+ const GLOBAL_DOCS = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'RISK.md', 'DEPS.md', 'REVIEW.md', 'MONITOR.md', 'FUNCTION_MAP.md', 'INTERACTION_MAP.md', 'DEV_GUIDE.md'];
3226
+ const PLATFORM_DOCS = ['TECH.md', 'TEST.md', 'UI_SPEC.md', 'DEV_GUIDE.md'];
3227
3227
  let taskDocs = docs.filter(([n]) => includeDocs.includes(n));
3228
3228
  if (ctx.phase === '1') {
3229
3229
  taskDocs = taskDocs.filter(([n]) => GLOBAL_DOCS.includes(n));
@@ -3234,6 +3234,7 @@ sequenceDiagram
3234
3234
  // v8.0.0+: 骨架进度检测 — 每次只生成下一个未填充的文档
3235
3235
  // 替代 v7.5.0 的 detectIterationDocsStatus(基于文件是否存在)
3236
3236
  let perDocStatus = null;
3237
+ let lastDocQuality = null;
3237
3238
  if (!isGlobal && !isTask && !ctx.phase && ctx.iteration && ctx.iteration !== 'GLOBAL') {
3238
3239
  try {
3239
3240
  const iterDir = await (0, context_1.getIterationDir)(ctx.iteration);
@@ -3256,6 +3257,27 @@ sequenceDiagram
3256
3257
  taskDocs = taskDocs.filter(([n]) => n === nextDocName);
3257
3258
  }
3258
3259
  logger_1.logger.info(`🦴 骨架进度: ${perDocStatus.filledCount}/${perDocStatus.totalCount} 已填充,下一个: ${perDocStatus.nextUnfilled.relPath}`);
3260
+ // v8.1.0+: 校验上一个已填充文档的质量
3261
+ if (perDocStatus.filled.length > 0) {
3262
+ const lastFilled = perDocStatus.filled[perDocStatus.filled.length - 1];
3263
+ const lastFilledPath = (0, path_1.join)(specDir, lastFilled);
3264
+ if (await (0, fs_extra_1.pathExists)(lastFilledPath)) {
3265
+ try {
3266
+ const lastContent = await (0, fs_extra_2.readFile)(lastFilledPath, 'utf-8');
3267
+ const lastDocName = lastFilled.includes('/') ? lastFilled.split('/').pop() : lastFilled;
3268
+ const lastPlatform = lastFilled.includes('/') ? lastFilled.split('/')[0] : undefined;
3269
+ const quality = await (0, spec_skeleton_1.validateContentQuality)(lastDocName, lastContent, lastPlatform !== lastDocName ? lastPlatform : undefined);
3270
+ lastDocQuality = { docName: lastFilled, score: quality.score, issues: quality.issues };
3271
+ if (quality.score < 60) {
3272
+ logger_1.logger.warn(`⚠️ ${lastFilled} 质量评分: ${quality.score}/100 — ${quality.issues.join(';')}`);
3273
+ }
3274
+ else if (quality.score < 80) {
3275
+ logger_1.logger.info(`📝 ${lastFilled} 质量评分: ${quality.score}/100 — ${quality.issues.length > 0 ? quality.issues.join(';') : '良好'}`);
3276
+ }
3277
+ }
3278
+ catch { /* skip */ }
3279
+ }
3280
+ }
3259
3281
  }
3260
3282
  else if (perDocStatus.filledCount === perDocStatus.totalCount) {
3261
3283
  logger_1.logger.info(`🎉 所有 ${perDocStatus.totalCount} 个骨架文件均已填充`);
@@ -3446,13 +3468,60 @@ sequenceDiagram
3446
3468
  }
3447
3469
  catch {
3448
3470
  // 回退:静态列表
3449
- prompt += `- \`020-specs/overview/\` 下的 REQUIREMENT.md, ANALYSIS.md, TECH.md, DEPS.md, RISK.md, REVIEW.md, MONITOR.md, FUNCTION_MAP.md\n`;
3450
- prompt += `- \`020-specs/{端名}/\` 下的 TECH.md, TEST.md, UI_SPEC.md\n\n`;
3471
+ prompt += `- \`020-specs/overview/\` 下的 REQUIREMENT.md, ANALYSIS.md, TECH.md, DEPS.md, RISK.md, REVIEW.md, MONITOR.md, FUNCTION_MAP.md, INTERACTION_MAP.md, DEV_GUIDE.md\n`;
3472
+ prompt += `- \`020-specs/{端名}/\` 下的 TECH.md, TEST.md, UI_SPEC.md, DEV_GUIDE.md\n\n`;
3451
3473
  }
3452
3474
  prompt += `\n**注意**:\n`;
3453
3475
  prompt += `- 只覆盖上述已存在的文件,不要创建新文件或新目录\n`;
3454
3476
  prompt += `- 每个文件写入正确路径(如 \`overview/ANALYSIS.md\`,不是根目录的 \`ANALYSIS.md\`)\n`;
3455
3477
  prompt += `- 写完后用 Read 验证文件内容已替换占位内容\n\n`;
3478
+ // ── v8.1.0+: 逐文档模式自动注入 PRD 内容 + 前序文档摘要 ──
3479
+ if (perDocStatus?.nextUnfilled) {
3480
+ try {
3481
+ const iterDirForCtx = await (0, context_1.getIterationDir)(iter);
3482
+ if (iterDirForCtx) {
3483
+ // 1. 注入 PRD 内容(010-requirements/ 下的需求文档)
3484
+ const reqDir = (0, path_1.join)(iterDirForCtx, '010-requirements');
3485
+ let prdContent = '';
3486
+ const prdSources = (0, path_1.join)(reqDir, 'sources');
3487
+ const prdConverted = (0, path_1.join)(reqDir, 'converted');
3488
+ const prdFeatures = (0, path_1.join)(reqDir, 'features');
3489
+ for (const dir of [prdSources, prdConverted, prdFeatures]) {
3490
+ if (await (0, fs_extra_1.pathExists)(dir)) {
3491
+ const files = (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readdirSync(dir).filter((f) => f.endsWith('.md')).slice(0, 5);
3492
+ for (const f of files) {
3493
+ const content = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile((0, path_1.join)(dir, f), 'utf-8');
3494
+ prdContent += `\n### ${f}\n${content.slice(0, 2000)}\n`;
3495
+ }
3496
+ }
3497
+ }
3498
+ if (prdContent.length > 0) {
3499
+ prompt += `\n## 📎 需求文档原文(PRD,写作核心输入)\n\n`;
3500
+ prompt += `> 以下是 010-requirements/ 下的需求文档,这是你撰写专业分析文档的核心输入。所有分析必须基于这些内容,不要臆造。\n`;
3501
+ prompt += prdContent.slice(0, 5000);
3502
+ prompt += `\n\n`;
3503
+ }
3504
+ // 2. 注入前序已填充文档摘要
3505
+ const specDir = (0, path_1.join)(iterDirForCtx, '020-specs');
3506
+ if (perDocStatus.filled.length > 0) {
3507
+ prompt += `## 📎 前序已填充文档摘要(当前文档必须与之保持一致)\n\n`;
3508
+ prompt += `> 以下文档已生成,当前文档必须与之保持一致(字段名、接口路径、状态枚举等不能冲突)。\n\n`;
3509
+ for (const filledPath of perDocStatus.filled) {
3510
+ const fullPath = (0, path_1.join)(specDir, filledPath);
3511
+ try {
3512
+ const content = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile(fullPath, 'utf-8');
3513
+ const summary = content.slice(0, 1500);
3514
+ prompt += `### ${filledPath}\n\`\`\`\n${summary}${content.length > 1500 ? '\n... (截断)' : ''}\n\`\`\`\n\n`;
3515
+ }
3516
+ catch { /* skip unreadable */ }
3517
+ }
3518
+ }
3519
+ }
3520
+ }
3521
+ catch (e) {
3522
+ logger_1.logger.debug?.(`PRD/前序文档注入失败(非关键): ${e.message}`);
3523
+ }
3524
+ }
3456
3525
  prompt += `## ⚠️ 迭代名称仅为目录标识(重要)\n\n`;
3457
3526
  prompt += `- 迭代名称("${iter}")仅为目录标识符,**不代表需求内容**\n`;
3458
3527
  prompt += `- **需求文档是唯一事实来源**:所有分析必须 100% 基于 010-requirements/ 下的文档内容\n`;
@@ -3752,7 +3821,7 @@ sequenceDiagram
3752
3821
  // 定义生成顺序
3753
3822
  const chainOrder = isTask
3754
3823
  ? ['REQ.md', 'TECH.md', 'SCHEMA.md', 'TASK.md']
3755
- : ['REQUIREMENT.md', 'FUNCTION_MAP.md', 'INTERACTION_MAP.md', 'API_CONTRACT.yaml', 'ANALYSIS.md', 'TECH.md', 'TEST.md', 'REVIEW.md', 'RISK.md', 'DEPS.md', 'MONITOR.md', 'UI_SPEC.md'];
3824
+ : ['REQUIREMENT.md', 'FUNCTION_MAP.md', 'INTERACTION_MAP.md', 'API_CONTRACT.yaml', 'ANALYSIS.md', 'TECH.md', 'TEST.md', 'REVIEW.md', 'RISK.md', 'DEPS.md', 'MONITOR.md', 'UI_SPEC.md', 'DEV_GUIDE.md'];
3756
3825
  const orderedDocs = taskDocs.filter(([n]) => chainOrder.includes(n));
3757
3826
  const customDocs = taskDocs.filter(([n]) => !chainOrder.includes(n));
3758
3827
  const ordered = [...orderedDocs.sort((a, b) => chainOrder.indexOf(a[0]) - chainOrder.indexOf(b[0])), ...customDocs];
@@ -3802,6 +3871,12 @@ sequenceDiagram
3802
3871
  prompt += `> 当前为**单文档模式**:本次只生成 1 个文档,写入后立即推进下一个。\n`;
3803
3872
  prompt += `> 骨架进度: ${perDocStatus.filledCount}/${perDocStatus.totalCount} 已填充,剩余 ${perDocStatus.unfilled.length} 个\n`;
3804
3873
  prompt += `> 待填充: ${perDocStatus.unfilled.slice(0, 5).join('、')}${perDocStatus.unfilled.length > 5 ? '...' : ''}\n\n`;
3874
+ // v8.1.0+: 注入上一文档质量警告
3875
+ if (lastDocQuality && lastDocQuality.score < 80) {
3876
+ prompt += `> ⚠️ **上一文档 ${lastDocQuality.docName} 质量评分仅 ${lastDocQuality.score}/100**\n`;
3877
+ prompt += `> 问题: ${lastDocQuality.issues.join(';')}\n`;
3878
+ prompt += `> 请确保当前文档达到专业标准——内容充实、章节完整、无占位符残留\n\n`;
3879
+ }
3805
3880
  if (remainingAfterThis > 0) {
3806
3881
  prompt += `**当前文档写入完成后,立即输出以下标记推进下一个文档**:\n\n`;
3807
3882
  prompt += `[SPECCORE_EXEC: speccore analyze --prompt -I ${iter}${options?.auto ? ' --auto' : ''}${options?.withCode ? ' --with-code' : ''}]\n\n`;