autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
412 lines (411 loc) • 18.3 kB
JavaScript
import { readdirSync, statSync } from 'node:fs';
import { extname as pathExtname, join as pathJoin, relative as pathRelative } from 'node:path';
import { resolveProjectRoot } from '#shared/resolveProjectRoot.js';
// ─── v3.0: AST ProjectGraph ──────────────────────────
import ProjectGraph from '../core/ast/ProjectGraph.js';
// ─── v3.1: Multi-Language Discovery + Enhancement ────────
import { initEnhancementRegistry } from '../core/enhancement/index.js';
import { CacheCoordinator } from '../infrastructure/cache/CacheCoordinator.js';
import { GraphCache } from '../infrastructure/cache/GraphCache.js';
// ─── P3: Infrastructure ──────────────────────────────
import Logger from '../infrastructure/logging/Logger.js';
import { unwrapRawDb } from '../repository/search/SearchRepoAdapter.js';
import * as AgentModule from './modules/AgentModule.js';
import * as AiModule from './modules/AiModule.js';
import * as AppModule from './modules/AppModule.js';
import * as GuardModule from './modules/GuardModule.js';
// ─── DI Modules ──────────────────────────────────────
import * as InfraModule from './modules/InfraModule.js';
import * as KnowledgeModule from './modules/KnowledgeModule.js';
import { PanoramaModule } from './modules/PanoramaModule.js';
import * as SignalModule from './modules/SignalModule.js';
import * as VectorModule from './modules/VectorModule.js';
/**
* DependencyInjection 容器
* 管理所有应用层的仓储、服务和基础设施依赖的创建和注入
*/
export class ServiceContainer {
logger;
_aiDependentSingletons = [];
services;
singletons;
constructor() {
this.services = {};
this.singletons = {};
this.logger = Logger.getInstance();
}
// ─── 通用注册方法 ──────────────────────────────────
/**
* 注册一个惰性单例服务 — 消除 `if (!this.singletons.xxx)` 样板代码
*
* @param name 服务名称
* @param factory 工厂函数(首次 get 时执行)
* @param [options] 选项
* - aiDependent: 标记为 AI Provider 依赖项,热重载时自动清除缓存
*/
singleton(name, factory, options = {}) {
if (options.aiDependent) {
this._aiDependentSingletons = this._aiDependentSingletons || [];
if (!this._aiDependentSingletons.includes(name)) {
this._aiDependentSingletons.push(name);
}
}
this.register(name, () => {
if (!this.singletons[name]) {
this.singletons[name] = factory(this);
}
return this.singletons[name];
});
}
/** 静态单例获取(路由层使用) */
static getInstance() {
return getServiceContainer();
}
/**
* 初始化所有服务和仓储
* @param bootstrapComponents Bootstrap 初始化的组件(db, auditLogger, gateway 等)
*/
async initialize(bootstrapComponents = {}) {
try {
// ── 多项目防护:禁止同一进程内切换项目 ──
const newRoot = bootstrapComponents.projectRoot;
const existingRoot = this.singletons._projectRoot;
if (newRoot && existingRoot && newRoot !== existingRoot) {
throw new Error(`[ServiceContainer] 不允许在同一进程中切换项目。` +
`当前绑定: ${existingRoot}, 请求: ${newRoot}。` +
`请为每个项目启动独立进程。`);
}
// 如果提供了 bootstrap 组件,将它们注入到单例缓存中
if (bootstrapComponents.db) {
this.singletons.database = bootstrapComponents.db;
}
if (bootstrapComponents.auditLogger) {
this.singletons.auditLogger = bootstrapComponents.auditLogger;
}
if (bootstrapComponents.gateway) {
this.singletons.gateway = bootstrapComponents.gateway;
}
if (bootstrapComponents.constitution) {
this.singletons.constitution = bootstrapComponents.constitution;
}
if (bootstrapComponents.projectRoot) {
this.singletons._projectRoot = bootstrapComponents.projectRoot;
}
if (bootstrapComponents.config) {
this.singletons._config = bootstrapComponents.config;
}
if (bootstrapComponents.skillHooks) {
this.singletons.skillHooks = bootstrapComponents.skillHooks;
}
// ═══ AI Provider 初始化(委托 AiModule)═══
await AiModule.initialize(this);
// RecipeExtractor 实例(用于工具增强)
AppModule.initRecipeExtractor(this);
// 注册所有模块 (替代 _registerInfrastructure / _registerRepositories / _registerServices)
InfraModule.register(this);
// ═══ AI Provider 热重载标记 ═══
// 哪些 singleton key 持有 aiProvider 引用,在 reloadAiProvider() 时需要清除
// 由各 Module 通过 singleton(name, factory, { aiDependent: true }) 自动注册
// 预初始化为空数组,确保模块注册前已就绪
this._aiDependentSingletons = this._aiDependentSingletons || [];
// ═══ 容器级语言偏好 ═══
this.singletons._lang = null;
// 注册模块 (顺序重要: AppModule 先注册 qualityScorer 等基础服务)
// SignalModule 优先注册并预热,确保后续模块可访问 signalBus
SignalModule.register(this);
this.get('signalBus'); // eager: 确保 singletons.signalBus 在后续工厂可用
AppModule.register(this);
KnowledgeModule.register(this);
VectorModule.register(this);
GuardModule.register(this);
AgentModule.register(this);
AiModule.register(this);
PanoramaModule.register(this);
// v3.1: 初始化 Enhancement Pack 注册表(异步加载所有框架增强包)
try {
await initEnhancementRegistry();
}
catch (e) {
this.logger.warn('Enhancement registry init failed (non-blocking)', {
error: e.message,
});
}
// v3.3: 初始化 VectorService(绑定 EventBus 事件监听)
await VectorModule.initializeVectorService(this);
// v3.4: 初始化 Knowledge 服务(绑定 EventBus → SearchEngine.refreshIndex + sourceRefs)
KnowledgeModule.initializeKnowledgeServices(this);
// v3.5: 跨进程缓存协调器(利用 SQLite PRAGMA data_version 检测其他进程写入)
this.#initCacheCoordinator();
this.logger.info('Service container initialized successfully');
}
catch (error) {
this.logger.error('Error initializing service container', {
error: error.message,
});
throw error;
}
}
/**
* 热重载 AI Provider(API Key 变更后调用,无需重启进程)
*
* 委托给 AiProviderManager.switchProvider() — 原子操作:
* 1. 替换 provider 引用 + DI 数据管道同步
* 2. Token 追踪 AOP 重新挂载
* 3. Embedding fallback 重建
* 4. 清除已缓存的依赖 AI 的 singleton(SearchEngine 等)
* 5. 监听器回调通知
*/
reloadAiProvider(newProvider) {
if (!newProvider) {
this.logger.warn('[ServiceContainer] reloadAiProvider called with null — ignored');
return;
}
const manager = this.singletons._aiProviderManager;
manager.switchProvider(newProvider);
}
// ─── 跨进程缓存协调 ─────
/**
* 初始化 CacheCoordinator:当其他进程写入 DB 后,自动清除本进程的内存缓存。
*
* 订阅的服务:
* - panoramaService: invalidate() — 模块图 + 全景分析
* - guardCheckEngine: clearCache() — 规则缓存
* - searchEngine: buildIndex() — 搜索索引
*
* 仅在长驻进程(HTTP server / MCP server)中自动启动轮询。
* CLI 场景无需启动(进程生命周期短,缓存不会过时)。
*/
#initCacheCoordinator() {
try {
const db = this.singletons.database;
const rawDb = db
? unwrapRawDb(db)
: null;
if (!rawDb) {
return;
}
const coordinator = new CacheCoordinator(rawDb);
this.singletons.cacheCoordinator = coordinator;
this.register('cacheCoordinator', () => coordinator);
// 懒订阅:仅在对应服务已初始化时绑定
coordinator.subscribe('panoramaService', () => {
const svc = this.singletons.panoramaService;
svc?.invalidate?.();
});
coordinator.subscribe('guardCheckEngine', () => {
const svc = this.singletons.guardCheckEngine;
svc?.clearCache?.();
});
coordinator.subscribe('searchEngine', () => {
const svc = this.singletons.searchEngine;
svc?.buildIndex?.();
});
// 长驻进程自动启动轮询(CLI 不启动)
const isMcp = process.env.ASD_MCP_MODE === '1';
const isApiServer = process.env.ASD_API_SERVER === '1';
if (isMcp || isApiServer) {
coordinator.start();
}
this.logger.info('CacheCoordinator initialized', {
subscribers: coordinator.subscriberCount,
polling: isMcp || isApiServer,
});
}
catch (err) {
this.logger.warn('CacheCoordinator init failed (non-blocking)', {
error: err.message,
});
}
}
// ─── 容器级语言偏好 ─────
/** 获取当前默认 UI 语言偏好 */
getLang() {
return this.singletons._lang || null;
}
/** 设置默认 UI 语言偏好(影响 Agent 回复语言) */
setLang(lang) {
this.singletons._lang = lang || null;
}
// ─── 工具执行上下文构建器 ─────────────────────
/**
* 构建 ToolRegistry.execute() 所需的上下文对象。
*
* 工具执行上下文构建
* 迁移后: 所有直接调用 ToolRegistry 的站点都使用此方法
*
* @param [extras] 合并到上下文的额外字段
* @returns 工具执行上下文
*/
buildToolContext(extras = {}) {
return {
container: this,
aiProvider: this.singletons.aiProvider || null,
projectRoot: resolveProjectRoot(this),
logger: this.logger,
source: extras.source || 'system',
lang: extras.lang || this.singletons._lang || null,
fileCache: this.singletons._fileCache || null,
...extras,
};
}
/** 注册服务或工厂函数 */
register(name, factory) {
if (this.services[name] && process.env.NODE_ENV !== 'production') {
this.logger.warn(`[ServiceContainer] 服务 "${name}" 被重复注册,前一个工厂将被覆盖`);
}
this.services[name] = factory;
}
get(name) {
if (!this.services[name]) {
throw new Error(`Service '${name}' not found in container`);
}
return this.services[name]();
}
/** 清除所有单例(用于测试) */
reset() {
this.singletons = {};
}
/** 获取所有已注册的服务名 */
getServiceNames() {
return Object.keys(this.services);
}
/**
* 构建 ProjectGraph (v3.0 AST 结构图)
* 优先从磁盘缓存加载,支持 per-file hash 增量更新
* @param projectRoot 项目根目录
* @param [options] 传递给 ProjectGraph.build() 的选项
*/
async buildProjectGraph(projectRoot, options = {}) {
if (this.singletons.projectGraph) {
return this.singletons.projectGraph;
}
const cache = new GraphCache(projectRoot);
const startTime = Date.now();
try {
// ── 尝试从缓存恢复 + 增量更新 ──
const cached = cache.load('project-graph');
if (cached?.data && cached.fileHashes) {
const graph = ProjectGraph.fromJSON(cached.data);
const currentFiles = this.#collectSourceFilePaths(projectRoot, options);
const oldHashes = cached.fileHashes || {};
// 计算差异:新增 / 变更 / 删除
const changedPaths = [];
const newHashes = {};
for (const fp of currentFiles) {
const rel = pathRelative(projectRoot, fp);
const h = cache.computeFileHash(fp);
newHashes[rel] = h;
if (!oldHashes[rel] || oldHashes[rel] !== h) {
changedPaths.push(fp);
}
}
const deletedPaths = Object.keys(oldHashes).filter((rel) => !newHashes[rel]);
if (changedPaths.length === 0 && deletedPaths.length === 0) {
// 完全命中
this.singletons.projectGraph = graph;
this.logger.info(`[ServiceContainer] ProjectGraph ⚡ 缓存命中 (${(await graph.getOverview())?.totalClasses} classes, ` +
`${Date.now() - startTime}ms)`);
return graph;
}
// 增量更新
const diff = await graph.incrementalUpdate(changedPaths, deletedPaths, options);
this.singletons.projectGraph = graph;
// 写回缓存
cache.save('project-graph', graph.toJSON(), { fileHashes: newHashes });
const overview = (await graph.getOverview());
this.logger.info(`[ServiceContainer] ProjectGraph 增量更新: +${diff.added} ~${diff.updated} -${diff.deleted} ` +
`(${overview.totalClasses} classes, ${Date.now() - startTime}ms)`);
return graph;
}
// ── 无缓存,全量构建 ──
const graph = await ProjectGraph.build(projectRoot, options);
this.singletons.projectGraph = graph;
const overview = (await graph.getOverview());
// 计算文件 hash 并写入缓存
const currentFiles = this.#collectSourceFilePaths(projectRoot, options);
const fileHashes = cache.computeFileHashes(currentFiles, projectRoot);
cache.save('project-graph', graph.toJSON(), { fileHashes });
this.logger.info(`[ServiceContainer] ProjectGraph built: ${overview.totalClasses} classes, ` +
`${overview.totalProtocols} protocols, ${overview.totalCategories} categories ` +
`(${overview.buildTimeMs}ms) — 缓存已写入`);
return graph;
}
catch (err) {
this.logger.warn(`[ServiceContainer] ProjectGraph build failed: ${err.message}`);
return null;
}
}
/** 收集项目源码文件路径(用于 hash 计算) */
#collectSourceFilePaths(projectRoot, options = {}) {
const DEFAULTS_EXT = { '.m': true, '.h': true, '.swift': true };
const extSet = new Set(options.extensions || Object.keys(DEFAULTS_EXT));
const excludePatterns = options.excludePatterns || [
'Pods/',
'Carthage/',
'node_modules/',
'.build/',
'build/',
'DerivedData/',
'vendor/',
'.git/',
'__tests__/',
'Tests/',
];
const maxFiles = options.maxFiles || 500;
const maxFileSizeBytes = options.maxFileSizeBytes || 500_000;
const results = [];
function walk(dir) {
if (results.length >= maxFiles) {
return;
}
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
}
catch {
return;
}
for (const entry of entries) {
if (results.length >= maxFiles) {
return;
}
const fullPath = pathJoin(dir, entry.name);
const relativePath = pathRelative(projectRoot, fullPath);
if (excludePatterns.some((p) => relativePath.includes(p))) {
continue;
}
if (entry.isDirectory()) {
walk(fullPath);
}
else if (entry.isFile() && extSet.has(pathExtname(entry.name))) {
try {
const stat = statSync(fullPath);
if (stat.size <= maxFileSizeBytes) {
results.push(fullPath);
}
}
catch {
/* skip */
}
}
}
}
walk(projectRoot);
return results;
}
}
let containerInstance = null;
/** 获取全局服务容器实例 */
export function getServiceContainer() {
if (!containerInstance) {
containerInstance = new ServiceContainer();
}
return containerInstance;
}
/** 重置全局服务容器(主要用于测试) */
export function resetServiceContainer() {
if (containerInstance) {
containerInstance.reset();
}
}
export default ServiceContainer;