Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions plugin/scripts/notification.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
function gitToplevelBasename(dir) {
try {
const top = execSync("git rev-parse --show-toplevel", {
cwd: dir,
Expand All @@ -17,9 +14,60 @@ function resolveProject(cwd) {
],
timeout: 500
}).toString().trim();
if (top) return basename(top);
} catch {}
return basename(dir);
return top ? basename(top) : null;
} catch {
return null;
}
}
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
}
function gitRemoteIdentity(dir) {
try {
return normalizeGitRemote(execSync("git config --get remote.origin.url", {
cwd: dir,
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 500
}).toString().trim());
} catch {
return null;
}
}
function remoteIdentityEnabled() {
const flag = process.env["AGENTMEMORY_PROJECT_FROM_REMOTE"];
return flag === "1" || flag === "true";
}
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
if (remoteIdentityEnabled()) {
const id = gitRemoteIdentity(dir);
if (id) return id;
}
return gitToplevelBasename(dir) ?? basename(dir);
}

//#endregion
Expand Down
62 changes: 55 additions & 7 deletions plugin/scripts/post-tool-failure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
function gitToplevelBasename(dir) {
try {
const top = execSync("git rev-parse --show-toplevel", {
cwd: dir,
Expand All @@ -17,9 +14,60 @@ function resolveProject(cwd) {
],
timeout: 500
}).toString().trim();
if (top) return basename(top);
} catch {}
return basename(dir);
return top ? basename(top) : null;
} catch {
return null;
}
}
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
}
function gitRemoteIdentity(dir) {
try {
return normalizeGitRemote(execSync("git config --get remote.origin.url", {
cwd: dir,
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 500
}).toString().trim());
} catch {
return null;
}
}
function remoteIdentityEnabled() {
const flag = process.env["AGENTMEMORY_PROJECT_FROM_REMOTE"];
return flag === "1" || flag === "true";
}
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
if (remoteIdentityEnabled()) {
const id = gitRemoteIdentity(dir);
if (id) return id;
}
return gitToplevelBasename(dir) ?? basename(dir);
}

//#endregion
Expand Down
62 changes: 55 additions & 7 deletions plugin/scripts/post-tool-use.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
function gitToplevelBasename(dir) {
try {
const top = execSync("git rev-parse --show-toplevel", {
cwd: dir,
Expand All @@ -17,9 +14,60 @@ function resolveProject(cwd) {
],
timeout: 500
}).toString().trim();
if (top) return basename(top);
} catch {}
return basename(dir);
return top ? basename(top) : null;
} catch {
return null;
}
}
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
Comment on lines +22 to +41
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return null for local-path remotes instead of treating them as canonical IDs.

With AGENTMEMORY_PROJECT_FROM_REMOTE=1, a local origin like C:/work/repo.git currently normalizes to c:/work/repo, which is machine-specific and reintroduces the fragmentation this feature is meant to eliminate. These cases should fail normalization so resolveProject() falls back to the git toplevel/cwd basename.

