feat(conflicts): add bash write detection, dismiss/acknowledge, and worktree suppression
This commit is contained in:
parent
05191127ea
commit
38b8447ae6
8 changed files with 354 additions and 24 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { extractFilePaths, extractWritePaths } from './tool-files';
|
||||
import { extractFilePaths, extractWritePaths, extractWorktreePath } from './tool-files';
|
||||
import type { ToolCallContent } from '../adapters/sdk-messages';
|
||||
|
||||
function makeTc(name: string, input: unknown): ToolCallContent {
|
||||
|
|
@ -32,7 +32,7 @@ describe('extractFilePaths', () => {
|
|||
expect(result).toEqual([{ path: '/src', op: 'grep' }]);
|
||||
});
|
||||
|
||||
it('extracts Bash file paths from common commands', () => {
|
||||
it('extracts Bash read paths from common commands', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'cat /etc/hosts' }));
|
||||
expect(result).toEqual([{ path: '/etc/hosts', op: 'bash' }]);
|
||||
});
|
||||
|
|
@ -51,10 +51,57 @@ describe('extractFilePaths', () => {
|
|||
const result = extractFilePaths(makeTc('Read', {}));
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
// Bash write detection
|
||||
it('detects echo > redirect as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'echo "hello" > /tmp/out.txt' }));
|
||||
expect(result).toEqual([{ path: '/tmp/out.txt', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects >> append redirect as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'echo "data" >> /tmp/log.txt' }));
|
||||
expect(result).toEqual([{ path: '/tmp/log.txt', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects sed -i as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: "sed -i 's/foo/bar/g' /src/config.ts" }));
|
||||
expect(result).toEqual([{ path: '/src/config.ts', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects tee as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'echo "content" | tee /tmp/output.log' }));
|
||||
expect(result).toEqual([{ path: '/tmp/output.log', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects tee -a as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'echo "append" | tee -a /tmp/output.log' }));
|
||||
expect(result).toEqual([{ path: '/tmp/output.log', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects cp destination as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'cp /src/a.ts /src/b.ts' }));
|
||||
expect(result).toEqual([{ path: '/src/b.ts', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('detects mv destination as write', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'mv /old/file.ts /new/file.ts' }));
|
||||
expect(result).toEqual([{ path: '/new/file.ts', op: 'write' }]);
|
||||
});
|
||||
|
||||
it('ignores /dev/null redirects', () => {
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'echo "test" > /dev/null' }));
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('prefers write detection over read for ambiguous commands', () => {
|
||||
// "cat file > out" should detect the write target, not the read source
|
||||
const result = extractFilePaths(makeTc('Bash', { command: 'cat /src/input.ts > /tmp/output.ts' }));
|
||||
expect(result).toEqual([{ path: '/tmp/output.ts', op: 'write' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractWritePaths', () => {
|
||||
it('returns only write-op paths', () => {
|
||||
it('returns only write-op paths for Write/Edit tools', () => {
|
||||
expect(extractWritePaths(makeTc('Write', { file_path: '/a.ts' }))).toEqual(['/a.ts']);
|
||||
expect(extractWritePaths(makeTc('Edit', { file_path: '/b.ts' }))).toEqual(['/b.ts']);
|
||||
});
|
||||
|
|
@ -65,7 +112,37 @@ describe('extractWritePaths', () => {
|
|||
expect(extractWritePaths(makeTc('Grep', { path: '/src' }))).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty for bash commands', () => {
|
||||
it('returns empty for bash read commands', () => {
|
||||
expect(extractWritePaths(makeTc('Bash', { command: 'cat /foo' }))).toEqual([]);
|
||||
});
|
||||
|
||||
it('detects bash write commands', () => {
|
||||
expect(extractWritePaths(makeTc('Bash', { command: 'echo "x" > /tmp/out.ts' }))).toEqual(['/tmp/out.ts']);
|
||||
expect(extractWritePaths(makeTc('Bash', { command: "sed -i 's/a/b/' /src/file.ts" }))).toEqual(['/src/file.ts']);
|
||||
expect(extractWritePaths(makeTc('Bash', { command: 'cp /a.ts /b.ts' }))).toEqual(['/b.ts']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractWorktreePath', () => {
|
||||
it('detects Agent tool with isolation: worktree', () => {
|
||||
const result = extractWorktreePath(makeTc('Agent', { prompt: 'do stuff', isolation: 'worktree' }));
|
||||
expect(result).toMatch(/^worktree:/);
|
||||
});
|
||||
|
||||
it('detects Task tool with isolation: worktree', () => {
|
||||
const result = extractWorktreePath(makeTc('Task', { prompt: 'do stuff', isolation: 'worktree' }));
|
||||
expect(result).toMatch(/^worktree:/);
|
||||
});
|
||||
|
||||
it('returns null for Agent without isolation', () => {
|
||||
expect(extractWorktreePath(makeTc('Agent', { prompt: 'do stuff' }))).toBeNull();
|
||||
});
|
||||
|
||||
it('detects EnterWorktree with path', () => {
|
||||
expect(extractWorktreePath(makeTc('EnterWorktree', { path: '/tmp/wt-1' }))).toBe('/tmp/wt-1');
|
||||
});
|
||||
|
||||
it('returns null for unrelated tool', () => {
|
||||
expect(extractWorktreePath(makeTc('Read', { file_path: '/foo' }))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue