UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

406 lines (389 loc) 20.7 kB
/** * scan-prompts.js — scanKnowledge 任务配置 + 统一管线工厂 + 关系发现管线 * * scan pipeline (extract/summarize): * 共享 Insight Pipeline (Analyze → QualityGate → Produce → RejectionGate), * Analyze 使用与冷启动一致的 ANALYST_SYSTEM_PROMPT + ExplorationTracker 四阶段管理, * Produce 阶段均为工具驱动 (collect_scan_recipe),与冷启动 submit_knowledge 对齐。 * * relations pipeline (独立): * 知识图谱关系发现: Explore → Synthesize 两阶段, * 通过查询知识库 + 读取源码发现条目间语义关系。 * * @module scan-prompts */ import { ANALYST_SYSTEM_PROMPT } from './insight-analyst.js'; import { buildRetryPrompt, insightGateEvaluator } from './insight-gate.js'; import { buildCodeContextSection, producerRejectionGateEvaluator } from './insight-producer.js'; /** * task → Produce 阶段配置 (extract + summarize) * * 两种 task 均为工具驱动 (collect_scan_recipe),Recipe 格式与冷启动 submit_knowledge 对齐: * - extract: 多文件 target 扫描 → 多个 Recipe * - summarize: 单文件/代码片段 → 1~2 个 Recipe */ export const SCAN_TASK_CONFIGS = { // ─── extract: Recipe 提取(工具驱动,与冷启动 submit_knowledge 字段对齐) ───── extract: { producePrompt: `你是知识管理专家。你会收到一段代码分析文本,需要将其中的知识点转化为结构化的知识候选。 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 collect_scan_recipe 调用。 每个候选必须: 1. 有清晰的标题 (描述知识点的核心,使用项目真实类名,不以项目名开头) 2. 有项目特写风格的正文 (content.markdown 字段,结合代码展示) 3. 标注相关文件的完整相对路径 + 行号 (reasoning.sources,如 ["Packages/ModuleName/Sources/.../FileName.swift"]) 4. 选择正确的 kind (rule/pattern/fact) 5. 提供完整的 Cursor 交付字段 (trigger, doClause, whenClause 等) 6. 标注所属模块/包名(特别是来自本地子包的知识) ## 「项目特写」写作要求(content.markdown) content.markdown 字段必须是「项目特写」: 1. **项目选择了什么** — 采用了哪种写法/模式/约定 2. **为什么这样选** — 统计分布、占比、历史决策 3. **项目禁止什么** — 反模式、已废弃写法 4. **新代码怎么写** — 可直接复制使用的代码模板 + 来源标注 (来源: Full/Relative/Path/FileName.ext:行号) ## 工作流程 1. 阅读分析文本,识别每个独立的知识点/发现 2. 用 read_project_file 批量获取关键代码片段: read_project_file({ filePaths: ["Full/Path/To/FileA.swift", "Full/Path/To/FileB.swift"], maxLines: 80 }) 3. 立刻调用 collect_scan_recipe 提交 4. 重复直到分析中的所有知识点都已提交 ## 关键规则 - 分析中的每个要点/段落都应转化为至少一个候选 - read_project_file 支持 filePaths 数组批量读取多个文件,一次调用完成 - reasoning.sources 必须是非空数组,填写文件的完整相对路径(从项目根目录开始),禁止只写文件名 - content.markdown 中的来源标注必须使用完整相对路径: (来源: Full/Path/FileName.ext:行号) - 如果分析提到了 3 个模式,就应该提交 3 个候选,不要合并 - 禁止: 不要搜索新文件、不要做额外分析,专注于格式化和提交 - 【跨维度去重】每条候选必须聚焦当前维度独有的视角,不得将同一知识点换个说法重复提交到不同维度。宁可少提交也不要充数 容错规则: - 如果 read_project_file 返回"文件不存在"或错误,不要重试同一文件的其他路径变体 - 文件读取失败时,直接使用分析文本中已有的代码和描述来提交候选 - 永远不要因为文件读取失败而跳过知识点 — 分析文本已经包含足够信息 - 先提交候选,再考虑是否需要读取更多代码(提交优先于验证)`, fallback: (label) => ({ targetName: label, extracted: 0, recipes: [] }), }, // ─── summarize: 代码摘要(工具驱动,与 extract 管线对齐) ────── summarize: { producePrompt: `你是技术文档专家。你会收到一段代码分析文本,需要将其转化为高质量的知识候选。 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 collect_scan_recipe 调用。 这是单文件/代码片段的深度分析,提交一个(或少量)高质量的知识候选: 1. 清晰的标题(描述代码的核心功能,使用项目真实类名,不以项目名开头) 2. 完整的技术文档正文(content.markdown 字段,≥200 字符) 3. 实用的使用指南(usageGuide 字段,含示例) 4. 准确的分类(category)和标签(tags) ## content.markdown 写作要求 1. **功能概述** — 这段代码做什么,解决什么问题 2. **核心实现** — 关键代码逻辑,含代码块 (\`\`\`) 3. **使用方式** — 如何调用/集成,含示例代码 4. **注意事项** — 边界条件、性能考量、已知限制 ## 工作流程 1. 阅读分析文本,理解代码的核心功能和设计决策 2. 如需验证细节,用 read_project_file 获取代码片段 3. 调用 collect_scan_recipe 提交知识候选 ## 关键规则 - 单文件通常提交 1 个候选,除非代码明确包含多个独立知识点 - reasoning.sources 必须是非空数组,填写源文件路径 - kind 选择: 优先 pattern(代码模式)或 fact(技术事实) - 必填: trigger (@kebab-case)、doClause (英文祈使句)、content.rationale - content.markdown 必须包含代码块,展示核心实现`, fallback: (label) => ({ targetName: label, extracted: 0, recipes: [] }), }, }; // ────────────────────────────────────────────────────────────────── // 统一管线工厂 — 生成标准 4 阶段 Pipeline (与冷启动对齐) // ────────────────────────────────────────────────────────────────── /** * 构建 scanKnowledge 的标准 4 阶段 Pipeline stages * * 与冷启动 orchestrator 完全对齐: * 1. analyze — 代码分析 (ExplorationTracker 四阶段管理) * 2. quality_gate — 分析质量门控 (insightGateEvaluator + buildAnalysisArtifact) * 3. produce — 知识生产 (artifact-aware promptBuilder + 工具驱动提交) * 4. rejection_gate — 拒绝率门控 (producerRejectionGateEvaluator) * * 与冷启动对齐的关键节点: * - quality_gate 通过 strategyContext.activeContext 走 buildAnalysisArtifact * (而非降级的 buildAnalysisReport),保留 findings/evidenceMap/negativeSignals * - produce 使用 promptBuilder (而非 promptTransform), * 从 gateArtifact 注入结构化发现和代码证据到 prompt * - strategyContext 需要包含 activeContext / outputType / dimId * (由 AgentFactory.buildSystemContext 设置) * * @param opts.task 任务类型 * @param opts.producePrompt Produce 阶段 systemPrompt * @param opts.analyzeCaps Analyze 阶段 capabilities * @param opts.produceCaps Produce 阶段 capabilities * @param [opts.files] 源文件 (fallback prompt 用) * @param [opts.analyzeMaxIter=24] Analyze 最大迭代 * @returns PipelineStrategy stages 数组 */ export function buildScanPipelineStages({ task, producePrompt, analyzeCaps, produceCaps, files, analyzeMaxIter = 24, } = {}) { // ── Stage 1: Analyze ── const analyzeStage = { name: 'analyze', capabilities: analyzeCaps, budget: { maxIterations: analyzeMaxIter, maxTokens: 8192, temperature: 0.3, timeoutMs: 300_000, // 5 min (与冷启动对齐) }, systemPrompt: ANALYST_SYSTEM_PROMPT, retryPromptBuilder: (retryCtx, _origPrompt, prev) => { const prevAnalysis = prev.analyze?.reply || ''; const retryHint = buildRetryPrompt(retryCtx.reason ?? ''); return `${prevAnalysis}\n\n⚠️ 上述分析未通过质量检查: ${retryCtx.reason}\n\n${retryHint}`; }, }; // ── Stage 2: Quality Gate ── // insightGateEvaluator 读取 strategyContext.activeContext: // - 有 activeContext → buildAnalysisArtifact (完整: findings/evidenceMap/negativeSignals) // - 无 activeContext → buildAnalysisReport (降级: 仅文本 + 文件列表) // buildSystemContext 通过 trace 设置 activeContext,确保走完整路径 const qualityGateStage = { name: 'quality_gate', gate: { evaluator: insightGateEvaluator, maxRetries: 1, }, }; // ── Stage 3: Produce ── // extract 和 summarize 都是工具驱动 (collect_scan_recipe) const isToolDriven = task === 'extract' || task === 'summarize'; const isSummarize = task === 'summarize'; const submitToolNames = isToolDriven ? ['collect_scan_recipe'] : []; const produceStage = { name: 'produce', submitToolName: 'collect_scan_recipe', // 透传给 ExplorationTracker nudge 文本 pipelineType: 'scan', // 统一场景判别标识 capabilities: produceCaps, budget: { maxIterations: isSummarize ? 12 : 24, temperature: 0.2, timeoutMs: isSummarize ? 120_000 : 180_000, // 显式传入 tracker 阈值,避免依赖默认值 (softSubmitLimit:8) 导致转换不触发 maxSubmits: isSummarize ? 3 : 10, softSubmitLimit: isSummarize ? 2 : 8, idleRoundsToExit: 2, }, systemPrompt: producePrompt, // 使用 promptBuilder (而非 promptTransform) — 与冷启动对齐 // promptBuilder 接收 gateArtifact (来自 quality_gate 的 AnalysisArtifact), // 注入结构化 findings + 代码证据到 prompt,而非仅传入 analyze.reply 纯文本 promptBuilder: (ctx) => { return buildScanProducerPrompt(ctx, files, task); }, // retry 配置 (拒绝率过高时缩减预算) ...(isToolDriven ? { retryBudget: { maxIterations: isSummarize ? 3 : 5, temperature: 0.3, timeoutMs: isSummarize ? 60_000 : 120_000, }, retryPromptBuilder: (retryCtx, _origPrompt, prev) => { const prevProduce = prev.produce; const submitCalls = (prevProduce?.toolCalls || []).filter((tc) => submitToolNames.includes((tc.tool || tc.name))); const rejected = submitCalls.filter((tc) => { const res = tc.result; if (!res) { return false; } if (typeof res === 'string') { return res.includes('rejected') || res.includes('error'); } return res.status === 'rejected' || res.status === 'error'; }).length; return `你的 ${rejected} 个提交被拒绝了。请根据拒绝原因改进后重新提交,确保: 1. content 必须是对象: { markdown: "...", rationale: "...", pattern: "..." } 2. content.markdown 字段 ≥ 200 字符,含代码块 (\`\`\`) 3. content.rationale 必填 — 设计原理说明 4. reasoning.sources 必须是非空数组 5. 标题使用项目真实类名,不以项目名开头 6. 必填: trigger (@kebab-case)、kind (rule/pattern/fact)、doClause (英文祈使句)`; }, skipOnDegrade: true, } : { skipOnDegrade: true, }), }; const stages = [analyzeStage, qualityGateStage, produceStage]; // ── Stage 4: Rejection Gate (仅工具驱动模式) ── if (isToolDriven) { stages.push({ name: 'rejection_gate', gate: { evaluator: (source, phaseResults, ctx) => producerRejectionGateEvaluator(source, phaseResults, { ...ctx, submitToolNames, }), maxRetries: 1, }, skipOnDegrade: true, }); } return stages; } // ────────────────────────────────────────────────────────────────── // Scan Producer Prompt Builder — artifact-aware (与冷启动 buildProducerPromptV2 对齐) // ────────────────────────────────────────────────────────────────── /** * 构建 scan produce 阶段的 prompt — 从 gateArtifact 注入结构化信息 * * 与冷启动 buildProducerPromptV2 对齐的关键: * - 优先使用 gateArtifact 中的 findings (结构化发现) * - 注入 evidenceMap (Analyst 已读取的代码证据) * - 注入 negativeSignals (搜索但未找到的模式) * - 当 artifact 不可用时 fallback 到 analyze.reply 纯文本 * * @param ctx promptBuilder 上下文 (含 gateArtifact, phaseResults, ...) * @param [files] 源文件 (fallback 用) * @param task 任务类型 */ function buildScanProducerPrompt(ctx, files, task) { const artifact = ctx.gateArtifact; const analysis = ctx.phaseResults?.analyze?.reply || ''; // ── 有完整 artifact 时 (走 buildAnalysisArtifact 路径) ── if (artifact?.analysisText) { const parts = []; // §1 分析文本 parts.push(`将以下代码分析转化为 collect_scan_recipe 调用。\n\n---\n${artifact.analysisText}\n---`); // §2 结构化发现 (来自 ActiveContext scratchpad) if (artifact.findings && artifact.findings.length > 0) { const findingLines = ['## 关键发现 (Analyst 已确认)']; const sorted = [...artifact.findings].sort((a, b) => (b.importance || 0) - (a.importance || 0)); for (const f of sorted) { const badge = (f.importance || 0) >= 8 ? '⚠️' : '📋'; findingLines.push(`${badge} **[${f.importance || 5}/10]** ${f.finding}`); if (f.evidence) { findingLines.push(` 证据: ${f.evidence}`); } } findingLines.push(''); findingLines.push('☝️ 上述每个发现都应至少转化为一个候选。'); parts.push(findingLines.join('\n')); } // §3 代码证据 (来自 EvidenceCollector) if (artifact.evidenceMap && artifact.evidenceMap.size > 0) { const codeContext = buildCodeContextSection(artifact.evidenceMap); if (codeContext) { parts.push(codeContext); } } // §4 负空间信号 (搜索但未找到的模式 — 不要猜测) if (artifact.negativeSignals && artifact.negativeSignals.length > 0) { const nsLines = ['## ⛔ 不存在的模式 (不要猜测)']; for (const ns of artifact.negativeSignals.slice(0, 5)) { nsLines.push(`- "${ns.searchPattern}" — ${ns.implication}`); } parts.push(nsLines.join('\n')); } // §5 引用文件 if (artifact.referencedFiles && artifact.referencedFiles.length > 0) { parts.push(`分析中引用的关键文件: ${artifact.referencedFiles.slice(0, 15).join(', ')}`); } return parts.join('\n\n'); } // ── Fallback: 无 artifact 时退回纯文本 (不应该发生,但防御性保留) ── if (analysis.length >= 200) { return `将以下代码分析转化为结构化输出。\n\n## 代码分析\n${analysis}`; } // Fallback: analyze reply 不足时直接提供源代码 const fileCtx = (files || []) .slice(0, 15) .map((f) => { const body = (f.content || '').length > 1200 ? `${(f.content ?? '').slice(0, 1200)}\n// ... (truncated)` : f.content || ''; return `### ${f.relativePath || f.name}\n\`\`\`\n${body}\n\`\`\``; }) .join('\n\n'); const preamble = analysis ? `## 部分分析\n${analysis}\n\n` : ''; return `${preamble}分析以下 ${files?.length || 0} 个源文件,提取知识 Recipe。\n\n${fileCtx}`; } // ────────────────────────────────────────────────────────────────── // Relations Pipeline — 知识图谱关系发现(独立管线) // ────────────────────────────────────────────────────────────────── /** Explore 阶段: 查询知识库,分析条目间关联 */ const RELATIONS_EXPLORE_PROMPT = `你是知识图谱架构师。你的任务是探索项目知识库中的知识条目,发现它们之间的语义关系。 ## 工作流程 1. 使用 search_knowledge 查询知识库中的条目分类 2. 逐组分析相关知识条目的内容、依赖、关联代码 3. 使用 read_project_file 验证跨条目的代码引用关系 4. 详细记录发现的所有关系及其代码证据 ## 关系类型 - requires: A 需要 B 才能正常工作 - extends: A 扩展了 B 的功能 - enforces: A 强制规范了 B 的使用方式 - depends_on: A 依赖 B - inherits: A 继承自 B - implements: A 实现了 B 的接口/协议 - calls: A 调用了 B - prerequisite: 理解 A 之前需要先了解 B ## 分析要求 - 每个关系必须有明确的代码证据(文件名 + 代码片段) - 不要臆造不存在的关系 - 优先发现强关联(requires, implements, inherits),再发现弱关联(calls, prerequisite) - 将发现以结构化文本记录: "FromTitle → ToTitle (type): evidence"`; /** Synthesize 阶段: 将探索结果转化为 JSON */ const RELATIONS_SYNTHESIZE_PROMPT = `你是结构化数据专家。将知识图谱探索结果转化为 JSON 格式的关系列表。 ## 输出格式(纯 JSON,不含 markdown 包装) { "analyzed": 知识条目数量, "relations": [ { "from": "知识条目A精确标题", "to": "知识条目B精确标题", "type": "关系类型", "evidence": "具体代码证据描述" } ] } ## 规则 - 严格对照探索阶段的发现,不添加未被提及的关系 - type 必须是: requires / extends / enforces / depends_on / inherits / implements / calls / prerequisite - evidence 必须引用具体的代码或文件 - from 和 to 使用知识条目的精确标题`; /** * 构建知识图谱关系发现的独立 Pipeline stages * * 与 scan pipeline 不同,relations pipeline: * - 不需要源文件输入 (从知识库查询) * - 2 阶段: explore (工具驱动) → synthesize (文本输出) * - 无质量门控 (探索结果质量由工具返回保证) * * @param [opts.exploreCaps] Explore 阶段 capabilities * @param [opts.exploreMaxIter=20] Explore 最大迭代 * @returns PipelineStrategy stages 数组 */ export function buildRelationsPipelineStages({ exploreCaps = ['knowledge_production', 'code_analysis'], exploreMaxIter = 20, } = {}) { return [ { name: 'explore', capabilities: exploreCaps, budget: { maxIterations: exploreMaxIter, maxTokens: 8192, temperature: 0.3, timeoutMs: 300_000, }, systemPrompt: RELATIONS_EXPLORE_PROMPT, }, { name: 'synthesize', capabilities: [], budget: { maxIterations: 4, maxTokens: 8192, temperature: 0.2, timeoutMs: 60_000, }, systemPrompt: RELATIONS_SYNTHESIZE_PROMPT, promptTransform: (_input, prev) => { const exploration = prev.explore?.reply || ''; return `基于以下知识图谱探索结果,输出结构化关系 JSON。\n\n## 探索结果\n${exploration}`; }, }, ]; } export default SCAN_TASK_CONFIGS;