const { execFileSync, spawnSync } = require("child_process");
const { existsSync } = require("fs");
const { join } = require("path");
const { homedir } = require("os");
const GATE_VERSION = "0.8.6";
const SIFT_BASH = process.env.RELIARY_SIFT_BASH !== "0"; const PACK_REGEN_ON_EDIT = process.env.RELIARY_PACK_REGEN_ON_EDIT === "1";
let LOG_LEVEL = 3;
try {
if (process.env.RELIARY_LOG === "error") LOG_LEVEL = 1;
else if (process.env.RELIARY_LOG === "warn") LOG_LEVEL = 2;
else if (process.env.RELIARY_LOG === "debug") LOG_LEVEL = 4;
else if (process.env.RELIARY_LOG === "trace") LOG_LEVEL = 5;
} catch {}
function gateLog(level, msg) {
const lv = { error: 1, warn: 2, info: 3, debug: 4, trace: 5 }[level] || 3;
if (lv > LOG_LEVEL) return;
console.error(`[gate] ${msg}`);
}
let RELIARY_BIN = process.env.RELIARY_BIN_PATH || null;
if (!RELIARY_BIN) {
try {
const which = execFileSync("which", ["reliary"], { encoding: "utf-8", timeout: 2000 });
if (which) RELIARY_BIN = which.trim();
} catch {}
}
if (!RELIARY_BIN) {
try {
const which = execFileSync("which", ["reliary-agent"], { encoding: "utf-8", timeout: 2000 });
if (which) RELIARY_BIN = which.trim();
} catch {}
}
if (!RELIARY_BIN) {
for (const c of ["/usr/local/bin/reliary", "/usr/bin/reliary",
"/usr/local/bin/reliary-agent", "/usr/bin/reliary-agent",
join(homedir(), ".local", "bin", "reliary"),
join(homedir(), ".local", "bin", "reliary-agent")]) {
if (existsSync(c)) { RELIARY_BIN = c; break; }
}
}
gateLog("info", `v${GATE_VERSION} thin shim — reliary: ${RELIARY_BIN ? "found" : "missing"}${RELIARY_BIN ? ` (${RELIARY_BIN.split("/").pop()})` : ""}; pack_regen_on_edit: ${PACK_REGEN_ON_EDIT}`);
let sessionTurns = 0;
function triggerReindex(filePath) {
if (!filePath || !RELIARY_BIN) return;
try {
execFileSync(RELIARY_BIN, ["reindex-file", filePath], { stdio: "ignore", timeout: 5000 });
} catch {
}
}
function triggerPackRegen(filePath) {
if (!filePath || !RELIARY_BIN || !PACK_REGEN_ON_EDIT) return;
try {
const fs = require("fs");
const path = require("path");
let dir = path.dirname(path.resolve(filePath));
let projectRoot = null;
for (let i = 0; i < 20; i++) {
if (fs.existsSync(path.join(dir, ".reliary", "index.sqlite"))) {
projectRoot = dir;
break;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
if (!projectRoot) return;
execFileSync(RELIARY_BIN, ["pack", projectRoot, "--format", "l2l3", "--strategy", "full"], {
stdio: "ignore", timeout: 30000,
});
gateLog("debug", `pack regenerated for ${projectRoot}`);
} catch (e) {
gateLog("debug", `pack regen failed: ${e.message ? e.message.slice(0, 60) : "unknown"}`);
}
}
function handleToolCall(event) {
const name = event.toolName;
const input = event.input || {};
if (name === "write" || name === "edit") {
const p = input.path || input.file;
if (p) gateLog("debug", `post-edit reindex: ${p}`);
}
if (SIFT_BASH && name === "bash" && RELIARY_BIN) {
const cmd = input.command || "";
if (!cmd || cmd.length < 20) return; const prog = cmd.trim().split(/\s+/)[0].replace(/^.*\//, "");
const REWRITE = new Set(["git","cargo","pytest","npm","yarn","pnpm","ls","grep","rg",
"docker","kubectl","make","go","mvn","gradle","pip","ruff","mypy","tsc","eslint",
"jest","vitest","dotnet","composer","bundle"]);
if (!REWRITE.has(prog)) return;
if (/[|&;<>()`$]/.test(cmd)) return;
const result = spawnSync(RELIARY_BIN, ["wrap", "bash", "-c", cmd], {
encoding: "utf-8", timeout: 120000, maxBuffer: 10 * 1024 * 1024
});
if (result.error) {
gateLog("debug", `sift spawn error: ${result.error.message ? result.error.message.slice(0, 60) : "error"}`);
if (result.error.code === "ETIMEDOUT") {
const partial = (result.stdout || "") + (result.stderr || "");
return { block: true, reason: partial.trim() || "(command timed out after 120s)" };
}
return; }
if (result.status === 0) {
gateLog("debug", `sift: ${cmd.slice(0, 40)} → ${result.stdout.length} chars`);
return { block: true, reason: result.stdout };
}
const out = (result.stdout || result.stderr || "").trim();
gateLog("debug", `sift exit ${result.status}: ${out.slice(0, 60)}`);
return { block: true, reason: out || `(exit ${result.status})` };
}
}
function handleToolResult(event) {
const name = event.toolName;
const input = event.input || {};
if (name === "write" || name === "edit") {
const f = input.path || input.file;
triggerReindex(f);
triggerPackRegen(f);
}
}
function handleBeforeProviderRequest(event) {
const payload = event.payload;
if (!payload || !Array.isArray(payload.messages)) return payload;
let turnCount = 0;
for (const m of payload.messages) { if (m.role === "user") turnCount++; }
if (turnCount > sessionTurns) {
gateLog("info", `turn ${turnCount}`);
sessionTurns = turnCount;
}
return payload;
}
module.exports = function (pi) {
pi.on("tool_call", handleToolCall);
pi.on("tool_result", handleToolResult);
pi.on("before_provider_request", handleBeforeProviderRequest);
};
if (require.main === module && process.argv.includes("--selftest")) {
const target = process.argv[process.argv.indexOf("--selftest") + 1];
const report = { discovery: null, target: target || null, reindex: false, reason: null };
if (!RELIARY_BIN) {
report.reason = "binary discovery failed";
} else if (!target) {
report.reason = "no target file passed to --selftest";
} else {
report.discovery = RELIARY_BIN;
try {
const r = spawnSync(RELIARY_BIN, ["reindex-file", target], {
encoding: "utf-8", timeout: 10000,
});
report.reindex = r.status === 0;
if (!report.reindex) {
report.reason = (r.stderr || "").slice(0, 200) || `exit ${r.status}`;
}
} catch (e) {
report.reason = e.message;
}
}
console.log(JSON.stringify(report));
process.exit(report.discovery && report.reindex ? 0 : 1);
}