refactor(tools): fix empty catches, remove AI slop from code comments
This commit is contained in:
@@ -141,8 +141,6 @@ Create the work plan directly - that's your job as the planning agent.`,
|
||||
categoryModel = variantToUse ? { ...normalized, variant: variantToUse } : normalized
|
||||
}
|
||||
} else if (resolutionSkipped && (agentOverride?.model ?? agentCategoryModel)) {
|
||||
// Cold cache: resolution was skipped but user explicitly configured a model.
|
||||
// Honor the user override directly — don't fall through to hardcoded fallback chain.
|
||||
const normalized = normalizeModelFormat((agentOverride?.model ?? agentCategoryModel)!)
|
||||
if (normalized) {
|
||||
const agentCategoryVariant = agentOverride?.category
|
||||
@@ -164,8 +162,6 @@ Create the work plan directly - that's your job as the planning agent.`,
|
||||
normalizedAgentFallbackModels,
|
||||
defaultProviderID,
|
||||
)
|
||||
// Don't assign hardcoded fallback chain when resolution was skipped (cold cache)
|
||||
// — the chain may contain model IDs that don't exist in the provider yet.
|
||||
fallbackChain = configuredFallbackChain ?? (resolutionSkipped ? undefined : agentRequirement?.fallbackChain)
|
||||
|
||||
// Only promote fallback-only settings when resolution actually selected a fallback model.
|
||||
|
||||
@@ -227,10 +227,10 @@ describe("hashline edit operations", () => {
|
||||
})
|
||||
|
||||
it("preserves blank lines and indentation in range replace (no false unwrap)", () => {
|
||||
//#given — reproduces the 애국가 bug where blank+indented lines collapse
|
||||
//#given, reproduces the 애국가 bug where blank+indented lines collapse
|
||||
const lines = ["", "동해물과 백두산이 마르고 닳도록", "하느님이 보우하사 우리나라 만세", "", "무궁화 삼천리 화려강산", "대한사람 대한으로 길이 보전하세", ""]
|
||||
|
||||
//#when — replace the range with indented version (blank lines preserved)
|
||||
//#when, replace the range with indented version (blank lines preserved)
|
||||
const result = applyReplaceLines(
|
||||
lines,
|
||||
anchorFor(lines, 1),
|
||||
@@ -238,7 +238,7 @@ describe("hashline edit operations", () => {
|
||||
["", " 동해물과 백두산이 마르고 닳도록", " 하느님이 보우하사 우리나라 만세", "", " 무궁화 삼천리 화려강산", " 대한사람 대한으로 길이 보전하세", ""]
|
||||
)
|
||||
|
||||
//#then — all 7 lines preserved with indentation, not collapsed to 3
|
||||
//#then, all 7 lines preserved with indentation, not collapsed to 3
|
||||
expect(result).toEqual(["", " 동해물과 백두산이 마르고 닳도록", " 하느님이 보우하사 우리나라 만세", "", " 무궁화 삼천리 화려강산", " 대한사람 대한으로 길이 보전하세", ""])
|
||||
})
|
||||
|
||||
|
||||
@@ -350,10 +350,10 @@ describe("runFormattersForFile", () => {
|
||||
},
|
||||
})
|
||||
|
||||
//#when — run for a .go file, but only .ts formatters registered
|
||||
//#when, run for a .go file, but only .ts formatters registered
|
||||
await runFormattersForFile(client, "/project", "/src/main.go")
|
||||
|
||||
//#then — no error thrown
|
||||
//#then, no error thrown
|
||||
})
|
||||
|
||||
it("runs formatter for matching extension", async () => {
|
||||
@@ -367,10 +367,10 @@ describe("runFormattersForFile", () => {
|
||||
},
|
||||
})
|
||||
|
||||
//#when — echo is a safe no-op command
|
||||
//#when, echo is a safe no-op command
|
||||
await runFormattersForFile(client, "/tmp", "/tmp/test.ts")
|
||||
|
||||
//#then — should complete without error
|
||||
//#then, should complete without error
|
||||
expect(client.config.get).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,10 +23,10 @@ describe("parseLineRef", () => {
|
||||
})
|
||||
|
||||
it("gives specific hint when literal text is used instead of line number", () => {
|
||||
//#given — model sends "LINE#HK" instead of "1#HK"
|
||||
//#given, model sends "LINE#HK" instead of "1#HK"
|
||||
const ref = "LINE#HK"
|
||||
|
||||
//#when / #then — error should mention that LINE is not a valid number
|
||||
//#when / #then, error should mention that LINE is not a valid number
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
@@ -39,10 +39,10 @@ describe("parseLineRef", () => {
|
||||
})
|
||||
|
||||
it("extracts valid line number from mixed prefix like LINE42 without throwing", () => {
|
||||
//#given — normalizeLineRef extracts 42#VK from LINE42#VK
|
||||
//#given, normalizeLineRef extracts 42#VK from LINE42#VK
|
||||
const ref = "LINE42#VK"
|
||||
|
||||
//#when / #then — should parse successfully as line 42
|
||||
//#when / #then, should parse successfully as line 42
|
||||
const result = parseLineRef(ref)
|
||||
expect(result.line).toBe(42)
|
||||
expect(result.hash).toBe("VK")
|
||||
@@ -144,11 +144,11 @@ describe("validateLineRef", () => {
|
||||
})
|
||||
|
||||
it("suggests correct line number when hash matches a file line", () => {
|
||||
//#given — model sends LINE#XX where XX is the actual hash for line 1
|
||||
//#given, model sends LINE#XX where XX is the actual hash for line 1
|
||||
const lines = ["function hello() {", " return 42", "}"]
|
||||
const hash = computeLineHash(1, lines[0])
|
||||
|
||||
//#when / #then — error should suggest the correct reference
|
||||
//#when / #then, error should suggest the correct reference
|
||||
expect(() => validateLineRefs(lines, [`LINE#${hash}`])).toThrow(new RegExp(`1#${hash}`))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -48,7 +48,6 @@ export function parseLineRef(ref: string): LineRef {
|
||||
hash: match[2],
|
||||
}
|
||||
}
|
||||
// normalized equals ref.trim() in all error paths — extraction only succeeds for valid refs
|
||||
const hashIdx = normalized.indexOf('#')
|
||||
if (hashIdx > 0) {
|
||||
const prefix = normalized.slice(0, hashIdx)
|
||||
|
||||
@@ -20,8 +20,7 @@ describe("isServerInstalled", () => {
|
||||
afterEach(() => {
|
||||
try {
|
||||
rmSync(tempDir, { recursive: true, force: true })
|
||||
} catch (e) {
|
||||
// cleanup failed — ignored
|
||||
} catch {
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
type ManagedClientForCleanup = {
|
||||
client: {
|
||||
stop: () => Promise<void>;
|
||||
@@ -22,23 +24,32 @@ export type LspProcessCleanupHandle = {
|
||||
export function registerLspManagerProcessCleanup(options: ProcessCleanupOptions): LspProcessCleanupHandle {
|
||||
const handlers: RegisteredHandler[] = [];
|
||||
|
||||
// Synchronous cleanup for 'exit' event (cannot await)
|
||||
const logCleanupError = (phase: string, error: unknown): void => {
|
||||
log(`[lsp-manager-process-cleanup] ${phase}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
};
|
||||
|
||||
const syncCleanup = () => {
|
||||
for (const [, managed] of options.getClients()) {
|
||||
try {
|
||||
// Fire-and-forget during sync exit - process is terminating
|
||||
void managed.client.stop().catch(() => {});
|
||||
} catch {}
|
||||
void managed.client.stop().catch((error) => {
|
||||
logCleanupError("stop failed during exit cleanup", error);
|
||||
});
|
||||
} catch (error) {
|
||||
logCleanupError("failed to schedule exit cleanup", error);
|
||||
}
|
||||
}
|
||||
options.clearClients();
|
||||
options.clearCleanupInterval();
|
||||
};
|
||||
|
||||
// Async cleanup for signal handlers - properly await all stops
|
||||
const asyncCleanup = async () => {
|
||||
const stopPromises: Promise<void>[] = [];
|
||||
for (const [, managed] of options.getClients()) {
|
||||
stopPromises.push(managed.client.stop().catch(() => {}));
|
||||
stopPromises.push(managed.client.stop().catch((error) => {
|
||||
logCleanupError("stop failed during signal cleanup", error);
|
||||
}));
|
||||
}
|
||||
await Promise.allSettled(stopPromises);
|
||||
options.clearClients();
|
||||
@@ -52,8 +63,9 @@ export function registerLspManagerProcessCleanup(options: ProcessCleanupOptions)
|
||||
|
||||
registerHandler("exit", syncCleanup);
|
||||
|
||||
// Don't call process.exit() here; other handlers (background-agent manager) handle final exit.
|
||||
const signalCleanup = () => void asyncCleanup().catch(() => {});
|
||||
const signalCleanup = () => void asyncCleanup().catch((error) => {
|
||||
logCleanupError("signal cleanup failed", error);
|
||||
});
|
||||
registerHandler("SIGINT", signalCleanup);
|
||||
registerHandler("SIGTERM", signalCleanup);
|
||||
if (process.platform === "win32") {
|
||||
|
||||
@@ -2,11 +2,9 @@ import { spawn as bunSpawn } from "bun"
|
||||
import { spawn as nodeSpawn, type ChildProcess } from "node:child_process"
|
||||
import { existsSync, statSync } from "fs"
|
||||
import { log } from "../../shared/logger"
|
||||
// Bun spawn segfaults on Windows (oven-sh/bun#25798) — unfixed as of v1.3.8+
|
||||
function shouldUseNodeSpawn(): boolean {
|
||||
return process.platform === "win32"
|
||||
}
|
||||
// Prevents segfaults when libuv gets a non-existent cwd (oven-sh/bun#25798)
|
||||
export function validateCwd(cwd: string): { valid: boolean; error?: string } {
|
||||
try {
|
||||
if (!existsSync(cwd)) {
|
||||
@@ -24,7 +22,6 @@ export function validateCwd(cwd: string): { valid: boolean; error?: string } {
|
||||
interface StreamReader {
|
||||
read(): Promise<{ done: boolean; value: Uint8Array | undefined }>
|
||||
}
|
||||
// Bridges Bun Subprocess and Node.js ChildProcess under a common API
|
||||
export interface UnifiedProcess {
|
||||
stdin: { write(chunk: Uint8Array | string): void }
|
||||
stdout: { getReader(): StreamReader }
|
||||
|
||||
@@ -732,14 +732,14 @@ describe("skill tool - short name resolution", () => {
|
||||
]
|
||||
const tool = createSkillTool({ skills: loadedSkills })
|
||||
|
||||
// when / then — should not resolve (ambiguous), should suggest both
|
||||
// when / then, should not resolve (ambiguous), should suggest both
|
||||
await expect(tool.execute({ name: "debugging" }, mockContext)).rejects.toThrow(
|
||||
"not found"
|
||||
)
|
||||
})
|
||||
|
||||
it("prefers exact match over short name match", async () => {
|
||||
// given — "debugging" exists as both exact and as part of a namespace
|
||||
// given, "debugging" exists as both exact and as part of a namespace
|
||||
const loadedSkills = [
|
||||
createMockSkill("debugging"),
|
||||
createMockSkill("superpowers/debugging"),
|
||||
@@ -749,7 +749,7 @@ describe("skill tool - short name resolution", () => {
|
||||
// when
|
||||
const result = await tool.execute({ name: "debugging" }, mockContext)
|
||||
|
||||
// then — should match "debugging" exactly, not "superpowers/debugging"
|
||||
// then, should match "debugging" exactly, not "superpowers/debugging"
|
||||
expect(result).toContain("## Skill: debugging")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -535,7 +535,7 @@ describe("syncAllTasksToTodos", () => {
|
||||
// when
|
||||
await syncAllTasksToTodos(mockCtx, tasks, "session-1", writer);
|
||||
|
||||
// then — no duplicates
|
||||
// then, no duplicates
|
||||
const matching = writtenTodos.filter((t: TodoInfo) => t.content === "Task 1 (updated)");
|
||||
expect(matching.length).toBe(1);
|
||||
expect(matching[0].status).toBe("in_progress");
|
||||
|
||||
Reference in New Issue
Block a user