UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

1,226 lines 66 kB
/** * Mission Briefing 构建器 — 外部 Agent 驱动 Bootstrap 的核心数据构建 * * 将 Phase 1-4 的分析结果(AST / EntityGraph / DepGraph / Guard) * + 维度定义 + 提交规范 + 执行计划 整合为一站式 Mission Briefing, * 让外部 Agent (Cursor/Copilot) 拥有全部必要上下文来完成代码分析。 * * 设计原则: * - 100KB 响应硬上限,大项目自动降级压缩 * - 文件内容永远不包含 → Agent 自己读更快 * - Example 按项目主语言自适应 * - Tier 编号使用 1/2/3(与 tier-scheduler.js 一致) * * @module bootstrap/MissionBriefingBuilder */ import { getDimensionSOP, PRE_SUBMIT_CHECKLIST, sopToCompactText, } from '#domain/dimension/DimensionSop.js'; import { getCursorDeliverySpec } from '#domain/knowledge/FieldSpec.js'; import { PROJECT_SNAPSHOT_STYLE_GUIDE } from '#domain/knowledge/StyleGuide.js'; import { TierScheduler } from './pipeline/tier-scheduler.js'; import { EXAMPLE_TEMPLATES, SUBMISSION_SCHEMA } from './shared/dimension-text.js'; // ── 常量 ──────────────────────────────────────────────────── /** 响应体积硬上限 (bytes) */ const RESPONSE_SIZE_LIMIT = 100 * 1024; // 100KB /** 分级压缩阈值 */ const SIZE_THRESHOLDS = { S: 100, // <100 files → 完整 AST M: 500, // 100-500 files → top-50 classes L: Infinity, // 500+ files → top-30 classes 摘要模式 }; // ── 维度指引构建 ──────────────────────────────────────────── /** * 将 base-dimensions 中的维度定义转换为 Mission Briefing 中的维度任务对象 * * 取自 bootstrap-analyst.buildAnalystPrompt() + DIMENSION_CONFIGS_V3 + StyleGuide * * @param dim base-dimensions.js 中的维度定义 * @param tier 维度所在 tier 编号 (1/2/3) * @returns Mission Briefing 维度任务对象 */ function enrichDimensionTask(dim, tier) { // ── analysisGuide: SOP 化 — 优先使用维度专属 SOP,否则回退通用指引 ── const sop = getDimensionSOP(dim.id); let analysisGuide; if (sop) { // SOP 结构化模式: steps + timeEstimate + commonMistakes analysisGuide = { goal: `分析项目的${dim.label}`, focus: dim.guide || '', steps: sop.steps, timeEstimate: sop.timeEstimate || '1-5 min', commonMistakes: sop.commonMistakes || [], }; } else { // 无显式 SOP 的维度 (Enhancement Pack 等): 自动生成结构化 SOP // 保持 analysisGuide 为对象格式,确保 SOP 覆盖率 analysisGuide = { goal: `分析项目的${dim.label}`, focus: dim.guide || '', steps: [ { phase: '1. 全局扫描', action: `搜索项目中与 ${dim.label} 相关的核心文件和关键模式`, expectedOutput: '识别 3-5 个核心文件和主要模式', tools: ['grep_search 搜索关键词', '浏览核心目录结构'], }, { phase: '2. 深度验证', action: `阅读 5+ 个核心文件,验证 ${dim.label} 的实现方式是否一致`, expectedOutput: '每个模式至少有 3 个文件证据,含具体行号', tools: ['read_file 逐个阅读核心文件'], }, { phase: '3. 异常检测', action: '搜索不符合主流模式的例外,确认是否为历史遗留或特殊例外', expectedOutput: '识别例外模式及其原因', }, { phase: '4. 提交', action: '按项目特写格式提交知识候选(**最少 3 条,目标 5 条**,将不同关注点拆为独立候选)', qualityChecklist: [ '候选数量 ≥3(1-2 条是不合格的,不同关注点必须拆分为独立候选)', '每个 content ≥200 字符', '每个候选引用 ≥3 个文件路径', 'coreCode 提供可复制的完整代码骨架', ], }, ], timeEstimate: '1-5 min', commonMistakes: [ '不要只扫描 1 个文件就提交 — 至少读 5+ 个文件验证模式一致性', 'content 中必须有 (来源: Full/Path/FileName.ext:行号) 标注具体出处,必须是从项目根开始的完整相对路径', '【跨维度去重】每条候选必须属于当前维度的独有视角 — 禁止将同一知识点换个角度重复提交到多个维度来充数,宁可少提交也不要重复', '【本地子包覆盖】如果项目有本地子包/模块(如 Packages/ 下的包),必须同时分析其内部实现,不得只看主项目对其的调用', ], }; } // ── submissionSpec: 嵌入 Quality Checklist ── const submissionSpec = { knowledgeTypes: dim.knowledgeTypes || [], targetCandidateCount: '每维度最少 3 条,目标 5 条(1-2 条不合格)。将不同关注点(如命名规范 vs 文件组织 vs 注释风格)拆分为独立候选,不要合并到一条中。', contentStyle: PROJECT_SNAPSHOT_STYLE_GUIDE.split('\n') .filter((l) => !l.startsWith('#') || l.startsWith('##')) .filter((l) => l.trim()) .slice(0, 12) .join('\n'), contentQuality: 'content.markdown 必须 ≥200 字符,包含: (1) ## 标题 (2) 正文说明 (3) 至少一个 ```代码块``` (4) 来源标注「(来源: Full/Relative/Path/FileName.ext:行号)」。\n【最高优先级 — 源码位置】每个候选必须包含完整相对路径(从项目根目录开始)+ 行号。禁止只写文件名(如 NetworkClient.swift:42),必须写完整路径(如 Packages/AOXNetworkKit/Sources/AOXNetworkKit/Client/NetworkClient.swift:42)。reasoning.sources 中也必须是完整相对路径。\n【模块归属】每个候选必须标注所属模块(如「所属模块: AOXNetworkKit」)。\n短于 200 字符的提交会被拒绝。\n【禁止】标题和正文中不得出现 "Agent" 字样 — 所有候选必须以项目规范/开发规范的视角撰写,描述的是项目规则而非 AI Agent 指南。', crossDimensionDedup: '【跨维度去重 — 系统强制拒绝】每条候选必须属于且仅属于当前维度的视角。禁止将同一知识点换个角度/换个说法重复提交到多个维度。' + '例如: BaseViewController 的继承规则只应出现在 code-pattern(设计模式)中,不应同时出现在 architecture(分层架构)和 code-standard(命名规范)中。' + '如果某个发现与多个维度相关,只在最核心的维度提交,其他维度用不同的独立知识点填充。' + '宁可少提交也不要重复充数 — 与前序维度标题相同的候选会被系统自动拒绝(硬去重)。', cursorFields: getCursorDeliverySpec(), dimensionCompleteGuide: '调用 dimension_complete 时必须传递: referencedFiles=[本维度分析过的全部文件路径], keyFindings=[3-5条关键发现摘要], analysisText=详细分析报告(≥500字符,含##标题+列表+代码块)', preSubmitChecklist: PRE_SUBMIT_CHECKLIST, }; // ── skillMeta ── const sm = dim.skillMeta; const skillMeta = dim.skillWorthy ? { name: sm?.name || `project-${dim.id}`, description: sm?.description || `${dim.label} skill (auto-generated)`, format: 'Markdown 正文,需包含 # 标题、列表、代码块等结构化内容,≥100 字符', } : undefined; return { id: dim.id, label: dim.label, tier, // 1/2/3 与 tier-scheduler.js 一致 outputType: dim.dualOutput ? 'dual' : dim.skillWorthy ? 'skill' : 'candidate', status: 'pending', analysisGuide, submissionSpec, skillMeta, }; } // ── 维度级证据启发构建 (v2) ───────────────────────────────── /** * 从 Phase 1-4 数据中为维度提取证据启发 * * 将原本的"原始 AST / Guard 数据"转化为维度关联的结构化引导, * 让外部 Agent 更有方向性地开始分析: * - 与维度相关的 AST 类/协议/模式 * - Guard 违规中与维度话题相关的发现 * - 依赖图中与维度相关的模块关系 * * 设计对标: 内部 Agent 的 buildProducerPromptV2 提供结构化 findings 和 code evidence, * 外部 Agent 的 evidenceStarters 从 Phase 1-4 数据中提供类似的结构化起点。 * * @param dim 维度定义 * @param [opts.astData] analyzeProject() 结果 * @param [opts.guardAudit] GuardCheckEngine.auditFiles() 结果 * @param [opts.depGraphData] 依赖图 * @returns evidenceStarters 对象,为空则返回 undefined */ export function buildEvidenceStarters(dim, { astData, guardAudit, depGraphData, callGraphResult, panoramaResult }) { const starters = {}; const dimId = dim.id; const dimLabel = (dim.label || '').toLowerCase(); const dimGuide = (dim.guide || '').toLowerCase(); const dimKeywords = `${dimLabel} ${dimGuide}`; // §1: AST 相关发现 if (astData) { const classes = astData.classes || []; const protocols = astData.protocols || []; const patterns = astData.patternStats || {}; const fileSummaries = astData.fileSummaries || []; // naming-conventions → 前缀/后缀统计 (类名 + 顶层函数名) if (dimId === 'naming-conventions' || dimId === 'code-standard' || dimKeywords.includes('命名') || dimKeywords.includes('naming')) { const prefixStats = {}; for (const cls of classes) { const prefix = (cls.name || '').match(/^[A-Z]{2,4}/)?.[0]; if (prefix) { prefixStats[prefix] = (prefixStats[prefix] || 0) + 1; } } // 函数式代码: 统计顶层函数命名模式 (useXxx, handleXxx, getXxx, etc.) if (classes.length === 0) { const funcPrefixes = {}; for (const fs of fileSummaries) { for (const m of fs.methods || []) { if (!m.className) { const fp = (m.name || '').match(/^(use|handle|get|set|create|make|fetch|on|is|has|with|to)[A-Z]/)?.[1]; if (fp) { funcPrefixes[fp] = (funcPrefixes[fp] || 0) + 1; } } } } const topFuncPrefixes = Object.entries(funcPrefixes) .sort(([, a], [, b]) => b - a) .slice(0, 5); if (topFuncPrefixes.length > 0) { starters.functionNamingPatterns = { hint: '顶层函数命名前缀分布 — 用于分析函数式代码命名约定', data: topFuncPrefixes.map(([prefix, count]) => `${prefix}Xxx (${count} functions)`), }; } } if (Object.keys(prefixStats).length > 0) { starters.namingPatterns = { hint: '项目类名前缀分布 — 用于分析命名约定', data: Object.entries(prefixStats) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([prefix, count]) => `${prefix}* (${count} classes)`), }; } } // patterns-architecture → 设计模式 + 继承链 + 函数式模式 if (dimId === 'patterns-architecture' || dimId === 'architecture' || dimId === 'code-pattern' || dimKeywords.includes('架构') || dimKeywords.includes('pattern') || dimKeywords.includes('模式')) { if (Object.keys(patterns).length > 0) { // 压缩 patterns: 只保留顶层 key → 计数/类型摘要 const compactPatterns = {}; for (const [key, val] of Object.entries(patterns)) { if (typeof val === 'number' || typeof val === 'string' || typeof val === 'boolean') { compactPatterns[key] = val; } else if (Array.isArray(val)) { compactPatterns[key] = `${val.length} items`; } else if (val && typeof val === 'object') { compactPatterns[key] = Object.keys(val).slice(0, 10).join(', '); } } starters.detectedPatterns = { hint: 'AST 自动检测到的设计模式 — 作为架构分析起点', data: compactPatterns, }; } // 继承关系分析 const baseClasses = {}; for (const cls of classes) { if (cls.superclass) { baseClasses[cls.superclass] = (baseClasses[cls.superclass] || 0) + 1; } } const topBases = Object.entries(baseClasses) .sort((a, b) => b[1] - a[1]) .slice(0, 5); if (topBases.length > 0) { starters.inheritanceHotspots = { hint: '最常被继承的基类 — 关注其设计模式和扩展约定', data: topBases.map(([cls, count]) => `${cls} (${count} subclasses)`), }; } } // 协议/接口相关维度 if (dimKeywords.includes('protocol') || dimKeywords.includes('协议') || dimKeywords.includes('interface')) { if (protocols.length > 0) { starters.protocolSummary = { hint: `项目定义了 ${protocols.length} 个协议/接口`, data: protocols.slice(0, 8).map((p) => ({ name: p.name, methods: p.methodCount || p.methods?.length || 0, conformers: (p.conformers || []).length, })), }; } } // §1.2: 代码规模摘要 — 任何维度都可以从文件/函数统计中获取证据 const totalMethods = astData.projectMetrics?.totalMethods || 0; const fileCount = astData.fileCount || 0; if (classes.length === 0 && totalMethods > 0) { // 函数式/模块化代码: 提供函数和导出统计 const exportCount = fileSummaries.reduce((s, f) => s + (f.exports?.length || 0), 0); const asyncCount = fileSummaries.reduce((s, f) => s + (f.methods || []).filter((m) => m.isAsync).length, 0); const complexMethods = astData.projectMetrics?.complexMethods || []; if (dimId === 'code-pattern' || dimId === 'best-practice' || dimId === 'event-and-data-flow' || dimKeywords.includes('模式') || dimKeywords.includes('实践') || dimKeywords.includes('事件')) { const summary = [ `${totalMethods} functions across ${fileCount} files`, exportCount > 0 ? `${exportCount} exports` : null, asyncCount > 0 ? `${asyncCount} async functions` : null, complexMethods.length > 0 ? `${complexMethods.length} high-complexity functions` : null, ].filter(Boolean); if (summary.length > 0) { starters.codeSummary = { hint: '函数式代码结构统计 — 用于分析代码模式和最佳实践', data: summary, }; } } } } // §2: Guard 违规关联 if (guardAudit?.files) { const dimRelatedViolations = []; for (const fileResult of guardAudit.files) { for (const v of fileResult.violations || []) { // 粗略匹配: ruleId / message 是否与维度话题相关 const ruleText = `${v.ruleId || ''} ${v.message || ''}`.toLowerCase(); if (dimId.split('-').some((word) => word.length > 3 && ruleText.includes(word))) { dimRelatedViolations.push({ file: fileResult.filePath, rule: v.ruleId || '', message: (v.message || '').substring(0, 100), }); } } } if (dimRelatedViolations.length > 0) { starters.guardViolations = { hint: `Guard 审计发现 ${dimRelatedViolations.length} 条与本维度相关的违规 — 可作为分析切入点`, data: dimRelatedViolations.slice(0, 5), }; } // §2.5: 跨文件违规单独高亮 — architecture / best-practice 维度重点关注 const crossFileViolations = guardAudit.crossFileViolations || []; if (crossFileViolations.length > 0 && (dimId === 'architecture' || dimId === 'best-practice' || dimId === 'code-standard' || dimKeywords.includes('架构') || dimKeywords.includes('层级') || dimKeywords.includes('依赖方向'))) { starters.crossFileViolations = { hint: `Guard 检测到 ${crossFileViolations.length} 条跨文件违规(如层级穿透、循环引用) — 这是架构分析的关键信号`, data: crossFileViolations.slice(0, 5).map((v) => ({ rule: v.ruleId, message: (v.message || '').substring(0, 120), files: v.locations?.slice(0, 2).map((l) => l.filePath) || [], })), }; } } // §3: 依赖图关联 (扩大到所有架构/模块相关维度) if (depGraphData?.nodes) { const nodeCount = (depGraphData.nodes || []).length; const edgeCount = (depGraphData.edges || []).length; if (nodeCount > 0 && (dimId === 'patterns-architecture' || dimId === 'architecture' || dimId === 'data-flow-patterns' || dimId === 'project-profile' || dimId === 'module-export-scan' || dimKeywords.includes('架构') || dimKeywords.includes('模块') || dimKeywords.includes('依赖'))) { starters.dependencyOverview = { hint: `依赖图包含 ${nodeCount} 个模块、${edgeCount} 条依赖 — 分析模块间耦合关系`, data: { totalModules: nodeCount, totalEdges: edgeCount, topModules: (depGraphData.nodes || []) .slice(0, 5) .map((n) => (typeof n === 'string' ? n : n.label || n.id)), }, }; } } // §4: ObjC Category 关联 — 为 category-scan 和相关维度提供分类证据 if (astData) { const categories = astData.categories || []; if (categories.length > 0 && (dimId === 'category-scan' || dimId === 'category-extension' || dimKeywords.includes('category') || dimKeywords.includes('分类') || dimKeywords.includes('extension'))) { // 按 baseClass 聚合分类 const catByBase = {}; for (const cat of categories) { const base = cat.baseClass || cat.extendedClass || 'Unknown'; if (!catByBase[base]) { catByBase[base] = []; } catByBase[base].push(cat.name || '(anonymous)'); } const topBases = Object.entries(catByBase) .sort((a, b) => b[1].length - a[1].length) .slice(0, 8); starters.categorySummary = { hint: `项目定义了 ${categories.length} 个 Category — 关注命名前缀、功能归类、与基类的关系`, data: topBases.map(([base, cats]) => ({ baseClass: base, categoryCount: cats.length, categories: cats.slice(0, 5), })), }; } } // §5: 事件/数据流关联 — 为 event-and-data-flow 提供起始证据 if (astData && (dimId === 'event-and-data-flow' || dimId === 'data-flow-patterns' || dimKeywords.includes('事件') || dimKeywords.includes('event') || dimKeywords.includes('数据流'))) { const protocols = astData.protocols || []; // 查找 Delegate/DataSource 协议 — ObjC/Swift 的典型事件/数据流模式 const delegateProtocols = protocols.filter((p) => { const name = (p.name || '').toLowerCase(); return name.includes('delegate') || name.includes('datasource'); }); if (delegateProtocols.length > 0) { starters.delegatePatterns = { hint: `发现 ${delegateProtocols.length} 个 Delegate/DataSource 协议 — 项目的核心事件/数据传递通道`, data: delegateProtocols.slice(0, 8).map((p) => ({ name: p.name, methods: p.methodCount || p.methods?.length || 0, })), }; } // 查找 Notification/Observer 相关类 const classes = astData.classes || []; const observerClasses = classes.filter((c) => { const name = (c.name || '').toLowerCase(); return name.includes('observer') || name.includes('notification') || name.includes('event'); }); if (observerClasses.length > 0) { starters.observerPatterns = { hint: `发现 ${observerClasses.length} 个 Observer/Notification/Event 类`, data: observerClasses.slice(0, 5).map((c) => c.name), }; } } // §6: 调用图证据 — 为 best-practice / event-and-data-flow / code-pattern 提供热点方法 if (callGraphResult) { const callEdges = callGraphResult.edgesCreated; const methodEntities = callGraphResult.entitiesUpserted; if (callEdges && callEdges > 0 && (dimId === 'best-practice' || dimId === 'event-and-data-flow' || dimId === 'code-pattern' || dimKeywords.includes('并发') || dimKeywords.includes('concurrency') || dimKeywords.includes('事件') || dimKeywords.includes('flow'))) { starters.callGraphSummary = { hint: `调用图包含 ${methodEntities || 0} 个方法实体、${callEdges} 条调用边 — 关注高扇入/扇出方法和异步调用链`, data: { methodEntities: methodEntities || 0, callEdges, durationMs: callGraphResult.durationMs || 0, analysisHint: dimId === 'best-practice' ? '关注扇入最高的方法(核心抽象)和扇出最高的方法(协调者),以及 async/await 调用链' : dimId === 'event-and-data-flow' ? '关注数据流边和事件传播路径,特别是跨模块的观察者和回调链' : '关注方法调用模式中的设计模式(如 Template Method、Chain of Responsibility)', }, }; } } // §7: Panorama 热点模块 — 为 architecture / project-profile 提供耦合分析起点 if (panoramaResult) { const panoramaModules = panoramaResult.modules; const panoramaCycles = panoramaResult.cycles ?? []; if (panoramaModules instanceof Map && (dimId === 'architecture' || dimId === 'project-profile' || dimId === 'best-practice' || dimKeywords.includes('架构') || dimKeywords.includes('模块') || dimKeywords.includes('耦合'))) { // 提取高耦合模块(fanIn+fanOut 排序) const hotspots = []; for (const [, mod] of panoramaModules) { if (mod.fanIn >= 5 || mod.fanOut >= 5) { hotspots.push({ module: mod.name, fanIn: mod.fanIn, fanOut: mod.fanOut }); } } hotspots.sort((a, b) => b.fanIn + b.fanOut - (a.fanIn + a.fanOut)); if (hotspots.length > 0) { starters.couplingHotspots = { hint: `全景分析发现 ${hotspots.length} 个高耦合模块 — 优先分析其架构边界和依赖方向`, data: hotspots .slice(0, 8) .map((h) => `${h.module} (fanIn=${h.fanIn}, fanOut=${h.fanOut})`), }; } } // 循环依赖 → architecture / best-practice if (panoramaCycles.length > 0 && (dimId === 'architecture' || dimId === 'best-practice' || dimKeywords.includes('架构') || dimKeywords.includes('依赖'))) { starters.cyclicDependencies = { hint: `全景分析检测到 ${panoramaCycles.length} 组循环依赖 — 需要在分析中识别并记录`, data: panoramaCycles.slice(0, 5).map((c) => ({ cycle: c.cycle.join(' → '), severity: c.severity, })), }; } } if (Object.keys(starters).length === 0) { return undefined; } // §8: 信号强度评估 — 根据数据显著性为每个 starter 附加 strength (0-100) const withStrength = {}; for (const [key, value] of Object.entries(starters)) { let strength = 50; // 默认中等 const dataArr = Array.isArray(value.data) ? value.data : null; const dataCount = dataArr ? dataArr.length : 0; // 统计类 starters — 数据量越大信号越强 if (key === 'namingPrefixSuffix' || key === 'patternStats' || key === 'inheritanceChains') { strength = Math.min(90, 40 + dataCount * 10); } else if (key === 'guardViolations') { // Guard 违规 — 越多越值得关注 const violations = value.data?.totalViolations || dataCount; strength = Math.min(95, 50 + violations * 5); } else if (key === 'crossFileViolations') { // 跨文件违规 — 高优先级信号(涉及架构边界) strength = Math.min(95, 75 + dataCount * 8); } else if (key === 'callGraphSummary') { const edges = value.data?.callEdges ?? 0; strength = edges > 50 ? 85 : edges > 10 ? 70 : 55; } else if (key === 'couplingHotspots') { strength = Math.min(90, 60 + dataCount * 8); } else if (key === 'cyclicDependencies') { strength = Math.min(95, 70 + dataCount * 10); // 循环依赖是高优先级信号 } else if (key === 'delegatePatterns' || key === 'observerPatterns') { strength = Math.min(85, 45 + dataCount * 8); } else if (key === 'depGraph') { strength = 60; } withStrength[key] = { ...value, strength }; } // 按 strength 降序排列(通过 entries+sort 重构对象键顺序) const sorted = Object.fromEntries(Object.entries(withStrength).sort(([, a], [, b]) => b.strength - a.strength)); return sorted; } // ── AST 压缩 ──────────────────────────────────────────────── /** * 压缩 AST 数据以控制 Mission Briefing 体积 * * @param astProjectSummary analyzeProject() 返回值 * @param fileCount 项目文件数 * @returns 压缩后的 AST 数据 */ function compressAstForBriefing(astProjectSummary, fileCount) { if (!astProjectSummary) { return { available: false, classes: [], protocols: [], categories: [], patterns: {} }; } const classes = astProjectSummary.classes || []; const protocols = astProjectSummary.protocols || []; const categories = astProjectSummary.categories || []; // 确定压缩级别 let topN; let compressionLevel; if (fileCount < SIZE_THRESHOLDS.S) { topN = classes.length; // 完整返回 compressionLevel = 'none'; } else if (fileCount < SIZE_THRESHOLDS.M) { topN = 50; compressionLevel = 'medium'; } else { topN = 30; compressionLevel = 'high'; } // ObjC 去重: @interface/@implementation/@extension 会产生同名 class 条目 // 合并策略: 保留 methodCount 最高的条目,合并 protocols 和 superclass const classMap = new Map(); for (const c of classes) { const existing = classMap.get(c.name); if (!existing) { classMap.set(c.name, { ...c }); } else { // 保留更大的 methodCount if ((c.methodCount || 0) > (existing.methodCount || 0)) { existing.methodCount = c.methodCount; } // 合并 superclass(优先非空值) if (!existing.superclass && c.superclass) { existing.superclass = c.superclass; } // 合并 protocols const existProtos = new Set(existing.protocols || existing.conformedProtocols || []); for (const p of c.protocols || c.conformedProtocols || []) { existProtos.add(p); } existing.protocols = [...existProtos]; // 合并 file(保留第一个) if (!existing.file && (c.file || c.relativePath)) { existing.file = c.file || c.relativePath; } } } const dedupedClasses = [...classMap.values()]; // 按 methodCount 降序排序,取 top-N const sortedClasses = dedupedClasses .sort((a, b) => (b.methodCount || 0) - (a.methodCount || 0)) .slice(0, topN); const compressedClasses = sortedClasses.map((c) => ({ name: c.name, kind: c.kind || 'class', superclass: c.superclass || null, file: c.file || c.relativePath || null, methodCount: c.methodCount || c.methods?.length || 0, protocols: c.protocols || c.conformedProtocols || [], })); const compressedProtocols = protocols.slice(0, topN).map((p) => ({ name: p.name, file: p.file || p.relativePath || null, methodCount: p.methodCount || p.methods?.length || 0, conformers: p.conformers || [], })); const compressedCategories = categories.slice(0, topN).map((cat) => ({ baseClass: cat.baseClass || cat.extendedClass, name: cat.name || '', file: cat.file || cat.relativePath || null, methods: (cat.methods || []) .map((m) => (typeof m === 'string' ? m : m.name)) .slice(0, 10), })); // ── 结构化 summary: 含 kindDistribution + insight ── const kindDist = {}; for (const c of dedupedClasses) { const k = c.kind || 'class'; kindDist[k] = (kindDist[k] || 0) + 1; } const totalTypes = dedupedClasses.length; const kindParts = Object.entries(kindDist) .sort((a, b) => b[1] - a[1]) .map(([k, v]) => `${v} ${k}`); const summaryText = `${totalTypes} types (${kindParts.join(', ')}), ${protocols.length} protocols, ${categories.length} ${categories.length > 0 && categories[0]?.baseClass ? 'categories' : 'extensions'}, ${astProjectSummary.projectMetrics?.totalMethods || 0} methods`; // 生成 insight const valueTypeCount = (kindDist.struct || 0) + (kindDist.enum || 0); const refTypeCount = kindDist.class || 0; const actorCount = kindDist.actor || 0; let insight = ''; if (totalTypes > 0) { const vtRatio = Math.round((valueTypeCount / totalTypes) * 100); if (vtRatio >= 60) { insight = `Value types (struct+enum) account for ${vtRatio}% — project favors value semantics`; } else if (refTypeCount > valueTypeCount) { insight = `Reference types (class) account for ${Math.round((refTypeCount / totalTypes) * 100)}% — OOP-heavy codebase`; } else { insight = `Balanced mix of value types (${vtRatio}%) and reference types (${Math.round((refTypeCount / totalTypes) * 100)}%)`; } if (actorCount > 0) { insight += `; ${actorCount} actors indicate structured concurrency adoption`; } } const summary = { text: summaryText, kindDistribution: kindDist, insight, }; // ── 压缩 patternStats: 保留计数 + 代表性类名 ── const rawPatterns = astProjectSummary.patternStats || {}; const compressedPatterns = {}; for (const [key, val] of Object.entries(rawPatterns)) { if (typeof val === 'number' || typeof val === 'string' || typeof val === 'boolean') { compressedPatterns[key] = val; } else if (Array.isArray(val)) { compressedPatterns[key] = val.length; // 数组 → 计数 } else if (val && typeof val === 'object') { const sub = {}; for (const [sk, sv] of Object.entries(val)) { if (typeof sv === 'number' || typeof sv === 'string' || typeof sv === 'boolean') { sub[sk] = sv; } else if (Array.isArray(sv)) { // instances 数组: 提取 top-3 类名作为 representatives if (sk === 'instances' && sv.length > 0 && typeof sv[0] === 'object') { sub[sk] = sv.length; const classNames = sv .map((inst) => inst.className || '') .filter(Boolean); const unique = [...new Set(classNames)].slice(0, 3); if (unique.length > 0) { sub.representatives = unique.join(', '); } } else { sub[sk] = sv.length; } } else if (sv && typeof sv === 'object') { sub[sk] = Object.keys(sv).length; } } compressedPatterns[key] = sub; } } return { available: true, compressionLevel, summary, classes: compressedClasses, protocols: compressedProtocols, categories: compressedCategories, patterns: compressedPatterns, metrics: astProjectSummary.projectMetrics ? { totalMethods: astProjectSummary.projectMetrics.totalMethods, avgMethodsPerClass: astProjectSummary.projectMetrics.avgMethodsPerClass, maxNestingDepth: astProjectSummary.projectMetrics.maxNestingDepth, complexMethods: astProjectSummary.projectMetrics.complexMethods?.length || 0, longMethods: astProjectSummary.projectMetrics.longMethods?.length || 0, } : null, }; } /** 压缩 Code Entity Graph */ function summarizeEntityGraph(codeEntityResult) { if (!codeEntityResult) { return null; } return { totalEntities: codeEntityResult.entitiesUpserted || 0, totalEdges: codeEntityResult.edgesCreated || 0, }; } /** * 压缩 Call Graph 结果 * @param callGraphResult CodeEntityGraph.populateCallGraph() 返回值 */ function summarizeCallGraph(callGraphResult) { if (!callGraphResult) { return null; } return { methodEntities: callGraphResult.entitiesUpserted || 0, callEdges: callGraphResult.edgesCreated || 0, durationMs: callGraphResult.durationMs || 0, }; } /** 压缩 Guard 审计结果 */ function summarizeGuardFindings(guardAudit) { if (!guardAudit) { return null; } // 按 ruleId 聚合 violations const ruleMap = {}; // helper: 将单个 violation 累加到 ruleMap const addViolation = (v, examplePrefix) => { const ruleId = v.ruleId || 'unknown'; if (!ruleMap[ruleId]) { ruleMap[ruleId] = { ruleId, count: 0, example: null }; } ruleMap[ruleId].count++; if (!ruleMap[ruleId].example) { ruleMap[ruleId].example = `${examplePrefix} — ${v.message}`; } }; // 1) Per-file violations for (const fileResult of guardAudit.files || []) { for (const v of fileResult.violations || []) { addViolation(v, `${fileResult.filePath}:${v.line || '?'}`); } } // 2) Cross-file violations(之前被遗漏) for (const v of guardAudit.crossFileViolations || []) { const loc = v.locations?.[0]; const prefix = loc ? `${loc.filePath}:${loc.line || '?'}` : '(cross-file)'; addViolation(v, prefix); } // 取 top-5 violations const topViolations = Object.values(ruleMap) .sort((a, b) => b.count - a.count) .slice(0, 5); const totalErrors = guardAudit.summary?.totalErrors || 0; const totalViolations = guardAudit.summary?.totalViolations || 0; // §V2: 单独高亮跨文件违规 — 这类违规通常涉及架构层级或模块边界问题 const crossFileIssues = (guardAudit.crossFileViolations || []).map((v) => ({ ruleId: v.ruleId, message: v.message, locations: v.locations?.slice(0, 3), severity: v.severity || 'warning', })); return { totalViolations, errors: totalErrors, warnings: totalViolations - totalErrors, topViolations, ...(crossFileIssues.length > 0 ? { crossFileIssues } : {}), }; } // ── 执行计划构建 ───────────────────────────────────────────── /** * 根据激活的维度构建执行计划 * @param activeDimensions 激活的维度定义 * @returns executionPlan 对象 */ function buildExecutionPlan(activeDimensions) { const scheduler = new TierScheduler(); const tiers = scheduler.getTiers(); const activeDimIds = new Set(activeDimensions.map((d) => d.id)); const tierLabels = [ '基础数据层', '规范 + 设计 + 网络', '核心质量', '领域专项', '终端优化 + 总结', ]; const tierNotes = [ '这些维度相互独立,可以任意顺序分析。产出的上下文将帮助后续维度。', '建议利用 Tier 1 中了解到的项目结构和代码特征。', '利用前两层建立的架构和规范上下文深入分析。', '各维度相对独立,可充分利用并行能力。', 'agent-guidelines 应综合前序所有维度的发现。', ]; const plan = tiers .map((tierDimIds, index) => { const filteredDims = tierDimIds.filter((id) => activeDimIds.has(id)); if (filteredDims.length === 0) { return null; } return { tier: index + 1, // 1-based, 与 tier-scheduler.js 一致 label: tierLabels[index] || `Tier ${index + 1}`, dimensions: filteredDims, note: tierNotes[index] || '', }; }) .filter(Boolean); // 处理不在任何 tier 中的维度(Enhancement Pack 追加的)— 按 tierHint 归入 const scheduledIds = new Set(tiers.flat()); const unscheduled = activeDimensions.filter((d) => !scheduledIds.has(d.id)); if (unscheduled.length > 0 && plan.length > 0) { for (const dim of unscheduled) { const hint = typeof dim.tierHint === 'number' ? dim.tierHint : 1; const targetIdx = Math.max(0, Math.min(hint - 1, plan.length - 1)); plan[targetIdx]?.dimensions.push(dim.id); } } return { tiers: plan, totalDimensions: activeDimensions.length, workflow: '对每个维度: (1) 用你的原生能力阅读代码分析 → (2) 调用 autosnippet_submit_knowledge_batch 批量提交候选(**每维度最少 3 条,目标 5 条**,将不同关注点拆分为独立候选,1-2 条视为不合格) → (3) 调用 autosnippet_dimension_complete 完成维度(必须传 referencedFiles=[分析过的文件路径] 和 keyFindings=[3-5条关键发现])', }; } // ── Architecture Overview 自动推断 ──────────────────────── /** 知名外部依赖的角色映射 */ const KNOWN_DEPENDENCIES = { // Swift / iOS alamofire: 'HTTP networking', moya: 'Network abstraction over Alamofire', rxswift: 'Reactive programming (ReactiveX)', rxcocoa: 'RxSwift UIKit bindings', combine: 'Apple reactive framework', kingfisher: 'Image downloading & caching', sdwebimage: 'Image downloading & caching', snapkit: 'Auto Layout DSL', lottie: 'Animation rendering', realm: 'Mobile database', coredata: 'Apple persistence framework', swiftui: 'Declarative UI framework', // JavaScript / TypeScript react: 'UI component framework', vue: 'Progressive UI framework', angular: 'Full-featured UI framework', express: 'HTTP server framework', nestjs: 'Enterprise Node.js framework', axios: 'HTTP client', prisma: 'Database ORM', sequelize: 'SQL ORM', mongoose: 'MongoDB ODM', tailwindcss: 'Utility-first CSS', webpack: 'Module bundler', vite: 'Frontend build tool', jest: 'Testing framework', vitest: 'Vite-native testing', redux: 'State management', zustand: 'Lightweight state management', // Go gin: 'HTTP web framework', echo: 'HTTP web framework', gorm: 'Go ORM', cobra: 'CLI framework', // Python django: 'Full-stack web framework', flask: 'Micro web framework', fastapi: 'Async web framework', sqlalchemy: 'SQL toolkit & ORM', pytorch: 'Deep learning framework', tensorflow: 'Machine learning framework', pandas: 'Data analysis library', numpy: 'Numerical computing', }; /** * 从 targets + depGraph + localPackageModules 自动推断架构概览 */ function buildArchitectureOverview(targets, depGraphData, localPackageModules) { if (!targets || targets.length === 0) { return null; } // ── 分层: 按 inferredRole 分组 ── const roleGroups = {}; for (const t of targets) { const role = t.inferredRole || 'unknown'; if (!roleGroups[role]) { roleGroups[role] = { modules: [], fileCount: 0 }; } roleGroups[role].modules.push(t.name); roleGroups[role].fileCount += t.fileCount || 0; } // 层级命名映射 const ROLE_LAYER_MAP = { app: { name: 'App Shell', priority: 0, role: 'Application entry point, coordinators, DI assembly', }, core: { name: 'Core Infrastructure', priority: 2, role: 'Shared infrastructure libraries, base classes, utilities', }, networking: { name: 'Networking', priority: 2, role: 'Network client, middleware, API definitions', }, feature: { name: 'Feature Modules', priority: 1, role: 'Per-feature UI, view models, business logic', }, ui: { name: 'UI Components', priority: 2, role: 'Shared UI components, themes, extensions' }, test: { name: 'Tests', priority: 3, role: 'Unit tests, integration tests, mocks' }, unknown: { name: 'Other', priority: 3, role: 'Uncategorized modules' }, }; const layers = []; for (const [role, group] of Object.entries(roleGroups)) { if (role === 'test') { continue; } // 测试模块不加入架构层 const layerDef = ROLE_LAYER_MAP[role] || ROLE_LAYER_MAP.unknown; // 合并同优先级的层 const existing = layers.find((l) => l.name === layerDef.name); if (existing) { existing.modules.push(...group.modules); existing.fileCount += group.fileCount; } else { layers.push({ name: layerDef.name, modules: [...group.modules], fileCount: group.fileCount, role: layerDef.role, }); } } // 按 priority 排序 (App Shell → Features → Core → Other) layers.sort((a, b) => { const pa = Object.values(ROLE_LAYER_MAP).find((v) => v.name === a.name)?.priority ?? 99; const pb = Object.values(ROLE_LAYER_MAP).find((v) => v.name === b.name)?.priority ?? 99; return pa - pb; }); // ── 外部依赖识别 ── const externalDeps = []; const localModuleNames = new Set(targets.map((t) => t.name)); if (depGraphData?.nodes) { for (const n of depGraphData.nodes) { const id = typeof n === 'string' ? n : n.id || ''; const label = typeof n === 'string' ? n : n.label || id; if (!localModuleNames.has(id) && !localModuleNames.has(label)) { const knownRole = KNOWN_DEPENDENCIES[label.toLowerCase()]; externalDeps.push({ name: label, role: knownRole || 'third-party dependency', }); } } } // ── 关键洞察 ── const insights = []; const totalFiles = targets.reduce((s, t) => s + (t.fileCount || 0), 0); const featureGroup = roleGroups.feature; const coreGroup = roleGroups.core; const networkGroup = roleGroups.networking; // 本地子包占比 if (localPackageModules && localPackageModules.length > 0) { const pkgFiles = localPackageModules.reduce((s, m) => s + m.fileCount, 0); const pct = totalFiles > 0 ? Math.round((pkgFiles / totalFiles) * 100) : 0; insights.push(`${localPackageModules.length} local packages provide ${pct}% of the codebase (${pkgFiles}/${totalFiles} files)`); } // Feature 模块特征 if (featureGroup) { const avgFiles = Math.round(featureGroup.fileCount / featureGroup.modules.length); if (avgFiles <= 5) { insights.push(`Feature modules are thin (avg ${avgFiles} files) — business logic likely concentrates in core infrastructure`); } else { insights.push(`Feature modules average ${avgFiles} files each — self-contained feature architecture`); } } // 最大基础设施模块 if (coreGroup || networkGroup) { const infraModules = [...(coreGroup?.modules || []), ...(networkGroup?.modules || [])]; const heaviest = targets .filter((t) => infraModules.includes(t.name)) .sort((a, b) => (b.fileCount || 0) - (a.fileCount || 0)); if (heaviest.length > 0 && heaviest[0].fileCount) { insights.push(`${heaviest[0].name} (${heaviest[0].fileCount} files) is the heaviest infrastructure module`); } } // 推断架构风格 let style = 'Monolithic application'; if (localPackageModules && localPackageModules.length >= 2) { style = 'Modular monolith (local packages)'; } else if (targets.length >= 5 && featureGroup && featureGroup.modules.length >= 3) { style = 'Feature-modular architecture'; } return { style, layers, externalDeps, keyInsights: insights }; } /** * 从外部依赖图中提取技术栈信息 */ function buildTechnologyStack(depGraphData, targets) { if (!depGraphData?.nodes || !depGraphData?.edges) { return null; } const localModuleNames = new Set(targets.map((t) => t.name)); const stack = []; for (const n of depGraphData.nodes) { const id = typeof n === 'string' ? n : n.id || ''; const label = typeof n === 'string' ? n : n.label || id; if (localModuleNames.has(id) || localModuleNames.has(label)) { continue; } const role = KNOWN_DEPENDENCIES[label.toLowerCase()] || 'third-party dependency'; // 找出哪些模块依赖它 const usedBy = (depGraphData.edges || []) .filter((e) => { const edge = e; return edge.to === id || edge.to === label; }) .map((e) => e.from) .filter((f) => localModuleNames.has(f)) .slice(0, 5); stack.push({ name: label, role, usedBy }); } return stack.length > 0 ? stack : null; } /** * 提取项目关键抽象 — 从继承热点、协议遵从数、模块入口类中识别 */ function buildKeyAbstractions(astData, targets) { if (!astData) { return null; } const classes = astData.classes || []; const protocols = astData.protocols || []; const abstractions = []; // §1: 高继承热点 — 被多个子类继承的基类 const subclassCount = {}; for (const cls of classes) { if (cls.superclass) { subclassCount[cls.superclass] = (subclassCount[cls.superclass] || 0) + 1; } } const topBases = Object.entries(subclassCount) .filter(([, count]) => count >= 2) .sort((a, b) => b[1] - a[1]) .slice(0, 5); for (const [baseName, count] of topBases) { const baseCls = classes.find((c) => c.name === baseName); const module = baseCls?.targetName || _inferModule(baseCls?.file || baseCls?.relativePath, targets); abstractions.push({ name: baseName, kind: baseCls?.kind || 'class', module, significance: `Base class with ${count} subclasses`, detail: `Subclasses: ${classes .filter((c) => c.superclass === baseName) .map((c) => c.name) .slice(0, 5) .join(', ')}`, }); } // §2: 高方法数类 — 复杂度热点 const methodHeavy = classes .filter((c) => (c.methodCount || 0) >= 15) .sort((a, b) => (b.methodCount || 0) - (a.methodCount || 0)) .slice(0, 3); for (const cls of methodHeavy) { // 跳过已在继承热点中出现的 if (abstractions.some((a) => a.name === cls.name)) { continue; } const module = cls.targetName || _inferModule(cls.file || cls.relativePath, targets); abstractions.push({ name: cls.name, kind: cls.kind || 'class', module, significance: `Complexity hotspot (${cls.methodCount} methods)`, detail: cls.superclass ? `extends ${cls.superclass}` : 'root class', }); } // §3: 高遵从协议 — 核心抽象接口 const protoWithConformers = protocols .filter((p) => (p.conformers?.length || 0) >= 2 || (p.methodCount || 0) >= 3) .sort((a, b) => (b.conformers?.length || 0) - (a.conformers?.length || 0)) .slice(0, 5); for (const proto of protoWithConformers) { const module = proto.targetName || _inferModule(proto.file || proto.relativePath, targets); const conformerCount = proto.conformers?.length || 0; abstractions.push({ name: proto.name, kind: 'protocol', module, significance: conformerCount > 0 ? `Protocol with ${conformerCount} conformers` : `Protocol with ${proto.methodCount || 0} method requirements`, detail: conformerCount > 0 ? `Conformers: ${proto.conformers.slice(0, 5).join(', ')}` : `${proto.methodCount || 0} required methods`, }); } return abstractions.length > 0 ? abstractions.slice(0, 10) : null; } /** 从文件路径推断模块名 */ function _inferModule(filePath, targets) { if (!filePath) { return 'unknown'; } for (const t of targets) { if (filePath.includes(t.name)) { return t.name; } } return filePath.split('/')[0] || 'unknown'; } // ── Panorama 摘要构建 ────────────────────────────────────── /** * 从 PanoramaResult 提取 layers / couplingHotspots / cycles / gaps * 用于注入 MissionBriefing,使外部 Agent 获得项目全景视野 */ // ── 本地子包/模块 — mustCoverModules ──────────────────────── /** * 构建 mustCoverModules 段落 — 标记来自本地子包的基础设施模块 * * 语言无关:只依赖 Discoverer 返回的 target metadata 中的 isLocalPackage 标记。 * 无论 SPM (Swift)、monorepo (TS)、Gradle subproject (Java/Kotlin), * 只要某 target 来自非主 projectRoot 的子目录,就被视为本地子包。 * * @param localPackageModules Phase 1 收集的子包信息 * @returns mustCoverModules 段落 */ function buildMustCoverModules(localPackageModules) { if (!localPackageModules || localPackageModules.length === 0) { return null; } return { totalLocalPackages: localPackageModules.length, modules: localPackageModules.map((m) => ({ name: m.name, packageName: m.packageName, fileCount: m.fileCount, inferredRole: m.inferredRole, keyFiles: m.keyFiles || [], })), instruction: '【强制覆盖】以下本地子包/模块是项目的基础设施层,包含核心抽象和共享服务。' + '每个维度分析时必须同时覆盖主项目代码和这些子包代码。' + '提交的知识候选中必须包含子包源码的完整相对路径和行号(如 Packages/AOXNetworkKit/Sources/.../NetworkClient.swift:42),' + '不得仅引用主项目中对子包的调用,而忽略子包内部的实现细节。' + '对于 architecture、code-pattern、best-practice 维度,至少要有 1 条候选直接引用子包的核心实现文件。', }; } function summarizePanorama(panoramaResult) { if (!panoramaResult) { return null; } try { // PanoramaResult.layers: LayerHierarchy { levels: LayerLevel[] } const layerHierarchy = panoramaResult.layers; const layers = layerHierarchy?.levels ?? []; // PanoramaResult.modules: Map<string, PanoramaModule> const modules = panoramaResult.modules; const couplingHotspots = []; if (modules instanceof Map) { for (const [, mod] of modules) { if (mod.fanIn >= 10 || mod.fanOut >= 10) { couplingHotspots.push({ module: mod.name, fanIn: mod.fanIn, fanOut: mod.fanOut }); } } couplingHotspots.sort((a, b) => b.fanIn + b.fanOut - (a.fanIn + a.fanOut)); } // PanoramaResult.cycles: CyclicDependency[] const cycles = panoramaResult.cycles ?? []; // PanoramaResult.gaps: KnowledgeGap[] (dimension-based) const gaps = panoramaResult.gaps ?? []; return { layers: layers.slice(0, 10), couplingHotspots: couplingHotspots.slice(0, 10), cyclicDependencies: cycles.slice(0, 10), knowledgeGaps: gaps.slice(0, 20), }; } catch { return null; } } // ── Mission Briefing 主构建函数 ────────────────────────────── /** * 构建 Mission Briefing * * @param opts.projectMeta 项目元数据 * @param opts.astData analyzeProject() 原始结果 * @param opts.codeEntityResult CodeEntityGraph.populateFromAst() 结果 * @para