speccore 8.3.3 → 8.3.5
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 +3 -3
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +135 -24
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +17 -2
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/spec-skeleton.js +16 -16
- 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":"AAuIA,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;AAoCD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+0BzF"}
|
|
@@ -51,6 +51,7 @@ const questions_1 = require("../../core/questions");
|
|
|
51
51
|
const pipeline_engine_1 = require("../../core/pipeline-engine");
|
|
52
52
|
const code_scanner_1 = require("../../core/code-scanner");
|
|
53
53
|
const knowledge_graph_1 = require("../../core/knowledge-graph");
|
|
54
|
+
const git_integration_1 = require("../../core/git-integration");
|
|
54
55
|
/**
|
|
55
56
|
* 将 AI 返回的 scope 简写映射到 CONSTITUTION.md 标准端名
|
|
56
57
|
* v6.69.3+: 解决中文简写(如 "后端"、"admin")导致目录名错误的问题
|
|
@@ -162,16 +163,28 @@ async function detectPlatforms(iterationDir, specified) {
|
|
|
162
163
|
const platforms = await (0, spec_paths_1.parsePlatformList)();
|
|
163
164
|
if (platforms.length > 0)
|
|
164
165
|
return platforms;
|
|
165
|
-
// 2. 回退:扫描 020-specs/ 子目录(排除 global/
|
|
166
|
+
// 2. 回退:扫描 020-specs/ 子目录(排除 global/ 等非端目录 + 常见 AI 简写垃圾目录)
|
|
166
167
|
const specsDir = (0, path_1.join)(iterationDir, '020-specs');
|
|
167
168
|
if (await (0, fs_extra_1.pathExists)(specsDir)) {
|
|
168
169
|
const entries = await (0, fs_extra_1.readdir)(specsDir, { withFileTypes: true });
|
|
169
|
-
|
|
170
|
-
const
|
|
170
|
+
// v8.3.4+: 增加常见 AI 简写过滤,避免 api/web 等错误目录被当成端名
|
|
171
|
+
const knownNonPlatformDirs = new Set([
|
|
172
|
+
'sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research',
|
|
173
|
+
'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR,
|
|
174
|
+
// 常见 AI 简写垃圾目录(必须从标准端名映射,不能直接用)
|
|
175
|
+
'api', 'web', 'backend', 'frontend', 'server', 'mobile', 'admin', 'h5', 'pc', 'app',
|
|
176
|
+
]);
|
|
177
|
+
const rawPlatforms = entries
|
|
171
178
|
.filter((e) => e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name))
|
|
172
179
|
.map((e) => e.name);
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
// 二次过滤:用 normalizeScopePlatforms 排除任何残留的非标准端名
|
|
181
|
+
if (rawPlatforms.length > 0) {
|
|
182
|
+
const standardPlatforms = await (0, spec_paths_1.parsePlatformList)();
|
|
183
|
+
if (standardPlatforms.length > 0) {
|
|
184
|
+
return normalizeScopePlatforms(rawPlatforms, standardPlatforms);
|
|
185
|
+
}
|
|
186
|
+
return rawPlatforms;
|
|
187
|
+
}
|
|
175
188
|
}
|
|
176
189
|
return ['web']; // 默认
|
|
177
190
|
}
|
|
@@ -278,15 +291,15 @@ async function iterationSplitCommand(options) {
|
|
|
278
291
|
return;
|
|
279
292
|
}
|
|
280
293
|
// 非 Pipeline 模式的 Response 处理
|
|
281
|
-
|
|
294
|
+
// v8.3.4+ 修复:使用 getIterationDir 解析正确路径,避免 Iteration- 前缀重复
|
|
295
|
+
const iterDirFull = await (0, context_1.getIterationDir)(iter);
|
|
296
|
+
const iterDir = (0, path_1.join)(iterDirFull, '030-tasks');
|
|
282
297
|
await (0, fs_extra_1.ensureDir)(iterDir);
|
|
283
298
|
const backups = [];
|
|
284
299
|
// 尝试解析 AI 返回的 JSON Task 列表
|
|
285
300
|
try {
|
|
286
301
|
const tasks = JSON.parse(options.response);
|
|
287
302
|
if (Array.isArray(tasks)) {
|
|
288
|
-
// 获取迭代根目录(createTaskFromSection 需要迭代根路径)
|
|
289
|
-
const iterDirFull = await (0, context_1.getIterationDir)(iter);
|
|
290
303
|
const allPlatforms = await detectPlatforms(iterDirFull);
|
|
291
304
|
const sections = [];
|
|
292
305
|
for (let i = 0; i < tasks.length; i++) {
|
|
@@ -351,6 +364,57 @@ async function iterationSplitCommand(options) {
|
|
|
351
364
|
section._scopePlatforms = taskScopePlatforms;
|
|
352
365
|
sections.push(section);
|
|
353
366
|
}
|
|
367
|
+
// v8.3.4+: 校验所有任务的 scope 是否只含标准端名
|
|
368
|
+
const invalidScopes = [];
|
|
369
|
+
for (const sec of sections) {
|
|
370
|
+
const scopes = sec._scopePlatforms || [];
|
|
371
|
+
for (const s of scopes) {
|
|
372
|
+
if (!allPlatforms.includes(s))
|
|
373
|
+
invalidScopes.push(`任务 "${sec.name}" 的 scope "${s}"`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (invalidScopes.length > 0) {
|
|
377
|
+
logger_1.logger.error(`❌ 发现 ${invalidScopes.length} 个非标准端名,拆分被拒绝:`);
|
|
378
|
+
for (const msg of invalidScopes)
|
|
379
|
+
logger_1.logger.error(` ${msg}`);
|
|
380
|
+
logger_1.logger.error(` 标准端名: ${allPlatforms.join(', ')}`);
|
|
381
|
+
logger_1.logger.info(` 💡 请告诉 AI:scope 必须使用 CONSTITUTION.md 标准端名,禁止 api/web/backend 等简写`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// v8.3.4+: 功能点覆盖校验 — 对比 FUNCTION_MAP.md 检查是否有遗漏
|
|
385
|
+
const funcMapPath = (0, path_1.join)(iterDirFull, '020-specs', spec_paths_1.GLOBAL_SPECS_DIR, 'FUNCTION_MAP.md');
|
|
386
|
+
if (await (0, fs_extra_1.pathExists)(funcMapPath)) {
|
|
387
|
+
try {
|
|
388
|
+
const fmContent = await (0, fs_extra_1.readFile)(funcMapPath, 'utf-8');
|
|
389
|
+
const funcUnits = parseFunctionMap(fmContent, allPlatforms);
|
|
390
|
+
if (funcUnits.length > 0) {
|
|
391
|
+
const aiUnits = new Set();
|
|
392
|
+
for (const t of tasks) {
|
|
393
|
+
const unit = t.functionalUnit || t.name;
|
|
394
|
+
if (unit)
|
|
395
|
+
aiUnits.add(unit.trim());
|
|
396
|
+
}
|
|
397
|
+
const missingUnits = [];
|
|
398
|
+
for (const u of funcUnits) {
|
|
399
|
+
// 模糊匹配:如果 AI 任务中没有功能单元名包含或被包含于 FUNCTION_MAP 中的名称
|
|
400
|
+
const matched = [...aiUnits].some(aiu => aiu.toLowerCase().includes(u.name.toLowerCase()) ||
|
|
401
|
+
u.name.toLowerCase().includes(aiu.toLowerCase()));
|
|
402
|
+
if (!matched)
|
|
403
|
+
missingUnits.push(u.name);
|
|
404
|
+
}
|
|
405
|
+
if (missingUnits.length > 0) {
|
|
406
|
+
logger_1.logger.warn(`\n ⚠️ 功能点覆盖警告:${missingUnits.length} 个功能单元可能被遗漏`);
|
|
407
|
+
for (const mu of missingUnits.slice(0, 5))
|
|
408
|
+
logger_1.logger.warn(` 📌 ${mu}`);
|
|
409
|
+
if (missingUnits.length > 5)
|
|
410
|
+
logger_1.logger.warn(` ... 还有 ${missingUnits.length - 5} 个`);
|
|
411
|
+
logger_1.logger.warn(` 💡 建议检查:这些功能点是否已合并到其他任务中?`);
|
|
412
|
+
logger_1.logger.warn(` 💡 如确需补充,重新执行 split 并告诉 AI:"补充以下功能点: ${missingUnits.slice(0, 3).join(', ')}"`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch { }
|
|
417
|
+
}
|
|
354
418
|
// 检测已有任务 + 冲突处理
|
|
355
419
|
const existingTasks = await detectExistingTasks(iterDirFull);
|
|
356
420
|
if (existingTasks.length > 0 && !options.force) {
|
|
@@ -1029,9 +1093,50 @@ function filterTemplateNoise(sections) {
|
|
|
1029
1093
|
return true;
|
|
1030
1094
|
});
|
|
1031
1095
|
}
|
|
1096
|
+
// v8.3.4+: 从迭代目录路径提取迭代名(如 Iteration-011-meeting-upgrade → 011-meeting-upgrade)
|
|
1097
|
+
function extractIterationName(iterationDir) {
|
|
1098
|
+
const base = iterationDir.split('/').pop() || '';
|
|
1099
|
+
return base.replace(/^Iteration-/, '');
|
|
1100
|
+
}
|
|
1101
|
+
/** 生成带实际配置值的 git-config 内容 */
|
|
1102
|
+
function buildGitConfigContent(config, platformLabel, taskType) {
|
|
1103
|
+
const branchType = taskType === 'bugfix' ? 'bugfix' : taskType === 'refactor' ? 'refactor' : taskType === 'research' ? 'research' : 'feature';
|
|
1104
|
+
const exampleName = 'example-task';
|
|
1105
|
+
const exampleHash = 'a1b2';
|
|
1106
|
+
const prefix = config.branchPrefix ? `${config.branchPrefix}-` : '';
|
|
1107
|
+
return `# 子任务级 Git 配置(${platformLabel})
|
|
1108
|
+
# 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
|
|
1109
|
+
# 当前值已从迭代级/全局级配置自动填充,可直接使用或按需修改。
|
|
1110
|
+
|
|
1111
|
+
# === 当前生效配置 ===
|
|
1112
|
+
源分支: ${config.defaultBranch}
|
|
1113
|
+
分支前缀: ${config.branchPrefix || '(空)'}
|
|
1114
|
+
分支格式: ${config.branchFormat}
|
|
1115
|
+
自动拉取: ${config.autoPull ? 'true' : 'false'}
|
|
1116
|
+
远程名称: ${config.remoteName}
|
|
1117
|
+
|
|
1118
|
+
# === 保护分支(禁止直接推送)===
|
|
1119
|
+
${config.protectedBranches.map(b => `- ${b}`).join('\n')}
|
|
1120
|
+
|
|
1121
|
+
# === 分支命名示例 ===
|
|
1122
|
+
# 当前任务类型: ${branchType}
|
|
1123
|
+
# 命名格式: ${config.branchFormat}
|
|
1124
|
+
# 示例分支: ${branchType}/${prefix}${exampleName}-${exampleHash}
|
|
1125
|
+
# ({hash4} 为自动生成的 4 位随机字符)
|
|
1126
|
+
|
|
1127
|
+
# === 自定义配置区 ===
|
|
1128
|
+
# 如需覆盖上述值,取消下方注释并修改:
|
|
1129
|
+
# 源分支: ${config.defaultBranch}
|
|
1130
|
+
# 分支前缀:
|
|
1131
|
+
# 分支格式: {type}/{prefix}{name}-{hash4}
|
|
1132
|
+
# 自动拉取: false
|
|
1133
|
+
# 远程名称: origin
|
|
1134
|
+
`;
|
|
1135
|
+
}
|
|
1032
1136
|
async function createTaskFromSection(iterationDir, taskId, section, allPlatforms, taskType = 'feature', allSections) {
|
|
1033
1137
|
// taskId 已含 slug(nextTaskId 返回 Task-NNN-slug),直接用
|
|
1034
1138
|
const taskDir = (0, path_1.join)(iterationDir, '030-tasks', taskType, taskId);
|
|
1139
|
+
const iterationName = extractIterationName(iterationDir);
|
|
1035
1140
|
// 确定任务涉及的端:优先使用 AI 标注的 _scopePlatforms,否则从 020-specs/{端}/TECH.md 是否存在且有内容来推断
|
|
1036
1141
|
let taskPlatforms;
|
|
1037
1142
|
if (section._scopePlatforms && section._scopePlatforms.length > 0) {
|
|
@@ -1364,17 +1469,9 @@ ${section.content}
|
|
|
1364
1469
|
// src/ + tests/ 目录(AI 执行时代码输出位置)
|
|
1365
1470
|
await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'src'));
|
|
1366
1471
|
await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'tests'));
|
|
1367
|
-
// git-config
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
# 修改时去掉注释符 #,填入具体值即可。
|
|
1371
|
-
|
|
1372
|
-
# 源分支: 继承迭代配置
|
|
1373
|
-
# 分支前缀: 继承迭代配置
|
|
1374
|
-
# 分支格式: 继承迭代配置
|
|
1375
|
-
# 自动拉取: 继承迭代配置
|
|
1376
|
-
# 远程名称: 继承迭代配置
|
|
1377
|
-
`);
|
|
1472
|
+
// v8.3.4+: git-config 自动填充 — 读取迭代级/全局级实际配置值写入
|
|
1473
|
+
const gitConfig = (0, git_integration_1.loadGitConfig)(iterationName);
|
|
1474
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'git-config'), buildGitConfigContent(gitConfig, platformLabel, section.type));
|
|
1378
1475
|
// v6.70.0+: 从 section 提取接口/页面清单用于 TASK.md
|
|
1379
1476
|
const apiLines = section.content.split('\n').filter(l => l.includes('| GET') || l.includes('| POST') || l.includes('| PUT') || l.includes('| DELETE') || l.includes('| PATCH'));
|
|
1380
1477
|
const apiList = apiLines.length > 0
|
|
@@ -1536,9 +1633,9 @@ ${isBk ? apiList : pageList}
|
|
|
1536
1633
|
// 功能单元标识(v6.49.2+)
|
|
1537
1634
|
const featureName = section.functionalUnit || section.name || '未分类';
|
|
1538
1635
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'feature'), featureName);
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1636
|
+
// v8.3.4+: git-config 自动填充
|
|
1637
|
+
const gitConfig = (0, git_integration_1.loadGitConfig)(iterationName);
|
|
1638
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'git-config'), buildGitConfigContent(gitConfig, '后端', section.type));
|
|
1542
1639
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'TASK.md'), `# ${section.name} — ${fallbackBackend}(自动补充)
|
|
1543
1640
|
|
|
1544
1641
|
## 子任务信息
|
|
@@ -2261,7 +2358,8 @@ async function loadSpecContents(iterationDir) {
|
|
|
2261
2358
|
}
|
|
2262
2359
|
// 2. 读取各端子目录文档(如 admin/TECH.md、h5/TECH.md 等)
|
|
2263
2360
|
const entries = await (0, fs_extra_1.readdir)(specDir, { withFileTypes: true });
|
|
2264
|
-
|
|
2361
|
+
// v8.3.4+: 增加 overview 过滤,防止 analyze 全局文档目录被误当成端目录
|
|
2362
|
+
const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', 'overview', 'global', spec_paths_1.GLOBAL_SPECS_DIR]);
|
|
2265
2363
|
for (const e of entries) {
|
|
2266
2364
|
if (e.isDirectory() && !e.name.startsWith('_') && !e.name.startsWith('.') && !knownNonPlatformDirs.has(e.name)) {
|
|
2267
2365
|
const platform = e.name;
|
|
@@ -2710,6 +2808,12 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
|
|
|
2710
2808
|
p += `> 同一模块/领域的任务填相同的值,用于粒度校验和任务分组\n`;
|
|
2711
2809
|
p += `> **topic** 必须是英文短横线格式(如 \`user-authentication\`、\`product-crud\`),用于生成任务目录名 Task-NNN-{topic}\n`;
|
|
2712
2810
|
p += `> **sourceFile** 必须填写:该任务对应的 020-specs 源文档路径(如 \`bugs/login-timeout.md\`、\`features/user-auth.md\`、\`refactors/db-pool.md\`),用于在 CONTEXT.md 中生成来源追溯\n`;
|
|
2811
|
+
p += `> **scope 强制约束(违反则拆分无效)**:\n`;
|
|
2812
|
+
p += `> - scope 数组中的每个值必须是上面「项目端列表」中的标准端名之一\n`;
|
|
2813
|
+
p += `> - ⛔ 禁止出现 api、web、backend、frontend、server、admin、h5、mobile、pc、app 等非标准名称\n`;
|
|
2814
|
+
p += `> - ⛔ 禁止出现中文端名(如"后端"、"前端"、"移动端")\n`;
|
|
2815
|
+
p += `> - 每个任务按 scope 中的端创建子任务目录,目录名 = 标准端名\n`;
|
|
2816
|
+
p += `> - 如果 scope 含非标准端名,整个 JSON 视为不合格,必须重新生成\n`;
|
|
2713
2817
|
p += `> **reqContent 质量要求(必填,禁止模板化)**:\n`;
|
|
2714
2818
|
p += `> - 必须是**具体的、可执行的需求描述**,不是"待补充"或"参考全局文档"\n`;
|
|
2715
2819
|
p += `> - 包含:业务规则(含边界条件)、数据模型(字段/类型/约束)、接口清单(方法/路径/参数/响应)\n`;
|
|
@@ -2721,6 +2825,13 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
|
|
|
2721
2825
|
p += `> - 从 020-specs/overview/TECH.md 和对应端 TECH.md 中提取本任务相关的技术细节\n`;
|
|
2722
2826
|
p += `> - 直接写入 00-specs/TECH.md,执行时 AI 据此直接开发\n`;
|
|
2723
2827
|
p += `> **质量红线**:如果 reqContent/techContent 只有标题和占位符(如 "<!-- AI-FILL -->"),视为不合格,必须重新生成\n\n`;
|
|
2828
|
+
// v8.3.4+: 强制约束专节(类似 analyze Phase 2)
|
|
2829
|
+
p += `### ⛔ 强制约束(违反则拆分无效)\n\n`;
|
|
2830
|
+
p += `- **禁止跳过任何功能模块** — 即使某个模块信息不足,也必须基于已有信息拆分并填充 reqContent/techContent,标注「基于现有信息推断」而非留空\n`;
|
|
2831
|
+
p += `- **禁止输出空内容** — reqContent、techContent、devGuideContent 每个字段长度必须 ≥200 字符,不允许写「待补充」「TODO」「参考全局文档」\n`;
|
|
2832
|
+
p += `- **禁止省略字段** — JSON 中必须包含 reqContent、techContent、devGuideContent 三个字段,缺一不可\n`;
|
|
2833
|
+
p += `- **内容必须从 analyze 产物提取** — reqContent 从 REQUIREMENT.md 提取,techContent 从 TECH.md 提取,devGuideContent 从 DEV_GUIDE.md 提取\n`;
|
|
2834
|
+
p += `- **如果 token 不足**:优先保证 reqContent 完整,其次是 techContent,最后是 devGuideContent\n\n`;
|
|
2724
2835
|
p += `### ⚠️ 拆分规则(重要)\n\n`;
|
|
2725
2836
|
p += `- **按功能单元拆分,不按技术层拆分**:每个任务必须对应一个业务功能(如「预订管理」「签到」「审批」),不要按技术层分组(如「后端改进」「前端修复」)\n`;
|
|
2726
2837
|
p += `- ❌ 错误示例:Task-129-backend-improve、Task-130-admin-fix(按技术层分组)\n`;
|
|
@@ -2754,7 +2865,7 @@ async function buildSplitPrompt(iteration, constitutionContent, reqContent, spec
|
|
|
2754
2865
|
- 你的判断
|
|
2755
2866
|
- 建议后续动作\`
|
|
2756
2867
|
`;
|
|
2757
|
-
p += `3.
|
|
2868
|
+
p += `3. **禁止跳过任何模块** — 即使某个功能模块信息不足,也必须基于已有信息拆分并填充内容,宁可标注「基于现有信息推断:需要补充 xxx」也不许留空或省略\n`;
|
|
2758
2869
|
p += `4. **输出 JSON** — 直接输出拆分结果的 JSON 数组,不要输出其他内容\n`;
|
|
2759
2870
|
// 持久指令(用户调整时 AI 可回读此文件)
|
|
2760
2871
|
p += `\n## 🔄 调整指令(持久有效)\n\n`;
|