UNPKG

open-memex

Version:

Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.

88 lines (87 loc) • 3.29 kB
import { execSync } from "node:child_process"; import crypto from "node:crypto"; import path from "node:path"; /** v2: v1 `user` scope is renamed to `personal` (§19). */ export const PERSONAL_SCOPE = { key: "personal", kind: "personal", projectName: "personal", }; function normalizeRemote(url) { return url .trim() .replace(/\.git$/i, "") .replace(/^git@([^:]+):/, "https://$1/") .replace(/^ssh:\/\/git@/, "https://") .toLowerCase(); } function tryGitRemote(cwd) { try { const url = execSync("git config --get remote.origin.url", { cwd, stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", }).trim(); return url || null; } catch { return null; } } function sanitize(name) { const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 40); return cleaned.length > 0 ? cleaned : "project"; } function scopeFromSeed(seed, projectName) { const hash = crypto.createHash("sha256").update(seed).digest("hex").slice(0, 12); return { key: `project__${sanitize(projectName)}__${hash}`, kind: "project", projectName, }; } /** Scope keyed purely on the absolute cwd. Never consults git. Used both as * the fallback for `resolveProjectScope` when no remote is set, and as the * "legacy" scope detector for migration when a remote is added later. */ export function resolveCwdScope(worktree) { const target = worktree && worktree.length > 0 ? worktree : "."; const resolved = path.resolve(target); const seed = resolved.toLowerCase(); // basename can return "" on Windows drive roots (e.g. "C:\\"); walk the path // segments and pick the last non-empty, non-drive-letter component. const parts = resolved .split(/[\\/]+/) .filter((p) => p && !/^[A-Za-z]:$/.test(p)); let projectName = parts[parts.length - 1] ?? path.basename(resolved) ?? "workspace"; if (!projectName) projectName = "workspace"; return scopeFromSeed(seed, projectName); } /** Pick the directory that seeds scope resolution from a host's candidate * roots. OpenCode v1 hands the plugin both `worktree` (the repo root — but * the filesystem root, e.g. `C:\` or `/`, when the opened folder is not a * git repo) and `directory` (the folder actually opened). Seeding on a * filesystem root collapses every non-git folder on a drive into one shared * `project__workspace__…` scope (D71), so prefer `worktree` only when it is * a real directory below the filesystem root. */ export function pickScopeRoot(worktree, directory) { const isFsRoot = (p) => path.parse(p).root === p; if (worktree && !isFsRoot(worktree)) return worktree; if (directory) return directory; if (worktree) return worktree; return process.cwd(); } export function resolveProjectScope(worktree) { const target = worktree && worktree.length > 0 ? worktree : "."; const remote = tryGitRemote(target); if (remote) { const norm = normalizeRemote(remote); const m = norm.match(/\/([^/]+)$/); const projectName = m?.[1] ?? "workspace"; return scopeFromSeed(norm, projectName); } return resolveCwdScope(target); }