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.
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +67 -43
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +63 -11
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/spec-paths.d.ts +3 -2
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +23 -20
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/spec-skeleton.d.ts +72 -0
- package/dist/core/spec-skeleton.d.ts.map +1 -0
- package/dist/core/spec-skeleton.js +427 -0
- package/dist/core/spec-skeleton.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAoIA,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAuBD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+xBzF"}
|
|
@@ -46,6 +46,7 @@ const readline_1 = require("readline");
|
|
|
46
46
|
const prompt_builder_1 = require("../../core/prompt-builder");
|
|
47
47
|
const index_guard_1 = require("../../core/index-guard");
|
|
48
48
|
const spec_paths_1 = require("../../core/spec-paths");
|
|
49
|
+
const spec_skeleton_1 = require("../../core/spec-skeleton");
|
|
49
50
|
const questions_1 = require("../../core/questions");
|
|
50
51
|
const pipeline_engine_1 = require("../../core/pipeline-engine");
|
|
51
52
|
/**
|
|
@@ -1464,6 +1465,10 @@ ${isBk ? apiList : pageList}
|
|
|
1464
1465
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'RISK.md'), generateRiskTemplate(section, riskMaterial));
|
|
1465
1466
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'DEPS.md'), generateDepsTemplate(section, depsMaterial));
|
|
1466
1467
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'MONITOR.md'), generateMonitorTemplate(section, monitorMaterial));
|
|
1468
|
+
// v8.0.0+: 预创建 REQ.md 和 TECH.md 骨架文件
|
|
1469
|
+
// AI 用 Write 覆盖这些文件,不再自行创建路径(消除路径错误)
|
|
1470
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'REQ.md'), `${spec_skeleton_1.SKELETON_MARKER}\n# ${section.name} — ${platformLabel}\n\n> 本文件由 CLI 预创建,请用 Write 工具覆盖为专业需求规格。\n\n## 功能描述\n<!-- AI-FILL: 本端的需求描述 -->\n\n## 验收标准\n<!-- AI-FILL: Given/When/Then 格式 -->\n\n## 业务规则\n<!-- AI-FILL -->\n\n## 边界条件\n<!-- AI-FILL -->\n\n## 异常场景\n<!-- AI-FILL -->\n`);
|
|
1471
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'TECH.md'), `${spec_skeleton_1.SKELETON_MARKER}\n# ${section.name} — ${platformLabel} 技术方案\n\n> 本文件由 CLI 预创建,请用 Write 工具覆盖为专业技术方案。\n\n## 接口定义\n<!-- AI-FILL: 请求/响应结构体 -->\n\n## 数据模型\n<!-- AI-FILL: 字段/类型/约束 -->\n\n## 核心逻辑\n<!-- AI-FILL -->\n\n## 依赖说明\n<!-- AI-FILL -->\n`);
|
|
1467
1472
|
const adr = generateAdr(section);
|
|
1468
1473
|
if (adr) {
|
|
1469
1474
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'ADR.md'), adr);
|
|
@@ -3212,17 +3217,20 @@ function parseFunctionMapTree(content, allPlatforms) {
|
|
|
3212
3217
|
}
|
|
3213
3218
|
/**
|
|
3214
3219
|
* v7.5.0+: 从 ANALYSIS.md 提取功能单元清单
|
|
3215
|
-
*
|
|
3216
|
-
*
|
|
3220
|
+
* 支持两种格式:
|
|
3221
|
+
* A) 按端分节: ### X.X 端名(platform-name)— N 个页面 + | F-01 | 功能名 | ...
|
|
3222
|
+
* B) 按优先级分节: ### P0 — 必修 + 表格含「涉及端」列(v7.5.1+ 新增)
|
|
3217
3223
|
*/
|
|
3218
3224
|
function parseAnalysisFunctionalUnits(content, allPlatforms) {
|
|
3219
3225
|
const units = [];
|
|
3220
3226
|
const lines = content.split('\n');
|
|
3221
3227
|
let currentPlatforms = [];
|
|
3228
|
+
let platformColIdx = -1; // 「涉及端」列索引,-1 表示未检测到
|
|
3222
3229
|
for (let i = 0; i < lines.length; i++) {
|
|
3223
3230
|
const line = lines[i];
|
|
3224
3231
|
// 检测 ### 标题行,提取当前端名
|
|
3225
3232
|
if (line.startsWith('### ')) {
|
|
3233
|
+
const prevPlatforms = [...currentPlatforms]; // 保留上一轮端信息
|
|
3226
3234
|
currentPlatforms = [];
|
|
3227
3235
|
// 匹配括号内的端名: ### 1.1 后台管理端(admin-web)
|
|
3228
3236
|
const parenMatch = line.match(/[((]([a-zA-Z][\w-]*)[))]/);
|
|
@@ -3241,18 +3249,35 @@ function parseAnalysisFunctionalUnits(content, allPlatforms) {
|
|
|
3241
3249
|
}
|
|
3242
3250
|
}
|
|
3243
3251
|
// 后端服务检测
|
|
3244
|
-
if (/后端|服务|backend|service/i.test(line)
|
|
3252
|
+
if (currentPlatforms.length === 0 && /后端|服务|backend|service/i.test(line)) {
|
|
3245
3253
|
currentPlatforms.push(...allPlatforms.filter(p => /service|server|api|backend/i.test(p)));
|
|
3246
3254
|
}
|
|
3255
|
+
// v7.5.1: 标题未匹配到端时,保留上一轮的 currentPlatforms(优先级分节场景)
|
|
3256
|
+
if (currentPlatforms.length === 0) {
|
|
3257
|
+
currentPlatforms = prevPlatforms;
|
|
3258
|
+
}
|
|
3259
|
+
// 遇到新章节,重置列索引(下一张表可能结构不同)
|
|
3260
|
+
platformColIdx = -1;
|
|
3247
3261
|
continue;
|
|
3248
3262
|
}
|
|
3249
3263
|
// 解析表格行
|
|
3250
|
-
if (!line.startsWith('|')
|
|
3264
|
+
if (!line.startsWith('|'))
|
|
3251
3265
|
continue;
|
|
3252
3266
|
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
3253
3267
|
if (cells.length < 2)
|
|
3254
3268
|
continue;
|
|
3255
|
-
//
|
|
3269
|
+
// 分隔行跳过
|
|
3270
|
+
if (line.match(/^\|\s*[-:]/))
|
|
3271
|
+
continue;
|
|
3272
|
+
// 检测表头行:查找「涉及端」/「端」列
|
|
3273
|
+
if (platformColIdx === -1) {
|
|
3274
|
+
const idx = cells.findIndex(c => c === '涉及端' || c === '端' || c.includes('涉及端') || c.includes('涉及工程'));
|
|
3275
|
+
if (idx >= 0) {
|
|
3276
|
+
platformColIdx = idx;
|
|
3277
|
+
continue; // 表头行本身不是数据
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
// 检测 F-xx 编号行(支持 | F-01 | 或 | # | 格式)
|
|
3256
3281
|
const idMatch = cells[0]?.match(/^F-(\d+)$/i);
|
|
3257
3282
|
if (!idMatch)
|
|
3258
3283
|
continue;
|
|
@@ -3262,8 +3287,35 @@ function parseAnalysisFunctionalUnits(content, allPlatforms) {
|
|
|
3262
3287
|
continue;
|
|
3263
3288
|
// 提取描述(核心功能列,通常是第3或第4列)
|
|
3264
3289
|
const desc = cells.length >= 4 ? cells[3]?.trim() || '' : '';
|
|
3265
|
-
//
|
|
3266
|
-
|
|
3290
|
+
// v7.5.1+: 优先从「涉及端」列提取平台(格式 B)
|
|
3291
|
+
let rowPlatforms = [];
|
|
3292
|
+
if (platformColIdx >= 0 && cells[platformColIdx]) {
|
|
3293
|
+
const rawPlatform = cells[platformColIdx];
|
|
3294
|
+
// 按逗号、+、顿号、空格分割
|
|
3295
|
+
const parts = rawPlatform.split(/[,+、;\s]+/).map(s => s.trim()).filter(Boolean);
|
|
3296
|
+
for (const part of parts) {
|
|
3297
|
+
// 精确匹配
|
|
3298
|
+
const exact = allPlatforms.find(p => p === part);
|
|
3299
|
+
if (exact) {
|
|
3300
|
+
rowPlatforms.push(exact);
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
// 模糊匹配(部分名包含)
|
|
3304
|
+
const fuzzy = allPlatforms.filter(p => p.includes(part) || part.includes(p));
|
|
3305
|
+
if (fuzzy.length > 0) {
|
|
3306
|
+
rowPlatforms.push(...fuzzy);
|
|
3307
|
+
continue;
|
|
3308
|
+
}
|
|
3309
|
+
// 中文端名映射(如 "后台管理端" → admin-web)
|
|
3310
|
+
// 尝试从 CONSTITUTION 的「对应需求端」列匹配(此处简化:跳过中文值)
|
|
3311
|
+
}
|
|
3312
|
+
// 去重
|
|
3313
|
+
rowPlatforms = [...new Set(rowPlatforms)];
|
|
3314
|
+
}
|
|
3315
|
+
// 回退优先级:涉及端列 → 章节标题 → 全部端
|
|
3316
|
+
const platforms = rowPlatforms.length > 0
|
|
3317
|
+
? rowPlatforms
|
|
3318
|
+
: (currentPlatforms.length > 0 ? [...currentPlatforms] : [...allPlatforms]);
|
|
3267
3319
|
units.push({ name, platforms, description: desc, id });
|
|
3268
3320
|
}
|
|
3269
3321
|
// 去重(按名称)
|
|
@@ -3681,9 +3733,9 @@ async function buildContentFillingPrompt(iteration, iterationDir, sections, allP
|
|
|
3681
3733
|
}
|
|
3682
3734
|
p += `## 填充规则\n\n`;
|
|
3683
3735
|
p += `1. 先 Read 子任务目录下的 TASK.md(已有基本信息)和 .meta/feature(功能单元名)\n`;
|
|
3684
|
-
p += `2. REQ.md
|
|
3685
|
-
p += `3.
|
|
3686
|
-
p += `4.
|
|
3736
|
+
p += `2. REQ.md 和 TECH.md 已由 CLI 预创建(含骨架标记),请用 Write 工具直接覆盖为专业内容\n`;
|
|
3737
|
+
p += `3. REQ.md: 基于上方提供的「功能单元相关上下文」,撰写本子任务的需求规格(验收标准、业务规则、边界条件)\n`;
|
|
3738
|
+
p += `4. TECH.md: 基于上下文中的接口定义和数据模型,细化本子任务的技术方案\n`;
|
|
3687
3739
|
p += `5. 同一功能模块的各端子任务要保持 API 契约一致(前后端接口签名必须匹配)\n`;
|
|
3688
3740
|
p += `6. 禁止产出垃圾内容——每个文件必须有实质性专业内容\n`;
|
|
3689
3741
|
p += `7. REQ.md 必须包含:功能描述、验收标准(Given/When/Then)、业务规则、边界条件、异常场景\n`;
|
|
@@ -3692,7 +3744,7 @@ async function buildContentFillingPrompt(iteration, iterationDir, sections, allP
|
|
|
3692
3744
|
p += `- 不要创建新目录 — 目录已由 CLI 创建\n`;
|
|
3693
3745
|
p += `- 不要修改 .meta/ 下的文件\n`;
|
|
3694
3746
|
p += `- 不要修改 TASK.md(已由 CLI 生成)\n`;
|
|
3695
|
-
p += `-
|
|
3747
|
+
p += `- 只覆盖已存在的 REQ.md 和 TECH.md,不要创建新文件\n`;
|
|
3696
3748
|
p += '\n' + (0, questions_1.buildAutoModeInstruction)('split', iteration) + '\n';
|
|
3697
3749
|
return p;
|
|
3698
3750
|
}
|