speccore 8.0.1 → 8.1.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.
- package/dist/commands/analyze.js +75 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.js +13 -5
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/spec-skeleton.d.ts +18 -0
- package/dist/core/spec-skeleton.d.ts.map +1 -1
- package/dist/core/spec-skeleton.js +300 -2
- package/dist/core/spec-skeleton.js.map +1 -1
- package/dist/core/verify-engine.d.ts.map +1 -1
- package/dist/core/verify-engine.js +124 -0
- package/dist/core/verify-engine.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/analyze.js
CHANGED
|
@@ -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} 个骨架文件均已填充`);
|
|
@@ -3453,6 +3475,53 @@ sequenceDiagram
|
|
|
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`;
|
|
@@ -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`;
|