feat(tasks): full REST surface for tasks

CRUD, lifecycle transitions, work-graph links and notes over /tasks;
share link-kind inference (TaskTargetKinds) between the REST route and
task_update tool. End-to-end route tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 19:27:19 +00:00
parent 0c6ac903b7
commit 6e044d0ab9
4 changed files with 555 additions and 17 deletions
@@ -7,6 +7,7 @@ import com.correx.core.events.types.TaskLinkType
import com.correx.core.events.types.TaskNoteAuthor
import com.correx.core.events.types.TaskTargetKind
import com.correx.core.tasks.TaskService
import com.correx.core.tasks.TaskTargetKinds
import com.correx.core.tools.contract.Tool
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.tools.contract.ToolExecutor
@@ -20,8 +21,6 @@ import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
private val ADR_ID = Regex("(?i)^adr[-_ ]?\\d+$")
/**
* Agent-facing tool: update an existing task — edit fields, transition status, add a work-graph
* link, or attach a note. The task's project is derived from its id, so only the id is required.
@@ -148,10 +147,7 @@ class TaskUpdateTool(private val service: TaskService) : Tool, ToolExecutor {
/** Explicit kind when given (null if unrecognised); otherwise inferred from the target id. */
private fun resolveTargetKind(raw: String?, target: String): TaskTargetKind? =
if (raw != null) TaskTargetKind.entries.firstOrNull { it.name == raw.uppercase() }
else inferTargetKind(target)
private fun inferTargetKind(target: String): TaskTargetKind =
if (ADR_ID.matches(target) || target.endsWith(".md")) TaskTargetKind.DOC else TaskTargetKind.TASK
else TaskTargetKinds.infer(target)
private fun fail(request: ToolRequest, reason: String): ToolResult.Failure =
ToolResult.Failure(request.invocationId, reason, recoverable = false)