Per-list optional VerifyCommand (list_config.verify_command) runs via VerifyCommandRunner in the list's working dir right after a successful merge/continue-merge, before the task is allowed to reach Done. A non-zero exit or timeout leaves the merge in place but keeps the task out of Done and reports StatusVerifyFailed with an output excerpt through MergeResultDto/review_task; no command configured behaves exactly as before. Merges against the same repo are now serialized per working dir so a running verify can't be interrupted by a second merge landing mid-build. Adds the field to the List Settings modal (en/de localized) and covers success/failure/timeout in TaskMergeServiceTests + VerifyCommandRunnerTests.
94 lines
3.2 KiB
C#
94 lines
3.2 KiB
C#
using ClaudeDo.Data.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ClaudeDo.Data.Repositories;
|
|
|
|
public sealed class ListRepository
|
|
{
|
|
private readonly ClaudeDoDbContext _context;
|
|
private readonly AttachmentStore _attachments;
|
|
|
|
public ListRepository(ClaudeDoDbContext context, AttachmentStore? attachments = null)
|
|
{
|
|
_context = context;
|
|
_attachments = attachments ?? new AttachmentStore();
|
|
}
|
|
|
|
public async Task AddAsync(ListEntity entity, CancellationToken ct = default)
|
|
{
|
|
_context.Lists.Add(entity);
|
|
await _context.SaveChangesAsync(ct);
|
|
}
|
|
|
|
public async Task UpdateAsync(ListEntity entity, CancellationToken ct = default)
|
|
{
|
|
_context.Lists.Update(entity);
|
|
await _context.SaveChangesAsync(ct);
|
|
}
|
|
|
|
public async Task DeleteAsync(string listId, CancellationToken ct = default)
|
|
{
|
|
var taskIds = await _context.Tasks
|
|
.Where(t => t.ListId == listId)
|
|
.Select(t => t.Id)
|
|
.ToListAsync(ct);
|
|
await _context.Lists.Where(l => l.Id == listId).ExecuteDeleteAsync(ct);
|
|
foreach (var id in taskIds)
|
|
_attachments.DeleteTaskDir(id);
|
|
}
|
|
|
|
public async Task<ListEntity?> GetByIdAsync(string listId, CancellationToken ct = default)
|
|
{
|
|
return await _context.Lists.FirstOrDefaultAsync(l => l.Id == listId, ct);
|
|
}
|
|
|
|
public async Task<List<ListEntity>> GetAllAsync(CancellationToken ct = default)
|
|
{
|
|
return await _context.Lists.OrderBy(l => l.SortOrder).ThenBy(l => l.CreatedAt).ToListAsync(ct);
|
|
}
|
|
|
|
public async Task ReorderAsync(IReadOnlyList<string> orderedListIds, CancellationToken ct = default)
|
|
{
|
|
var idSet = orderedListIds.ToHashSet();
|
|
var entities = await _context.Lists.Where(l => idSet.Contains(l.Id)).ToListAsync(ct);
|
|
for (int i = 0; i < orderedListIds.Count; i++)
|
|
{
|
|
var e = entities.FirstOrDefault(x => x.Id == orderedListIds[i]);
|
|
if (e is not null) e.SortOrder = i;
|
|
}
|
|
await _context.SaveChangesAsync(ct);
|
|
}
|
|
|
|
public async Task<ListConfigEntity?> GetConfigAsync(string listId, CancellationToken ct = default)
|
|
{
|
|
return await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == listId, ct);
|
|
}
|
|
|
|
public async Task SetConfigAsync(ListConfigEntity config, CancellationToken ct = default)
|
|
{
|
|
var existing = await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == config.ListId, ct);
|
|
if (existing is null)
|
|
{
|
|
_context.ListConfigs.Add(config);
|
|
}
|
|
else
|
|
{
|
|
existing.Model = config.Model;
|
|
existing.SystemPrompt = config.SystemPrompt;
|
|
existing.AgentPath = config.AgentPath;
|
|
existing.MaxTurns = config.MaxTurns;
|
|
existing.SessionSkills = config.SessionSkills;
|
|
existing.VerifyCommand = config.VerifyCommand;
|
|
}
|
|
await _context.SaveChangesAsync(ct);
|
|
}
|
|
|
|
public async Task<bool> DeleteConfigAsync(string listId, CancellationToken ct = default)
|
|
{
|
|
var affected = await _context.ListConfigs
|
|
.Where(c => c.ListId == listId)
|
|
.ExecuteDeleteAsync(ct);
|
|
return affected > 0;
|
|
}
|
|
}
|