Make memory runtime-native for companion chat

Replace the old project-scoped file memory plumbing with runtime-native conversational memory and remove obsolete pi-memory-md shipping/wiring.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Harivansh Rathi 2026-03-08 15:24:52 -07:00
parent 9765576c0a
commit 5c389efcf9
10 changed files with 1859 additions and 1536 deletions

View file

@ -1,5 +1,4 @@
import fs from "node:fs";
import { createHash } from "node:crypto";
import os from "node:os";
import path from "node:path";
import type {
@ -62,7 +61,7 @@ export type ParsedFrontmatter = GrayMatterFile<string>["data"];
const DEFAULT_LOCAL_PATH = path.join(os.homedir(), ".pi", "memory-md");
export function getCurrentDate(): string {
return new Date().toISOString().split("T")[0] ?? "";
return new Date().toISOString().split("T")[0];
}
function expandPath(p: string): string {
@ -72,73 +71,12 @@ function expandPath(p: string): string {
return p;
}
function getLegacyProjectDirName(cwd: string): string {
return path.basename(cwd);
}
function getProjectDirName(cwd: string): string {
const projectName = getLegacyProjectDirName(cwd);
const hash = createHash("sha256")
.update(path.resolve(cwd))
.digest("hex")
.slice(0, 12);
return `${projectName}-${hash}`;
}
function migrateLegacyMemoryDir(
preferredDir: string,
legacyDir: string,
): string {
try {
fs.renameSync(legacyDir, preferredDir);
return preferredDir;
} catch (error) {
console.warn("Failed to migrate legacy memory dir:", error);
return legacyDir;
}
}
export function getMemoryDir(
settings: MemoryMdSettings,
ctx: ExtensionContext,
): string {
const basePath = settings.localPath || DEFAULT_LOCAL_PATH;
const preferredDir = path.join(basePath, getProjectDirName(ctx.cwd));
if (fs.existsSync(preferredDir)) {
return preferredDir;
}
const legacyDir = path.join(basePath, getLegacyProjectDirName(ctx.cwd));
if (fs.existsSync(legacyDir)) {
return migrateLegacyMemoryDir(preferredDir, legacyDir);
}
return preferredDir;
}
export function getProjectRepoPath(
settings: MemoryMdSettings,
ctx: ExtensionContext,
): string {
const basePath = settings.localPath || DEFAULT_LOCAL_PATH;
return path.relative(basePath, getMemoryDir(settings, ctx)).split(path.sep).join("/");
}
export function resolveMemoryPath(
settings: MemoryMdSettings,
ctx: ExtensionContext,
relativePath: string,
): string {
const memoryDir = getMemoryDir(settings, ctx);
const resolvedPath = path.resolve(memoryDir, relativePath.trim());
const resolvedRoot = path.resolve(memoryDir);
if (
resolvedPath !== resolvedRoot &&
!resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)
) {
throw new Error(`Memory path escapes root: ${relativePath}`);
}
return resolvedPath;
return path.join(basePath, path.basename(ctx.cwd));
}
function getRepoName(settings: MemoryMdSettings): string {
@ -147,38 +85,7 @@ function getRepoName(settings: MemoryMdSettings): string {
return match ? match[1] : "memory-md";
}
async function getGitHead(
pi: ExtensionAPI,
cwd: string,
): Promise<string | null> {
const result = await gitExec(pi, cwd, "rev-parse", "HEAD");
if (!result.success) {
return null;
}
const head = result.stdout.trim();
return head.length > 0 ? head : null;
}
function loadScopedSettings(settingsPath: string): MemoryMdSettings {
if (!fs.existsSync(settingsPath)) {
return {};
}
try {
const content = fs.readFileSync(settingsPath, "utf-8");
const parsed = JSON.parse(content);
const scoped = parsed["pi-memory-md"];
if (!scoped || typeof scoped !== "object" || Array.isArray(scoped)) {
return {};
}
return scoped as MemoryMdSettings;
} catch (error) {
console.warn("Failed to load memory settings:", error);
return {};
}
}
function loadSettings(cwd?: string): MemoryMdSettings {
function loadSettings(): MemoryMdSettings {
const DEFAULT_SETTINGS: MemoryMdSettings = {
enabled: true,
repoUrl: "",
@ -197,34 +104,27 @@ function loadSettings(cwd?: string): MemoryMdSettings {
"agent",
"settings.json",
);
const projectSettings = cwd
? path.join(cwd, ".pi", "settings.json")
: undefined;
const globalLoaded = loadScopedSettings(globalSettings);
const projectLoaded = projectSettings
? loadScopedSettings(projectSettings)
: {};
const loadedSettings = {
...DEFAULT_SETTINGS,
...globalLoaded,
...projectLoaded,
autoSync: {
...DEFAULT_SETTINGS.autoSync,
...globalLoaded.autoSync,
...projectLoaded.autoSync,
},
systemPrompt: {
...DEFAULT_SETTINGS.systemPrompt,
...globalLoaded.systemPrompt,
...projectLoaded.systemPrompt,
},
};
if (loadedSettings.localPath) {
loadedSettings.localPath = expandPath(loadedSettings.localPath);
if (!fs.existsSync(globalSettings)) {
return DEFAULT_SETTINGS;
}
return loadedSettings;
try {
const content = fs.readFileSync(globalSettings, "utf-8");
const parsed = JSON.parse(content);
const loadedSettings = {
...DEFAULT_SETTINGS,
...(parsed["pi-memory-md"] as MemoryMdSettings),
};
if (loadedSettings.localPath) {
loadedSettings.localPath = expandPath(loadedSettings.localPath);
}
return loadedSettings;
} catch (error) {
console.warn("Failed to load memory settings:", error);
return DEFAULT_SETTINGS;
}
}
/**
@ -265,40 +165,12 @@ export async function syncRepository(
if (fs.existsSync(localPath)) {
const gitDir = path.join(localPath, ".git");
if (!fs.existsSync(gitDir)) {
let existingEntries: string[];
try {
existingEntries = fs.readdirSync(localPath);
} catch {
return {
success: false,
message: `Path exists but is not a directory: ${localPath}`,
};
}
if (existingEntries.length === 0) {
const cloneIntoEmptyDir = await gitExec(pi, localPath, "clone", repoUrl, ".");
if (cloneIntoEmptyDir.success) {
isRepoInitialized.value = true;
const repoName = getRepoName(settings);
return {
success: true,
message: `Cloned [${repoName}] successfully`,
updated: true,
};
}
return {
success: false,
message: "Clone failed - check repo URL and auth",
};
}
return {
success: false,
message: `Directory exists but is not a git repo: ${localPath}`,
};
}
const previousHead = await getGitHead(pi, localPath);
const pullResult = await gitExec(
pi,
localPath,
@ -314,21 +186,15 @@ export async function syncRepository(
}
isRepoInitialized.value = true;
const currentHead = await getGitHead(pi, localPath);
const updated =
previousHead !== null &&
currentHead !== null &&
previousHead !== currentHead;
pullResult.stdout.includes("Updating") ||
pullResult.stdout.includes("Fast-forward");
const repoName = getRepoName(settings);
const message =
previousHead === null || currentHead === null
? `Synchronized [${repoName}]`
: updated
? `Pulled latest changes from [${repoName}]`
: `[${repoName}] is already latest`;
return {
success: true,
message,
message: updated
? `Pulled latest changes from [${repoName}]`
: `[${repoName}] is already latest`,
updated,
};
}
@ -456,7 +322,7 @@ export function writeMemoryFile(
* Build memory context for agent prompt.
*/
export function ensureDirectoryStructure(memoryDir: string): void {
function ensureDirectoryStructure(memoryDir: string): void {
const dirs = [
path.join(memoryDir, "core", "user"),
path.join(memoryDir, "core", "project"),
@ -468,7 +334,7 @@ export function ensureDirectoryStructure(memoryDir: string): void {
}
}
export function createDefaultFiles(memoryDir: string): void {
function createDefaultFiles(memoryDir: string): void {
const identityFile = path.join(memoryDir, "core", "user", "identity.md");
if (!fs.existsSync(identityFile)) {
writeMemoryFile(
@ -496,68 +362,6 @@ export function createDefaultFiles(memoryDir: string): void {
}
}
export function formatMemoryDirectoryTree(
memoryDir: string,
maxDepth = 3,
maxLines = 40,
): string {
if (!fs.existsSync(memoryDir)) {
return "Unable to generate directory tree.";
}
const lines = [`${path.basename(memoryDir) || memoryDir}/`];
let truncated = false;
function visit(dir: string, depth: number, prefix: string): void {
if (depth >= maxDepth || lines.length >= maxLines) {
truncated = true;
return;
}
let entries: fs.Dirent[];
try {
entries = fs
.readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.name !== "node_modules")
.sort((left, right) => {
if (left.isDirectory() !== right.isDirectory()) {
return left.isDirectory() ? -1 : 1;
}
return left.name.localeCompare(right.name);
});
} catch {
truncated = true;
return;
}
for (const [index, entry] of entries.entries()) {
if (lines.length >= maxLines) {
truncated = true;
return;
}
const isLast = index === entries.length - 1;
const marker = isLast ? "\\-- " : "|-- ";
const childPrefix = `${prefix}${isLast ? " " : "| "}`;
lines.push(
`${prefix}${marker}${entry.name}${entry.isDirectory() ? "/" : ""}`,
);
if (entry.isDirectory()) {
visit(path.join(dir, entry.name), depth + 1, childPrefix);
}
}
}
visit(memoryDir, 0, "");
if (truncated) {
lines.push("... (tree truncated)");
}
return lines.join("\n");
}
function buildMemoryContext(
settings: MemoryMdSettings,
ctx: ExtensionContext,
@ -630,7 +434,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
let memoryInjected = false;
pi.on("session_start", async (_event, ctx) => {
settings = loadSettings(ctx.cwd);
settings = loadSettings();
if (!settings.enabled) {
return;
@ -647,11 +451,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
return;
}
if (
settings.autoSync?.onSessionStart &&
settings.localPath &&
settings.repoUrl
) {
if (settings.autoSync?.onSessionStart && settings.localPath) {
syncPromise = syncRepository(pi, settings, repoInitialized).then(
(syncResult) => {
if (settings.repoUrl) {
@ -709,20 +509,14 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
return undefined;
});
registerAllTools(pi, () => settings, repoInitialized);
registerAllTools(pi, settings, repoInitialized);
pi.registerCommand("memory-status", {
description: "Show memory repository status",
handler: async (_args, ctx) => {
settings = loadSettings(ctx.cwd);
const projectName = path.basename(ctx.cwd);
const memoryDir = getMemoryDir(settings, ctx);
const projectRepoPath = getProjectRepoPath(settings, ctx);
const coreUserDir = path.join(memoryDir, "core", "user");
const repoConfigured = Boolean(settings.repoUrl);
const repoReady = Boolean(
settings.localPath && fs.existsSync(path.join(settings.localPath, ".git")),
);
if (!fs.existsSync(coreUserDir)) {
ctx.ui.notify(
@ -732,37 +526,12 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
return;
}
if (!repoConfigured) {
ctx.ui.notify(
`Memory: ${projectName} | Local only | Path: ${memoryDir}`,
"info",
);
return;
}
if (!repoReady || !settings.localPath) {
ctx.ui.notify(
`Memory: ${projectName} | Repo not initialized | Path: ${memoryDir}`,
"warning",
);
return;
}
const result = await gitExec(
pi,
settings.localPath,
settings.localPath!,
"status",
"--porcelain",
"--",
projectRepoPath,
);
if (!result.success) {
ctx.ui.notify(
`Memory: ${projectName} | Repo status unavailable | Path: ${memoryDir}`,
"warning",
);
return;
}
const isDirty = result.stdout.trim().length > 0;
ctx.ui.notify(
@ -775,36 +544,26 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
pi.registerCommand("memory-init", {
description: "Initialize memory repository",
handler: async (_args, ctx) => {
settings = loadSettings(ctx.cwd);
const memoryDir = getMemoryDir(settings, ctx);
const alreadyInitialized = fs.existsSync(
path.join(memoryDir, "core", "user"),
);
if (settings.repoUrl) {
const result = await syncRepository(pi, settings, repoInitialized);
if (!result.success) {
ctx.ui.notify(`Initialization failed: ${result.message}`, "error");
return;
}
const result = await syncRepository(pi, settings, repoInitialized);
if (!result.success) {
ctx.ui.notify(`Initialization failed: ${result.message}`, "error");
return;
}
ensureDirectoryStructure(memoryDir);
createDefaultFiles(memoryDir);
repoInitialized.value = true;
if (alreadyInitialized) {
ctx.ui.notify(
settings.repoUrl
? "Memory already exists and repository is ready"
: "Local memory already exists",
"info",
);
ctx.ui.notify(`Memory already exists: ${result.message}`, "info");
} else {
ctx.ui.notify(
settings.repoUrl
? "Memory initialized and repository is ready\n\nCreated:\n - core/user\n - core/project\n - reference"
: "Local memory initialized\n\nCreated:\n - core/user\n - core/project\n - reference",
`Memory initialized: ${result.message}\n\nCreated:\n - core/user\n - core/project\n - reference`,
"info",
);
}
@ -814,7 +573,6 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
pi.registerCommand("memory-refresh", {
description: "Refresh memory context from files",
handler: async (_args, ctx) => {
settings = loadSettings(ctx.cwd);
const memoryContext = buildMemoryContext(settings, ctx);
if (!memoryContext) {
@ -852,7 +610,6 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
pi.registerCommand("memory-check", {
description: "Check memory folder structure",
handler: async (_args, ctx) => {
settings = loadSettings(ctx.cwd);
const memoryDir = getMemoryDir(settings, ctx);
if (!fs.existsSync(memoryDir)) {
@ -860,7 +617,25 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
return;
}
ctx.ui.notify(formatMemoryDirectoryTree(memoryDir).trim(), "info");
const { execSync } = await import("node:child_process");
let treeOutput = "";
try {
treeOutput = execSync(`tree -L 3 -I "node_modules" "${memoryDir}"`, {
encoding: "utf-8",
});
} catch {
try {
treeOutput = execSync(
`find "${memoryDir}" -type d -not -path "*/node_modules/*"`,
{ encoding: "utf-8" },
);
} catch {
treeOutput = "Unable to generate directory tree.";
}
}
ctx.ui.notify(treeOutput.trim(), "info");
},
});
}

View file

@ -6,22 +6,15 @@ import { Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import type { MemoryFrontmatter, MemoryMdSettings } from "./memory-md.js";
import {
createDefaultFiles,
ensureDirectoryStructure,
formatMemoryDirectoryTree,
getCurrentDate,
getMemoryDir,
getProjectRepoPath,
gitExec,
listMemoryFiles,
readMemoryFile,
resolveMemoryPath,
syncRepository,
writeMemoryFile,
} from "./memory-md.js";
type MemorySettingsGetter = () => MemoryMdSettings;
function renderWithExpandHint(
text: string,
theme: Theme,
@ -41,7 +34,7 @@ function renderWithExpandHint(
export function registerMemorySync(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
isRepoInitialized: { value: boolean },
): void {
pi.registerTool({
@ -59,73 +52,26 @@ export function registerMemorySync(
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { action } = params as { action: "pull" | "push" | "status" };
const settings = getSettings();
const localPath = settings.localPath;
const localPath = settings.localPath!;
const memoryDir = getMemoryDir(settings, ctx);
const projectRepoPath = getProjectRepoPath(settings, ctx);
const coreUserDir = path.join(memoryDir, "core", "user");
const configured = Boolean(settings.repoUrl);
const initialized = fs.existsSync(coreUserDir);
const repoReady = Boolean(
localPath && fs.existsSync(path.join(localPath, ".git")),
);
if (action === "status") {
const initialized =
isRepoInitialized.value && fs.existsSync(coreUserDir);
if (!initialized) {
return {
content: [
{
type: "text",
text: "Memory not initialized. Use memory_init to set up.",
text: "Memory repository not initialized. Use memory_init to set up.",
},
],
details: { initialized: false, configured, dirty: null },
details: { initialized: false },
};
}
if (!configured) {
return {
content: [
{
type: "text",
text: "Memory repository is not configured. Local memory is available only on this machine.",
},
],
details: { initialized: true, configured: false, dirty: null },
};
}
if (!repoReady || !localPath) {
return {
content: [
{
type: "text",
text: "Memory repository is configured but not initialized locally.",
},
],
details: { initialized: true, configured: true, dirty: null },
};
}
const result = await gitExec(
pi,
localPath,
"status",
"--porcelain",
"--",
projectRepoPath,
);
if (!result.success) {
return {
content: [
{
type: "text",
text: "Unable to inspect memory repository status.",
},
],
details: { initialized: true, configured: true, dirty: null },
};
}
const result = await gitExec(pi, localPath, "status", "--porcelain");
const dirty = result.stdout.trim().length > 0;
return {
@ -142,95 +88,36 @@ export function registerMemorySync(
}
if (action === "pull") {
if (!configured) {
return {
content: [
{
type: "text",
text: "Memory repository is not configured. Nothing to pull.",
},
],
details: { success: false, configured: false },
};
}
const result = await syncRepository(pi, settings, isRepoInitialized);
return {
content: [{ type: "text", text: result.message }],
details: { success: result.success, configured: true },
details: { success: result.success },
};
}
if (action === "push") {
if (!configured || !localPath) {
return {
content: [
{
type: "text",
text: "Memory repository is not configured. Nothing to push.",
},
],
details: { success: false, configured: false },
};
}
if (!repoReady) {
return {
content: [
{
type: "text",
text: "Memory repository is configured but not initialized locally.",
},
],
details: { success: false, configured: true },
};
}
const syncResult = await syncRepository(pi, settings, isRepoInitialized);
if (!syncResult.success) {
return {
content: [{ type: "text", text: syncResult.message }],
details: { success: false, configured: true },
};
}
const statusResult = await gitExec(
pi,
localPath,
"status",
"--porcelain",
"--",
projectRepoPath,
);
if (!statusResult.success) {
return {
content: [
{
type: "text",
text: "Unable to inspect memory repository before push.",
},
],
details: { success: false, configured: true },
};
}
const hasChanges = statusResult.stdout.trim().length > 0;
if (hasChanges) {
await gitExec(pi, localPath, "add", "-A", "--", projectRepoPath);
await gitExec(pi, localPath, "add", ".");
const timestamp = new Date()
.toISOString()
.replace(/[:.]/g, "-")
.slice(0, 19);
const commitMessage = `Update memory for ${path.basename(ctx.cwd)} - ${timestamp}`;
const commitMessage = `Update memory - ${timestamp}`;
const commitResult = await gitExec(
pi,
localPath,
"commit",
"-m",
commitMessage,
"--only",
"--",
projectRepoPath,
);
if (!commitResult.success) {
@ -302,7 +189,7 @@ export function registerMemorySync(
export function registerMemoryRead(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
): void {
pi.registerTool({
name: "memory_read",
@ -317,8 +204,8 @@ export function registerMemoryRead(
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { path: relPath } = params as { path: string };
const settings = getSettings();
const fullPath = resolveMemoryPath(settings, ctx, relPath);
const memoryDir = getMemoryDir(settings, ctx);
const fullPath = path.join(memoryDir, relPath);
const memory = readMemoryFile(fullPath);
if (!memory) {
@ -384,7 +271,7 @@ export function registerMemoryRead(
export function registerMemoryWrite(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
): void {
pi.registerTool({
name: "memory_write",
@ -413,24 +300,17 @@ export function registerMemoryWrite(
tags?: string[];
};
const settings = getSettings();
const fullPath = resolveMemoryPath(settings, ctx, relPath);
const memoryDir = getMemoryDir(settings, ctx);
const fullPath = path.join(memoryDir, relPath);
const existing = readMemoryFile(fullPath);
const existingFrontmatter = existing?.frontmatter;
const existingFrontmatter = existing?.frontmatter || { description };
const frontmatter: MemoryFrontmatter = {
...existingFrontmatter,
description,
created: existingFrontmatter?.created ?? getCurrentDate(),
updated: getCurrentDate(),
...(existingFrontmatter?.limit !== undefined
? { limit: existingFrontmatter.limit }
: {}),
...(tags !== undefined
? { tags }
: existingFrontmatter?.tags
? { tags: existingFrontmatter.tags }
: {}),
...(tags && { tags }),
};
writeMemoryFile(fullPath, content, frontmatter);
@ -487,7 +367,7 @@ export function registerMemoryWrite(
export function registerMemoryList(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
): void {
pi.registerTool({
name: "memory_list",
@ -501,11 +381,8 @@ export function registerMemoryList(
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { directory } = params as { directory?: string };
const settings = getSettings();
const memoryDir = getMemoryDir(settings, ctx);
const searchDir = directory
? resolveMemoryPath(settings, ctx, directory)
: memoryDir;
const searchDir = directory ? path.join(memoryDir, directory) : memoryDir;
const files = listMemoryFiles(searchDir);
const relPaths = files.map((f) => path.relative(memoryDir, f));
@ -555,7 +432,7 @@ export function registerMemoryList(
export function registerMemorySearch(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
): void {
pi.registerTool({
name: "memory_search",
@ -580,7 +457,6 @@ export function registerMemorySearch(
query: string;
searchIn: "content" | "tags" | "description";
};
const settings = getSettings();
const memoryDir = getMemoryDir(settings, ctx);
const files = listMemoryFiles(memoryDir);
const results: Array<{ path: string; match: string }> = [];
@ -668,7 +544,7 @@ export function registerMemorySearch(
export function registerMemoryInit(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
isRepoInitialized: { value: boolean },
): void {
pi.registerTool({
@ -682,19 +558,10 @@ export function registerMemoryInit(
),
}) as any,
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
const { force = false } = params as { force?: boolean };
const settings = getSettings();
const memoryDir = getMemoryDir(settings, ctx);
const alreadyInitialized = fs.existsSync(
path.join(memoryDir, "core", "user"),
);
const repoReady = Boolean(
settings.localPath &&
fs.existsSync(path.join(settings.localPath, ".git")),
);
if (alreadyInitialized && (!settings.repoUrl || repoReady) && !force) {
if (isRepoInitialized.value && !force) {
return {
content: [
{
@ -706,35 +573,18 @@ export function registerMemoryInit(
};
}
if (settings.repoUrl) {
const result = await syncRepository(pi, settings, isRepoInitialized);
if (!result.success) {
return {
content: [
{
type: "text",
text: `Initialization failed: ${result.message}`,
},
],
details: { success: false },
};
}
}
ensureDirectoryStructure(memoryDir);
createDefaultFiles(memoryDir);
isRepoInitialized.value = true;
const result = await syncRepository(pi, settings, isRepoInitialized);
return {
content: [
{
type: "text",
text: settings.repoUrl
? `Memory repository initialized.\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`
: `Local memory initialized.\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`,
text: result.success
? `Memory repository initialized:\n${result.message}\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`
: `Initialization failed: ${result.message}`,
},
],
details: { success: true },
details: { success: result.success },
};
},
@ -778,7 +628,7 @@ export function registerMemoryInit(
export function registerMemoryCheck(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
): void {
pi.registerTool({
name: "memory_check",
@ -787,7 +637,6 @@ export function registerMemoryCheck(
parameters: Type.Object({}) as any,
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
const settings = getSettings();
const memoryDir = getMemoryDir(settings, ctx);
if (!fs.existsSync(memoryDir)) {
@ -802,7 +651,26 @@ export function registerMemoryCheck(
};
}
const treeOutput = formatMemoryDirectoryTree(memoryDir);
const { execSync } = await import("node:child_process");
let treeOutput = "";
try {
treeOutput = execSync(`tree -L 3 -I "node_modules" "${memoryDir}"`, {
encoding: "utf-8",
});
} catch {
try {
treeOutput = execSync(
`find "${memoryDir}" -type d -not -path "*/node_modules/*" | head -20`,
{
encoding: "utf-8",
},
);
} catch {
treeOutput =
"Unable to generate directory tree. Please check permissions.";
}
}
const files = listMemoryFiles(memoryDir);
const relPaths = files.map((f) => path.relative(memoryDir, f));
@ -851,14 +719,14 @@ export function registerMemoryCheck(
export function registerAllTools(
pi: ExtensionAPI,
getSettings: MemorySettingsGetter,
settings: MemoryMdSettings,
isRepoInitialized: { value: boolean },
): void {
registerMemorySync(pi, getSettings, isRepoInitialized);
registerMemoryRead(pi, getSettings);
registerMemoryWrite(pi, getSettings);
registerMemoryList(pi, getSettings);
registerMemorySearch(pi, getSettings);
registerMemoryInit(pi, getSettings, isRepoInitialized);
registerMemoryCheck(pi, getSettings);
registerMemorySync(pi, settings, isRepoInitialized);
registerMemoryRead(pi, settings);
registerMemoryWrite(pi, settings);
registerMemoryList(pi, settings);
registerMemorySearch(pi, settings);
registerMemoryInit(pi, settings, isRepoInitialized);
registerMemoryCheck(pi, settings);
}