Skip to content
Merged
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
87 changes: 87 additions & 0 deletions src/discover/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3162,6 +3162,93 @@ mod tests {
);
}

#[test]
fn test_subcommand_rules_require_token_boundaries() {
// The pnpm case is covered separately. The sbt rule is included here
// because it is already boundary-safe and guards the full issue family
// against future regressions.
let false_positives = [
"git branchless status",
"gh prs",
"glab mrs",
"cargo builder",
"prettierish",
"next builder",
"playwrighting",
"prismax",
"docker psql",
"kubectl getall",
"oc status-check",
"ruff checker",
"sqlfluff linting",
"pip installer",
"uv pip installer",
"go vetting",
"sbt tester",
"rake tester",
"rails tester",
"pio runner",
"quarto renderer",
"shopify themepark",
"terraform planner",
"trunk builder",
];

for command in false_positives {
assert!(
matches!(
classify_command(command),
Classification::Unsupported { .. }
),
"{command} must not classify as a supported command"
);
assert_eq!(
rewrite_command_no_prefixes(command, &[]),
None,
"{command} must not be rewritten"
);
}

let valid_commands = [
("git branch status", "rtk git"),
("gh pr list", "rtk gh"),
("glab mr list", "rtk glab"),
("cargo build --release", "rtk cargo"),
("prettier --check .", "rtk prettier"),
("next build --turbo", "rtk next"),
("playwright test", "rtk playwright"),
("prisma migrate status", "rtk prisma"),
("docker ps", "rtk docker"),
("kubectl get pods", "rtk kubectl"),
("oc status", "rtk oc"),
("ruff check .", "rtk ruff"),
("sqlfluff lint .", "rtk sqlfluff"),
("pip install flask", "rtk pip"),
("uv pip install flask", "rtk uv"),
("go test ./...", "rtk go"),
("sbt test", "rtk sbt"),
("rake test", "rtk rake"),
("rake test:unit", "rtk rake"),
("rails test:system", "rtk rake"),
("bundle exec rake test:models", "rtk rake"),
("bin/rails test:integration", "rtk rake"),
("pio run", "rtk pio"),
("quarto render docs", "rtk quarto"),
("shopify theme push", "rtk shopify"),
("terraform plan", "rtk terraform"),
("trunk build", "rtk trunk"),
];

for (command, expected_rtk_command) in valid_commands {
match classify_command(command) {
Classification::Supported { rtk_equivalent, .. } => {
assert_eq!(rtk_equivalent, expected_rtk_command, "{command}");
}
classification => panic!("{command} classified as {classification:?}"),
}
}
}

