Merge pull request #3348 from code-yeongyu/refactor/ulw-repo-cleanup-20260411

refactor: simplify nullish guards and remove dead no-op paths
This commit is contained in:
YeonGyu-Kim
2026-04-12 18:04:43 +09:00
committed by GitHub
15 changed files with 156 additions and 46 deletions
@@ -79,11 +79,7 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
const normalizedPlugins = [...otherPlugins]
if (canonicalEntries.length > 0 || legacyEntries.length > 0) {
normalizedPlugins.push(pluginEntry)
} else {
normalizedPlugins.push(pluginEntry)
}
normalizedPlugins.push(pluginEntry)
config.plugin = normalizedPlugins
@@ -0,0 +1,49 @@
import { afterEach, describe, expect, test } from "bun:test"
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { parseOpenCodeConfigFileWithError } from "./parse-opencode-config-file"
describe("parseOpenCodeConfigFileWithError", () => {
const tempDirectories: string[] = []
afterEach(() => {
for (const directory of tempDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true })
}
})
test("#given a valid object config #when parsing the file #then it returns the parsed config", () => {
// given
const directory = mkdtempSync(join(tmpdir(), "omo-parse-config-"))
tempDirectories.push(directory)
const filePath = join(directory, "opencode.json")
writeFileSync(filePath, '{"plugin": ["oh-my-openagent"]}\n', "utf-8")
// when
const result = parseOpenCodeConfigFileWithError(filePath)
// then
expect(result).toEqual({
config: { plugin: ["oh-my-openagent"] },
})
})
test("#given a null config payload #when parsing the file #then it returns a null parse error", () => {
// given
const directory = mkdtempSync(join(tmpdir(), "omo-parse-config-"))
tempDirectories.push(directory)
const filePath = join(directory, "opencode.json")
writeFileSync(filePath, "null\n", "utf-8")
// when
const result = parseOpenCodeConfigFileWithError(filePath)
// then
expect(result).toEqual({
config: null,
error: `Config file parsed to null/undefined: ${filePath}. Ensure it contains valid JSON.`,
})
})
})
@@ -30,7 +30,7 @@ export function parseOpenCodeConfigFileWithError(path: string): ParseConfigResul
const config = parseJsonc<OpenCodeConfig>(content)
if (config === null || config === undefined) {
if (config == null) {
return { config: null, error: `Config file parsed to null/undefined: ${path}. Ensure it contains valid JSON.` }
}
@@ -1,9 +1,7 @@
import { z } from "zod"
export const DynamicContextPruningConfigSchema = z.object({
/** Enable dynamic context pruning (default: false) */
enabled: z.boolean().default(false),
/** Notification level: off, minimal, or detailed (default: detailed) */
notification: z.enum(["off", "minimal", "detailed"]).default("detailed"),
/** Turn protection - prevent pruning recent tool outputs */
turn_protection: z
-2
View File
@@ -1,9 +1,7 @@
import { z } from "zod"
export const RalphLoopConfigSchema = z.object({
/** Enable ralph loop functionality (default: false - opt-in feature) */
enabled: z.boolean().default(false),
/** Default max iterations if not specified in command (default: 100) */
default_max_iterations: z.number().min(1).max(1000).default(100),
/** Custom state file directory relative to project root (default: .opencode/) */
state_dir: z.string().optional(),
-1
View File
@@ -1,7 +1,6 @@
import { z } from "zod"
export const StartWorkConfigSchema = z.object({
/** Enable auto-commit after each atomic task completion (default: true) */
auto_commit: z.boolean().default(true),
})
@@ -112,6 +112,20 @@ describe("loop-detector", () => {
expect(result).toBe("read")
})
test("#given nullish inputs #when signatures are created #then null and undefined behave the same", () => {
// given
const undefinedInput = undefined
const nullInput = null
// when
const undefinedResult = createToolCallSignature("read", undefinedInput)
const nullResult = createToolCallSignature("read", nullInput)
// then
expect(undefinedResult).toBe("read")
expect(nullResult).toBe(undefinedResult)
})
test("#given tool with empty object input #when signature created #then returns bare tool name", () => {
const result = createToolCallSignature("read", {})
@@ -259,5 +273,24 @@ describe("loop-detector", () => {
expect(result).toEqual({ triggered: false })
})
})
describe("#given nullish tool inputs", () => {
test("#when recorded #then null and undefined produce the same unknown-input window", () => {
// given
const settings = resolveCircuitBreakerSettings()
// when
const undefinedWindow = recordToolCall(undefined, "read", settings, undefined)
const nullWindow = recordToolCall(undefined, "read", settings, null)
// then
expect(undefinedWindow).toEqual(nullWindow)
expect(undefinedWindow).toEqual({
lastSignature: "read::__unknown-input__",
consecutiveCount: 1,
threshold: settings.consecutiveThreshold,
})
})
})
})
})
@@ -36,7 +36,7 @@ export function recordToolCall(
settings: CircuitBreakerSettings,
toolInput?: Record<string, unknown> | null
): ToolCallWindow {
if (toolInput === undefined || toolInput === null) {
if (toolInput == null) {
return {
lastSignature: `${toolName}::__unknown-input__`,
consecutiveCount: 1,
@@ -62,7 +62,7 @@ export function recordToolCall(
}
function sortObject(obj: unknown): unknown {
if (obj === null || obj === undefined) return obj
if (obj == null) return obj
if (typeof obj !== "object") return obj
if (Array.isArray(obj)) return obj.map(sortObject)
@@ -78,7 +78,7 @@ export function createToolCallSignature(
toolName: string,
toolInput?: Record<string, unknown> | null
): string {
if (toolInput === undefined || toolInput === null) {
if (toolInput == null) {
return toolName
}
if (Object.keys(toolInput).length === 0) {
@@ -35,7 +35,7 @@ export function expandEnvVars(value: string, options: ExpandEnvVarsOptions = {})
}
export function expandEnvVarsInObject<T>(obj: T, options: ExpandEnvVarsOptions = {}): T {
if (obj === null || obj === undefined) return obj
if (obj == null) return obj
if (typeof obj === "string") return expandEnvVars(obj, options) as T
if (Array.isArray(obj)) {
return obj.map((item) => expandEnvVarsInObject(item, options)) as T
@@ -0,0 +1,55 @@
import { describe, expect, test } from "bun:test"
import { resolvePluginPath, resolvePluginPaths } from "./plugin-path-resolver"
describe("resolvePluginPath", () => {
test("#given a plugin root placeholder #when resolving the path #then it replaces the placeholder", () => {
// given
const path = "${CLAUDE_PLUGIN_ROOT}/dist/index.js"
// when
const result = resolvePluginPath(path, "/tmp/plugin-root")
// then
expect(result).toBe("/tmp/plugin-root/dist/index.js")
})
})
describe("resolvePluginPaths", () => {
test("#given a nested object #when resolving paths #then it rewrites every nested string path", () => {
// given
const value = {
command: "node",
args: ["${CLAUDE_PLUGIN_ROOT}/server.js"],
nested: {
config: "${CLAUDE_PLUGIN_ROOT}/config.json",
},
}
// when
const result = resolvePluginPaths(value, "/tmp/plugin-root")
// then
expect(result).toEqual({
command: "node",
args: ["/tmp/plugin-root/server.js"],
nested: {
config: "/tmp/plugin-root/config.json",
},
})
})
test("#given nullish input #when resolving paths #then it returns the same nullish value", () => {
// given
const nullValue = null
const undefinedValue = undefined
// when
const nullResult = resolvePluginPaths(nullValue, "/tmp/plugin-root")
const undefinedResult = resolvePluginPaths(undefinedValue, "/tmp/plugin-root")
// then
expect(nullResult).toBeNull()
expect(undefinedResult).toBeUndefined()
})
})
@@ -5,7 +5,7 @@ export function resolvePluginPath(path: string, pluginRoot: string): string {
}
export function resolvePluginPaths<T>(obj: T, pluginRoot: string): T {
if (obj === null || obj === undefined) return obj
if (obj == null) return obj
if (typeof obj === "string") {
return resolvePluginPath(obj, pluginRoot) as T
}
+5 -12
View File
@@ -16,8 +16,10 @@ interface ToolExecuteOutput {
metadata: unknown;
}
interface ToolExecuteBeforeOutput {
args: unknown;
interface DirectoryAgentsInjectorHook {
"tool.execute.before"?: (input: ToolExecuteInput, output: { args: unknown }) => Promise<void>;
"tool.execute.after": (input: ToolExecuteInput, output: ToolExecuteOutput) => Promise<void>;
event: (input: EventInput) => Promise<void>;
}
interface EventInput {
@@ -30,7 +32,7 @@ interface EventInput {
export function createDirectoryAgentsInjectorHook(
ctx: PluginInput,
modelCacheState?: { anthropicContext1MEnabled: boolean },
) {
): DirectoryAgentsInjectorHook {
const sessionCaches = new Map<string, Set<string>>();
const truncator = createDynamicTruncator(ctx, modelCacheState);
@@ -50,14 +52,6 @@ export function createDirectoryAgentsInjectorHook(
}
};
const toolExecuteBefore = async (
input: ToolExecuteInput,
output: ToolExecuteBeforeOutput,
): Promise<void> => {
void input;
void output;
};
const eventHandler = async ({ event }: EventInput) => {
const props = event.properties as Record<string, unknown> | undefined;
@@ -80,7 +74,6 @@ export function createDirectoryAgentsInjectorHook(
};
return {
"tool.execute.before": toolExecuteBefore,
"tool.execute.after": toolExecuteAfter,
event: eventHandler,
};
+5 -12
View File
@@ -16,8 +16,10 @@ interface ToolExecuteOutput {
metadata: unknown;
}
interface ToolExecuteBeforeOutput {
args: unknown;
interface DirectoryReadmeInjectorHook {
"tool.execute.before"?: (input: ToolExecuteInput, output: { args: unknown }) => Promise<void>;
"tool.execute.after": (input: ToolExecuteInput, output: ToolExecuteOutput) => Promise<void>;
event: (input: EventInput) => Promise<void>;
}
interface EventInput {
@@ -30,7 +32,7 @@ interface EventInput {
export function createDirectoryReadmeInjectorHook(
ctx: PluginInput,
modelCacheState?: { anthropicContext1MEnabled: boolean },
) {
): DirectoryReadmeInjectorHook {
const sessionCaches = new Map<string, Set<string>>();
const truncator = createDynamicTruncator(ctx, modelCacheState);
@@ -50,14 +52,6 @@ export function createDirectoryReadmeInjectorHook(
}
};
const toolExecuteBefore = async (
input: ToolExecuteInput,
output: ToolExecuteBeforeOutput,
): Promise<void> => {
void input;
void output;
};
const eventHandler = async ({ event }: EventInput) => {
const props = event.properties as Record<string, unknown> | undefined;
@@ -80,7 +74,6 @@ export function createDirectoryReadmeInjectorHook(
};
return {
"tool.execute.before": toolExecuteBefore,
"tool.execute.after": toolExecuteAfter,
event: eventHandler,
};
-4
View File
@@ -1,4 +1,3 @@
import { normalizeSDKResponse } from "../shared/normalize-sdk-response"
import { getSessionPromptParams } from "../shared/session-prompt-params-state"
import { getModelCapabilities, resolveCompatibleModelSettings } from "../shared"
@@ -58,8 +57,6 @@ function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null {
? model.id
: undefined
const providerId = provider.id
const variant = message.variant
if (typeof providerID !== "string") return null
if (typeof modelID !== "string") return null
if (typeof providerId !== "string") return null
@@ -71,7 +68,6 @@ function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null {
provider: { id: providerId },
message,
rawMessage: message,
...(typeof variant === "string" ? {} : {}),
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ export function normalizeSDKResponse<TData>(
fallback: TData,
options?: NormalizeSDKResponseOptions,
): TData {
if (response === null || response === undefined) {
if (response == null) {
return fallback
}
@@ -17,7 +17,7 @@ export function normalizeSDKResponse<TData>(
if (typeof response === "object" && "data" in response) {
const data = (response as { data?: unknown }).data
if (data !== null && data !== undefined) {
if (data != null) {
return data as TData
}