UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

781 lines (780 loc) 34.9 kB
/** * bootstrap-phases.js — 共享的 Phase 1-4 数据收集管线 * * 内部 Agent (bootstrap-internal.js) 和外部 Agent (bootstrap-external.js) * 共享完全相同的项目分析逻辑。本模块将这些逻辑提取为可复用函数, * 消除约 300 行重复代码。 * * Phase 概览: * Phase 1 → 文件收集(DiscovererRegistry → 多语言项目类型检测) * Phase 1.5 → AST 代码结构分析(tree-sitter + SFC 预处理) * Phase 1.6 → Code Entity Graph(代码实体关系图谱) * Phase 2.2 → Panorama 全景汇总(RoleRefiner + CouplingAnalyzer + LayerInferrer) * Phase 2 → 依赖关系 → knowledge_edges * Phase 2.1 → Module 实体写入 Entity Graph * Phase 3 → Guard 规则审计 * Phase 4 → 维度条件化过滤 + Enhancement Pack + 语言画像 * * @module bootstrap/shared/bootstrap-phases */ import fs from 'node:fs'; import path from 'node:path'; import { analyzeProject, isAvailable as astIsAvailable, generateContextForAgent, } from '#core/AstAnalyzer.js'; import { DimensionCopy } from '#domain/dimension/DimensionCopy.js'; import { LanguageService } from '#shared/LanguageService.js'; import pathGuard from '#shared/PathGuard.js'; import { detectPrimaryLanguage } from '../../LanguageExtensions.js'; import { inferTargetRole } from '../../TargetClassifier.js'; import { baseDimensions, resolveActiveDimensions } from '../base-dimensions.js'; // ── 类型定义 ──────────────────────────────────────────────── // ── R13: AutoSnippet 生成物黑名单 ───────────────────────── const ASD_GENERATED_BASENAMES = new Set(['AGENTS.md', 'CLAUDE.md', 'copilot-instructions.md']); const ASD_GENERATED_PATH_SEGMENTS = [ `${path.sep}.cursor${path.sep}`, // .cursor/rules/*.mdc `${path.sep}.github${path.sep}copilot-instructions.md`, ]; /** 判断文件是否为 AutoSnippet 生成物(用于排除自引用循环知识) */ export function isAutoSnippetGenerated(filePath) { const base = path.basename(filePath); if (ASD_GENERATED_BASENAMES.has(base)) { return true; } for (const seg of ASD_GENERATED_PATH_SEGMENTS) { if (filePath.includes(seg)) { return true; } } if (base.endsWith('.mdc')) { return true; } return false; } // ── Phase 1: 文件收集 ────────────────────────────────────── /** * Phase 1: 通过 DiscovererRegistry 检测项目类型并收集源文件 * * @param projectRoot 项目根目录 * @returns >} */ export async function runPhase1_FileCollection(projectRoot, logger, options = {}) { const maxFiles = options.maxFiles || 500; const { getDiscovererRegistry } = await import('#core/discovery/index.js'); const registry = getDiscovererRegistry(); const discoverer = await registry.detect(projectRoot); logger.info(`[Bootstrap] Project type: ${discoverer.displayName} (${discoverer.id})`); await discoverer.load(projectRoot); const allTargets = await discoverer.listTargets(); const seenPaths = new Set(); const allFiles = []; for (const t of allTargets) { const isTestTarget = typeof t === 'object' && /^test/i.test(t.type || ''); try { const fileList = await discoverer.getTargetFiles(t); for (const f of fileList) { const fp = typeof f === 'string' ? f : f.path; if (seenPaths.has(fp)) { continue; } if (isAutoSnippetGenerated(fp)) { continue; // R13: skip generated files } seenPaths.add(fp); try { const content = fs.readFileSync(fp, 'utf8'); allFiles.push({ name: f.name || path.basename(fp), path: fp, relativePath: f.relativePath || path.basename(fp), content, targetName: typeof t === 'string' ? t : t.name, isTest: isTestTarget || LanguageService.isTestFile(fp), }); } catch { /* skip unreadable */ } if (allFiles.length >= maxFiles) { break; } } } catch { /* skip target */ } if (allFiles.length >= maxFiles) { break; } } // 文件截断警告:当达到 maxFiles 上限时,通知调用方分析可能不完整 const truncated = seenPaths.size > allFiles.length || allFiles.length >= maxFiles; if (truncated) { logger.warn(`[Bootstrap] File collection truncated at ${maxFiles} files (total discovered: ${seenPaths.size}). ` + `Analysis may be incomplete — consider increasing maxFiles or narrowing target scope.`); } // 语言统计 const langStats = {}; for (const f of allFiles) { const ext = path.extname(f.name).replace('.', '') || 'unknown'; langStats[ext] = (langStats[ext] || 0) + 1; } return { allFiles, allTargets: allTargets, discoverer: discoverer, langStats, truncated, }; } // ── Phase 1.5: AST 代码结构分析 ──────────────────────────── /** * Phase 1.5: tree-sitter AST 分析 * - 1.5a: 按需安装缺失的语法包 * - 1.5b: 执行 AST 分析 + SFC 预处理 * * @param allFiles Phase 1 收集的文件 * @param langStats 语言统计 * @param [options.generateAstContext=false] 是否生成 astContext 文本 * @returns >} */ export async function runPhase1_5_AstAnalysis(allFiles, langStats, logger, options = {}) { const warnings = []; let astProjectSummary = null; let astContext = ''; // Phase 1.5a: 按需安装缺失的 tree-sitter 语法包 try { const { ensureGrammars, inferLanguagesFromStats, reloadPlugins } = await import('#core/ast/ensure-grammars.js'); const neededLangs = inferLanguagesFromStats(langStats); if (neededLangs.length > 0) { const result = await ensureGrammars(neededLangs, { logger }); if (result.installed.length > 0) { logger.info(`[Bootstrap] Installed grammars: ${result.installed.join(', ')}`); await reloadPlugins(); } } await import('#core/ast/index.js'); } catch (e) { logger.warn(`[Bootstrap] Grammar auto-install skipped: ${e instanceof Error ? e.message : String(e)}`); } // Phase 1.5b: AST 分析 const primaryLangEarly = detectPrimaryLanguage(langStats); if (astIsAvailable() && primaryLangEarly) { try { const astFiles = allFiles.map((f) => ({ name: f.name, relativePath: f.relativePath, content: f.content, })); let sfcPreprocessor; try { const { initEnhancementRegistry } = await import('#core/enhancement/index.js'); const enhReg = await initEnhancementRegistry(); const preprocessPack = enhReg .all() .find((p) => typeof p.preprocessFile === 'function'); if (preprocessPack) { sfcPreprocessor = preprocessPack.preprocessFile.bind(preprocessPack); } } catch { /* Enhancement 未加载 */ } astProjectSummary = analyzeProject(astFiles, primaryLangEarly, { preprocessFile: sfcPreprocessor, }); // 内部 Agent 专用: 生成 astContext 文本 if (options.generateAstContext) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- astProjectSummary flows from analyzeProject return astContext = generateContextForAgent(astProjectSummary); } logger.info(`[Bootstrap] AST: ${astProjectSummary.classes.length} classes, ` + `${astProjectSummary.protocols.length} protocols` + (astProjectSummary.categories ? `, ${astProjectSummary.categories.length} categories` : '') + (astProjectSummary.patternStats ? `, ${Object.keys(astProjectSummary.patternStats).length} patterns` : '')); } catch (e) { logger.warn(`[Bootstrap] AST analysis failed (degraded): ${e instanceof Error ? e.message : String(e)}`); warnings.push(`AST analysis partially failed: ${e instanceof Error ? e.message : String(e)}`); } } else { logger.info(`[Bootstrap] AST skipped: tree-sitter ${astIsAvailable() ? 'available' : 'not available'}, lang=${primaryLangEarly}`); } return { astProjectSummary, astContext, warnings }; } // ── Phase 1.6: Code Entity Graph ─────────────────────────── /** * Phase 1.6: 从 AST 结果构建代码实体关系图谱 * * @param astProjectSummary AST 分析结果 * @param container ServiceContainer * @returns >} */ export async function runPhase1_6_EntityGraph(astProjectSummary, projectRoot, container, logger) { const warnings = []; let codeEntityResult = null; if (astProjectSummary) { try { const { CodeEntityGraph } = await import('#service/knowledge/CodeEntityGraph.js'); const entityRepo = container.get('codeEntityRepository'); const edgeRepo = container.get('knowledgeEdgeRepository'); if (entityRepo && edgeRepo) { const ceg = new CodeEntityGraph(entityRepo, edgeRepo, { projectRoot }); await ceg.clearProject(); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- ProjectAnalysisResult structurally compatible at runtime codeEntityResult = await ceg.populateFromAst(astProjectSummary); logger.info(`[Bootstrap] Entity Graph: ${codeEntityResult.entitiesUpserted} entities, ${codeEntityResult.edgesCreated} edges`); } } catch (e) { logger.warn(`[Bootstrap] Entity Graph failed (degraded): ${e instanceof Error ? e.message : String(e)}`); warnings.push(`Entity Graph failed: ${e instanceof Error ? e.message : String(e)}`); } } return { codeEntityResult, warnings }; } // ── Phase 2: 依赖关系 ────────────────────────────────────── /** * Phase 1.7: 跨文件调用图分析 (Phase 5) * * 从 AST 的 callSites 构建全局调用图并写入 CodeEntityGraph。 * * @param astProjectSummary AST 分析结果 (含 fileSummaries[].callSites) * @param container ServiceContainer * @param [incrementalOpts] 增量分析选项 * @param [incrementalOpts.changedFiles] 变更文件的相对路径 * @returns >} */ export async function runPhase1_7_CallGraph(astProjectSummary, projectRoot, container, logger, incrementalOpts = null) { const warnings = []; let callGraphResult = null; if (!astProjectSummary?.fileSummaries?.length) { return { callGraphResult, warnings }; } // 检查是否有 callSites 数据 (Phase 5 提取) const hasCallSites = astProjectSummary.fileSummaries.some((f) => f.callSites && f.callSites.length > 0); if (!hasCallSites) { logger.info('[Bootstrap] Call Graph skipped: no call sites extracted'); return { callGraphResult, warnings }; } try { const { CallGraphAnalyzer } = await import('#core/analysis/CallGraphAnalyzer.js'); const { CodeEntityGraph } = await import('#service/knowledge/CodeEntityGraph.js'); const analyzer = new CallGraphAnalyzer(projectRoot); const changedFiles = incrementalOpts?.changedFiles; const isIncremental = (changedFiles?.length ?? 0) > 0 && changedFiles.length <= 10; // Phase 5 分析 (带超时保护 + 渐进式 partial result) const result = isIncremental ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- ProjectAnalysisResult structurally compatible with AstProjectSummary await analyzer.analyzeIncremental(astProjectSummary, changedFiles, { timeout: 15_000, maxCallSitesPerFile: 500, minConfidence: 0.5, }) : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- ProjectAnalysisResult structurally compatible with AstProjectSummary await analyzer.analyze(astProjectSummary, { timeout: 15_000, maxCallSitesPerFile: 500, minConfidence: 0.5, }); // 写入 CodeEntityGraph const entityRepo = container.get('codeEntityRepository'); const edgeRepo = container.get('knowledgeEdgeRepository'); if (entityRepo && edgeRepo && result && result.callEdges.length > 0) { const ceg = new CodeEntityGraph(entityRepo, edgeRepo, { projectRoot }); // 增量模式: 先删除变更文件的旧边 if (isIncremental) { await ceg.clearCallGraphForFiles(changedFiles ?? null); } callGraphResult = await ceg.populateCallGraph(result.callEdges, result.dataFlowEdges); const partialTag = result.stats.partial ? ' [partial]' : ''; const incrTag = isIncremental ? ' [incremental]' : ''; logger.info(`[Bootstrap] Call Graph${incrTag}${partialTag}: ${result.callEdges.length} call edges, ` + `${result.dataFlowEdges.length} data flow edges, ` + `resolution rate: ${(result.stats.resolvedRate * 100).toFixed(1)}%`); } else if (result) { logger.info(`[Bootstrap] Call Graph: ${result.stats.totalCallSites} call sites, 0 resolved edges`); } } catch (e) { logger.warn(`[Bootstrap] Call Graph failed (degraded): ${e instanceof Error ? e.message : String(e)}`); warnings.push(`Call Graph failed: ${e instanceof Error ? e.message : String(e)}`); } return { callGraphResult, warnings }; } // ── Phase 2: 依赖关系 ────────────────────────────────────── /** * Phase 2: 获取依赖图并写入 knowledge_edges * * @param discoverer DiscovererRegistry 检测到的 discoverer * @param container ServiceContainer * @param [sourceTag='bootstrap'] edge 的 source 标签后缀 * @returns >} */ export async function runPhase2_DependencyGraph(discoverer, container, logger, sourceTag = 'bootstrap') { const warnings = []; let depGraphData = null; let depEdgesWritten = 0; try { const knowledgeGraphService = container.get('knowledgeGraphService'); depGraphData = await discoverer.getDependencyGraph(); if (knowledgeGraphService) { for (const edge of depGraphData.edges || []) { const result = await knowledgeGraphService.addEdge(edge.from, 'module', edge.to, 'module', 'depends_on', { weight: 1.0, source: `${discoverer.id}-${sourceTag}` }); if (result?.success) { depEdgesWritten++; } } } } catch (e) { logger.warn(`[Bootstrap] DepGraph failed: ${e instanceof Error ? e.message : String(e)}`); warnings.push(`Dependency graph failed: ${e instanceof Error ? e.message : String(e)}`); } return { depGraphData, depEdgesWritten, warnings }; } // ── Phase 2.1: Module 实体写入 ───────────────────────────── /** * Phase 2.1: 将依赖图的 module 节点写入 Code Entity Graph * * @param depGraphData 依赖图数据 */ export async function runPhase2_1_ModuleEntities(depGraphData, projectRoot, container, logger) { if (!depGraphData?.nodes?.length) { return; } try { const { CodeEntityGraph } = await import('#service/knowledge/CodeEntityGraph.js'); const entityRepo = container.get('codeEntityRepository'); const edgeRepo = container.get('knowledgeEdgeRepository'); if (entityRepo && edgeRepo) { const ceg = new CodeEntityGraph(entityRepo, edgeRepo, { projectRoot }); const result = await ceg.populateFromSpm(depGraphData); logger.info(`[Bootstrap] Entity Graph modules: ${result.entitiesUpserted} entities`); } } catch (e) { logger.warn(`[Bootstrap] Entity Graph modules failed: ${e instanceof Error ? e.message : String(e)}`); } } // ── Phase 3: Guard 审计 ──────────────────────────────────── /** * Phase 3: Guard 规则审计 * * @param allFiles Phase 1 收集的文件 * @param [options.summaryPrefix='Bootstrap scan'] - ViolationsStore 摘要前缀 * @returns >} */ export async function runPhase3_GuardAudit(allFiles, container, logger, options = {}) { const warnings = []; let guardAudit = null; let guardEngine = null; if (options.skipGuard) { return { guardAudit, guardEngine, warnings }; } try { const { GuardCheckEngine } = await import('#service/guard/GuardCheckEngine.js'); const db = container.get('database'); guardEngine = new GuardCheckEngine(db); const guardFiles = allFiles.map((f) => ({ path: f.path, content: f.content, isTest: f.isTest, })); guardAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' }); // 写入 ViolationsStore try { const violationsStore = container.get('violationsStore'); const prefix = options.summaryPrefix || 'Bootstrap scan'; for (const fileResult of guardAudit.files || []) { if (fileResult.violations.length > 0) { const fileSummary = fileResult.summary; violationsStore.appendRun({ filePath: fileResult.filePath, violations: fileResult.violations, summary: `${prefix}: ${fileSummary?.errors ?? 0}E ${fileSummary?.warnings ?? 0}W`, }); } } } catch { /* ViolationsStore not available */ } } catch (e) { logger.warn(`[Bootstrap] Guard audit failed: ${e instanceof Error ? e.message : String(e)}`); warnings.push(`Guard audit failed: ${e instanceof Error ? e.message : String(e)}`); } return { guardAudit, guardEngine, warnings }; } // ── Phase 4: 维度解析 + Enhancement Pack ─────────────────── /** * Phase 4: 维度条件化过滤 + Enhancement Pack 动态追加 + 语言画像 + Skill 增强 * * @param params.astProjectSummary AST 结果(供 Enhancement Pack 模式检测) * @param params.guardEngine Guard 引擎(供 Enhancement Pack 规则注入) * @param params.allFiles 文件列表(供 Guard 二次审计) * @returns {Promise<{ * activeDimensions: Array, * enhancementPackInfo: Array, * enhancementPatterns: Array, * enhancementGuardRules: Array, * langProfile: object, * detectedFrameworks: string[], * guardAudit: object|null * }>} */ export async function runPhase4_DimensionResolve(params) { const { primaryLang, langStats, allTargets, astProjectSummary, guardEngine, allFiles, logger } = params; // 框架检测 const detectedFrameworks = allTargets .map((t) => (typeof t === 'object' ? t.framework : null)) .filter(Boolean); // 条件维度过滤 const activeDimensions = resolveActiveDimensions(baseDimensions, primaryLang, detectedFrameworks); // Enhancement Pack 动态追加 const enhancementPackInfo = []; const enhancementGuardRules = []; const enhancementPatterns = []; let guardAudit = null; try { const { initEnhancementRegistry } = await import('#core/enhancement/index.js'); const enhReg = await initEnhancementRegistry(); const matchedPacks = enhReg.resolve(primaryLang, detectedFrameworks); for (const pack of matchedPacks) { enhancementPackInfo.push({ id: pack.id, displayName: pack.displayName }); // 追加额外维度 for (const dim of pack.getExtraDimensions()) { if (!activeDimensions.some((d) => d.id === dim.id)) { activeDimensions.push(dim); } } // 收集 Guard 规则 const guardRules = pack.getGuardRules(); if (guardRules.length > 0) { enhancementGuardRules.push(...guardRules); } // AST 模式检测 if (astProjectSummary) { try { const patterns = pack.detectPatterns(astProjectSummary); if (patterns.length > 0) { enhancementPatterns.push(...patterns.map((p) => ({ ...p, source: pack.id }))); } } catch { /* graceful degradation */ } } } if (matchedPacks.length > 0) { logger.info(`[Bootstrap] Enhancement packs: ${matchedPacks.map((p) => p.id).join(', ')} → ` + `+${activeDimensions.length - baseDimensions.length} dims, ${enhancementGuardRules.length} guard rules, ${enhancementPatterns.length} patterns`); } } catch (enhErr) { logger.warn(`[Bootstrap] Enhancement packs skipped: ${enhErr instanceof Error ? enhErr.message : String(enhErr)}`); } // Enhancement Pack Guard 规则注入 + 补充审计 if (enhancementGuardRules.length > 0 && guardEngine) { try { guardEngine.injectExternalRules(enhancementGuardRules); const guardFiles = allFiles.map((f) => ({ path: f.path, content: f.content, isTest: f.isTest, })); guardAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' }); logger.info(`[Bootstrap] Guard re-audit with ${guardEngine.getExternalRuleCount()} Enhancement Pack rules → ${guardAudit.summary?.totalViolations ?? 0} total violations`); } catch (e) { logger.warn(`[Bootstrap] Enhancement Pack guard re-audit failed: ${e instanceof Error ? e.message : String(e)}`); } } // 语言画像 + 差异化文案 const langProfile = LanguageService.detectProfile(langStats); DimensionCopy.applyMulti(activeDimensions, langProfile.primary, langProfile.secondary); return { activeDimensions, enhancementPackInfo, enhancementPatterns, enhancementGuardRules, langProfile, detectedFrameworks, guardAudit, }; } // ── 一站式调用 ───────────────────────────────────────────── /** * runAllPhases — 一站式执行 Phase 1~4 全部数据收集 * * 内部 Agent 和外部 Agent 均可调用此函数获取统一的分析结果。 * * @param projectRoot 项目根目录 * @param ctx { container, logger } * @param [options.incremental=false] 启用增量评估 (Phase 1 后执行) * @param [options.generateReport=false] 生成 Phase 级详细报告 * @param [options.clearOldData=false] 先清除旧 checkpoints/snapshots * @param [options.generateAstContext=false] 生成 astContext 文本 * @param [options.summaryPrefix='Bootstrap scan'] */ export async function runAllPhases(projectRoot, ctx, options = {}) { const warnings = []; const report = options.generateReport ? { phases: {}, startTime: Date.now() } : null; // 路径安全守卫 if (!pathGuard.configured) { const { default: Bootstrap } = await import('../../../../../bootstrap.js'); Bootstrap.configurePathGuard(projectRoot); } // ── 清除旧数据 (if requested) ── if (options.clearOldData) { try { const { clearCheckpoints, clearSnapshots } = await import('../pipeline/orchestrator.js'); await clearCheckpoints(projectRoot); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- AllPhasesContext structurally compatible with clearSnapshots param await clearSnapshots(projectRoot, ctx); ctx.logger.info('[Bootstrap] Cleared old checkpoints and snapshots'); } catch (err) { warnings.push(`clearOldData failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`); } } // ── Phase 1: 文件收集 ── const p1Start = Date.now(); const phase1 = await runPhase1_FileCollection(projectRoot, ctx.logger, options); const { allFiles, allTargets, discoverer, langStats, truncated } = phase1; if (truncated) { warnings.push(`File collection truncated at ${options.maxFiles || 500} files. Analysis may be incomplete.`); } if (report) { report.phases.fileCollection = { fileCount: allFiles.length, targetCount: allTargets.length, ms: Date.now() - p1Start, }; } if (allFiles.length === 0) { return { allFiles, langStats, primaryLang: null, discoverer, allTargets, truncated, astProjectSummary: null, astContext: '', codeEntityResult: null, callGraphResult: null, depGraphData: null, depEdgesWritten: 0, guardAudit: null, guardEngine: null, activeDimensions: [], enhancementPackInfo: [], enhancementPatterns: [], enhancementGuardRules: [], langProfile: {}, targetsSummary: [], localPackageModules: [], warnings, report: report || {}, incrementalPlan: null, panoramaResult: null, detectedFrameworks: [], isEmpty: true, }; } // ── Incremental evaluation (Phase 1 后执行,需要 allFiles) ── let incrementalPlan = null; if (options.incremental) { try { const { IncrementalBootstrap } = await import('../pipeline/IncrementalBootstrap.js'); const db = ctx.container?.resolve?.('db') ?? ctx.db; if (db) { const ib = new IncrementalBootstrap(db, projectRoot, { logger: ctx.logger }); const dimIds = baseDimensions.map((d) => d.id); incrementalPlan = await ib.evaluate(allFiles, dimIds); if (report) { report.phases.incremental = { plan: incrementalPlan }; } ctx.logger.info(`[Bootstrap] Incremental mode: ${incrementalPlan.mode}, affected: ${incrementalPlan.affectedDimensions?.length || 0}`); } else { warnings.push('incremental: db not available, falling back to full'); } } catch (err) { warnings.push(`incremental evaluation failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`); } } // ── Phase 1.5: AST 分析 ── const p15Start = Date.now(); const phase1_5 = await runPhase1_5_AstAnalysis(allFiles, langStats, ctx.logger, { generateAstContext: options.generateAstContext || false, }); warnings.push(...phase1_5.warnings); if (report) { report.phases.ast = { classCount: phase1_5.astProjectSummary?.classes?.length || 0, ms: Date.now() - p15Start, }; } // ── Phase 1.6: Entity Graph ── const p16Start = Date.now(); const phase1_6 = await runPhase1_6_EntityGraph(phase1_5.astProjectSummary, projectRoot, ctx.container, ctx.logger); warnings.push(...phase1_6.warnings); if (report) { report.phases.entityGraph = { entityCount: phase1_6.codeEntityResult?.entitiesUpserted || 0, edgeCount: phase1_6.codeEntityResult?.edgesCreated || 0, ms: Date.now() - p16Start, }; } // ── Phase 1.7: Call Graph (Phase 5) ── const p17Start = Date.now(); const phase1_7 = await runPhase1_7_CallGraph(phase1_5.astProjectSummary, projectRoot, ctx.container, ctx.logger); warnings.push(...phase1_7.warnings); if (report) { report.phases.callGraph = { result: phase1_7.callGraphResult, ms: Date.now() - p17Start }; } // ── Phase 2: 依赖图 ── const p2Start = Date.now(); const phase2 = await runPhase2_DependencyGraph(discoverer, ctx.container, ctx.logger, options.sourceTag || 'bootstrap'); warnings.push(...phase2.warnings); if (report) { report.phases.depGraph = { edgesWritten: phase2.depEdgesWritten || 0, ms: Date.now() - p2Start, }; } // ── Phase 2.1: Module 实体 ── await runPhase2_1_ModuleEntities(phase2.depGraphData, projectRoot, ctx.container, ctx.logger); // ── Phase 2.2: Panorama 全景汇总 ── // 必须在 Phase 2.1 之后:此时 code_entities 中已有 module 记录 let panoramaResult = null; try { const panoramaService = ctx.container.get('panoramaService'); if (panoramaService && typeof panoramaService.invalidate === 'function') { const pPanoStart = Date.now(); panoramaService.invalidate(); const result = await panoramaService.getResult(); panoramaResult = result; ctx.logger.info(`[Bootstrap] Phase 2.2: Panorama computed in ${Date.now() - pPanoStart}ms`); if (report) { const overview = await panoramaService.getOverview(); report.phases.panorama = { moduleCount: overview.moduleCount ?? 0, layerCount: overview.layerCount ?? 0, ms: Date.now() - pPanoStart, }; } } } catch (err) { warnings.push(`Phase 2.2 panorama failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`); } // ── Phase 3: Guard 审计 ── const p3Start = Date.now(); const phase3 = await runPhase3_GuardAudit(allFiles, ctx.container, ctx.logger, { skipGuard: options.skipGuard || false, summaryPrefix: options.summaryPrefix || 'Bootstrap scan', }); warnings.push(...phase3.warnings); if (report) { report.phases.guard = { ruleCount: phase3.guardAudit?.rules?.length || 0, ms: Date.now() - p3Start, }; } // ── Phase 4: 维度解析 + Enhancement Pack ── const p4Start = Date.now(); const primaryLang = detectPrimaryLanguage(langStats); const phase4 = await runPhase4_DimensionResolve({ primaryLang, langStats, allTargets, astProjectSummary: phase1_5.astProjectSummary, guardEngine: phase3.guardEngine, allFiles, logger: ctx.logger, }); if (report) { report.phases.dimension = { activeDimCount: phase4.activeDimensions?.length || 0, detectedFrameworks: phase4.detectedFrameworks, ms: Date.now() - p4Start, }; } // 如果 Enhancement Pack 产生了新的 guardAudit,覆盖 Phase 3 的结果 const finalGuardAudit = phase4.guardAudit || phase3.guardAudit; // Targets 摘要 const targetsSummary = allTargets.map((t) => { const name = typeof t === 'string' ? t : t.name; const pkgName = typeof t === 'object' ? t.packageName : undefined; const targetPath = typeof t === 'object' ? t.path : undefined; return { name, type: (typeof t === 'object' ? t.type : undefined) || 'target', packageName: pkgName || undefined, inferredRole: inferTargetRole(name), fileCount: allFiles.filter((f) => f.targetName === name).length, // 标记来自子包的 target(如 Packages/AOXNetworkKit)—— 语言无关 isLocalPackage: typeof targetPath === 'string' && targetPath !== projectRoot ? true : undefined, }; }); // 本地子包汇总 — 供 MissionBriefing 构建 mustCoverModules const localPackageModules = targetsSummary .filter((t) => t.isLocalPackage && t.fileCount > 0) .map((t) => ({ name: t.name, packageName: t.packageName || t.name, fileCount: t.fileCount, inferredRole: t.inferredRole, // 提取该模块的关键文件路径(前 8 个,用于 evidenceStarters) keyFiles: allFiles .filter((f) => f.targetName === t.name) .slice(0, 8) .map((f) => f.relativePath), })); // 完成报告 if (report) { report.totalMs = Date.now() - report.startTime; } return { allFiles, langStats, primaryLang, discoverer, allTargets, truncated, astProjectSummary: phase1_5.astProjectSummary, astContext: phase1_5.astContext, codeEntityResult: phase1_6.codeEntityResult, callGraphResult: phase1_7.callGraphResult, depGraphData: phase2.depGraphData, depEdgesWritten: phase2.depEdgesWritten, guardAudit: finalGuardAudit, guardEngine: phase3.guardEngine, activeDimensions: phase4.activeDimensions, enhancementPackInfo: phase4.enhancementPackInfo, enhancementPatterns: phase4.enhancementPatterns, enhancementGuardRules: phase4.enhancementGuardRules, langProfile: phase4.langProfile, detectedFrameworks: phase4.detectedFrameworks, targetsSummary, localPackageModules, // 本地子包汇总(语言无关) warnings, report, // NEW: Phase 级报告 (null if generateReport=false) incrementalPlan, // NEW: 增量评估结果 (null if incremental=false) panoramaResult, // Phase 2.2: 全景汇总 (null if panoramaService unavailable) isEmpty: false, }; }