mirror of
https://github.com/rtk-ai/rtk.git
synced 2026-09-19 07:33:17 +08:00
chore: migrate to Rust edition 2024
Bump edition 2021 -> 2024. rust-version stays at 1.91, already well above
the 1.85 floor the edition needs; docs/guide/resources/troubleshooting.md
still claimed 1.70+, which is where a failed `cargo install --git` lands.
Four things the edition forces:
- std::env::{set_var,remove_var} are unsafe in 2024 with no safe std
replacement. Rather than wrap the test call sites in unsafe -- which the
crate denies and .semgrep.yml flags -- route them through temp-env, a
dev-only dependency whose closure API is safe and which restores the
previous value even when the body panics. The hand-rolled CLAUDE_DIR_LOCK
and PI_DIR_LOCK guards existed only to serialise those mutations and are
now redundant; CWD_LOCK and TEST_ENV_LOCK stay, they order more than the
env var itself.
- unsafe_op_in_unsafe_fn is on by default, so the libc calls in the proxy
signal handler and in stream.rs's relay handler need explicit unsafe
blocks, scoped to the libc calls themselves.
- `gen` is a reserved keyword, so the closure by that name in diff_cmd.rs
becomes make_lines.
- Tightened tail-expression temporary scopes let clippy prove the binding in
setup_test_env is inlinable, so let_and_return now fires there.
if_let_rescope changes when the scrutinee temporary drops in an if let/else.
The two sites in show_claude_config take cargo fix --edition's match rewrite,
which keeps the 2021 drop timing.
rustfmt.toml is kept rather than dropped: cargo fmt passes --edition from
Cargo.toml, but a bare rustfmt invocation has no crate context and falls
back to edition 2015, which cannot parse the let-chains the next commit
introduces. Pinning it there keeps format-on-save and pre-commit hooks in
agreement with CI.
clippy::collapsible_if is allowed crate-wide for now; the follow-up commit
adopts let-chains and removes the allow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+57
@@ -723,6 +723,15 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
@@ -781,6 +790,29 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -863,6 +895,15 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
@@ -939,6 +980,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml_edit",
|
||||
@@ -1025,6 +1067,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
@@ -1159,6 +1207,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "temp-env"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.26.0"
|
||||
|
||||
+7
-1
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rtk"
|
||||
version = "0.48.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
authors = ["Patrick Szymkowiak"]
|
||||
description = "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
|
||||
@@ -54,6 +54,12 @@ windows-sys = { version = "0.59", features = [
|
||||
[build-dependencies]
|
||||
toml = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
# Safe, closure-scoped env mutation for tests. std::env::set_var is unsafe
|
||||
# in edition 2024 and has no safe std replacement; temp-env restores the
|
||||
# previous value on completion or panic and serializes callers internally.
|
||||
temp-env = "0.3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
@@ -144,7 +144,7 @@ cargo build --release
|
||||
cargo install --path . --force
|
||||
```
|
||||
|
||||
Minimum required Rust version: 1.70+.
|
||||
Minimum required Rust version: 1.91 (edition 2024 needs Cargo 1.85 or newer).
|
||||
|
||||
## OpenCode not using RTK
|
||||
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
# cargo fmt passes --edition from Cargo.toml, but a bare `rustfmt` invocation --
|
||||
# editor format-on-save, rust-analyzer.rustfmt.overrideCommand, a pre-commit
|
||||
# hook -- has no crate context and falls back to edition 2015, which cannot even
|
||||
# parse this crate's let-chains. Pin it here so those agree with CI.
|
||||
edition = "2024"
|
||||
style_edition = "2024"
|
||||
|
||||
@@ -7342,13 +7342,13 @@ diff --git a/b.rs b/b.rs
|
||||
for _ in 0..3_000 {
|
||||
let n = (lcg(&mut seed) % 12) as usize;
|
||||
let m = (lcg(&mut seed) % 12) as usize;
|
||||
let mut gen = |k: usize| -> Vec<String> {
|
||||
let mut make_lines = |k: usize| -> Vec<String> {
|
||||
(0..k)
|
||||
.map(|_| format!("key{} = {}", lcg(&mut seed) % 3, lcg(&mut seed) % 5))
|
||||
.collect()
|
||||
};
|
||||
let a_lines = gen(n);
|
||||
let b_lines = gen(m);
|
||||
let a_lines = make_lines(n);
|
||||
let b_lines = make_lines(m);
|
||||
let a: Vec<&str> = a_lines.iter().map(|s| s.as_str()).collect();
|
||||
let b: Vec<&str> = b_lines.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
|
||||
+31
-29
@@ -1346,13 +1346,14 @@ mod tests {
|
||||
#[test]
|
||||
fn native_run_returns_zero_on_success() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::env::set_var("RTK_TEE_DIR", tmp.path());
|
||||
std::fs::write(tmp.path().join("a.txt"), "x").unwrap();
|
||||
let root = tmp.path().to_string_lossy().into_owned();
|
||||
assert_eq!(
|
||||
run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(),
|
||||
0
|
||||
);
|
||||
temp_env::with_var("RTK_TEE_DIR", Some(tmp.path()), || {
|
||||
std::fs::write(tmp.path().join("a.txt"), "x").unwrap();
|
||||
let root = tmp.path().to_string_lossy().into_owned();
|
||||
assert_eq!(
|
||||
run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(),
|
||||
0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1411,28 +1412,29 @@ mod tests {
|
||||
#[test]
|
||||
fn disclosure_survives_the_output_guard() {
|
||||
let tee = tempfile::tempdir().unwrap();
|
||||
std::env::set_var("RTK_TEE_DIR", tee.path());
|
||||
let timer = tracking::TimedExecution::start();
|
||||
let shown = render(
|
||||
vec!["visible.txt".to_string()],
|
||||
50,
|
||||
false,
|
||||
&["secret.txt".to_string()],
|
||||
"find . -name '*.txt'",
|
||||
"visible.txt",
|
||||
&timer,
|
||||
);
|
||||
assert!(shown.contains("(1 filtered"), "{shown}");
|
||||
let shown = render(
|
||||
vec![],
|
||||
50,
|
||||
false,
|
||||
&["secret.txt".to_string()],
|
||||
"find . -name secret.txt",
|
||||
"",
|
||||
&timer,
|
||||
);
|
||||
assert!(shown.contains("(1 filtered"), "{shown}");
|
||||
temp_env::with_var("RTK_TEE_DIR", Some(tee.path()), || {
|
||||
let timer = tracking::TimedExecution::start();
|
||||
let shown = render(
|
||||
vec!["visible.txt".to_string()],
|
||||
50,
|
||||
false,
|
||||
&["secret.txt".to_string()],
|
||||
"find . -name '*.txt'",
|
||||
"visible.txt",
|
||||
&timer,
|
||||
);
|
||||
assert!(shown.contains("(1 filtered"), "{shown}");
|
||||
let shown = render(
|
||||
vec![],
|
||||
50,
|
||||
false,
|
||||
&["secret.txt".to_string()],
|
||||
"find . -name secret.txt",
|
||||
"",
|
||||
&timer,
|
||||
);
|
||||
assert!(shown.contains("(1 filtered"), "{shown}");
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -1266,9 +1266,9 @@ mod tests {
|
||||
let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap();
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let cfg = temp_cfg(dir.path());
|
||||
std::env::set_var("RTK_RECALL", "0");
|
||||
record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log");
|
||||
std::env::remove_var("RTK_RECALL");
|
||||
temp_env::with_var("RTK_RECALL", Some("0"), || {
|
||||
record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log");
|
||||
});
|
||||
assert!(
|
||||
!dir.path().join("recall_test.db").exists(),
|
||||
"RTK_RECALL=0 must prevent any recall.db write from the hook path"
|
||||
|
||||
+9
-3
@@ -287,11 +287,17 @@ mod signal_relay {
|
||||
unsafe extern "C" fn relay(sig: libc::c_int) {
|
||||
let pid = CHILD_PID.load(Ordering::SeqCst);
|
||||
if pid == 0 || RELAYED.swap(sig, Ordering::SeqCst) != 0 {
|
||||
libc::signal(sig, libc::SIG_DFL);
|
||||
libc::raise(sig);
|
||||
// nosemgrep: unsafe-block
|
||||
unsafe {
|
||||
libc::signal(sig, libc::SIG_DFL);
|
||||
libc::raise(sig);
|
||||
}
|
||||
return;
|
||||
}
|
||||
libc::kill(pid as libc::pid_t, sig);
|
||||
// nosemgrep: unsafe-block
|
||||
unsafe {
|
||||
libc::kill(pid as libc::pid_t, sig);
|
||||
}
|
||||
}
|
||||
|
||||
fn escalate(pid: u32) {
|
||||
|
||||
+7
-5
@@ -113,12 +113,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_disabled_env_emits_nothing() {
|
||||
let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap();
|
||||
std::env::set_var("RTK_RECALL", "0");
|
||||
let big = "x".repeat(1000);
|
||||
let hint = tee_and_hint(&big, "cmd", 1);
|
||||
let forced = force_tee_hint(&big, "cmd");
|
||||
let tail = force_tee_tail_hint(&big, "cmd", 5);
|
||||
std::env::remove_var("RTK_RECALL");
|
||||
let (hint, forced, tail) = temp_env::with_var("RTK_RECALL", Some("0"), || {
|
||||
(
|
||||
tee_and_hint(&big, "cmd", 1),
|
||||
force_tee_hint(&big, "cmd"),
|
||||
force_tee_tail_hint(&big, "cmd", 5),
|
||||
)
|
||||
});
|
||||
assert!(hint.is_none(), "disabled must never emit tokens");
|
||||
assert!(forced.is_none());
|
||||
assert!(tail.is_none());
|
||||
|
||||
+22
-21
@@ -275,34 +275,35 @@ mod tests {
|
||||
|
||||
/// Regression for #1307: the env opt-out must short-circuit telemetry
|
||||
/// consent paths so `rtk init` cannot hang in non-interactive environments.
|
||||
/// All cases are bundled in one test to serialize env-var mutations.
|
||||
/// All cases live in one test so the opt-out states cannot interleave.
|
||||
#[test]
|
||||
fn test_telemetry_disabled_by_env_honors_opt_out() {
|
||||
#[allow(deprecated)]
|
||||
std::env::remove_var(TELEMETRY_DISABLED_ENV);
|
||||
assert!(
|
||||
!telemetry_disabled_by_env(),
|
||||
"unset env must not count as disabled"
|
||||
);
|
||||
temp_env::with_var_unset(TELEMETRY_DISABLED_ENV, || {
|
||||
assert!(
|
||||
!telemetry_disabled_by_env(),
|
||||
"unset env must not count as disabled"
|
||||
);
|
||||
});
|
||||
|
||||
#[allow(deprecated)]
|
||||
std::env::set_var(TELEMETRY_DISABLED_ENV, TELEMETRY_DISABLED_VALUE);
|
||||
assert!(
|
||||
telemetry_disabled_by_env(),
|
||||
"RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)"
|
||||
temp_env::with_var(
|
||||
TELEMETRY_DISABLED_ENV,
|
||||
Some(TELEMETRY_DISABLED_VALUE),
|
||||
|| {
|
||||
assert!(
|
||||
telemetry_disabled_by_env(),
|
||||
"RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)"
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
for other in ["0", "true", "false", "yes", "no", ""] {
|
||||
#[allow(deprecated)]
|
||||
std::env::set_var(TELEMETRY_DISABLED_ENV, other);
|
||||
assert!(
|
||||
!telemetry_disabled_by_env(),
|
||||
"value {other:?} must not be treated as disabled"
|
||||
);
|
||||
temp_env::with_var(TELEMETRY_DISABLED_ENV, Some(other), || {
|
||||
assert!(
|
||||
!telemetry_disabled_by_env(),
|
||||
"value {other:?} must not be treated as disabled"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
std::env::remove_var(TELEMETRY_DISABLED_ENV);
|
||||
}
|
||||
|
||||
// A canned 64-hex-char hash for deterministic `device_hash_line` assertions.
|
||||
|
||||
+50
-56
@@ -2054,19 +2054,16 @@ mod tests {
|
||||
));
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
env::set_var("RTK_DB_PATH", &db_path);
|
||||
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
|
||||
let timer = TimedExecution::start();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
timer.track("test cmd", "rtk test", "raw input data", "filtered");
|
||||
|
||||
let timer = TimedExecution::start();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
timer.track("test cmd", "rtk test", "raw input data", "filtered");
|
||||
|
||||
// Verify via DB that record exists
|
||||
let tracker = Tracker::new().expect("Failed to create tracker");
|
||||
let recent = tracker.get_recent(5).expect("Failed to get recent");
|
||||
assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test"));
|
||||
|
||||
drop(tracker);
|
||||
env::remove_var("RTK_DB_PATH");
|
||||
// Verify via DB that record exists
|
||||
let tracker = Tracker::new().expect("Failed to create tracker");
|
||||
let recent = tracker.get_recent(5).expect("Failed to get recent");
|
||||
assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test"));
|
||||
});
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
}
|
||||
@@ -2083,49 +2080,48 @@ mod tests {
|
||||
));
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
env::set_var("RTK_DB_PATH", &db_path);
|
||||
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
|
||||
let timer = TimedExecution::start();
|
||||
timer.track_passthrough("git tag", "rtk git tag (passthrough)");
|
||||
|
||||
let timer = TimedExecution::start();
|
||||
timer.track_passthrough("git tag", "rtk git tag (passthrough)");
|
||||
let tracker = Tracker::new().expect("Failed to create tracker");
|
||||
let recent = tracker.get_recent(5).expect("Failed to get recent");
|
||||
|
||||
let tracker = Tracker::new().expect("Failed to create tracker");
|
||||
let recent = tracker.get_recent(5).expect("Failed to get recent");
|
||||
let pt = recent
|
||||
.iter()
|
||||
.find(|r| r.rtk_cmd.contains("passthrough"))
|
||||
.expect("Passthrough record not found");
|
||||
|
||||
let pt = recent
|
||||
.iter()
|
||||
.find(|r| r.rtk_cmd.contains("passthrough"))
|
||||
.expect("Passthrough record not found");
|
||||
|
||||
// savings_pct should be 0 for passthrough
|
||||
assert_eq!(pt.savings_pct, 0.0);
|
||||
assert_eq!(pt.saved_tokens, 0);
|
||||
|
||||
drop(tracker);
|
||||
env::remove_var("RTK_DB_PATH");
|
||||
// savings_pct should be 0 for passthrough
|
||||
assert_eq!(pt.savings_pct, 0.0);
|
||||
assert_eq!(pt.saved_tokens, 0);
|
||||
});
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
}
|
||||
|
||||
// 7. get_db_path respects environment variable RTK_DB_PATH
|
||||
// 8. get_db_path falls back to default when no custom config
|
||||
// Combined into one test to avoid env var race between parallel tests
|
||||
// Combined into one test so the set and unset cases cannot interleave.
|
||||
#[test]
|
||||
fn test_db_path_env_and_default() {
|
||||
use std::env;
|
||||
let _guard = ENV_LOCK.lock().unwrap();
|
||||
|
||||
let custom_path = env::temp_dir().join("rtk_test_custom.db");
|
||||
env::set_var("RTK_DB_PATH", &custom_path);
|
||||
let db_path = get_db_path().expect("Failed to get db path");
|
||||
assert_eq!(db_path, custom_path);
|
||||
temp_env::with_var("RTK_DB_PATH", Some(&custom_path), || {
|
||||
let db_path = get_db_path().expect("Failed to get db path");
|
||||
assert_eq!(db_path, custom_path);
|
||||
});
|
||||
|
||||
env::remove_var("RTK_DB_PATH");
|
||||
let db_path = get_db_path().expect("Failed to get db path");
|
||||
assert!(
|
||||
db_path.ends_with("rtk/history.db"),
|
||||
"expected default path ending with rtk/history.db, got: {}",
|
||||
db_path.display()
|
||||
);
|
||||
temp_env::with_var_unset("RTK_DB_PATH", || {
|
||||
let db_path = get_db_path().expect("Failed to get db path");
|
||||
assert!(
|
||||
db_path.ends_with("rtk/history.db"),
|
||||
"expected default path ending with rtk/history.db, got: {}",
|
||||
db_path.display()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 8b. Tracker::new() gates schema migration behind PRAGMA user_version, so a
|
||||
@@ -2140,24 +2136,22 @@ mod tests {
|
||||
env::temp_dir().join(format!("rtk_test_schema_version_{}.db", std::process::id()));
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
env::set_var("RTK_DB_PATH", &db_path);
|
||||
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
|
||||
let tracker = Tracker::new().expect("first open should run migrations");
|
||||
let version: i64 = tracker
|
||||
.conn
|
||||
.query_row("PRAGMA user_version", [], |row| row.get(0))
|
||||
.expect("user_version should be readable");
|
||||
assert_eq!(version, SCHEMA_VERSION);
|
||||
drop(tracker);
|
||||
|
||||
let tracker = Tracker::new().expect("first open should run migrations");
|
||||
let version: i64 = tracker
|
||||
.conn
|
||||
.query_row("PRAGMA user_version", [], |row| row.get(0))
|
||||
.expect("user_version should be readable");
|
||||
assert_eq!(version, SCHEMA_VERSION);
|
||||
drop(tracker);
|
||||
|
||||
// Second open on the same file must skip migrations without erroring, and
|
||||
// the DB must still be fully usable (tables from the first open persist).
|
||||
let tracker2 = Tracker::new().expect("second open should skip migrations cleanly");
|
||||
tracker2
|
||||
.record("git status", "rtk git status", 100, 20, 50)
|
||||
.expect("commands table should already exist and accept writes");
|
||||
|
||||
env::remove_var("RTK_DB_PATH");
|
||||
// Second open on the same file must skip migrations without erroring, and
|
||||
// the DB must still be fully usable (tables from the first open persist).
|
||||
let tracker2 = Tracker::new().expect("second open should skip migrations cleanly");
|
||||
tracker2
|
||||
.record("git status", "rtk git status", 100, 20, 50)
|
||||
.expect("commands table should already exist and accept writes");
|
||||
});
|
||||
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
|
||||
let _ = std::fs::remove_file(&db_path);
|
||||
}
|
||||
|
||||
@@ -2264,8 +2264,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_audit_log_silent_when_disabled() {
|
||||
std::env::remove_var("RTK_HOOK_AUDIT");
|
||||
audit_log("test", "git status", "rtk git status");
|
||||
temp_env::with_var_unset("RTK_HOOK_AUDIT", || {
|
||||
audit_log("test", "git status", "rtk git status");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+59
-87
@@ -5276,70 +5276,72 @@ fn show_claude_config() -> Result<()> {
|
||||
}
|
||||
|
||||
// Check OpenCode plugin
|
||||
if let Ok(opencode_dir) = resolve_opencode_dir() {
|
||||
let plugin = opencode_plugin_path(&opencode_dir);
|
||||
if plugin.exists() {
|
||||
println!("[ok] OpenCode: plugin installed ({})", plugin.display());
|
||||
} else {
|
||||
println!("[--] OpenCode: plugin not found");
|
||||
match resolve_opencode_dir() {
|
||||
Ok(opencode_dir) => {
|
||||
let plugin = opencode_plugin_path(&opencode_dir);
|
||||
if plugin.exists() {
|
||||
println!("[ok] OpenCode: plugin installed ({})", plugin.display());
|
||||
} else {
|
||||
println!("[--] OpenCode: plugin not found");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("[--] OpenCode: config dir not found");
|
||||
_ => println!("[--] OpenCode: config dir not found"),
|
||||
}
|
||||
|
||||
// Check Cursor hooks
|
||||
if let Ok(cursor_dir) = resolve_cursor_dir() {
|
||||
let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE);
|
||||
let cursor_hooks_json = cursor_dir.join(HOOKS_JSON);
|
||||
match resolve_cursor_dir() {
|
||||
Ok(cursor_dir) => {
|
||||
let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE);
|
||||
let cursor_hooks_json = cursor_dir.join(HOOKS_JSON);
|
||||
|
||||
// Check for binary command in hooks.json first
|
||||
let cursor_binary_registered = if cursor_hooks_json.exists() {
|
||||
let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default();
|
||||
if let Ok(root) = from_json_str::<serde_json::Value>(&content) {
|
||||
cursor_hook_already_present(&root)
|
||||
// Check for binary command in hooks.json first
|
||||
let cursor_binary_registered = if cursor_hooks_json.exists() {
|
||||
let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default();
|
||||
if let Ok(root) = from_json_str::<serde_json::Value>(&content) {
|
||||
cursor_hook_already_present(&root)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
};
|
||||
|
||||
if cursor_binary_registered {
|
||||
println!("[ok] Cursor hook: registered in hooks.json");
|
||||
} else if cursor_hook.exists() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let meta = fs::metadata(&cursor_hook)?;
|
||||
let is_executable = meta.permissions().mode() & 0o111 != 0;
|
||||
let content = fs::read_to_string(&cursor_hook)?;
|
||||
let _is_thin = content.contains("rtk rewrite");
|
||||
if cursor_binary_registered {
|
||||
println!("[ok] Cursor hook: registered in hooks.json");
|
||||
} else if cursor_hook.exists() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let meta = fs::metadata(&cursor_hook)?;
|
||||
let is_executable = meta.permissions().mode() & 0o111 != 0;
|
||||
let content = fs::read_to_string(&cursor_hook)?;
|
||||
let _is_thin = content.contains("rtk rewrite");
|
||||
|
||||
if !is_executable {
|
||||
println!(
|
||||
"[warn] Cursor hook: {} (legacy script, NOT executable)",
|
||||
cursor_hook.display()
|
||||
);
|
||||
} else {
|
||||
if !is_executable {
|
||||
println!(
|
||||
"[warn] Cursor hook: {} (legacy script, NOT executable)",
|
||||
cursor_hook.display()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
|
||||
cursor_hook.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
println!(
|
||||
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
|
||||
cursor_hook.display()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("[--] Cursor hook: not found");
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
println!(
|
||||
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
|
||||
cursor_hook.display()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("[--] Cursor hook: not found");
|
||||
}
|
||||
} else {
|
||||
println!("[--] Cursor: home dir not found");
|
||||
_ => println!("[--] Cursor: home dir not found"),
|
||||
}
|
||||
|
||||
println!("\nUsage:");
|
||||
@@ -9026,52 +9028,30 @@ mod tests {
|
||||
}
|
||||
|
||||
use std::sync::Mutex;
|
||||
static CLAUDE_DIR_LOCK: Mutex<()> = Mutex::new(());
|
||||
static PI_DIR_LOCK: Mutex<()> = Mutex::new(());
|
||||
/// Serialises all tests that mutate the process-wide working directory.
|
||||
static CWD_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
fn with_claude_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
|
||||
let _guard = CLAUDE_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let claude_dir = tmp.path().join(CLAUDE_DIR);
|
||||
fs::create_dir_all(&claude_dir).unwrap();
|
||||
|
||||
let orig = std::env::var_os("CLAUDE_CONFIG_DIR");
|
||||
std::env::set_var("CLAUDE_CONFIG_DIR", &claude_dir);
|
||||
f(&claude_dir);
|
||||
match orig {
|
||||
Some(v) => std::env::set_var("CLAUDE_CONFIG_DIR", v),
|
||||
None => std::env::remove_var("CLAUDE_CONFIG_DIR"),
|
||||
}
|
||||
temp_env::with_var("CLAUDE_CONFIG_DIR", Some(&claude_dir), || f(&claude_dir));
|
||||
}
|
||||
|
||||
fn with_pi_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
|
||||
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let pi_dir = tmp.path().join("pi_agent");
|
||||
fs::create_dir_all(&pi_dir).unwrap();
|
||||
|
||||
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
|
||||
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &pi_dir);
|
||||
f(&pi_dir);
|
||||
match orig {
|
||||
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
|
||||
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
|
||||
}
|
||||
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&pi_dir), || f(&pi_dir));
|
||||
}
|
||||
|
||||
// OMP reuses PI_CODING_AGENT_DIR, so this overrides the same variable as
|
||||
// with_pi_dir_override; temp-env serialises the two against each other.
|
||||
fn with_omp_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
|
||||
// OMP reuses PI_CODING_AGENT_DIR, so share the Pi environment lock.
|
||||
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let omp_dir = tmp.path().join("omp_agent");
|
||||
fs::create_dir_all(&omp_dir).unwrap();
|
||||
|
||||
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
|
||||
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &omp_dir);
|
||||
f(&omp_dir);
|
||||
match orig {
|
||||
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
|
||||
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
|
||||
}
|
||||
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&omp_dir), || f(&omp_dir));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -9604,18 +9584,10 @@ mod tests {
|
||||
fn test_run_pi_mode_global_creates_plugin_when_dir_absent() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let absent_dir = tmp.path().join("no_such_pi_dir");
|
||||
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
|
||||
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &absent_dir);
|
||||
|
||||
let result = run_pi_mode(true, InitContext::default());
|
||||
|
||||
match orig {
|
||||
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
|
||||
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
|
||||
}
|
||||
|
||||
result.unwrap();
|
||||
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&absent_dir), || {
|
||||
run_pi_mode(true, InitContext::default())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let plugin = absent_dir.join(PI_EXTENSIONS_SUBDIR).join(PI_PLUGIN_FILE);
|
||||
assert!(
|
||||
|
||||
+8
-11
@@ -403,8 +403,7 @@ mod tests {
|
||||
/// Overrides the store path via a scoped env var (not possible with
|
||||
/// the real function), so we test the logic by calling internal fns.
|
||||
fn setup_test_env(temp: &TempDir) -> PathBuf {
|
||||
let store_file = temp.path().join("trusted_filters.json");
|
||||
store_file
|
||||
temp.path().join("trusted_filters.json")
|
||||
}
|
||||
|
||||
fn check_trust_with_store(filter_path: &Path, store_file: &Path) -> Result<TrustStatus> {
|
||||
@@ -565,15 +564,13 @@ mod tests {
|
||||
std::fs::write(&filter, "[filters.test]\nmatch_command = \"echo\"").unwrap();
|
||||
|
||||
// Both env vars must be set: trust override + CI indicator
|
||||
#[allow(deprecated)]
|
||||
std::env::set_var("RTK_TRUST_PROJECT_FILTERS", "1");
|
||||
#[allow(deprecated)]
|
||||
std::env::set_var("CI", "true");
|
||||
let status = check_trust(&filter).unwrap();
|
||||
#[allow(deprecated)]
|
||||
std::env::remove_var("RTK_TRUST_PROJECT_FILTERS");
|
||||
#[allow(deprecated)]
|
||||
std::env::remove_var("CI");
|
||||
let status = temp_env::with_vars(
|
||||
[
|
||||
("RTK_TRUST_PROJECT_FILTERS", Some("1")),
|
||||
("CI", Some("true")),
|
||||
],
|
||||
|| check_trust(&filter).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(status, TrustStatus::EnvOverride);
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::collapsible_if)]
|
||||
mod analytics;
|
||||
mod cmds;
|
||||
mod core;
|
||||
@@ -2960,11 +2961,17 @@ fn run_cli() -> Result<i32> {
|
||||
unsafe extern "C" fn handle_signal(sig: libc::c_int) {
|
||||
let pid = PROXY_CHILD_PID.load(Ordering::SeqCst);
|
||||
if pid != 0 {
|
||||
libc::kill(pid as libc::pid_t, libc::SIGTERM);
|
||||
libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0);
|
||||
// nosemgrep: unsafe-block
|
||||
unsafe {
|
||||
libc::kill(pid as libc::pid_t, libc::SIGTERM);
|
||||
libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
// nosemgrep: unsafe-block
|
||||
unsafe {
|
||||
libc::signal(sig, libc::SIG_DFL);
|
||||
libc::raise(sig);
|
||||
}
|
||||
libc::signal(sig, libc::SIG_DFL);
|
||||
libc::raise(sig);
|
||||
}
|
||||
// nosemgrep: unsafe-block
|
||||
unsafe {
|
||||
|
||||
Reference in New Issue
Block a user