autosnippet 3.2.7 → 3.2.8
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/cli.js +7 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
- package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +3 -3
- package/lib/core/AstAnalyzer.js +26 -4
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/routes/remote.js +15 -15
- package/lib/injection/ServiceContainer.js +6 -11
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/chat/ChatAgent.js +1 -1
- package/lib/service/chat/ChatAgentPrompts.js +13 -1
- package/lib/service/chat/ExplorationTracker.js +52 -8
- package/lib/service/chat/HandoffProtocol.js +19 -1
- package/lib/service/chat/WorkingMemory.js +3 -1
- package/lib/service/chat/memory/ActiveContext.js +3 -1
- package/lib/service/chat/memory/SessionStore.js +4 -3
- package/lib/service/chat/tools/ast-graph.js +229 -32
- package/lib/service/chat/tools/index.js +6 -1
- package/lib/service/chat/tools/infrastructure.js +5 -0
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +9 -0
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
|
@@ -75,6 +75,9 @@ export async function bootstrapExternal(ctx) {
|
|
|
75
75
|
contentMaxLines: 120,
|
|
76
76
|
sourceTag: 'bootstrap-external',
|
|
77
77
|
summaryPrefix: 'Bootstrap-external scan',
|
|
78
|
+
clearOldData: true,
|
|
79
|
+
generateReport: true,
|
|
80
|
+
incremental: true,
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
// 空项目 fast-path
|
|
@@ -88,9 +91,9 @@ export async function bootstrapExternal(ctx) {
|
|
|
88
91
|
|
|
89
92
|
const {
|
|
90
93
|
allFiles, primaryLang, depGraphData, langStats,
|
|
91
|
-
astProjectSummary, codeEntityResult, guardAudit,
|
|
94
|
+
astProjectSummary, codeEntityResult, callGraphResult, guardAudit,
|
|
92
95
|
activeDimensions: dimensions, targetsSummary,
|
|
93
|
-
langProfile,
|
|
96
|
+
langProfile, incrementalPlan,
|
|
94
97
|
} = phaseResults;
|
|
95
98
|
|
|
96
99
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -115,6 +118,7 @@ export async function bootstrapExternal(ctx) {
|
|
|
115
118
|
allFiles,
|
|
116
119
|
astProjectSummary,
|
|
117
120
|
codeEntityResult,
|
|
121
|
+
callGraphResult,
|
|
118
122
|
depGraphData,
|
|
119
123
|
guardAudit,
|
|
120
124
|
langStats,
|
|
@@ -138,12 +142,15 @@ export async function bootstrapExternal(ctx) {
|
|
|
138
142
|
projectMeta,
|
|
139
143
|
astData: astProjectSummary,
|
|
140
144
|
codeEntityResult,
|
|
145
|
+
callGraphResult,
|
|
141
146
|
depGraphData,
|
|
142
147
|
guardAudit,
|
|
143
148
|
targets: targetsSummary,
|
|
144
149
|
activeDimensions: dimensions,
|
|
145
150
|
session,
|
|
146
151
|
languageExtension: buildLanguageExtension(primaryLang), // §7.1
|
|
152
|
+
incrementalPlan,
|
|
153
|
+
languageStats: langStats,
|
|
147
154
|
});
|
|
148
155
|
|
|
149
156
|
// 附加 warnings
|
|
@@ -87,6 +87,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
87
87
|
const maxFiles = args.maxFiles || 500;
|
|
88
88
|
const skipGuard = args.skipGuard || false;
|
|
89
89
|
const contentMaxLines = args.contentMaxLines || 120;
|
|
90
|
+
const skipAsyncFill = args.skipAsyncFill || false;
|
|
90
91
|
|
|
91
92
|
// ═══════════════════════════════════════════════════════════
|
|
92
93
|
// Phase 1-4: 共享管线(文件收集→AST→依赖→Guard→维度解析)
|
|
@@ -149,7 +150,12 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
149
150
|
categories: astProjectSummary?.categories?.length || 0,
|
|
150
151
|
patterns: Object.keys(astProjectSummary?.patternStats || {}),
|
|
151
152
|
},
|
|
152
|
-
codeEntityGraph: phaseReport?.phases?.entityGraph || {
|
|
153
|
+
codeEntityGraph: phaseReport?.phases?.entityGraph || { entityCount: 0, edgeCount: 0, ms: 0 },
|
|
154
|
+
callGraph: phaseReport?.phases?.callGraph ? {
|
|
155
|
+
entities: phaseReport.phases.callGraph.result?.entitiesUpserted || 0,
|
|
156
|
+
edges: phaseReport.phases.callGraph.result?.edgesCreated || 0,
|
|
157
|
+
ms: phaseReport.phases.callGraph.ms || 0,
|
|
158
|
+
} : { entities: 0, edges: 0, ms: 0 },
|
|
153
159
|
dependencyGraph: { edgesWritten: depEdgesWritten || 0 },
|
|
154
160
|
enhancementPacks: {
|
|
155
161
|
matched: enhancementPackInfo,
|
|
@@ -371,12 +377,17 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
371
377
|
};
|
|
372
378
|
|
|
373
379
|
// 使用 setImmediate 避免阻塞 HTTP 响应
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
ctx.logger.
|
|
380
|
+
// skipAsyncFill: CLI 非 --wait 模式跳过异步填充,避免进程退出后 DB 断连
|
|
381
|
+
if (!skipAsyncFill) {
|
|
382
|
+
setImmediate(() => {
|
|
383
|
+
ctx.logger.info(`[Bootstrap] Dispatching v3 AI-First pipeline`);
|
|
384
|
+
fillDimensionsV3(fillContext).catch((e) => {
|
|
385
|
+
ctx.logger.error(`[Bootstrap] Async fill (v3) failed: ${e.message}`);
|
|
386
|
+
});
|
|
378
387
|
});
|
|
379
|
-
}
|
|
388
|
+
} else {
|
|
389
|
+
ctx.logger.info(`[Bootstrap] Async fill skipped (skipAsyncFill=true)`);
|
|
390
|
+
}
|
|
380
391
|
|
|
381
392
|
// ── SkillHooks: onBootstrapStarted (fire-and-forget) ──
|
|
382
393
|
try {
|
|
@@ -99,6 +99,15 @@ export async function consolidatedStructure(ctx, args) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// ─── autosnippet_call_context (Phase 5) ─────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 调用链上下文查询:直接转发到 structure.callContext
|
|
106
|
+
*/
|
|
107
|
+
export async function consolidatedCallContext(ctx, args) {
|
|
108
|
+
return structureHandlers.callContext(ctx, args);
|
|
109
|
+
}
|
|
110
|
+
|
|
102
111
|
// ─── autosnippet_graph (整合 4 → 1) ─────────────────────────
|
|
103
112
|
|
|
104
113
|
/**
|
|
@@ -416,14 +416,14 @@ export async function scanProject(ctx, args) {
|
|
|
416
416
|
|
|
417
417
|
const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
|
|
418
418
|
|
|
419
|
-
// 优先使用 ModuleService(多语言统一入口),回退到
|
|
419
|
+
// 优先使用 ModuleService(多语言统一入口),回退到 SpmHelper
|
|
420
420
|
let service;
|
|
421
421
|
try {
|
|
422
422
|
const { ModuleService } = await import('../../../service/module/ModuleService.js');
|
|
423
423
|
service = new ModuleService(projectRoot);
|
|
424
424
|
} catch {
|
|
425
|
-
const {
|
|
426
|
-
service = new
|
|
425
|
+
const { SpmHelper } = await import('../../../platform/ios/spm/SpmHelper.js');
|
|
426
|
+
service = new SpmHelper(projectRoot);
|
|
427
427
|
}
|
|
428
428
|
await service.load();
|
|
429
429
|
const allTargets = await service.listTargets();
|
|
@@ -527,6 +527,68 @@ async function _fallbackPathFromRecipe(ctx, fromId, toId) {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
// ─── call_context — 调用链上下文 (Phase 5) ──────────────────
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* autosnippet_call_context handler
|
|
534
|
+
* 查询方法的调用者、被调用者、影响半径
|
|
535
|
+
*/
|
|
536
|
+
export async function callContext(ctx, args) {
|
|
537
|
+
if (!args.methodName) {
|
|
538
|
+
throw new Error('Missing required parameter: methodName');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const ceg = ctx.container.get('codeEntityGraph');
|
|
542
|
+
if (!ceg) {
|
|
543
|
+
return envelope({
|
|
544
|
+
success: false,
|
|
545
|
+
message: 'CodeEntityGraph not available — 请先运行 bootstrap',
|
|
546
|
+
meta: { tool: 'autosnippet_call_context' },
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const direction = args.direction || 'both';
|
|
551
|
+
const maxDepth = Math.min(Math.max(args.maxDepth ?? 2, 1), 5);
|
|
552
|
+
const result = {};
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
if (direction === 'callers' || direction === 'both') {
|
|
556
|
+
result.callers = ceg.getCallers(args.methodName, maxDepth);
|
|
557
|
+
}
|
|
558
|
+
if (direction === 'callees' || direction === 'both') {
|
|
559
|
+
result.callees = ceg.getCallees(args.methodName, maxDepth);
|
|
560
|
+
}
|
|
561
|
+
if (direction === 'impact') {
|
|
562
|
+
result.impact = ceg.getCallImpactRadius(args.methodName);
|
|
563
|
+
}
|
|
564
|
+
} catch (err) {
|
|
565
|
+
if (err.message?.includes('no such table')) {
|
|
566
|
+
return envelope({
|
|
567
|
+
success: true,
|
|
568
|
+
data: {
|
|
569
|
+
methodName: args.methodName,
|
|
570
|
+
callers: [],
|
|
571
|
+
callees: [],
|
|
572
|
+
note: 'knowledge_edges 表不存在,请运行 bootstrap 后再查询',
|
|
573
|
+
},
|
|
574
|
+
meta: { tool: 'autosnippet_call_context' },
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
throw err;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return envelope({
|
|
581
|
+
success: true,
|
|
582
|
+
data: {
|
|
583
|
+
methodName: args.methodName,
|
|
584
|
+
direction,
|
|
585
|
+
maxDepth,
|
|
586
|
+
...result,
|
|
587
|
+
},
|
|
588
|
+
meta: { tool: 'autosnippet_call_context' },
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
530
592
|
// ─── graph_stats — 图谱统计 ────────────────────────────────
|
|
531
593
|
|
|
532
594
|
export async function graphStats(ctx) {
|
|
@@ -60,10 +60,73 @@ export async function wikiPlan(ctx, args) {
|
|
|
60
60
|
const session = getActiveSession(container, args.sessionId);
|
|
61
61
|
const cachedData = session?.phaseCache;
|
|
62
62
|
|
|
63
|
-
if (cachedData?.
|
|
64
|
-
|
|
63
|
+
if (cachedData?.astProjectSummary) {
|
|
64
|
+
// Bootstrap phase cache → WikiGenerator-compatible format 转换
|
|
65
|
+
const allFiles = cachedData.allFiles || [];
|
|
66
|
+
const ast = cachedData.astProjectSummary;
|
|
67
|
+
|
|
68
|
+
// projectInfo: 从 bootstrap 文件列表和语言统计构建
|
|
69
|
+
const filesByModule = {};
|
|
70
|
+
for (const f of allFiles) {
|
|
71
|
+
const mod = f.targetName || '_default';
|
|
72
|
+
if (!filesByModule[mod]) filesByModule[mod] = [];
|
|
73
|
+
filesByModule[mod].push(f.relativePath);
|
|
74
|
+
}
|
|
75
|
+
projectInfo = {
|
|
76
|
+
name: path.basename(projectRoot),
|
|
77
|
+
root: projectRoot,
|
|
78
|
+
sourceFiles: allFiles.map(f => f.relativePath),
|
|
79
|
+
languages: cachedData.langStats || {},
|
|
80
|
+
primaryLanguage: cachedData.primaryLang || 'unknown',
|
|
81
|
+
sourceFilesByModule: filesByModule,
|
|
82
|
+
buildSystems: [],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// astInfo: 从 AstAnalyzer 结果构建
|
|
86
|
+
const classesByModule = {};
|
|
87
|
+
const protocolsByModule = {};
|
|
88
|
+
for (const cls of ast.classes || []) {
|
|
89
|
+
const mod = cls.targetName || '_default';
|
|
90
|
+
if (!classesByModule[mod]) classesByModule[mod] = [];
|
|
91
|
+
classesByModule[mod].push(cls.name);
|
|
92
|
+
}
|
|
93
|
+
for (const p of ast.protocols || []) {
|
|
94
|
+
const mod = p.targetName || '_default';
|
|
95
|
+
if (!protocolsByModule[mod]) protocolsByModule[mod] = [];
|
|
96
|
+
protocolsByModule[mod].push(p.name);
|
|
97
|
+
}
|
|
98
|
+
astInfo = {
|
|
99
|
+
classes: (ast.classes || []).map(c => c.name),
|
|
100
|
+
protocols: (ast.protocols || []).map(p => p.name),
|
|
101
|
+
overview: ast.projectMetrics || null,
|
|
102
|
+
classNamesByModule: classesByModule,
|
|
103
|
+
protocolNamesByModule: protocolsByModule,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// moduleInfo: 从依赖图和 targets 构建
|
|
107
|
+
moduleInfo = {
|
|
108
|
+
targets: (cachedData.targetsSummary || []).map(t => ({
|
|
109
|
+
name: t.name, type: t.type, fileCount: t.fileCount,
|
|
110
|
+
})),
|
|
111
|
+
depGraph: cachedData.depGraphData || null,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// knowledgeInfo: 始终从 DB 获取最新(bootstrap 期间可能已写入知识)
|
|
115
|
+
try {
|
|
116
|
+
const ks = tryGet(container, 'knowledgeService');
|
|
117
|
+
if (ks) {
|
|
118
|
+
const items = await ks.list({ limit: 200 });
|
|
119
|
+
const stats = typeof ks.getStats === 'function' ? await ks.getStats() : null;
|
|
120
|
+
knowledgeInfo = { recipes: items?.items || items || [], stats };
|
|
121
|
+
} else {
|
|
122
|
+
knowledgeInfo = { recipes: [], stats: null };
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
knowledgeInfo = { recipes: [], stats: null };
|
|
126
|
+
}
|
|
127
|
+
|
|
65
128
|
cacheHit = true;
|
|
66
|
-
logger.info('[wiki-plan] Reusing bootstrap phase cache');
|
|
129
|
+
logger.info('[wiki-plan] Reusing bootstrap phase cache (converted to WikiGenerator format)');
|
|
67
130
|
} else {
|
|
68
131
|
// 无缓存(独立调用 wiki_plan 或进程已重启)→ 重新扫描
|
|
69
132
|
logger.info('[wiki-plan] No bootstrap cache, running fresh scan...');
|
|
@@ -210,7 +210,42 @@ export const TOOLS = [
|
|
|
210
210
|
},
|
|
211
211
|
},
|
|
212
212
|
|
|
213
|
-
// 6.
|
|
213
|
+
// 6. 调用链上下文 (Phase 5)
|
|
214
|
+
{
|
|
215
|
+
name: 'autosnippet_call_context',
|
|
216
|
+
tier: 'agent',
|
|
217
|
+
description:
|
|
218
|
+
'查询方法的调用链上下文。\n' +
|
|
219
|
+
'• callers: 谁调用了这个方法?(调用者链)\n' +
|
|
220
|
+
'• callees: 这个方法调用了谁?(依赖链)\n' +
|
|
221
|
+
'• impact: 修改此方法的影响半径分析\n' +
|
|
222
|
+
'• both: 同时获取调用者和被调用者',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
methodName: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
description: '方法名 (e.g. "UserService.getUser" 或 "getUser")',
|
|
229
|
+
},
|
|
230
|
+
direction: {
|
|
231
|
+
type: 'string',
|
|
232
|
+
enum: ['callers', 'callees', 'both', 'impact'],
|
|
233
|
+
default: 'both',
|
|
234
|
+
description: 'callers=调用者 | callees=被调用者 | both=双向 | impact=影响分析',
|
|
235
|
+
},
|
|
236
|
+
maxDepth: {
|
|
237
|
+
type: 'number',
|
|
238
|
+
default: 2,
|
|
239
|
+
minimum: 1,
|
|
240
|
+
maximum: 5,
|
|
241
|
+
description: '最大遍历深度',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
required: ['methodName'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// 7. Guard 检查(统一入口)
|
|
214
249
|
{
|
|
215
250
|
name: 'autosnippet_guard',
|
|
216
251
|
tier: 'agent',
|
|
@@ -120,7 +120,7 @@ let _larkClient = null;
|
|
|
120
120
|
let _wsConnected = false;
|
|
121
121
|
let _wsStarting = false;
|
|
122
122
|
|
|
123
|
-
async function startLarkWS() {
|
|
123
|
+
async function startLarkWS({ silent = false } = {}) {
|
|
124
124
|
// 如果已连接且对象存在 → 直接返回
|
|
125
125
|
if (_wsClient && _wsConnected) return { success: true, message: 'Already connected' };
|
|
126
126
|
if (_wsStarting) return { success: true, message: 'Connection in progress' };
|
|
@@ -173,16 +173,18 @@ async function startLarkWS() {
|
|
|
173
173
|
|
|
174
174
|
logger.info('[Remote/Lark] ✅ WebSocket long connection established');
|
|
175
175
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
176
|
+
// 向飞书发送上线通知(仅首次启动,重连时静默)
|
|
177
|
+
if (!silent) {
|
|
178
|
+
setTimeout(() => {
|
|
179
|
+
sendLarkNotification([
|
|
180
|
+
'🟢 IDE 桥接已上线',
|
|
181
|
+
`时间: ${new Date().toLocaleString('zh-CN')}`,
|
|
182
|
+
`平台: macOS | Node ${process.version}`,
|
|
183
|
+
'',
|
|
184
|
+
'发送任意文字即可远程编程,/help 查看命令。',
|
|
185
|
+
].join('\n')).catch(() => {});
|
|
186
|
+
}, 1000);
|
|
187
|
+
}
|
|
186
188
|
|
|
187
189
|
return { success: true, message: 'Connected via WebSocket' };
|
|
188
190
|
} catch (err) {
|
|
@@ -246,14 +248,12 @@ setInterval(async () => {
|
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
// WSClient 不存在或已标记断开 →
|
|
251
|
+
// WSClient 不存在或已标记断开 → 自动重连(静默,不打扰用户)
|
|
250
252
|
if (!_wsClient && !_wsStarting) {
|
|
251
253
|
logger.info('[Remote/Lark] Connection lost, auto-reconnecting...');
|
|
252
|
-
const result = await startLarkWS();
|
|
254
|
+
const result = await startLarkWS({ silent: true });
|
|
253
255
|
if (result.success) {
|
|
254
256
|
logger.info('[Remote/Lark] ✅ Auto-reconnected successfully');
|
|
255
|
-
// 重连成功通知
|
|
256
|
-
sendLarkNotification('🔄 IDE 桥接重连成功').catch(() => {});
|
|
257
257
|
} else {
|
|
258
258
|
logger.warn(`[Remote/Lark] Auto-reconnect failed: ${result.message}`);
|
|
259
259
|
}
|
|
@@ -18,7 +18,7 @@ import { IndexingPipeline } from '../infrastructure/vector/IndexingPipeline.js';
|
|
|
18
18
|
// ─── P0: Vector Storage ──────────────────────────────
|
|
19
19
|
import { JsonVectorAdapter } from '../infrastructure/vector/JsonVectorAdapter.js';
|
|
20
20
|
// ─── P2: SPM ──────────────────────────────────────────
|
|
21
|
-
import {
|
|
21
|
+
import { SpmHelper } from '../platform/ios/spm/SpmHelper.js';
|
|
22
22
|
import { KnowledgeRepositoryImpl } from '../repository/knowledge/KnowledgeRepository.impl.js';
|
|
23
23
|
// ─── P1: Token Usage Tracking ─────────────────────────
|
|
24
24
|
import { TokenUsageStore } from '../repository/token/TokenUsageStore.js';
|
|
@@ -121,7 +121,7 @@ export class ServiceContainer {
|
|
|
121
121
|
this.singletons.skillHooks = bootstrapComponents.skillHooks;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// AiFactory 模块引用(用于
|
|
124
|
+
// AiFactory 模块引用(用于 SpmHelper AI 扫描)
|
|
125
125
|
try {
|
|
126
126
|
this.singletons._aiFactory = await import('../external/ai/AiFactory.js');
|
|
127
127
|
} catch {
|
|
@@ -658,18 +658,11 @@ export class ServiceContainer {
|
|
|
658
658
|
return this.singletons.feedbackCollector;
|
|
659
659
|
});
|
|
660
660
|
|
|
661
|
-
//
|
|
661
|
+
// SpmHelper (SPM 包结构解析与依赖操作辅助)
|
|
662
662
|
this.register('spmService', () => {
|
|
663
663
|
if (!this.singletons.spmService) {
|
|
664
664
|
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
665
|
-
this.singletons.spmService = new
|
|
666
|
-
aiFactory: this.singletons._aiFactory || null,
|
|
667
|
-
chatAgent: this.singletons.chatAgent || null,
|
|
668
|
-
qualityScorer: this.get('qualityScorer'),
|
|
669
|
-
recipeExtractor: this.singletons._recipeExtractor || null,
|
|
670
|
-
guardCheckEngine: this.get('guardCheckEngine'),
|
|
671
|
-
violationsStore: this.get('violationsStore'),
|
|
672
|
-
});
|
|
665
|
+
this.singletons.spmService = new SpmHelper(projectRoot);
|
|
673
666
|
}
|
|
674
667
|
return this.singletons.spmService;
|
|
675
668
|
});
|
|
@@ -766,9 +759,11 @@ export class ServiceContainer {
|
|
|
766
759
|
if (!this.singletons.cursorDeliveryPipeline) {
|
|
767
760
|
const knowledgeService = this.get('knowledgeService');
|
|
768
761
|
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
762
|
+
const database = this.get('database');
|
|
769
763
|
this.singletons.cursorDeliveryPipeline = new CursorDeliveryPipeline({
|
|
770
764
|
knowledgeService,
|
|
771
765
|
projectRoot,
|
|
766
|
+
database,
|
|
772
767
|
logger: this.logger,
|
|
773
768
|
});
|
|
774
769
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* PlaceholderConverter.js — Xcode <#…#> ↔ VSCode ${N:…} 占位符转换
|
|
15
15
|
*
|
|
16
16
|
* spm/
|
|
17
|
-
*
|
|
17
|
+
* SpmHelper.js — SPM 包结构解析与依赖操作辅助工具
|
|
18
18
|
* SpmDiscoverer.js — SPM 项目自动发现(ProjectDiscoverer 接口)
|
|
19
19
|
* PackageSwiftParser.js — Package.swift 解析器
|
|
20
20
|
* DependencyGraph.js — SPM Target 依赖图
|
|
@@ -36,7 +36,7 @@ export { PackageSwiftParser } from './spm/PackageSwiftParser.js';
|
|
|
36
36
|
export { PolicyEngine } from './spm/PolicyEngine.js';
|
|
37
37
|
export { SpmDiscoverer } from './spm/SpmDiscoverer.js';
|
|
38
38
|
// ── Swift Package Manager ──
|
|
39
|
-
export {
|
|
39
|
+
export { SpmHelper } from './spm/SpmHelper.js';
|
|
40
40
|
export { saveEventFilter } from './xcode/SaveEventFilter.js';
|
|
41
41
|
// ── Xcode IDE 自动化 ──
|
|
42
42
|
export {
|
|
@@ -218,10 +218,11 @@ export class PackageSwiftParser {
|
|
|
218
218
|
|
|
219
219
|
#extractDependencies(content) {
|
|
220
220
|
const deps = [];
|
|
221
|
-
|
|
221
|
+
|
|
222
|
+
// 1. URL 依赖: .package(url: "...", ...)
|
|
223
|
+
const urlRe = /\.package\s*\(\s*url\s*:\s*"([^"]+)"[^)]*\)/g;
|
|
222
224
|
let m;
|
|
223
|
-
while ((m =
|
|
224
|
-
// 提取版本约束
|
|
225
|
+
while ((m = urlRe.exec(content)) !== null) {
|
|
225
226
|
const block = m[0];
|
|
226
227
|
const fromMatch = block.match(/from\s*:\s*"([^"]+)"/);
|
|
227
228
|
const exactMatch = block.match(/exact\s*:\s*"([^"]+)"/);
|
|
@@ -231,6 +232,16 @@ export class PackageSwiftParser {
|
|
|
231
232
|
type: 'package',
|
|
232
233
|
});
|
|
233
234
|
}
|
|
235
|
+
|
|
236
|
+
// 2. Local path 依赖: .package(path: "...")
|
|
237
|
+
const pathRe = /\.package\s*\(\s*path\s*:\s*"([^"]+)"\s*\)/g;
|
|
238
|
+
while ((m = pathRe.exec(content)) !== null) {
|
|
239
|
+
deps.push({
|
|
240
|
+
path: m[1],
|
|
241
|
+
type: 'local',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
234
245
|
return deps;
|
|
235
246
|
}
|
|
236
247
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module SpmDiscoverer
|
|
3
|
-
* @description 包装现有
|
|
3
|
+
* @description 包装现有 SpmHelper,适配 ProjectDiscoverer 接口
|
|
4
4
|
*
|
|
5
5
|
* 检测: 项目根或子目录存在 Package.swift
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
-
import { basename, join, relative } from 'node:path';
|
|
9
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
10
10
|
import { ProjectDiscoverer } from '../../../core/discovery/ProjectDiscoverer.js';
|
|
11
11
|
import { LanguageService } from '../../../shared/LanguageService.js';
|
|
12
|
+
import { PackageSwiftParser } from './PackageSwiftParser.js';
|
|
12
13
|
|
|
13
14
|
export class SpmDiscoverer extends ProjectDiscoverer {
|
|
14
|
-
/** @type {import('./
|
|
15
|
+
/** @type {import('./SpmHelper.js').SpmHelper|null} */
|
|
15
16
|
#spm = null;
|
|
16
17
|
#projectRoot = null;
|
|
17
18
|
|
|
@@ -52,9 +53,9 @@ export class SpmDiscoverer extends ProjectDiscoverer {
|
|
|
52
53
|
|
|
53
54
|
async load(projectRoot) {
|
|
54
55
|
this.#projectRoot = projectRoot;
|
|
55
|
-
// 动态加载
|
|
56
|
-
const {
|
|
57
|
-
this.#spm = new
|
|
56
|
+
// 动态加载 SpmHelper(避免循环导入)
|
|
57
|
+
const { SpmHelper } = await import('./SpmHelper.js');
|
|
58
|
+
this.#spm = new SpmHelper(projectRoot);
|
|
58
59
|
await this.#spm.load();
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -97,21 +98,126 @@ export class SpmDiscoverer extends ProjectDiscoverer {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
async getDependencyGraph() {
|
|
100
|
-
if (!this.#
|
|
101
|
+
if (!this.#projectRoot) {
|
|
102
|
+
return { nodes: [], edges: [] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 直接用 PackageSwiftParser 构建依赖图,不依赖 SpmHelper
|
|
106
|
+
const parser = new PackageSwiftParser(this.#projectRoot);
|
|
107
|
+
const allPkgPaths = parser.findAllPackageSwifts(this.#projectRoot);
|
|
108
|
+
|
|
109
|
+
if (allPkgPaths.length === 0) {
|
|
101
110
|
return { nodes: [], edges: [] };
|
|
102
111
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
|
|
113
|
+
const nodes = [];
|
|
114
|
+
const edges = [];
|
|
115
|
+
const pkgNameSet = new Set();
|
|
116
|
+
// targetName → 所属 packageName 映射(用于跨包 target 依赖解析)
|
|
117
|
+
const targetToPkg = new Map();
|
|
118
|
+
|
|
119
|
+
// ── 第一遍:收集所有 package + target 节点 ──
|
|
120
|
+
const allParsed = [];
|
|
121
|
+
// 记录 umbrella 包名(无 targets 且无 products 的纯组织性入口包),不作为图节点
|
|
122
|
+
const umbrellaNames = new Set();
|
|
123
|
+
for (const pkgPath of allPkgPaths) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = parser.parse(pkgPath);
|
|
126
|
+
if (pkgNameSet.has(parsed.name)) continue;
|
|
127
|
+
pkgNameSet.add(parsed.name);
|
|
128
|
+
allParsed.push({ ...parsed, _dir: dirname(pkgPath) });
|
|
129
|
+
|
|
130
|
+
// 跳过 umbrella 包(无 targets + 无 products)——它只是组织子包的入口
|
|
131
|
+
const hasTargets = parsed.targets && parsed.targets.length > 0;
|
|
132
|
+
const hasProducts = parsed.products && parsed.products.length > 0;
|
|
133
|
+
if (!hasTargets && !hasProducts) {
|
|
134
|
+
umbrellaNames.add(parsed.name);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// package 节点
|
|
139
|
+
nodes.push({
|
|
140
|
+
id: parsed.name,
|
|
141
|
+
label: parsed.name,
|
|
142
|
+
type: 'package',
|
|
143
|
+
fullPath: dirname(pkgPath),
|
|
144
|
+
targetCount: parsed.targets.length,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// target 节点
|
|
148
|
+
for (const t of parsed.targets) {
|
|
149
|
+
nodes.push({
|
|
150
|
+
id: t.name,
|
|
151
|
+
label: t.name,
|
|
152
|
+
type: 'target',
|
|
153
|
+
parent: parsed.name,
|
|
154
|
+
targetType: t.type,
|
|
155
|
+
});
|
|
156
|
+
targetToPkg.set(t.name, parsed.name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// product name → package(product 名可能和 target 名不同)
|
|
160
|
+
for (const prod of parsed.products || []) {
|
|
161
|
+
if (!targetToPkg.has(prod.name)) {
|
|
162
|
+
targetToPkg.set(prod.name, parsed.name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// 解析失败,跳过
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── 第二遍:构建 edges ──
|
|
171
|
+
for (const parsed of allParsed) {
|
|
172
|
+
// 跳过 umbrella 包的边
|
|
173
|
+
if (umbrellaNames.has(parsed.name)) continue;
|
|
174
|
+
|
|
175
|
+
// 包级 local path 依赖
|
|
176
|
+
for (const dep of parsed.dependencies || []) {
|
|
177
|
+
if (dep.type === 'local' && dep.path) {
|
|
178
|
+
const depPkgSwift = join(parsed._dir, dep.path, 'Package.swift');
|
|
179
|
+
if (existsSync(depPkgSwift)) {
|
|
180
|
+
try {
|
|
181
|
+
const depParsed = parser.parse(depPkgSwift);
|
|
182
|
+
// 跳过指向 umbrella 包的边
|
|
183
|
+
if (!umbrellaNames.has(depParsed.name)) {
|
|
184
|
+
edges.push({ from: parsed.name, to: depParsed.name, type: 'depends_on' });
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
const targetName = basename(dep.path);
|
|
188
|
+
if (!umbrellaNames.has(targetName)) {
|
|
189
|
+
edges.push({ from: parsed.name, to: targetName, type: 'depends_on' });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else if (dep.url) {
|
|
194
|
+
const remoteName = basename(dep.url).replace(/\.git$/, '');
|
|
195
|
+
if (!pkgNameSet.has(remoteName)) {
|
|
196
|
+
pkgNameSet.add(remoteName);
|
|
197
|
+
nodes.push({ id: remoteName, label: remoteName, type: 'remote', indirect: true });
|
|
198
|
+
}
|
|
199
|
+
edges.push({ from: parsed.name, to: remoteName, type: 'depends_on' });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// target 级依赖
|
|
204
|
+
for (const t of parsed.targets || []) {
|
|
205
|
+
// target → parent package (contains)
|
|
206
|
+
edges.push({ from: parsed.name, to: t.name, type: 'contains' });
|
|
207
|
+
|
|
208
|
+
for (const depName of t.dependencies || []) {
|
|
209
|
+
// target → target 依赖(跳过指向 umbrella 包的)
|
|
210
|
+
if (!umbrellaNames.has(depName)) {
|
|
211
|
+
edges.push({ from: t.name, to: depName, type: 'depends_on' });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { nodes, edges };
|
|
112
218
|
}
|
|
113
219
|
|
|
114
|
-
/** 获取底层
|
|
220
|
+
/** 获取底层 SpmHelper(向后兼容) */
|
|
115
221
|
getSpmService() {
|
|
116
222
|
return this.#spm;
|
|
117
223
|
}
|