speccore 8.3.121 → 8.3.126
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.map +1 -1
- package/dist/commands/init.js +512 -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 +29 -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
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.buildCodeIndex = buildCodeIndex;
|
|
4
37
|
exports.findRelevantCode = findRelevantCode;
|
|
@@ -7,6 +40,7 @@ exports.loadCodeIndex = loadCodeIndex;
|
|
|
7
40
|
exports.checkCodeIndexFreshness = checkCodeIndexFreshness;
|
|
8
41
|
exports.isIndexStale = isIndexStale;
|
|
9
42
|
exports.loadFullIndex = loadFullIndex;
|
|
43
|
+
exports.resolveIterationScope = resolveIterationScope;
|
|
10
44
|
/**
|
|
11
45
|
* code-scanner — 智能源码发现 + 范围控制 + 多端识别
|
|
12
46
|
*
|
|
@@ -20,9 +54,12 @@ exports.loadFullIndex = loadFullIndex;
|
|
|
20
54
|
const fs_extra_1 = require("fs-extra");
|
|
21
55
|
const path_1 = require("path");
|
|
22
56
|
const child_process_1 = require("child_process");
|
|
57
|
+
const logger_1 = require("../utils/logger");
|
|
23
58
|
const knowledge_graph_1 = require("./knowledge-graph");
|
|
24
59
|
const reverse_sync_1 = require("./reverse-sync");
|
|
25
60
|
const spec_paths_1 = require("./spec-paths");
|
|
61
|
+
// v8.3.125+: 同义词扩展,补充 SEMANTIC_MAP 覆盖范围
|
|
62
|
+
const synonyms_1 = require("../utils/synonyms");
|
|
26
63
|
// ── 端名缓存(v6.48.0+):从 CONSTITUTION.md 加载,优先于通用模式匹配 ──
|
|
27
64
|
let _constitutionPlatforms = null;
|
|
28
65
|
async function getConstitutionPlatforms() {
|
|
@@ -233,22 +270,83 @@ taskId) {
|
|
|
233
270
|
const filesToSearch = scopeDirs.length > 0
|
|
234
271
|
? index.files.filter(f => scopeDirs.some(dir => f.path.startsWith(dir)))
|
|
235
272
|
: index.files;
|
|
236
|
-
// ── P0:
|
|
237
|
-
|
|
273
|
+
// ── P0: 深度知识图谱关联 + Spec 内容引导 ──
|
|
274
|
+
// v8.3.122+: 不再只获取 depends_on 任务ID,而是深度利用知识图谱的全部关联信息
|
|
238
275
|
let kgTaskIds = new Set();
|
|
276
|
+
let specKeywords = []; // 从 spec 文件提取的技术关键词(高权重)
|
|
277
|
+
let kgSourceFilePaths = new Set(); // 知识图谱中的 source-file 实体路径
|
|
278
|
+
let kgCodeEntities = new Set(); // business_module 的 codeEntities
|
|
239
279
|
if (iteration) {
|
|
240
|
-
const kg = await (0, knowledge_graph_1.
|
|
280
|
+
const kg = await (0, knowledge_graph_1.loadFreshKnowledgeGraph)(process.cwd(), iteration);
|
|
241
281
|
if (kg && taskId) {
|
|
242
|
-
// 1. 当前任务直接关联
|
|
282
|
+
// 1. 当前任务直接关联 + 依赖任务
|
|
243
283
|
kgTaskIds.add(taskId);
|
|
244
|
-
// 2. 依赖任务也纳入
|
|
245
284
|
for (const rel of kg.relations) {
|
|
246
285
|
if (rel.from === taskId && rel.type === 'depends_on') {
|
|
247
286
|
kgTaskIds.add(rel.to);
|
|
248
287
|
}
|
|
249
288
|
}
|
|
289
|
+
// v8.3.122+: 深度利用知识图谱 — 获取完整任务上下文
|
|
290
|
+
const fullContext = (0, knowledge_graph_1.getFullTaskContext)(kg, taskId);
|
|
291
|
+
// 2. 从 relatedSpecs 读取内容,提取精准技术关键词
|
|
292
|
+
for (const spec of fullContext.relatedSpecs) {
|
|
293
|
+
if (spec.file && await (0, fs_extra_1.pathExists)(spec.file)) {
|
|
294
|
+
try {
|
|
295
|
+
const specContent = await (0, fs_extra_1.readFile)(spec.file, 'utf-8');
|
|
296
|
+
const techKws = extractTechnicalKeywords(specContent);
|
|
297
|
+
specKeywords.push(...techKws);
|
|
298
|
+
logger_1.logger.debug(` 📄 Spec ${spec.file} 提取 ${techKws.length} 个技术关键词`);
|
|
299
|
+
}
|
|
300
|
+
catch { /* 读取失败不阻断 */ }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// 3. 从知识图谱 source-file 实体中查找关联文件
|
|
304
|
+
const demandKws = extractKeywords(requirements);
|
|
305
|
+
for (const entity of Object.values(kg.entities)) {
|
|
306
|
+
if (entity.type === 'source-file' && entity.file) {
|
|
307
|
+
// 3a. 通过平台匹配:任务的平台与该 source-file 的 endpoint 一致
|
|
308
|
+
const taskPlatforms = fullContext.siblingSubtasks
|
|
309
|
+
.map(st => st.platform)
|
|
310
|
+
.filter(Boolean);
|
|
311
|
+
if (entity.endpoint && taskPlatforms.includes(entity.endpoint)) {
|
|
312
|
+
kgSourceFilePaths.add(entity.file);
|
|
313
|
+
}
|
|
314
|
+
// 3b. 通过语义标签匹配:semanticTags 与需求关键词匹配
|
|
315
|
+
if (entity.semanticTags && entity.semanticTags.length > 0) {
|
|
316
|
+
for (const tag of entity.semanticTags) {
|
|
317
|
+
const tagLower = tag.toLowerCase();
|
|
318
|
+
if (demandKws.some(kw => tagLower.includes(kw.toLowerCase()) || kw.toLowerCase().includes(tagLower))) {
|
|
319
|
+
kgSourceFilePaths.add(entity.file);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// 3c. 通过 exports 匹配:导出的类/函数名出现在 spec 关键词中
|
|
324
|
+
if (entity.exports && specKeywords.length > 0) {
|
|
325
|
+
for (const exp of entity.exports) {
|
|
326
|
+
const expLower = exp.toLowerCase();
|
|
327
|
+
if (specKeywords.some(kw => expLower.includes(kw.toLowerCase()) || kw.toLowerCase().includes(expLower))) {
|
|
328
|
+
kgSourceFilePaths.add(entity.file);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// 4. 从 business_module 实体获取 codeEntities
|
|
334
|
+
if (entity.type === 'business_module' && entity.codeEntities) {
|
|
335
|
+
const moduleName = (entity.businessModule || entity.title || '').toLowerCase();
|
|
336
|
+
if (demandKws.some(kw => moduleName.includes(kw.toLowerCase()) || kw.toLowerCase().includes(moduleName))) {
|
|
337
|
+
for (const ce of entity.codeEntities) {
|
|
338
|
+
kgCodeEntities.add(ce);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
250
343
|
}
|
|
251
344
|
}
|
|
345
|
+
// 去重并限制 spec 关键词数量
|
|
346
|
+
specKeywords = [...new Set(specKeywords)].slice(0, 50);
|
|
347
|
+
if (specKeywords.length > 0) {
|
|
348
|
+
logger_1.logger.debug(` 🔑 Spec 技术关键词: ${specKeywords.slice(0, 10).join(', ')}${specKeywords.length > 10 ? '...' : ''}`);
|
|
349
|
+
}
|
|
252
350
|
// ── P0: 扫描 @spec 注释,找到关联的代码文件 ──
|
|
253
351
|
let specAnnotatedFiles = new Map(); // filePath -> score boost
|
|
254
352
|
if (taskId) {
|
|
@@ -283,6 +381,17 @@ taskId) {
|
|
|
283
381
|
if (specBoost) {
|
|
284
382
|
score += specBoost;
|
|
285
383
|
}
|
|
384
|
+
// v8.3.122+: 知识图谱 source-file 实体直接关联(平台/语义标签/exports 匹配)
|
|
385
|
+
if (kgSourceFilePaths.has(f.path)) {
|
|
386
|
+
score += 40;
|
|
387
|
+
}
|
|
388
|
+
// v8.3.122+: 知识图谱 business_module codeEntities 关联
|
|
389
|
+
for (const ce of kgCodeEntities) {
|
|
390
|
+
if (f.path.includes(ce) || (0, path_1.basename)(f.path).replace(/\.[^.]+$/, '') === ce) {
|
|
391
|
+
score += 35;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
286
395
|
// 文件名匹配(精确 + 模糊)
|
|
287
396
|
for (const kw of keywords) {
|
|
288
397
|
if (f.path.toLowerCase().includes(kw.toLowerCase())) {
|
|
@@ -300,6 +409,26 @@ taskId) {
|
|
|
300
409
|
}
|
|
301
410
|
}
|
|
302
411
|
}
|
|
412
|
+
// v8.3.122+: Spec 技术关键词高权重匹配(从 TECH.md/REQ.md 等提取的精准关键词)
|
|
413
|
+
for (const kw of specKeywords) {
|
|
414
|
+
const kwLower = kw.toLowerCase();
|
|
415
|
+
// 文件名匹配(权重 +25,比普通关键词 +10 更高)
|
|
416
|
+
if (f.path.toLowerCase().includes(kwLower)) {
|
|
417
|
+
score += 25;
|
|
418
|
+
}
|
|
419
|
+
// 导出名匹配
|
|
420
|
+
for (const exp of f.exports) {
|
|
421
|
+
if (exp.toLowerCase().includes(kwLower) || kwLower.includes(exp.toLowerCase())) {
|
|
422
|
+
score += 20;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// API 路径匹配
|
|
426
|
+
for (const api of f.apis) {
|
|
427
|
+
if (api.toLowerCase().includes(kwLower) || kwLower.includes(api.toLowerCase())) {
|
|
428
|
+
score += 22;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
303
432
|
// API 匹配
|
|
304
433
|
for (const api of f.apis) {
|
|
305
434
|
if (requirements.includes(api))
|
|
@@ -397,6 +526,59 @@ taskId) {
|
|
|
397
526
|
if (bonus)
|
|
398
527
|
s.score += bonus;
|
|
399
528
|
}
|
|
529
|
+
// v8.3.122+: 反向 Import 传播 — 如果 A 被命中,A 所 import 的公共模块 C 也加分
|
|
530
|
+
// 解决需求文档不提"拦截器/通用方法/缓存"但代码实际依赖这些模块的问题
|
|
531
|
+
const SHARED_DIRS = ['shared', 'common', 'utils', 'lib', 'helpers', 'interceptors', 'middleware', 'core', 'base', 'config', 'constants', 'types', 'decorators', 'filters', 'guards', 'pipes'];
|
|
532
|
+
const isSharedFile = (path) => SHARED_DIRS.some(dir => path.toLowerCase().includes(`/${dir}/`));
|
|
533
|
+
const reverseImportBonus = new Map();
|
|
534
|
+
for (const s of scored) {
|
|
535
|
+
const matchedFile = s.file;
|
|
536
|
+
for (const imp of matchedFile.imports) {
|
|
537
|
+
const importedBase = imp.replace(/^\.\.?\//, '').replace(/\.[^.]+$/, '');
|
|
538
|
+
for (const other of filesToSearch) {
|
|
539
|
+
if (other.path === matchedFile.path)
|
|
540
|
+
continue;
|
|
541
|
+
const otherBase = (0, path_1.basename)(other.path).replace(/\.[^.]+$/, '');
|
|
542
|
+
const otherDirBase = (0, path_1.basename)((0, path_1.dirname)(other.path));
|
|
543
|
+
if (otherBase === importedBase || otherDirBase === importedBase || other.path.includes(imp.replace(/^\.\.?\//, ''))) {
|
|
544
|
+
const bonus = isSharedFile(other.path) ? 15 : 5;
|
|
545
|
+
reverseImportBonus.set(other.path, (reverseImportBonus.get(other.path) || 0) + bonus);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
for (const s of scored) {
|
|
551
|
+
const bonus = reverseImportBonus.get(s.file.path);
|
|
552
|
+
if (bonus)
|
|
553
|
+
s.score += bonus;
|
|
554
|
+
}
|
|
555
|
+
// v8.3.122+: 高被引用公共模块主动发现 — shared/common/utils 下被 3+ 模块引用的文件自动入榜
|
|
556
|
+
const referenceCount = new Map();
|
|
557
|
+
const sharedCandidates = filesToSearch.filter(f => isSharedFile(f.path));
|
|
558
|
+
for (const f of filesToSearch) {
|
|
559
|
+
for (const imp of f.imports) {
|
|
560
|
+
const impBase = imp.replace(/^\.\.?\//, '').replace(/\.[^.]+$/, '');
|
|
561
|
+
const impDirBase = impBase.includes('/') ? (0, path_1.basename)(impBase) : impBase;
|
|
562
|
+
for (const sc of sharedCandidates) {
|
|
563
|
+
const scBase = (0, path_1.basename)(sc.path).replace(/\.[^.]+$/, '');
|
|
564
|
+
const scDirBase = (0, path_1.basename)((0, path_1.dirname)(sc.path));
|
|
565
|
+
if (scBase === impBase || scBase === impDirBase || scDirBase === impBase || sc.path.includes(imp.replace(/^\.\.?\//, ''))) {
|
|
566
|
+
referenceCount.set(sc.path, (referenceCount.get(sc.path) || 0) + 1);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
for (const [path, count] of referenceCount) {
|
|
572
|
+
if (count >= 3) {
|
|
573
|
+
const alreadyScored = scored.some(s => s.file.path === path);
|
|
574
|
+
if (!alreadyScored) {
|
|
575
|
+
const fileObj = filesToSearch.find(f => f.path === path);
|
|
576
|
+
if (fileObj) {
|
|
577
|
+
scored.push({ file: fileObj, score: 10 + Math.min(count, 10) });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
400
582
|
// P2: 模块邻近度 — 同模块文件加分
|
|
401
583
|
const moduleBonus = new Map();
|
|
402
584
|
const scoredModules = new Set(scored.map(s => `${s.file.endpoint}:${s.file.module}`));
|
|
@@ -487,12 +669,16 @@ async function loadContractApiPaths() {
|
|
|
487
669
|
}
|
|
488
670
|
/**
|
|
489
671
|
* 读取匹配到的源码文件内容(用于分析注入)
|
|
672
|
+
* v8.3.122+: 移除 matches.slice(0, 5) 硬限制,改为按 maxBytes 动态控制,
|
|
673
|
+
* 让 analyze 传入的 limit 和 maxBytes 真正生效。
|
|
490
674
|
*/
|
|
491
|
-
async function readRelevantSource(matches, maxBytes = 50000 // 最多读 50KB
|
|
675
|
+
async function readRelevantSource(matches, maxBytes = 50000, // 最多读 50KB
|
|
676
|
+
maxFiles // 最多读多少个文件(不传则不限制,由 maxBytes 控制)
|
|
492
677
|
) {
|
|
493
678
|
const result = {};
|
|
494
679
|
let totalBytes = 0;
|
|
495
|
-
|
|
680
|
+
const filesToRead = maxFiles ? matches.slice(0, maxFiles) : matches;
|
|
681
|
+
for (const m of filesToRead) {
|
|
496
682
|
if (totalBytes >= maxBytes)
|
|
497
683
|
break;
|
|
498
684
|
try {
|
|
@@ -653,6 +839,10 @@ function expandKeywords(keywords) {
|
|
|
653
839
|
}
|
|
654
840
|
}
|
|
655
841
|
}
|
|
842
|
+
// v8.3.125+: 补充 SYNONYM_GROUPS 扩展(覆盖更多中英文同义词,如 "登录" ↔ "auth")
|
|
843
|
+
for (const syn of (0, synonyms_1.expandSynonyms)(keywords)) {
|
|
844
|
+
expanded.add(syn);
|
|
845
|
+
}
|
|
656
846
|
return [...expanded];
|
|
657
847
|
}
|
|
658
848
|
function extractKeywords(text) {
|
|
@@ -675,14 +865,126 @@ function extractKeywords(text) {
|
|
|
675
865
|
return expanded.slice(0, 30);
|
|
676
866
|
}
|
|
677
867
|
/**
|
|
678
|
-
*
|
|
868
|
+
* 从 Spec/Markdown 内容中提取技术关键词(类名、接口名、API路径、表名等)
|
|
869
|
+
* v8.3.122+: 用于增强 findRelevantCode 的代码匹配精准度
|
|
870
|
+
*/
|
|
871
|
+
function extractTechnicalKeywords(content) {
|
|
872
|
+
const keywords = [];
|
|
873
|
+
// 1. 类名/接口名/类型别名(代码块内外都找)
|
|
874
|
+
const classMatches = content.match(/(?:class|interface|type|enum)\s+([A-Z][a-zA-Z0-9_]*)/g) || [];
|
|
875
|
+
for (const m of classMatches) {
|
|
876
|
+
const name = m.replace(/^(class|interface|type|enum)\s+/, '').trim();
|
|
877
|
+
if (name && name.length >= 2)
|
|
878
|
+
keywords.push(name);
|
|
879
|
+
}
|
|
880
|
+
// 2. 函数名(从代码块中提取 export function xxx)
|
|
881
|
+
const funcMatches = content.match(/(?:export\s+)?(?:function|const|let|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[=:]/g) || [];
|
|
882
|
+
for (const m of funcMatches) {
|
|
883
|
+
const name = m.replace(/^(?:export\s+)?(?:function|const|let|var)\s+/, '').replace(/\s*[=:]$/, '').trim();
|
|
884
|
+
if (name && name.length >= 2 && !['if', 'for', 'while', 'return', 'await', 'async'].includes(name)) {
|
|
885
|
+
keywords.push(name);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// 3. API 路径(/api/xxx, /v1/xxx)
|
|
889
|
+
const apiMatches = content.match(/['"]\s*(\/[\w/{}.-]+)\s*['"]/g) || [];
|
|
890
|
+
for (const m of apiMatches) {
|
|
891
|
+
const path = m.replace(/['"]/g, '').trim();
|
|
892
|
+
if (path.startsWith('/') && path.length > 2) {
|
|
893
|
+
keywords.push(path);
|
|
894
|
+
// 也提取路径中的段
|
|
895
|
+
const segments = path.split('/').filter(s => s.length >= 2 && !s.match(/^\{.*\}$/));
|
|
896
|
+
keywords.push(...segments);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// 4. 数据库表名(从 schema 描述中提取)
|
|
900
|
+
const tableMatches = content.match(/(?:表名|table|TABLE)[:\s]+[`']?([a-zA-Z_][a-zA-Z0-9_]*)[`']?/g) || [];
|
|
901
|
+
for (const m of tableMatches) {
|
|
902
|
+
const name = m.replace(/(?:表名|table|TABLE)[:\s]+/, '').replace(/[`']/g, '').trim();
|
|
903
|
+
if (name)
|
|
904
|
+
keywords.push(name);
|
|
905
|
+
}
|
|
906
|
+
// 5. 组件名(前端 <XxxComponent />, <Xxx />)
|
|
907
|
+
const componentMatches = content.match(/<([A-Z][a-zA-Z0-9]*(?:Component|Page|View|Card|Modal|Form)?)\s*\/?>/g) || [];
|
|
908
|
+
for (const m of componentMatches) {
|
|
909
|
+
const name = m.replace(/[<\s/>]/g, '');
|
|
910
|
+
if (name && name.length >= 2)
|
|
911
|
+
keywords.push(name);
|
|
912
|
+
}
|
|
913
|
+
// 6. 驼峰命名/下划线命名的复合词(通常是有意义的标识符)
|
|
914
|
+
const identifierMatches = content.match(/\b[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*\b/g) || [];
|
|
915
|
+
for (const m of identifierMatches) {
|
|
916
|
+
if (m.length >= 4)
|
|
917
|
+
keywords.push(m);
|
|
918
|
+
}
|
|
919
|
+
return [...new Set(keywords)].slice(0, 50);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* 检查索引是否过期
|
|
923
|
+
* v8.3.125+: 基于实际文件 mtime 判断 + 24h 强制刷新(捕获新增文件)
|
|
679
924
|
*/
|
|
680
|
-
async function isIndexStale() {
|
|
925
|
+
async function isIndexStale(scope) {
|
|
681
926
|
const index = await loadIndex();
|
|
682
927
|
if (!index)
|
|
683
928
|
return true;
|
|
929
|
+
// 24 小时强制刷新(捕获新增文件)
|
|
684
930
|
const age = Date.now() - new Date(index.updatedAt).getTime();
|
|
685
|
-
|
|
931
|
+
if (age > 86400000)
|
|
932
|
+
return true;
|
|
933
|
+
// 检查已有文件是否被修改或删除(最多检查 300 个,避免大量 stat)
|
|
934
|
+
const checkLimit = Math.min(index.files.length, 300);
|
|
935
|
+
for (let i = 0; i < checkLimit; i++) {
|
|
936
|
+
const file = index.files[i];
|
|
937
|
+
try {
|
|
938
|
+
const fullPath = (0, path_1.join)(process.cwd(), file.path);
|
|
939
|
+
if (!(await (0, fs_extra_1.pathExists)(fullPath)))
|
|
940
|
+
return true;
|
|
941
|
+
const st = await (0, fs_extra_1.stat)(fullPath);
|
|
942
|
+
if (st.mtimeMs > file.lastModified + 1000)
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
catch {
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
// 采样检查是否有新增文件:对比 scope 下文件数
|
|
950
|
+
const dirs = scope ? scope.split(',').map(s => s.trim()) : DEFAULT_SCOPE;
|
|
951
|
+
for (const dir of dirs.slice(0, 3)) {
|
|
952
|
+
if (!(await (0, fs_extra_1.pathExists)(dir)))
|
|
953
|
+
continue;
|
|
954
|
+
try {
|
|
955
|
+
let count = 0;
|
|
956
|
+
await quickCountSourceFiles(dir, 0, (n) => { count = n; });
|
|
957
|
+
const indexedCount = index.files.filter(f => f.path.startsWith(dir.replace(/\/$/, ''))).length;
|
|
958
|
+
if (count > indexedCount + 5)
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
catch { /* 跳过 */ }
|
|
962
|
+
}
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
/** 快速统计目录下源码文件数(不深入解析) */
|
|
966
|
+
async function quickCountSourceFiles(dir, depth, cb) {
|
|
967
|
+
try {
|
|
968
|
+
const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
|
|
969
|
+
let count = 0;
|
|
970
|
+
for (const e of entries) {
|
|
971
|
+
if (e.name.startsWith('.') && e.name !== '.env')
|
|
972
|
+
continue;
|
|
973
|
+
if (['node_modules', 'dist', 'build', 'target', '__pycache__', 'outputs', '.speccore', 'templates', 'docs', 'tests', 'examples'].includes(e.name))
|
|
974
|
+
continue;
|
|
975
|
+
if (e.isDirectory() && depth < 4) {
|
|
976
|
+
await quickCountSourceFiles((0, path_1.join)(dir, e.name), depth + 1, (n) => { count += n; });
|
|
977
|
+
}
|
|
978
|
+
else if (e.isFile()) {
|
|
979
|
+
const ext = e.name.split('.').pop() || '';
|
|
980
|
+
if (['ts', 'tsx', 'js', 'jsx', 'py', 'java', 'go', 'rs', 'vue', 'sql', 'yaml', 'yml'].includes(ext)) {
|
|
981
|
+
count++;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
cb(count);
|
|
986
|
+
}
|
|
987
|
+
catch { /* 跳过 */ }
|
|
686
988
|
}
|
|
687
989
|
/**
|
|
688
990
|
* 加载完整索引(含端、模块、联动)
|
|
@@ -690,6 +992,70 @@ async function isIndexStale() {
|
|
|
690
992
|
async function loadFullIndex() {
|
|
691
993
|
return loadIndex();
|
|
692
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* v8.3.125+: 根据迭代名解析涉及的源码路径 scope
|
|
997
|
+
* 读取 PLATFORMS.md / 020-specs/ 子目录推断端名,匹配 CONSTITUTION.md 中的源码路径
|
|
998
|
+
*/
|
|
999
|
+
async function resolveIterationScope(iteration) {
|
|
1000
|
+
if (!iteration)
|
|
1001
|
+
return undefined;
|
|
1002
|
+
try {
|
|
1003
|
+
const { getIterationDir } = await Promise.resolve().then(() => __importStar(require('../core/context')));
|
|
1004
|
+
const { logger: log } = await Promise.resolve().then(() => __importStar(require('../utils/logger')));
|
|
1005
|
+
const iterDir = await getIterationDir(iteration);
|
|
1006
|
+
if (!(await (0, fs_extra_1.pathExists)(iterDir)))
|
|
1007
|
+
return undefined;
|
|
1008
|
+
// 读取 CONSTITUTION.md 获取源码路径
|
|
1009
|
+
const constitutionPath = (0, path_1.join)(process.cwd(), '.speccore', 'CONSTITUTION.md');
|
|
1010
|
+
let sourcePaths = ['src'];
|
|
1011
|
+
if (await (0, fs_extra_1.pathExists)(constitutionPath)) {
|
|
1012
|
+
const content = await (0, fs_extra_1.readFile)(constitutionPath, 'utf-8');
|
|
1013
|
+
const match = content.match(/源码路径[\s\S]*?\n\s*-\s*`?([^`\n]+)`?/g);
|
|
1014
|
+
if (match) {
|
|
1015
|
+
sourcePaths = match.map(m => m.replace(/.*-\s*`?/, '').replace(/`?$/, '').trim()).filter(Boolean);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (sourcePaths.length <= 1)
|
|
1019
|
+
return undefined;
|
|
1020
|
+
// 1. 尝试从 PLATFORMS.md 读取端列表
|
|
1021
|
+
let platformNames = [];
|
|
1022
|
+
const platformsPath = (0, path_1.join)(iterDir, '020-specs', 'PLATFORMS.md');
|
|
1023
|
+
if (await (0, fs_extra_1.pathExists)(platformsPath)) {
|
|
1024
|
+
const pContent = await (0, fs_extra_1.readFile)(platformsPath, 'utf-8');
|
|
1025
|
+
const pMatch = pContent.match(/-\s*(\w+)/g);
|
|
1026
|
+
if (pMatch)
|
|
1027
|
+
platformNames = pMatch.map(m => m.replace(/^-\s*/, '').trim().toLowerCase());
|
|
1028
|
+
}
|
|
1029
|
+
// 2. fallback:从 020-specs/ 子目录推断端名
|
|
1030
|
+
if (platformNames.length === 0) {
|
|
1031
|
+
const specsDir = (0, path_1.join)(iterDir, '020-specs');
|
|
1032
|
+
if (await (0, fs_extra_1.pathExists)(specsDir)) {
|
|
1033
|
+
const entries = await (0, fs_extra_1.readdir)(specsDir, { withFileTypes: true });
|
|
1034
|
+
platformNames = entries
|
|
1035
|
+
.filter(e => e.isDirectory() && e.name !== 'overview' && e.name !== 'requirements')
|
|
1036
|
+
.map(e => e.name.toLowerCase());
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
// 3. 筛选 sourcePaths
|
|
1040
|
+
if (platformNames.length > 0) {
|
|
1041
|
+
const matched = sourcePaths.filter(sp => {
|
|
1042
|
+
const lower = sp.toLowerCase();
|
|
1043
|
+
return platformNames.some(pn => lower.includes(pn));
|
|
1044
|
+
});
|
|
1045
|
+
// 保留 shared/common/lib 等公共路径
|
|
1046
|
+
const sharedPaths = sourcePaths.filter(sp => /shared|common|lib|utils|pkg/.test(sp.toLowerCase()));
|
|
1047
|
+
const combined = [...new Set([...matched, ...sharedPaths])];
|
|
1048
|
+
if (combined.length > 0) {
|
|
1049
|
+
log?.info?.(` 📂 代码索引按端筛选: ${combined.join(', ')}(涉及端: ${platformNames.join(', ')})`);
|
|
1050
|
+
return combined.join(',');
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
// 筛选失败不阻断
|
|
1056
|
+
}
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
693
1059
|
// ── 端识别 ──
|
|
694
1060
|
async function detectEndpoint(filePath) {
|
|
695
1061
|
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
|