fix(team): preserve metadata-only file additions
This commit is contained in:
parent
eb4e4ba3e5
commit
ab3be12b94
2 changed files with 71 additions and 5 deletions
|
|
@ -46,6 +46,7 @@ interface LogFileRef {
|
||||||
|
|
||||||
interface MetadataChangePath {
|
interface MetadataChangePath {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
|
kind?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParsedJsonlEntry {
|
interface ParsedJsonlEntry {
|
||||||
|
|
@ -617,11 +618,13 @@ export class TaskChangeComputer {
|
||||||
|
|
||||||
for (const target of targetPaths) {
|
for (const target of targetPaths) {
|
||||||
seenFiles.add(this.normalizeFilePathKey(target.filePath));
|
seenFiles.add(this.normalizeFilePathKey(target.filePath));
|
||||||
|
const snippetType: SnippetDiff['type'] =
|
||||||
|
!hasTextPayload && target.kind === 'add' ? 'write-new' : 'edit';
|
||||||
addSnippet({
|
addSnippet({
|
||||||
toolUseId,
|
toolUseId,
|
||||||
filePath: target.filePath,
|
filePath: target.filePath,
|
||||||
toolName: 'Edit',
|
toolName: 'Edit',
|
||||||
type: 'edit',
|
type: snippetType,
|
||||||
oldString,
|
oldString,
|
||||||
newString,
|
newString,
|
||||||
replaceAll,
|
replaceAll,
|
||||||
|
|
@ -718,10 +721,11 @@ export class TaskChangeComputer {
|
||||||
const changeObj = change as Record<string, unknown>;
|
const changeObj = change as Record<string, unknown>;
|
||||||
const filePath = typeof changeObj.path === 'string' ? changeObj.path : '';
|
const filePath = typeof changeObj.path === 'string' ? changeObj.path : '';
|
||||||
if (!filePath) continue;
|
if (!filePath) continue;
|
||||||
|
const kind = typeof changeObj.kind === 'string' ? changeObj.kind : undefined;
|
||||||
const normalized = this.normalizeFilePathKey(filePath);
|
const normalized = this.normalizeFilePathKey(filePath);
|
||||||
if (seen.has(normalized)) continue;
|
if (seen.has(normalized)) continue;
|
||||||
seen.add(normalized);
|
seen.add(normalized);
|
||||||
paths.push({ filePath });
|
paths.push({ filePath, ...(kind ? { kind } : {}) });
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { afterEach, describe, expect, it } from 'vitest';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import * as fs from 'fs/promises';
|
|
||||||
|
|
||||||
import { TaskChangeComputer } from '../../../../src/main/services/team/TaskChangeComputer';
|
import { TaskChangeComputer } from '../../../../src/main/services/team/TaskChangeComputer';
|
||||||
|
|
||||||
import type { TaskChangeTaskMeta } from '../../../../src/main/services/team/taskChangeWorkerTypes';
|
import type { TaskChangeTaskMeta } from '../../../../src/main/services/team/taskChangeWorkerTypes';
|
||||||
|
|
||||||
const NO_TASK_BOUNDARIES_WARNING =
|
const NO_TASK_BOUNDARIES_WARNING =
|
||||||
|
|
@ -84,6 +84,18 @@ function metadataOnlyMultiFileEditToolUse(
|
||||||
toolUseId: string,
|
toolUseId: string,
|
||||||
filePaths: string[],
|
filePaths: string[],
|
||||||
primaryPath = filePaths[0] ?? ''
|
primaryPath = filePaths[0] ?? ''
|
||||||
|
): object {
|
||||||
|
return metadataOnlyMultiFileEditChangesToolUse(
|
||||||
|
toolUseId,
|
||||||
|
filePaths.map((filePath) => ({ filePath, kind: 'add' })),
|
||||||
|
primaryPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function metadataOnlyMultiFileEditChangesToolUse(
|
||||||
|
toolUseId: string,
|
||||||
|
changes: Array<{ filePath: string; kind?: string }>,
|
||||||
|
primaryPath = changes[0]?.filePath ?? ''
|
||||||
): object {
|
): object {
|
||||||
return {
|
return {
|
||||||
timestamp: '2026-03-01T10:00:00.000Z',
|
timestamp: '2026-03-01T10:00:00.000Z',
|
||||||
|
|
@ -97,7 +109,10 @@ function metadataOnlyMultiFileEditToolUse(
|
||||||
name: 'Edit',
|
name: 'Edit',
|
||||||
input: {
|
input: {
|
||||||
file_path: primaryPath,
|
file_path: primaryPath,
|
||||||
changes: filePaths.map((filePath) => ({ path: filePath, kind: 'add' })),
|
changes: changes.map((change) => ({
|
||||||
|
path: change.filePath,
|
||||||
|
...(change.kind ? { kind: change.kind } : {}),
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -644,6 +659,8 @@ describe('TaskChangeComputer', () => {
|
||||||
|
|
||||||
expect(result.files.map((file) => file.relativePath)).toEqual(['src/a.ts']);
|
expect(result.files.map((file) => file.relativePath)).toEqual(['src/a.ts']);
|
||||||
expect(result.files[0]?.snippets).toHaveLength(1);
|
expect(result.files[0]?.snippets).toHaveLength(1);
|
||||||
|
expect(result.files[0]?.isNewFile).toBe(false);
|
||||||
|
expect(result.files[0]?.snippets[0]?.type).toBe('edit');
|
||||||
expect(result.files[0]?.snippets[0]?.oldString).toBe('');
|
expect(result.files[0]?.snippets[0]?.oldString).toBe('');
|
||||||
expect(result.files[0]?.snippets[0]?.newString).toBe('');
|
expect(result.files[0]?.snippets[0]?.newString).toBe('');
|
||||||
expect(result.totalFiles).toBe(1);
|
expect(result.totalFiles).toBe(1);
|
||||||
|
|
@ -696,11 +713,56 @@ describe('TaskChangeComputer', () => {
|
||||||
'dfdf/style.css',
|
'dfdf/style.css',
|
||||||
]);
|
]);
|
||||||
expect(result.files.every((file) => file.snippets[0]?.toolUseId === 'tool-1')).toBe(true);
|
expect(result.files.every((file) => file.snippets[0]?.toolUseId === 'tool-1')).toBe(true);
|
||||||
|
expect(result.files.every((file) => file.isNewFile)).toBe(true);
|
||||||
|
expect(result.files.every((file) => file.snippets[0]?.type === 'write-new')).toBe(true);
|
||||||
expect(result.files.every((file) => file.linesAdded === 0 && file.linesRemoved === 0)).toBe(
|
expect(result.files.every((file) => file.linesAdded === 0 && file.linesRemoved === 0)).toBe(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves metadata-only Edit change kinds without upgrading updates to new files', async () => {
|
||||||
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'task-change-computer-'));
|
||||||
|
const logPath = path.join(tmpDir, 'agent.jsonl');
|
||||||
|
await writeJsonl(logPath, [
|
||||||
|
metadataOnlyMultiFileEditChangesToolUse('tool-1', [
|
||||||
|
{ filePath: '/repo/src/new.ts', kind: 'add' },
|
||||||
|
{ filePath: '/repo/src/existing.ts', kind: 'update' },
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const logsFinder = {
|
||||||
|
findLogFileRefsForTask: () => Promise.resolve([{ filePath: logPath, memberName: 'tom' }]),
|
||||||
|
};
|
||||||
|
const boundaryParser = {
|
||||||
|
parseBoundaries: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
boundaries: [],
|
||||||
|
scopes: [],
|
||||||
|
isSingleTaskSession: true,
|
||||||
|
detectedMechanism: 'none' as const,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const computer = new TaskChangeComputer(logsFinder as never, boundaryParser as never);
|
||||||
|
|
||||||
|
const result = await computer.computeTaskChanges({
|
||||||
|
teamName: 'team-a',
|
||||||
|
taskId: 'task-1',
|
||||||
|
taskMeta: null,
|
||||||
|
effectiveOptions: {},
|
||||||
|
projectPath: '/repo',
|
||||||
|
includeDetails: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filesByPath = new Map(result.files.map((file) => [file.relativePath, file]));
|
||||||
|
const newFile = filesByPath.get('src/new.ts');
|
||||||
|
const existingFile = filesByPath.get('src/existing.ts');
|
||||||
|
|
||||||
|
expect(newFile?.isNewFile).toBe(true);
|
||||||
|
expect(newFile?.snippets[0]?.type).toBe('write-new');
|
||||||
|
expect(existingFile?.isNewFile).toBe(false);
|
||||||
|
expect(existingFile?.snippets[0]?.type).toBe('edit');
|
||||||
|
});
|
||||||
|
|
||||||
it('does not include repeated tool ids from outside the scoped source lines', async () => {
|
it('does not include repeated tool ids from outside the scoped source lines', async () => {
|
||||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'task-change-computer-'));
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'task-change-computer-'));
|
||||||
const logPath = path.join(tmpDir, 'agent.jsonl');
|
const logPath = path.join(tmpDir, 'agent.jsonl');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue