speccore 8.3.160 → 8.3.162
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/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +16 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +545 -292
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +33 -4
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/change.d.ts.map +1 -1
- package/dist/commands/change.js +14 -14
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/execute.d.ts +1 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +25 -4
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +226 -43
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +45 -36
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +60 -0
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/prompt-builder.d.ts +8 -3
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +155 -5
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/requirement-clarifier.d.ts.map +1 -1
- package/dist/core/requirement-clarifier.js +36 -3
- package/dist/core/requirement-clarifier.js.map +1 -1
- package/dist/core/schemas/task.schema.d.ts +2 -2
- package/dist/core/structured-extractor.d.ts +2 -0
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +61 -0
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/unified-retrieval.d.ts +2 -1
- package/dist/core/unified-retrieval.d.ts.map +1 -1
- package/dist/core/unified-retrieval.js +34 -15
- package/dist/core/unified-retrieval.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/analyze.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.UNITS_CACHE_DIR = void 0;
|
|
37
37
|
exports.analyzeCommand = analyzeCommand;
|
|
38
|
+
exports.buildMultiDocPrompt = buildMultiDocPrompt;
|
|
38
39
|
exports.extractIterationUnits = extractIterationUnits;
|
|
39
40
|
exports.saveUnitAnalysis = saveUnitAnalysis;
|
|
40
41
|
exports.loadUnitAnalyses = loadUnitAnalyses;
|
|
@@ -64,6 +65,8 @@ const task_paths_1 = require("../core/task-paths");
|
|
|
64
65
|
const analyze_engine_1 = require("../core/analyze-engine");
|
|
65
66
|
const fs_extra_2 = require("fs-extra");
|
|
66
67
|
const prompt_builder_1 = require("../core/prompt-builder");
|
|
68
|
+
// v8.3.160+: 子 Agent 适配层(analyze 自定义 Prompt 中手动注入)
|
|
69
|
+
const agent_adapter_1 = require("../core/agent-adapter");
|
|
67
70
|
const questions_1 = require("../core/questions");
|
|
68
71
|
const platform_registry_1 = require("../core/platform-registry");
|
|
69
72
|
const index_guard_1 = require("../core/index-guard");
|
|
@@ -871,15 +874,15 @@ async function analyzeCommand(options) {
|
|
|
871
874
|
if (isGlobalScope) {
|
|
872
875
|
// 全局层 Pipeline 步骤映射
|
|
873
876
|
if (currentStep === 'init' || currentStep === 'discovery') {
|
|
874
|
-
prompt = await
|
|
875
|
-
iteration: iter || 'GLOBAL', task: options.task,
|
|
876
|
-
|
|
877
|
-
}
|
|
877
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
878
|
+
iteration: iter || 'GLOBAL', task: options.task, platform: options.platform,
|
|
879
|
+
analyzeOptions: options,
|
|
880
|
+
}));
|
|
878
881
|
}
|
|
879
882
|
else if (currentStep === 'global-analysis') {
|
|
880
|
-
prompt = await
|
|
881
|
-
iteration: iter || 'GLOBAL',
|
|
882
|
-
}
|
|
883
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
884
|
+
iteration: iter || 'GLOBAL', analyzeOptions: options,
|
|
885
|
+
}));
|
|
883
886
|
}
|
|
884
887
|
else if (currentStep === 'consistency-check') {
|
|
885
888
|
prompt = `\n# 任务: 全局一致性检查\n\n` +
|
|
@@ -891,9 +894,9 @@ async function analyzeCommand(options) {
|
|
|
891
894
|
`汇总所有分析结果,生成 .speccore/GLOBAL/INDEX.md。\n`;
|
|
892
895
|
}
|
|
893
896
|
else {
|
|
894
|
-
prompt = await
|
|
895
|
-
iteration: iter || 'GLOBAL',
|
|
896
|
-
}
|
|
897
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
898
|
+
iteration: iter || 'GLOBAL', analyzeOptions: options,
|
|
899
|
+
}));
|
|
897
900
|
}
|
|
898
901
|
}
|
|
899
902
|
else if (currentStep === 'clarify-product' || currentStep === 'clarify-interaction' || currentStep === 'clarify-security') {
|
|
@@ -905,10 +908,10 @@ async function analyzeCommand(options) {
|
|
|
905
908
|
prompt = await buildConfirmCheckPrompt(iter);
|
|
906
909
|
}
|
|
907
910
|
else if (currentStep === 'phase1-prompt') {
|
|
908
|
-
prompt = await
|
|
909
|
-
iteration: iter, task: options.task,
|
|
910
|
-
|
|
911
|
-
}
|
|
911
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
912
|
+
iteration: iter, task: options.task, platform: options.platform,
|
|
913
|
+
analyzeOptions: options,
|
|
914
|
+
}));
|
|
912
915
|
}
|
|
913
916
|
else if (currentStep === 'contract-prompt') {
|
|
914
917
|
// 契约先行阶段:基于 Phase 1 文档生成跨端契约
|
|
@@ -917,16 +920,17 @@ async function analyzeCommand(options) {
|
|
|
917
920
|
else if (platformMatch) {
|
|
918
921
|
// 逐端推进阶段:为指定端生成专属文档
|
|
919
922
|
const platform = platformMatch[1];
|
|
920
|
-
prompt = await
|
|
921
|
-
iteration: iter,
|
|
922
|
-
|
|
923
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
924
|
+
iteration: iter, platform,
|
|
925
|
+
analyzeOptions: { ...options, phase: '2' },
|
|
926
|
+
}));
|
|
923
927
|
}
|
|
924
928
|
else {
|
|
925
929
|
// 回退到默认 prompt
|
|
926
|
-
prompt = await
|
|
927
|
-
iteration: iter, task: options.task,
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
+
prompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
931
|
+
iteration: iter, task: options.task, platform: options.platform,
|
|
932
|
+
analyzeOptions: options,
|
|
933
|
+
}));
|
|
930
934
|
}
|
|
931
935
|
// 添加 Pipeline 继续指令
|
|
932
936
|
const totalSteps = steps.filter(s => !s.id.endsWith('-done') && s.id !== 'done').length;
|
|
@@ -988,11 +992,10 @@ async function analyzeCommand(options) {
|
|
|
988
992
|
const prompts = [];
|
|
989
993
|
for (let i = 0; i < targetTasks.length; i++) {
|
|
990
994
|
const taskId = targetTasks[i];
|
|
991
|
-
const singlePrompt = await
|
|
992
|
-
iteration: iter, task: taskId,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}, options);
|
|
995
|
+
const singlePrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
996
|
+
iteration: iter, task: taskId, platform: options.platform,
|
|
997
|
+
analyzeOptions: options,
|
|
998
|
+
}));
|
|
996
999
|
prompts.push(`<!-- ═══════════════════════════════════════════ -->
|
|
997
1000
|
<!-- 任务 ${i + 1}/${targetTasks.length}: ${taskId} -->
|
|
998
1001
|
<!-- ═══════════════════════════════════════════ -->
|
|
@@ -1003,7 +1006,14 @@ ${singlePrompt}`);
|
|
|
1003
1006
|
process.exitCode = 10;
|
|
1004
1007
|
return;
|
|
1005
1008
|
}
|
|
1006
|
-
|
|
1009
|
+
// v8.3.160+: 统一使用 buildPrompt 入口
|
|
1010
|
+
const promptObj = await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
1011
|
+
iteration: iter,
|
|
1012
|
+
task: options.task,
|
|
1013
|
+
platform: options.platform,
|
|
1014
|
+
analyzeOptions: options,
|
|
1015
|
+
});
|
|
1016
|
+
const prompt = (0, prompt_builder_1.buildPromptText)(promptObj);
|
|
1007
1017
|
process.stdout.write(`[SPECCORE_PROMPT]\n${prompt}`);
|
|
1008
1018
|
process.exitCode = 10;
|
|
1009
1019
|
return;
|
|
@@ -1050,6 +1060,14 @@ ${singlePrompt}`);
|
|
|
1050
1060
|
return;
|
|
1051
1061
|
}
|
|
1052
1062
|
}
|
|
1063
|
+
// v8.3.161+: AI 上下文中禁止 --skip-clarify(质量门禁不可被 AI 自行绕过)
|
|
1064
|
+
if (options.skipClarify && !process.stdout.isTTY) {
|
|
1065
|
+
logger_1.logger.warn('⚠️ AI 上下文检测到 --skip-clarify,此行为不推荐');
|
|
1066
|
+
logger_1.logger.info(' 需求澄清是质量门禁,应由人类用户确认后使用');
|
|
1067
|
+
logger_1.logger.info(' 如果需求文档已经过 clarify 流程,会自动跳过澄清阶段');
|
|
1068
|
+
logger_1.logger.info(' 如需强制跳过,请在交互式终端中执行此命令');
|
|
1069
|
+
// 不阻止,但输出强烈警告(保持向后兼容)
|
|
1070
|
+
}
|
|
1053
1071
|
// v8.3.0+: 解析 [CLARIFY:xxx] 标记 — 需求澄清是强制前置步骤
|
|
1054
1072
|
const clarifyBlocks = parseClarifyMarkers(options.apply);
|
|
1055
1073
|
if (clarifyBlocks.size > 0 && !isGlobalScope) {
|
|
@@ -1111,6 +1129,32 @@ ${singlePrompt}`);
|
|
|
1111
1129
|
if (options.apply.startsWith('{')) {
|
|
1112
1130
|
try {
|
|
1113
1131
|
const docs = JSON.parse(options.apply);
|
|
1132
|
+
const docEntries = Object.entries(docs);
|
|
1133
|
+
// v8.3.161+: 批量 apply 防护 — JSON 模式同样限制文档数量
|
|
1134
|
+
const MAX_DOCS_PER_APPLY = 3;
|
|
1135
|
+
if (docEntries.length > MAX_DOCS_PER_APPLY) {
|
|
1136
|
+
logger_1.logger.error(`❌ 批量 apply 拦截: JSON 包含 ${docEntries.length} 个文档,超过单次上限 ${MAX_DOCS_PER_APPLY}`);
|
|
1137
|
+
logger_1.logger.info(' 请分批 apply,每次不超过 3 个文档:');
|
|
1138
|
+
const docNames = docEntries.map(([k]) => k);
|
|
1139
|
+
for (let i = 0; i < docNames.length; i += MAX_DOCS_PER_APPLY) {
|
|
1140
|
+
const batch = docNames.slice(i, i + MAX_DOCS_PER_APPLY);
|
|
1141
|
+
logger_1.logger.info(` 批次 ${Math.floor(i / MAX_DOCS_PER_APPLY) + 1}: ${batch.join(', ')}`);
|
|
1142
|
+
}
|
|
1143
|
+
logger_1.logger.info('');
|
|
1144
|
+
logger_1.logger.info(' 原因:单会话内批量生成过多文档会导致上下文溢出、质量下降');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
// v8.3.161+: 迭代级 analyze 禁止直接写入 overview/REQUIREMENT.md
|
|
1148
|
+
if (!isGlobalScope) {
|
|
1149
|
+
for (const filename of Object.keys(docs)) {
|
|
1150
|
+
if (filename === 'overview/REQUIREMENT.md' || filename === 'REQUIREMENT.md') {
|
|
1151
|
+
logger_1.logger.error(`❌ 写入路径拦截: analyze --apply 不允许直接写入迭代级的 ${filename}`);
|
|
1152
|
+
logger_1.logger.info(' 原始需求文档应在 010-requirements/ 中维护');
|
|
1153
|
+
logger_1.logger.info(' analyze 的输出应为分析规格文档(TECH.md / TEST.md / UI_SPEC.md 等)');
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1114
1158
|
let count = 0;
|
|
1115
1159
|
if (isTaskLevel && taskDir) {
|
|
1116
1160
|
// 任务级:写 Task/00-specs/(v6.44.0+ 统一写入 00-specs/)
|
|
@@ -1368,15 +1412,36 @@ ${singlePrompt}`);
|
|
|
1368
1412
|
skippedCount++;
|
|
1369
1413
|
continue;
|
|
1370
1414
|
}
|
|
1371
|
-
// 综合文档写入 overview/ 子目录,端专属文档写入 {端}/ 子目录
|
|
1415
|
+
// 综合文档写入 overview/ 子目录,端专属文档写入 {功能模块}/{端}/ 子目录
|
|
1372
1416
|
// v8.3.24+: 按文件名前缀路由,支持多段指定
|
|
1373
|
-
//
|
|
1417
|
+
// v8.3.25+: 如果 AI 输出旧格式 {端}/{文件},尝试回退到功能模块路由
|
|
1374
1418
|
let targetDir;
|
|
1375
1419
|
if (globalSet.has(cleanFilename)) {
|
|
1376
1420
|
targetDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
|
|
1377
1421
|
}
|
|
1378
1422
|
else if (platformDir && validPlatforms.has(platformDir)) {
|
|
1379
|
-
|
|
1423
|
+
// 检查是否应归入功能模块(骨架文件已按功能模块创建)
|
|
1424
|
+
let featureFallback = null;
|
|
1425
|
+
try {
|
|
1426
|
+
const specEntries = await (0, fs_extra_2.readdir)(specDir, { withFileTypes: true });
|
|
1427
|
+
for (const entry of specEntries) {
|
|
1428
|
+
if (!entry.isDirectory() || entry.name === spec_paths_1.GLOBAL_SPECS_DIR || validPlatforms.has(entry.name))
|
|
1429
|
+
continue;
|
|
1430
|
+
const platformInFeature = (0, path_1.join)(specDir, entry.name, platformDir);
|
|
1431
|
+
if (await (0, fs_extra_1.pathExists)(platformInFeature)) {
|
|
1432
|
+
featureFallback = entry.name;
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
catch { }
|
|
1438
|
+
if (featureFallback) {
|
|
1439
|
+
targetDir = (0, path_1.join)(specDir, featureFallback, platformDir);
|
|
1440
|
+
logger_1.logger.info(` 📁 自动归入功能模块: ${featureFallback}/${platformDir}/${cleanFilename.split('/').pop()}`);
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
targetDir = (0, path_1.join)(specDir, platformDir);
|
|
1444
|
+
}
|
|
1380
1445
|
}
|
|
1381
1446
|
else {
|
|
1382
1447
|
targetDir = specDir;
|
|
@@ -1545,6 +1610,33 @@ ${singlePrompt}`);
|
|
|
1545
1610
|
// v8.2.0+: [DOC:xxx] 标记解析 —— 统一报告自动拆分
|
|
1546
1611
|
// AI 输出格式: [DOC:REQUIREMENT.md]...内容...[DOC:TECH.md]...内容...
|
|
1547
1612
|
const docBlocks = parseDocMarkers(options.apply);
|
|
1613
|
+
// v8.3.161+: 批量 apply 防护 — 禁止单会话内批量写入过多文档
|
|
1614
|
+
const MAX_DOCS_PER_APPLY = 3;
|
|
1615
|
+
if (docBlocks.size > MAX_DOCS_PER_APPLY) {
|
|
1616
|
+
logger_1.logger.error(`❌ 批量 apply 拦截: 检测到 ${docBlocks.size} 个 [DOC:xxx] 文档,超过单次上限 ${MAX_DOCS_PER_APPLY}`);
|
|
1617
|
+
logger_1.logger.info(' 请分批 apply,每次不超过 3 个文档:');
|
|
1618
|
+
const docNames = Array.from(docBlocks.keys());
|
|
1619
|
+
for (let i = 0; i < docNames.length; i += MAX_DOCS_PER_APPLY) {
|
|
1620
|
+
const batch = docNames.slice(i, i + MAX_DOCS_PER_APPLY);
|
|
1621
|
+
logger_1.logger.info(` 批次 ${Math.floor(i / MAX_DOCS_PER_APPLY) + 1}: ${batch.join(', ')}`);
|
|
1622
|
+
}
|
|
1623
|
+
logger_1.logger.info('');
|
|
1624
|
+
logger_1.logger.info(' 原因:单会话内批量生成过多文档会导致上下文溢出、质量下降');
|
|
1625
|
+
logger_1.logger.info(' 请在新会话中逐个批次执行,确保每个文档的质量深入');
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
// v8.3.161+: 迭代级 analyze 禁止直接写入 overview/REQUIREMENT.md
|
|
1629
|
+
if (!isGlobalScope) {
|
|
1630
|
+
for (const docName of docBlocks.keys()) {
|
|
1631
|
+
if (docName === 'overview/REQUIREMENT.md' || docName === 'REQUIREMENT.md') {
|
|
1632
|
+
logger_1.logger.error(`❌ 写入路径拦截: analyze --apply 不允许直接写入迭代级的 ${docName}`);
|
|
1633
|
+
logger_1.logger.info(' 原始需求文档应在 010-requirements/ 中维护');
|
|
1634
|
+
logger_1.logger.info(' analyze 的输出应为分析规格文档(TECH.md / TEST.md / UI_SPEC.md 等)');
|
|
1635
|
+
logger_1.logger.info(` 如需更新需求文档,请使用: speccore doc2spec 或手动编辑 010-requirements/`);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1548
1640
|
if (docBlocks.size > 1) {
|
|
1549
1641
|
// 将 [DOC:xxx] 格式转换为 JSON 格式复用现有写入逻辑
|
|
1550
1642
|
const docs = {};
|
|
@@ -1930,9 +2022,9 @@ ${singlePrompt}`);
|
|
|
1930
2022
|
if (isGlobalScope) {
|
|
1931
2023
|
// 全局层 Pipeline 步骤映射
|
|
1932
2024
|
if (state.currentStep === 'global-analysis') {
|
|
1933
|
-
nextPrompt = await
|
|
1934
|
-
iteration: options.iteration || 'GLOBAL',
|
|
1935
|
-
});
|
|
2025
|
+
nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
2026
|
+
iteration: options.iteration || 'GLOBAL', analyzeOptions: options,
|
|
2027
|
+
}));
|
|
1936
2028
|
}
|
|
1937
2029
|
else if (state.currentStep === 'consistency-check') {
|
|
1938
2030
|
nextPrompt = `\n# 任务: 全局一致性检查\n\n` +
|
|
@@ -1944,9 +2036,9 @@ ${singlePrompt}`);
|
|
|
1944
2036
|
`汇总所有分析结果,生成 .speccore/GLOBAL/INDEX.md。\n`;
|
|
1945
2037
|
}
|
|
1946
2038
|
else {
|
|
1947
|
-
nextPrompt = await
|
|
1948
|
-
iteration: options.iteration || 'GLOBAL',
|
|
1949
|
-
});
|
|
2039
|
+
nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
2040
|
+
iteration: options.iteration || 'GLOBAL', analyzeOptions: options,
|
|
2041
|
+
}));
|
|
1950
2042
|
}
|
|
1951
2043
|
// v8.3.160+: clarify 阶段推进(拆分为 product / interaction / security)
|
|
1952
2044
|
}
|
|
@@ -1961,23 +2053,23 @@ ${singlePrompt}`);
|
|
|
1961
2053
|
}
|
|
1962
2054
|
else if (nextPlatformMatch) {
|
|
1963
2055
|
const platform = nextPlatformMatch[1];
|
|
1964
|
-
nextPrompt = await
|
|
1965
|
-
iteration: options.iteration,
|
|
1966
|
-
phase: '2',
|
|
1967
|
-
|
|
1968
|
-
});
|
|
2056
|
+
nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
2057
|
+
iteration: options.iteration, platform,
|
|
2058
|
+
analyzeOptions: { ...options, phase: '2' },
|
|
2059
|
+
}));
|
|
1969
2060
|
}
|
|
1970
2061
|
else if (state.currentStep === 'phase2-prompt') {
|
|
1971
2062
|
// 兼容旧 Pipeline 步骤名
|
|
1972
|
-
nextPrompt = await
|
|
2063
|
+
nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
1973
2064
|
iteration: options.iteration,
|
|
1974
|
-
phase: '2',
|
|
1975
|
-
});
|
|
2065
|
+
analyzeOptions: { ...options, phase: '2' },
|
|
2066
|
+
}));
|
|
1976
2067
|
}
|
|
1977
2068
|
else {
|
|
1978
|
-
nextPrompt = await
|
|
2069
|
+
nextPrompt = (0, prompt_builder_1.buildPromptText)(await (0, prompt_builder_1.buildPrompt)('analyze', {
|
|
1979
2070
|
iteration: options.iteration,
|
|
1980
|
-
|
|
2071
|
+
analyzeOptions: options,
|
|
2072
|
+
}));
|
|
1981
2073
|
}
|
|
1982
2074
|
// v8.3.160+: 步骤隔离模式输出标记 + 子 Agent 激活
|
|
1983
2075
|
const scopeFlag = isGlobalScope ? '--scope global' : `-I ${options.iteration}`;
|
|
@@ -2656,30 +2748,60 @@ async function detectGlobalLayerProgress() {
|
|
|
2656
2748
|
missing.push('Layer 2: platforms/_shared/_ASSOCIATION.md');
|
|
2657
2749
|
}
|
|
2658
2750
|
}
|
|
2659
|
-
// Layer 3:
|
|
2751
|
+
// Layer 3: 检查功能模块深入文档(v8.3.160+: 支持模块级进度追踪)
|
|
2752
|
+
let pendingModules;
|
|
2660
2753
|
if (completedLayer >= 2) {
|
|
2661
2754
|
try {
|
|
2755
|
+
// v8.3.160+: 优先从 _MODULES.md 读取模块列表,进行模块级进度检测
|
|
2756
|
+
const moduleNames = await parseModuleNamesFromModulesMd(globalDir);
|
|
2662
2757
|
const platformsDir = (0, path_1.join)(globalDir, 'platforms');
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2758
|
+
if (moduleNames && moduleNames.length > 0) {
|
|
2759
|
+
// 检查 platforms/_shared/modules/{模块名}.md(v8.3.160+ 统一存放位置)
|
|
2760
|
+
const sharedModulesDir = (0, path_1.join)(platformsDir, '_shared', 'modules');
|
|
2761
|
+
const completedModules = new Set();
|
|
2762
|
+
if (await (0, fs_extra_1.pathExists)(sharedModulesDir)) {
|
|
2763
|
+
const files = await (0, fs_extra_2.readdir)(sharedModulesDir).catch(() => []);
|
|
2764
|
+
for (const f of files.filter(f => f.endsWith('.md'))) {
|
|
2765
|
+
const baseName = f.replace('.md', '').replace(/-/g, '');
|
|
2766
|
+
for (const modName of moduleNames) {
|
|
2767
|
+
const normalizedMod = modName.toLowerCase().replace(/[^\u4e00-\u9fffa-z0-9]/g, '');
|
|
2768
|
+
if (baseName.includes(normalizedMod) || normalizedMod.includes(baseName)) {
|
|
2769
|
+
completedModules.add(modName);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
pendingModules = moduleNames.filter(m => !completedModules.has(m));
|
|
2775
|
+
if (pendingModules.length === 0) {
|
|
2776
|
+
completedLayer = 3;
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
missing.push(`Layer 3: 还有 ${pendingModules.length}/${moduleNames.length} 个功能模块待分析`);
|
|
2780
|
+
}
|
|
2667
2781
|
}
|
|
2668
2782
|
else {
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
return files.some(f => f.endsWith('.md'));
|
|
2675
|
-
}));
|
|
2676
|
-
if (moduleDocChecks.every(Boolean)) {
|
|
2677
|
-
completedLayer = 3;
|
|
2783
|
+
// 回退:检查各端 modules/ 目录是否有文档(旧逻辑)
|
|
2784
|
+
const entries = await (0, fs_extra_2.readdir)(platformsDir, { withFileTypes: true });
|
|
2785
|
+
const platformDirs = entries.filter(e => e.isDirectory() && e.name !== '_shared').map(e => e.name);
|
|
2786
|
+
if (platformDirs.length === 0) {
|
|
2787
|
+
missing.push('Layer 3: platforms/{端}/modules/*.md(功能模块深入文档)');
|
|
2678
2788
|
}
|
|
2679
2789
|
else {
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2790
|
+
const moduleDocChecks = await Promise.all(platformDirs.map(async (d) => {
|
|
2791
|
+
const modulesDir = (0, path_1.join)(platformsDir, d, 'modules');
|
|
2792
|
+
if (!await (0, fs_extra_1.pathExists)(modulesDir))
|
|
2793
|
+
return false;
|
|
2794
|
+
const files = await (0, fs_extra_2.readdir)(modulesDir).catch(() => []);
|
|
2795
|
+
return files.some(f => f.endsWith('.md'));
|
|
2796
|
+
}));
|
|
2797
|
+
if (moduleDocChecks.every(Boolean)) {
|
|
2798
|
+
completedLayer = 3;
|
|
2799
|
+
}
|
|
2800
|
+
else {
|
|
2801
|
+
for (const [i, d] of platformDirs.entries()) {
|
|
2802
|
+
if (!moduleDocChecks[i]) {
|
|
2803
|
+
missing.push(`Layer 3: platforms/${d}/modules/*.md(功能模块深入文档)`);
|
|
2804
|
+
}
|
|
2683
2805
|
}
|
|
2684
2806
|
}
|
|
2685
2807
|
}
|
|
@@ -2691,6 +2813,7 @@ async function detectGlobalLayerProgress() {
|
|
|
2691
2813
|
}
|
|
2692
2814
|
// Layer 4 子层检测
|
|
2693
2815
|
let subLayer;
|
|
2816
|
+
let pendingPlatforms = [];
|
|
2694
2817
|
if (completedLayer >= 3) {
|
|
2695
2818
|
const overviewDir = (0, path_1.join)(globalDir, 'overview');
|
|
2696
2819
|
const requirementsDir = (0, path_1.join)(globalDir, 'requirements');
|
|
@@ -2751,6 +2874,7 @@ async function detectGlobalLayerProgress() {
|
|
|
2751
2874
|
}
|
|
2752
2875
|
}
|
|
2753
2876
|
// 4d: 各端技术文档(v8.3.0+ 修复:每个端都要有,不再用 some(Boolean))
|
|
2877
|
+
// v8.3.160+: 记录未完成的端,用于端级分批
|
|
2754
2878
|
try {
|
|
2755
2879
|
const platformsDir = (0, path_1.join)(globalDir, 'platforms');
|
|
2756
2880
|
const entries = await (0, fs_extra_2.readdir)(platformsDir, { withFileTypes: true });
|
|
@@ -2758,25 +2882,38 @@ async function detectGlobalLayerProgress() {
|
|
|
2758
2882
|
const missingPlatformDocs = [];
|
|
2759
2883
|
for (const d of platformDirs) {
|
|
2760
2884
|
const isBackend = /service|server|api|backend/i.test(d);
|
|
2885
|
+
let hasMissing = false;
|
|
2761
2886
|
if (isBackend) {
|
|
2762
2887
|
const hasApi = await (0, fs_extra_1.pathExists)((0, path_1.join)(platformsDir, d, 'API_INVENTORY.md'));
|
|
2763
2888
|
const hasData = await (0, fs_extra_1.pathExists)((0, path_1.join)(platformsDir, d, 'DATA_MODEL.md'));
|
|
2764
|
-
if (!hasApi)
|
|
2889
|
+
if (!hasApi) {
|
|
2765
2890
|
missingPlatformDocs.push(`platforms/${d}/API_INVENTORY.md`);
|
|
2766
|
-
|
|
2891
|
+
hasMissing = true;
|
|
2892
|
+
}
|
|
2893
|
+
if (!hasData) {
|
|
2767
2894
|
missingPlatformDocs.push(`platforms/${d}/DATA_MODEL.md`);
|
|
2895
|
+
hasMissing = true;
|
|
2896
|
+
}
|
|
2768
2897
|
}
|
|
2769
2898
|
else {
|
|
2770
2899
|
const hasUi = await (0, fs_extra_1.pathExists)((0, path_1.join)(platformsDir, d, 'UI_FLOW.md'));
|
|
2771
2900
|
const hasApiMap = await (0, fs_extra_1.pathExists)((0, path_1.join)(platformsDir, d, 'API_CALL_MAP.md'));
|
|
2772
2901
|
const hasState = await (0, fs_extra_1.pathExists)((0, path_1.join)(platformsDir, d, 'STATE_MANAGEMENT.md'));
|
|
2773
|
-
if (!hasUi)
|
|
2902
|
+
if (!hasUi) {
|
|
2774
2903
|
missingPlatformDocs.push(`platforms/${d}/UI_FLOW.md`);
|
|
2775
|
-
|
|
2904
|
+
hasMissing = true;
|
|
2905
|
+
}
|
|
2906
|
+
if (!hasApiMap) {
|
|
2776
2907
|
missingPlatformDocs.push(`platforms/${d}/API_CALL_MAP.md`);
|
|
2777
|
-
|
|
2908
|
+
hasMissing = true;
|
|
2909
|
+
}
|
|
2910
|
+
if (!hasState) {
|
|
2778
2911
|
missingPlatformDocs.push(`platforms/${d}/STATE_MANAGEMENT.md`);
|
|
2912
|
+
hasMissing = true;
|
|
2913
|
+
}
|
|
2779
2914
|
}
|
|
2915
|
+
if (hasMissing)
|
|
2916
|
+
pendingPlatforms.push(d);
|
|
2780
2917
|
}
|
|
2781
2918
|
if (missingPlatformDocs.length === 0 && platformDirs.length > 0) {
|
|
2782
2919
|
completedSubLayers.push('4d');
|
|
@@ -2798,12 +2935,12 @@ async function detectGlobalLayerProgress() {
|
|
|
2798
2935
|
subLayer = { completed: completedSubLayers, next: nextSub };
|
|
2799
2936
|
}
|
|
2800
2937
|
}
|
|
2801
|
-
return { completedLayer, nextLayer: Math.min(completedLayer + 1, 4), missing, subLayer };
|
|
2938
|
+
return { completedLayer, nextLayer: Math.min(completedLayer + 1, 4), missing, subLayer, pendingModules, pendingPlatforms };
|
|
2802
2939
|
}
|
|
2803
2940
|
// ── v7.2.0+: 全局分析下一步引导 ──
|
|
2804
2941
|
async function buildGlobalAnalysisGuide(options) {
|
|
2805
2942
|
const progress = await detectGlobalLayerProgress();
|
|
2806
|
-
const { completedLayer, nextLayer, subLayer } = progress;
|
|
2943
|
+
const { completedLayer, nextLayer, subLayer, pendingModules, pendingPlatforms } = progress;
|
|
2807
2944
|
const deepDoc = options?.deep;
|
|
2808
2945
|
let guide = '';
|
|
2809
2946
|
// 进度条
|
|
@@ -2814,6 +2951,23 @@ async function buildGlobalAnalysisGuide(options) {
|
|
|
2814
2951
|
return ` ${status} ${l}`;
|
|
2815
2952
|
}).join('\n');
|
|
2816
2953
|
guide += '\n';
|
|
2954
|
+
// v8.3.160+: Layer 3 模块级进度显示
|
|
2955
|
+
if (pendingModules && pendingModules.length > 0) {
|
|
2956
|
+
guide += `\n 📦 Layer 3 待分析模块 (${pendingModules.length} 个):\n`;
|
|
2957
|
+
for (const mod of pendingModules.slice(0, 10)) {
|
|
2958
|
+
guide += ` ⬜ ${mod}\n`;
|
|
2959
|
+
}
|
|
2960
|
+
if (pendingModules.length > 10) {
|
|
2961
|
+
guide += ` ... 还有 ${pendingModules.length - 10} 个模块\n`;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
// v8.3.160+: Layer 4d 端级进度显示
|
|
2965
|
+
if (pendingPlatforms && pendingPlatforms.length > 0) {
|
|
2966
|
+
guide += `\n 🖥️ Layer 4d 待处理端 (${pendingPlatforms.length} 个):\n`;
|
|
2967
|
+
for (const p of pendingPlatforms) {
|
|
2968
|
+
guide += ` ⬜ ${p}\n`;
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2817
2971
|
if (completedLayer === 4 && (!subLayer || subLayer.completed.length === 4)) {
|
|
2818
2972
|
guide += '\n🎉 全局分析全部完成!\n';
|
|
2819
2973
|
guide += ' 所有文档已生成在 .speccore/GLOBAL/ 目录\n';
|
|
@@ -2863,9 +3017,9 @@ async function buildGlobalAnalysisGuide(options) {
|
|
|
2863
3017
|
}
|
|
2864
3018
|
// ── v7.4.5+: Layer 3 功能模块代码上下文预提取 ──
|
|
2865
3019
|
// 从 _MODULES.md 解析功能模块名,从 structured-data.json 交叉匹配相关 API/Entity/Component/Route
|
|
2866
|
-
|
|
3020
|
+
// v8.3.160+: 提取公共模块解析函数,供进度检测复用
|
|
3021
|
+
async function parseModuleNamesFromModulesMd(projectRoot) {
|
|
2867
3022
|
const modulesPath = (0, path_1.join)(projectRoot, '.speccore', 'GLOBAL', 'platforms', '_shared', '_MODULES.md');
|
|
2868
|
-
const structDataPath = (0, path_1.join)(projectRoot, '.speccore', 'cache', 'structured-data.json');
|
|
2869
3023
|
if (!(await (0, fs_extra_1.pathExists)(modulesPath)))
|
|
2870
3024
|
return null;
|
|
2871
3025
|
const modulesContent = await (0, fs_extra_2.readFile)(modulesPath, 'utf-8');
|
|
@@ -2887,8 +3041,13 @@ async function buildLayer3ModuleContext(projectRoot) {
|
|
|
2887
3041
|
moduleNames.push(name);
|
|
2888
3042
|
}
|
|
2889
3043
|
}
|
|
2890
|
-
|
|
3044
|
+
return moduleNames.length > 0 ? moduleNames : null;
|
|
3045
|
+
}
|
|
3046
|
+
async function buildLayer3ModuleContext(projectRoot) {
|
|
3047
|
+
const moduleNames = await parseModuleNamesFromModulesMd(projectRoot);
|
|
3048
|
+
if (!moduleNames)
|
|
2891
3049
|
return null;
|
|
3050
|
+
const structDataPath = (0, path_1.join)(projectRoot, '.speccore', 'cache', 'structured-data.json');
|
|
2892
3051
|
// 读取结构化数据
|
|
2893
3052
|
let structData = null;
|
|
2894
3053
|
if (await (0, fs_extra_1.pathExists)(structDataPath)) {
|
|
@@ -2964,6 +3123,7 @@ async function buildLayer3ModuleContext(projectRoot) {
|
|
|
2964
3123
|
return ctx.join('\n\n---\n\n');
|
|
2965
3124
|
}
|
|
2966
3125
|
// ── buildMultiDocPrompt: 多文档协议 ──
|
|
3126
|
+
// v8.3.160+: 改为 export,供 prompt-builder.ts 统一调用
|
|
2967
3127
|
async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
2968
3128
|
const iter = ctx.iteration || '当前迭代';
|
|
2969
3129
|
const task = ctx.task ? ` — ${ctx.task}` : '';
|
|
@@ -2975,6 +3135,27 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
2975
3135
|
// v8.3.0+: 从 clarifyCtx 提取需求澄清状态
|
|
2976
3136
|
const needsClarify = clarifyCtx?.needsClarify ?? false;
|
|
2977
3137
|
const clarifyTargets = clarifyCtx?.clarifyTargets ?? [];
|
|
3138
|
+
// v8.3.160+: 按 Phase 注入子 Agent 角色定义
|
|
3139
|
+
let agentContextText = '';
|
|
3140
|
+
const phaseSubagentMap = {
|
|
3141
|
+
'0': 'product-analyst',
|
|
3142
|
+
'1': 'spec-analyzer',
|
|
3143
|
+
'2': 'spec-analyzer',
|
|
3144
|
+
};
|
|
3145
|
+
const subagent = phaseSubagentMap[ctx.phase || ''] || (isGlobal ? 'spec-global-analyzer' : 'spec-analyzer');
|
|
3146
|
+
try {
|
|
3147
|
+
const agentCtx = {
|
|
3148
|
+
subagent,
|
|
3149
|
+
iteration: ctx.iteration || '',
|
|
3150
|
+
contextBudget: 12000,
|
|
3151
|
+
contextType: ctx.phase === '2' ? 'platform-only' : 'full',
|
|
3152
|
+
cwd: process.cwd(),
|
|
3153
|
+
};
|
|
3154
|
+
agentContextText = await agent_adapter_1.defaultAdapter.prepareContext(agentCtx);
|
|
3155
|
+
}
|
|
3156
|
+
catch (e) {
|
|
3157
|
+
logger_1.logger.debug?.(`agent-adapter 注入失败(非关键): ${e.message}`);
|
|
3158
|
+
}
|
|
2978
3159
|
// global 范围: 从源码反推需求 + 生成技术栈配置
|
|
2979
3160
|
if (isGlobal) {
|
|
2980
3161
|
// v6.74.0+: 流式全局分析模式
|
|
@@ -3064,6 +3245,10 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3064
3245
|
};
|
|
3065
3246
|
const layerMeta = LAYER_ROLES[targetLayer];
|
|
3066
3247
|
let prompt = `\n# 任务: ${command} (全局分析${ctx.withCode ? '+源码' : ''})\n\n`;
|
|
3248
|
+
// v8.3.160+: 注入子 Agent 角色定义
|
|
3249
|
+
if (agentContextText) {
|
|
3250
|
+
prompt = agentContextText + '\n\n' + prompt;
|
|
3251
|
+
}
|
|
3067
3252
|
// v7.2.0+: 分层专注指令 — 强烈约束 AI 只执行当前 layer
|
|
3068
3253
|
prompt += `## 🎯 当前执行层级: Layer ${targetLayer}/4 — ${layerMeta.role}\n\n`;
|
|
3069
3254
|
prompt += `> ⚠️ **重要约束**: 你当前只需要完成 **Layer ${targetLayer}** 的工作。不要提前做后续层的内容。\n`;
|
|
@@ -3099,36 +3284,22 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3099
3284
|
prompt += `**重要**: 不要直接扫描源码文件。已使用代码扫描工具提取了结构化数据,你只需要读取这些数据并整理成 _INDEX.md。\n\n`;
|
|
3100
3285
|
prompt += `**步骤**: \n`;
|
|
3101
3286
|
prompt += `1. Read \`.speccore/cache/structured-data.json\` — 获取所有端的 API/Entity/Route/Component 清单\n`;
|
|
3287
|
+
prompt += ` - **大项目优化**:如端数量 > 3,可按端读取 \`structured-data.{端名}.json\` 分段文件,避免一次性加载过多数据\n`;
|
|
3102
3288
|
prompt += `2. 对每个端,基于结构化数据生成 \`_INDEX.md\`(补充扫描工具未覆盖的内容)\n`;
|
|
3103
3289
|
prompt += `3. 扫描工具未覆盖的维度(消息队列、定时任务、配置、外部集成、日志监控、错误处理),需要 Read 相关配置文件补充\n\n`;
|
|
3104
|
-
|
|
3105
|
-
prompt +=
|
|
3106
|
-
prompt +=
|
|
3107
|
-
prompt +=
|
|
3108
|
-
prompt +=
|
|
3109
|
-
prompt +=
|
|
3110
|
-
prompt +=
|
|
3111
|
-
prompt +=
|
|
3112
|
-
prompt += `| 定时任务 | 搜索定时任务(@Scheduled/cron/agenda/node-cron) | 任务名、触发频率、执行类 |\n`;
|
|
3113
|
-
prompt += `| 配置管理 | 配置文件(application*.yml/.env/config/) | 环境变量名、Feature Flag、配置中心引用 |\n`;
|
|
3114
|
-
prompt += `| 外部集成 | 搜索第三方调用(HTTP client/SDK/微信支付/短信/邮件/OSS) | 集成目标、调用位置 |\n`;
|
|
3115
|
-
prompt += `| 日志监控 | 搜索日志/监控/埋点代码(logger/metrics/tracing) | 日志级别策略、埋点事件名 |\n`;
|
|
3116
|
-
prompt += `| 错误处理 | 搜索全局异常处理器(ExceptionHandler/ErrorBoundary) | 异常处理类名、错误码范围 |\n`;
|
|
3117
|
-
prompt += `| 依赖项 | pom.xml/package.json/go.mod/requirements.txt | 依赖项列表(识别公共服务候选、过期版本、已知 CVE) |\n\n`;
|
|
3118
|
-
prompt += `**前端端扫描维度(10项)**:\n`;
|
|
3119
|
-
prompt += `| 扫描项 | 读取位置 | 提取内容 |\n`;
|
|
3120
|
-
prompt += `| :--- | :--- | :--- |\n`;
|
|
3121
|
-
prompt += `| 路由 | router/routes 配置文件 | 页面路径、页面名称、组件名、懒加载标记 |\n`;
|
|
3122
|
-
prompt += `| 页面 | pages/views/screens 目录 | 页面名称、主要功能(从文件名推断) |\n`;
|
|
3123
|
-
prompt += `| API 调用 | 搜索 API 调用模式(axios/fetch/$.ajax/uni.request) | 调用的接口路径列表、调用位置 |\n`;
|
|
3124
|
-
prompt += `| 状态管理 | store/pinia/vuex/redux 目录 | 全局状态名称、actions 名称、持久化策略 |\n`;
|
|
3125
|
-
prompt += `| 组件库 | components/ui 目录、设计系统配置 | 组件名、复用度、设计 token |\n`;
|
|
3126
|
-
prompt += `| 拦截器 | 请求/响应拦截器配置 | 拦截器逻辑(鉴权头注入、错误统一处理) |\n`;
|
|
3127
|
-
prompt += `| 外部 SDK | 搜索第三方 SDK 引入(埋点/推送/地图/支付/分享) | SDK 名称、初始化位置、使用范围 |\n`;
|
|
3128
|
-
prompt += `| 国际化 | i18n/locales/lang 目录 | 支持语言、命名空间、 key 数量级 |\n`;
|
|
3129
|
-
prompt += `| 错误处理 | 错误边界/全局错误处理器 | 错误捕获范围、降级策略、上报机制 |\n`;
|
|
3130
|
-
prompt += `| 性能 | 搜索性能相关代码(懒加载/虚拟滚动/缓存/预加载) | 优化手段、适用场景 |\n\n`;
|
|
3290
|
+
// v8.3.160+: 扫描维度从详细表格精简为分类列表
|
|
3291
|
+
prompt += `**后端端扫描维度**(从 structured-data.json + 源码补充):\n`;
|
|
3292
|
+
prompt += `- 接口层(Controller/Handler)、数据层(Entity/Model)、业务层(Service/UseCase)\n`;
|
|
3293
|
+
prompt += `- 中间件、消息队列、定时任务、配置管理、外部集成\n`;
|
|
3294
|
+
prompt += `- 日志监控、错误处理、依赖项(识别过期版本/CVE)\n\n`;
|
|
3295
|
+
prompt += `**前端端扫描维度**:\n`;
|
|
3296
|
+
prompt += `- 路由、页面、API 调用、状态管理、组件库\n`;
|
|
3297
|
+
prompt += `- 拦截器、外部 SDK、国际化、错误处理、性能优化\n\n`;
|
|
3131
3298
|
prompt += `**输出**:每个端一个 \`_INDEX.md\`,按上述维度组织,只含名称和路径列表,不含详细逻辑\n`;
|
|
3299
|
+
prompt += `**分批策略(v8.3.160+)**:\n`;
|
|
3300
|
+
prompt += `- 如端数量 ≤ 5:一次生成所有端的 \`_INDEX.md\`\n`;
|
|
3301
|
+
prompt += `- 如端数量 > 5:一次处理 3-5 个端,剩余端在输出末尾标注 \`[PENDING: {端名列表}]\`,供续批处理\n`;
|
|
3302
|
+
prompt += `- 续批命令:\`speccore analyze --scope global --layer 1 --resume\`\n`;
|
|
3132
3303
|
prompt += `**最小内容标准(v8.3.61+)**:\n`;
|
|
3133
3304
|
prompt += `- 每个 \_INDEX.md ≥ 80 行(维度少可接受 50+,但严禁 < 30 行)\n`;
|
|
3134
3305
|
prompt += `- 必须包含:端名、技术栈、目录结构概览、各维度清单表格\n`;
|
|
@@ -3170,48 +3341,25 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3170
3341
|
// ── Layer 2 指令(仅 targetLayer === 2 时注入)──
|
|
3171
3342
|
if (targetLayer === 2) {
|
|
3172
3343
|
prompt += `## 🔗 Layer 2: 跨端关联分析(基于 Layer 1 索引 + structured-data.json)\n\n`;
|
|
3173
|
-
prompt += `> 📊
|
|
3174
|
-
prompt +=
|
|
3175
|
-
prompt +=
|
|
3176
|
-
prompt +=
|
|
3177
|
-
prompt +=
|
|
3178
|
-
prompt +=
|
|
3179
|
-
prompt +=
|
|
3180
|
-
prompt +=
|
|
3181
|
-
prompt +=
|
|
3182
|
-
prompt +=
|
|
3183
|
-
prompt +=
|
|
3184
|
-
prompt +=
|
|
3185
|
-
prompt += ` - 识别「异步事件触发 → 多端状态同步」链路\n`;
|
|
3186
|
-
prompt += ` - 标注:无消费者的消息、无生产者的消息( orphaned topic )\n\n`;
|
|
3187
|
-
prompt += `4. **定时任务影响分析**:\n`;
|
|
3188
|
-
prompt += ` - 哪些定时任务修改了被前端展示的数据(数据新鲜度风险)\n`;
|
|
3189
|
-
prompt += ` - 哪些定时任务触发了前端需要感知的通知/推送\n`;
|
|
3190
|
-
prompt += ` - 批处理任务 vs 实时接口的数据竞争风险\n\n`;
|
|
3191
|
-
prompt += `5. **外部集成一致性检查**:\n`;
|
|
3192
|
-
prompt += ` - 多个端是否独立调用了同一第三方 API(重复集成 = 维护风险)\n`;
|
|
3193
|
-
prompt += ` - 前端 SDK 与后端 SDK 版本是否一致(如支付 SDK)\n`;
|
|
3194
|
-
prompt += ` - 第三方回调/Webhook 的接收端分布\n\n`;
|
|
3195
|
-
prompt += `6. **配置一致性检查**:\n`;
|
|
3196
|
-
prompt += ` - 各端的超时配置、重试策略、限流阈值是否一致\n`;
|
|
3197
|
-
prompt += ` - 跨端共享的 Feature Flag 定义是否一致\n`;
|
|
3198
|
-
prompt += ` - 环境变量命名是否规范(如 API_BASE_URL 在各端是否指向同一值)\n\n`;
|
|
3199
|
-
prompt += `7. **归纳功能模块**(从索引聚类,不是从代码反推):\n`;
|
|
3200
|
-
prompt += ` - **从页面聚类**:哪些页面经常一起出现(如 RoomList + RoomDetail + RoomEdit)\n`;
|
|
3201
|
-
prompt += ` - **从接口聚类**:哪些接口共享同一实体前缀(如 /api/rooms/*)\n`;
|
|
3202
|
-
prompt += ` - **从消息聚类**:哪些消息队列共享同一业务领域\n`;
|
|
3203
|
-
prompt += ` - **交叉验证**:页面聚类 vs 接口聚类 vs 消息聚类 → 确定功能模块边界\n`;
|
|
3204
|
-
prompt += ` - 每个功能模块标注:涉及端、核心页面、核心接口、实体名称、消息队列\n\n`;
|
|
3344
|
+
prompt += `> 📊 **输入**: Read \`.speccore/cache/structured-data.json\` 获取 API/Entity 清单,与 Layer 1 索引交叉验证\n\n`;
|
|
3345
|
+
prompt += `**分析维度**(7 项,每项产出写入 \_ASSOCIATION.md 对应章节):\n\n`;
|
|
3346
|
+
prompt += `| # | 维度 | 核心动作 | 风险标注 |\n`;
|
|
3347
|
+
prompt += `| :--- | :--- | :--- | :--- |\n`;
|
|
3348
|
+
prompt += `| 1 | 接口匹配 | 前端 API 路径 vs 后端接口路径 → 建立调用链路 | 接口缺口 / 未使用接口 |\n`;
|
|
3349
|
+
prompt += `| 2 | 公共服务 | 被 2+ 端调用的服务 / 独立部署服务 | 重复实现 / 版本不一致 |\n`;
|
|
3350
|
+
prompt += `| 3 | 消息流 | 生产者 ↔ 队列 ↔ 消费者 ↔ 前端推送链路 | orphaned topic / 多端同步缺失 |\n`;
|
|
3351
|
+
prompt += `| 4 | 定时任务 | 修改前端数据的任务 / 触发通知的任务 | 数据新鲜度 / 竞争风险 |\n`;
|
|
3352
|
+
prompt += `| 5 | 外部集成 | 多端独立调用同一第三方 API / SDK 版本 | 重复集成 / 版本漂移 |\n`;
|
|
3353
|
+
prompt += `| 6 | 配置一致性 | 超时/重试/限流 / Feature Flag / 环境变量 | 配置漂移 / 环境差异 |\n`;
|
|
3354
|
+
prompt += `| 7 | 功能模块聚类 | 页面聚类 vs 接口聚类 vs 消息聚类 → 交叉验证 | 边界模糊 / 职责不清 |\n\n`;
|
|
3355
|
+
prompt += `**模块归纳方法**:从索引聚类(非代码反推),每个模块标注:涉及端、核心页面、核心接口、实体名、消息队列\n\n`;
|
|
3205
3356
|
prompt += `**输出**:\n`;
|
|
3206
|
-
prompt += `- \`_ASSOCIATION.md
|
|
3207
|
-
prompt +=
|
|
3208
|
-
prompt += ` - 此文档中必须包含 **接口依赖 Mermaid 图**(graph TD),展示前端页面 → 后端接口的调用关系\n`;
|
|
3209
|
-
prompt += `- \`_MODULES.md\`:功能模块候选清单(从源码聚类,含消息/定时任务维度,供 Layer 3 验证)\n`;
|
|
3210
|
-
prompt += ` - 此文档中必须包含 **模块全景 Mermaid 图**(graph LR),展示所有功能模块及其所属端\n`;
|
|
3357
|
+
prompt += `- \`_ASSOCIATION.md\`:关联矩阵 + 接口缺口/未使用清单 + 消息流 + 定时任务影响 + 外部集成 + 配置风险 + 模块关系 Mermaid 图(graph LR)+ 接口依赖 Mermaid 图(graph TD)\n`;
|
|
3358
|
+
prompt += `- \`_MODULES.md\`:功能模块候选清单(含消息/定时任务维度)+ 模块全景 Mermaid 图(graph LR)\n`;
|
|
3211
3359
|
prompt += `**最小内容标准(v8.3.61+)**:\n`;
|
|
3212
|
-
prompt += `- \_ASSOCIATION.md ≥ 100
|
|
3213
|
-
prompt += `- \_MODULES.md ≥ 50
|
|
3214
|
-
prompt += `-
|
|
3360
|
+
prompt += `- \_ASSOCIATION.md ≥ 100 行,含关联矩阵 + ≥1 个 Mermaid 图\n`;
|
|
3361
|
+
prompt += `- \_MODULES.md ≥ 50 行,含模块清单 + 模块全景 Mermaid 图\n`;
|
|
3362
|
+
prompt += `- 严禁空壳文档\n`;
|
|
3215
3363
|
prompt += `**存放**:\`.speccore/GLOBAL/platforms/_shared/\`\n\n`;
|
|
3216
3364
|
} // end if (targetLayer === 2)
|
|
3217
3365
|
// ── Layer 3 指令(仅 targetLayer === 3 时注入)──
|
|
@@ -3240,6 +3388,12 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3240
3388
|
prompt += `- 读取 room-service: RoomController#getAvailability, RoomService 的详细逻辑\n`;
|
|
3241
3389
|
prompt += `- 关联验证:前端提交的数据字段 vs 后端接收的 DTO 字段是否一致\n`;
|
|
3242
3390
|
prompt += `- 关联验证:前端展示的状态 vs 后端实体的状态枚举是否一致\n\n`;
|
|
3391
|
+
prompt += `**分批策略(v8.3.160+)**:\n`;
|
|
3392
|
+
prompt += `- 如功能模块数量 ≤ 2:一次生成所有模块的分析文档\n`;
|
|
3393
|
+
prompt += `- 如功能模块数量 > 2:一次处理 1-2 个模块,确保每个模块分析深入、不空洞\n`;
|
|
3394
|
+
prompt += `- 剩余模块在输出末尾标注 \`[PENDING: {模块名列表}]\`,供续批处理\n`;
|
|
3395
|
+
prompt += `- 续批命令:\`speccore analyze --scope global --layer 3 --resume\`\n`;
|
|
3396
|
+
prompt += `- **关键原则**:宁可少分析几个模块,也要保证每个模块的分析质量(≥100 行、有 Mermaid 图、有具体代码引用)\n\n`;
|
|
3243
3397
|
prompt += `**每个功能模块输出**:\n`;
|
|
3244
3398
|
prompt += `- 后端端:该功能模块相关的 API 详细设计、数据模型、业务规则、安全策略\n`;
|
|
3245
3399
|
prompt += `- 前端端:该功能模块相关的页面详细设计、交互流程、字段映射、错误处理\n`;
|
|
@@ -3740,22 +3894,35 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3740
3894
|
}
|
|
3741
3895
|
}
|
|
3742
3896
|
// 4d: 动态构建各端文档清单
|
|
3897
|
+
// v8.3.160+: 按端分批,一次处理 1-2 个端的所有缺失文档
|
|
3898
|
+
const platformMissingDocs = {};
|
|
3743
3899
|
if (subLayerTarget === '4d') {
|
|
3744
3900
|
try {
|
|
3745
3901
|
const entries = await (0, fs_extra_2.readdir)(platformsDir, { withFileTypes: true });
|
|
3746
3902
|
const platformNames = entries.filter(e => e.isDirectory() && e.name !== '_shared').map(e => e.name);
|
|
3747
3903
|
for (const p of platformNames) {
|
|
3748
3904
|
const isBackend = /service|server|api|backend/i.test(p);
|
|
3905
|
+
const expectedPlatformDocs = [];
|
|
3749
3906
|
if (isBackend) {
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3907
|
+
expectedPlatformDocs.push(`platforms/${p}/API_INVENTORY.md`);
|
|
3908
|
+
expectedPlatformDocs.push(`platforms/${p}/DATA_MODEL.md`);
|
|
3909
|
+
expectedPlatformDocs.push(`platforms/${p}/BUSINESS_RULES.md`);
|
|
3753
3910
|
}
|
|
3754
3911
|
else {
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3912
|
+
expectedPlatformDocs.push(`platforms/${p}/UI_FLOW.md`);
|
|
3913
|
+
expectedPlatformDocs.push(`platforms/${p}/API_CALL_MAP.md`);
|
|
3914
|
+
expectedPlatformDocs.push(`platforms/${p}/STATE_MANAGEMENT.md`);
|
|
3915
|
+
}
|
|
3916
|
+
SUB_LAYER_DOCS['4d'].push(...expectedPlatformDocs);
|
|
3917
|
+
// 检查该端哪些文档缺失
|
|
3918
|
+
const missing = [];
|
|
3919
|
+
for (const doc of expectedPlatformDocs) {
|
|
3920
|
+
const fullPath = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), '.speccore', 'GLOBAL', doc);
|
|
3921
|
+
if (!(await (0, fs_extra_1.pathExists)(fullPath)))
|
|
3922
|
+
missing.push(doc);
|
|
3758
3923
|
}
|
|
3924
|
+
if (missing.length > 0)
|
|
3925
|
+
platformMissingDocs[p] = missing;
|
|
3759
3926
|
}
|
|
3760
3927
|
}
|
|
3761
3928
|
catch { }
|
|
@@ -3773,10 +3940,24 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3773
3940
|
missingDocs.push(doc);
|
|
3774
3941
|
}
|
|
3775
3942
|
}
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3943
|
+
// v8.3.160+: 4d 按端分批模式
|
|
3944
|
+
let targetPlatforms = [];
|
|
3945
|
+
let nextDoc = null;
|
|
3946
|
+
if (subLayerTarget === '4d' && Object.keys(platformMissingDocs).length > 0) {
|
|
3947
|
+
targetPlatforms = Object.keys(platformMissingDocs).slice(0, 2); // 一次 1-2 个端
|
|
3948
|
+
}
|
|
3949
|
+
else if (missingDocs.length > 0) {
|
|
3950
|
+
nextDoc = missingDocs[0];
|
|
3951
|
+
}
|
|
3952
|
+
prompt += `## 🌍 Layer 4: 全局汇总 — 子层 ${subLayerTarget}/4${subLayerTarget === '4d' ? '(v8.3.160+ 按端分批模式)' : '(v7.5.0+ 逐文档模式)'}\n\n`;
|
|
3953
|
+
if (subLayerTarget === '4d' && targetPlatforms.length > 0) {
|
|
3954
|
+
prompt += `> ⚠️ **专注约束**: 你当前只处理 **${targetPlatforms.join(', ')}** 端的技术文档。\n`;
|
|
3955
|
+
prompt += `> 一次生成这些端的所有缺失文档,不要处理其他端。\n`;
|
|
3956
|
+
}
|
|
3957
|
+
else if (nextDoc) {
|
|
3958
|
+
prompt += `> ⚠️ **专注约束**: 你当前只执行 **子层 ${subLayerTarget}** 中的 **下一个缺失文档**。\n`;
|
|
3959
|
+
prompt += `> 忽略前面关于其他子层文档的要求,只关注当前要生成的文档。\n`;
|
|
3960
|
+
}
|
|
3780
3961
|
prompt += `> 已存在: ${existingDocs.length}/${expectedDocs.length} 份文档\n`;
|
|
3781
3962
|
if (missingDocs.length > 0) {
|
|
3782
3963
|
prompt += `> 缺失: ${missingDocs.slice(0, 5).join('、')}${missingDocs.length > 5 ? '...' : ''}\n`;
|
|
@@ -3786,7 +3967,20 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3786
3967
|
prompt += `- Read Layer 1 的所有 _INDEX.md\n`;
|
|
3787
3968
|
prompt += `- Read Layer 2 的 _ASSOCIATION.md + _MODULES.md\n`;
|
|
3788
3969
|
prompt += `- Read Layer 3 的功能模块深入文档\n\n`;
|
|
3789
|
-
if (
|
|
3970
|
+
if (subLayerTarget === '4d' && targetPlatforms.length > 0) {
|
|
3971
|
+
// v8.3.160+: 按端分批生成所有缺失文档
|
|
3972
|
+
prompt += `## 🎯 本次处理端: ${targetPlatforms.join(', ')}\n\n`;
|
|
3973
|
+
for (const p of targetPlatforms) {
|
|
3974
|
+
const docs = platformMissingDocs[p];
|
|
3975
|
+
prompt += `**${p} 端缺失文档 (${docs.length} 份)**: \n`;
|
|
3976
|
+
for (const doc of docs) {
|
|
3977
|
+
const docName = doc.split('/').pop();
|
|
3978
|
+
prompt += `- \`${docName}\`\n`;
|
|
3979
|
+
}
|
|
3980
|
+
prompt += `\n`;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
else if (nextDoc) {
|
|
3790
3984
|
prompt += `## 🎯 本次只生成: \`${nextDoc}\`\n\n`;
|
|
3791
3985
|
// 根据文档路径生成专属指令
|
|
3792
3986
|
const docName = nextDoc.split('/').pop();
|
|
@@ -3838,7 +4032,15 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3838
4032
|
prompt += `- 必须包含真实数据,禁止占位符\n`;
|
|
3839
4033
|
}
|
|
3840
4034
|
// 自动链式推进
|
|
3841
|
-
|
|
4035
|
+
// v8.3.160+: 4d 按端分批续批
|
|
4036
|
+
const remainingPlatforms = Object.keys(platformMissingDocs).filter(p => !targetPlatforms.includes(p));
|
|
4037
|
+
if (subLayerTarget === '4d' && remainingPlatforms.length > 0) {
|
|
4038
|
+
prompt += `\n## 🔗 自动链式推进\n\n`;
|
|
4039
|
+
prompt += `> 当前批次(${targetPlatforms.join(', ')})写入完成后,**还有 ${remainingPlatforms.length} 个端待处理**。\n`;
|
|
4040
|
+
prompt += `> **立即输出以下标记**继续子层 4d:\n\n`;
|
|
4041
|
+
prompt += `[SPECCORE_EXEC: speccore analyze --scope global --layer 4${options?.withCode ? ' --with-code' : ''}]\n\n`;
|
|
4042
|
+
}
|
|
4043
|
+
else if (missingDocs.length > 1) {
|
|
3842
4044
|
prompt += `\n## 🔗 自动链式推进\n\n`;
|
|
3843
4045
|
prompt += `> 当前文档写入完成后,**立即输出以下标记**推进下一个文档:\n\n`;
|
|
3844
4046
|
prompt += `[SPECCORE_EXEC: speccore analyze --scope global --layer 4${options?.withCode ? ' --with-code' : ''}]\n\n`;
|
|
@@ -3886,14 +4088,26 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3886
4088
|
prompt += `- platforms/ 和 requirements/ 下的文件按原路径写(如 \`platforms/backend/_INDEX.md\`)\n`;
|
|
3887
4089
|
prompt += `- overview/ 下的文件写纯文件名即可(如 \`ARCHITECTURE.md\` 自动路由到 overview/)\n`;
|
|
3888
4090
|
prompt += `- PATTERNS/ 下的文件写 \`PATTERNS/{分类}/{模式名}.md\`\n`;
|
|
4091
|
+
prompt += `- **Layer 3 功能模块分析文档写** \`platforms/_shared/modules/{kebab-case模块名}.md\`(v8.3.160+ 统一存放,便于进度追踪)\n`;
|
|
3889
4092
|
// v7.4.5+: 自动链式推进 — 用 [SPECCORE_EXEC:] 标记触发下一层
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
4093
|
+
// v8.3.160+: Layer 3 支持模块级续批,有未完成模块时继续 Layer 3
|
|
4094
|
+
let nextLayerCmd;
|
|
4095
|
+
if (targetLayer === 3 && progress.pendingModules && progress.pendingModules.length > 0) {
|
|
4096
|
+
nextLayerCmd = `speccore analyze --scope global --layer 3${ctx.withCode ? ' --with-code' : ''}`;
|
|
4097
|
+
}
|
|
4098
|
+
else {
|
|
4099
|
+
nextLayerCmd = targetLayer < 4
|
|
4100
|
+
? `speccore analyze --scope global --layer ${targetLayer + 1}${ctx.withCode ? ' --with-code' : ''}`
|
|
4101
|
+
: `speccore analyze --scope global --layer 4${ctx.withCode ? ' --with-code' : ''}`;
|
|
4102
|
+
}
|
|
3893
4103
|
const isLastSubLayer = targetLayer === 4 && (!progress.subLayer || progress.subLayer.next === '4d' ||
|
|
3894
4104
|
(progress.subLayer.completed.length >= 3));
|
|
3895
4105
|
prompt += `\n## 🔗 自动链式推进\n\n`;
|
|
3896
|
-
if (targetLayer
|
|
4106
|
+
if (targetLayer === 3 && progress.pendingModules && progress.pendingModules.length > 0) {
|
|
4107
|
+
prompt += `> 当前 Layer 3 还有 ${progress.pendingModules.length} 个功能模块待分析,**继续执行 Layer 3**(不要推进到 Layer 4):\n\n`;
|
|
4108
|
+
prompt += `[SPECCORE_EXEC: ${nextLayerCmd}]\n`;
|
|
4109
|
+
}
|
|
4110
|
+
else if (targetLayer < 4) {
|
|
3897
4111
|
prompt += `> 当前 Layer ${targetLayer} 所有文件写入完成后,**立即输出以下标记**触发下一层(不要等待用户指令):\n\n`;
|
|
3898
4112
|
prompt += `[SPECCORE_EXEC: ${nextLayerCmd}]\n`;
|
|
3899
4113
|
}
|
|
@@ -3919,8 +4133,9 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3919
4133
|
prompt += `- [ ] 没有空壳文档\n`;
|
|
3920
4134
|
}
|
|
3921
4135
|
else if (targetLayer === 3) {
|
|
3922
|
-
prompt += `- [ ]
|
|
4136
|
+
prompt += `- [ ] 当前批次处理的 1-2 个功能模块分析文档行数 ≥ 100\n`;
|
|
3923
4137
|
prompt += `- [ ] 每个文档包含模块概述、涉及端清单、至少 1 个 Mermaid 图\n`;
|
|
4138
|
+
prompt += `- [ ] 如还有未分析模块,输出末尾标注 [PENDING: {模块名列表}]\n`;
|
|
3924
4139
|
prompt += `- [ ] 没有空壳文档\n`;
|
|
3925
4140
|
}
|
|
3926
4141
|
else if (targetLayer === 4) {
|
|
@@ -4235,10 +4450,14 @@ sequenceDiagram
|
|
|
4235
4450
|
};
|
|
4236
4451
|
const includeDocs = isTask ? (DOC_MATRIX[taskType] || DOC_MATRIX['feature']) : DOC_MATRIX['feature'];
|
|
4237
4452
|
// ── v6.61.0+: 恢复 Phase 1/Phase 2 分步逻辑,但 CLI 自动触发 Phase 2 ──
|
|
4238
|
-
//
|
|
4239
|
-
// Phase
|
|
4240
|
-
|
|
4453
|
+
// v8.3.160+: 改为功能模块优先 — 全局只保留索引文档,每个功能模块有自己的综合文档
|
|
4454
|
+
// Phase 1: 全局索引(FUNCTION_MAP.md/INTERACTION_MAP.md/PLATFORMS.md) + 各功能模块 overview/
|
|
4455
|
+
// Phase 2: 各功能模块的各端专属文档({功能模块}/{端}/TECH.md 等)
|
|
4456
|
+
const GLOBAL_INDEX_DOCS = ['FUNCTION_MAP.md', 'INTERACTION_MAP.md', 'PLATFORMS.md'];
|
|
4457
|
+
const FEATURE_OVERVIEW_DOCS_LIST = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md', 'RISK.md', 'DEPS.md', 'REVIEW.md', 'MONITOR.md', 'DEV_GUIDE.md'];
|
|
4241
4458
|
const PLATFORM_DOCS = ['TECH.md', 'TEST.md', 'UI_SPEC.md', 'DEV_GUIDE.md'];
|
|
4459
|
+
// 向后兼容:保留 GLOBAL_DOCS 引用
|
|
4460
|
+
const GLOBAL_DOCS = [...GLOBAL_INDEX_DOCS, ...FEATURE_OVERVIEW_DOCS_LIST];
|
|
4242
4461
|
let taskDocs = docs.filter(([n]) => includeDocs.includes(n));
|
|
4243
4462
|
if (ctx.phase === '1') {
|
|
4244
4463
|
taskDocs = taskDocs.filter(([n]) => GLOBAL_DOCS.includes(n));
|
|
@@ -4431,6 +4650,10 @@ sequenceDiagram
|
|
|
4431
4650
|
techDoc[1] = `# 技术架构(跨端全局)\n\n> ${iter}\n\n## 写作要求\n撰写整体技术架构,覆盖所有端的交互关系:\n- 系统整体分层设计(各端在架构中的位置)\n- 跨端交互协议(前端↔后端通信方式、数据流向)\n- 中间件选型(缓存、消息队列、网关等)\n- 数据库整体设计(核心表结构、ER 关系)\n- 技术栈选型及理由\n`;
|
|
4432
4651
|
}
|
|
4433
4652
|
let prompt = `\n# 任务: ${command}${task} (${taskDocs.length}个文档 · ${isTask ? `类型:${taskType}` : '迭代全量'}${ctx.phase ? ` · Phase ${ctx.phase}` : ''})\n\n`;
|
|
4653
|
+
// v8.3.160+: 注入子 Agent 角色定义
|
|
4654
|
+
if (agentContextText) {
|
|
4655
|
+
prompt = agentContextText + '\n\n' + prompt;
|
|
4656
|
+
}
|
|
4434
4657
|
// v8.3.0+: 如果检测到需求质量不足,在 prompt 开头注入强制澄清阶段
|
|
4435
4658
|
if (needsClarify && !isGlobal && !isTask && !ctx.phase) {
|
|
4436
4659
|
const now = new Date().toISOString();
|
|
@@ -4598,29 +4821,21 @@ status: "clarified"
|
|
|
4598
4821
|
prompt += `- 写完后用 Read 验证文件内容已替换占位内容\n\n`;
|
|
4599
4822
|
// ── v8.1.0+: 逐文档模式自动注入 PRD 内容 + 前序文档摘要 ──
|
|
4600
4823
|
// v8.3.2+ 修复:PRD 注入不再仅限逐文档模式,Phase 1/2 模式下也必须注入需求内容
|
|
4824
|
+
// v8.3.160+: 改为路径引用模式(不再全文塞进,让 AI 按需 Read)
|
|
4601
4825
|
if (!isGlobal && !isTask && ctx.iteration && ctx.iteration !== 'GLOBAL') {
|
|
4602
4826
|
try {
|
|
4603
4827
|
const iterDirForCtx = await (0, context_1.getIterationDir)(iter);
|
|
4604
4828
|
if (iterDirForCtx) {
|
|
4605
|
-
// v8.
|
|
4606
|
-
|
|
4607
|
-
let prdContent = '';
|
|
4608
|
-
const seenPaths = new Set();
|
|
4829
|
+
// v8.3.160+: 路径引用模式 — 列出文档清单,让 AI 按需 Read
|
|
4830
|
+
const docList = [];
|
|
4609
4831
|
const goldenReqDir = (0, path_1.join)(iterDirForCtx, '020-specs', 'requirements');
|
|
4610
4832
|
if (await (0, fs_extra_1.pathExists)(goldenReqDir)) {
|
|
4611
|
-
// 黄金需求目录存在,只读这里(唯一依据)
|
|
4612
4833
|
const files = (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readdirSync(goldenReqDir).filter((f) => f.endsWith('.md')).slice(0, 10);
|
|
4613
4834
|
for (const f of files) {
|
|
4614
|
-
|
|
4615
|
-
let content = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile(filePath, 'utf-8');
|
|
4616
|
-
content = await (0, prompt_builder_1.processMarkdownContent)(content, filePath, seenPaths, undefined, {
|
|
4617
|
-
maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
|
|
4618
|
-
});
|
|
4619
|
-
prdContent += `\n### ${f}\n${content.slice(0, 2000)}\n`;
|
|
4835
|
+
docList.push(`020-specs/requirements/${f}`);
|
|
4620
4836
|
}
|
|
4621
4837
|
}
|
|
4622
4838
|
else {
|
|
4623
|
-
// 回退:从 010-requirements/ 读取原始需求
|
|
4624
4839
|
const reqDir = (0, path_1.join)(iterDirForCtx, '010-requirements');
|
|
4625
4840
|
const prdSources = (0, path_1.join)(reqDir, 'sources');
|
|
4626
4841
|
const prdConverted = (0, path_1.join)(reqDir, 'converted');
|
|
@@ -4629,42 +4844,30 @@ status: "clarified"
|
|
|
4629
4844
|
if (await (0, fs_extra_1.pathExists)(dir)) {
|
|
4630
4845
|
const files = (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readdirSync(dir).filter((f) => f.endsWith('.md')).slice(0, 5);
|
|
4631
4846
|
for (const f of files) {
|
|
4632
|
-
const
|
|
4633
|
-
|
|
4634
|
-
content = await (0, prompt_builder_1.processMarkdownContent)(content, filePath, seenPaths, undefined, {
|
|
4635
|
-
maxLinkDepth: 2, maxLinkChars: 1200, maxSvgChars: 1500,
|
|
4636
|
-
});
|
|
4637
|
-
prdContent += `\n### ${f}\n${content.slice(0, 2000)}\n`;
|
|
4847
|
+
const relDir = (0, path_1.relative)(iterDirForCtx, dir);
|
|
4848
|
+
docList.push(`${relDir}/${f}`);
|
|
4638
4849
|
}
|
|
4639
4850
|
}
|
|
4640
4851
|
}
|
|
4641
4852
|
}
|
|
4642
|
-
if (
|
|
4643
|
-
prompt += `\n## 📎
|
|
4644
|
-
|
|
4645
|
-
prompt += `>
|
|
4646
|
-
|
|
4647
|
-
|
|
4853
|
+
if (docList.length > 0) {
|
|
4854
|
+
prompt += `\n## 📎 需求文档清单(请按需 Read)\n\n`;
|
|
4855
|
+
prompt += `> 以下是你撰写专业分析文档的核心输入。所有分析必须基于这些内容,不要臆造。\n`;
|
|
4856
|
+
prompt += `> 用 Read 工具按需读取,不需要一次性读完所有文档。\n\n`;
|
|
4857
|
+
for (const docPath of docList) {
|
|
4858
|
+
prompt += `- \`${docPath}\`\n`;
|
|
4859
|
+
}
|
|
4860
|
+
prompt += `\n`;
|
|
4648
4861
|
}
|
|
4649
|
-
// 2.
|
|
4862
|
+
// 2. 前序已填充文档路径(改为路径引用,不再注入摘要)
|
|
4650
4863
|
if (perDocStatus?.filled.length && perDocStatus.filled.length > 0) {
|
|
4651
|
-
|
|
4652
|
-
prompt +=
|
|
4653
|
-
prompt += `>
|
|
4864
|
+
prompt += `## 📎 前序已填充文档(必须与之保持一致)\n\n`;
|
|
4865
|
+
prompt += `> 以下文档已生成,当前文档必须与之保持一致(字段名、接口路径、状态枚举等不能冲突)。\n`;
|
|
4866
|
+
prompt += `> 用 Read 工具读取这些文件,提取关键约束。\n\n`;
|
|
4654
4867
|
for (const filledPath of perDocStatus.filled) {
|
|
4655
|
-
|
|
4656
|
-
try {
|
|
4657
|
-
let content = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile(fullPath, 'utf-8');
|
|
4658
|
-
if (fullPath.endsWith('.md')) {
|
|
4659
|
-
content = await (0, prompt_builder_1.processMarkdownContent)(content, fullPath, seenPaths, undefined, {
|
|
4660
|
-
maxLinkDepth: 1, maxLinkChars: 800, maxSvgChars: 1000,
|
|
4661
|
-
});
|
|
4662
|
-
}
|
|
4663
|
-
const summary = content.slice(0, 1500);
|
|
4664
|
-
prompt += `### ${filledPath}\n\`\`\`\n${summary}${content.length > 1500 ? '\n... (截断)' : ''}\n\`\`\`\n\n`;
|
|
4665
|
-
}
|
|
4666
|
-
catch { /* skip unreadable */ }
|
|
4868
|
+
prompt += `- \`020-specs/${filledPath}\`\n`;
|
|
4667
4869
|
}
|
|
4870
|
+
prompt += `\n`;
|
|
4668
4871
|
}
|
|
4669
4872
|
}
|
|
4670
4873
|
}
|
|
@@ -4742,9 +4945,18 @@ status: "clarified"
|
|
|
4742
4945
|
prompt += `- Read 020-specs/overview/ANALYSIS.md → 分析报告\n`;
|
|
4743
4946
|
prompt += `- Read 020-specs/overview/TECH.md → 整体技术架构\n`;
|
|
4744
4947
|
prompt += `- Read 020-specs/overview/RISK.md、DEPS.md、REVIEW.md、MONITOR.md(如存在)\n\n`;
|
|
4745
|
-
prompt += `### Step 2:
|
|
4746
|
-
prompt +=
|
|
4747
|
-
prompt +=
|
|
4948
|
+
prompt += `### Step 2: 按功能模块组织,为每个功能模块的每个端撰写专属文档(v8.3.1+ 强制四文档)\n`;
|
|
4949
|
+
prompt += `先 Read \`020-specs/overview/FUNCTION_MAP.md\` 获取功能模块清单。\n\n`;
|
|
4950
|
+
prompt += `**分批策略(v8.3.160+)**:\n`;
|
|
4951
|
+
prompt += `- 如功能模块数量 ≤ 2:一次生成所有模块的所有端文档\n`;
|
|
4952
|
+
prompt += `- 如功能模块数量 > 2:一次只处理 **1-2 个功能模块** 的所有端文档,确保质量深入\n`;
|
|
4953
|
+
prompt += `- 剩余模块在输出末尾标注 \`[PENDING: {模块名列表}]\`,供续批处理\n`;
|
|
4954
|
+
prompt += `- 续批命令:\`speccore analyze --prompt -I ${iter} --phase 2\`\n`;
|
|
4955
|
+
prompt += `- **关键原则**:宁可少分析几个模块,也要保证每个模块的分析质量(文档充实、有具体代码引用、表格有数据行)\n\n`;
|
|
4956
|
+
prompt += `每个功能模块涉及的每个端,分别撰写以下 **4 份文档**,缺一不可:\n\n`;
|
|
4957
|
+
prompt += `**文件路径格式**:\`{功能模块名}/{端名}/{文件名}\`(如 \`booking/backend/TECH.md\`、\`booking/h5-mobile/UI_SPEC.md\`)\n`;
|
|
4958
|
+
prompt += `> ⚠️ **严禁**只写 \`{端}/{文件}\` 格式(如 \`backend/TECH.md\`),这会导致所有功能模块的文档混在一起无法区分\n\n`;
|
|
4959
|
+
prompt += `**1. {功能模块}/{端}/TECH.md — 该端在该功能模块下的技术方案(必须对齐 overview/TECH.md 架构)**\n`;
|
|
4748
4960
|
prompt += ` - 该端的分层架构、模块划分、核心接口设计(路径/方法/参数/响应/状态码)\n`;
|
|
4749
4961
|
prompt += ` - 数据库表结构(字段/类型/索引/约束,如适用)\n`;
|
|
4750
4962
|
prompt += ` - 业务规则实现(含边界条件和异常流)\n`;
|
|
@@ -4752,15 +4964,15 @@ status: "clarified"
|
|
|
4752
4964
|
prompt += ` - 表格格式:| 业务模块 | 代码实体 | 关系类型 | 说明 |\n`;
|
|
4753
4965
|
prompt += ` - 示例:| 会议室档案 | backend/RoomController.java | api_controller | REST 控制器 |\n`;
|
|
4754
4966
|
prompt += ` - 示例:| 会议室档案 | admin-web/src/pages/RoomList.vue | page | 列表页 |\n\n`;
|
|
4755
|
-
prompt += `**2. {端}/TEST.md —
|
|
4967
|
+
prompt += `**2. {功能模块}/{端}/TEST.md — 该端在该功能模块下的测试计划**\n`;
|
|
4756
4968
|
prompt += ` - 覆盖该端所有功能模块的测试用例(含前置条件、步骤、预期结果)\n`;
|
|
4757
4969
|
prompt += ` - 边界值和异常输入测试\n`;
|
|
4758
4970
|
prompt += ` - 必须覆盖 REQUIREMENT.md 中该端的验收标准\n\n`;
|
|
4759
|
-
prompt += `**3. {端}/UI_SPEC.md —
|
|
4971
|
+
prompt += `**3. {功能模块}/{端}/UI_SPEC.md — 该端在该功能模块下的 UI 规格(仅前端端需要)**\n`;
|
|
4760
4972
|
prompt += ` - 页面结构与路由(每个页面的路径、入口、权限)\n`;
|
|
4761
4973
|
prompt += ` - 组件清单和字段→UI 映射\n`;
|
|
4762
4974
|
prompt += ` - 状态枚举和错误处理策略\n\n`;
|
|
4763
|
-
prompt += `**4. {端}/DEV_GUIDE.md —
|
|
4975
|
+
prompt += `**4. {功能模块}/{端}/DEV_GUIDE.md — 该端在该功能模块下的开发者实现指南(v8.3.1+ 新增强制要求)**\n`;
|
|
4764
4976
|
prompt += ` - ⛔ **禁止只写框架/占位符** — 必须是可执行的实现指导\n`;
|
|
4765
4977
|
prompt += ` - **本端改造范围清单**:列出本端所有需要新增/修改/删除的文件(相对项目根目录的具体路径),表格表头:| # | 类型 | 文件/目录 | 说明 |\n`;
|
|
4766
4978
|
prompt += ` - **本端实施步骤(按依赖排序)**:Step-by-step,具体到文件/函数级,说明每步为什么先做\n`;
|
|
@@ -4780,10 +4992,14 @@ status: "clarified"
|
|
|
4780
4992
|
prompt += `- 如果「验证方式」表格只有表头 → **必须补充可执行的命令/操作**\n`;
|
|
4781
4993
|
prompt += `- DEV_GUIDE.md 必须有至少 3 个具体文件路径和 3 个可执行验证步骤\n\n`;
|
|
4782
4994
|
prompt += `### 写入方式\n`;
|
|
4783
|
-
prompt +=
|
|
4784
|
-
prompt +=
|
|
4785
|
-
prompt +=
|
|
4786
|
-
prompt +=
|
|
4995
|
+
prompt += `**按功能模块批量写入**(推荐):一次 --apply 写入一个功能模块的所有端文档\n`;
|
|
4996
|
+
prompt += `\`\`\`bash\n`;
|
|
4997
|
+
prompt += `speccore analyze --apply '{"booking/backend/TECH.md":"...","booking/backend/TEST.md":"...","booking/h5-mobile/TECH.md":"...","booking/h5-mobile/UI_SPEC.md":"..."}' -I ${iter}\n`;
|
|
4998
|
+
prompt += `\`\`\`\n\n`;
|
|
4999
|
+
prompt += `**或者逐功能模块-逐端写入**(每端一次 --apply):\n`;
|
|
5000
|
+
prompt += `\`\`\`bash\n`;
|
|
5001
|
+
prompt += `speccore analyze --apply '{"{功能模块}/{端}/TECH.md":"...","{功能模块}/{端}/TEST.md":"..."}' -I ${iter}\n`;
|
|
5002
|
+
prompt += `\`\`\`\n\n`;
|
|
4787
5003
|
prompt += `### ⛔ 强制约束(违反则分析无效)\n`;
|
|
4788
5004
|
prompt += `- **禁止省略任何一份文档** — 4 份文档(TECH.md + TEST.md + UI_SPEC.md + DEV_GUIDE.md)缺一不可\n`;
|
|
4789
5005
|
prompt += `- **禁止输出空表格** — 每个 Markdown 表格必须有至少 1 行数据,只有表头的表格视为未完成\n`;
|
|
@@ -4804,25 +5020,31 @@ status: "clarified"
|
|
|
4804
5020
|
const tpl = templateMap[doc[0]] || '';
|
|
4805
5021
|
prompt += ` - ${doc[0]} → 参考 ${tpl}\n`;
|
|
4806
5022
|
}
|
|
4807
|
-
prompt += `2.
|
|
4808
|
-
prompt += `
|
|
4809
|
-
prompt += `
|
|
4810
|
-
prompt += `
|
|
4811
|
-
prompt += `
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
prompt += `
|
|
4822
|
-
prompt += `
|
|
4823
|
-
prompt += `
|
|
4824
|
-
prompt += `
|
|
4825
|
-
prompt += `
|
|
5023
|
+
prompt += `2. 读取全局层产物(建立全局视角,按需 Read):\n`;
|
|
5024
|
+
prompt += ` \`.speccore/GLOBAL/\` 下已有系统级产物,按需读取了解当前状态:\n`;
|
|
5025
|
+
prompt += ` - \`requirements/REQUIREMENT.md\`、\`overview/FUNCTION_MAP.md\`、\`overview/API_CONTRACT.yaml\`\n`;
|
|
5026
|
+
prompt += ` - \`overview/ARCHITECTURE.md\`、\`platforms/{端}/_INDEX.md\`、\`platforms/_shared/_ASSOCIATION.md\`\n`;
|
|
5027
|
+
prompt += ` - 如不存在则跳过\n\n`;
|
|
5028
|
+
// v8.3.160+: 迭代级分析结合源码上下文
|
|
5029
|
+
if (ctx.withCode) {
|
|
5030
|
+
prompt += `2b. **源码上下文(--with-code)**:\n`;
|
|
5031
|
+
// v8.3.160+: 大项目按端分段读取,避免一次性加载全量结构化数据
|
|
5032
|
+
prompt += ` - 先 Read \`.speccore/cache/structured-data.json\` 的 stats 部分(了解各端规模)\n`;
|
|
5033
|
+
prompt += ` - **按需读取各端分段文件**:\`.speccore/cache/structured-data.{端名}.json\`(只读取本功能模块涉及的端)\n`;
|
|
5034
|
+
prompt += ` - 再按需 Read 各端源码中与本功能模块相关的关键文件(从 CONSTITUTION.md 获取源码路径)\n`;
|
|
5035
|
+
prompt += ` - 重点关注:接口定义、数据模型、业务逻辑实现、状态管理\n\n`;
|
|
5036
|
+
}
|
|
5037
|
+
prompt += `3. 读取迭代需求文档(按功能模块组织):\n`;
|
|
5038
|
+
prompt += ` a. 先读 010-requirements/INDEX.md — 获取功能模块清单\n`;
|
|
5039
|
+
prompt += ` b. **按功能模块逐一读取**:每个功能模块独立分析,不要跨模块混淆\n`;
|
|
5040
|
+
prompt += ` - 读取该模块对应的 010-requirements/converted/*.md 或 features/{模块}/README.md\n`;
|
|
5041
|
+
prompt += ` - 读取该模块涉及的原型文件(HTML/图片/链接)\n`;
|
|
5042
|
+
prompt += ` c. **文档长度自适应**:如果单个需求文档超过 5000 字,先快速扫描目录和章节标题,标记关键章节,再深入阅读\n`;
|
|
5043
|
+
prompt += `4. **按功能模块逐一撰写文档(重要)**:\n`;
|
|
5044
|
+
prompt += ` - 每个功能模块独立生成一套综合文档,放在 \`{功能模块}/overview/\` 目录下\n`;
|
|
5045
|
+
prompt += ` - 不要生成全局的 \`overview/REQUIREMENT.md\`(不要把所有模块拟合在一起)\n`;
|
|
5046
|
+
prompt += ` - 全局 \`overview/\` 只保留 FUNCTION_MAP.md、INTERACTION_MAP.md、PLATFORMS.md 作为索引\n`;
|
|
5047
|
+
prompt += ` - 示例:\`booking/overview/REQUIREMENT.md\`、\`booking/overview/ANALYSIS.md\`、\`payment/overview/REQUIREMENT.md\` 等\n`;
|
|
4826
5048
|
prompt += `5. **文档忠实度约束(最高优先级)**:\n`;
|
|
4827
5049
|
prompt += ` - **严禁臆造**:只能写需求文档中明确提及的功能、接口、页面、字段、业务规则\n`;
|
|
4828
5050
|
prompt += ` - **严禁扩展**:不要基于迭代名称或你的知识补充文档中未提及的内容\n`;
|
|
@@ -4844,26 +5066,16 @@ status: "clarified"
|
|
|
4844
5066
|
prompt += ` - 第 4 步:如果以上都无法确定,根据需求文档内容判断\n`;
|
|
4845
5067
|
prompt += ` - 第 5 步:将发现的端列表写入 020-specs/PLATFORMS.md\n`;
|
|
4846
5068
|
// v6.70.0+: REQUIREMENT.md 以产品视角撰写(不按端分章节)
|
|
4847
|
-
//
|
|
4848
|
-
prompt += `9. **REQUIREMENT.md
|
|
4849
|
-
prompt += ` -
|
|
4850
|
-
prompt += ` -
|
|
4851
|
-
prompt += `
|
|
4852
|
-
prompt += `
|
|
4853
|
-
prompt += ` - **核心场景地图**:按业务流程组织的场景列表,每个场景标注优先级\n`;
|
|
4854
|
-
prompt += ` - **功能全景图**:所有功能模块的可视化列表(表格或脑图描述)\n`;
|
|
4855
|
-
prompt += ` - **功能优先级矩阵**:P0(必须)/ P1(重要)/ P2(可选)标注\n`;
|
|
4856
|
-
prompt += ` - **发布里程碑**:如有分期计划,标注各阶段交付内容\n`;
|
|
4857
|
-
prompt += ` - **风险预判**:技术风险、业务风险、依赖风险(如有)\n`;
|
|
4858
|
-
prompt += ` - 每个场景描述:用户操作 → 系统响应 → 业务规则 → 边界条件 → 验收标准\n`;
|
|
4859
|
-
prompt += ` - 系统响应中自然包含前后端交互,但不刻意标注技术实现细节\n`;
|
|
4860
|
-
prompt += ` - 示例正确写法:「用户选择时间段后点击预订,系统检查会议室可用性,如可用则锁定会议室并创建待支付订单」\n`;
|
|
4861
|
-
prompt += ` - 示例错误写法:「后端 booking-service 需要新增 /api/bookings 接口,接收 roomId 参数」\n`;
|
|
4862
|
-
prompt += ` - 技术实现细节留在 TECH.md 和各端专属文档中,不在 REQUIREMENT.md 展开\n`;
|
|
5069
|
+
// v8.3.160+: 压缩写作风格说明
|
|
5070
|
+
prompt += `9. **REQUIREMENT.md 写作风格**:产品/用户视角,按业务场景组织章节(不按端分节)\n`;
|
|
5071
|
+
prompt += ` - 必须包含:产品愿景、用户画像、核心场景地图(标注优先级)、功能全景图、功能优先级矩阵(P0/P1/P2)、发布里程碑、风险预判\n`;
|
|
5072
|
+
prompt += ` - 场景描述格式:用户操作 → 系统响应 → 业务规则 → 边界条件 → 验收标准\n`;
|
|
5073
|
+
prompt += ` - ✅ 正确:「用户点击预订,系统检查可用性,如可用则锁定并创建订单」\n`;
|
|
5074
|
+
prompt += ` - ❌ 错误:「后端新增 /api/bookings 接口,接收 roomId」→ 技术细节留给 TECH.md\n`;
|
|
4863
5075
|
prompt += ` - 端的信息只在「功能模块清单」表格中标注,正文不区分端\n`;
|
|
4864
5076
|
// v6.49.14+: 功能模块清单必须含涉及端列 + 来源链接
|
|
4865
5077
|
// v6.71.3+: 增加「与全局层对比」列
|
|
4866
|
-
prompt += `10.
|
|
5078
|
+
prompt += `10. **功能模块清单(重要)**:每个功能模块的 \`overview/REQUIREMENT.md\` 都必须包含功能模块清单表格\n`;
|
|
4867
5079
|
prompt += ` - 表格格式:| # | 功能模块 | 涉及端 | 全局对比 | 来源 | 说明 |\n`;
|
|
4868
5080
|
prompt += ` - 「涉及端」:每个模块标注需要**新开发工作**的端(标准端名,逗号分隔)\n`;
|
|
4869
5081
|
prompt += ` - 「涉及」= 该端需要写新接口/新页面/新逻辑\n`;
|
|
@@ -4884,12 +5096,12 @@ status: "clarified"
|
|
|
4884
5096
|
prompt += ` - split 命令将读取「涉及端」列来决定创建哪些端的子任务目录\n`;
|
|
4885
5097
|
// v6.70.0+: 跨端功能映射表(FUNCTION_MAP.md)
|
|
4886
5098
|
// v6.71.3+: 增加与全局层关联分析
|
|
4887
|
-
prompt += `7a.
|
|
5099
|
+
prompt += `7a. **迭代需求与全局层关联分析(重要)**:在每个功能模块的 \`overview/REQUIREMENT.md\` 中,必须对比全局层产物\n`;
|
|
4888
5100
|
prompt += ` - 对比迭代需求中的功能模块 vs .speccore/GLOBAL/overview/FUNCTION_MAP.md 中的功能单元\n`;
|
|
4889
5101
|
prompt += ` - 标注每个功能模块的「全局对比」类型(新增/扩展/重构/复用)\n`;
|
|
4890
5102
|
prompt += ` - 识别冲突:如迭代需求修改了全局层已有接口的字段/路径 → 在 RISK.md 中标注\n`;
|
|
4891
5103
|
prompt += ` - 识别依赖:如迭代的新功能依赖全局层的某个功能 → 在 FUNCTION_MAP.md「依赖任务」中标注\n\n`;
|
|
4892
|
-
prompt += `7b.
|
|
5104
|
+
prompt += `7b. **跨端功能映射表(重要)**:全局索引 \`overview/FUNCTION_MAP.md\` 汇总所有功能模块的映射关系\n`;
|
|
4893
5105
|
prompt += ` - 这是 split 阶段的核心输入,决定任务如何按功能单元拆分\n`;
|
|
4894
5106
|
prompt += ` - 表格格式:| # | 功能单元 | 涉及端 | 全局对比 | 共享能力 | 依赖任务 | 说明 |\n`;
|
|
4895
5107
|
prompt += ` - 「功能单元」必须与 REQUIREMENT.md 功能模块清单一一对应,不允许合并\n`;
|
|
@@ -4902,7 +5114,7 @@ status: "clarified"
|
|
|
4902
5114
|
prompt += ` - **错误示例**(禁止):将"审批流程"和"定时任务"合并为一行\n`;
|
|
4903
5115
|
prompt += ` - FUNCTION_MAP.md 生成后,split 将**严格按此表**创建任务目录,不再由 AI 推断\n`;
|
|
4904
5116
|
// v6.70.0+: 跨端交互图谱(INTERACTION_MAP.md)
|
|
4905
|
-
prompt += `7c.
|
|
5117
|
+
prompt += `7c. **跨端交互图谱(重要)**:全局索引 \`overview/INTERACTION_MAP.md\` 汇总所有功能模块的交互时序\n`;
|
|
4906
5118
|
prompt += ` - 按功能单元组织,每个功能单元一个 Mermaid sequenceDiagram\n`;
|
|
4907
5119
|
prompt += ` - 展示完整的业务交互时序:用户操作 → 前端处理 → 后端调用 → 数据返回\n`;
|
|
4908
5120
|
prompt += ` - 明确标出后端服务之间的内部调用(产品文档写"系统处理"的地方)\n`;
|
|
@@ -4911,43 +5123,40 @@ status: "clarified"
|
|
|
4911
5123
|
prompt += ` - 如涉及状态变更,附「状态流转」表格\n`;
|
|
4912
5124
|
prompt += ` - INTERACTION_MAP.md 是前后端开发者的共同参考,补全产品文档中隐含的技术交互\n`;
|
|
4913
5125
|
// 注入工程类型信息(v6.49.0+)
|
|
5126
|
+
// v8.3.160+: 精简为路径引用,具体维度让 AI 从 CONSTITUTION.md 读取
|
|
4914
5127
|
const platformTypes = await (0, spec_paths_1.parsePlatformTypes)();
|
|
4915
5128
|
if (platformTypes.size > 0) {
|
|
4916
|
-
prompt += `8. **工程类型识别**:CONSTITUTION.md
|
|
5129
|
+
prompt += `8. **工程类型识别**:CONSTITUTION.md 已配置各端的工程类型,请 Read 后据此生成针对性内容\n`;
|
|
4917
5130
|
prompt += ` | 工程标识 | 工程类型 |\n`;
|
|
4918
5131
|
prompt += ` | :--- | :--- |\n`;
|
|
4919
5132
|
for (const [name, type] of platformTypes) {
|
|
4920
5133
|
prompt += ` | ${name} | ${type} |\n`;
|
|
4921
5134
|
}
|
|
4922
|
-
prompt += `\n
|
|
4923
|
-
prompt += ` - Java服务 → API设计、数据库、缓存、消息队列、安全、性能\n`;
|
|
4924
|
-
prompt += ` - Node服务 → API设计、数据库、中间件、异步处理、安全\n`;
|
|
4925
|
-
prompt += ` - Go服务 → API设计、数据库、并发、微服务、性能\n`;
|
|
4926
|
-
prompt += ` - Python服务 → API设计、数据库、数据分析、AI/ML集成\n`;
|
|
4927
|
-
prompt += ` - H5微信公众号 → 微信JS-SDK、OAuth授权、分享、支付、模板消息\n`;
|
|
4928
|
-
prompt += ` - H5移动端 → 响应式、viewport适配、触摸交互、弱网优化、首屏性能\n`;
|
|
4929
|
-
prompt += ` - Android移动端 → 生命周期、权限、推送、适配、内存优化\n`;
|
|
4930
|
-
prompt += ` - iOS移动端 → Swift/SwiftUI、App Store规范、推送、性能\n`;
|
|
4931
|
-
prompt += ` - 微信小程序 → 包体积(2MB)、平台API、setData优化、页面栈\n`;
|
|
4932
|
-
prompt += ` - Web管理后台 → 复杂表单、数据表格、权限UI、状态管理\n`;
|
|
4933
|
-
prompt += ` - 桌面应用 → 本地存储、系统API、自动更新、离线支持\n`;
|
|
5135
|
+
prompt += `\n`;
|
|
4934
5136
|
}
|
|
4935
5137
|
const dirStepNum = platformTypes.size > 0 ? 9 : 8;
|
|
4936
5138
|
prompt += `${dirStepNum}. **目录结构(严格遵循,禁止自创目录)**:\n`;
|
|
4937
|
-
prompt += ` -
|
|
4938
|
-
prompt += ` -
|
|
4939
|
-
prompt += ` -
|
|
4940
|
-
prompt += ` -
|
|
4941
|
-
prompt += ` -
|
|
4942
|
-
prompt += ` -
|
|
4943
|
-
prompt += ` -
|
|
4944
|
-
prompt += ` -
|
|
4945
|
-
prompt += ` - RISK.md
|
|
4946
|
-
prompt += ` -
|
|
4947
|
-
prompt += ` -
|
|
5139
|
+
prompt += ` - **全局索引**(跨端汇总)→ 通过 --apply 写入,CLI 自动路由到 \`020-specs/overview/{文件名}\`\n`;
|
|
5140
|
+
prompt += ` - FUNCTION_MAP.md(功能单元 × 端映射表)\n`;
|
|
5141
|
+
prompt += ` - INTERACTION_MAP.md(跨端交互时序图)\n`;
|
|
5142
|
+
prompt += ` - PLATFORMS.md(端列表)\n`;
|
|
5143
|
+
prompt += ` - **功能模块综合文档**(每个功能模块独立)→ 通过 --apply 写入,CLI 自动路由到 \`020-specs/{功能模块名}/overview/{文件名}\`\n`;
|
|
5144
|
+
prompt += ` - {功能模块}/overview/REQUIREMENT.md(该模块的需求规格)\n`;
|
|
5145
|
+
prompt += ` - {功能模块}/overview/ANALYSIS.md(该模块的需求分析)\n`;
|
|
5146
|
+
prompt += ` - {功能模块}/overview/TECH.md(该模块的技术方案)\n`;
|
|
5147
|
+
prompt += ` - {功能模块}/overview/RISK.md(该模块的风险评估)\n`;
|
|
5148
|
+
prompt += ` - {功能模块}/overview/DEPS.md(该模块的依赖清单)\n`;
|
|
5149
|
+
prompt += ` - {功能模块}/overview/REVIEW.md(该模块的评审检查项)\n`;
|
|
5150
|
+
prompt += ` - {功能模块}/overview/MONITOR.md(该模块的监控指标)\n`;
|
|
5151
|
+
prompt += ` - {功能模块}/overview/DEV_GUIDE.md(该模块的实现指南)\n`;
|
|
5152
|
+
prompt += ` - **端专属文档**(按功能模块组织)→ 通过 --apply 写入,CLI 自动路由到 \`020-specs/{功能模块名}/{端名}/{文件名}\`\n`;
|
|
5153
|
+
prompt += ` - {功能模块}/{端}/TECH.md(该模块该端的技术方案)\n`;
|
|
5154
|
+
prompt += ` - {功能模块}/{端}/TEST.md(该模块该端的测试用例)\n`;
|
|
5155
|
+
prompt += ` - {功能模块}/{端}/UI_SPEC.md(该模块该端的 UI 规范,仅前端端)\n`;
|
|
5156
|
+
prompt += ` - {功能模块}/{端}/DEV_GUIDE.md(该模块该端的实现指南)\n`;
|
|
4948
5157
|
prompt += ` - **禁止**:不要创建 020-specs/ 下的任何额外子目录(如数字编号、中文名称等)\n`;
|
|
4949
5158
|
prompt += ` - **禁止直接用 Write 工具写文件到 020-specs/**:必须通过 \`speccore analyze --apply '{"文件名":"内容"}' -I ${iter}\` 写入\n`;
|
|
4950
|
-
prompt += ` - ⚠️ 直接 Write
|
|
5159
|
+
prompt += ` - ⚠️ 直接 Write 会导致目录结构错误,必须走 --apply 让 CLI 自动路由到正确子目录\n`;
|
|
4951
5160
|
if (ctx.phase !== '1') {
|
|
4952
5161
|
// 端专业性约束只在默认模式(全量)中输出
|
|
4953
5162
|
prompt += `\n## ⚠️ 端专业性约束\n`;
|
|
@@ -4961,7 +5170,7 @@ status: "clarified"
|
|
|
4961
5170
|
prompt += `- 缓存策略/并发与事务/消息队列(如涉及)\n`;
|
|
4962
5171
|
prompt += `- 安全:SQL 注入防护/接口鉴权/数据脱敏\n`;
|
|
4963
5172
|
prompt += `- 性能:QPS 预估/慢查询优化/连接池配置\n`;
|
|
4964
|
-
prompt += `- **不需要**产品视角的需求描述(用户故事、业务场景已在 overview/REQUIREMENT.md 中)\n\n`;
|
|
5173
|
+
prompt += `- **不需要**产品视角的需求描述(用户故事、业务场景已在 {功能模块}/overview/REQUIREMENT.md 中)\n\n`;
|
|
4965
5174
|
prompt += `### 前端端(h5 / admin-web / miniapp)必含内容 — 产品+技术双视角\n`;
|
|
4966
5175
|
prompt += `- **产品视角(主要)**:\n`;
|
|
4967
5176
|
prompt += ` - 用户旅程:该端用户如何完成核心任务(步骤流程图)\n`;
|
|
@@ -4979,24 +5188,26 @@ status: "clarified"
|
|
|
4979
5188
|
// v6.60.0+: 文档与端的对应关系(不再分 Phase)
|
|
4980
5189
|
// v6.71.0+: 前后端文档差异化
|
|
4981
5190
|
prompt += `### 文档与端的对应关系\n`;
|
|
4982
|
-
prompt += `- **overview/
|
|
4983
|
-
prompt += `- **overview/
|
|
4984
|
-
prompt += `- **overview/
|
|
4985
|
-
prompt += `- **overview/
|
|
4986
|
-
prompt += `- **overview/
|
|
4987
|
-
prompt += `- **overview/
|
|
4988
|
-
prompt += `-
|
|
4989
|
-
prompt += `-
|
|
4990
|
-
prompt += `-
|
|
5191
|
+
prompt += `- **overview/FUNCTION_MAP.md**:全局索引 — 所有功能单元 × 端映射表\n`;
|
|
5192
|
+
prompt += `- **overview/INTERACTION_MAP.md**:全局索引 — 所有跨端交互时序图\n`;
|
|
5193
|
+
prompt += `- **overview/PLATFORMS.md**:全局索引 — 端列表\n`;
|
|
5194
|
+
prompt += `- **{功能模块}/overview/REQUIREMENT.md**:该功能模块的需求规格(产品视角,按业务场景组织)\n`;
|
|
5195
|
+
prompt += `- **{功能模块}/overview/ANALYSIS.md**:该功能模块的需求分析\n`;
|
|
5196
|
+
prompt += `- **{功能模块}/overview/DEPS.md**:该功能模块的依赖清单\n`;
|
|
5197
|
+
prompt += `- **{功能模块}/overview/DEV_GUIDE.md**:该功能模块的开发者实现指南\n`;
|
|
5198
|
+
prompt += `- **{功能模块}/overview/RISK.md**:该功能模块的风险评估\n`;
|
|
5199
|
+
prompt += `- **{功能模块}/overview/REVIEW.md**:该功能模块的评审检查项\n`;
|
|
5200
|
+
prompt += `- **{功能模块}/overview/MONITOR.md**:该功能模块的监控指标\n`;
|
|
5201
|
+
prompt += `- **后端端(*service)/{功能模块}/{端}/TECH.md**:纯技术视角 — 接口设计+数据模型+架构+性能\n`;
|
|
5202
|
+
prompt += `- **前端端(h5/admin/miniapp)/{功能模块}/{端}/TECH.md**:产品+技术双视角 — 用户旅程+页面清单+交互流程+API调用链\n`;
|
|
5203
|
+
prompt += `- **前端端/{功能模块}/{端}/UI_SPEC.md**:UI 规格(字段映射、组件设计、交互细节)\n`;
|
|
4991
5204
|
prompt += ` - ⚠️ **必须包含「业务-代码映射」章节**:在 TECH.md 末尾添加一个表格,列出本端涉及的业务模块及其对应的代码实体(文件/表/API/组件等),关系类型由你根据技术栈自主决定(如 api_controller、uses_table、page、component、route、middleware、interceptor、gateway 等)\n`;
|
|
4992
5205
|
prompt += ` - 表格格式:| 业务模块 | 代码实体 | 关系类型 | 说明 |\n`;
|
|
4993
5206
|
prompt += ` - 示例:| 会议室档案 | backend/RoomController.java | api_controller | REST 控制器 |\n`;
|
|
4994
5207
|
prompt += ` - 示例:| 会议室档案 | admin-web/src/pages/RoomList.vue | page | 列表页 |\n`;
|
|
4995
|
-
prompt += `- **{端}/TEST.md
|
|
4996
|
-
prompt += `- **{端}/
|
|
4997
|
-
prompt += `- **{端}/
|
|
4998
|
-
prompt += `- **{端}/MONITOR.md**:该端专属监控指标\n`;
|
|
4999
|
-
prompt += `- **{端}/UI_SPEC.md**:前端端专属 UI 规格,字段映射必须与后端 API 响应字段一一对应\n`;
|
|
5208
|
+
prompt += `- **{功能模块}/{端}/TEST.md**:该功能模块下该端的测试计划\n`;
|
|
5209
|
+
prompt += `- **{功能模块}/{端}/DEV_GUIDE.md**:该功能模块下该端的实现指南\n`;
|
|
5210
|
+
prompt += `- **{功能模块}/{端}/UI_SPEC.md**:前端端专属 UI 规格,字段映射必须与后端 API 响应字段一一对应\n`;
|
|
5000
5211
|
prompt += `- 分析完成后会自动生成 QUALITY_AUDIT.md 质量报告,检查各端内容是否完整\n`;
|
|
5001
5212
|
}
|
|
5002
5213
|
// 步骤 2-7 已在上面的 phase 分支中处理
|
|
@@ -5461,6 +5672,17 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
5461
5672
|
prompt += `3. \`010-requirements/converted/*.md\` — 已转换文档\n`;
|
|
5462
5673
|
prompt += `4. \`010-requirements/features/*/README.md\` — 功能级补充\n`;
|
|
5463
5674
|
prompt += `5. \`010-requirements/prototypes/\` — 原型文件\n\n`;
|
|
5675
|
+
// v8.3.160+ 修复:列出实际扫描到的源文档清单,明确每份文档独立澄清
|
|
5676
|
+
if (docPaths.length > 0) {
|
|
5677
|
+
prompt += `## 源文档清单(本次需澄清的原始文档)\n\n`;
|
|
5678
|
+
prompt += `共扫描到 ${docPaths.length} 份需求文档,**每份文档必须独立澄清**,分别生成对应的黄金需求文档:\n\n`;
|
|
5679
|
+
for (const p of docPaths) {
|
|
5680
|
+
const name = p.split('/').pop() || '-';
|
|
5681
|
+
const clarifiedName = name.replace(/\.md$/, '') + '-clarified.md';
|
|
5682
|
+
prompt += `- \`${name}\` → 澄清为 \`${clarifiedName}\`\n`;
|
|
5683
|
+
}
|
|
5684
|
+
prompt += `\n> ⚠️ **重要**:每份源文档对应一份独立的澄清文档,禁止将多份源文档合并为一份。\n\n`;
|
|
5685
|
+
}
|
|
5464
5686
|
// v8.2.0+: 如果检测到多个功能单元,启用单元化澄清模式
|
|
5465
5687
|
if (hasMultipleUnits) {
|
|
5466
5688
|
prompt += `## ⚠️ 功能单元拆分澄清模式(v8.2.0+)\n\n`;
|
|
@@ -5576,9 +5798,40 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
5576
5798
|
prompt += ` - 记录取舍:每轮迭代记录「采纳/拒绝/待确认」的决策\n\n`;
|
|
5577
5799
|
prompt += `## Step 5: 确认写入\n\n`;
|
|
5578
5800
|
prompt += `当用户确认 "满意,可以写入" 后:\n\n`;
|
|
5579
|
-
prompt +=
|
|
5580
|
-
|
|
5581
|
-
|
|
5801
|
+
prompt += `**必须为每份源文档分别生成独立的澄清文档**,使用 \`[CLARIFY:requirements/文件名-clarified.md]\` 标记分隔:\n\n`;
|
|
5802
|
+
if (docPaths.length > 1) {
|
|
5803
|
+
prompt += `本次共 ${docPaths.length} 份源文档,需要输出 ${docPaths.length} 个 \`[CLARIFY:...]\` 标记块:\n\n`;
|
|
5804
|
+
for (const p of docPaths) {
|
|
5805
|
+
const name = p.split('/').pop() || '-';
|
|
5806
|
+
const baseName = name.replace(/\.md$/, '');
|
|
5807
|
+
prompt += `\`\`\`\n`;
|
|
5808
|
+
prompt += `[CLARIFY:requirements/${baseName}-clarified.md]\n`;
|
|
5809
|
+
prompt += `---\n`;
|
|
5810
|
+
prompt += `source: "010-requirements/.../${name}"\n`;
|
|
5811
|
+
prompt += `clarified-at: "${new Date().toISOString()}"\n`;
|
|
5812
|
+
prompt += `status: "clarified"\n`;
|
|
5813
|
+
prompt += `---\n\n`;
|
|
5814
|
+
prompt += `# ${baseName}(澄清后的专业 PRD)\n`;
|
|
5815
|
+
prompt += `...\n`;
|
|
5816
|
+
prompt += `\`\`\`\n\n`;
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
else {
|
|
5820
|
+
prompt += `\`\`\`\n`;
|
|
5821
|
+
prompt += `[CLARIFY:requirements/xxx-clarified.md]\n`;
|
|
5822
|
+
prompt += `---\n`;
|
|
5823
|
+
prompt += `source: "原始文档路径"\n`;
|
|
5824
|
+
prompt += `clarified-at: "${new Date().toISOString()}"\n`;
|
|
5825
|
+
prompt += `status: "clarified"\n`;
|
|
5826
|
+
prompt += `---\n\n`;
|
|
5827
|
+
prompt += `# 功能名称\n`;
|
|
5828
|
+
prompt += `...\n`;
|
|
5829
|
+
prompt += `\`\`\`\n\n`;
|
|
5830
|
+
}
|
|
5831
|
+
prompt += `> ⚠️ **禁止合并**:每份源文档必须对应一份独立的 \`-clarified.md\`,即使内容有关联也要分开输出。\n\n`;
|
|
5832
|
+
prompt += `同时生成对应的对比报告文件(可选):\n`;
|
|
5833
|
+
prompt += `- \`020-specs/requirements/{源文件名}-clarified-diff.md\` — 保存最终对比报告\n\n`;
|
|
5834
|
+
prompt += `使用以下命令写入:\n\n`;
|
|
5582
5835
|
prompt += `\`\`\`bash\n`;
|
|
5583
5836
|
prompt += `speccore analyze --apply '{"020-specs/requirements/xxx-clarified.md":"...","020-specs/requirements/xxx-clarified-diff.md":"..."}' -I ${iteration}\n`;
|
|
5584
5837
|
prompt += `\`\`\`\n\n`;
|