paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
108 lines (105 loc) • 3.91 kB
JavaScript
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
init_esm_shims
} from "./chunk-IDFRWBMN.js";
// src/rag/git-state.ts
init_esm_shims();
import { execFileSync } from "child_process";
import { opendirSync, readFileSync } from "fs";
import { dirname, isAbsolute, join, resolve } from "path";
function git(projectRoot, args) {
try {
return execFileSync("git", args, {
cwd: projectRoot,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"]
}).trim();
} catch {
return void 0;
}
}
function refExists(projectRoot, ref) {
return git(projectRoot, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]) !== void 0;
}
function detectBaseBranch(projectRoot, explicit) {
if (explicit && refExists(projectRoot, explicit)) return explicit;
for (const candidate of ["main", "master"]) {
if (refExists(projectRoot, candidate)) return candidate;
}
return void 0;
}
function readGitState(projectRoot, options = {}) {
if (git(projectRoot, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
return {};
}
const branchRaw = git(projectRoot, ["symbolic-ref", "--short", "--quiet", "HEAD"]);
const branch = branchRaw && branchRaw.length > 0 ? branchRaw : void 0;
const head_commit = git(projectRoot, ["rev-parse", "HEAD"]);
const base_branch = detectBaseBranch(projectRoot, options.baseBranch);
const base_commit = base_branch ? git(projectRoot, ["merge-base", "HEAD", base_branch]) : void 0;
return { branch, head_commit, base_branch, base_commit };
}
function reflogSubjects(projectRoot, ref) {
return (git(projectRoot, ["reflog", "show", "--format=%gs", ref]) ?? "").split("\n").map((line) => line.trim()).filter(Boolean);
}
var COMMIT_ID = /^[0-9a-f]{40}([0-9a-f]{24})?$/i;
function forkBranchOf(projectRoot, branch) {
if (!branch) return null;
const created = reflogSubjects(projectRoot, `refs/heads/${branch}`).at(-1) ?? "";
let from = /^branch: Created from (.+)$/.exec(created)?.[1]?.trim() ?? "";
if (from === "HEAD") {
const moved = reflogSubjects(projectRoot, "HEAD").map((subject) => /^checkout: moving from (.+) to (.+)$/.exec(subject)).filter((match) => match !== null && match[2] === branch).at(-1);
from = moved?.[1]?.trim() ?? "";
}
from = from.replace(/^refs\/heads\//, "");
if (from === "" || from === branch || from === "HEAD" || COMMIT_ID.test(from)) return null;
return refExists(projectRoot, from) ? from : null;
}
function opensAsDirectory(path) {
try {
opendirSync(path).closeSync();
return true;
} catch {
return false;
}
}
function findGitDir(projectRoot) {
let dir = resolve(projectRoot);
for (; ; ) {
const candidate = join(dir, ".git");
let contents;
try {
contents = readFileSync(candidate, "utf8");
} catch (error) {
const code = error.code;
if (code === "EISDIR") return candidate;
if (code !== "ENOENT" && code !== "ENOTDIR" && opensAsDirectory(candidate)) return candidate;
const parent = dirname(dir);
if (parent === dir) return null;
dir = parent;
continue;
}
const pointer = /^gitdir:\s*(.+)$/m.exec(contents);
if (!pointer) return null;
const target = pointer[1].trim();
return isAbsolute(target) ? target : resolve(dir, target);
}
}
function readHeadRef(projectRoot) {
const gitDir = findGitDir(projectRoot);
if (gitDir === null) return { repo: false, branch: null, detached: false };
let head;
try {
head = readFileSync(join(gitDir, "HEAD"), "utf8").trim();
} catch {
return { repo: false, branch: null, detached: false };
}
const symbolic = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
if (symbolic) return { repo: true, branch: symbolic[1], detached: false };
return { repo: true, branch: null, detached: true };
}
export {
readGitState,
forkBranchOf,
readHeadRef
};