From 8289897fd930b40991df29618f66128f9d4b6083 Mon Sep 17 00:00:00 2001 From: Jake Abendroth Date: Wed, 29 Jul 2026 05:18:35 -0700 Subject: [PATCH 1/3] feat(hook-augment): augment Bash tool searches via graph Bash-based searches (rg, grep, ag, ack, ugrep, git grep) bypass hook-augment because the PreToolUse matcher only covers Grep/Glob. Detection lives in hook_augment itself, reusing the existing search_graph path without duplicating the resolver. - ha_tokenize: minimal shell tokenizer (quote/backslash handling, no evaluation) - ha_parse_bash_search_pattern: strips VAR=val prefixes and wrappers (env, nice, time, command, rtk, tokf run), identifies the binary, walks flags, bails on -f/--file and multiple -e, handles rtk grep -l as value-taking - ha_tool_event_supported: Bash added alongside Grep/Glob for PreToolUse in HA_DIALECT_EVENT - ha_process: Bash branch routes tool_input.command through the extractor before the shared ha_extract_token + search_graph path Binaries covered: grep/egrep/fgrep, rg, ag, ack, ugrep/ug, git grep Wrappers covered: env, nice, time, command, rtk, tokf run Closes #1082 Signed-off-by: Jake Abendroth --- src/cli/cli.h | 1 + src/cli/hook_augment.c | 218 ++++++++++++++++++++++++++++++++++++++++- tests/test_cli.c | 50 ++++++++++ 3 files changed, 267 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.h b/src/cli/cli.h index 27ef4e3ec..4857be2a5 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -220,6 +220,7 @@ bool cbm_hook_augment_invocation_supported_for_testing(const char *dialect, bool cbm_hook_path_contains_for_testing(const char *root, const char *candidate, bool case_insensitive); const char *cbm_hook_no_project_index_guidance_for_testing(const char *event); +bool cbm_hook_augment_parse_bash_pattern_for_testing(const char *cmd, char *out, size_t out_sz); bool cbm_mcp_command_path_probe_safe_for_testing(const char *command, bool windows); void cbm_set_mcp_command_path_probe_counter_for_testing(int *counter); int cbm_install_editor_mcp_with_previous_for_testing(const char *binary_path, diff --git a/src/cli/hook_augment.c b/src/cli/hook_augment.c index 404d71eb4..36a7f8d30 100644 --- a/src/cli/hook_augment.c +++ b/src/cli/hook_augment.c @@ -1015,6 +1015,204 @@ static bool ha_dialect_event_supported(ha_lifecycle_dialect_t dialect, const cha return ha_lifecycle_event_supported(event); } +/* ── Bash search-command pattern extractor ──────────────────────────────── + * Tokenises and walks a Bash tool command to extract a search pattern for + * graph augmentation. Returns true and fills out when one clear pattern is + * found; false on unrecognised binary, -f pattern-file, multiple -e, or any + * other ambiguity. Never executes or rewrites the command. */ + +#define HA_BASH_TOK_MAX 32 +#define HA_BASH_TOK_SZ 256 + +static int ha_tokenize(const char *cmd, char toks[][HA_BASH_TOK_SZ], int max) { + int n = 0; + const char *p = cmd; + while (*p && n < max) { + while (*p && isspace((unsigned char)*p)) + p++; + if (!*p) + break; + char *d = toks[n]; + int dlen = 0; + while (*p && !isspace((unsigned char)*p)) { + if (*p == '\'') { + for (p++; *p && *p != '\''; p++) + if (dlen < HA_BASH_TOK_SZ - 1) + d[dlen++] = *p; + if (*p == '\'') + p++; + } else if (*p == '"') { + for (p++; *p && *p != '"'; p++) { + if (*p == '\\' && p[1] && strchr("\\\"$`", p[1])) + p++; + if (dlen < HA_BASH_TOK_SZ - 1) + d[dlen++] = *p; + } + if (*p == '"') + p++; + } else if (*p == '\\' && p[1]) { + p++; + if (dlen < HA_BASH_TOK_SZ - 1) + d[dlen++] = *p++; + } else { + if (dlen < HA_BASH_TOK_SZ - 1) + d[dlen++] = *p++; + } + } + d[dlen] = '\0'; + if (dlen > 0) + n++; + } + return n; +} + +static bool ha_is_env_assign(const char *t) { + if (!t || !t[0]) + return false; + if (!isalpha((unsigned char)t[0]) && t[0] != '_') + return false; + const char *p = t + 1; + while (isalnum((unsigned char)*p) || *p == '_') + p++; + return *p == '='; +} + +typedef enum { HA_BIN_GREP, HA_BIN_RG, HA_BIN_AG, HA_BIN_ACK, HA_BIN_UGREP } ha_bin_t; + +static const char *ha_search_bin_val_flags(ha_bin_t bin, bool rtk_grep) { + switch (bin) { + case HA_BIN_RG: + return "ABCmtTgMP"; + case HA_BIN_AG: + return "ABCmpG"; + default: + return rtk_grep ? "ABCmdDl" : "ABCmdD"; + } +} + +static bool ha_parse_bash_search_pattern(const char *cmd, char *out, size_t out_sz) { + if (!cmd || !out || out_sz == 0) + return false; + char toks[HA_BASH_TOK_MAX][HA_BASH_TOK_SZ]; + int n = ha_tokenize(cmd, toks, HA_BASH_TOK_MAX); + if (n == 0) + return false; + + int i = 0; + while (i < n && ha_is_env_assign(toks[i])) + i++; + if (i >= n) + return false; + + bool rtk = false; + for (;;) { + const char *t = toks[i]; + if (strcmp(t, "env") == 0 || strcmp(t, "nice") == 0 || strcmp(t, "time") == 0 || + strcmp(t, "command") == 0) { + i++; + } else if (strcmp(t, "rtk") == 0) { + rtk = true; + i++; + } else if (strcmp(t, "tokf") == 0 && i + 1 < n && strcmp(toks[i + 1], "run") == 0) { + i += 2; + } else { + break; + } + while (i < n && ha_is_env_assign(toks[i])) + i++; + if (i >= n) + return false; + } + + const char *bin_tok = toks[i++]; + ha_bin_t bin; + + if (strcmp(bin_tok, "grep") == 0 || strcmp(bin_tok, "egrep") == 0 || + strcmp(bin_tok, "fgrep") == 0) { + bin = HA_BIN_GREP; + } else if (strcmp(bin_tok, "rg") == 0) { + bin = HA_BIN_RG; + } else if (strcmp(bin_tok, "ag") == 0) { + bin = HA_BIN_AG; + } else if (strcmp(bin_tok, "ack") == 0) { + bin = HA_BIN_ACK; + } else if (strcmp(bin_tok, "ugrep") == 0 || strcmp(bin_tok, "ug") == 0) { + bin = HA_BIN_UGREP; + } else if (strcmp(bin_tok, "git") == 0) { + if (i >= n || strcmp(toks[i], "grep") != 0) + return false; + i++; + bin = HA_BIN_GREP; + } else { + return false; + } + + const char *val_flags = ha_search_bin_val_flags(bin, rtk && bin == HA_BIN_GREP); + const char *pattern = NULL; + int e_count = 0; + bool end_of_flags = false; + + for (; i < n; i++) { + const char *t = toks[i]; + + if (end_of_flags || t[0] != '-' || t[1] == '\0') { + if (!pattern) + pattern = t; + else + break; + continue; + } + + if (t[1] == '-') { + if (t[2] == '\0') { + end_of_flags = true; + continue; + } + const char *name = t + 2; + const char *eq = strchr(name, '='); + size_t nlen = eq ? (size_t)(eq - name) : strlen(name); + if ((nlen == 6 && strncmp(name, "regexp", 6) == 0) || + (nlen == 7 && strncmp(name, "pattern", 7) == 0)) { + pattern = eq ? eq + 1 : (i + 1 < n ? toks[++i] : NULL); + e_count++; + } else if (nlen == 4 && strncmp(name, "file", 4) == 0) { + return false; + } + continue; + } + + const char *f = t + 1; + bool consumed_next = false; + while (*f) { + char flag = *f++; + if (flag == 'e') { + if (*f) { + pattern = f; + f += strlen(f); + } else if (!consumed_next && i + 1 < n) { + pattern = toks[++i]; + consumed_next = true; + } + e_count++; + } else if (flag == 'f') { + return false; + } else if (strchr(val_flags, flag)) { + if (*f) { + f += strlen(f); + } else if (!consumed_next && i + 1 < n) { + i++; + consumed_next = true; + } + } + } + } + + if (e_count > 1 || !pattern || !pattern[0]) + return false; + int w = snprintf(out, out_sz, "%s", pattern); + return w > 0 && (size_t)w < out_sz; +} + static bool ha_tool_event_supported(ha_lifecycle_dialect_t dialect, const char *event, const char *tool, bool *coverage) { if (coverage) { @@ -1025,7 +1223,8 @@ static bool ha_tool_event_supported(ha_lifecycle_dialect_t dialect, const char * } if (dialect == HA_DIALECT_EVENT) { if (strcmp(event, "PreToolUse") == 0 && - (strcmp(tool, "Grep") == 0 || strcmp(tool, "Glob") == 0)) { + (strcmp(tool, "Grep") == 0 || strcmp(tool, "Glob") == 0 || + strcmp(tool, "Bash") == 0)) { return true; } if (strcmp(event, "PostToolUse") == 0 && strcmp(tool, "Read") == 0) { @@ -1310,6 +1509,10 @@ bool cbm_hook_path_contains_for_testing(const char *root, const char *candidate, const char *cbm_hook_no_project_index_guidance_for_testing(const char *event) { return ha_no_project_index_guidance(event); } + +bool cbm_hook_augment_parse_bash_pattern_for_testing(const char *cmd, char *out, size_t out_sz) { + return ha_parse_bash_search_pattern(cmd, out, out_sz); +} #endif static char *ha_process(cbm_mcp_server_t *srv, const char *input_json, const char *forced_event, @@ -1358,7 +1561,18 @@ static char *ha_process(cbm_mcp_server_t *srv, const char *input_json, const cha return NULL; } - const char *pattern = ha_obj_str(tin, "pattern"); + char bash_pattern[HA_BASH_TOK_SZ]; + const char *pattern; + if (strcmp(tool, "Bash") == 0) { + const char *cmd = ha_obj_str(tin, "command"); + if (!ha_parse_bash_search_pattern(cmd, bash_pattern, sizeof(bash_pattern))) { + yyjson_doc_free(doc); + return NULL; + } + pattern = bash_pattern; + } else { + pattern = ha_obj_str(tin, "pattern"); + } char token[HA_MAX_TOKEN + 1]; if (!ha_extract_token(pattern, token, sizeof(token))) { yyjson_doc_free(doc); diff --git a/tests/test_cli.c b/tests/test_cli.c index 3edb18c84..c2f4f9332 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -8791,6 +8791,55 @@ TEST(cli_hook_augment_context_tracks_search_json_shape) { PASS(); } +TEST(cli_hook_augment_bash_pattern_extractor) { + char out[256]; + + /* common forms */ + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -n CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -rn CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -e CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("ag CreateStripeCheckout src/", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("git grep CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + + /* value-taking flags are skipped correctly */ + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -A 5 CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -t py CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + + /* env-var prefix and wrappers */ + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rtk grep -n CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("tokf run rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("env FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + + /* rtk -l consumes N and still finds the pattern */ + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rtk grep -l 80 CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + + /* bail-out cases */ + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -f /path/patterns .", out, sizeof(out))); + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -e FOO -e BAR .", out, sizeof(out))); + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("ls -la", out, sizeof(out))); + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("", out, sizeof(out))); + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing(NULL, out, sizeof(out))); + + /* -- end-of-flags separator */ + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -- CreateStripeCheckout .", out, sizeof(out))); + ASSERT_STR_EQ(out, "CreateStripeCheckout"); + + PASS(); +} + TEST(cli_hook_augment_lifecycle_output_contract) { static const struct { const char *event; @@ -11976,6 +12025,7 @@ SUITE(cli) { RUN_TEST(cli_claude_hook_commands_shell_quote_custom_config_dir); RUN_TEST(cli_codex_migrates_to_single_hook_representation); RUN_TEST(cli_hook_augment_context_tracks_search_json_shape); + RUN_TEST(cli_hook_augment_bash_pattern_extractor); RUN_TEST(cli_hook_augment_lifecycle_output_contract); RUN_TEST(cli_hook_augment_subagent_tier_router_contract); RUN_TEST(cli_hook_augment_subagent_no_project_guidance_is_read_only); From eb12d6dd49d7a73f523408672941f7c1d60bfb13 Mon Sep 17 00:00:00 2001 From: Jake Abendroth Date: Wed, 29 Jul 2026 16:15:22 -0700 Subject: [PATCH 2/3] style: apply clang-format Signed-off-by: Jake Abendroth --- src/cli/hook_augment.c | 5 ++--- tests/test_cli.c | 45 ++++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/cli/hook_augment.c b/src/cli/hook_augment.c index 36a7f8d30..f4e55c3d5 100644 --- a/src/cli/hook_augment.c +++ b/src/cli/hook_augment.c @@ -1022,7 +1022,7 @@ static bool ha_dialect_event_supported(ha_lifecycle_dialect_t dialect, const cha * other ambiguity. Never executes or rewrites the command. */ #define HA_BASH_TOK_MAX 32 -#define HA_BASH_TOK_SZ 256 +#define HA_BASH_TOK_SZ 256 static int ha_tokenize(const char *cmd, char toks[][HA_BASH_TOK_SZ], int max) { int n = 0; @@ -1223,8 +1223,7 @@ static bool ha_tool_event_supported(ha_lifecycle_dialect_t dialect, const char * } if (dialect == HA_DIALECT_EVENT) { if (strcmp(event, "PreToolUse") == 0 && - (strcmp(tool, "Grep") == 0 || strcmp(tool, "Glob") == 0 || - strcmp(tool, "Bash") == 0)) { + (strcmp(tool, "Grep") == 0 || strcmp(tool, "Glob") == 0 || strcmp(tool, "Bash") == 0)) { return true; } if (strcmp(event, "PostToolUse") == 0 && strcmp(tool, "Read") == 0) { diff --git a/tests/test_cli.c b/tests/test_cli.c index c2f4f9332..9884cccf1 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -8795,46 +8795,61 @@ TEST(cli_hook_augment_bash_pattern_extractor) { char out[256]; /* common forms */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -n CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -n CreateStripeCheckout .", out, + sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -rn CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -rn CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -e CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -e CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("ag CreateStripeCheckout src/", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("ag CreateStripeCheckout src/", out, + sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("git grep CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("git grep CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); /* value-taking flags are skipped correctly */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -A 5 CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -A 5 CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -t py CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rg -t py CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); /* env-var prefix and wrappers */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("FOO=bar rg CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rtk grep -n CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing( + "rtk grep -n CreateStripeCheckout .", out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("tokf run rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing( + "tokf run rg CreateStripeCheckout .", out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("env FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing( + "env FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); /* rtk -l consumes N and still finds the pattern */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("rtk grep -l 80 CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing( + "rtk grep -l 80 CreateStripeCheckout .", out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); /* bail-out cases */ - ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -f /path/patterns .", out, sizeof(out))); - ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -e FOO -e BAR .", out, sizeof(out))); + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -f /path/patterns .", out, + sizeof(out))); + ASSERT_FALSE( + cbm_hook_augment_parse_bash_pattern_for_testing("grep -e FOO -e BAR .", out, sizeof(out))); ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("ls -la", out, sizeof(out))); ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("", out, sizeof(out))); ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing(NULL, out, sizeof(out))); /* -- end-of-flags separator */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -- CreateStripeCheckout .", out, sizeof(out))); + ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -- CreateStripeCheckout .", + out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); PASS(); From 5882be29f785430fd2dbf17c8f16c337c10340a3 Mon Sep 17 00:00:00 2001 From: Jake Abendroth Date: Fri, 31 Jul 2026 15:41:35 -0700 Subject: [PATCH 3/3] fix(hook-augment): bail on rtk grep -l per maintainer requirement rtk grep adds its own -l that shadows grep's valueless -l, making the pattern position ambiguous. Fail closed instead of guessing. Removes l from the rtk_grep val_flags string and adds an explicit return false when rtk && bin == HA_BIN_GREP && flag == 'l'. Test flipped from ASSERT_TRUE to ASSERT_FALSE. Signed-off-by: Jake Abendroth --- src/cli/hook_augment.c | 4 +++- tests/test_cli.c | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli/hook_augment.c b/src/cli/hook_augment.c index f4e55c3d5..9ac8015d4 100644 --- a/src/cli/hook_augment.c +++ b/src/cli/hook_augment.c @@ -1086,7 +1086,7 @@ static const char *ha_search_bin_val_flags(ha_bin_t bin, bool rtk_grep) { case HA_BIN_AG: return "ABCmpG"; default: - return rtk_grep ? "ABCmdDl" : "ABCmdD"; + return "ABCmdD"; } } @@ -1196,6 +1196,8 @@ static bool ha_parse_bash_search_pattern(const char *cmd, char *out, size_t out_ e_count++; } else if (flag == 'f') { return false; + } else if (rtk && bin == HA_BIN_GREP && flag == 'l') { + return false; } else if (strchr(val_flags, flag)) { if (*f) { f += strlen(f); diff --git a/tests/test_cli.c b/tests/test_cli.c index 9884cccf1..fe2eb39aa 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -8833,10 +8833,9 @@ TEST(cli_hook_augment_bash_pattern_extractor) { "env FOO=bar rg CreateStripeCheckout .", out, sizeof(out))); ASSERT_STR_EQ(out, "CreateStripeCheckout"); - /* rtk -l consumes N and still finds the pattern */ - ASSERT_TRUE(cbm_hook_augment_parse_bash_pattern_for_testing( + /* rtk -l shadows grep's -l with a value-taking form — bail out */ + ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing( "rtk grep -l 80 CreateStripeCheckout .", out, sizeof(out))); - ASSERT_STR_EQ(out, "CreateStripeCheckout"); /* bail-out cases */ ASSERT_FALSE(cbm_hook_augment_parse_bash_pattern_for_testing("grep -f /path/patterns .", out,