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:
+4
-3
@@ -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") {
|
||||
|
||||
+8
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-9
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user