autosnippet 3.0.13 → 3.1.0
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 +54 -36
- 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
package/bin/api-server.js
CHANGED
|
@@ -44,6 +44,8 @@ async function main() {
|
|
|
44
44
|
auditLogger: components.auditLogger,
|
|
45
45
|
gateway: components.gateway,
|
|
46
46
|
constitution: components.constitution,
|
|
47
|
+
config: components.config,
|
|
48
|
+
skillHooks: components.skillHooks,
|
|
47
49
|
projectRoot,
|
|
48
50
|
});
|
|
49
51
|
logger.info('Service container initialized successfully');
|
package/bin/cli.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* asd mirror - 镜像 .cursor/ → .qoder/ .trae/
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { existsSync,
|
|
20
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
21
21
|
import { dirname, join, resolve } from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { Command } from 'commander';
|
|
@@ -648,25 +648,29 @@ program
|
|
|
648
648
|
// ── Level 1: 项目配置文件(确定性高)──
|
|
649
649
|
const hasAppleConfig = entries.some(
|
|
650
650
|
(e) =>
|
|
651
|
-
e.name === 'Package.swift' ||
|
|
652
|
-
e.name === 'Podfile' ||
|
|
653
|
-
e.name === 'Cartfile' ||
|
|
654
|
-
e.name === 'project.yml' ||
|
|
655
|
-
e.name.endsWith('.xcodeproj') ||
|
|
656
|
-
e.name.endsWith('.xcworkspace')
|
|
651
|
+
e.name === 'Package.swift' || // SPM
|
|
652
|
+
e.name === 'Podfile' || // CocoaPods
|
|
653
|
+
e.name === 'Cartfile' || // Carthage
|
|
654
|
+
e.name === 'project.yml' || // XcodeGen
|
|
655
|
+
e.name.endsWith('.xcodeproj') || // Xcode project
|
|
656
|
+
e.name.endsWith('.xcworkspace') // Xcode workspace
|
|
657
657
|
);
|
|
658
|
-
if (hasAppleConfig)
|
|
658
|
+
if (hasAppleConfig) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
659
661
|
|
|
660
662
|
// ── Level 2: 目录结构特征 ──
|
|
661
663
|
const hasAppleDir = entries.some(
|
|
662
664
|
(e) =>
|
|
663
665
|
e.isDirectory() &&
|
|
664
|
-
(e.name === 'Tuist' ||
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
(e.name === 'Tuist' || // Tuist 项目
|
|
667
|
+
e.name === 'Pods' || // CocoaPods 产物
|
|
668
|
+
e.name === 'Carthage' || // Carthage 产物
|
|
669
|
+
e.name === 'DerivedData') // Xcode 构建产物
|
|
668
670
|
);
|
|
669
|
-
if (hasAppleDir)
|
|
671
|
+
if (hasAppleDir) {
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
670
674
|
|
|
671
675
|
// ── Level 3: 向下扫一层(处理 monorepo 或 Sources/ 下有 .swift 的情况)──
|
|
672
676
|
const APPLE_EXTS = new Set(['.swift', '.m', '.mm', '.h']);
|
|
@@ -683,7 +687,9 @@ program
|
|
|
683
687
|
if (subEntries.some((f) => APPLE_EXTS.has(f.slice(f.lastIndexOf('.'))))) {
|
|
684
688
|
return true;
|
|
685
689
|
}
|
|
686
|
-
} catch {
|
|
690
|
+
} catch {
|
|
691
|
+
/* 读取失败忽略 */
|
|
692
|
+
}
|
|
687
693
|
}
|
|
688
694
|
}
|
|
689
695
|
|
|
@@ -710,7 +716,8 @@ program
|
|
|
710
716
|
? `http://127.0.0.1:${port}`
|
|
711
717
|
: `http://localhost:5173`;
|
|
712
718
|
} else {
|
|
713
|
-
process.env.ASD_DASHBOARD_URL =
|
|
719
|
+
process.env.ASD_DASHBOARD_URL =
|
|
720
|
+
process.env.ASD_DASHBOARD_URL || `http://${host}:${port}`;
|
|
714
721
|
}
|
|
715
722
|
|
|
716
723
|
const { FileWatcher } = await import('../lib/service/automation/FileWatcher.js');
|
|
@@ -725,7 +732,6 @@ program
|
|
|
725
732
|
}
|
|
726
733
|
}
|
|
727
734
|
} else if (process.env.ASD_DEBUG === '1') {
|
|
728
|
-
console.log('ℹ️ Non-Apple project — file watcher skipped (use VSCode extension instead)');
|
|
729
735
|
}
|
|
730
736
|
} catch (err) {
|
|
731
737
|
console.error(`❌ API server failed to start: ${err.message}`);
|
|
@@ -885,7 +891,6 @@ program
|
|
|
885
891
|
const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
|
|
886
892
|
copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
|
|
887
893
|
}
|
|
888
|
-
console.log(` ✅ ${target}/rules/ — ${files.length} rules mirrored`);
|
|
889
894
|
}
|
|
890
895
|
|
|
891
896
|
const cursorSkillsDir = join(cursorDir, 'skills');
|
|
@@ -897,10 +902,8 @@ program
|
|
|
897
902
|
for (const dir of skillDirs) {
|
|
898
903
|
_copyDirRecursive(join(cursorSkillsDir, dir.name), join(targetSkillsDir, dir.name));
|
|
899
904
|
}
|
|
900
|
-
console.log(` ✅ ${target}/skills/ — ${skillDirs.length} skills mirrored`);
|
|
901
905
|
}
|
|
902
906
|
}
|
|
903
|
-
console.log('\n 💡 完成!如有新增交付物料,可随时重新运行 asd mirror');
|
|
904
907
|
});
|
|
905
908
|
|
|
906
909
|
/** @private 递归复制目录(mirror 命令用) */
|
|
@@ -1004,6 +1007,8 @@ async function initContainer(opts = {}) {
|
|
|
1004
1007
|
auditLogger: bootstrap.components.auditLogger,
|
|
1005
1008
|
gateway: bootstrap.components.gateway,
|
|
1006
1009
|
constitution: bootstrap.components.constitution,
|
|
1010
|
+
config: bootstrap.components.config,
|
|
1011
|
+
skillHooks: bootstrap.components.skillHooks,
|
|
1007
1012
|
projectRoot,
|
|
1008
1013
|
});
|
|
1009
1014
|
return { bootstrap, container };
|
package/config/default.json
CHANGED
package/lib/bootstrap.js
CHANGED
|
@@ -57,7 +57,7 @@ export class Bootstrap {
|
|
|
57
57
|
// 2. 初始化日志系统
|
|
58
58
|
await this.initializeLogger();
|
|
59
59
|
|
|
60
|
-
this.components.logger.info('AutoSnippet
|
|
60
|
+
this.components.logger.info('AutoSnippet - Starting initialization...');
|
|
61
61
|
|
|
62
62
|
// 3. 连接数据库
|
|
63
63
|
await this.initializeDatabase();
|
|
@@ -75,7 +75,7 @@ export class Bootstrap {
|
|
|
75
75
|
// await this.registerRoutes();
|
|
76
76
|
|
|
77
77
|
const duration = Date.now() - startTime;
|
|
78
|
-
this.components.logger.info(`AutoSnippet
|
|
78
|
+
this.components.logger.info(`AutoSnippet initialized successfully (${duration}ms)`);
|
|
79
79
|
|
|
80
80
|
return this.components;
|
|
81
81
|
} catch (error) {
|
|
@@ -200,14 +200,14 @@ export class Bootstrap {
|
|
|
200
200
|
* 关闭应用程序
|
|
201
201
|
*/
|
|
202
202
|
async shutdown() {
|
|
203
|
-
this.components.logger?.info('AutoSnippet
|
|
203
|
+
this.components.logger?.info('AutoSnippet - Shutting down...');
|
|
204
204
|
|
|
205
205
|
// 关闭数据库连接
|
|
206
206
|
if (this.components.db) {
|
|
207
207
|
await this.components.db.close();
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
this.components.logger?.info('AutoSnippet
|
|
210
|
+
this.components.logger?.info('AutoSnippet - Shutdown complete');
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
/**
|
package/lib/cli/SetupService.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
} from 'node:fs';
|
|
54
54
|
import { dirname, join, resolve } from 'node:path';
|
|
55
55
|
import { fileURLToPath } from 'node:url';
|
|
56
|
-
import { checkWriteSafety
|
|
56
|
+
import { checkWriteSafety } from '../service/cursor/FileProtection.js';
|
|
57
57
|
|
|
58
58
|
const __filename = fileURLToPath(import.meta.url);
|
|
59
59
|
const __dirname = dirname(__filename);
|
|
@@ -103,21 +103,15 @@ export class SetupService {
|
|
|
103
103
|
const results = [];
|
|
104
104
|
const total = steps.length;
|
|
105
105
|
|
|
106
|
-
console.log('');
|
|
107
|
-
console.log(` ⚙ AutoSnippet Setup — ${this.projectName}`);
|
|
108
|
-
console.log(` ${'─'.repeat(44)}`);
|
|
109
|
-
|
|
110
106
|
for (let i = 0; i < total; i++) {
|
|
111
107
|
const { label, fn } = steps[i];
|
|
112
108
|
const tag = `[${i + 1}/${total}]`;
|
|
113
109
|
process.stdout.write(` ${tag} ${label}...`);
|
|
114
110
|
try {
|
|
115
111
|
const r = await fn();
|
|
116
|
-
const
|
|
117
|
-
console.log(` ✅${detail}`);
|
|
112
|
+
const _detail = this._formatStepDetail(r);
|
|
118
113
|
results.push({ step: i + 1, label, ok: true, ...(r || {}) });
|
|
119
114
|
} catch (err) {
|
|
120
|
-
console.log(` ❌`);
|
|
121
115
|
console.error(` ${err.message}`);
|
|
122
116
|
results.push({ step: i + 1, label, ok: false, error: err.message });
|
|
123
117
|
}
|
|
@@ -129,7 +123,9 @@ export class SetupService {
|
|
|
129
123
|
|
|
130
124
|
/** @private 格式化步骤结果的简要信息 */
|
|
131
125
|
_formatStepDetail(r) {
|
|
132
|
-
if (!r)
|
|
126
|
+
if (!r) {
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
133
129
|
const parts = [];
|
|
134
130
|
if (r.configured) {
|
|
135
131
|
parts.push(r.configured.join(', '));
|
|
@@ -145,17 +141,8 @@ export class SetupService {
|
|
|
145
141
|
|
|
146
142
|
printSummary() {
|
|
147
143
|
const results = this._results || [];
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
console.log(` ${'─'.repeat(44)}`);
|
|
152
|
-
console.log(` ✨ Setup 完成: ${ok} 成功${fail > 0 ? `, ${fail} 失败` : ''}`);
|
|
153
|
-
console.log('');
|
|
154
|
-
console.log(' 后续操作:');
|
|
155
|
-
console.log(' asd coldstart 扫描项目、AI 生成知识库');
|
|
156
|
-
console.log(' asd ui 启动 Dashboard + API Server');
|
|
157
|
-
console.log(' asd watch 启动 Xcode 文件监听');
|
|
158
|
-
console.log('');
|
|
144
|
+
const _ok = results.filter((r) => r.ok).length;
|
|
145
|
+
const _fail = results.filter((r) => !r.ok).length;
|
|
159
146
|
}
|
|
160
147
|
|
|
161
148
|
/* ═══ Step 1: 运行时目录与配置 ═══════════════════════ */
|
|
@@ -577,7 +564,12 @@ export class SetupService {
|
|
|
577
564
|
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
578
565
|
for (const cmd of ['code', 'cursor', 'codex', 'code-insiders']) {
|
|
579
566
|
try {
|
|
580
|
-
const p = execSync(`${whichCmd} ${cmd}`, {
|
|
567
|
+
const p = execSync(`${whichCmd} ${cmd}`, {
|
|
568
|
+
encoding: 'utf8',
|
|
569
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
570
|
+
})
|
|
571
|
+
.trim()
|
|
572
|
+
.split(/\r?\n/)[0];
|
|
581
573
|
if (p) {
|
|
582
574
|
candidates.push({ name: cmd, cli: p });
|
|
583
575
|
}
|
|
@@ -591,7 +583,11 @@ export class SetupService {
|
|
|
591
583
|
// macOS: /Applications/xxx.app/Contents/Resources/app/bin/
|
|
592
584
|
const appPaths = [
|
|
593
585
|
{ name: 'vscode', app: '/Applications/Visual Studio Code.app', bin: 'code' },
|
|
594
|
-
{
|
|
586
|
+
{
|
|
587
|
+
name: 'vscode-insiders',
|
|
588
|
+
app: '/Applications/Visual Studio Code - Insiders.app',
|
|
589
|
+
bin: 'code-insiders',
|
|
590
|
+
},
|
|
595
591
|
{ name: 'cursor', app: '/Applications/Cursor.app', bin: 'cursor' },
|
|
596
592
|
{ name: 'codex', app: '/Applications/Codex.app', bin: 'codex' },
|
|
597
593
|
];
|
|
@@ -603,10 +599,15 @@ export class SetupService {
|
|
|
603
599
|
}
|
|
604
600
|
} else if (process.platform === 'win32') {
|
|
605
601
|
// Windows: %LOCALAPPDATA%\Programs\xxx
|
|
606
|
-
const localAppData =
|
|
602
|
+
const localAppData =
|
|
603
|
+
process.env.LOCALAPPDATA || join(process.env.USERPROFILE || '', 'AppData', 'Local');
|
|
607
604
|
const winPaths = [
|
|
608
605
|
{ name: 'vscode', dir: 'Microsoft VS Code', bin: 'bin/code.cmd' },
|
|
609
|
-
{
|
|
606
|
+
{
|
|
607
|
+
name: 'vscode-insiders',
|
|
608
|
+
dir: 'Microsoft VS Code Insiders',
|
|
609
|
+
bin: 'bin/code-insiders.cmd',
|
|
610
|
+
},
|
|
610
611
|
{ name: 'cursor', dir: 'cursor', bin: 'cursor.exe' },
|
|
611
612
|
];
|
|
612
613
|
for (const { name, dir, bin } of winPaths) {
|
|
@@ -639,7 +640,9 @@ export class SetupService {
|
|
|
639
640
|
} catch {
|
|
640
641
|
/* use as-is */
|
|
641
642
|
}
|
|
642
|
-
if (seen.has(realPath))
|
|
643
|
+
if (seen.has(realPath)) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
643
646
|
seen.add(realPath);
|
|
644
647
|
return true;
|
|
645
648
|
});
|
|
@@ -720,9 +723,8 @@ export class SetupService {
|
|
|
720
723
|
}
|
|
721
724
|
|
|
722
725
|
// 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
|
|
723
|
-
const { canWrite
|
|
726
|
+
const { canWrite } = checkWriteSafety(dest);
|
|
724
727
|
if (!canWrite) {
|
|
725
|
-
console.log(` ⏭️ copilot-instructions.md — 跳过(${reason},非 AutoSnippet 生成)`);
|
|
726
728
|
return;
|
|
727
729
|
}
|
|
728
730
|
|
|
@@ -772,7 +774,6 @@ export class SetupService {
|
|
|
772
774
|
].join('\n');
|
|
773
775
|
writeFileSync(agentsPath, agentsContent);
|
|
774
776
|
} else {
|
|
775
|
-
console.log(' ⏭️ AGENTS.md — 跳过(用户文件,非 AutoSnippet 生成)');
|
|
776
777
|
}
|
|
777
778
|
}
|
|
778
779
|
|
|
@@ -805,7 +806,6 @@ export class SetupService {
|
|
|
805
806
|
].join('\n');
|
|
806
807
|
writeFileSync(claudePath, claudeContent);
|
|
807
808
|
} else {
|
|
808
|
-
console.log(' ⏭️ CLAUDE.md — 跳过(用户文件,非 AutoSnippet 生成)');
|
|
809
809
|
}
|
|
810
810
|
}
|
|
811
811
|
}
|
|
@@ -181,7 +181,9 @@ export class UpgradeService {
|
|
|
181
181
|
pipeline
|
|
182
182
|
.deliver()
|
|
183
183
|
.then((_result) => {})
|
|
184
|
-
.catch((_err) => {
|
|
184
|
+
.catch((_err) => {
|
|
185
|
+
/* fire-and-forget: delivery failure is non-critical during upgrade */
|
|
186
|
+
});
|
|
185
187
|
}
|
|
186
188
|
})
|
|
187
189
|
.catch(() => {
|
|
@@ -266,7 +268,6 @@ export class UpgradeService {
|
|
|
266
268
|
mkdirSync(destDir, { recursive: true });
|
|
267
269
|
const { written } = safeCopyFile(src, dest);
|
|
268
270
|
if (!written) {
|
|
269
|
-
console.log(' ⏭️ copilot-instructions.md — 跳过(用户文件,非 AutoSnippet 生成)');
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
|
package/lib/core/AstAnalyzer.js
CHANGED
|
@@ -128,7 +128,7 @@ function analyzeProject(files, lang, options) {
|
|
|
128
128
|
|
|
129
129
|
// SFC 预处理: .vue / .svelte 等文件 → 提取 <script> 块再交给 AST
|
|
130
130
|
if (preprocessFile) {
|
|
131
|
-
const ext = file.name ?
|
|
131
|
+
const ext = file.name ? `.${file.name.split('.').pop()}` : '';
|
|
132
132
|
const result = preprocessFile(content, ext);
|
|
133
133
|
if (result) {
|
|
134
134
|
content = result.content;
|
|
@@ -88,7 +88,7 @@ export async function ensureGrammars(detectedLanguages, options = {}) {
|
|
|
88
88
|
if (result.failed.length > 0) {
|
|
89
89
|
logger?.warn?.(
|
|
90
90
|
`[ensure-grammars] ${result.failed.length} grammar(s) missing. ` +
|
|
91
|
-
|
|
91
|
+
`Expected in: ${GRAMMARS_DIR}`
|
|
92
92
|
);
|
|
93
93
|
} else {
|
|
94
94
|
logger?.info?.('[ensure-grammars] All required grammar .wasm files available');
|
package/lib/core/ast/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { registerLanguage } from '../AstAnalyzer.js';
|
|
23
|
-
import { initParser,
|
|
23
|
+
import { initParser, isParserReady, loadLanguageWasm } from './parser-init.js';
|
|
24
24
|
|
|
25
25
|
let _loaded = false;
|
|
26
26
|
|
|
@@ -36,17 +36,68 @@ export function _resetForReload() {
|
|
|
36
36
|
* 语言注册表 — langId → { wasmFile, module, setGrammarFn, langId, tsxWasmFile?, setTsxGrammarFn? }
|
|
37
37
|
*/
|
|
38
38
|
const LANG_REGISTRY = [
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
39
|
+
{
|
|
40
|
+
langId: 'objectivec',
|
|
41
|
+
wasmFile: 'tree-sitter-objc.wasm',
|
|
42
|
+
module: './lang-objc.js',
|
|
43
|
+
setFn: 'setGrammar',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
langId: 'swift',
|
|
47
|
+
wasmFile: 'tree-sitter-swift.wasm',
|
|
48
|
+
module: './lang-swift.js',
|
|
49
|
+
setFn: 'setGrammar',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
langId: 'typescript',
|
|
53
|
+
wasmFile: 'tree-sitter-typescript.wasm',
|
|
54
|
+
module: './lang-typescript.js',
|
|
55
|
+
setFn: 'setGrammar',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
langId: 'tsx',
|
|
59
|
+
wasmFile: 'tree-sitter-tsx.wasm',
|
|
60
|
+
module: './lang-typescript.js',
|
|
61
|
+
setFn: 'setTsxGrammar',
|
|
62
|
+
pluginKey: 'tsxPlugin',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
langId: 'javascript',
|
|
66
|
+
wasmFile: 'tree-sitter-javascript.wasm',
|
|
67
|
+
module: './lang-javascript.js',
|
|
68
|
+
setFn: 'setGrammar',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
langId: 'python',
|
|
72
|
+
wasmFile: 'tree-sitter-python.wasm',
|
|
73
|
+
module: './lang-python.js',
|
|
74
|
+
setFn: 'setGrammar',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
langId: 'java',
|
|
78
|
+
wasmFile: 'tree-sitter-java.wasm',
|
|
79
|
+
module: './lang-java.js',
|
|
80
|
+
setFn: 'setGrammar',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
langId: 'kotlin',
|
|
84
|
+
wasmFile: 'tree-sitter-kotlin.wasm',
|
|
85
|
+
module: './lang-kotlin.js',
|
|
86
|
+
setFn: 'setGrammar',
|
|
87
|
+
},
|
|
47
88
|
{ langId: 'go', wasmFile: 'tree-sitter-go.wasm', module: './lang-go.js', setFn: 'setGrammar' },
|
|
48
|
-
{
|
|
49
|
-
|
|
89
|
+
{
|
|
90
|
+
langId: 'dart',
|
|
91
|
+
wasmFile: 'tree-sitter-dart.wasm',
|
|
92
|
+
module: './lang-dart.js',
|
|
93
|
+
setFn: 'setGrammar',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
langId: 'rust',
|
|
97
|
+
wasmFile: 'tree-sitter-rust.wasm',
|
|
98
|
+
module: './lang-rust.js',
|
|
99
|
+
setFn: 'setGrammar',
|
|
100
|
+
},
|
|
50
101
|
];
|
|
51
102
|
|
|
52
103
|
/**
|
|
@@ -146,8 +146,12 @@ function _parseClassDef(node, ctx) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
let kind = 'class';
|
|
149
|
-
if (isAbstract)
|
|
150
|
-
|
|
149
|
+
if (isAbstract) {
|
|
150
|
+
kind = 'abstract-class';
|
|
151
|
+
}
|
|
152
|
+
if (isSealed) {
|
|
153
|
+
kind = 'sealed-class';
|
|
154
|
+
}
|
|
151
155
|
|
|
152
156
|
ctx.classes.push({
|
|
153
157
|
name,
|
|
@@ -227,7 +231,9 @@ function _parseMixinDecl(node, ctx) {
|
|
|
227
231
|
);
|
|
228
232
|
const name = nameNode?.text || 'Unknown';
|
|
229
233
|
|
|
230
|
-
const onClause = node.namedChildren.find(
|
|
234
|
+
const onClause = node.namedChildren.find(
|
|
235
|
+
(c) => c.type === 'on_clause' || c.type === 'superclass'
|
|
236
|
+
);
|
|
231
237
|
const constraints = [];
|
|
232
238
|
if (onClause) {
|
|
233
239
|
for (let i = 0; i < onClause.namedChildCount; i++) {
|
|
@@ -262,9 +268,7 @@ function _parseExtensionDecl(node, ctx) {
|
|
|
262
268
|
const name = nameNode?.text || 'anonymous_extension';
|
|
263
269
|
|
|
264
270
|
// on Type
|
|
265
|
-
const onType = node.namedChildren.find(
|
|
266
|
-
(c) => c.type === 'type_identifier' && c !== nameNode
|
|
267
|
-
);
|
|
271
|
+
const onType = node.namedChildren.find((c) => c.type === 'type_identifier' && c !== nameNode);
|
|
268
272
|
|
|
269
273
|
ctx.categories.push({
|
|
270
274
|
name,
|
|
@@ -273,7 +277,9 @@ function _parseExtensionDecl(node, ctx) {
|
|
|
273
277
|
endLine: node.endPosition.row + 1,
|
|
274
278
|
});
|
|
275
279
|
|
|
276
|
-
const body = node.namedChildren.find(
|
|
280
|
+
const body = node.namedChildren.find(
|
|
281
|
+
(c) => c.type === 'class_body' || c.type === 'extension_body'
|
|
282
|
+
);
|
|
277
283
|
if (body) {
|
|
278
284
|
_walkClassBody(body, ctx, name);
|
|
279
285
|
}
|
|
@@ -310,9 +316,7 @@ function _parseFunctionDef(node, className) {
|
|
|
310
316
|
const isAsync = node.text.includes('async') || node.text.includes('async*');
|
|
311
317
|
const isOverride = node.text.includes('@override');
|
|
312
318
|
|
|
313
|
-
const body = node.namedChildren.find(
|
|
314
|
-
(c) => c.type === 'function_body' || c.type === 'block'
|
|
315
|
-
);
|
|
319
|
+
const body = node.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
|
|
316
320
|
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
317
321
|
const complexity = body ? _estimateComplexity(body) : 1;
|
|
318
322
|
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
@@ -372,13 +376,17 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
|
|
|
372
376
|
const classPropMap = {};
|
|
373
377
|
for (const m of methods) {
|
|
374
378
|
if (m.className) {
|
|
375
|
-
if (!classMethodMap[m.className])
|
|
379
|
+
if (!classMethodMap[m.className]) {
|
|
380
|
+
classMethodMap[m.className] = [];
|
|
381
|
+
}
|
|
376
382
|
classMethodMap[m.className].push(m);
|
|
377
383
|
}
|
|
378
384
|
}
|
|
379
385
|
for (const p of properties) {
|
|
380
386
|
if (p.className) {
|
|
381
|
-
if (!classPropMap[p.className])
|
|
387
|
+
if (!classPropMap[p.className]) {
|
|
388
|
+
classPropMap[p.className] = [];
|
|
389
|
+
}
|
|
382
390
|
classPropMap[p.className].push(p);
|
|
383
391
|
}
|
|
384
392
|
}
|
|
@@ -430,10 +438,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
|
|
|
430
438
|
}
|
|
431
439
|
|
|
432
440
|
// ChangeNotifier (Provider pattern)
|
|
433
|
-
if (
|
|
434
|
-
cls.superclass === 'ChangeNotifier' ||
|
|
435
|
-
(cls.mixins && cls.mixins.includes('ChangeNotifier'))
|
|
436
|
-
) {
|
|
441
|
+
if (cls.superclass === 'ChangeNotifier' || cls.mixins?.includes('ChangeNotifier')) {
|
|
437
442
|
patterns.push({
|
|
438
443
|
type: 'change-notifier',
|
|
439
444
|
className: cls.name,
|
|
@@ -448,9 +453,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
|
|
|
448
453
|
const hasPrivateConstructor = classMethods.some(
|
|
449
454
|
(m) => m.kind === 'constructor' && m.name.startsWith('_')
|
|
450
455
|
);
|
|
451
|
-
const hasStaticInstance = classProps.some(
|
|
452
|
-
(p) => p.isStatic && (p.isFinal || p.isConst)
|
|
453
|
-
);
|
|
456
|
+
const hasStaticInstance = classProps.some((p) => p.isStatic && (p.isFinal || p.isConst));
|
|
454
457
|
const hasFactoryConstructor = classMethods.some((m) => m.kind === 'factory');
|
|
455
458
|
|
|
456
459
|
if (hasPrivateConstructor && (hasStaticInstance || hasFactoryConstructor)) {
|
|
@@ -493,7 +496,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
|
|
|
493
496
|
}
|
|
494
497
|
|
|
495
498
|
// Freezed pattern — @freezed/@Freezed annotation + with _$ClassName mixin
|
|
496
|
-
if (cls.mixins
|
|
499
|
+
if (cls.mixins?.some((m) => m.startsWith('_$'))) {
|
|
497
500
|
patterns.push({
|
|
498
501
|
type: 'freezed',
|
|
499
502
|
className: cls.name,
|
|
@@ -548,7 +551,10 @@ function _detectStreamUsage(root, patterns) {
|
|
|
548
551
|
if (node.type === 'type_identifier' && node.text === 'Stream') {
|
|
549
552
|
streamCount++;
|
|
550
553
|
}
|
|
551
|
-
if (
|
|
554
|
+
if (
|
|
555
|
+
node.type === 'identifier' &&
|
|
556
|
+
(node.text === 'StreamController' || node.text === 'StreamSubscription')
|
|
557
|
+
) {
|
|
552
558
|
streamCount++;
|
|
553
559
|
}
|
|
554
560
|
for (let i = 0; i < node.namedChildCount; i++) {
|
package/lib/core/ast/lang-go.js
CHANGED
|
@@ -39,9 +39,7 @@ function walkGo(root, ctx) {
|
|
|
39
39
|
// 单行 import
|
|
40
40
|
const spec = child.namedChildren.find((c) => c.type === 'import_spec');
|
|
41
41
|
if (spec) {
|
|
42
|
-
const strLit = spec.namedChildren.find(
|
|
43
|
-
(c) => c.type === 'interpreted_string_literal'
|
|
44
|
-
);
|
|
42
|
+
const strLit = spec.namedChildren.find((c) => c.type === 'interpreted_string_literal');
|
|
45
43
|
if (strLit) {
|
|
46
44
|
ctx.imports.push(strLit.text.replace(/"/g, ''));
|
|
47
45
|
}
|
|
@@ -117,7 +115,7 @@ function _walkTypeDeclaration(node, ctx) {
|
|
|
117
115
|
|
|
118
116
|
function _parseStruct(name, structNode, specNode, ctx) {
|
|
119
117
|
const fields = [];
|
|
120
|
-
|
|
118
|
+
const embeddedTypes = [];
|
|
121
119
|
|
|
122
120
|
const fieldList = structNode.namedChildren.find((c) => c.type === 'field_declaration_list');
|
|
123
121
|
if (fieldList) {
|
|
@@ -326,14 +324,9 @@ function detectGoPatterns(root, lang, methods, properties, classes) {
|
|
|
326
324
|
continue;
|
|
327
325
|
}
|
|
328
326
|
// 检查是否有 package-level var 指向此 struct
|
|
329
|
-
const hasPackageVar = properties.some(
|
|
330
|
-
(p) => !p.className && !p.isConst && !p.isExported
|
|
331
|
-
);
|
|
327
|
+
const hasPackageVar = properties.some((p) => !p.className && !p.isConst && !p.isExported);
|
|
332
328
|
const hasNewFunc = methods.some(
|
|
333
|
-
(m) =>
|
|
334
|
-
!m.className &&
|
|
335
|
-
/^(?:New|Get|Default)/.test(m.name) &&
|
|
336
|
-
m.isExported
|
|
329
|
+
(m) => !m.className && /^(?:New|Get|Default)/.test(m.name) && m.isExported
|
|
337
330
|
);
|
|
338
331
|
if (hasPackageVar && hasNewFunc) {
|
|
339
332
|
patterns.push({
|
|
@@ -346,11 +339,7 @@ function detectGoPatterns(root, lang, methods, properties, classes) {
|
|
|
346
339
|
|
|
347
340
|
// Factory: New* / Create* package-level functions
|
|
348
341
|
for (const m of methods) {
|
|
349
|
-
if (
|
|
350
|
-
!m.className &&
|
|
351
|
-
m.isExported &&
|
|
352
|
-
/^(?:New|Create|Make|Build|Open|Connect)/.test(m.name)
|
|
353
|
-
) {
|
|
342
|
+
if (!m.className && m.isExported && /^(?:New|Create|Make|Build|Open|Connect)/.test(m.name)) {
|
|
354
343
|
patterns.push({
|
|
355
344
|
type: 'factory',
|
|
356
345
|
methodName: m.name,
|
|
@@ -446,10 +435,7 @@ function _countParams(paramList) {
|
|
|
446
435
|
let count = 0;
|
|
447
436
|
for (let i = 0; i < paramList.namedChildCount; i++) {
|
|
448
437
|
const child = paramList.namedChild(i);
|
|
449
|
-
if (
|
|
450
|
-
child.type === 'parameter_declaration' ||
|
|
451
|
-
child.type === 'variadic_parameter_declaration'
|
|
452
|
-
) {
|
|
438
|
+
if (child.type === 'parameter_declaration' || child.type === 'variadic_parameter_declaration') {
|
|
453
439
|
// Each identifier in the same declaration is a parameter
|
|
454
440
|
const ids = child.namedChildren.filter((c) => c.type === 'identifier');
|
|
455
441
|
count += Math.max(ids.length, 1);
|