import type { FolderReader } from './workspaceScope' import { describe, expect, it } from 'vitest' import { folderScope } from './workspaceScope' /** 内存里的工作区文件夹:路径 → 内容(目录不用列,列出来的文件所在的目录都算有);记下读过哪些。 */ function memoryFolder(files: Record): FolderReader & { asked: string[] } { const asked: string[] = [] const directories = new Set(Object.keys(files).flatMap((file) => { const parts = file.split('/') return parts.slice(0, -1).map((_, i) => parts.slice(0, i + 1).join('/')) })) return { asked, exists: async (path) => { asked.push(path) return path in files || directories.has(path) }, read: async (path) => { asked.push(path) return files[path] }, } } async function kept(files: Record, paths: string[], useIgnoreFiles = true): Promise { const scope = folderScope(memoryFolder(files), useIgnoreFiles) const skipped = await Promise.all(paths.map(path => scope.skips(path))) return paths.filter((_, i) => !skipped[i]) } const TASKS = 'backend/src/luffy_agent/commerce/tasks.py' const WORKTREE = '.claude/worktrees/agent-aa35211c29ea9e068' describe('工作区扫描还要跳过的文件', () => { it('嵌套的 git worktree(子目录里的 .git 是个文件)整个跳过,工作区自己的 .git 不算', async () => { const files = { '.git/HEAD': 'ref: refs/heads/main', [TASKS]: '', [`${WORKTREE}/.git`]: 'gitdir: /repo/.git/worktrees/agent-aa35211c29ea9e068', [`${WORKTREE}/${TASKS}`]: '', } expect(await kept(files, [TASKS, `${WORKTREE}/${TASKS}`], false)).toEqual([TASKS]) }) it('嵌套的仓库(子目录里的 .git 是个目录)也跳过', async () => { expect(await kept({ 'vendor/lib/.git/HEAD': '', 'vendor/lib/a.py': '', 'vendor/b.py': '' }, ['vendor/lib/a.py', 'vendor/b.py'])).toEqual(['vendor/b.py']) }) it('.gitignore 忽略的目录跳过;每层的规则相对那一层,取反照 gitignore 写法;.ignore 也读', async () => { const files = { '.gitignore': '.claude/worktrees/\nbuild/\n*.log\n!keep.log\n', 'pkg/.gitignore': 'generated/\n', 'tools/.ignore': 'scratch.py\n', [`${WORKTREE}/${TASKS}`]: '', } const paths = [ TASKS, `${WORKTREE}/${TASKS}`, 'build/out/a.py', 'src/build.py', 'pkg/generated/a.py', 'generated/a.py', 'logs/x.log', 'logs/keep.log', 'tools/scratch.py', 'tools/run.py', ] expect(await kept(files, paths)).toEqual([TASKS, 'src/build.py', 'generated/a.py', 'logs/keep.log', 'tools/run.py']) }) it('search.useIgnoreFiles 关着时不读忽略文件,嵌套的 checkout 照样跳过', async () => { const files = { '.gitignore': 'build/\n', 'sub/.git': 'gitdir: x', 'sub/a.py': '' } expect(await kept(files, ['build/a.py', 'sub/a.py'], false)).toEqual(['build/a.py']) }) it('同一个目录只查一次', async () => { const folder = memoryFolder({ 'a/b/c.py': '', 'a/b/d.py': '' }) const scope = folderScope(folder, true) await scope.skips('a/b/c.py') await scope.skips('a/b/d.py') expect(folder.asked.filter(path => path === 'a/b/.git')).toEqual(['a/b/.git']) expect(folder.asked.filter(path => path === 'a/.gitignore')).toEqual(['a/.gitignore']) }) })