mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-08 08:53:43 +08:00
Merge pull request #619 from catsmonster/fix/hook-augment-windows-paths
fix(hook): make hook-augment work on Windows drive-letter paths
This commit is contained in:
@@ -321,6 +321,12 @@ int cbm_cmd_config(int argc, char **argv);
|
||||
* path returns 0 with no stdout output. */
|
||||
int cbm_cmd_hook_augment(void);
|
||||
|
||||
/* True for an absolute path the augmenter can walk up: POSIX "/..." or a
|
||||
* Windows drive root — "X:/..." or a bare "X:" (callers normalize '\\' to '/'
|
||||
* first). Exposed for tests — regression coverage for the Windows drive-letter
|
||||
* no-op (#618). */
|
||||
bool cbm_hook_path_is_abs(const char *path);
|
||||
|
||||
/* Build the agent.install.plan.v1 install receipt for <home> (issue #388):
|
||||
* a machine-readable JSON list of the config/instruction/hook files `install`
|
||||
* would write, produced WITHOUT mutating anything. Returns a heap JSON string
|
||||
|
||||
+35
-8
@@ -243,6 +243,20 @@ static void ha_emit(const char *text) {
|
||||
yyjson_mut_doc_free(doc);
|
||||
}
|
||||
|
||||
/* True for an absolute path we can walk up: POSIX "/..." or a Windows drive
|
||||
* root — "X:/..." or a bare "X:" (callers normalize '\\' to '/' first).
|
||||
* Declared in cli.h so the Windows drive-letter handling (#618) has direct
|
||||
* regression coverage. */
|
||||
bool cbm_hook_path_is_abs(const char *d) {
|
||||
if (!d || !d[0]) {
|
||||
return false;
|
||||
}
|
||||
if (d[0] == '/') {
|
||||
return true;
|
||||
}
|
||||
return isalpha((unsigned char)d[0]) && d[1] == ':' && (d[2] == '/' || d[2] == '\0');
|
||||
}
|
||||
|
||||
/* Walk up from `start`, deriving a project name at each level and querying
|
||||
* search_graph until an indexed project is found (or the walk is exhausted).
|
||||
* Stops at the first non-error result: a valid project with zero hits is a
|
||||
@@ -251,7 +265,7 @@ static char *ha_resolve_and_query(cbm_mcp_server_t *srv, const char *start, cons
|
||||
char dir[4096];
|
||||
snprintf(dir, sizeof(dir), "%s", start);
|
||||
|
||||
for (int level = 0; level < HA_MAX_WALKUP && dir[0] == '/'; level++) {
|
||||
for (int level = 0; level < HA_MAX_WALKUP && cbm_hook_path_is_abs(dir); level++) {
|
||||
char *project = cbm_project_name_from_path(dir);
|
||||
if (project) {
|
||||
char *args = ha_build_args(project, token);
|
||||
@@ -275,7 +289,10 @@ static char *ha_resolve_and_query(cbm_mcp_server_t *srv, const char *start, cons
|
||||
/* Not indexed at this level — climb to the parent. */
|
||||
char *slash = strrchr(dir, '/');
|
||||
if (!slash || slash == dir) {
|
||||
break;
|
||||
break; /* POSIX root "/" */
|
||||
}
|
||||
if (slash == dir + 2 && dir[1] == ':') {
|
||||
break; /* Windows drive root "X:/" — don't strip to "X:" */
|
||||
}
|
||||
*slash = '\0';
|
||||
}
|
||||
@@ -313,9 +330,9 @@ int cbm_cmd_hook_augment(void) {
|
||||
}
|
||||
|
||||
const char *cwd = ha_obj_str(root, "cwd");
|
||||
#ifndef _WIN32
|
||||
char cwdbuf[4096];
|
||||
if (!cwd || cwd[0] != '/') {
|
||||
#ifndef _WIN32
|
||||
if (!cwd || !cbm_hook_path_is_abs(cwd)) {
|
||||
if (!getcwd(cwdbuf, sizeof(cwdbuf))) {
|
||||
yyjson_doc_free(doc);
|
||||
free(input);
|
||||
@@ -324,10 +341,20 @@ int cbm_cmd_hook_augment(void) {
|
||||
cwd = cwdbuf;
|
||||
}
|
||||
#else
|
||||
/* Windows: Claude Code passes cwd in the hook payload. The walk-up loop
|
||||
* below requires POSIX-style absolute paths ('/'-prefixed), so without a
|
||||
* usable cwd there is nothing to augment — fail open cleanly. */
|
||||
if (!cwd || cwd[0] != '/') {
|
||||
/* Windows: Claude Code passes an absolute drive-letter cwd in the hook
|
||||
* payload (e.g. C:\repo). Normalize '\\' -> '/' and require an absolute
|
||||
* path; the walk-up loop handles POSIX and "X:/..." roots alike. Without
|
||||
* a usable cwd there is nothing to augment — fail open cleanly. */
|
||||
if (cwd) {
|
||||
snprintf(cwdbuf, sizeof(cwdbuf), "%s", cwd);
|
||||
for (char *p = cwdbuf; *p; p++) {
|
||||
if (*p == '\\') {
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
cwd = cwdbuf;
|
||||
}
|
||||
if (!cwd || !cbm_hook_path_is_abs(cwd)) {
|
||||
yyjson_doc_free(doc);
|
||||
free(input);
|
||||
return 0;
|
||||
|
||||
@@ -2361,6 +2361,26 @@ TEST(cli_hook_gate_script_no_predictable_tmp_issue384) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* issue #618: hook-augment was a structural no-op on Windows because its path
|
||||
* guards required POSIX-style '/'-prefixed absolute paths, so a drive-letter
|
||||
* cwd (C:/repo) was rejected before any search_graph query. The predicate must
|
||||
* accept POSIX and Windows drive roots alike (callers normalize '\\' to '/'). */
|
||||
TEST(cli_hook_augment_path_is_abs) {
|
||||
/* POSIX absolute (unchanged behavior) */
|
||||
ASSERT(cbm_hook_path_is_abs("/home/u/proj"));
|
||||
/* Windows drive roots — the #618 regression */
|
||||
ASSERT(cbm_hook_path_is_abs("C:/Users/me/proj"));
|
||||
ASSERT(cbm_hook_path_is_abs("C:/"));
|
||||
ASSERT(cbm_hook_path_is_abs("C:"));
|
||||
ASSERT(cbm_hook_path_is_abs("d:/lowercase/drive"));
|
||||
/* Not absolute → augmenter no-ops cleanly */
|
||||
ASSERT(!cbm_hook_path_is_abs("relative/path"));
|
||||
ASSERT(!cbm_hook_path_is_abs("proj"));
|
||||
ASSERT(!cbm_hook_path_is_abs(""));
|
||||
ASSERT(!cbm_hook_path_is_abs(NULL));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_upsert_claude_hook_existing) {
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX");
|
||||
@@ -3025,6 +3045,7 @@ SUITE(cli) {
|
||||
|
||||
/* Claude Code hooks (5 tests — group D) */
|
||||
RUN_TEST(cli_hook_gate_script_no_predictable_tmp_issue384);
|
||||
RUN_TEST(cli_hook_augment_path_is_abs);
|
||||
RUN_TEST(cli_upsert_claude_hook_fresh);
|
||||
RUN_TEST(cli_upsert_claude_hook_existing);
|
||||
RUN_TEST(cli_upsert_claude_hook_replace);
|
||||
|
||||
Reference in New Issue
Block a user