refactor(search): rename --after/--updated-after to --since/--updated-since
The --since naming is more intuitive (matches git log --since) and consistent with the list commands which already use --since. Renames the CLI flags, SearchCliFilters fields, SearchFilters fields, autocorrect registry, and robot-docs manifest. No behavioral change. Affected paths: - cli/mod.rs: SearchArgs field + clap attribute rename - cli/commands/search.rs: SearchCliFilters + run_search plumbing - search/filters.rs: SearchFilters struct + apply_filters logic - main.rs: handle_search + robot-docs JSON - cli/autocorrect.rs: COMMAND_FLAGS entry for search Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -131,8 +131,8 @@ const COMMAND_FLAGS: &[(&str, &[&str])] = &[
|
||||
"--project",
|
||||
"--label",
|
||||
"--path",
|
||||
"--after",
|
||||
"--updated-after",
|
||||
"--since",
|
||||
"--updated-since",
|
||||
"--limit",
|
||||
"--explain",
|
||||
"--no-explain",
|
||||
@@ -294,16 +294,33 @@ fn valid_flags_for(subcommand: Option<&str>) -> Vec<&'static str> {
|
||||
|
||||
/// Run the pre-clap correction pass on raw args.
|
||||
///
|
||||
/// When `strict` is true (robot mode), only deterministic corrections are applied
|
||||
/// (single-dash long flags, case normalization). Fuzzy matching is disabled to
|
||||
/// prevent misleading agents with speculative corrections.
|
||||
///
|
||||
/// Returns the (possibly modified) args and any corrections applied.
|
||||
pub fn correct_args(raw: Vec<String>) -> CorrectionResult {
|
||||
pub fn correct_args(raw: Vec<String>, strict: bool) -> CorrectionResult {
|
||||
let subcommand = detect_subcommand(&raw);
|
||||
let valid = valid_flags_for(subcommand);
|
||||
|
||||
let mut corrected = Vec::with_capacity(raw.len());
|
||||
let mut corrections = Vec::new();
|
||||
let mut past_terminator = false;
|
||||
|
||||
for arg in raw {
|
||||
if let Some(fixed) = try_correct(&arg, &valid) {
|
||||
// B1: Stop correcting after POSIX `--` option terminator
|
||||
if arg == "--" {
|
||||
past_terminator = true;
|
||||
corrected.push(arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if past_terminator {
|
||||
corrected.push(arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(fixed) = try_correct(&arg, &valid, strict) {
|
||||
let s = fixed.corrected.clone();
|
||||
corrections.push(fixed);
|
||||
corrected.push(s);
|
||||
@@ -318,13 +335,33 @@ pub fn correct_args(raw: Vec<String>) -> CorrectionResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clap built-in flags that should never be corrected. These are handled by clap
|
||||
/// directly and are not in our GLOBAL_FLAGS registry.
|
||||
const CLAP_BUILTINS: &[&str] = &["--help", "--version"];
|
||||
|
||||
/// Try to correct a single arg. Returns `None` if no correction needed.
|
||||
fn try_correct(arg: &str, valid_flags: &[&str]) -> Option<Correction> {
|
||||
///
|
||||
/// When `strict` is true, fuzzy matching is disabled — only deterministic
|
||||
/// corrections (single-dash fix, case normalization) are applied.
|
||||
fn try_correct(arg: &str, valid_flags: &[&str], strict: bool) -> Option<Correction> {
|
||||
// Only attempt correction on flag-like args (starts with `-`)
|
||||
if !arg.starts_with('-') {
|
||||
return None;
|
||||
}
|
||||
|
||||
// B2: Never correct clap built-in flags (--help, --version)
|
||||
let flag_part_for_builtin = if let Some(eq_pos) = arg.find('=') {
|
||||
&arg[..eq_pos]
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
if CLAP_BUILTINS
|
||||
.iter()
|
||||
.any(|b| b.eq_ignore_ascii_case(flag_part_for_builtin))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Skip short flags — they're unambiguous single chars (-p, -n, -v, -J)
|
||||
// Also skip stacked short flags (-vvv)
|
||||
if !arg.starts_with("--") {
|
||||
@@ -371,8 +408,9 @@ fn try_correct(arg: &str, valid_flags: &[&str]) -> Option<Correction> {
|
||||
});
|
||||
}
|
||||
|
||||
// Try fuzzy on the single-dash candidate
|
||||
if let Some((best_flag, score)) = best_fuzzy_match(&lower, valid_flags)
|
||||
// Try fuzzy on the single-dash candidate (skip in strict mode)
|
||||
if !strict
|
||||
&& let Some((best_flag, score)) = best_fuzzy_match(&lower, valid_flags)
|
||||
&& score >= FUZZY_FLAG_THRESHOLD
|
||||
{
|
||||
return Some(Correction {
|
||||
@@ -415,8 +453,9 @@ fn try_correct(arg: &str, valid_flags: &[&str]) -> Option<Correction> {
|
||||
});
|
||||
}
|
||||
|
||||
// Rule 3: Fuzzy flag match — `--staate` -> `--state`
|
||||
if let Some((best_flag, score)) = best_fuzzy_match(&lower, valid_flags)
|
||||
// Rule 3: Fuzzy flag match — `--staate` -> `--state` (skip in strict mode)
|
||||
if !strict
|
||||
&& let Some((best_flag, score)) = best_fuzzy_match(&lower, valid_flags)
|
||||
&& score >= FUZZY_FLAG_THRESHOLD
|
||||
{
|
||||
let corrected = match value_suffix {
|
||||
@@ -510,7 +549,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn single_dash_robot() {
|
||||
let result = correct_args(args("lore -robot issues -n 5"));
|
||||
let result = correct_args(args("lore -robot issues -n 5"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].original, "-robot");
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
@@ -523,7 +562,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn single_dash_state() {
|
||||
let result = correct_args(args("lore --robot issues -state opened"));
|
||||
let result = correct_args(args("lore --robot issues -state opened"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--state");
|
||||
}
|
||||
@@ -532,7 +571,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn case_robot() {
|
||||
let result = correct_args(args("lore --Robot issues"));
|
||||
let result = correct_args(args("lore --Robot issues"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
assert_eq!(
|
||||
@@ -543,7 +582,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn case_state_upper() {
|
||||
let result = correct_args(args("lore --robot issues --State opened"));
|
||||
let result = correct_args(args("lore --robot issues --State opened"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--state");
|
||||
assert_eq!(
|
||||
@@ -554,7 +593,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn case_all_upper() {
|
||||
let result = correct_args(args("lore --ROBOT issues --STATE opened"));
|
||||
let result = correct_args(args("lore --ROBOT issues --STATE opened"), false);
|
||||
assert_eq!(result.corrections.len(), 2);
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
assert_eq!(result.corrections[1].corrected, "--state");
|
||||
@@ -564,7 +603,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fuzzy_staate() {
|
||||
let result = correct_args(args("lore --robot issues --staate opened"));
|
||||
let result = correct_args(args("lore --robot issues --staate opened"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--state");
|
||||
assert_eq!(result.corrections[0].rule, CorrectionRule::FuzzyFlag);
|
||||
@@ -572,7 +611,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fuzzy_projct() {
|
||||
let result = correct_args(args("lore --robot issues --projct group/repo"));
|
||||
let result = correct_args(args("lore --robot issues --projct group/repo"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--project");
|
||||
assert_eq!(result.corrections[0].rule, CorrectionRule::FuzzyFlag);
|
||||
@@ -583,7 +622,7 @@ mod tests {
|
||||
#[test]
|
||||
fn already_correct() {
|
||||
let original = args("lore --robot issues --state opened -n 10");
|
||||
let result = correct_args(original.clone());
|
||||
let result = correct_args(original.clone(), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
assert_eq!(result.args, original);
|
||||
}
|
||||
@@ -591,27 +630,27 @@ mod tests {
|
||||
#[test]
|
||||
fn short_flags_untouched() {
|
||||
let original = args("lore -J issues -n 10 -s opened -p group/repo");
|
||||
let result = correct_args(original.clone());
|
||||
let result = correct_args(original.clone(), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacked_short_flags_untouched() {
|
||||
let original = args("lore -vvv issues");
|
||||
let result = correct_args(original.clone());
|
||||
let result = correct_args(original.clone(), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positional_args_untouched() {
|
||||
let result = correct_args(args("lore --robot search authentication"));
|
||||
let result = correct_args(args("lore --robot search authentication"), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildly_wrong_flag_not_corrected() {
|
||||
// `--xyzzy` shouldn't match anything above 0.8
|
||||
let result = correct_args(args("lore --robot issues --xyzzy foo"));
|
||||
let result = correct_args(args("lore --robot issues --xyzzy foo"), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
}
|
||||
|
||||
@@ -619,7 +658,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flag_eq_value_case_correction() {
|
||||
let result = correct_args(args("lore --robot issues --State=opened"));
|
||||
let result = correct_args(args("lore --robot issues --State=opened"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--state=opened");
|
||||
}
|
||||
@@ -628,15 +667,81 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn multiple_corrections() {
|
||||
let result = correct_args(args(
|
||||
"lore -robot issues --State opened --projct group/repo",
|
||||
));
|
||||
let result = correct_args(
|
||||
args("lore -robot issues --State opened --projct group/repo"),
|
||||
false,
|
||||
);
|
||||
assert_eq!(result.corrections.len(), 3);
|
||||
assert_eq!(result.args[1], "--robot");
|
||||
assert_eq!(result.args[3], "--state");
|
||||
assert_eq!(result.args[5], "--project");
|
||||
}
|
||||
|
||||
// ---- B1: POSIX -- option terminator ----
|
||||
|
||||
#[test]
|
||||
fn option_terminator_stops_corrections() {
|
||||
let result = correct_args(args("lore issues -- --staate --projct"), false);
|
||||
// Nothing after `--` should be corrected
|
||||
assert!(result.corrections.is_empty());
|
||||
assert_eq!(result.args[2], "--");
|
||||
assert_eq!(result.args[3], "--staate");
|
||||
assert_eq!(result.args[4], "--projct");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correction_before_terminator_still_works() {
|
||||
let result = correct_args(args("lore --Robot issues -- --staate"), false);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
assert_eq!(result.args[4], "--staate"); // untouched after --
|
||||
}
|
||||
|
||||
// ---- B2: Clap built-in flags not corrected ----
|
||||
|
||||
#[test]
|
||||
fn version_flag_not_corrected() {
|
||||
let result = correct_args(args("lore --version"), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
assert_eq!(result.args[1], "--version");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_flag_not_corrected() {
|
||||
let result = correct_args(args("lore --help"), false);
|
||||
assert!(result.corrections.is_empty());
|
||||
assert_eq!(result.args[1], "--help");
|
||||
}
|
||||
|
||||
// ---- I6: Strict mode (robot) disables fuzzy matching ----
|
||||
|
||||
#[test]
|
||||
fn strict_mode_disables_fuzzy() {
|
||||
// Fuzzy match works in non-strict
|
||||
let non_strict = correct_args(args("lore --robot issues --staate opened"), false);
|
||||
assert_eq!(non_strict.corrections.len(), 1);
|
||||
assert_eq!(non_strict.corrections[0].rule, CorrectionRule::FuzzyFlag);
|
||||
|
||||
// Fuzzy match disabled in strict
|
||||
let strict = correct_args(args("lore --robot issues --staate opened"), true);
|
||||
assert!(strict.corrections.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_mode_still_fixes_case() {
|
||||
let result = correct_args(args("lore --Robot issues --State opened"), true);
|
||||
assert_eq!(result.corrections.len(), 2);
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
assert_eq!(result.corrections[1].corrected, "--state");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_mode_still_fixes_single_dash() {
|
||||
let result = correct_args(args("lore -robot issues"), true);
|
||||
assert_eq!(result.corrections.len(), 1);
|
||||
assert_eq!(result.corrections[0].corrected, "--robot");
|
||||
}
|
||||
|
||||
// ---- Teaching notes ----
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user