refactor(detekt): extract magic-number constants, wrap long lines, drop legit suppressions

Mechanical detekt cleanup across apps/core/infrastructure (behavior-preserving):
- MagicNumber 62->10: named constants + removed 'legit' MagicNumber suppressions
  (Tier level from ordinal, ReplayInferenceProvider CHARS_PER_TOKEN, ConfigLoader
  DEFAULT_* consts, capability scores, HTTP ranges, column widths, etc.)
- MaxLineLength cut to ~0 in production modules (line wraps, no logic change)
- Dropped one dead parameter (ConfigLoader.parseArray lineNum)

Structural findings (ReturnCount/LongMethod/Complexity/LargeClass) left for a
deliberate decomposition pass. Full build + detekt gate green.
This commit is contained in:
2026-07-12 23:12:28 +04:00
parent 135a34eb2f
commit aee2e67c66
70 changed files with 1034 additions and 236 deletions
@@ -17,6 +17,7 @@ import kotlinx.serialization.json.Json
import org.slf4j.LoggerFactory
private const val DEFAULT_REQUEST_TIMEOUT_MS = 60_000L
private const val ERROR_BODY_PREVIEW_LENGTH = 200
private fun defaultHttpClient(): HttpClient = HttpClient(CIO) {
install(ContentNegotiation) {
@@ -125,9 +126,10 @@ class LlamaCppEmbedder(
return nestedResult
}
val preview = responseBody.take(ERROR_BODY_PREVIEW_LENGTH)
throw IllegalStateException(
"Unexpected embedding response shape from $url. " +
"Response body (first 200 chars): ${responseBody.take(200)}"
"Response body (first $ERROR_BODY_PREVIEW_LENGTH chars): $preview"
)
}
}
@@ -260,7 +260,8 @@ class LlamaCppInferenceProvider(
when {
line.startsWith("id = ") -> id = line.removePrefix("id = ").trim()
line.startsWith("function.name = ") -> name = line.removePrefix("function.name = ").trim()
line.startsWith("function.arguments = ") -> arguments = line.removePrefix("function.arguments = ").trim()
line.startsWith("function.arguments = ") ->
arguments = line.removePrefix("function.arguments = ").trim()
}
}
return if (name != null && arguments != null) {
@@ -41,7 +41,11 @@ class SqliteEventStoreConcurrencyTest {
assertEquals(n, events.size, "Expected exactly $n events but got ${events.size}")
val sessionSeqs = events.map { it.sessionSequence }.sorted()
assertEquals((1L..n.toLong()).toList(), sessionSeqs, "Session sequences must be exactly 1..$n with no gaps or duplicates")
assertEquals(
(1L..n.toLong()).toList(),
sessionSeqs,
"Session sequences must be exactly 1..$n with no gaps or duplicates",
)
val globalSeqs = events.map { it.sequence }
assertEquals(globalSeqs.size, globalSeqs.toSet().size, "Global sequence values must all be unique")
@@ -84,12 +84,17 @@ object InfrastructureModule {
private val defaultArtifactsRoot: String =
"${System.getProperty("user.home")}/.config/correx/artifacts"
private const val LLAMA_CODING_SCORE = 0.7
private const val LLAMA_REASONING_SCORE = 0.6
private const val LLAMA_SUMMARIZATION_SCORE = 0.8
private const val LLAMA_TOOL_CALLING_SCORE = 0.5
private val DEFAULT_LLAMA_CAPABILITIES: Set<CapabilityScore> = setOf(
CapabilityScore(ModelCapability.General, 1.0),
CapabilityScore(ModelCapability.Coding, 0.7),
CapabilityScore(ModelCapability.Reasoning, 0.6),
CapabilityScore(ModelCapability.Summarization, 0.8),
CapabilityScore(ModelCapability.ToolCalling, 0.5),
CapabilityScore(ModelCapability.Coding, LLAMA_CODING_SCORE),
CapabilityScore(ModelCapability.Reasoning, LLAMA_REASONING_SCORE),
CapabilityScore(ModelCapability.Summarization, LLAMA_SUMMARIZATION_SCORE),
CapabilityScore(ModelCapability.ToolCalling, LLAMA_TOOL_CALLING_SCORE),
)
fun createEventStore(artifactStore: ArtifactStore, dbPath: String = defaultDbPath): EventStore {
@@ -326,6 +331,17 @@ object InfrastructureModule {
val repository = DefaultTalkieRepository(replayer)
val ideaReader = IdeaReader(eventStore)
val contextBuilder = DefaultTalkieContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() })
return DefaultTalkieFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider)
return DefaultTalkieFacade(
repository,
contextBuilder,
inferenceRouter,
eventStore,
config,
null,
embedder,
l3MemoryStore,
workflowSummaryProvider,
sessionProfileProvider,
)
}
}
@@ -39,9 +39,10 @@ class FileWriteTool(
private val normalizedAllowedPaths: Set<Path> = allowedPaths.map { it.normalize().toAbsolutePath() }.toSet()
override val name: String = "file_write"
override val description: String = "Write content to a file at the specified relative path (creates or overwrites). " +
"Writing to a nested path auto-creates any missing parent directories — there is no separate " +
"mkdir step. Do not write shell commands into a file's content."
override val description: String =
"Write content to a file at the specified relative path (creates or overwrites). " +
"Writing to a nested path auto-creates any missing parent directories — there is no separate " +
"mkdir step. Do not write shell commands into a file's content."
override val parametersSchema: JsonObject = buildJsonObject {
put("type", "object")
putJsonObject("properties") {
@@ -179,7 +179,9 @@ class ListDirTool(
private fun render(root: Path, entries: List<String>, truncated: Boolean, ignoredCount: Int): String {
val notes = buildList {
if (truncated) add("truncated at $MAX_ENTRIES entries; narrow the path or list a sub-directory")
if (ignoredCount > 0) add("$ignoredCount ${if (ignoredCount == 1) "entry" else "entries"} hidden by .gitignore")
if (ignoredCount > 0) {
add("$ignoredCount ${if (ignoredCount == 1) "entry" else "entries"} hidden by .gitignore")
}
}
return buildString {
appendLine("<path>$root</path>")
@@ -203,14 +205,14 @@ class ListDirTool(
.filter { it != targetName && isSimilarName(it, targetName) }
.toList()
.sortedWith(compareByDescending<String> { commonPrefixLen(it, targetName) }.thenBy { it })
.take(3)
.take(MAX_SIMILAR_SUGGESTIONS)
}
} catch (_: Exception) { emptyList() }
}
private fun isSimilarName(a: String, b: String): Boolean {
val prefix = commonPrefixLen(a, b)
return prefix >= 2 || levenshtein(a, b) <= 3
return prefix >= SIMILAR_NAME_MIN_COMMON_PREFIX || levenshtein(a, b) <= SIMILAR_NAME_MAX_LEVENSHTEIN
}
private fun commonPrefixLen(a: String, b: String): Int {
@@ -238,6 +240,9 @@ class ListDirTool(
private companion object {
const val MAX_ENTRIES = 400
const val MAX_SIMILAR_SUGGESTIONS = 3
const val SIMILAR_NAME_MIN_COMMON_PREFIX = 2
const val SIMILAR_NAME_MAX_LEVENSHTEIN = 3
}
}
@@ -35,6 +35,7 @@ private const val MAX_RESULTS = 200
// symbol search wants, and reading it line-by-line stalls the walk. ponytail: fixed byte cap; make it
// a param if a real use case needs to grep large generated files.
private const val GREP_MAX_FILE_BYTES = 2_000_000L
private const val GREP_LINE_PREVIEW_LENGTH = 200
/**
* `.gitignore`-aware path search by glob pattern — the affordance a model should reach for to answer
@@ -58,7 +59,10 @@ class GlobTool(
putJsonObject("properties") {
putJsonObject("pattern") {
put("type", "string")
put("description", "Glob pattern, e.g. '**/*.kt' or 'src/**/use*.ts'. Matched against paths relative to 'path'.")
put(
"description",
"Glob pattern, e.g. '**/*.kt' or 'src/**/use*.ts'. Matched against paths relative to 'path'.",
)
}
putJsonObject("path") {
put("type", "string")
@@ -80,13 +84,27 @@ class GlobTool(
return@withContext ToolResult.Failure(request.invocationId, it.reason, recoverable = false)
}
val pattern = (request.parameters["pattern"] as? String)?.takeIf { it.isNotBlank() }
?: return@withContext ToolResult.Failure(request.invocationId, "glob requires a non-empty 'pattern'", recoverable = true)
?: return@withContext ToolResult.Failure(
request.invocationId,
"glob requires a non-empty 'pattern'",
recoverable = true,
)
val base = resolveSearchPath(request, workingDir)
if (!Files.isDirectory(base)) {
return@withContext ToolResult.Failure(request.invocationId, "Not a directory: ${request.parameters["path"] ?: "."}", recoverable = true)
return@withContext ToolResult.Failure(
request.invocationId,
"Not a directory: ${request.parameters["path"] ?: "."}",
recoverable = true,
)
}
val matcher = runCatching { FileSystems.getDefault().getPathMatcher("glob:$pattern") }
.getOrElse { return@withContext ToolResult.Failure(request.invocationId, "Invalid glob pattern: ${it.message}", recoverable = true) }
.getOrElse {
return@withContext ToolResult.Failure(
request.invocationId,
"Invalid glob pattern: ${it.message}",
recoverable = true,
)
}
val hits = ArrayList<String>()
var truncated = false
@@ -147,16 +165,36 @@ class GrepTool(
return@withContext ToolResult.Failure(request.invocationId, it.reason, recoverable = false)
}
val patternStr = (request.parameters["pattern"] as? String)?.takeIf { it.isNotBlank() }
?: return@withContext ToolResult.Failure(request.invocationId, "grep requires a non-empty 'pattern'", recoverable = true)
?: return@withContext ToolResult.Failure(
request.invocationId,
"grep requires a non-empty 'pattern'",
recoverable = true,
)
val regex = runCatching { Regex(patternStr) }
.getOrElse { return@withContext ToolResult.Failure(request.invocationId, "Invalid regex: ${it.message}", recoverable = true) }
.getOrElse {
return@withContext ToolResult.Failure(
request.invocationId,
"Invalid regex: ${it.message}",
recoverable = true,
)
}
val base = resolveSearchPath(request, workingDir)
if (!Files.isDirectory(base)) {
return@withContext ToolResult.Failure(request.invocationId, "Not a directory: ${request.parameters["path"] ?: "."}", recoverable = true)
return@withContext ToolResult.Failure(
request.invocationId,
"Not a directory: ${request.parameters["path"] ?: "."}",
recoverable = true,
)
}
val fileFilter = (request.parameters["glob"] as? String)?.takeIf { it.isNotBlank() }?.let {
runCatching { FileSystems.getDefault().getPathMatcher("glob:$it") }
.getOrElse { e -> return@withContext ToolResult.Failure(request.invocationId, "Invalid glob: ${e.message}", recoverable = true) }
.getOrElse { e ->
return@withContext ToolResult.Failure(
request.invocationId,
"Invalid glob: ${e.message}",
recoverable = true,
)
}
}
val hits = ArrayList<String>()
@@ -169,7 +207,7 @@ class GrepTool(
if (text != null && !text.contains('\u0000')) {
text.lineSequence().forEachIndexed { idx, line ->
if (!truncated && regex.containsMatchIn(line)) {
hits += "$rel:${idx + 1}: ${line.trim().take(200)}"
hits += "$rel:${idx + 1}: ${line.trim().take(GREP_LINE_PREVIEW_LENGTH)}"
if (hits.size >= MAX_RESULTS) truncated = true
}
}
@@ -69,7 +69,8 @@ class WorkspaceSearchToolsTest {
fun `grep respects an optional file glob and prunes gitignore`(): Unit = runBlocking {
val root = fixture()
val tool = GrepTool(allowedPaths = setOf(root), workingDir = root)
val out = (tool.execute(request("grep", mapOf("pattern" to "useAuth", "glob" to "**/*.ts"))) as ToolResult.Success).output
val req = request("grep", mapOf("pattern" to "useAuth", "glob" to "**/*.ts"))
val out = (tool.execute(req) as ToolResult.Success).output
assertTrue(out.contains("src/hooks/useAuth.ts:"), out)
assertTrue(out.contains("src/main.ts:"), out)
assertFalse(out.contains("node_modules"), out) // gitignored
@@ -119,10 +119,19 @@ class TaskCreateTool(private val service: TaskService) : Tool, ToolExecutor {
}
return null
}
val duplicates = service.findDuplicates(ProjectId(request.stringParam("project")!!), request.stringParam("title")!!)
val duplicates = service.findDuplicates(
ProjectId(request.stringParam("project")!!),
request.stringParam("title")!!,
)
return duplicates.takeIf { it.isNotEmpty() }?.let {
val listed = it.joinToString(", ") { d -> "${d.taskId.value} '${d.state.title.orEmpty()}' [${d.state.status.name}]" }
fail(request, "Possible duplicate(s): $listed. Reuse one (task_context/task_update), or force=true with force_reason.")
val listed = it.joinToString(", ") { d ->
"${d.taskId.value} '${d.state.title.orEmpty()}' [${d.state.status.name}]"
}
fail(
request,
"Possible duplicate(s): $listed. Reuse one (task_context/task_update), " +
"or force=true with force_reason.",
)
}
}
@@ -75,7 +75,10 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
putJsonObject("depends_on") {
put("type", "array")
putJsonObject("items") { put("type", "string") }
put("description", "refs (or 0-based indices) of tasks in THIS batch that must finish first.")
put(
"description",
"refs (or 0-based indices) of tasks in THIS batch that must finish first.",
)
}
}
put("required", buildJsonArray { add(JsonPrimitive("title")); add(JsonPrimitive("goal")) })
@@ -123,7 +126,9 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
dedupFailure(request, project, specs, parent)?.let { return it }
val pid = ProjectId(project)
val parentTask = parent?.let { service.createTask(pid, it.title, it.goal, it.acceptanceCriteria, it.affectedPaths) }
val parentTask = parent?.let {
service.createTask(pid, it.title, it.goal, it.acceptanceCriteria, it.affectedPaths)
}
val children = specs.map { service.createTask(pid, it.title, it.goal, it.acceptanceCriteria, it.affectedPaths) }
wireLinks(adjacency, children, parentTask)
recordProvenance(request, listOfNotNull(parentTask) + children)
@@ -140,7 +145,9 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
/** Create the `DEPENDS_ON` edges between children, and (if any) the parent's epic edges. */
private suspend fun wireLinks(adjacency: List<List<Int>>, children: List<Task>, parentTask: Task?) {
adjacency.forEachIndexed { i, deps ->
deps.forEach { j -> service.link(children[i].taskId, children[j].taskId.value, TaskLinkType.DEPENDS_ON, TaskTargetKind.TASK) }
deps.forEach { j ->
service.link(children[i].taskId, children[j].taskId.value, TaskLinkType.DEPENDS_ON, TaskTargetKind.TASK)
}
}
if (parentTask != null) {
children.forEach { child ->
@@ -156,15 +163,26 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
if (!request.boolParam("force")) return
val reason = request.stringParam("force_reason") ?: return
created.forEach {
service.addNote(it.taskId, TaskNoteAuthor.AGENT, "[force] created via decompose despite the duplicate guard: $reason")
service.addNote(
it.taskId,
TaskNoteAuthor.AGENT,
"[force] created via decompose despite the duplicate guard: $reason",
)
}
}
/** Intra-batch + against-board duplicate guard, mirroring [TaskCreateTool]; force needs a reason. */
private fun dedupFailure(request: ToolRequest, project: String, specs: List<TaskSpec>, parent: TaskSpec?): ToolResult.Failure? {
private fun dedupFailure(
request: ToolRequest,
project: String,
specs: List<TaskSpec>,
parent: TaskSpec?,
): ToolResult.Failure? {
val titles = (listOfNotNull(parent?.title) + specs.map { it.title })
val within = titles.groupBy { normalize(it) }.filter { it.value.size > 1 }.keys
if (within.isNotEmpty()) return fail(request, "Duplicate titles within this batch: ${within.joinToString(", ")}.")
if (within.isNotEmpty()) {
return fail(request, "Duplicate titles within this batch: ${within.joinToString(", ")}.")
}
if (request.boolParam("force")) {
return if (request.stringParam("force_reason") == null) {
fail(request, "force=true requires 'force_reason' explaining why a duplicate is acceptable.")
@@ -175,8 +193,14 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
val pid = ProjectId(project)
val clashes = titles.flatMap { t -> service.findDuplicates(pid, t) }.distinctBy { it.taskId.value }
return clashes.takeIf { it.isNotEmpty() }?.let {
val listed = it.joinToString(", ") { d -> "${d.taskId.value} '${d.state.title.orEmpty()}' [${d.state.status.name}]" }
fail(request, "Possible duplicate(s): $listed. Reuse one (task_context/task_update), or force=true with force_reason.")
val listed = it.joinToString(", ") { d ->
"${d.taskId.value} '${d.state.title.orEmpty()}' [${d.state.status.name}]"
}
fail(
request,
"Possible duplicate(s): $listed. Reuse one (task_context/task_update), " +
"or force=true with force_reason.",
)
}
}
@@ -194,7 +218,8 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
val readyLine = if (readyIds.isEmpty()) {
"Nothing is ready yet — check the dependency graph."
} else {
"Ready to work now: ${readyIds.joinToString(", ")}. Claim one to start; the rest unblock as their dependencies complete."
"Ready to work now: ${readyIds.joinToString(", ")}. Claim one to start; " +
"the rest unblock as their dependencies complete."
}
return "Decomposed into ${children.size} task(s)${if (parentTask != null) " under an epic" else ""}:\n" +
lines.joinToString("\n") + "\n" + readyLine
@@ -230,7 +255,9 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
(obj[key] as? JsonPrimitive)?.content?.takeIf { it.isNotBlank() && it != "null" }
private fun strList(obj: JsonObject, key: String): List<String> =
(obj[key] as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content?.takeIf(String::isNotBlank) } ?: emptyList()
(obj[key] as? JsonArray)
?.mapNotNull { (it as? JsonPrimitive)?.content?.takeIf(String::isNotBlank) }
?: emptyList()
/** Top-level args are flattened to strings by the orchestrator, so re-parse the value as JSON. */
private fun elementOf(request: ToolRequest, name: String) =
@@ -253,7 +280,9 @@ class TaskDecomposeTool(private val service: TaskService) : Tool, ToolExecutor {
val deps = ArrayList<Int>()
for (d in s.deps) {
val j = refToIndex[d] ?: d.toIntOrNull()?.takeIf { it in specs.indices }
?: return Edges.Err("task #$i ('${s.title}') depends_on '$d', which is not a ref or index in this batch.")
?: return Edges.Err(
"task #$i ('${s.title}') depends_on '$d', which is not a ref or index in this batch.",
)
if (j == i) return Edges.Err("task #$i ('${s.title}') depends on itself.")
deps.add(j)
}
@@ -35,7 +35,10 @@ class TaskReadyTool(private val service: TaskService) : Tool, ToolExecutor {
put("type", "string")
put("description", "Limit to a project (e.g. 'auth'). Omit for the whole board.")
}
putJsonObject("limit") { put("type", "integer"); put("description", "Max results (default $DEFAULT_LIMIT).") }
putJsonObject("limit") {
put("type", "integer")
put("description", "Max results (default $DEFAULT_LIMIT).")
}
}
}
override val tier: Tier = Tier.T1
@@ -50,7 +53,9 @@ class TaskReadyTool(private val service: TaskService) : Tool, ToolExecutor {
val output = if (ready.isEmpty()) {
"no ready tasks"
} else {
ready.joinToString("\n") { "${it.taskId.value} [${it.state.status.name}] ${it.state.title.orEmpty()}".trimEnd() }
ready.joinToString("\n") {
"${it.taskId.value} [${it.state.status.name}] ${it.state.title.orEmpty()}".trimEnd()
}
}
return ToolResult.Success(
invocationId = request.invocationId,
@@ -35,12 +35,18 @@ class TaskSearchTool(private val service: TaskService) : Tool, ToolExecutor {
override val parametersSchema: JsonObject = buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("query") { put("type", "string"); put("description", "Free-text query; all terms must match.") }
putJsonObject("query") {
put("type", "string")
put("description", "Free-text query; all terms must match.")
}
putJsonObject("project") {
put("type", "string")
put("description", "Limit to a project (e.g. 'auth'). Omit to search every project.")
}
putJsonObject("limit") { put("type", "integer"); put("description", "Max results (default $DEFAULT_LIMIT).") }
putJsonObject("limit") {
put("type", "integer")
put("description", "Max results (default $DEFAULT_LIMIT).")
}
}
put("required", buildJsonArray { add(JsonPrimitive("query")) })
}
@@ -61,7 +67,9 @@ class TaskSearchTool(private val service: TaskService) : Tool, ToolExecutor {
val output = if (hits.isEmpty()) {
"no tasks match \"$query\""
} else {
hits.joinToString("\n") { "${it.taskId.value} [${it.state.status.name}] ${it.state.title.orEmpty()}".trimEnd() }
hits.joinToString("\n") {
"${it.taskId.value} [${it.state.status.name}] ${it.state.title.orEmpty()}".trimEnd()
}
}
return ToolResult.Success(
invocationId = request.invocationId,
@@ -69,23 +69,35 @@ class TaskUpdateTool(
}
putJsonObject("claimant") { put("type", "string"); put("description", "Who claims it (for action=claim).") }
putJsonObject("reason") { put("type", "string"); put("description", "Reason (for action=block/cancel).") }
putJsonObject("link_target") { put("type", "string"); put("description", "Id to link to (task/artifact/session).") }
putJsonObject("link_target") {
put("type", "string")
put("description", "Id to link to (task/artifact/session).")
}
putJsonObject("link_type") {
put("type", "string")
put("description", "DEPENDS_ON, BLOCKS, IMPLEMENTS, RELATES_TO, PRODUCED, CONTEXT. Default RELATES_TO.")
}
putJsonObject("link_kind") {
put("type", "string")
put("description", "What the link points at: TASK, DOC, ARTIFACT, SESSION. Omit to infer from the target id.")
put(
"description",
"What the link points at: TASK, DOC, ARTIFACT, SESSION. Omit to infer from the target id.",
)
}
putJsonObject("note") { put("type", "string"); put("description", "Append an agent note.") }
putJsonObject("force") {
put("type", "boolean")
put("description", "Override a gate (claim over unmet blockers; complete with no in-scope writes). Default false.")
put(
"description",
"Override a gate (claim over unmet blockers; complete with no in-scope writes). Default false.",
)
}
putJsonObject("force_reason") {
put("type", "string")
put("description", "Required when force=true: why the override is justified. Recorded as a note on the task.")
put(
"description",
"Required when force=true: why the override is justified. Recorded as a note on the task.",
)
}
}
put("required", buildJsonArray { add(JsonPrimitive("id")) })
@@ -159,7 +171,10 @@ class TaskUpdateTool(
val unmet = service.blockers(taskId)
if (unmet.isEmpty()) return null
val listed = unmet.joinToString(", ") { "${it.taskId.value} [${it.state.status.name}]" }
return fail(request, "Cannot claim ${taskId.value} — unmet blockers: $listed. Complete them, or force=true with force_reason.")
return fail(
request,
"Cannot claim ${taskId.value} — unmet blockers: $listed. Complete them, or force=true with force_reason.",
)
}
/**
@@ -88,7 +88,7 @@ class WebFetchTool(
val contentType = response.headers[HttpHeaders.ContentType]
val declaredLength = response.headers[HttpHeaders.ContentLength]?.toLongOrNull()
when {
response.status.value in 300..399 ->
response.status.value in HTTP_REDIRECT_MIN..HTTP_REDIRECT_MAX ->
fail(
invocationId,
"URL redirected (HTTP ${response.status.value}) to " +
@@ -159,5 +159,7 @@ class WebFetchTool(
companion object {
const val DEFAULT_MAX_BYTES: Long = 10_000_000 // 10 MB — a 200MB "page" is an attack or a mistake
private const val READ_CHUNK = 8_192
private const val HTTP_REDIRECT_MIN = 300
private const val HTTP_REDIRECT_MAX = 399
}
}
@@ -94,7 +94,10 @@ class TaskToolsTest {
// affected_paths survived the JSON parse (not dropped like listParam would on the flattened arg).
assertEquals(listOf("apps/web/**"), service.getTask(TaskId("webui-2"))!!.state.affectedPaths)
// scaffold is the only ready task; the dependents and the epic are blocked.
assertEquals(listOf("webui-2"), service.ready(com.correx.core.events.types.ProjectId("webui")).map { it.taskId.value })
assertEquals(
listOf("webui-2"),
service.ready(com.correx.core.events.types.ProjectId("webui")).map { it.taskId.value },
)
assertEquals(listOf("webui-2"), service.blockers(TaskId("webui-3")).map { it.taskId.value })
assertEquals(3, service.blockers(TaskId("webui-1")).size)
// child implements the epic; epic depends on the child.
@@ -122,12 +125,14 @@ class TaskToolsTest {
@Test
fun `task_decompose rejects a dependency cycle`() = runBlocking {
val cyclicTasks = """[{"ref":"a","title":"A","goal":"g","depends_on":["b"]},""" +
"""{"ref":"b","title":"B","goal":"g","depends_on":["a"]}]"""
val result = decompose.execute(
request(
"task_decompose",
mapOf(
"project" to "webui",
"tasks" to """[{"ref":"a","title":"A","goal":"g","depends_on":["b"]},{"ref":"b","title":"B","goal":"g","depends_on":["a"]}]""",
"tasks" to cyclicTasks,
),
),
)
@@ -168,7 +173,8 @@ class TaskToolsTest {
assertTrue(decompose.execute(request("task_decompose", dup)) is ToolResult.Failure)
assertTrue(decompose.execute(request("task_decompose", dup + ("force" to true))) is ToolResult.Failure)
val forced = decompose.execute(request("task_decompose", dup + ("force" to true) + ("force_reason" to "intentional rebuild")))
val forcedArgs = dup + ("force" to true) + ("force_reason" to "intentional rebuild")
val forced = decompose.execute(request("task_decompose", forcedArgs))
assertTrue(forced is ToolResult.Success)
val id = (forced as ToolResult.Success).metadata.getValue("taskIds").substringBefore(",")
assertTrue(service.getTask(TaskId(id))!!.state.notes.any { it.body.startsWith("[force]") })
@@ -254,8 +260,15 @@ class TaskToolsTest {
@Test
fun `task_search ranks matches across projects and reports misses`() = runBlocking {
create.execute(request("task_create", mapOf("project" to "auth", "title" to "JWT refresh", "goal" to "stay authed")))
create.execute(request("task_create", mapOf("project" to "auth", "title" to "Rotate keys", "goal" to "rotate signing keys")))
create.execute(
request("task_create", mapOf("project" to "auth", "title" to "JWT refresh", "goal" to "stay authed")),
)
create.execute(
request(
"task_create",
mapOf("project" to "auth", "title" to "Rotate keys", "goal" to "rotate signing keys"),
),
)
val hit = search.execute(request("task_search", mapOf("query" to "jwt")))
assertTrue(hit is ToolResult.Success)
@@ -288,13 +301,18 @@ class TaskToolsTest {
fun `task_create blocks a duplicate title and force needs a recorded reason`() = runBlocking {
create.execute(request("task_create", mapOf("project" to "auth", "title" to "JWT refresh", "goal" to "g")))
val dup = create.execute(request("task_create", mapOf("project" to "auth", "title" to "jwt refresh", "goal" to "g")))
val dup = create.execute(
request("task_create", mapOf("project" to "auth", "title" to "jwt refresh", "goal" to "g")),
)
assertTrue(dup is ToolResult.Failure)
assertTrue((dup as ToolResult.Failure).reason.contains("duplicate", ignoreCase = true))
// force without a reason is rejected.
val noReason = create.execute(
request("task_create", mapOf("project" to "auth", "title" to "jwt refresh", "goal" to "g", "force" to true)),
request(
"task_create",
mapOf("project" to "auth", "title" to "jwt refresh", "goal" to "g", "force" to true),
),
)
assertTrue(noReason is ToolResult.Failure)
assertTrue((noReason as ToolResult.Failure).reason.contains("force_reason"))
@@ -332,7 +350,9 @@ class TaskToolsTest {
assertEquals(TaskStatus.TODO, service.getTask(TaskId("auth-2"))!!.state.status) // not mutated
// force without a reason is rejected.
val noReason = update.execute(request("task_update", mapOf("id" to "auth-2", "action" to "claim", "force" to true)))
val noReason = update.execute(
request("task_update", mapOf("id" to "auth-2", "action" to "claim", "force" to true)),
)
assertTrue(noReason is ToolResult.Failure)
assertTrue((noReason as ToolResult.Failure).reason.contains("force_reason"))
assertEquals(TaskStatus.TODO, service.getTask(TaskId("auth-2"))!!.state.status)
@@ -341,12 +361,21 @@ class TaskToolsTest {
val forced = update.execute(
request(
"task_update",
mapOf("id" to "auth-2", "action" to "claim", "force" to true, "force_reason" to "prep work, auth-1 lands soon"),
mapOf(
"id" to "auth-2",
"action" to "claim",
"force" to true,
"force_reason" to "prep work, auth-1 lands soon",
),
),
)
assertTrue((forced as ToolResult.Success).output.contains("WARNING"))
assertEquals(TaskStatus.IN_PROGRESS, service.getTask(TaskId("auth-2"))!!.state.status)
assertTrue(service.getTask(TaskId("auth-2"))!!.state.notes.any { it.body.contains("[force]") && it.body.contains("prep work") })
assertTrue(
service.getTask(TaskId("auth-2"))!!.state.notes.any {
it.body.contains("[force]") && it.body.contains("prep work")
},
)
}
@Test
@@ -41,7 +41,9 @@ object PlanLinter {
fun lint(graph: WorkflowGraph, seeds: Set<String> = seedArtifacts): PlanLintResult =
PlanLintResult(
hardFailures = unproducedNeeds(graph, seeds) + trapStates(graph) + unreferencedPromptArtifacts(graph, seeds),
hardFailures = unproducedNeeds(graph, seeds) +
trapStates(graph) +
unreferencedPromptArtifacts(graph, seeds),
softFindings = stageCount(graph) + fanOut(graph) + emptyBriefs(graph) + duplicateBriefs(graph),
)
@@ -147,7 +147,10 @@ class PlanLinterTest {
start = "implement",
)
val result = PlanLinter.lint(g)
assertTrue(result.hardFailures.none { it.code == "trap_state" }, "loop with exit must not trap: ${result.hardFailures}")
assertTrue(
result.hardFailures.none { it.code == "trap_state" },
"loop with exit must not trap: ${result.hardFailures}",
)
assertTrue(result.passed, "hard=${result.hardFailures}")
}