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
JavaScript
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);
}