Files
claude-code/src/main/java/io/mybatis/learn/s12/WorktreeTaskManager.java
T
2026-03-25 00:15:00 +08:00

196 lines
7.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package io.mybatis.learn.s12;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
/**
* Worktree任务管理器 - 持久化任务板 + worktree绑定
*
* TIPS: 对应Python TaskManager类(s12第122-217行)。
* 与S07的TaskManager不同,此版本增加了 worktree 字段用于目录隔离绑定。
* 任务数据结构:{id, subject, description, status, owner, worktree, blockedBy, created_at, updated_at}
*/
public class WorktreeTaskManager {
private static final Logger log = LoggerFactory.getLogger(WorktreeTaskManager.class);
private final Path tasksDir;
private final ObjectMapper mapper = new ObjectMapper();
private int nextId;
public WorktreeTaskManager(Path tasksDir) {
this.tasksDir = tasksDir;
try { Files.createDirectories(tasksDir); } catch (IOException e) {
log.error("创建worktree任务目录失败: {}, error={}", tasksDir, e.getMessage());
throw new RuntimeException(e);
}
this.nextId = maxId() + 1;
log.info("WorktreeTaskManager 初始化完成,nextId={}, dir={}", nextId, tasksDir);
}
private int maxId() {
try (var files = Files.list(tasksDir)) {
return files.filter(f -> f.getFileName().toString().matches("task_\\d+\\.json"))
.mapToInt(f -> {
String name = f.getFileName().toString();
return Integer.parseInt(name.substring(5, name.length() - 5));
})
.max().orElse(0);
} catch (IOException e) { return 0; }
}
private Path taskPath(int taskId) {
return tasksDir.resolve("task_" + taskId + ".json");
}
@SuppressWarnings("unchecked")
private Map<String, Object> load(int taskId) throws IOException {
Path path = taskPath(taskId);
if (!Files.exists(path)) throw new IOException("Task " + taskId + " not found");
return mapper.readValue(path.toFile(), Map.class);
}
private void save(Map<String, Object> task) throws IOException {
mapper.writerWithDefaultPrettyPrinter().writeValue(
taskPath(((Number) task.get("id")).intValue()).toFile(), task);
}
public boolean exists(int taskId) {
return Files.exists(taskPath(taskId));
}
public synchronized String create(String subject, String description) {
if (log.isDebugEnabled()) {
System.out.printf("📋 创建worktree任务: %s%n", subject);
}
Map<String, Object> task = new LinkedHashMap<>();
task.put("id", nextId);
task.put("subject", subject);
task.put("description", description != null ? description : "");
task.put("status", "pending");
task.put("owner", "");
task.put("worktree", "");
task.put("blockedBy", new ArrayList<>());
task.put("created_at", System.currentTimeMillis() / 1000.0);
task.put("updated_at", System.currentTimeMillis() / 1000.0);
try {
save(task);
nextId++;
if (log.isDebugEnabled()) {
System.out.printf("✅ worktree任务 #%s 已创建 (nextId=%d)%n", task.get("id"), nextId);
}
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(task);
} catch (IOException e) {
log.warn("worktree任务创建失败: subject={}, error={}", subject, e.getMessage());
return "Error: " + e.getMessage();
}
}
public String get(int taskId) {
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(load(taskId));
} catch (IOException e) {
return "Error: " + e.getMessage();
}
}
public String update(int taskId, String status, String owner) {
if (log.isDebugEnabled()) {
System.out.printf("🔄 更新worktree任务 #%d (status=%s, owner=%s)%n", taskId, status, owner);
}
try {
var task = load(taskId);
if (status != null) {
if (!Set.of("pending", "in_progress", "completed").contains(status))
return "Error: Invalid status: " + status;
task.put("status", status);
}
if (owner != null) task.put("owner", owner);
task.put("updated_at", System.currentTimeMillis() / 1000.0);
save(task);
if (log.isDebugEnabled()) {
System.out.printf("✅ worktree任务 #%d 已更新%n", taskId);
}
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(task);
} catch (IOException e) {
log.warn("worktree任务更新失败: taskId={}, error={}", taskId, e.getMessage());
return "Error: " + e.getMessage();
}
}
/**
* 绑定worktree到任务(如果任务是pending则自动变为in_progress
*/
public String bindWorktree(int taskId, String worktree, String owner) {
if (log.isDebugEnabled()) {
System.out.printf("🔗 绑定worktree: #%d → %s (owner=%s)%n", taskId, worktree, owner);
}
try {
var task = load(taskId);
task.put("worktree", worktree);
if (owner != null && !owner.isEmpty()) task.put("owner", owner);
if ("pending".equals(task.get("status"))) task.put("status", "in_progress");
task.put("updated_at", System.currentTimeMillis() / 1000.0);
save(task);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(task);
} catch (IOException e) {
log.warn("绑定worktree失败: taskId={}, worktree={}, error={}", taskId, worktree, e.getMessage());
return "Error: " + e.getMessage();
}
}
public String unbindWorktree(int taskId) {
if (log.isDebugEnabled()) {
System.out.printf("🔗 解绑worktree: #%d%n", taskId);
}
try {
var task = load(taskId);
task.put("worktree", "");
task.put("updated_at", System.currentTimeMillis() / 1000.0);
save(task);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(task);
} catch (IOException e) {
log.warn("解绑worktree失败: taskId={}, error={}", taskId, e.getMessage());
return "Error: " + e.getMessage();
}
}
@SuppressWarnings("unchecked")
public String listAll() {
try (var files = Files.list(tasksDir)) {
List<Map<String, Object>> tasks = new ArrayList<>();
files.filter(f -> f.getFileName().toString().matches("task_\\d+\\.json"))
.sorted()
.forEach(f -> {
try { tasks.add(mapper.readValue(f.toFile(), Map.class)); }
catch (IOException e) { /* skip */ }
});
if (tasks.isEmpty()) return "No tasks.";
StringBuilder sb = new StringBuilder();
for (var t : tasks) {
String status = (String) t.get("status");
String marker = switch (status) {
case "pending" -> "[ ]";
case "in_progress" -> "[>]";
case "completed" -> "[x]";
default -> "[?]";
};
String owner = t.get("owner") != null && !t.get("owner").toString().isEmpty()
? " owner=" + t.get("owner") : "";
String wt = t.get("worktree") != null && !t.get("worktree").toString().isEmpty()
? " wt=" + t.get("worktree") : "";
sb.append(String.format("%s #%s: %s%s%s%n",
marker, t.get("id"), t.get("subject"), owner, wt));
}
return sb.toString().trim();
} catch (IOException e) {
return "Error: " + e.getMessage();
}
}
}