From f4c54796af8ba4deec0839a120479e78e662cc56 Mon Sep 17 00:00:00 2001 From: unurgunite Date: Fri, 18 Sep 2026 16:26:45 +0300 Subject: [PATCH 1/2] [0.1.7] Gate cli_overrides on 1.6.2 --- .../docscribe/runner/DocscribeDaemon.kt | 42 +++++++++++- .../runner/DocscribeDaemonCliOverridesTest.kt | 64 +++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/test/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemonCliOverridesTest.kt diff --git a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt index 3aa9787..de56579 100644 --- a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt +++ b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt @@ -166,7 +166,16 @@ class DocscribeDaemon( formatJson: Boolean = false, ): RunResult { val handle = synchronized(lock) { ensureRunning(projectDir) } ?: return fallback(command, file, projectDir, formatJson) - val params = if (command == "update_types") buildUpdateTypesParams(projectDir, file) else buildExecuteParams(file, projectDir) + // Daemons before 1.6.2 crash on ANY cli_overrides (their ConfigBuilder + // calls options[:include].any? on the partial hash -> NoMethodError). + // Omit overrides there; the daemon falls back to its docscribe.yml. + val includeCliOverrides = supportsCliOverrides(capabilities?.version) + val params = + if (command == "update_types") { + buildUpdateTypesParams(projectDir, file, includeCliOverrides) + } else { + buildExecuteParams(file, projectDir, includeCliOverrides) + } val response = performRpcCall(handle, command, params) // Fallback for older daemons that don't support update_types (< 1.6.2) if (command == "update_types" && isUnknownMethodError(response)) { @@ -183,9 +192,10 @@ class DocscribeDaemon( internal fun buildUpdateTypesParams( projectDir: String?, file: String? = null, + includeCliOverrides: Boolean = true, ): Map { val dir = projectDir ?: project.basePath ?: "." - return Companion.buildUpdateTypesParams(dir, file) + return Companion.buildUpdateTypesParams(dir, file, includeCliOverrides) } /** @@ -209,7 +219,7 @@ class DocscribeDaemon( return fallback("check", file = null, projectDir = projectDir, formatJson = true) } val effectiveDir = projectDir ?: project.basePath ?: "" - val params = buildBatchParams(files, effectiveDir) + val params = buildBatchParams(files, effectiveDir, includeCliOverrides = supportsCliOverrides(capabilities?.version)) val response = rpcCall(handle, "check_batch", params) return processBatchResponse(response, projectDir) } @@ -274,6 +284,7 @@ class DocscribeDaemon( private fun buildExecuteParams( file: String?, projectDir: String?, + includeCliOverrides: Boolean = true, ): Map { val dir = projectDir ?: project.basePath ?: "" val map = @@ -282,6 +293,7 @@ class DocscribeDaemon( "project_dir" to dir, "no_boilerplate" to true, ) + if (!includeCliOverrides) return map val cliOverrides = buildRbsCliOverrides(dir) if (cliOverrides != null) map["cli_overrides"] = cliOverrides return map @@ -929,6 +941,26 @@ class DocscribeDaemon( private const val BATCH_PER_FILE_TIMEOUT_SECONDS = 120L private const val SERVER_MODE_MIN_VERSION = "1.5.1" private const val BATCH_MODE_MIN_VERSION = "1.5.2" + private const val CLI_OVERRIDES_MIN_VERSION = "1.6.2" + + /** + * Whether the daemon understands the `cli_overrides` RPC parameter. + * + * Daemons before 1.6.2 merge overrides raw into `ConfigBuilder`, whose + * `filter_overrides?` calls `options[:include].any?` on the partial hash + * and crashes with `NoMethodError: undefined method 'any?' for nil`. + * Unknown versions are assumed new (preserves current behavior). + * + * @param version Full version string (e.g. `"1.6.2"`), or `null` if unknown. + * @return `true` unless the version is known to be older than 1.6.2. + */ + @JvmStatic + fun supportsCliOverrides(version: String?): Boolean { + val v = version?.trim()?.takeIf { it.isNotEmpty() } ?: return true + val parts = v.split(".").map { it.toIntOrNull() ?: return true } + return parts.size == 3 && atLeast(parts, CLI_OVERRIDES_MIN_VERSION) + } + private val sharedGson by lazy { GsonBuilder().create() } /** @@ -944,6 +976,7 @@ class DocscribeDaemon( files: List, projectDir: String, timeoutSeconds: Long = BATCH_PER_FILE_TIMEOUT_SECONDS, + includeCliOverrides: Boolean = true, ): Map { val map = mutableMapOf( @@ -952,6 +985,7 @@ class DocscribeDaemon( "no_boilerplate" to true, "timeout" to timeoutSeconds, ) + if (!includeCliOverrides) return map val cliOverrides = buildRbsCliOverridesStatic(projectDir) if (cliOverrides != null) map["cli_overrides"] = cliOverrides return map @@ -992,9 +1026,11 @@ class DocscribeDaemon( internal fun buildUpdateTypesParams( projectDir: String, file: String? = null, + includeCliOverrides: Boolean = true, ): Map { val map = mutableMapOf("dir" to projectDir) if (file != null) map["file"] = file + if (!includeCliOverrides) return map val cliOverrides = buildRbsCliOverridesStatic(projectDir) if (cliOverrides != null) map["cli_overrides"] = cliOverrides return map diff --git a/src/test/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemonCliOverridesTest.kt b/src/test/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemonCliOverridesTest.kt new file mode 100644 index 0000000..43d4c2f --- /dev/null +++ b/src/test/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemonCliOverridesTest.kt @@ -0,0 +1,64 @@ +package com.florexlabs.docscribe.runner + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File +import java.nio.file.Files + +class DocscribeDaemonCliOverridesTest { + @Test + fun `supportsCliOverrides false for gems before 1_6_2`() { + assertFalse(DocscribeDaemon.supportsCliOverrides("1.6.1")) + assertFalse(DocscribeDaemon.supportsCliOverrides("1.6.0")) + assertFalse(DocscribeDaemon.supportsCliOverrides("1.5.2")) + assertFalse(DocscribeDaemon.supportsCliOverrides("1.5.1")) + } + + @Test + fun `supportsCliOverrides true for 1_6_2 and newer`() { + assertTrue(DocscribeDaemon.supportsCliOverrides("1.6.2")) + assertTrue(DocscribeDaemon.supportsCliOverrides("1.7.0")) + assertTrue(DocscribeDaemon.supportsCliOverrides("2.0.0")) + } + + @Test + fun `supportsCliOverrides true for unknown versions`() { + assertTrue(DocscribeDaemon.supportsCliOverrides(null)) + assertTrue(DocscribeDaemon.supportsCliOverrides("")) + assertTrue(DocscribeDaemon.supportsCliOverrides("dev")) + } + + @Test + fun `buildUpdateTypesParams omits cli_overrides when gated off`() { + val dir = Files.createTempDirectory("rbs-gate-update").toFile() + try { + val sig = File(dir, "sig") + sig.mkdir() + File(sig, "a.rbs").writeText("class A; end") + val params = DocscribeDaemon.buildUpdateTypesParams(dir.absolutePath, includeCliOverrides = false) + assertFalse(params.containsKey("cli_overrides")) + } finally { + dir.deleteRecursively() + } + } + + @Test + fun `buildBatchParams omits cli_overrides when gated off`() { + val dir = Files.createTempDirectory("rbs-gate-batch").toFile() + try { + val sig = File(dir, "sig") + sig.mkdir() + File(sig, "b.rbs").writeText("class B; end") + val params = + DocscribeDaemon.buildBatchParams( + listOf("/tmp/a.rb"), + dir.absolutePath, + includeCliOverrides = false, + ) + assertFalse(params.containsKey("cli_overrides")) + } finally { + dir.deleteRecursively() + } + } +} From 03d4c10e93ce6d4b0e22417099198abaa61fdd13 Mon Sep 17 00:00:00 2001 From: unurgunite Date: Fri, 18 Sep 2026 16:55:24 +0300 Subject: [PATCH 2/2] [0.1.7] Fix detekt issues --- .../com/florexlabs/docscribe/runner/DocscribeDaemon.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt index de56579..438f71b 100644 --- a/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt +++ b/src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt @@ -51,7 +51,7 @@ import kotlin.concurrent.Volatile * - `update_types` — refresh YARD docs from RBS signatures. * - `shutdown` — graceful server stop. */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") @Service(Service.Level.PROJECT) class DocscribeDaemon( private val project: Project, @@ -942,6 +942,7 @@ class DocscribeDaemon( private const val SERVER_MODE_MIN_VERSION = "1.5.1" private const val BATCH_MODE_MIN_VERSION = "1.5.2" private const val CLI_OVERRIDES_MIN_VERSION = "1.6.2" + private const val VERSION_PARTS_COUNT = 3 /** * Whether the daemon understands the `cli_overrides` RPC parameter. @@ -958,7 +959,7 @@ class DocscribeDaemon( fun supportsCliOverrides(version: String?): Boolean { val v = version?.trim()?.takeIf { it.isNotEmpty() } ?: return true val parts = v.split(".").map { it.toIntOrNull() ?: return true } - return parts.size == 3 && atLeast(parts, CLI_OVERRIDES_MIN_VERSION) + return parts.size == VERSION_PARTS_COUNT && atLeast(parts, CLI_OVERRIDES_MIN_VERSION) } private val sharedGson by lazy { GsonBuilder().create() }