feat: wire conflictFiles through errand.get and add repository tests

- `errand.get` now returns `conflictFiles: string[]` (always an array,
  never null) with defensive JSON.parse error handling
- `errand.get` returns `projectPath: string | null` computed from
  workspaceRoot + getProjectCloneDir so cw errand resolve can locate
  the worktree without a second tRPC call
- Add `apps/server/db/repositories/drizzle/errand.test.ts` covering
  conflictFiles store/retrieve, null for non-conflict errands, and
  findAll including conflictFiles
- Update `errand.test.ts` mock to include getProjectCloneDir and fix
  conflictFiles expectation from null to []

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas May
2026-03-06 16:28:56 +01:00
parent 377e8de5e9
commit 813979388b
4 changed files with 187 additions and 7 deletions

View File

@@ -35,6 +35,7 @@ vi.mock('../../git/manager.js', () => ({
vi.mock('../../git/project-clones.js', () => ({
ensureProjectClone: mockEnsureProjectClone,
getProjectCloneDir: vi.fn().mockReturnValue('repos/test-project-abc123'),
}));
vi.mock('../../agent/file-io.js', async (importOriginal) => {
@@ -393,7 +394,7 @@ describe('errand procedures', () => {
expect(result.id).toBe(errand.id);
expect(result).toHaveProperty('agentAlias');
expect(result.conflictFiles).toBeNull();
expect(result.conflictFiles).toEqual([]);
});
it('parses conflictFiles JSON when present', async () => {

View File

@@ -18,8 +18,9 @@ import {
} from './_helpers.js';
import { writeErrandManifest } from '../../agent/file-io.js';
import { buildErrandPrompt } from '../../agent/prompts/index.js';
import { join } from 'node:path';
import { SimpleGitWorktreeManager } from '../../git/manager.js';
import { ensureProjectClone } from '../../git/project-clones.js';
import { ensureProjectClone, getProjectCloneDir } from '../../git/project-clones.js';
import type { TRPCContext } from '../context.js';
// ErrandStatus values for input validation
@@ -200,10 +201,27 @@ export function errandProcedures(publicProcedure: ProcedureBuilder) {
if (!errand) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Errand not found' });
}
return {
...errand,
conflictFiles: errand.conflictFiles ? (JSON.parse(errand.conflictFiles) as string[]) : null,
};
// Parse conflictFiles; return [] on null or malformed JSON
let conflictFiles: string[] = [];
if (errand.conflictFiles) {
try {
conflictFiles = JSON.parse(errand.conflictFiles) as string[];
} catch {
conflictFiles = [];
}
}
// Compute project clone path for cw errand resolve
let projectPath: string | null = null;
if (errand.projectId && ctx.workspaceRoot) {
const project = await requireProjectRepository(ctx).findById(errand.projectId);
if (project) {
projectPath = join(ctx.workspaceRoot, getProjectCloneDir(project.name, project.id));
}
}
return { ...errand, conflictFiles, projectPath };
}),
// -----------------------------------------------------------------------