@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
40 lines (39 loc) • 2.07 kB
JavaScript
// Require this no earlier than the first Hook release on or after 2026-11-15,
// once the warning counts show no older clients (owner-approved 2026-10-14).
export const EXPECTED_REPOSITORY_REQUIRED = false;
const WRITE_ROUTES = new Set(["stage", "unstage", "discard", "commit", "push", "pr", "pr/merge", "host-token", "checkout", "fetch", "pull", "apply"]
.map(action => `/v1/git/${action}`));
export const isGitWriteRoute = (route) => WRITE_ROUTES.has(route);
/** Request metadata is a diagnostic hint, never an authentication identity.
* The raw SSH byte pipe does not expose the paired key's label to the Hook. */
function clientIdentity(headers) {
const clean = (value) => (Array.isArray(value) ? value.join(", ") : value ?? "")
.replace(/[\x00-\x1f\x7f]/g, " ").trim().slice(0, 200);
const client = clean(headers["x-phren-client"]);
const version = clean(headers["x-phren-app-version"] || headers["x-phren-client-version"]);
const agent = clean(headers["user-agent"]);
return [client, version, agent].filter(Boolean).join(" / ") || "unknown client";
}
/** Per Hook run, bounded client counts and one warning per client and route. */
export class GitWriteDiagnostics {
clients = new Map();
record(route, headers) {
let client = clientIdentity(headers);
if (!this.clients.has(client) && this.clients.size >= 128)
client = "other clients";
let entry = this.clients.get(client);
if (!entry) {
entry = { count: 0, routes: new Set() };
this.clients.set(client, entry);
}
entry.count++;
if (!entry.routes.has(route)) {
entry.routes.add(route);
console.warn(`[phren-hook] ${route}: git write without expectedRepository from ${client}; update the app before the compatibility window ends.`);
}
}
snapshot() {
return [...this.clients].map(([client, entry]) => ({ client, count: entry.count,
warning: `${entry.count} git writes without expectedRepository from ${client}` }));
}
}