fix(hooks): send repo basename as project, not full path (#474)#687
Conversation
Hooks were sending `data.cwd` (an absolute filesystem path) as the `project` field on every observe/session/start call. Native sessions, replay-import, and manual memory_lesson_save calls all use the repo basename. The mismatch caused auto-injected context to filter out the bulk of relevant lessons because the path never matches the stored project name. Add shared `resolveProject(cwd)` helper: 1. AGENTMEMORY_PROJECT_NAME env (per-repo escape hatch) 2. basename of `git rev-parse --show-toplevel` (handles subdirs) 3. basename of cwd (final fallback when not in a git repo) Applied to 9 hooks: notification, post-tool-use, post-tool-failure, prompt-submit, session-start, subagent-start, subagent-stop, task-completed, pre-compact. Build: split hook entries into per-entry tsdown configs so each hook bundles into a fully self-contained .mjs. Previous shared config hoisted helpers into hashed chunks that changed on every rebuild.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a centralized resolveProject(cwd?) helper (env → git top-level basename → cwd basename) and uses it across TypeScript and JavaScript hook scripts and plugin emitters so outgoing hook payloads send a normalized project name; also updates tsdown config for per-hook bundling. ChangesProject Field Resolution Normalization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with 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.
Inline comments:
In `@plugin/scripts/prompt-submit.mjs`:
- Around line 6-10: The resolveProject function calls cwd.trim() without
ensuring cwd is a string; update resolveProject to validate or coerce cwd before
trimming (e.g., check typeof cwd === "string" or convert with String(cwd)) and
only call .trim() on a confirmed string, then fall back to process.cwd() when
invalid—modify the cwd handling at the start of resolveProject (the explicit/dir
assignment logic) so non-string inputs don't cause an exception.
In `@test/hook-project.test.ts`:
- Around line 33-65: Tests assume a repo named "agentmemory" and use split("/")
which is brittle; replace hardcoded expectations with a repository-agnostic
basename variable (e.g., const repoBasename = path.basename(process.cwd())) and
use that in assertions for resolveProject(), resolveProject(nested), and the
default/empty-cwd tests, and in the "falls back" test replace dir.split("/")
with path.basename(dir) to be path-separator safe; keep references to the
resolveProject function and the existing tmp dir creation but use path.basename
for all basename checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f731b206-38f4-4b39-ae15-c63f3b0d30c5
📒 Files selected for processing (22)
plugin/scripts/notification.mjsplugin/scripts/post-tool-failure.mjsplugin/scripts/post-tool-use.mjsplugin/scripts/pre-compact.mjsplugin/scripts/pre-tool-use.mjsplugin/scripts/prompt-submit.mjsplugin/scripts/session-start.mjsplugin/scripts/subagent-start.mjsplugin/scripts/subagent-stop.mjsplugin/scripts/task-completed.mjssrc/hooks/_project.tssrc/hooks/notification.tssrc/hooks/post-tool-failure.tssrc/hooks/post-tool-use.tssrc/hooks/pre-compact.tssrc/hooks/prompt-submit.tssrc/hooks/session-start.tssrc/hooks/subagent-start.tssrc/hooks/subagent-stop.tssrc/hooks/task-completed.tstest/hook-project.test.tstsdown.config.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(); | ||
| try { |
There was a problem hiding this comment.
Guard cwd type before calling .trim() in resolveProject.
Line 9 can throw when cwd is non-string input, causing this hook event to be dropped. Coerce/validate type before trimming.
Suggested fix
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();
+ const dir = typeof cwd === "string" && cwd.trim() ? cwd : process.cwd();
try {
const top = execSync("git rev-parse --show-toplevel", {🤖 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/prompt-submit.mjs` around lines 6 - 10, The resolveProject
function calls cwd.trim() without ensuring cwd is a string; update
resolveProject to validate or coerce cwd before trimming (e.g., check typeof cwd
=== "string" or convert with String(cwd)) and only call .trim() on a confirmed
string, then fall back to process.cwd() when invalid—modify the cwd handling at
the start of resolveProject (the explicit/dir assignment logic) so non-string
inputs don't cause an exception.
| it("ignores empty env override", () => { | ||
| process.env.AGENTMEMORY_PROJECT_NAME = " "; | ||
| const repoBasename = "agentmemory"; | ||
| expect(resolveProject(process.cwd())).toBe(repoBasename); | ||
| }); | ||
|
|
||
| it("returns git toplevel basename when cwd is inside a repo", () => { | ||
| const top = resolveProject(process.cwd()); | ||
| expect(top).toBe("agentmemory"); | ||
| }); | ||
|
|
||
| it("returns git toplevel basename from a nested subdir", () => { | ||
| const nested = join(process.cwd(), "src", "hooks"); | ||
| expect(resolveProject(nested)).toBe("agentmemory"); | ||
| }); | ||
|
|
||
| it("falls back to basename(cwd) when not in a git repo", () => { | ||
| const dir = mkdtempSync(join(tmpdir(), "amem-noproj-")); | ||
| try { | ||
| expect(resolveProject(dir)).toBe(dir.split("/").pop()); | ||
| } finally { | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
|
|
||
| it("defaults to process.cwd() when no cwd argument given", () => { | ||
| expect(resolveProject()).toBe("agentmemory"); | ||
| }); | ||
|
|
||
| it("defaults to process.cwd() when cwd argument is empty", () => { | ||
| expect(resolveProject("")).toBe("agentmemory"); | ||
| expect(resolveProject(" ")).toBe("agentmemory"); | ||
| }); |
There was a problem hiding this comment.
Make project-name expectations repository-agnostic and path-safe.
Several assertions assume the repo is named "agentmemory" and one uses split("/"); this makes tests brittle on forks and non-POSIX environments.
Suggested fix
-import { join } from "node:path";
+import { basename, join } from "node:path";
import { resolveProject } from "../src/hooks/_project.js";
describe("resolveProject — hook project basename resolver", () => {
+ const repoBasename = basename(process.cwd());
const originalEnv = process.env.AGENTMEMORY_PROJECT_NAME;
@@
it("ignores empty env override", () => {
process.env.AGENTMEMORY_PROJECT_NAME = " ";
- const repoBasename = "agentmemory";
expect(resolveProject(process.cwd())).toBe(repoBasename);
});
@@
it("returns git toplevel basename when cwd is inside a repo", () => {
const top = resolveProject(process.cwd());
- expect(top).toBe("agentmemory");
+ expect(top).toBe(repoBasename);
});
@@
it("returns git toplevel basename from a nested subdir", () => {
const nested = join(process.cwd(), "src", "hooks");
- expect(resolveProject(nested)).toBe("agentmemory");
+ expect(resolveProject(nested)).toBe(repoBasename);
});
@@
it("falls back to basename(cwd) when not in a git repo", () => {
const dir = mkdtempSync(join(tmpdir(), "amem-noproj-"));
try {
- expect(resolveProject(dir)).toBe(dir.split("/").pop());
+ expect(resolveProject(dir)).toBe(basename(dir));
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
@@
it("defaults to process.cwd() when no cwd argument given", () => {
- expect(resolveProject()).toBe("agentmemory");
+ expect(resolveProject()).toBe(repoBasename);
});
@@
it("defaults to process.cwd() when cwd argument is empty", () => {
- expect(resolveProject("")).toBe("agentmemory");
- expect(resolveProject(" ")).toBe("agentmemory");
+ expect(resolveProject("")).toBe(repoBasename);
+ expect(resolveProject(" ")).toBe(repoBasename);
});🤖 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 `@test/hook-project.test.ts` around lines 33 - 65, Tests assume a repo named
"agentmemory" and use split("/") which is brittle; replace hardcoded
expectations with a repository-agnostic basename variable (e.g., const
repoBasename = path.basename(process.cwd())) and use that in assertions for
resolveProject(), resolveProject(nested), and the default/empty-cwd tests, and
in the "falls back" test replace dir.split("/") with path.basename(dir) to be
path-separator safe; keep references to the resolveProject function and the
existing tmp dir creation but use path.basename for all basename checks.
Bumps version across 9 files + adds CHANGELOG entry summarizing the 18 commits since v0.9.22. Highlights: - GitHub Copilot CLI first-class support (#534) — plugin + hooks + MCP with LSP-style Content-Length framing on the standalone stdio transport. - Five new MCP adapters: Warp, Cline, Continue, Zed, Droid (#677); ADAPTERS count 11 → 17. - Three silent DX bugs fixed: graph extraction never fired on session end (#666 / #698), status reported zero memories (#666), consolidation defaulted off even with an LLM provider configured (#612 / #696). - Nine telemetry hooks switched to fire-and-forget so they don't block Claude Code's next-prompt boundary (#573 / #688). - Hook project field now sends repo basename instead of full filesystem path so auto-injected context isn't silently filtered out (#474 / #687). - Local-LLM docs: Ollama / LM Studio / vLLM section added (#671 / #697). Version-bump files: package.json, plugin/.claude-plugin/plugin.json, plugin/plugin.json, plugin/.codex-plugin/plugin.json, packages/mcp/package.json, src/version.ts, src/types.ts, src/functions/export-import.ts, test/export-import.test.ts.
* chore(release): v0.9.23 Bumps version across 9 files + adds CHANGELOG entry summarizing the 18 commits since v0.9.22. Highlights: - GitHub Copilot CLI first-class support (#534) — plugin + hooks + MCP with LSP-style Content-Length framing on the standalone stdio transport. - Five new MCP adapters: Warp, Cline, Continue, Zed, Droid (#677); ADAPTERS count 11 → 17. - Three silent DX bugs fixed: graph extraction never fired on session end (#666 / #698), status reported zero memories (#666), consolidation defaulted off even with an LLM provider configured (#612 / #696). - Nine telemetry hooks switched to fire-and-forget so they don't block Claude Code's next-prompt boundary (#573 / #688). - Hook project field now sends repo basename instead of full filesystem path so auto-injected context isn't silently filtered out (#474 / #687). - Local-LLM docs: Ollama / LM Studio / vLLM section added (#671 / #697). Version-bump files: package.json, plugin/.claude-plugin/plugin.json, plugin/plugin.json, plugin/.codex-plugin/plugin.json, packages/mcp/package.json, src/version.ts, src/types.ts, src/functions/export-import.ts, test/export-import.test.ts. * chore(release): add #701 + #709 to v0.9.23 CHANGELOG
Summary
Hooks were sending
data.cwd(an absolute filesystem path) as theprojectfield on everyobserve/session/start/enrichcall. Native sessions,mem::replay::import-jsonl, andmemory_lesson_saveall use the repo basename. The mismatch caused auto-injected context to filter out the bulk of relevant lessons because the path never matches the stored project name.Changes
src/hooks/_project.tsexportingresolveProject(cwd?)with the resolution order:AGENTMEMORY_PROJECT_NAMEenv (per-repo escape hatch)git rev-parse --show-toplevel(handles subdirs)cwd(final fallback when not in a git repo)resolveProjectfor theprojectfield while still sending the rawcwdpath in thecwdfield:notification,post-tool-use,post-tool-failure,prompt-submit,session-start,subagent-start,subagent-stop,task-completed,pre-compact.pre-tool-use) or don't send it (post-commit,session-end,stop) left untouched.tsdown.config.tssplit into per-entry configs for the hook bundles so each hook compiles into a fully self-contained.mjswith no shared hashed chunks. Previous shared-entry config caused tsdown to hoist helpers into_project-<hash>.mjsartifacts that changed on every rebuild and were prone to being committed accidentally.Test plan
npx vitest run test/hook-project.test.ts— 8/8 pass (env override, whitespace trim, empty env ignored, git toplevel from cwd, git toplevel from nested subdir, basename fallback in tmpdir, default toprocess.cwd(), empty/whitespace cwd argument)npx vitest run— 1238 pass, 1 integration file skipped (requires running server, pre-existing)find dist/hooks plugin/scripts -name '_project*'returns emptyhead -25 plugin/scripts/notification.mjsshowsresolveProjectinlined per-hookCloses #474.
Summary by CodeRabbit
New Features
Tests
Chores