diff --git a/packages/coding-agent/src/core/gateway/memory.ts b/packages/coding-agent/src/core/gateway/memory.ts index 709ce4f..e66b479 100644 --- a/packages/coding-agent/src/core/gateway/memory.ts +++ b/packages/coding-agent/src/core/gateway/memory.ts @@ -479,6 +479,15 @@ function getRepoName(settings: MemoryMdSettings): string { return match?.[1] ?? "memory-md"; } +async function getGitHead(cwd: string): Promise { + const result = await runGit(cwd, "rev-parse", "HEAD"); + if (!result.success) { + return null; + } + const head = result.stdout.trim(); + return head.length > 0 ? head : null; +} + async function getRepositoryDirtyState( localPath: string, projectPath?: string, @@ -541,6 +550,7 @@ async function syncRepository( message: `Directory exists but is not a git repo: ${localPath}`, }; } + const previousHead = await getGitHead(localPath); const pullResult = await runGit(localPath, "pull", "--rebase", "--autostash"); if (!pullResult.success) { return { @@ -549,14 +559,20 @@ async function syncRepository( pullResult.stderr.trim() || "Pull failed. Check repository state.", }; } + const currentHead = await getGitHead(localPath); const updated = - pullResult.stdout.includes("Updating") || - pullResult.stdout.includes("Fast-forward"); + previousHead !== null && + currentHead !== null && + previousHead !== currentHead; + const message = + previousHead === null || currentHead === null + ? `Synchronized ${getRepoName(settings)}` + : updated + ? `Pulled latest changes from ${getRepoName(settings)}` + : `${getRepoName(settings)} is already up to date`; return { success: true, - message: updated - ? `Pulled latest changes from ${getRepoName(settings)}` - : `${getRepoName(settings)} is already up to date`, + message, updated, }; } @@ -861,17 +877,16 @@ export async function syncProjectMemory( }; } - if (!repositoryReady) { - const cloned = await syncRepository(settings); - if (!cloned.success) { - return { - success: false, - message: cloned.message, - configured, - initialized, - dirty: null, - }; - } + const syncResult = await syncRepository(settings); + if (!syncResult.success) { + return { + success: false, + message: syncResult.message, + configured, + initialized, + dirty: null, + updated: syncResult.updated, + }; } const statusResult = await runGit( @@ -941,5 +956,6 @@ export async function syncProjectMemory( initialized, dirty: await getRepositoryDirtyState(localPath, projectPath), committed: hasChanges, + updated: syncResult.updated, }; } diff --git a/packages/pi-memory-md/memory-md.ts b/packages/pi-memory-md/memory-md.ts index ec54077..178388a 100644 --- a/packages/pi-memory-md/memory-md.ts +++ b/packages/pi-memory-md/memory-md.ts @@ -147,6 +147,18 @@ function getRepoName(settings: MemoryMdSettings): string { return match ? match[1] : "memory-md"; } +async function getGitHead( + pi: ExtensionAPI, + cwd: string, +): Promise { + 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 {}; @@ -286,6 +298,7 @@ export async function syncRepository( }; } + const previousHead = await getGitHead(pi, localPath); const pullResult = await gitExec( pi, localPath, @@ -301,15 +314,21 @@ export async function syncRepository( } isRepoInitialized.value = true; + const currentHead = await getGitHead(pi, localPath); const updated = - pullResult.stdout.includes("Updating") || - pullResult.stdout.includes("Fast-forward"); + previousHead !== null && + currentHead !== null && + previousHead !== currentHead; 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: updated - ? `Pulled latest changes from [${repoName}]` - : `[${repoName}] is already latest`, + message, updated, }; } @@ -477,6 +496,68 @@ 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, @@ -779,25 +860,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) { return; } - 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"); + ctx.ui.notify(formatMemoryDirectoryTree(memoryDir).trim(), "info"); }, }); } diff --git a/packages/pi-memory-md/tools.ts b/packages/pi-memory-md/tools.ts index 58feb2c..fd479f0 100644 --- a/packages/pi-memory-md/tools.ts +++ b/packages/pi-memory-md/tools.ts @@ -8,6 +8,7 @@ import type { MemoryFrontmatter, MemoryMdSettings } from "./memory-md.js"; import { createDefaultFiles, ensureDirectoryStructure, + formatMemoryDirectoryTree, getCurrentDate, getMemoryDir, getProjectRepoPath, @@ -184,6 +185,14 @@ export function registerMemorySync( }; } + 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, @@ -793,26 +802,7 @@ export function registerMemoryCheck( }; } - 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 treeOutput = formatMemoryDirectoryTree(memoryDir); const files = listMemoryFiles(memoryDir); const relPaths = files.map((f) => path.relative(memoryDir, f));