// senses/git-workspace-status/src/index.ts import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; // senses/git-workspace-status/src/schema.ts import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; var snapshots = sqliteTable("snapshots", { ts: integer("ts").primaryKey(), branch: text("branch").notNull(), headShort: text("head_short").notNull(), porcelainLines: integer("porcelain_lines").notNull(), hasUpstream: integer("has_upstream").notNull(), aheadCount: integer("ahead_count").notNull(), behindCount: integer("behind_count").notNull(), /** Empty string when the snapshot succeeded; otherwise a short error summary. */ gitError: text("git_error").notNull() }); // senses/git-workspace-status/src/index.ts var GIT_TIMEOUT_MS = 15e3; function workspaceRoot() { const raw = process.env.GIT_WORKSPACE_ROOT; return raw ? resolve(raw) : resolve(process.cwd()); } function gitErrorMessage(err) { if (err instanceof Error) { const m = err.message.trim(); return m.length > 200 ? `${m.slice(0, 197)}...` : m; } return String(err); } function runGit(cwd, args) { return execFileSync("git", args, { cwd, encoding: "utf8", timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }).trimEnd(); } function countPorcelainLines(output) { if (!output) return 0; return output.split("\n").filter((line) => line.length > 0).length; } async function compute() { const root = workspaceRoot(); const ts = Date.now(); let branch = ""; let headShort = ""; let porcelainLines = 0; let hasUpstream = 0; let aheadCount = 0; let behindCount = 0; let gitError = ""; try { const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim(); if (inside !== "true") { gitError = "not a git work tree"; return { signal: { ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError }, workflow: null }; } branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]); headShort = runGit(root, ["rev-parse", "--short", "HEAD"]); porcelainLines = countPorcelainLines(runGit(root, ["status", "--porcelain"])); try { runGit(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]); hasUpstream = 1; const lb = runGit(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]); const parts = lb.split(/[\t\s]+/).filter(Boolean); if (parts.length >= 2) { aheadCount = Number.parseInt(parts[0], 10) || 0; behindCount = Number.parseInt(parts[1], 10) || 0; } } catch { hasUpstream = 0; aheadCount = 0; behindCount = 0; } } catch (e) { gitError = gitErrorMessage(e); } return { signal: { ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError }, workflow: null }; } export { compute, snapshots as table };