#[test]
fn test_rewrite_playwright() {
let commands = vec![
Expand Down
42 changes: 21 additions & 21 deletions src/discover/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl Default for RtkRule {

pub const RULES: &[RtkRule] = &[
RtkRule {
pattern: r"^(?:git|yadm)\s+(?:-[Cc]\s+\S+\s+)*(status|log|diff|show|add|commit|checkout|push|pull|branch|fetch|stash|worktree)",
pattern: r"^(?:git|yadm)\s+(?:-[Cc]\s+\S+\s+)*(status|log|diff|show|add|commit|checkout|push|pull|branch|fetch|stash|worktree)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk git",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["git", "yadm"],
Expand All @@ -71,7 +71,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^gh\s+(pr|issue|run|repo|api|release)",
pattern: r"^gh\s+(pr|issue|run|repo|api|release)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk gh",
rewrite_prefixes: &["gh"],
category: "GitHub",
Expand All @@ -80,7 +80,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^glab\s+(mr|issue|ci|pipeline|api|release)",
pattern: r"^glab\s+(mr|issue|ci|pipeline|api|release)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk glab",
rewrite_prefixes: &["glab"],
category: "GitLab",
Expand All @@ -89,7 +89,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^cargo\s+(build|test|clippy|check|fmt|install)",
pattern: r"^cargo\s+(build|test|clippy|check|fmt|install)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk cargo",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["cargo"],
Expand Down Expand Up @@ -241,7 +241,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prettier",
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prettier(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk prettier",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &[
Expand All @@ -266,7 +266,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?next\s+build",
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?next\s+build(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk next",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &[
Expand Down Expand Up @@ -377,7 +377,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?playwright",
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?playwright(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk playwright",
rewrite_prefixes: &[
"npm exec playwright",
Expand All @@ -401,7 +401,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prisma",
pattern: r"^((p?np(m|x)|p?npm\s+(exec|run|run-script)|npm\s+(rum|urn|x)|pnpm\s+dlx)\s+)?prisma(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk prisma",
rewrite_prefixes: &[
"npm exec prisma",
Expand All @@ -425,23 +425,23 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^docker\s+(ps|images|logs|run|exec|build|compose\s+(ps|logs|build))",
pattern: r"^docker\s+(ps|images|logs|run|exec|build|compose\s+(ps|logs|build))(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk docker",
rewrite_prefixes: &["docker"],
category: "Infra",
savings_pct: 85.0,
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^kubectl\s+(get|logs|describe|apply)",
pattern: r"^kubectl\s+(get|logs|describe|apply)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk kubectl",
rewrite_prefixes: &["kubectl"],
category: "Infra",
savings_pct: 85.0,
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^oc\s+(get|logs|describe|apply|status|adm)",
pattern: r"^oc\s+(get|logs|describe|apply|status|adm)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk oc",
rewrite_prefixes: &["oc"],
category: "Infra",
Expand Down Expand Up @@ -491,7 +491,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^ruff\s+(check|format)",
pattern: r"^ruff\s+(check|format)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk ruff",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["ruff"],
Expand All @@ -501,7 +501,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^sqlfluff\s+lint",
pattern: r"^sqlfluff\s+lint(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk sqlfluff",
rewrite_prefixes: &["sqlfluff"],
category: "Python",
Expand All @@ -518,7 +518,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^(pip3?|uv\s+pip)\s+(list|outdated|install|show)",
pattern: r"^(pip3?|uv\s+pip)\s+(list|outdated|install|show)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk pip",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["pip3", "pip", "uv pip"],
Expand All @@ -536,7 +536,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^go\s+(test|build|vet)",
pattern: r"^go\s+(test|build|vet)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk go",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["go"],
Expand Down Expand Up @@ -574,7 +574,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^(?:bundle\s+exec\s+)?(?:bin/)?(?:rake|rails)\s+test",
pattern: r"^(?:bundle\s+exec\s+)?(?:bin/)?(?:rake|rails)\s+test(?:[\s:]|$|[;|&()<>])",
rtk_cmd: "rtk rake",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &[
Expand Down Expand Up @@ -930,7 +930,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^pio\s+run",
pattern: r"^pio\s+run(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk pio",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["pio"],
Expand Down Expand Up @@ -980,7 +980,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^quarto\s+render",
pattern: r"^quarto\s+render(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk quarto",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["quarto"],
Expand All @@ -1006,7 +1006,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^shopify\s+theme\s+(push|pull)",
pattern: r"^shopify\s+theme\s+(push|pull)(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk shopify",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["shopify"],
Expand Down Expand Up @@ -1041,7 +1041,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^terraform\s+plan",
pattern: r"^terraform\s+plan(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk terraform",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["terraform"],
Expand All @@ -1059,7 +1059,7 @@ pub const RULES: &[RtkRule] = &[
..RtkRule::DEFAULT
},
RtkRule {
pattern: r"^trunk\s+build",
pattern: r"^trunk\s+build(?:\s|$|[;|&()<>])",
rtk_cmd: "rtk trunk",
pipeline_safety: PipelineSafety::ProducerOnly,
rewrite_prefixes: &["trunk"],
Expand Down
2 changes: 1 addition & 1 deletion src/filters/pio-run.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[filters.pio-run]
description = "Compact PlatformIO build output"
match_command = "^pio\\s+run"
match_command = "^pio\\s+run(\\s|$|[;|&()<>])"
strip_ansi = true
strip_lines_matching = [
"^\\s*$",
Expand Down
2 changes: 1 addition & 1 deletion src/filters/quarto-render.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[filters.quarto-render]
description = "Compact quarto render output"
match_command = "^quarto\\s+render"
match_command = "^quarto\\s+render(\\s|$|[;|&()<>])"
strip_ansi = true
strip_lines_matching = [
"^\\s*$",
Expand Down
2 changes: 1 addition & 1 deletion src/filters/shopify-theme.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[filters.shopify-theme]
description = "Compact shopify theme push/pull output"
match_command = "^shopify\\s+theme\\s+(push|pull)"
match_command = "^shopify\\s+theme\\s+(push|pull)(\\s|$|[;|&()<>])"
strip_ansi = true
strip_lines_matching = [
"^\\s*$",
Expand Down
2 changes: 1 addition & 1 deletion src/filters/terraform-plan.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[filters.terraform-plan]
description = "Compact Terraform plan output"
match_command = "^terraform\\s+plan"
match_command = "^terraform\\s+plan(\\s|$|[;|&()<>])"
strip_ansi = true
strip_lines_matching = [
"^Refreshing state",
Expand Down
2 changes: 1 addition & 1 deletion src/filters/trunk-build.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[filters.trunk-build]
description = "Compact trunk build output"
match_command = "^trunk\\s+build"
match_command = "^trunk\\s+build(\\s|$|[;|&()<>])"
strip_ansi = true
strip_lines_matching = [
"^\\s*$",
Expand Down
Loading