feat(checkpoint): 添加 Checkpoint 可视化管理功能

Core 层增强:
- 添加 safety.ts: 7点安全检查机制
- 添加 session-tracker.ts: 会话级检查点跟踪
- 添加 lock.ts: 并发控制文件锁
- 添加 lfs.ts: Git LFS 大文件支持
- 添加 path-validator.ts: 路径验证
- 添加 commit-message.ts: 智能提交消息生成
- 增强 manager.ts: 支持三种恢复模式、unrevert 撤销回滚

Server 层:
- 添加 checkpoints.ts: 16个 REST API 端点
  - GET/POST /checkpoints: 列表/创建检查点
  - GET/DELETE /checkpoints/🆔 获取/删除检查点
  - GET /checkpoints/:id/diff: 获取差异
  - POST /checkpoints/:id/restore: 恢复到检查点
  - POST /checkpoints/unrevert: 撤销回滚
  - GET /checkpoints/:id/safety-check: 安全检查

UI 层:
- 添加 CheckpointPanel.tsx: 检查点列表面板
- 添加 CheckpointDiffViewer.tsx: 差异查看器
- 添加 RestoreDialog.tsx: 恢复确认对话框
- 添加 16 个 API 客户端函数
- 添加完整的 TypeScript 类型定义

Web/Desktop 集成:
- 添加 History 按钮到工具栏
- 集成 CheckpointPanel 组件
This commit is contained in:
2025-12-12 22:52:27 +08:00
parent a225e66ad7
commit cb554c65b4
23 changed files with 4970 additions and 116 deletions
+189
View File
@@ -0,0 +1,189 @@
/**
* Git LFS 大文件支持模块
* 参考 Cline 的 LFS 模式检测
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import { minimatch } from 'minimatch';
/**
* LFS 模式加载器
* 从 .gitattributes 文件中加载 LFS 模式
*/
export class LFSPatternLoader {
private patterns: string[] = [];
private loaded = false;
/**
* 从工作目录加载 LFS 模式
*/
async loadPatterns(workDir: string): Promise<void> {
const gitattributesPath = path.join(workDir, '.gitattributes');
try {
const content = await fs.readFile(gitattributesPath, 'utf-8');
this.patterns = this.parseLfsPatterns(content);
this.loaded = true;
} catch {
// .gitattributes 不存在或无法读取
this.patterns = [];
this.loaded = true;
}
}
/**
* 解析 .gitattributes 内容,提取 LFS 模式
*/
private parseLfsPatterns(content: string): string[] {
const patterns: string[] = [];
for (const line of content.split('\n')) {
const trimmed = line.trim();
// 跳过空行和注释
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
// 匹配 LFS 配置行: pattern filter=lfs diff=lfs merge=lfs -text
// 或简单形式: pattern filter=lfs
if (trimmed.includes('filter=lfs')) {
// 提取模式(第一个空白字符前的部分)
const match = trimmed.match(/^(\S+)/);
if (match) {
patterns.push(match[1]);
}
}
}
return patterns;
}
/**
* 检查文件是否为 LFS 管理的文件
*/
isLfsFile(filePath: string): boolean {
if (!this.loaded) {
return false;
}
// 规范化路径(使用正斜杠)
const normalizedPath = filePath.replace(/\\/g, '/');
return this.patterns.some((pattern) =>
minimatch(normalizedPath, pattern, { matchBase: true })
);
}
/**
* 获取所有 LFS 模式
*/
getPatterns(): string[] {
return [...this.patterns];
}
/**
* 获取排除模式(用于 .gitignore
*/
getExcludePatterns(): string[] {
return this.patterns.map((p) => {
// 确保模式以 / 开头(相对于仓库根目录)
if (!p.startsWith('/') && !p.startsWith('*')) {
return `/${p}`;
}
return p;
});
}
/**
* 检查是否已加载
*/
isLoaded(): boolean {
return this.loaded;
}
/**
* 重置(清除已加载的模式)
*/
reset(): void {
this.patterns = [];
this.loaded = false;
}
/**
* 过滤文件列表,排除 LFS 文件
*/
filterNonLfsFiles(files: string[]): string[] {
return files.filter((file) => !this.isLfsFile(file));
}
/**
* 获取 LFS 文件列表
*/
filterLfsFiles(files: string[]): string[] {
return files.filter((file) => this.isLfsFile(file));
}
}
/**
* 常见的大文件扩展名(作为 LFS 候选)
*/
export const COMMON_LARGE_FILE_EXTENSIONS = [
// 图片
'.psd',
'.ai',
'.eps',
'.tiff',
'.raw',
'.cr2',
'.nef',
// 视频
'.mp4',
'.mov',
'.avi',
'.mkv',
'.wmv',
'.flv',
// 音频
'.mp3',
'.wav',
'.flac',
'.aac',
'.ogg',
// 压缩文件
'.zip',
'.tar',
'.gz',
'.rar',
'.7z',
// 二进制
'.exe',
'.dll',
'.so',
'.dylib',
// 数据文件
'.db',
'.sqlite',
'.mdb',
// 其他
'.pdf',
'.docx',
'.xlsx',
'.pptx',
];
/**
* 检查文件扩展名是否为常见大文件类型
*/
export function isCommonLargeFile(filePath: string): boolean {
const ext = path.extname(filePath).toLowerCase();
return COMMON_LARGE_FILE_EXTENSIONS.includes(ext);
}
/**
* 创建 LFS 模式加载器实例
*/
export function createLFSPatternLoader(): LFSPatternLoader {
return new LFSPatternLoader();
}