speccore 8.3.121 → 8.3.127
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 +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +16 -0
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze.d.ts +1 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +198 -70
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/change.js +14 -7
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/context.js +25 -31
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +6 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/done.js +29 -28
- package/dist/commands/done.js.map +1 -1
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +281 -20
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +513 -28
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/create.js +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +158 -70
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/iteration-from-global.js +1 -2
- package/dist/commands/iteration-from-global.js.map +1 -1
- package/dist/commands/pattern.js +30 -16
- package/dist/commands/pattern.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +15 -4
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +15 -8
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/status-panel.js +1 -1
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/sync.js +15 -8
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/trace.js +27 -24
- package/dist/commands/trace.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +33 -5
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +35 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +25 -0
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/ai-context-generator.js +35 -22
- package/dist/core/ai-context-generator.js.map +1 -1
- package/dist/core/ai-impact-analyzer.d.ts.map +1 -1
- package/dist/core/ai-impact-analyzer.js +1 -1
- package/dist/core/ai-impact-analyzer.js.map +1 -1
- package/dist/core/analyze-engine.d.ts +2 -1
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +217 -53
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +31 -13
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +21 -5
- package/dist/core/change-detection.js.map +1 -1
- package/dist/core/code-scanner.d.ts +12 -3
- package/dist/core/code-scanner.d.ts.map +1 -1
- package/dist/core/code-scanner.js +376 -10
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/context-builder.d.ts +2 -1
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +104 -25
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +21 -6
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/git-integration.d.ts +5 -1
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +52 -15
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/knowledge-graph.d.ts +2 -0
- package/dist/core/knowledge-graph.d.ts.map +1 -1
- package/dist/core/knowledge-graph.js +246 -232
- package/dist/core/knowledge-graph.js.map +1 -1
- package/dist/core/name-validator.js +1 -1
- package/dist/core/name-validator.js.map +1 -1
- package/dist/core/pipeline-test.d.ts.map +1 -1
- package/dist/core/pipeline-test.js +7 -4
- package/dist/core/pipeline-test.js.map +1 -1
- package/dist/core/prompt-builder.d.ts +3 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +629 -77
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/rag-engine.d.ts +10 -5
- package/dist/core/rag-engine.d.ts.map +1 -1
- package/dist/core/rag-engine.js +134 -16
- package/dist/core/rag-engine.js.map +1 -1
- package/dist/core/spec-paths.d.ts +42 -2
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +207 -11
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/structured-extractor.d.ts +67 -0
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +1298 -23
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/test-config.d.ts +1 -1
- package/dist/core/test-config.d.ts.map +1 -1
- package/dist/core/test-config.js +85 -65
- package/dist/core/test-config.js.map +1 -1
- package/dist/core/ui-verify/report-generator.d.ts.map +1 -1
- package/dist/core/ui-verify/report-generator.js +7 -3
- package/dist/core/ui-verify/report-generator.js.map +1 -1
- package/dist/core/ui-verify/smoke-engine.js +7 -1
- package/dist/core/ui-verify/smoke-engine.js.map +1 -1
- package/dist/core/unified-config.d.ts +43 -3
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +133 -13
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/unified-retrieval.d.ts.map +1 -1
- package/dist/core/unified-retrieval.js +41 -15
- package/dist/core/unified-retrieval.js.map +1 -1
- package/dist/core/unit-context-assembler.js +2 -2
- package/dist/core/unit-context-assembler.js.map +1 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +59 -44
- package/dist/core/validator.js.map +1 -1
- package/dist/core/verify-engine.d.ts +9 -2
- package/dist/core/verify-engine.d.ts.map +1 -1
- package/dist/core/verify-engine.js +274 -108
- package/dist/core/verify-engine.js.map +1 -1
- package/dist/core/verify-report-aggregator.js +9 -20
- package/dist/core/verify-report-aggregator.js.map +1 -1
- package/dist/utils/synonyms.d.ts +24 -0
- package/dist/utils/synonyms.d.ts.map +1 -0
- package/dist/utils/synonyms.js +114 -0
- package/dist/utils/synonyms.js.map +1 -0
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ exports.generateSpecsFromRequirements = generateSpecsFromRequirements;
|
|
|
12
12
|
*
|
|
13
13
|
* 输入:
|
|
14
14
|
* - 纯需求: --req docs/a.md docs/b.md
|
|
15
|
-
* - 纯代码: --src backend/src
|
|
15
|
+
* - 纯代码: --src backend/src frontend/src
|
|
16
16
|
* - 需求+代码: 两者都指定
|
|
17
17
|
*
|
|
18
18
|
* 输出范围 (scope):
|
|
@@ -25,6 +25,8 @@ const path_1 = require("path");
|
|
|
25
25
|
const logger_1 = require("../utils/logger");
|
|
26
26
|
const task_utils_1 = require("../utils/task-utils");
|
|
27
27
|
const code_scanner_1 = require("./code-scanner");
|
|
28
|
+
// v8.3.125+: 同义词扩展,解决中英文/缩写匹配失败问题
|
|
29
|
+
const synonyms_1 = require("../utils/synonyms");
|
|
28
30
|
const ai_context_generator_1 = require("./ai-context-generator");
|
|
29
31
|
const git_integration_1 = require("./git-integration");
|
|
30
32
|
const rag_engine_1 = require("./rag-engine");
|
|
@@ -51,7 +53,7 @@ async function runAnalysis(input) {
|
|
|
51
53
|
if (input.scope === 'iteration' && input.iteration) {
|
|
52
54
|
const productReqDir = (0, path_1.join)(`Iteration-${input.iteration}`, '010-requirements');
|
|
53
55
|
if (await (0, fs_extra_1.pathExists)(productReqDir)) {
|
|
54
|
-
// 递归扫描子目录 (
|
|
56
|
+
// 递归扫描子目录 ({端名}/ 等)
|
|
55
57
|
const scanDir = async (dir) => {
|
|
56
58
|
const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
|
|
57
59
|
for (const e of entries) {
|
|
@@ -160,9 +162,11 @@ async function runAnalysis(input) {
|
|
|
160
162
|
logger_1.logger.debug('RAG 索引生成失败(非关键):', e);
|
|
161
163
|
}
|
|
162
164
|
// ── 统一刷新代码索引 + 知识图谱(所有 scope)──
|
|
165
|
+
// v8.3.125+: 按迭代端名筛选源码路径,减少增量刷新范围
|
|
163
166
|
try {
|
|
164
167
|
logger_1.logger.info(' 🔄 刷新代码索引...');
|
|
165
|
-
await (0, code_scanner_1.
|
|
168
|
+
const codeIndexScope = await (0, code_scanner_1.resolveIterationScope)(effectiveInput.iteration);
|
|
169
|
+
await (0, code_scanner_1.buildCodeIndex)(codeIndexScope, true);
|
|
166
170
|
logger_1.logger.info(' ✅ 代码索引已刷新');
|
|
167
171
|
}
|
|
168
172
|
catch (e) {
|
|
@@ -203,16 +207,18 @@ async function analyzeRequirements(input) {
|
|
|
203
207
|
const issues = scanCompleteness(fullContent);
|
|
204
208
|
const archImpact = await analyzeArchitectureImpact(fullContent);
|
|
205
209
|
// ── 需求分析时默认读取关联代码(避免分析不接地气 + 减少重复 token 消耗) ──
|
|
210
|
+
// v8.3.122+: 增加文件数和字节配额,确保深入阅读业务逻辑
|
|
206
211
|
let sourceContents = {};
|
|
207
212
|
const shouldReadSource = input.readSource !== false;
|
|
208
213
|
if (shouldReadSource && (input.iteration || input.taskId)) {
|
|
209
|
-
const limit = input.depth === 'deep' ?
|
|
210
|
-
const maxBytes = input.depth === 'deep' ?
|
|
214
|
+
const limit = input.depth === 'deep' ? 25 : (input.depth === 'quick' ? 5 : 12);
|
|
215
|
+
const maxBytes = input.depth === 'deep' ? 150000 : (input.depth === 'quick' ? 30000 : 70000);
|
|
211
216
|
const rawMatches = await (0, code_scanner_1.findRelevantCode)(fullContent, limit, input.sourceScope, input.iteration, input.taskId);
|
|
212
|
-
sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes);
|
|
217
|
+
sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes, limit);
|
|
213
218
|
if (Object.keys(sourceContents).length > 0) {
|
|
214
219
|
const scopeHint = input.sourceScope ? ` (范围: ${input.sourceScope})` : '';
|
|
215
|
-
|
|
220
|
+
const totalChars = Object.values(sourceContents).reduce((a, c) => a + c.length, 0);
|
|
221
|
+
logger_1.logger.info(` 📖 需求分析已关联 ${Object.keys(sourceContents).length} 个源码文件 (${Math.round(totalChars / 1000)}K 字符)${scopeHint}`);
|
|
216
222
|
}
|
|
217
223
|
}
|
|
218
224
|
let outputPath;
|
|
@@ -339,15 +345,17 @@ async function analyzeCombined(input) {
|
|
|
339
345
|
// 代码结构分析
|
|
340
346
|
const fileStats = await scanSourceDirs(input.sources, input.depth);
|
|
341
347
|
const apiInventory = await buildApiInventory(input.sources);
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
348
|
+
// 确保代码索引是最新的
|
|
349
|
+
// v8.3.125+: 按迭代端名筛选 scope + 增量刷新 + mtime 精确判断过期
|
|
350
|
+
const codeIndexScope = await (0, code_scanner_1.resolveIterationScope)(input.iteration);
|
|
351
|
+
if (await (0, code_scanner_1.isIndexStale)(codeIndexScope)) {
|
|
352
|
+
logger_1.logger.info(' 🔍 索引已过期,自动重建代码索引...');
|
|
353
|
+
await (0, code_scanner_1.buildCodeIndex)(codeIndexScope, true);
|
|
346
354
|
logger_1.logger.info(' ✅ 代码索引已更新(包含多端识别 + 模块分组 + git 联动分析)');
|
|
347
355
|
logger_1.logger.info(' 💡 提示:可手动运行 `speccore code-index --show` 查看索引摘要');
|
|
348
356
|
}
|
|
349
357
|
else {
|
|
350
|
-
logger_1.logger.info(' 📚
|
|
358
|
+
logger_1.logger.info(' 📚 使用缓存的代码索引(源码未变更)');
|
|
351
359
|
}
|
|
352
360
|
// ── AI 上下文生成: 替代关键词匹配(传入已读取的需求内容,避免重复 readFile) ──
|
|
353
361
|
const aiContext = await (0, ai_context_generator_1.generateAIContext)({
|
|
@@ -364,13 +372,14 @@ async function analyzeCombined(input) {
|
|
|
364
372
|
logger_1.logger.info(` 🔗 API: ${aiContext.totalApis} 个`);
|
|
365
373
|
// 默认读取相关源码内容注入分析(除非明确关闭)
|
|
366
374
|
// 解耦设计:从完整索引中按 sourceScope 筛选,不是按 scope 建索引
|
|
375
|
+
// v8.3.122+: 增加配额,确保深入阅读业务逻辑和公共模块
|
|
367
376
|
let sourceContents = {};
|
|
368
377
|
const shouldReadSource = input.readSource !== false;
|
|
369
378
|
if (shouldReadSource) {
|
|
370
|
-
const limit = input.depth === 'deep' ?
|
|
371
|
-
const maxBytes = input.depth === 'deep' ?
|
|
379
|
+
const limit = input.depth === 'deep' ? 30 : (input.depth === 'quick' ? 8 : 15);
|
|
380
|
+
const maxBytes = input.depth === 'deep' ? 200000 : (input.depth === 'quick' ? 40000 : 100000);
|
|
372
381
|
const rawMatches = await (0, code_scanner_1.findRelevantCode)(fullReqContent, limit, input.sourceScope, input.iteration, input.taskId);
|
|
373
|
-
sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes);
|
|
382
|
+
sourceContents = await (0, code_scanner_1.readRelevantSource)(rawMatches, maxBytes, limit);
|
|
374
383
|
if (Object.keys(sourceContents).length > 0) {
|
|
375
384
|
const scopeHint = input.sourceScope ? ` (范围: ${input.sourceScope})` : '';
|
|
376
385
|
logger_1.logger.info(` 📖 已读取 ${Object.keys(sourceContents).length} 个源码文件${scopeHint} (${Object.values(sourceContents).reduce((a, c) => a + c.length, 0)} 字符)`);
|
|
@@ -835,12 +844,14 @@ function buildIterationReqReport(input, issues, archImpact, sourceContents) {
|
|
|
835
844
|
if (archImpact.newDependencies.length > 0)
|
|
836
845
|
r += `- [ ] 确认新增依赖的引入方案和排期\n`;
|
|
837
846
|
// ── 代码关联分析(v6.8.0 新增:需求分析默认关联代码) ──
|
|
847
|
+
// v8.3.122+: 按深度动态调整代码展示配额,确保业务逻辑可见
|
|
838
848
|
if (sourceContents && Object.keys(sourceContents).length > 0) {
|
|
839
849
|
r += `\n---\n\n## 3.5 关联代码现状\n\n`;
|
|
840
850
|
r += `> 以下源码文件与当前需求相关,供技术方案参考\n\n`;
|
|
851
|
+
const previewLimit = input.depth === 'deep' ? 3000 : (input.depth === 'quick' ? 800 : 1500);
|
|
841
852
|
for (const [path, content] of Object.entries(sourceContents)) {
|
|
842
|
-
const preview = content.slice(0,
|
|
843
|
-
r += `### \`${path}\`\n\n\`\`\`${path.split('.').pop() || 'ts'}\n${preview}${content.length >
|
|
853
|
+
const preview = content.slice(0, previewLimit).replace(/\n/g, '\n ');
|
|
854
|
+
r += `### \`${path}\`\n\n\`\`\`${path.split('.').pop() || 'ts'}\n${preview}${content.length > previewLimit ? '\n // ... 截断 ...' : ''}\n\`\`\`\n\n`;
|
|
844
855
|
}
|
|
845
856
|
}
|
|
846
857
|
r += `\n---\n\n## 4. 技术方案(待填写)\n\n`;
|
|
@@ -1098,14 +1109,20 @@ async function buildAIEnhancedReport(input, scope, params) {
|
|
|
1098
1109
|
if (readFiles.length > 0 || uncoveredFiles.length > 0) {
|
|
1099
1110
|
r += `## 📚 源码分析清单\n\n`;
|
|
1100
1111
|
// 已分析文件
|
|
1112
|
+
// v8.3.122+: 增加代码内容预览,确保 AI 能看到业务逻辑
|
|
1101
1113
|
if (readFiles.length > 0) {
|
|
1102
1114
|
r += `### ✅ 已分析(${readFiles.length} 个文件)\n\n`;
|
|
1115
|
+
const previewLimit = input.depth === 'deep' ? 3000 : (input.depth === 'quick' ? 800 : 1500);
|
|
1103
1116
|
for (const [file, content] of Object.entries(sourceContents)) {
|
|
1104
1117
|
const lines = content.split('\n').length;
|
|
1105
1118
|
const size = (content.length / 1024).toFixed(1);
|
|
1106
1119
|
r += `- \`${file}\` (${lines} 行, ${size}KB)\n`;
|
|
1120
|
+
// 展示代码预览
|
|
1121
|
+
if (content.length > 0) {
|
|
1122
|
+
const preview = content.slice(0, previewLimit).replace(/\n/g, '\n ');
|
|
1123
|
+
r += `\n \`\`\`${file.split('.').pop() || 'ts'}\n ${preview}${content.length > previewLimit ? '\n // ... 截断 ...' : ''}\n \`\`\`\n\n`;
|
|
1124
|
+
}
|
|
1107
1125
|
}
|
|
1108
|
-
r += `\n`;
|
|
1109
1126
|
}
|
|
1110
1127
|
// 未覆盖文件(按目录分组)
|
|
1111
1128
|
if (uncoveredFiles.length > 0) {
|
|
@@ -1182,7 +1199,10 @@ function icon(severity) {
|
|
|
1182
1199
|
default: return '⚪';
|
|
1183
1200
|
}
|
|
1184
1201
|
}
|
|
1185
|
-
/** 按需求功能 × 端 分目录写入分析报告(按端提取差异化内容)
|
|
1202
|
+
/** 按需求功能 × 端 分目录写入分析报告(按端提取差异化内容)
|
|
1203
|
+
* v8.3.122+: 修复路径 — 从 020-specs/{platform}/ 改为 020-specs/{feature}/{platform}/
|
|
1204
|
+
* 与 split 读取路径保持一致,确保端级分析文档能被正确关联
|
|
1205
|
+
*/
|
|
1186
1206
|
async function writePerPlatform(iterDir, report, filename) {
|
|
1187
1207
|
const reqDir = (0, path_1.join)(iterDir, '010-requirements');
|
|
1188
1208
|
const specsBase = (0, path_1.join)(iterDir, '020-specs');
|
|
@@ -1197,16 +1217,14 @@ async function writePerPlatform(iterDir, report, filename) {
|
|
|
1197
1217
|
return;
|
|
1198
1218
|
// 平台列表从 CONSTITUTION 获取(不再硬编码默认值)
|
|
1199
1219
|
const platforms = await detectPlatformsFromConstitution();
|
|
1200
|
-
for (const
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
for (const feature of features) {
|
|
1207
|
-
// 每个 feature 文件也按端提取
|
|
1220
|
+
for (const feature of features) {
|
|
1221
|
+
for (const platform of platforms) {
|
|
1222
|
+
// v8.3.122+: 输出到 020-specs/{feature}/{platform}/ 与 split 读取路径一致
|
|
1223
|
+
// v8.3.125+: 使用增量合并,避免多次分析覆盖丢失
|
|
1224
|
+
const platformDir = (0, path_1.join)(specsBase, feature, platform);
|
|
1225
|
+
await (0, fs_extra_1.ensureDir)(platformDir);
|
|
1208
1226
|
const featureContent = extractFeatureForPlatform(report, feature, platform);
|
|
1209
|
-
await (
|
|
1227
|
+
await writeMergedFile((0, path_1.join)(platformDir, filename), featureContent);
|
|
1210
1228
|
}
|
|
1211
1229
|
}
|
|
1212
1230
|
}
|
|
@@ -1264,8 +1282,8 @@ function extractPlatformContent(report, platform) {
|
|
|
1264
1282
|
}
|
|
1265
1283
|
/** 从报告中提取特定功能 × 特定端的内容 */
|
|
1266
1284
|
function extractFeatureForPlatform(report, feature, platform) {
|
|
1267
|
-
//
|
|
1268
|
-
const featureKeywords = [
|
|
1285
|
+
// v8.3.125+: 使用同义词扩展,解决 "登录" 与 "auth" 等跨语言/缩写不匹配问题
|
|
1286
|
+
const featureKeywords = [...(0, synonyms_1.expandSynonyms)((0, synonyms_1.extractNormalizedKeywords)(feature))];
|
|
1269
1287
|
const lines = report.split('\n');
|
|
1270
1288
|
const sections = [];
|
|
1271
1289
|
let current = { heading: '', content: [] };
|
|
@@ -1285,9 +1303,10 @@ function extractFeatureForPlatform(report, feature, platform) {
|
|
|
1285
1303
|
sections.push({ heading: current.heading, content: current.content });
|
|
1286
1304
|
}
|
|
1287
1305
|
// 先按功能名匹配,再按端关键词过滤
|
|
1306
|
+
// v8.3.125+: 使用同义词扩展后的关键词集合匹配
|
|
1288
1307
|
const featureSections = sections.filter(s => {
|
|
1289
1308
|
const text = (s.heading + ' ' + s.content.join(' ')).toLowerCase();
|
|
1290
|
-
return featureKeywords.some(kw => text.includes(kw
|
|
1309
|
+
return featureKeywords.some(kw => text.includes(kw));
|
|
1291
1310
|
});
|
|
1292
1311
|
if (featureSections.length > 0) {
|
|
1293
1312
|
const platformKws = PLATFORM_KEYWORDS[platform] || [platform];
|
|
@@ -1300,9 +1319,41 @@ function extractFeatureForPlatform(report, feature, platform) {
|
|
|
1300
1319
|
}
|
|
1301
1320
|
return `# ${feature} (${platform})\n\n_该功能在 ${platform} 端暂无独立分析内容,请参考完整报告。_\n`;
|
|
1302
1321
|
}
|
|
1322
|
+
/** 从报告中提取特定功能模块的内容(不限制端,用于模块总览) */
|
|
1323
|
+
function extractFeatureContent(report, feature) {
|
|
1324
|
+
// v8.3.125+: 使用同义词扩展
|
|
1325
|
+
const featureKeywords = [...(0, synonyms_1.expandSynonyms)((0, synonyms_1.extractNormalizedKeywords)(feature))];
|
|
1326
|
+
const lines = report.split('\n');
|
|
1327
|
+
const sections = [];
|
|
1328
|
+
let current = { heading: '', content: [] };
|
|
1329
|
+
for (const line of lines) {
|
|
1330
|
+
const hm = line.match(/^(#{1,4})\s+(.+)/);
|
|
1331
|
+
if (hm) {
|
|
1332
|
+
if (current.heading || current.content.length > 0) {
|
|
1333
|
+
sections.push({ heading: current.heading, content: current.content });
|
|
1334
|
+
}
|
|
1335
|
+
current = { heading: line, content: [] };
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
current.content.push(line);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (current.heading || current.content.length > 0) {
|
|
1342
|
+
sections.push({ heading: current.heading, content: current.content });
|
|
1343
|
+
}
|
|
1344
|
+
const matched = sections.filter(s => {
|
|
1345
|
+
const text = (s.heading + ' ' + s.content.join(' ')).toLowerCase();
|
|
1346
|
+
return featureKeywords.some(kw => text.includes(kw));
|
|
1347
|
+
});
|
|
1348
|
+
if (matched.length > 0) {
|
|
1349
|
+
return matched.map(s => s.heading + '\n' + s.content.join('\n')).join('\n\n');
|
|
1350
|
+
}
|
|
1351
|
+
return '';
|
|
1352
|
+
}
|
|
1303
1353
|
/**
|
|
1304
|
-
* 局部分析:只分析单个功能模块,写入 020-specs/
|
|
1354
|
+
* 局部分析:只分析单个功能模块,写入 020-specs/{feature}/overview/
|
|
1305
1355
|
* 用于 --feature 模式,避免全量重跑
|
|
1356
|
+
* v8.3.122+: 修复路径与规范一致
|
|
1306
1357
|
*/
|
|
1307
1358
|
async function analyzeSingleFeature(iterDir, featureName) {
|
|
1308
1359
|
const reqDir = (0, path_1.join)(iterDir, '010-requirements');
|
|
@@ -1346,10 +1397,10 @@ async function analyzeSingleFeature(iterDir, featureName) {
|
|
|
1346
1397
|
r += `_未检测到明显架构影响_\n\n`;
|
|
1347
1398
|
}
|
|
1348
1399
|
r += `## 3. 需求原文\n\n${content}\n`;
|
|
1349
|
-
// 写入 020-specs/
|
|
1350
|
-
const
|
|
1351
|
-
await (0, fs_extra_1.ensureDir)(
|
|
1352
|
-
const outputPath = (0, path_1.join)(
|
|
1400
|
+
// v8.3.122+: 写入 020-specs/{feature}/overview/ 与规范一致
|
|
1401
|
+
const overviewDir = (0, path_1.join)(iterDir, '020-specs', featureName, 'overview');
|
|
1402
|
+
await (0, fs_extra_1.ensureDir)(overviewDir);
|
|
1403
|
+
const outputPath = (0, path_1.join)(overviewDir, 'ANALYSIS.md');
|
|
1353
1404
|
await (0, fs_extra_1.writeFile)(outputPath, r);
|
|
1354
1405
|
return { outputPath, report: r };
|
|
1355
1406
|
}
|
|
@@ -1402,10 +1453,13 @@ async function analyzeSingleTypedDoc(iterDir, docPath) {
|
|
|
1402
1453
|
await (0, fs_extra_1.writeFile)(outputPath, r);
|
|
1403
1454
|
return { outputPath, report: r };
|
|
1404
1455
|
}
|
|
1405
|
-
/**
|
|
1456
|
+
/** 按功能模块写入模块总览(提取该功能模块在所有端中的内容)
|
|
1457
|
+
* v8.3.122+: 修复路径 — 从 020-specs/features/{feature}.md 改为 020-specs/{feature}/overview/
|
|
1458
|
+
* 与 split 读取路径保持一致,避免整份报告冗余复制
|
|
1459
|
+
*/
|
|
1406
1460
|
async function writePerFeature(iterDir, report, filename) {
|
|
1407
1461
|
const reqDir = (0, path_1.join)(iterDir, '010-requirements');
|
|
1408
|
-
const
|
|
1462
|
+
const specsBase = (0, path_1.join)(iterDir, '020-specs');
|
|
1409
1463
|
// 非 feature 型目录的排除名单
|
|
1410
1464
|
const EXCLUDED_DIRS = new Set(['sources', 'assets', 'converted', 'staging', 'bugs', 'refactors', 'research', 'prototypes']);
|
|
1411
1465
|
try {
|
|
@@ -1416,16 +1470,18 @@ async function writePerFeature(iterDir, report, filename) {
|
|
|
1416
1470
|
.map(e => e.name);
|
|
1417
1471
|
if (features.length === 0)
|
|
1418
1472
|
return;
|
|
1419
|
-
await (0, fs_extra_1.ensureDir)(featuresDir);
|
|
1420
1473
|
for (const feature of features) {
|
|
1421
|
-
//
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1474
|
+
// 提取该功能模块的专属内容(不限制端)
|
|
1475
|
+
let featureContent = extractFeatureContent(report, feature);
|
|
1476
|
+
if (featureContent.trim().length === 0) {
|
|
1477
|
+
// 无匹配内容时回退:写入提示说明
|
|
1478
|
+
featureContent = `# ${feature} — 模块总览\n\n_未从分析报告中提取到该模块的专属内容,请参考完整报告或检查需求文档命名。_\n`;
|
|
1426
1479
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1480
|
+
// v8.3.122+: 输出到 020-specs/{feature}/overview/ 与 split 读取路径一致
|
|
1481
|
+
// v8.3.125+: 使用增量合并,避免多次分析覆盖丢失
|
|
1482
|
+
const overviewDir = (0, path_1.join)(specsBase, feature, 'overview');
|
|
1483
|
+
await (0, fs_extra_1.ensureDir)(overviewDir);
|
|
1484
|
+
await writeMergedFile((0, path_1.join)(overviewDir, filename), featureContent);
|
|
1429
1485
|
}
|
|
1430
1486
|
}
|
|
1431
1487
|
catch { }
|
|
@@ -1716,17 +1772,21 @@ async function detectPlatformsFromConstitution() {
|
|
|
1716
1772
|
const lines = content.split('\n');
|
|
1717
1773
|
// ── Layer 1: 表格「对应端」/「对应需求端」列(旧版回退)──
|
|
1718
1774
|
let headerRowIndex = -1;
|
|
1719
|
-
let
|
|
1775
|
+
let platformColIdx = -1;
|
|
1776
|
+
let identifierColIdx = -1;
|
|
1720
1777
|
for (let i = 0; i < lines.length; i++) {
|
|
1721
1778
|
const cells = lines[i].split('|').map((c) => c.trim()).filter(Boolean);
|
|
1722
|
-
const
|
|
1723
|
-
if (
|
|
1779
|
+
const pIdx = cells.findIndex((c) => c.includes('对应端') || c.includes('对应需求端'));
|
|
1780
|
+
if (pIdx >= 0) {
|
|
1724
1781
|
headerRowIndex = i;
|
|
1725
|
-
|
|
1782
|
+
platformColIdx = pIdx;
|
|
1783
|
+
identifierColIdx = cells.findIndex((c) => c.includes('工程标识') || c.includes('工程'));
|
|
1784
|
+
if (identifierColIdx < 0)
|
|
1785
|
+
identifierColIdx = 0;
|
|
1726
1786
|
break;
|
|
1727
1787
|
}
|
|
1728
1788
|
}
|
|
1729
|
-
if (headerRowIndex >= 0) {
|
|
1789
|
+
if (headerRowIndex >= 0 && platformColIdx >= 0) {
|
|
1730
1790
|
const platforms = [];
|
|
1731
1791
|
const backendPlatforms = [];
|
|
1732
1792
|
const seen = new Set();
|
|
@@ -1742,8 +1802,8 @@ async function detectPlatformsFromConstitution() {
|
|
|
1742
1802
|
if (!line.startsWith('|'))
|
|
1743
1803
|
break;
|
|
1744
1804
|
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
1745
|
-
const projectName = cells[
|
|
1746
|
-
const platformChinese = cells
|
|
1805
|
+
const projectName = identifierColIdx >= 0 && cells.length > identifierColIdx ? cells[identifierColIdx] : '';
|
|
1806
|
+
const platformChinese = cells.length > platformColIdx ? cells[platformColIdx] : '';
|
|
1747
1807
|
// 跳过空值和占位符(如「待填写」)
|
|
1748
1808
|
if (!platformChinese || !projectName)
|
|
1749
1809
|
continue;
|
|
@@ -3057,4 +3117,108 @@ function extractStatusEnumsFromContent(platformContent) {
|
|
|
3057
3117
|
}
|
|
3058
3118
|
return enums;
|
|
3059
3119
|
}
|
|
3120
|
+
// ═══════════════════════════════════════════════
|
|
3121
|
+
// v8.3.125+: 增量合并 — 避免多次分析覆盖丢失
|
|
3122
|
+
// ═══════════════════════════════════════════════
|
|
3123
|
+
/**
|
|
3124
|
+
* 按 Markdown 标题合并文档内容
|
|
3125
|
+
* v8.3.122+: 新内容中存在的章节替换旧章节,新章节追加,旧文档中未变动的章节保留
|
|
3126
|
+
* v8.3.125+: 从 analyze.ts 迁移到 analyze-engine.ts,供 writePerPlatform/writePerFeature 使用
|
|
3127
|
+
*/
|
|
3128
|
+
function mergeDocumentContent(existing, newContent) {
|
|
3129
|
+
if (!existing.trim())
|
|
3130
|
+
return newContent;
|
|
3131
|
+
// 解析文档为标题块
|
|
3132
|
+
function parseSections(text) {
|
|
3133
|
+
const sections = new Map();
|
|
3134
|
+
const lines = text.split('\n');
|
|
3135
|
+
let currentHeading = '';
|
|
3136
|
+
let currentLevel = 0;
|
|
3137
|
+
let currentBody = [];
|
|
3138
|
+
let headerLines = []; // 文档开头无标题的内容
|
|
3139
|
+
for (const line of lines) {
|
|
3140
|
+
const hm = line.match(/^(#{1,3})\s+(.+)/);
|
|
3141
|
+
if (hm) {
|
|
3142
|
+
if (currentHeading) {
|
|
3143
|
+
sections.set(currentHeading, {
|
|
3144
|
+
heading: currentHeading,
|
|
3145
|
+
level: currentLevel,
|
|
3146
|
+
body: currentBody.join('\n'),
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
else if (currentBody.length > 0) {
|
|
3150
|
+
headerLines = [...currentBody];
|
|
3151
|
+
}
|
|
3152
|
+
currentHeading = hm[2].trim();
|
|
3153
|
+
currentLevel = hm[1].length;
|
|
3154
|
+
currentBody = [line];
|
|
3155
|
+
}
|
|
3156
|
+
else {
|
|
3157
|
+
currentBody.push(line);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
if (currentHeading) {
|
|
3161
|
+
sections.set(currentHeading, {
|
|
3162
|
+
heading: currentHeading,
|
|
3163
|
+
level: currentLevel,
|
|
3164
|
+
body: currentBody.join('\n'),
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
// 保存文档头部内容(如 frontmatter、标题等)
|
|
3168
|
+
if (headerLines.length > 0) {
|
|
3169
|
+
sections.set('__HEADER__', { heading: '__HEADER__', level: 0, body: headerLines.join('\n') });
|
|
3170
|
+
}
|
|
3171
|
+
return sections;
|
|
3172
|
+
}
|
|
3173
|
+
const oldSections = parseSections(existing);
|
|
3174
|
+
const newSections = parseSections(newContent);
|
|
3175
|
+
// 构建合并结果:保留旧文档的头部,按旧文档顺序输出
|
|
3176
|
+
// 对于每个旧章节:如果新文档有同名章节,用新内容替换;否则保留旧内容
|
|
3177
|
+
// 新文档中有但旧文档中没有的章节,追加到最后
|
|
3178
|
+
const result = [];
|
|
3179
|
+
// 先输出头部
|
|
3180
|
+
const header = oldSections.get('__HEADER__');
|
|
3181
|
+
if (header) {
|
|
3182
|
+
result.push(header.body);
|
|
3183
|
+
oldSections.delete('__HEADER__');
|
|
3184
|
+
}
|
|
3185
|
+
newSections.delete('__HEADER__');
|
|
3186
|
+
// 收集旧文档中存在的章节标题顺序
|
|
3187
|
+
const oldOrder = [...oldSections.keys()];
|
|
3188
|
+
const addedNew = new Set();
|
|
3189
|
+
for (const heading of oldOrder) {
|
|
3190
|
+
if (newSections.has(heading)) {
|
|
3191
|
+
result.push(newSections.get(heading).body);
|
|
3192
|
+
addedNew.add(heading);
|
|
3193
|
+
}
|
|
3194
|
+
else {
|
|
3195
|
+
result.push(oldSections.get(heading).body);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
// 追加新文档中旧文档没有的章节
|
|
3199
|
+
for (const [heading, section] of newSections) {
|
|
3200
|
+
if (!addedNew.has(heading)) {
|
|
3201
|
+
result.push(section.body);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return result.join('\n\n');
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* 安全写入文件:如果文件已存在,先读取并与新内容按章节合并
|
|
3208
|
+
* v8.3.125+: 替代直接 writeFile,避免多次分析覆盖丢失
|
|
3209
|
+
*/
|
|
3210
|
+
async function writeMergedFile(filePath, newContent) {
|
|
3211
|
+
try {
|
|
3212
|
+
if (await (0, fs_extra_1.pathExists)(filePath)) {
|
|
3213
|
+
const existing = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
|
|
3214
|
+
const merged = mergeDocumentContent(existing, newContent);
|
|
3215
|
+
await (0, fs_extra_1.writeFile)(filePath, merged);
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
catch {
|
|
3220
|
+
// 读取或合并失败,回退到直接写入
|
|
3221
|
+
}
|
|
3222
|
+
await (0, fs_extra_1.writeFile)(filePath, newContent);
|
|
3223
|
+
}
|
|
3060
3224
|
//# sourceMappingURL=analyze-engine.js.map
|