Files
metona-ollama-desktop/src/renderer/services/verification.ts
T
thzxx d3c3bacb8d v0.12.4: 安全豁免 + 搜索质量 + 上下文管控 + 版本纪律 Cleanup
Fix:
- 工作空间路径安全豁免 — AppData 拦截自相矛盾,tree/list_directory 恢复正常
- 搜索自动抓取硬上限 MAX_AUTO_FETCH=8,防止上下文爆炸 132%
- 搜索结果相关性过滤 — 跳过 Canva/ChatGPT 等无关条目
- 浏览器回退 LRU 缓存 — 同 URL 10min 内不再重复渲染
- Plan Mode 任务描述固化到系统提示词 — 压缩后不丢失目标
- ephemeral 清理时机优化 — 高负载时跳过,避免 Plan 进度丢失

Refactor:
- 全项目 v0.12.0 → v0.12.4 版本号同步(7 文件)
- 版本号纪律:仅 5 白名单文件保留版本号,其余 14 源码全部清除
- docs/DEVELOPMENT.md 据实重写(目录/架构/规范/SearXNG/Harness)
- SearXNG 抓取条数输入框改为 min=1 max=8 数字框
- 代码注释中的版本标记全部移除,仅描述功能
2026-06-23 20:45:53 +08:00

186 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Verification System — 验证系统模块
* Harness Engineering: 计算性反馈管道
*
* 在 Agent 修改文件后自动运行:
* - Linter(代码风格检查)
* - Type Checker(类型检查)
* - Test Runner(单元测试)
* - Diff Analyzer(修改范围分析)
*
* 设计原则:
* - 所有验证利用现有 run_command 工具执行
* - 验证 Hook 注册到 hooks.ts 的 post_tool 阶段
* - 验证失败的结果作为 Observation 注入 Agent 上下文
* - 可配置开关(设置面板)
*/
import { registerHook } from './hooks.js';
import type { HarnessHook } from '../types.js';
import { logInfo, logDebug, logWarn } from './log-service.js';
import type { LoopContext, HookData, HookResult } from '../types.js';
// ═══════════════════════════════════════════════════════════════
// 验证配置
// ═══════════════════════════════════════════════════════════════
export interface VerificationConfig {
/** 是否启用自动 Lint */
autoLint: boolean;
/** 是否启用自动类型检查 */
autoTypeCheck: boolean;
/** 是否启用自动测试 */
autoTest: boolean;
/** Lint 命令(留空自动检测) */
lintCommand: string;
/** 类型检查命令 */
typeCheckCommand: string;
/** 测试命令 */
testCommand: string;
}
/** 默认配置 */
const defaultConfig: VerificationConfig = {
autoLint: false,
autoTypeCheck: false,
autoTest: false,
lintCommand: '',
typeCheckCommand: '',
testCommand: '',
};
/** 运行时配置 */
let config: VerificationConfig = { ...defaultConfig };
// ═══════════════════════════════════════════════════════════════
// 文件类型检测
// ═══════════════════════════════════════════════════════════════
/** 检测文件扩展名对应的语言 */
function detectFileType(filePath: string): string | null {
const ext = filePath.split('.').pop()?.toLowerCase() || '';
const map: Record<string, string> = {
ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx',
py: 'python', rs: 'rust', go: 'go', java: 'java',
cpp: 'cpp', c: 'c', rb: 'ruby', css: 'css',
html: 'html', vue: 'vue', svelte: 'svelte',
};
return map[ext] || null;
}
/** 检测项目根目录(查找 package.json / pyproject.toml / Cargo.toml */
function detectProjectRoot(filePath: string): string | null {
// 简化实现:向上查找 package.json
const parts = filePath.replace(/\\/g, '/').split('/');
for (let i = parts.length - 1; i >= 0; i--) {
const dir = parts.slice(0, i + 1).join('/');
// 这里无法实际访问文件系统,返回文件所在目录的父级作为近似
}
// 返回文件所在目录
const lastSep = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
return lastSep > 0 ? filePath.slice(0, lastSep) : null;
}
// ═══════════════════════════════════════════════════════════════
// 验证 Hook 实现
// ═══════════════════════════════════════════════════════════════
/**
* DiffAnalyzerHook — 修改范围分析
* 检测 Agent 的修改是否跨越了架构边界
*/
export const diffAnalyzerHook: HarnessHook = {
name: 'DiffAnalyzer',
phase: 'post_tool',
priority: 75,
enabled: true,
handler: async (_ctx: LoopContext, data: HookData): Promise<HookResult> => {
const { toolName, toolArgs } = data;
if (toolName !== 'write_file' && toolName !== 'edit_file') {
return { passed: true, message: '' };
}
const filePath = (toolArgs?.path as string) || '';
if (!filePath) return { passed: true, message: '' };
// 分析修改的文件类型
const fileType = detectFileType(filePath);
if (!fileType) return { passed: true, message: '' };
// 分析跨层调用风险
const warnings: string[] = [];
// 检测是否修改了核心基础模块
const criticalPaths = ['/core/', '/base/', '/common/', '/shared/', '/utils/', '/lib/'];
const filePathLower = filePath.toLowerCase();
for (const cp of criticalPaths) {
if (filePathLower.includes(cp)) {
warnings.push(`⚠️ 修改了核心基础模块 "${filePath}",可能影响多个依赖方`);
break;
}
}
if (warnings.length > 0) {
return {
passed: true, // 警告不阻止,但注入提醒
message: warnings.join('; '),
data: { warnings },
};
}
return { passed: true, message: '' };
},
};
/**
* FileChangeAuditHook — 文件变更审计
* 记录 Agent 的每一次文件修改
*/
export const fileChangeAuditHook: HarnessHook = {
name: 'FileChangeAudit',
phase: 'post_tool',
priority: 30,
enabled: true,
handler: async (ctx: LoopContext, data: HookData): Promise<HookResult> => {
const { toolName, toolArgs, toolResult } = data;
if (toolName !== 'write_file' && toolName !== 'edit_file' && toolName !== 'delete_file') {
return { passed: true, message: '' };
}
const filePath = (toolArgs?.path as string) || '';
const success = toolResult?.success || false;
logInfo(
`文件变更审计 [第 ${ctx.loopCount} 轮]: ` +
`${toolName} ${filePath}${success ? '✅' : '❌'}`
);
return { passed: true, message: '' };
},
};
// ═══════════════════════════════════════════════════════════════
// 初始化
// ═══════════════════════════════════════════════════════════════
let verificationInitialized = false;
export function initVerificationSystem(): void {
if (verificationInitialized) return;
registerHook(diffAnalyzerHook);
registerHook(fileChangeAuditHook);
verificationInitialized = true;
logInfo('验证系统已初始化', 'DiffAnalyzer + FileChangeAudit Hook 已注册');
}
/** 获取验证配置 */
export function getVerificationConfig(): VerificationConfig {
return { ...config };
}
/** 更新验证配置 */
export function updateVerificationConfig(updates: Partial<VerificationConfig>): void {
Object.assign(config, updates);
logInfo('验证配置已更新', JSON.stringify(config));
}