Suggested fix
 function normalizeGitRemote(url) {
 	const raw = (url ?? "").trim();
 	if (!raw) return null;
+	if (/^(file:|\/|\.{1,2}[\\/]|[A-Za-z]:[\\/]|\\\\)/.test(raw)) return null;
 	let host = "";
 	let path = "";
 	const scp = raw.match(/^[^`@/`]+@([^:/]+):(.+)$/);
 	if (scp) {
 		host = scp[1];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
if (/^(file:|\/|\.{1,2}[\\/]|[A-Za-z]:[\\/]|\\\\)/.test(raw)) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^`@/`]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^`@/`]*`@/`, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/scripts/post-tool-use.mjs` around lines 22 - 41, normalizeGitRemote
currently treats local filesystem paths like "C:/work/repo.git" as valid
remotes; update normalizeGitRemote to detect and reject local-path remotes by
returning null so resolveProject() can fall back to repo top-level. Before
parsing/scp handling, add a guard that returns null when raw matches local path
patterns (e.g., starts with "/", "./", "../", "~/" or Windows drive-letter
patterns like /^[A-Za-z]:[\\/]/, or contains backslashes without a
protocol/user@host), then continue the existing scp/noCreds logic; reference the
normalizeGitRemote function and its local variables raw, scp, noCreds, host, and
path when making the change.

}
function gitRemoteIdentity(dir) {
try {
return normalizeGitRemote(execSync("git config --get remote.origin.url", {
cwd: dir,
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 500
}).toString().trim());
} catch {
return null;
}
}
function remoteIdentityEnabled() {
const flag = process.env["AGENTMEMORY_PROJECT_FROM_REMOTE"];
return flag === "1" || flag === "true";
}
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
if (remoteIdentityEnabled()) {
const id = gitRemoteIdentity(dir);
if (id) return id;
}
return gitToplevelBasename(dir) ?? basename(dir);
}

//#endregion
Expand Down
62 changes: 55 additions & 7 deletions plugin/scripts/pre-compact.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
function gitToplevelBasename(dir) {
try {
const top = execSync("git rev-parse --show-toplevel", {
cwd: dir,
Expand All @@ -17,9 +14,60 @@ function resolveProject(cwd) {
],
timeout: 500
}).toString().trim();
if (top) return basename(top);
} catch {}
return basename(dir);
return top ? basename(top) : null;
} catch {
return null;
}
}
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
}
function gitRemoteIdentity(dir) {
try {
return normalizeGitRemote(execSync("git config --get remote.origin.url", {
cwd: dir,
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 500
}).toString().trim());
} catch {
return null;
}
}
function remoteIdentityEnabled() {
const flag = process.env["AGENTMEMORY_PROJECT_FROM_REMOTE"];
return flag === "1" || flag === "true";
}
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
if (remoteIdentityEnabled()) {
const id = gitRemoteIdentity(dir);
if (id) return id;
}
return gitToplevelBasename(dir) ?? basename(dir);
}

//#endregion
Expand Down
62 changes: 55 additions & 7 deletions plugin/scripts/prompt-submit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
function gitToplevelBasename(dir) {
try {
const top = execSync("git rev-parse --show-toplevel", {
cwd: dir,
Expand All @@ -17,9 +14,60 @@ function resolveProject(cwd) {
],
timeout: 500
}).toString().trim();
if (top) return basename(top);
} catch {}
return basename(dir);
return top ? basename(top) : null;
} catch {
return null;
}
}
function normalizeGitRemote(url) {
const raw = (url ?? "").trim();
if (!raw) return null;
let host = "";
let path = "";
const scp = raw.match(/^[^@/]+@([^:/]+):(.+)$/);
if (scp) {
host = scp[1];
path = scp[2];
} else {
const noCreds = raw.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "").replace(/^[^@/]*@/, "");
const slash = noCreds.indexOf("/");
if (slash === -1) return null;
host = noCreds.slice(0, slash);
path = noCreds.slice(slash + 1);
}
host = host.toLowerCase().replace(/:\d+$/, "");
path = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
if (!host || !path) return null;
return `${host}/${path}`;
}
function gitRemoteIdentity(dir) {
try {
return normalizeGitRemote(execSync("git config --get remote.origin.url", {
cwd: dir,
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 500
}).toString().trim());
} catch {
return null;
}
}
function remoteIdentityEnabled() {
const flag = process.env["AGENTMEMORY_PROJECT_FROM_REMOTE"];
return flag === "1" || flag === "true";
}
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
if (explicit && explicit.trim()) return explicit.trim();
const dir = cwd && cwd.trim() ? cwd : process.cwd();
if (remoteIdentityEnabled()) {
const id = gitRemoteIdentity(dir);
if (id) return id;
}
return gitToplevelBasename(dir) ?? basename(dir);
}

//#endregion
Expand Down
Loading