autosnippet 3.0.13 → 3.1.1
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/bin/api-server.js +2 -0
- package/bin/cli.js +24 -19
- package/config/default.json +1 -1
- package/lib/bootstrap.js +4 -4
- package/lib/cli/SetupService.js +29 -29
- package/lib/cli/UpgradeService.js +3 -2
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/ast/ensure-grammars.js +1 -1
- package/lib/core/ast/index.js +62 -11
- package/lib/core/ast/lang-dart.js +27 -21
- package/lib/core/ast/lang-go.js +6 -20
- package/lib/core/ast/lang-rust.js +53 -28
- package/lib/core/ast/parser-init.js +9 -5
- package/lib/core/discovery/DartDiscoverer.js +4 -10
- package/lib/core/discovery/GoDiscoverer.js +45 -25
- package/lib/core/discovery/NodeDiscoverer.js +1 -3
- package/lib/core/discovery/PythonDiscoverer.js +7 -1
- package/lib/core/discovery/RustDiscoverer.js +111 -38
- package/lib/core/discovery/index.js +2 -2
- package/lib/core/enhancement/django-enhancement.js +10 -4
- package/lib/core/enhancement/fastapi-enhancement.js +16 -9
- package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
- package/lib/core/enhancement/go-web-enhancement.js +3 -6
- package/lib/core/enhancement/ml-enhancement.js +6 -3
- package/lib/core/enhancement/nextjs-enhancement.js +17 -7
- package/lib/core/enhancement/node-server-enhancement.js +4 -2
- package/lib/core/enhancement/react-enhancement.js +6 -3
- package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
- package/lib/core/enhancement/rust-web-enhancement.js +13 -7
- package/lib/core/enhancement/vue-enhancement.js +10 -5
- package/lib/external/ai/AiFactory.js +3 -1
- package/lib/external/ai/AiProvider.js +3 -1
- package/lib/external/mcp/McpServer.js +2 -0
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
- package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
- package/lib/external/mcp/handlers/bootstrap.js +4 -10
- package/lib/external/mcp/handlers/browse.js +6 -2
- package/lib/external/mcp/handlers/guard.js +6 -2
- package/lib/external/mcp/handlers/skill.js +6 -2
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/routes/candidates.js +3 -1
- package/lib/http/routes/extract.js +4 -5
- package/lib/http/routes/guardRules.js +1 -1
- package/lib/http/routes/modules.js +9 -3
- package/lib/http/routes/skills.js +54 -6
- package/lib/http/routes/violations.js +4 -3
- package/lib/infrastructure/external/ClipboardManager.js +24 -7
- package/lib/infrastructure/external/NativeUi.js +3 -1
- package/lib/infrastructure/external/OpenBrowser.js +1 -0
- package/lib/infrastructure/external/XcodeAutomation.js +5 -5
- package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
- package/lib/injection/ServiceContainer.js +34 -11
- package/lib/platform/ios/index.js +20 -25
- package/lib/platform/ios/routes/spm.js +6 -3
- package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
- package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
- package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
- package/lib/platform/ios/spm/SpmService.js +3 -1
- package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
- package/lib/service/automation/FileWatcher.js +1 -3
- package/lib/service/automation/handlers/CreateHandler.js +3 -5
- package/lib/service/automation/handlers/GuardHandler.js +11 -32
- package/lib/service/automation/handlers/SearchHandler.js +9 -9
- package/lib/service/chat/CandidateGuardrail.js +11 -6
- package/lib/service/chat/ChatAgent.js +31 -22
- package/lib/service/chat/HandoffProtocol.js +5 -2
- package/lib/service/chat/tools/composite.js +3 -2
- package/lib/service/chat/tools/index.js +60 -71
- package/lib/service/chat/tools/infrastructure.js +9 -4
- package/lib/service/chat/tools/lifecycle.js +22 -5
- package/lib/service/chat/tools/project-access.js +5 -9
- package/lib/service/chat/tools.js +1 -2
- package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
- package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
- package/lib/service/cursor/KnowledgeCompressor.js +16 -7
- package/lib/service/guard/ComplianceReporter.js +5 -2
- package/lib/service/guard/GuardCheckEngine.js +53 -26
- package/lib/service/guard/GuardCodeChecks.js +217 -188
- package/lib/service/guard/GuardCrossFileChecks.js +203 -184
- package/lib/service/guard/GuardPatternUtils.js +17 -10
- package/lib/service/module/ModuleService.js +180 -56
- package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
- package/lib/service/snippet/SnippetFactory.js +3 -3
- package/lib/service/snippet/SnippetInstaller.js +35 -11
- package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
- package/lib/service/wiki/WikiGenerator.js +67 -40
- package/lib/service/wiki/WikiRenderers.js +105 -80
- package/lib/service/wiki/WikiUtils.js +217 -80
- package/lib/shared/LanguageService.js +111 -53
- package/lib/shared/PathGuard.js +0 -8
- package/package.json +3 -9
- package/scripts/bench-real-projects.mjs +29 -29
- package/scripts/generate-recipe-drafts.js +17 -27
- package/scripts/init-snippets.js +43 -24
- package/scripts/install-vscode-copilot.js +3 -19
- package/scripts/setup-mcp-config.js +0 -4
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { LanguageService } from '../../shared/LanguageService.js';
|
|
12
11
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
12
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
13
13
|
|
|
14
14
|
const logger = Logger.getInstance();
|
|
15
15
|
|
|
@@ -131,12 +131,10 @@ export function inferModuleFromPath(filePath) {
|
|
|
131
131
|
// Java/Kotlin: src/main/java/{pkg}/... → 跳过域名前缀,取最后一个有意义的包目录
|
|
132
132
|
// 例: src/main/java/org/springframework/samples/petclinic/vet/Vet.java → "vet"
|
|
133
133
|
// 例: src/main/java/com/example/demo/DemoApp.java → "demo"
|
|
134
|
-
const JAVA_DOMAIN_PREFIXES = new Set(['com', 'org', 'net', 'io', 'de', 'fr', 'jp', 'cn', 'uk', 'us', 'edu', 'gov']);
|
|
135
134
|
for (const langDir of ['java', 'kotlin']) {
|
|
136
135
|
const langIdx = parts.indexOf(langDir);
|
|
137
136
|
if (langIdx >= 0 && langIdx + 1 < parts.length) {
|
|
138
137
|
// 文件名所在目录(倒数第二个 part)才是 "模块"
|
|
139
|
-
const fileName = parts[parts.length - 1];
|
|
140
138
|
const pkgParts = parts.slice(langIdx + 1, parts.length - 1); // 包路径(不含文件名)
|
|
141
139
|
if (pkgParts.length >= 2) {
|
|
142
140
|
// 从尾部取: 最后一个包段即为功能模块
|
|
@@ -294,8 +292,7 @@ export function getInheritanceRoots(codeEntityGraph) {
|
|
|
294
292
|
}
|
|
295
293
|
try {
|
|
296
294
|
// 尝试查询继承关系
|
|
297
|
-
const entities =
|
|
298
|
-
codeEntityGraph.queryEntities?.({ entityType: 'class', limit: 50 }) || [];
|
|
295
|
+
const entities = codeEntityGraph.queryEntities?.({ entityType: 'class', limit: 50 }) || [];
|
|
299
296
|
const roots = [];
|
|
300
297
|
for (const e of entities) {
|
|
301
298
|
const _parents =
|
|
@@ -341,9 +338,7 @@ export function dedup(files, wikiDir, emit) {
|
|
|
341
338
|
/* skip */
|
|
342
339
|
}
|
|
343
340
|
removed.push(file.path);
|
|
344
|
-
logger.info(
|
|
345
|
-
`[WikiGenerator] Dedup: removed ${file.path} (same hash as ${existing.path})`
|
|
346
|
-
);
|
|
341
|
+
logger.info(`[WikiGenerator] Dedup: removed ${file.path} (same hash as ${existing.path})`);
|
|
347
342
|
}
|
|
348
343
|
// hash 不同 → 保留两个(不同目录允许同名)
|
|
349
344
|
} else {
|
|
@@ -361,8 +356,7 @@ export function dedup(files, wikiDir, emit) {
|
|
|
361
356
|
const firstPath = hashMap.get(file.hash);
|
|
362
357
|
// 优先保留代码生成的(非 synced)
|
|
363
358
|
const isFirstSynced = firstPath.startsWith('documents/') || firstPath.startsWith('skills/');
|
|
364
|
-
const isCurrentSynced =
|
|
365
|
-
file.path.startsWith('documents/') || file.path.startsWith('skills/');
|
|
359
|
+
const isCurrentSynced = file.path.startsWith('documents/') || file.path.startsWith('skills/');
|
|
366
360
|
|
|
367
361
|
if (isCurrentSynced && !isFirstSynced) {
|
|
368
362
|
// 当前是 synced,first 是 codegen → 删除 synced
|
|
@@ -411,19 +405,69 @@ export function dedup(files, wikiDir, emit) {
|
|
|
411
405
|
*/
|
|
412
406
|
export function getLangTerms(langId) {
|
|
413
407
|
const TERMS = {
|
|
414
|
-
swift:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
408
|
+
swift: {
|
|
409
|
+
typeLabel: { zh: '类/结构体', en: 'Classes/Structs' },
|
|
410
|
+
interfaceLabel: { zh: '协议', en: 'Protocols' },
|
|
411
|
+
moduleMetric: { zh: 'SPM Targets', en: 'SPM Targets' },
|
|
412
|
+
},
|
|
413
|
+
objectivec: {
|
|
414
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
415
|
+
interfaceLabel: { zh: '协议', en: 'Protocols' },
|
|
416
|
+
moduleMetric: { zh: 'Targets', en: 'Targets' },
|
|
417
|
+
},
|
|
418
|
+
typescript: {
|
|
419
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
420
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
421
|
+
moduleMetric: { zh: 'Packages', en: 'Packages' },
|
|
422
|
+
},
|
|
423
|
+
javascript: {
|
|
424
|
+
typeLabel: { zh: '类/模块', en: 'Classes/Modules' },
|
|
425
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
426
|
+
moduleMetric: { zh: 'Packages', en: 'Packages' },
|
|
427
|
+
},
|
|
428
|
+
python: {
|
|
429
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
430
|
+
interfaceLabel: { zh: '抽象基类', en: 'Abstract Base' },
|
|
431
|
+
moduleMetric: { zh: 'Packages', en: 'Packages' },
|
|
432
|
+
},
|
|
433
|
+
go: {
|
|
434
|
+
typeLabel: { zh: '结构体', en: 'Structs' },
|
|
435
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
436
|
+
moduleMetric: { zh: 'Go Modules', en: 'Go Modules' },
|
|
437
|
+
},
|
|
438
|
+
rust: {
|
|
439
|
+
typeLabel: { zh: '结构体/枚举', en: 'Structs/Enums' },
|
|
440
|
+
interfaceLabel: { zh: 'Trait', en: 'Traits' },
|
|
441
|
+
moduleMetric: { zh: 'Crates', en: 'Crates' },
|
|
442
|
+
},
|
|
443
|
+
java: {
|
|
444
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
445
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
446
|
+
moduleMetric: { zh: 'Modules', en: 'Modules' },
|
|
447
|
+
},
|
|
448
|
+
kotlin: {
|
|
449
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
450
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
451
|
+
moduleMetric: { zh: 'Modules', en: 'Modules' },
|
|
452
|
+
},
|
|
453
|
+
dart: {
|
|
454
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
455
|
+
interfaceLabel: { zh: '抽象类', en: 'Abstract Classes' },
|
|
456
|
+
moduleMetric: { zh: 'Packages', en: 'Packages' },
|
|
457
|
+
},
|
|
458
|
+
csharp: {
|
|
459
|
+
typeLabel: { zh: '类', en: 'Classes' },
|
|
460
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
461
|
+
moduleMetric: { zh: 'Projects', en: 'Projects' },
|
|
462
|
+
},
|
|
425
463
|
};
|
|
426
|
-
return
|
|
464
|
+
return (
|
|
465
|
+
TERMS[langId] || {
|
|
466
|
+
typeLabel: { zh: '类型', en: 'Types' },
|
|
467
|
+
interfaceLabel: { zh: '接口', en: 'Interfaces' },
|
|
468
|
+
moduleMetric: { zh: 'Modules', en: 'Modules' },
|
|
469
|
+
}
|
|
470
|
+
);
|
|
427
471
|
}
|
|
428
472
|
|
|
429
473
|
/**
|
|
@@ -456,11 +500,13 @@ export function detectBuildSystems(rootEntryNames, projectRoot) {
|
|
|
456
500
|
try {
|
|
457
501
|
const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
458
502
|
for (const dir of entries) {
|
|
459
|
-
if (!dir.isDirectory() || dir.name.startsWith('.') || skipDirs.has(dir.name))
|
|
503
|
+
if (!dir.isDirectory() || dir.name.startsWith('.') || skipDirs.has(dir.name)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
460
506
|
try {
|
|
461
|
-
const subEntries = fs
|
|
462
|
-
(
|
|
463
|
-
|
|
507
|
+
const subEntries = fs
|
|
508
|
+
.readdirSync(path.join(projectRoot, dir.name))
|
|
509
|
+
.filter((n) => !n.startsWith('.'));
|
|
464
510
|
const subResults = LanguageService.matchBuildMarkers(subEntries);
|
|
465
511
|
for (const r of subResults) {
|
|
466
512
|
if (!seenEco.has(r.eco)) {
|
|
@@ -468,9 +514,13 @@ export function detectBuildSystems(rootEntryNames, projectRoot) {
|
|
|
468
514
|
seenEco.add(r.eco);
|
|
469
515
|
}
|
|
470
516
|
}
|
|
471
|
-
} catch {
|
|
517
|
+
} catch {
|
|
518
|
+
/* skip unreadable subdirs */
|
|
519
|
+
}
|
|
472
520
|
}
|
|
473
|
-
} catch {
|
|
521
|
+
} catch {
|
|
522
|
+
/* skip */
|
|
523
|
+
}
|
|
474
524
|
}
|
|
475
525
|
|
|
476
526
|
return results;
|
|
@@ -498,12 +548,29 @@ export function detectBuildSystems(rootEntryNames, projectRoot) {
|
|
|
498
548
|
|
|
499
549
|
/** 入口文件名模式 */
|
|
500
550
|
const ENTRY_POINT_NAMES = new Set([
|
|
501
|
-
'index.js',
|
|
502
|
-
'
|
|
503
|
-
'
|
|
551
|
+
'index.js',
|
|
552
|
+
'index.ts',
|
|
553
|
+
'index.tsx',
|
|
554
|
+
'index.jsx',
|
|
555
|
+
'index.mjs',
|
|
556
|
+
'main.js',
|
|
557
|
+
'main.ts',
|
|
558
|
+
'main.go',
|
|
559
|
+
'main.py',
|
|
560
|
+
'main.rs',
|
|
561
|
+
'main.dart',
|
|
562
|
+
'main.c',
|
|
563
|
+
'main.cpp',
|
|
564
|
+
'mod.rs',
|
|
565
|
+
'lib.rs',
|
|
504
566
|
'__init__.py',
|
|
505
|
-
'app.js',
|
|
506
|
-
'
|
|
567
|
+
'app.js',
|
|
568
|
+
'app.ts',
|
|
569
|
+
'app.py',
|
|
570
|
+
'app.rb',
|
|
571
|
+
'server.js',
|
|
572
|
+
'server.ts',
|
|
573
|
+
'server.py',
|
|
507
574
|
]);
|
|
508
575
|
|
|
509
576
|
/** 多语言 import/require 正则 (轻量级, 不依赖 AST) */
|
|
@@ -540,11 +607,7 @@ const IMPORT_PATTERNS = [
|
|
|
540
607
|
* @returns {FolderProfile[]}
|
|
541
608
|
*/
|
|
542
609
|
export function profileFolders(projectInfo, options = {}) {
|
|
543
|
-
const {
|
|
544
|
-
minFiles = 3,
|
|
545
|
-
maxFolders = 20,
|
|
546
|
-
sampleLines = 40,
|
|
547
|
-
} = options;
|
|
610
|
+
const { minFiles = 3, maxFolders = 20, sampleLines = 40 } = options;
|
|
548
611
|
|
|
549
612
|
const root = projectInfo.root;
|
|
550
613
|
const sourceFiles = projectInfo.sourceFiles || [];
|
|
@@ -578,12 +641,18 @@ export function profileFolders(projectInfo, options = {}) {
|
|
|
578
641
|
// ── 3. 筛选重要文件夹 ──
|
|
579
642
|
const candidates = [];
|
|
580
643
|
for (const [dir, files] of folderRecursive) {
|
|
581
|
-
if (files.length < minFiles)
|
|
644
|
+
if (files.length < minFiles) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
582
647
|
// 排除根目录 '.'
|
|
583
|
-
if (dir === '.')
|
|
648
|
+
if (dir === '.') {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
584
651
|
// 排除太深的目录 (depth > 4), 这些通常是叶子目录, 信息量低
|
|
585
652
|
const depth = dir.split('/').length;
|
|
586
|
-
if (depth > 4)
|
|
653
|
+
if (depth > 4) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
587
656
|
|
|
588
657
|
candidates.push({ dir, files, depth });
|
|
589
658
|
}
|
|
@@ -607,7 +676,9 @@ export function profileFolders(projectInfo, options = {}) {
|
|
|
607
676
|
|
|
608
677
|
// 按 fileCount 降序 + depth 升序 排序
|
|
609
678
|
profiles.sort((a, b) => {
|
|
610
|
-
if (b.fileCount !== a.fileCount)
|
|
679
|
+
if (b.fileCount !== a.fileCount) {
|
|
680
|
+
return b.fileCount - a.fileCount;
|
|
681
|
+
}
|
|
611
682
|
return a.depth - b.depth;
|
|
612
683
|
});
|
|
613
684
|
|
|
@@ -623,18 +694,20 @@ function _pruneRedundantFolders(candidates, maxFolders) {
|
|
|
623
694
|
const removedDirs = new Set();
|
|
624
695
|
|
|
625
696
|
for (const c of candidates) {
|
|
626
|
-
if (removedDirs.has(c.dir))
|
|
697
|
+
if (removedDirs.has(c.dir)) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
627
700
|
|
|
628
701
|
// 检查是否有已 kept 的父目录, 且文件比率 > 80%
|
|
629
702
|
let isRedundant = false;
|
|
630
703
|
for (const k of kept) {
|
|
631
|
-
if (c.dir.startsWith(k.dir
|
|
704
|
+
if (c.dir.startsWith(`${k.dir}/`)) {
|
|
632
705
|
// c 是 k 的子目录
|
|
633
706
|
if (c.files.length / k.files.length > 0.8) {
|
|
634
707
|
isRedundant = true;
|
|
635
708
|
break;
|
|
636
709
|
}
|
|
637
|
-
} else if (k.dir.startsWith(c.dir
|
|
710
|
+
} else if (k.dir.startsWith(`${c.dir}/`)) {
|
|
638
711
|
// c 是 k 的父目录, k 覆盖了 c 大部分 → 保留 c (更高层), 移除 k
|
|
639
712
|
if (k.files.length / c.files.length > 0.8) {
|
|
640
713
|
removedDirs.add(k.dir);
|
|
@@ -646,10 +719,12 @@ function _pruneRedundantFolders(candidates, maxFolders) {
|
|
|
646
719
|
kept.push(c);
|
|
647
720
|
}
|
|
648
721
|
|
|
649
|
-
if (kept.length >= maxFolders)
|
|
722
|
+
if (kept.length >= maxFolders) {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
650
725
|
}
|
|
651
726
|
|
|
652
|
-
return kept.filter(c => !removedDirs.has(c.dir));
|
|
727
|
+
return kept.filter((c) => !removedDirs.has(c.dir));
|
|
653
728
|
}
|
|
654
729
|
|
|
655
730
|
/**
|
|
@@ -670,14 +745,16 @@ function _buildFolderProfile(relDir, files, depth, projectRoot, sampleLines) {
|
|
|
670
745
|
try {
|
|
671
746
|
const stat = fs.statSync(path.join(projectRoot, f));
|
|
672
747
|
totalSize += stat.size;
|
|
673
|
-
} catch {
|
|
748
|
+
} catch {
|
|
749
|
+
/* skip */
|
|
750
|
+
}
|
|
674
751
|
}
|
|
675
752
|
|
|
676
753
|
// ── 文件名列表 ──
|
|
677
|
-
const fileNames = files.map(f => path.basename(f)).sort();
|
|
754
|
+
const fileNames = files.map((f) => path.basename(f)).sort();
|
|
678
755
|
|
|
679
756
|
// ── 入口点检测 ──
|
|
680
|
-
const entryPoints = files.filter(f => ENTRY_POINT_NAMES.has(path.basename(f).toLowerCase()));
|
|
757
|
+
const entryPoints = files.filter((f) => ENTRY_POINT_NAMES.has(path.basename(f).toLowerCase()));
|
|
681
758
|
|
|
682
759
|
// ── 重要文件 (大文件 top5 + 入口文件) ──
|
|
683
760
|
const fileSizes = [];
|
|
@@ -685,15 +762,12 @@ function _buildFolderProfile(relDir, files, depth, projectRoot, sampleLines) {
|
|
|
685
762
|
try {
|
|
686
763
|
const stat = fs.statSync(path.join(projectRoot, f));
|
|
687
764
|
fileSizes.push({ file: f, size: stat.size });
|
|
688
|
-
} catch {
|
|
765
|
+
} catch {
|
|
766
|
+
/* skip */
|
|
767
|
+
}
|
|
689
768
|
}
|
|
690
769
|
fileSizes.sort((a, b) => b.size - a.size);
|
|
691
|
-
const keyFiles = [
|
|
692
|
-
...new Set([
|
|
693
|
-
...entryPoints,
|
|
694
|
-
...fileSizes.slice(0, 5).map(fs => fs.file),
|
|
695
|
-
]),
|
|
696
|
-
];
|
|
770
|
+
const keyFiles = [...new Set([...entryPoints, ...fileSizes.slice(0, 5).map((fs) => fs.file)])];
|
|
697
771
|
|
|
698
772
|
// ── README 检测 ──
|
|
699
773
|
let readme = null;
|
|
@@ -706,7 +780,9 @@ function _buildFolderProfile(relDir, files, depth, projectRoot, sampleLines) {
|
|
|
706
780
|
readme = content.slice(0, 1000); // 只取前 1000 字符
|
|
707
781
|
break;
|
|
708
782
|
}
|
|
709
|
-
} catch {
|
|
783
|
+
} catch {
|
|
784
|
+
/* skip */
|
|
785
|
+
}
|
|
710
786
|
}
|
|
711
787
|
|
|
712
788
|
// ── 命名模式检测 ──
|
|
@@ -739,7 +815,7 @@ function _buildFolderProfile(relDir, files, depth, projectRoot, sampleLines) {
|
|
|
739
815
|
readme,
|
|
740
816
|
purpose: purpose ? purpose : null,
|
|
741
817
|
imports,
|
|
742
|
-
entryPoints: [...new Set(entryPoints.map(f => path.basename(f)))],
|
|
818
|
+
entryPoints: [...new Set(entryPoints.map((f) => path.basename(f)))],
|
|
743
819
|
namingPatterns,
|
|
744
820
|
headerComments,
|
|
745
821
|
};
|
|
@@ -753,14 +829,20 @@ function _buildFolderProfile(relDir, files, depth, projectRoot, sampleLines) {
|
|
|
753
829
|
*/
|
|
754
830
|
function _detectNamingPatterns(fileNames) {
|
|
755
831
|
const patterns = [];
|
|
756
|
-
const lower = fileNames.map(n => n.toLowerCase());
|
|
832
|
+
const lower = fileNames.map((n) => n.toLowerCase());
|
|
757
833
|
|
|
758
834
|
// 测试文件
|
|
759
|
-
const testFiles = lower.filter(
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
835
|
+
const testFiles = lower.filter(
|
|
836
|
+
(n) =>
|
|
837
|
+
n.startsWith('test_') ||
|
|
838
|
+
n.startsWith('test.') ||
|
|
839
|
+
n.endsWith('_test.go') ||
|
|
840
|
+
n.endsWith('.test.js') ||
|
|
841
|
+
n.endsWith('.test.ts') ||
|
|
842
|
+
n.endsWith('.spec.js') ||
|
|
843
|
+
n.endsWith('.spec.ts') ||
|
|
844
|
+
n.endsWith('_spec.rb') ||
|
|
845
|
+
(n.startsWith('test') && n.includes('.'))
|
|
764
846
|
);
|
|
765
847
|
if (testFiles.length > 0) {
|
|
766
848
|
patterns.push(`test files: ${testFiles.length}`);
|
|
@@ -803,16 +885,54 @@ function _extractImports(keyFiles, projectRoot, sampleLines, currentDir) {
|
|
|
803
885
|
|
|
804
886
|
// Node.js / 常见运行时内置模块 — 不应计入项目文件夹依赖
|
|
805
887
|
const BUILTIN_MODULES = new Set([
|
|
806
|
-
'fs',
|
|
807
|
-
'
|
|
808
|
-
'
|
|
809
|
-
'
|
|
810
|
-
'
|
|
888
|
+
'fs',
|
|
889
|
+
'path',
|
|
890
|
+
'os',
|
|
891
|
+
'http',
|
|
892
|
+
'https',
|
|
893
|
+
'url',
|
|
894
|
+
'util',
|
|
895
|
+
'crypto',
|
|
896
|
+
'stream',
|
|
897
|
+
'events',
|
|
898
|
+
'child_process',
|
|
899
|
+
'cluster',
|
|
900
|
+
'net',
|
|
901
|
+
'dns',
|
|
902
|
+
'tls',
|
|
903
|
+
'zlib',
|
|
904
|
+
'readline',
|
|
905
|
+
'assert',
|
|
906
|
+
'buffer',
|
|
907
|
+
'querystring',
|
|
908
|
+
'string_decoder',
|
|
909
|
+
'timers',
|
|
910
|
+
'tty',
|
|
911
|
+
'dgram',
|
|
912
|
+
'vm',
|
|
913
|
+
'worker_threads',
|
|
914
|
+
'perf_hooks',
|
|
915
|
+
'async_hooks',
|
|
916
|
+
'v8',
|
|
917
|
+
'inspector',
|
|
918
|
+
'console',
|
|
919
|
+
'process',
|
|
920
|
+
'module',
|
|
811
921
|
// node: prefix 会被 firstSeg 拆出 "node" — 直接排除
|
|
812
922
|
'node',
|
|
813
923
|
// 常见第三方包 (非项目目录)
|
|
814
|
-
'react',
|
|
815
|
-
'
|
|
924
|
+
'react',
|
|
925
|
+
'vue',
|
|
926
|
+
'express',
|
|
927
|
+
'lodash',
|
|
928
|
+
'axios',
|
|
929
|
+
'moment',
|
|
930
|
+
'dayjs',
|
|
931
|
+
'webpack',
|
|
932
|
+
'vite',
|
|
933
|
+
'jest',
|
|
934
|
+
'mocha',
|
|
935
|
+
'chai',
|
|
816
936
|
]);
|
|
817
937
|
|
|
818
938
|
for (const relFile of keyFiles) {
|
|
@@ -829,13 +949,20 @@ function _extractImports(keyFiles, projectRoot, sampleLines, currentDir) {
|
|
|
829
949
|
const target = match[1] || match[2];
|
|
830
950
|
if (target) {
|
|
831
951
|
// 跳过 node: 协议前缀 (Node.js 内置模块)
|
|
832
|
-
if (target.startsWith('node:'))
|
|
952
|
+
if (target.startsWith('node:')) {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
833
955
|
|
|
834
956
|
// 解析相对路径 import → 文件夹名
|
|
835
957
|
if (target.startsWith('.') || target.startsWith('/')) {
|
|
836
958
|
const resolved = path.normalize(path.join(currentDir, target));
|
|
837
959
|
const topDir = resolved.split('/')[0];
|
|
838
|
-
if (
|
|
960
|
+
if (
|
|
961
|
+
topDir &&
|
|
962
|
+
topDir !== '.' &&
|
|
963
|
+
topDir !== '..' &&
|
|
964
|
+
topDir !== currentDir.split('/')[0]
|
|
965
|
+
) {
|
|
839
966
|
importTargets.add(topDir);
|
|
840
967
|
}
|
|
841
968
|
} else {
|
|
@@ -849,7 +976,9 @@ function _extractImports(keyFiles, projectRoot, sampleLines, currentDir) {
|
|
|
849
976
|
}
|
|
850
977
|
}
|
|
851
978
|
}
|
|
852
|
-
} catch {
|
|
979
|
+
} catch {
|
|
980
|
+
/* skip unreadable files */
|
|
981
|
+
}
|
|
853
982
|
}
|
|
854
983
|
|
|
855
984
|
return [...importTargets].slice(0, 20);
|
|
@@ -870,18 +999,24 @@ function _extractHeaderComment(fullPath) {
|
|
|
870
999
|
if (blockMatch) {
|
|
871
1000
|
const comment = blockMatch[1]
|
|
872
1001
|
.split('\n')
|
|
873
|
-
.map(l => l.replace(/^\s*\*\s?/, '').trim())
|
|
874
|
-
.filter(l => l && !l.startsWith('@'))
|
|
1002
|
+
.map((l) => l.replace(/^\s*\*\s?/, '').trim())
|
|
1003
|
+
.filter((l) => l && !l.startsWith('@'))
|
|
875
1004
|
.join(' ')
|
|
876
1005
|
.slice(0, 200);
|
|
877
|
-
if (comment.length > 10)
|
|
1006
|
+
if (comment.length > 10) {
|
|
1007
|
+
return comment;
|
|
1008
|
+
}
|
|
878
1009
|
}
|
|
879
1010
|
|
|
880
1011
|
// 尝试匹配 # 或 // 开头的连续行注释
|
|
881
1012
|
const lineComments = [];
|
|
882
1013
|
for (const line of lines) {
|
|
883
1014
|
const stripped = line.trim();
|
|
884
|
-
if (
|
|
1015
|
+
if (
|
|
1016
|
+
stripped.startsWith('#') &&
|
|
1017
|
+
!stripped.startsWith('#!') &&
|
|
1018
|
+
!stripped.startsWith('#include')
|
|
1019
|
+
) {
|
|
885
1020
|
lineComments.push(stripped.replace(/^#+\s*/, ''));
|
|
886
1021
|
} else if (stripped.startsWith('//')) {
|
|
887
1022
|
lineComments.push(stripped.replace(/^\/\/\s*/, ''));
|
|
@@ -897,7 +1032,9 @@ function _extractHeaderComment(fullPath) {
|
|
|
897
1032
|
}
|
|
898
1033
|
if (lineComments.length > 0) {
|
|
899
1034
|
const comment = lineComments.join(' ').slice(0, 200);
|
|
900
|
-
if (comment.length > 10)
|
|
1035
|
+
if (comment.length > 10) {
|
|
1036
|
+
return comment;
|
|
1037
|
+
}
|
|
901
1038
|
}
|
|
902
1039
|
|
|
903
1040
|
return null;
|