diff --git a/.claude/hooks/dist/arch-context-inject.mjs b/.claude/hooks/dist/arch-context-inject.mjs index 60a45b1b..2b077e11 100644 --- a/.claude/hooks/dist/arch-context-inject.mjs +++ b/.claude/hooks/dist/arch-context-inject.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/daemon-client.mjs b/.claude/hooks/dist/daemon-client.mjs index 96c5bbd6..ce2536ba 100644 --- a/.claude/hooks/dist/daemon-client.mjs +++ b/.claude/hooks/dist/daemon-client.mjs @@ -1,6 +1,6 @@ // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -15,8 +15,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -26,7 +25,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -165,18 +169,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -190,6 +208,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/edit-context-inject.mjs b/.claude/hooks/dist/edit-context-inject.mjs index 8a5ff8a7..220d2039 100644 --- a/.claude/hooks/dist/edit-context-inject.mjs +++ b/.claude/hooks/dist/edit-context-inject.mjs @@ -4,7 +4,7 @@ import { basename } from "path"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -19,8 +19,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -30,7 +29,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -164,18 +168,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -189,6 +207,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/impact-refactor.mjs b/.claude/hooks/dist/impact-refactor.mjs index a785bb82..33734557 100644 --- a/.claude/hooks/dist/impact-refactor.mjs +++ b/.claude/hooks/dist/impact-refactor.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/import-validator.mjs b/.claude/hooks/dist/import-validator.mjs index 02f04b29..f620517e 100644 --- a/.claude/hooks/dist/import-validator.mjs +++ b/.claude/hooks/dist/import-validator.mjs @@ -4,7 +4,7 @@ import { basename } from "path"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -19,8 +19,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -30,7 +29,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -164,18 +168,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -189,6 +207,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/post-edit-diagnostics.mjs b/.claude/hooks/dist/post-edit-diagnostics.mjs index 4c1c3bc0..15e2c85c 100644 --- a/.claude/hooks/dist/post-edit-diagnostics.mjs +++ b/.claude/hooks/dist/post-edit-diagnostics.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/post-edit-notify.mjs b/.claude/hooks/dist/post-edit-notify.mjs index 0187793a..facd4266 100644 --- a/.claude/hooks/dist/post-edit-notify.mjs +++ b/.claude/hooks/dist/post-edit-notify.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/session-start-dead-code.mjs b/.claude/hooks/dist/session-start-dead-code.mjs index 67a0b757..5336cf5c 100644 --- a/.claude/hooks/dist/session-start-dead-code.mjs +++ b/.claude/hooks/dist/session-start-dead-code.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/signature-helper.mjs b/.claude/hooks/dist/signature-helper.mjs index 1b040fc8..09336eb3 100644 --- a/.claude/hooks/dist/signature-helper.mjs +++ b/.claude/hooks/dist/signature-helper.mjs @@ -3,7 +3,7 @@ import { readFileSync as readFileSync2 } from "fs"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -18,8 +18,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -29,7 +28,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -163,18 +167,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -188,6 +206,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/smart-search-router.mjs b/.claude/hooks/dist/smart-search-router.mjs index c917411b..5a3dd115 100644 --- a/.claude/hooks/dist/smart-search-router.mjs +++ b/.claude/hooks/dist/smart-search-router.mjs @@ -5,7 +5,7 @@ import { join as join2 } from "path"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -20,8 +20,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -31,7 +30,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -165,18 +169,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -190,6 +208,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/tldr-context-inject.mjs b/.claude/hooks/dist/tldr-context-inject.mjs index c725edb9..f87b2994 100644 --- a/.claude/hooks/dist/tldr-context-inject.mjs +++ b/.claude/hooks/dist/tldr-context-inject.mjs @@ -4,7 +4,7 @@ import { join as join2, dirname } from "path"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -19,8 +19,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -30,7 +29,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -164,18 +168,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -189,6 +207,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/dist/tldr-read-enforcer.mjs b/.claude/hooks/dist/tldr-read-enforcer.mjs index 2aee9800..9d6613ad 100644 --- a/.claude/hooks/dist/tldr-read-enforcer.mjs +++ b/.claude/hooks/dist/tldr-read-enforcer.mjs @@ -4,7 +4,7 @@ import { basename, extname } from "path"; // src/daemon-client.ts import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs"; -import { execSync, spawnSync } from "child_process"; +import { execSync, spawn } from "child_process"; import { join, resolve } from "path"; import { tmpdir } from "os"; import * as net from "net"; @@ -19,8 +19,7 @@ function getLockPath(projectDir) { } function getPidPath(projectDir) { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash("md5").update(resolvedPath).digest("hex").substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, ".tldr", "daemon.pid"); } function isDaemonProcessRunning(projectDir) { const pidPath = getPidPath(projectDir); @@ -30,7 +29,12 @@ function isDaemonProcessRunning(projectDir) { if (isNaN(pid) || pid <= 0) return false; process.kill(pid, 0); return true; - } catch { + } catch (e) { + if (e.code === "EPERM") return true; + try { + unlinkSync(pidPath); + } catch { + } return false; } } @@ -164,18 +168,32 @@ function tryStartDaemon(projectDir) { const tldrPath = join(projectDir, "opc", "packages", "tldr-code"); let started = false; if (existsSync(tldrPath)) { - const result = spawnSync("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { - timeout: 1e4, - stdio: "ignore", - cwd: tldrPath - }); - started = result.status === 0; + try { + execSync("uv --version", { stdio: "ignore", timeout: 2e3 }); + const child = spawn("uv", ["run", "tldr", "daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true, + cwd: tldrPath + }); + child.on("error", () => { + }); + if (child.pid !== void 0) { + child.unref(); + started = true; + } + } catch { + } } if (!started && !process.env.TLDR_DEV) { - spawnSync("tldr", ["daemon", "start", "--project", projectDir], { - timeout: 5e3, - stdio: "ignore" + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { }); + child.unref(); } const start = Date.now(); while (Date.now() - start < 1e4) { @@ -189,6 +207,23 @@ function tryStartDaemon(projectDir) { while (Date.now() < end) { } } + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn("tldr", ["daemon", "start", "--project", projectDir], { + detached: true, + stdio: "ignore", + windowsHide: true + }); + child.on("error", () => { + }); + child.unref(); + const lrStart = Date.now(); + while (Date.now() - lrStart < 3e3) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { + } + } + } return isDaemonReachable(projectDir); } finally { releaseLock(projectDir); diff --git a/.claude/hooks/src/daemon-client.ts b/.claude/hooks/src/daemon-client.ts index 479b997a..e474b9ba 100644 --- a/.claude/hooks/src/daemon-client.ts +++ b/.claude/hooks/src/daemon-client.ts @@ -11,7 +11,7 @@ */ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs'; -import { execSync, spawnSync } from 'child_process'; +import { execSync, spawn } from 'child_process'; import { join, resolve } from 'path'; import { tmpdir } from 'os'; import * as net from 'net'; @@ -37,11 +37,11 @@ function getLockPath(projectDir: string): string { /** * Get PID file path for daemon. + * The Python TLDR daemon writes its PID to .tldr/daemon.pid inside the project directory. */ function getPidPath(projectDir: string): string { const resolvedPath = resolveProjectDir(projectDir); - const hash = crypto.createHash('md5').update(resolvedPath).digest('hex').substring(0, 8); - return `${tmpdir()}/tldr-${hash}.pid`; + return join(resolvedPath, '.tldr', 'daemon.pid'); } /** @@ -59,8 +59,11 @@ function isDaemonProcessRunning(projectDir: string): boolean { // kill(pid, 0) checks if process exists without sending signal process.kill(pid, 0); return true; // Process exists - } catch { - return false; // Process doesn't exist or permission denied + } catch (e: any) { + if (e.code === 'EPERM') return true; // Process alive, different permissions + // ESRCH or other error — stale PID file, clean it up + try { unlinkSync(pidPath); } catch {} + return false; } } @@ -385,23 +388,44 @@ export function tryStartDaemon(projectDir: string): boolean { const tldrPath = join(projectDir, 'opc', 'packages', 'tldr-code'); let started = false; - // Try local dev installation first (only if it exists) + // Use spawn+detach so daemon survives as independent background process. + // Do NOT use spawnSync — it kills the child process on timeout, which happens + // during initial indexing (30-60s) and causes repeated daemon spawns. + + // Try local dev installation first (only if uv is available AND tldr path exists). + // spawn() emits 'error' asynchronously for ENOENT — not catchable after unref(). + // Guard: check uv is in PATH synchronously, then check child.pid after spawn. if (existsSync(tldrPath)) { - const result = spawnSync('uv', ['run', 'tldr', 'daemon', 'start', '--project', projectDir], { - timeout: 10000, - stdio: 'ignore', - cwd: tldrPath, - }); - started = result.status === 0; + try { + execSync('uv --version', { stdio: 'ignore', timeout: 2000 }); + const child = spawn('uv', ['run', 'tldr', 'daemon', 'start', '--project', projectDir], { + detached: true, + stdio: 'ignore', + windowsHide: true, + cwd: tldrPath, + }); + // Register unconditionally — error fires when pid is undefined (spawn failed), + // so the handler must be attached before the pid check, not inside it. + child.on('error', () => { /* uv disappeared between check and spawn */ }); + if (child.pid !== undefined) { + child.unref(); + started = true; + } + // If child.pid is undefined the OS rejected the spawn — fall through to fallback + } catch { + // uv not available — fall through to global tldr fallback below + } } - // Fallback to global tldr if local didn't work - // Skip fallback in dev mode (TLDR_DEV=1) to prevent duplicate daemons + // Fallback to global tldr if local didn't start (or TLDR_DEV is set) if (!started && !process.env.TLDR_DEV) { - spawnSync('tldr', ['daemon', 'start', '--project', projectDir], { - timeout: 5000, + const child = spawn('tldr', ['daemon', 'start', '--project', projectDir], { + detached: true, stdio: 'ignore', + windowsHide: true, }); + child.on('error', () => { /* tldr not in PATH — callers degrade gracefully */ }); + child.unref(); } // Wait for daemon to become reachable (up to 10s for slow starts) @@ -418,6 +442,30 @@ export function tryStartDaemon(projectDir: string): boolean { while (Date.now() < end) { /* spin */ } } + // Last-resort fallback: uv was used but daemon never became reachable. + // This covers the case where uv is in PATH but tldr is not installed in + // the uv environment — uv exits non-zero silently, no PID file is written. + // Without this, started=true suppresses the global fallback and every + // subsequent invocation spins for 10s. + if (started && !isDaemonProcessRunning(projectDir) && !isDaemonReachable(projectDir) && !process.env.TLDR_DEV) { + const child = spawn('tldr', ['daemon', 'start', '--project', projectDir], { + detached: true, + stdio: 'ignore', + windowsHide: true, + }); + child.on('error', () => {}); + child.unref(); + + // Give the global daemon a brief window to become reachable for the current request. + // Without this wait, isDaemonReachable() below always returns false immediately. + const lrStart = Date.now(); + while (Date.now() - lrStart < 3000) { + if (isDaemonReachable(projectDir)) break; + const wait = Date.now() + 100; + while (Date.now() < wait) { /* spin */ } + } + } + return isDaemonReachable(projectDir); } finally { // Always release lock