autosnippet 2.8.3 → 2.10.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/README.md +5 -5
- package/bin/cli.js +5 -33
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
- package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
- package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +13 -11
- package/lib/cli/KnowledgeSyncService.js +343 -0
- package/lib/cli/SetupService.js +9 -27
- package/lib/core/ast/ProjectGraph.js +160 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +109 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +125 -0
- package/lib/domain/knowledge/values/Content.js +86 -0
- package/lib/domain/knowledge/values/Quality.js +93 -0
- package/lib/domain/knowledge/values/Reasoning.js +69 -0
- package/lib/domain/knowledge/values/Relations.js +168 -0
- package/lib/domain/knowledge/values/Stats.js +87 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +48 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
- package/lib/external/mcp/handlers/bootstrap.js +121 -12
- package/lib/external/mcp/handlers/browse.js +77 -73
- package/lib/external/mcp/handlers/candidate.js +29 -276
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +205 -0
- package/lib/external/mcp/handlers/skill.js +4 -2
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +125 -138
- package/lib/http/HttpServer.js +4 -8
- package/lib/http/middleware/requestLogger.js +3 -3
- package/lib/http/routes/ai.js +17 -1
- package/lib/http/routes/extract.js +48 -4
- package/lib/http/routes/knowledge.js +246 -0
- package/lib/http/routes/search.js +12 -17
- package/lib/http/routes/skills.js +44 -1
- package/lib/infrastructure/cache/GraphCache.js +143 -0
- package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/infrastructure/realtime/RealtimeService.js +14 -2
- package/lib/injection/ServiceContainer.js +164 -63
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
- package/lib/repository/token/TokenUsageStore.js +162 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +67 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +6 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -18
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +35 -17
- package/lib/service/chat/AnalystAgent.js +25 -14
- package/lib/service/chat/CandidateGuardrail.js +1 -1
- package/lib/service/chat/ChatAgent.js +280 -48
- package/lib/service/chat/ContextWindow.js +92 -8
- package/lib/service/chat/HandoffProtocol.js +26 -1
- package/lib/service/chat/ProducerAgent.js +11 -9
- package/lib/service/chat/tools.js +298 -194
- package/lib/service/guard/GuardCheckEngine.js +114 -10
- package/lib/service/guard/GuardService.js +59 -48
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
- package/lib/service/knowledge/KnowledgeService.js +725 -0
- package/lib/service/search/SearchEngine.js +92 -19
- package/lib/service/skills/SignalCollector.js +15 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +119 -18
- package/package.json +1 -1
- package/scripts/install-cursor-skill.js +0 -6
- package/scripts/migrate-md-to-knowledge.mjs +364 -0
- package/skills/autosnippet-analysis/SKILL.md +15 -7
- package/skills/autosnippet-candidates/SKILL.md +6 -6
- package/skills/autosnippet-coldstart/SKILL.md +7 -3
- package/skills/autosnippet-concepts/SKILL.md +7 -6
- package/skills/autosnippet-create/SKILL.md +13 -13
- package/skills/autosnippet-intent/SKILL.md +3 -2
- package/skills/autosnippet-lifecycle/SKILL.md +5 -5
- package/skills/autosnippet-recipes/SKILL.md +16 -4
- package/templates/constitution.yaml +1 -1
- package/templates/copilot-instructions.md +6 -6
- package/templates/recipes-setup/README.md +3 -3
- package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
- package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
- package/lib/cli/CandidateSyncService.js +0 -261
- package/lib/cli/SyncService.js +0 -356
- package/lib/domain/candidate/Candidate.js +0 -196
- package/lib/domain/candidate/CandidateRepository.js +0 -107
- package/lib/domain/candidate/Reasoning.js +0 -52
- package/lib/domain/recipe/Recipe.js +0 -421
- package/lib/domain/recipe/RecipeRepository.js +0 -54
- package/lib/domain/types/CandidateStatus.js +0 -52
- package/lib/http/routes/candidates.js +0 -559
- package/lib/http/routes/recipes.js +0 -397
- package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
- package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
- package/lib/service/candidate/CandidateAggregator.js +0 -52
- package/lib/service/candidate/CandidateFileWriter.js +0 -383
- package/lib/service/candidate/CandidateService.js +0 -973
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- package/lib/service/recipe/RecipeStatsTracker.js +0 -148
|
@@ -3,11 +3,15 @@ import Logger from '../infrastructure/logging/Logger.js';
|
|
|
3
3
|
import AuditStore from '../infrastructure/audit/AuditStore.js';
|
|
4
4
|
import AuditLogger from '../infrastructure/audit/AuditLogger.js';
|
|
5
5
|
import Gateway from '../core/gateway/Gateway.js';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
7
|
+
import { join as pathJoin, relative as pathRelative, extname as pathExtname } from 'node:path';
|
|
8
|
+
|
|
9
|
+
import { KnowledgeRepositoryImpl } from '../repository/knowledge/KnowledgeRepository.impl.js';
|
|
10
|
+
import { KnowledgeFileWriter } from '../service/knowledge/KnowledgeFileWriter.js';
|
|
11
|
+
import { KnowledgeSyncService } from '../cli/KnowledgeSyncService.js';
|
|
12
|
+
import { KnowledgeService } from '../service/knowledge/KnowledgeService.js';
|
|
13
|
+
import { ConfidenceRouter } from '../service/knowledge/ConfidenceRouter.js';
|
|
9
14
|
import { SnippetFactory } from '../service/snippet/SnippetFactory.js';
|
|
10
|
-
import { RecipeService } from '../service/recipe/RecipeService.js';
|
|
11
15
|
import { GuardService } from '../service/guard/GuardService.js';
|
|
12
16
|
import { KnowledgeGraphService } from '../service/knowledge/KnowledgeGraphService.js';
|
|
13
17
|
import { SearchEngine } from '../service/search/SearchEngine.js';
|
|
@@ -20,13 +24,9 @@ import { RetrievalFunnel } from '../service/search/RetrievalFunnel.js';
|
|
|
20
24
|
import { JsonVectorAdapter } from '../infrastructure/vector/JsonVectorAdapter.js';
|
|
21
25
|
import { IndexingPipeline } from '../infrastructure/vector/IndexingPipeline.js';
|
|
22
26
|
|
|
23
|
-
// ─── P1: Injection / Snippet
|
|
27
|
+
// ─── P1: Injection / Snippet ─────────────────
|
|
24
28
|
import { RecipeParser } from '../service/recipe/RecipeParser.js';
|
|
25
|
-
import { RecipeStatsTracker } from '../service/recipe/RecipeStatsTracker.js';
|
|
26
29
|
import { RecipeCandidateValidator } from '../service/recipe/RecipeCandidateValidator.js';
|
|
27
|
-
import { RecipeFileWriter } from '../service/recipe/RecipeFileWriter.js';
|
|
28
|
-
import { CandidateFileWriter } from '../service/candidate/CandidateFileWriter.js';
|
|
29
|
-
import { CandidateService } from '../service/candidate/CandidateService.js';
|
|
30
30
|
import { SnippetInstaller } from '../service/snippet/SnippetInstaller.js';
|
|
31
31
|
|
|
32
32
|
// ─── P1: Guard Advanced ──────────────────────────────
|
|
@@ -34,6 +34,9 @@ import { ExclusionManager } from '../service/guard/ExclusionManager.js';
|
|
|
34
34
|
import { RuleLearner } from '../service/guard/RuleLearner.js';
|
|
35
35
|
import { ViolationsStore } from '../service/guard/ViolationsStore.js';
|
|
36
36
|
|
|
37
|
+
// ─── P1: Token Usage Tracking ─────────────────────────
|
|
38
|
+
import { TokenUsageStore } from '../repository/token/TokenUsageStore.js';
|
|
39
|
+
|
|
37
40
|
// ─── P2: Quality ──────────────────────────────────────
|
|
38
41
|
import { QualityScorer } from '../service/quality/QualityScorer.js';
|
|
39
42
|
import { FeedbackCollector } from '../service/quality/FeedbackCollector.js';
|
|
@@ -53,6 +56,7 @@ import { SkillHooks } from '../service/skills/SkillHooks.js';
|
|
|
53
56
|
|
|
54
57
|
// ─── v3.0: AST ProjectGraph ──────────────────────────
|
|
55
58
|
import ProjectGraph from '../core/ast/ProjectGraph.js';
|
|
59
|
+
import { GraphCache } from '../infrastructure/cache/GraphCache.js';
|
|
56
60
|
|
|
57
61
|
// ─── P3: Infrastructure ──────────────────────────────
|
|
58
62
|
import { EventBus } from '../infrastructure/event/EventBus.js';
|
|
@@ -232,22 +236,31 @@ export class ServiceContainer {
|
|
|
232
236
|
* 注册仓储
|
|
233
237
|
*/
|
|
234
238
|
_registerRepositories() {
|
|
235
|
-
//
|
|
236
|
-
this.register('
|
|
237
|
-
if (!this.singletons.
|
|
239
|
+
// KnowledgeRepository (统一知识实体)
|
|
240
|
+
this.register('knowledgeRepository', () => {
|
|
241
|
+
if (!this.singletons.knowledgeRepository) {
|
|
238
242
|
const database = this.get('database');
|
|
239
|
-
this.singletons.
|
|
243
|
+
this.singletons.knowledgeRepository = new KnowledgeRepositoryImpl(database);
|
|
240
244
|
}
|
|
241
|
-
return this.singletons.
|
|
245
|
+
return this.singletons.knowledgeRepository;
|
|
242
246
|
});
|
|
243
247
|
|
|
244
|
-
//
|
|
245
|
-
this.register('
|
|
246
|
-
if (!this.singletons.
|
|
247
|
-
const
|
|
248
|
-
this.singletons.
|
|
248
|
+
// KnowledgeFileWriter (统一 .md 序列化/落盘)
|
|
249
|
+
this.register('knowledgeFileWriter', () => {
|
|
250
|
+
if (!this.singletons.knowledgeFileWriter) {
|
|
251
|
+
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
252
|
+
this.singletons.knowledgeFileWriter = new KnowledgeFileWriter(projectRoot);
|
|
253
|
+
}
|
|
254
|
+
return this.singletons.knowledgeFileWriter;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// KnowledgeSyncService (统一 .md ↔ DB 同步)
|
|
258
|
+
this.register('knowledgeSyncService', () => {
|
|
259
|
+
if (!this.singletons.knowledgeSyncService) {
|
|
260
|
+
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
261
|
+
this.singletons.knowledgeSyncService = new KnowledgeSyncService(projectRoot);
|
|
249
262
|
}
|
|
250
|
-
return this.singletons.
|
|
263
|
+
return this.singletons.knowledgeSyncService;
|
|
251
264
|
});
|
|
252
265
|
}
|
|
253
266
|
|
|
@@ -255,54 +268,45 @@ export class ServiceContainer {
|
|
|
255
268
|
* 注册服务
|
|
256
269
|
*/
|
|
257
270
|
_registerServices() {
|
|
258
|
-
//
|
|
259
|
-
this.register('
|
|
260
|
-
if (!this.singletons.
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const gateway = this.get('gateway');
|
|
264
|
-
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
265
|
-
const candidateFileWriter = new CandidateFileWriter(projectRoot);
|
|
266
|
-
const skillHooks = this.get('skillHooks');
|
|
267
|
-
this.singletons.candidateService = new CandidateService(
|
|
268
|
-
candidateRepository,
|
|
269
|
-
auditLogger,
|
|
270
|
-
gateway,
|
|
271
|
-
{ fileWriter: candidateFileWriter, skillHooks }
|
|
272
|
-
);
|
|
271
|
+
// ConfidenceRouter (V3: 知识条目自动审核路由)
|
|
272
|
+
this.register('confidenceRouter', () => {
|
|
273
|
+
if (!this.singletons.confidenceRouter) {
|
|
274
|
+
const qualityScorer = this.get('qualityScorer');
|
|
275
|
+
this.singletons.confidenceRouter = new ConfidenceRouter({}, qualityScorer);
|
|
273
276
|
}
|
|
274
|
-
return this.singletons.
|
|
277
|
+
return this.singletons.confidenceRouter;
|
|
275
278
|
});
|
|
276
279
|
|
|
277
|
-
//
|
|
278
|
-
this.register('
|
|
279
|
-
if (!this.singletons.
|
|
280
|
-
const
|
|
280
|
+
// KnowledgeService (V3: 统一知识服务)
|
|
281
|
+
this.register('knowledgeService', () => {
|
|
282
|
+
if (!this.singletons.knowledgeService) {
|
|
283
|
+
const knowledgeRepository = this.get('knowledgeRepository');
|
|
281
284
|
const auditLogger = this.get('auditLogger');
|
|
282
285
|
const gateway = this.get('gateway');
|
|
283
286
|
const knowledgeGraphService = this.get('knowledgeGraphService');
|
|
284
|
-
const
|
|
285
|
-
const fileWriter = new RecipeFileWriter(projectRoot);
|
|
287
|
+
const fileWriter = this.get('knowledgeFileWriter');
|
|
286
288
|
const skillHooks = this.get('skillHooks');
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
const confidenceRouter = this.get('confidenceRouter');
|
|
290
|
+
const qualityScorer = this.get('qualityScorer');
|
|
291
|
+
this.singletons.knowledgeService = new KnowledgeService(
|
|
292
|
+
knowledgeRepository,
|
|
289
293
|
auditLogger,
|
|
290
294
|
gateway,
|
|
291
295
|
knowledgeGraphService,
|
|
292
|
-
{ fileWriter, skillHooks }
|
|
296
|
+
{ fileWriter, skillHooks, confidenceRouter, qualityScorer }
|
|
293
297
|
);
|
|
294
298
|
}
|
|
295
|
-
return this.singletons.
|
|
299
|
+
return this.singletons.knowledgeService;
|
|
296
300
|
});
|
|
297
301
|
|
|
298
|
-
// GuardService (
|
|
302
|
+
// GuardService (V3: uses knowledgeRepository)
|
|
299
303
|
this.register('guardService', () => {
|
|
300
304
|
if (!this.singletons.guardService) {
|
|
301
|
-
const
|
|
305
|
+
const knowledgeRepository = this.get('knowledgeRepository');
|
|
302
306
|
const auditLogger = this.get('auditLogger');
|
|
303
307
|
const gateway = this.get('gateway');
|
|
304
308
|
this.singletons.guardService = new GuardService(
|
|
305
|
-
|
|
309
|
+
knowledgeRepository,
|
|
306
310
|
auditLogger,
|
|
307
311
|
gateway
|
|
308
312
|
);
|
|
@@ -388,15 +392,6 @@ export class ServiceContainer {
|
|
|
388
392
|
return this.singletons.recipeParser;
|
|
389
393
|
});
|
|
390
394
|
|
|
391
|
-
// RecipeStatsTracker
|
|
392
|
-
this.register('recipeStatsTracker', () => {
|
|
393
|
-
if (!this.singletons.recipeStatsTracker) {
|
|
394
|
-
const projectRoot = this.singletons._projectRoot || process.cwd();
|
|
395
|
-
this.singletons.recipeStatsTracker = new RecipeStatsTracker(projectRoot);
|
|
396
|
-
}
|
|
397
|
-
return this.singletons.recipeStatsTracker;
|
|
398
|
-
});
|
|
399
|
-
|
|
400
395
|
// RecipeCandidateValidator
|
|
401
396
|
this.register('recipeCandidateValidator', () => {
|
|
402
397
|
if (!this.singletons.recipeCandidateValidator) {
|
|
@@ -405,11 +400,11 @@ export class ServiceContainer {
|
|
|
405
400
|
return this.singletons.recipeCandidateValidator;
|
|
406
401
|
});
|
|
407
402
|
|
|
408
|
-
// SnippetFactory (
|
|
403
|
+
// SnippetFactory (V3: uses knowledgeRepository)
|
|
409
404
|
this.register('snippetFactory', () => {
|
|
410
405
|
if (!this.singletons.snippetFactory) {
|
|
411
|
-
const
|
|
412
|
-
this.singletons.snippetFactory = new SnippetFactory(
|
|
406
|
+
const knowledgeRepo = this.get('knowledgeRepository');
|
|
407
|
+
this.singletons.snippetFactory = new SnippetFactory(knowledgeRepo);
|
|
413
408
|
}
|
|
414
409
|
return this.singletons.snippetFactory;
|
|
415
410
|
});
|
|
@@ -450,6 +445,15 @@ export class ServiceContainer {
|
|
|
450
445
|
return this.singletons.violationsStore;
|
|
451
446
|
});
|
|
452
447
|
|
|
448
|
+
// Token Usage: 持久化 AI token 消耗
|
|
449
|
+
this.register('tokenUsageStore', () => {
|
|
450
|
+
if (!this.singletons.tokenUsageStore) {
|
|
451
|
+
const db = this.get('database').getDb();
|
|
452
|
+
this.singletons.tokenUsageStore = new TokenUsageStore(db);
|
|
453
|
+
}
|
|
454
|
+
return this.singletons.tokenUsageStore;
|
|
455
|
+
});
|
|
456
|
+
|
|
453
457
|
// QualityScorer
|
|
454
458
|
this.register('qualityScorer', () => {
|
|
455
459
|
if (!this.singletons.qualityScorer) {
|
|
@@ -566,7 +570,7 @@ export class ServiceContainer {
|
|
|
566
570
|
|
|
567
571
|
/**
|
|
568
572
|
* 构建 ProjectGraph (v3.0 AST 结构图)
|
|
569
|
-
*
|
|
573
|
+
* 优先从磁盘缓存加载,支持 per-file hash 增量更新
|
|
570
574
|
* @param {string} projectRoot 项目根目录
|
|
571
575
|
* @param {object} [options] 传递给 ProjectGraph.build() 的选项
|
|
572
576
|
* @returns {Promise<import('../core/ast/ProjectGraph.js').default|null>}
|
|
@@ -575,14 +579,70 @@ export class ServiceContainer {
|
|
|
575
579
|
if (this.singletons.projectGraph) {
|
|
576
580
|
return this.singletons.projectGraph;
|
|
577
581
|
}
|
|
582
|
+
|
|
583
|
+
const cache = new GraphCache(projectRoot);
|
|
584
|
+
const startTime = Date.now();
|
|
585
|
+
|
|
578
586
|
try {
|
|
587
|
+
// ── 尝试从缓存恢复 + 增量更新 ──
|
|
588
|
+
const cached = cache.load('project-graph');
|
|
589
|
+
if (cached?.data && cached.fileHashes) {
|
|
590
|
+
const graph = ProjectGraph.fromJSON(cached.data);
|
|
591
|
+
const currentFiles = this.#collectSourceFilePaths(projectRoot, options);
|
|
592
|
+
const oldHashes = cached.fileHashes || {};
|
|
593
|
+
|
|
594
|
+
// 计算差异:新增 / 变更 / 删除
|
|
595
|
+
const changedPaths = [];
|
|
596
|
+
const newHashes = {};
|
|
597
|
+
for (const fp of currentFiles) {
|
|
598
|
+
const rel = pathRelative(projectRoot, fp);
|
|
599
|
+
const h = cache.computeFileHash(fp);
|
|
600
|
+
newHashes[rel] = h;
|
|
601
|
+
if (!oldHashes[rel] || oldHashes[rel] !== h) {
|
|
602
|
+
changedPaths.push(fp);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const deletedPaths = Object.keys(oldHashes).filter(rel => !newHashes[rel]);
|
|
606
|
+
|
|
607
|
+
if (changedPaths.length === 0 && deletedPaths.length === 0) {
|
|
608
|
+
// 完全命中
|
|
609
|
+
this.singletons.projectGraph = graph;
|
|
610
|
+
this.logger.info(
|
|
611
|
+
`[ServiceContainer] ProjectGraph ⚡ 缓存命中 (${graph.getOverview().totalClasses} classes, ` +
|
|
612
|
+
`${Date.now() - startTime}ms)`
|
|
613
|
+
);
|
|
614
|
+
return graph;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 增量更新
|
|
618
|
+
const diff = await graph.incrementalUpdate(changedPaths, deletedPaths, options);
|
|
619
|
+
this.singletons.projectGraph = graph;
|
|
620
|
+
|
|
621
|
+
// 写回缓存
|
|
622
|
+
cache.save('project-graph', graph.toJSON(), { fileHashes: newHashes });
|
|
623
|
+
|
|
624
|
+
const overview = graph.getOverview();
|
|
625
|
+
this.logger.info(
|
|
626
|
+
`[ServiceContainer] ProjectGraph 增量更新: +${diff.added} ~${diff.updated} -${diff.deleted} ` +
|
|
627
|
+
`(${overview.totalClasses} classes, ${Date.now() - startTime}ms)`
|
|
628
|
+
);
|
|
629
|
+
return graph;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ── 无缓存,全量构建 ──
|
|
579
633
|
const graph = await ProjectGraph.build(projectRoot, options);
|
|
580
634
|
this.singletons.projectGraph = graph;
|
|
581
635
|
const overview = graph.getOverview();
|
|
636
|
+
|
|
637
|
+
// 计算文件 hash 并写入缓存
|
|
638
|
+
const currentFiles = this.#collectSourceFilePaths(projectRoot, options);
|
|
639
|
+
const fileHashes = cache.computeFileHashes(currentFiles, projectRoot);
|
|
640
|
+
cache.save('project-graph', graph.toJSON(), { fileHashes });
|
|
641
|
+
|
|
582
642
|
this.logger.info(
|
|
583
643
|
`[ServiceContainer] ProjectGraph built: ${overview.totalClasses} classes, ` +
|
|
584
644
|
`${overview.totalProtocols} protocols, ${overview.totalCategories} categories ` +
|
|
585
|
-
`(${overview.buildTimeMs}ms)
|
|
645
|
+
`(${overview.buildTimeMs}ms) — 缓存已写入`
|
|
586
646
|
);
|
|
587
647
|
return graph;
|
|
588
648
|
} catch (err) {
|
|
@@ -590,6 +650,47 @@ export class ServiceContainer {
|
|
|
590
650
|
return null;
|
|
591
651
|
}
|
|
592
652
|
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* 收集项目源码文件路径(用于 hash 计算)
|
|
656
|
+
* @param {string} projectRoot
|
|
657
|
+
* @param {object} options
|
|
658
|
+
* @returns {string[]}
|
|
659
|
+
*/
|
|
660
|
+
#collectSourceFilePaths(projectRoot, options = {}) {
|
|
661
|
+
const DEFAULTS_EXT = { '.m': true, '.h': true, '.swift': true };
|
|
662
|
+
const extSet = new Set(options.extensions || Object.keys(DEFAULTS_EXT));
|
|
663
|
+
const excludePatterns = options.excludePatterns || [
|
|
664
|
+
'Pods/', 'Carthage/', 'node_modules/', '.build/', 'build/',
|
|
665
|
+
'DerivedData/', 'vendor/', '.git/', '__tests__/', 'Tests/',
|
|
666
|
+
];
|
|
667
|
+
const maxFiles = options.maxFiles || 500;
|
|
668
|
+
const maxFileSizeBytes = options.maxFileSizeBytes || 500_000;
|
|
669
|
+
const results = [];
|
|
670
|
+
|
|
671
|
+
function walk(dir) {
|
|
672
|
+
if (results.length >= maxFiles) return;
|
|
673
|
+
let entries;
|
|
674
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
675
|
+
for (const entry of entries) {
|
|
676
|
+
if (results.length >= maxFiles) return;
|
|
677
|
+
const fullPath = pathJoin(dir, entry.name);
|
|
678
|
+
const relativePath = pathRelative(projectRoot, fullPath);
|
|
679
|
+
if (excludePatterns.some(p => relativePath.includes(p))) continue;
|
|
680
|
+
if (entry.isDirectory()) {
|
|
681
|
+
walk(fullPath);
|
|
682
|
+
} else if (entry.isFile() && extSet.has(pathExtname(entry.name))) {
|
|
683
|
+
try {
|
|
684
|
+
const stat = statSync(fullPath);
|
|
685
|
+
if (stat.size <= maxFileSizeBytes) results.push(fullPath);
|
|
686
|
+
} catch { /* skip */ }
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
walk(projectRoot);
|
|
692
|
+
return results;
|
|
693
|
+
}
|
|
593
694
|
}
|
|
594
695
|
|
|
595
696
|
let containerInstance = null;
|