Skip to content

Commit 272534b

Browse files
committed
fix: increase signal in git diff, git log, and json filters (#621)
- git diff: raise max_hunk_lines from 30 to 100 (LLMs need full hunks) - git log: show 3 body lines instead of 1 (preserves BREAKING CHANGE, migration notes) - json: show values by default (LLMs need values for config debugging), add --schema for types-only Tested with phi4:14b on local LLM — all 3 fixes improve comprehension. Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
1 parent 4020aa0 commit 272534b

4 files changed

Lines changed: 173 additions & 43 deletions

File tree

src/filters/stat.toml

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
[filters.stat]
2-
description = "Compact stat output — strip blank lines"
2+
description = "Compact stat output — strip device/inode/birth noise"
33
match_command = "^stat\\b"
44
strip_ansi = true
55
strip_lines_matching = [
66
"^\\s*$",
7+
"^\\s*Device:",
8+
"^\\s*Birth:",
79
]
8-
max_lines = 30
10+
truncate_lines_at = 120
11+
max_lines = 20
912

1013
[[tests.stat]]
11-
name = "macOS stat output kept"
12-
input = """
13-
16777234 8690244974 -rw-r--r-- 1 patrick staff 0 12345 "Mar 10 12:00:00 2026" "Mar 10 11:00:00 2026" "Mar 10 11:00:00 2026" "Mar 9 10:00:00 2026" 4096 24 0 file.txt
14-
"""
15-
expected = "16777234 8690244974 -rw-r--r-- 1 patrick staff 0 12345 \"Mar 10 12:00:00 2026\" \"Mar 10 11:00:00 2026\" \"Mar 10 11:00:00 2026\" \"Mar 9 10:00:00 2026\" 4096 24 0 file.txt"
16-
17-
[[tests.stat]]
18-
name = "linux stat output kept"
14+
name = "linux stat output strips device and birth"
1915
input = """
2016
File: main.rs
2117
Size: 12345 Blocks: 24 IO Block: 4096 regular file
@@ -26,7 +22,21 @@ Modify: 2026-03-10 11:00:00.000000000 +0100
2622
Change: 2026-03-10 11:00:00.000000000 +0100
2723
Birth: 2026-03-09 10:00:00.000000000 +0100
2824
"""
29-
expected = " File: main.rs\n Size: 12345 Blocks: 24 IO Block: 4096 regular file\nDevice: 801h/2049d Inode: 1234567 Links: 1\nAccess: (0644/-rw-r--r--) Uid: ( 1000/ patrick) Gid: ( 1000/ patrick)\nAccess: 2026-03-10 12:00:00.000000000 +0100\nModify: 2026-03-10 11:00:00.000000000 +0100\nChange: 2026-03-10 11:00:00.000000000 +0100\n Birth: 2026-03-09 10:00:00.000000000 +0100"
25+
expected = " File: main.rs\n Size: 12345 Blocks: 24 IO Block: 4096 regular file\nAccess: (0644/-rw-r--r--) Uid: ( 1000/ patrick) Gid: ( 1000/ patrick)\nAccess: 2026-03-10 12:00:00.000000000 +0100\nModify: 2026-03-10 11:00:00.000000000 +0100\nChange: 2026-03-10 11:00:00.000000000 +0100"
26+
27+
[[tests.stat]]
28+
name = "macOS stat -x strips device and birth"
29+
input = """
30+
File: "main.rs"
31+
Size: 82848 FileType: Regular File
32+
Mode: (0644/-rw-r--r--) Uid: ( 501/ patrick) Gid: ( 20/ staff)
33+
Device: 1,15 Inode: 66302332 Links: 1
34+
Access: Wed Mar 18 21:21:15 2026
35+
Modify: Wed Mar 18 20:56:11 2026
36+
Change: Wed Mar 18 20:56:11 2026
37+
Birth: Wed Mar 18 20:56:11 2026
38+
"""
39+
expected = " File: \"main.rs\"\n Size: 82848 FileType: Regular File\n Mode: (0644/-rw-r--r--) Uid: ( 501/ patrick) Gid: ( 20/ staff)\nAccess: Wed Mar 18 21:21:15 2026\nModify: Wed Mar 18 20:56:11 2026\nChange: Wed Mar 18 20:56:11 2026"
3040

3141
[[tests.stat]]
3242
name = "empty input passes through"

src/git.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ pub(crate) fn compact_diff(diff: &str, max_lines: usize) -> String {
297297
let mut removed = 0;
298298
let mut in_hunk = false;
299299
let mut hunk_lines = 0;
300-
let max_hunk_lines = 30;
300+
let max_hunk_lines = 100;
301301
let mut was_truncated = false;
302302

303303
for line in diff.lines() {
@@ -532,17 +532,25 @@ fn filter_log_output(
532532
Some(h) => truncate_line(h.trim(), truncate_width),
533533
None => continue,
534534
};
535-
// Remaining lines are the body — keep first non-empty line only
536-
let body_line = lines.map(|l| l.trim()).find(|l| {
537-
!l.is_empty() && !l.starts_with("Signed-off-by:") && !l.starts_with("Co-authored-by:")
538-
});
539-
540-
match body_line {
541-
Some(body) => {
542-
let truncated_body = truncate_line(body, truncate_width);
543-
result.push(format!("{}\n {}", header, truncated_body));
535+
// Remaining lines are the body — keep up to 3 non-empty, non-trailer lines
536+
let body_lines: Vec<&str> = lines
537+
.map(|l| l.trim())
538+
.filter(|l| {
539+
!l.is_empty()
540+
&& !l.starts_with("Signed-off-by:")
541+
&& !l.starts_with("Co-authored-by:")
542+
})
543+
.take(3)
544+
.collect();
545+
546+
if body_lines.is_empty() {
547+
result.push(header);
548+
} else {
549+
let mut entry = header;
550+
for body in &body_lines {
551+
entry.push_str(&format!("\n {}", truncate_line(body, truncate_width)));
544552
}
545-
None => result.push(header),
553+
result.push(entry);
546554
}
547555
}
548556

src/json_cmd.rs

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ fn validate_json_extension(file: &Path) -> Result<()> {
3333
Ok(())
3434
}
3535

36-
/// Show JSON structure without values
37-
pub fn run(file: &Path, max_depth: usize, verbose: u8) -> Result<()> {
36+
/// Show JSON (compact with values, or schema-only with --schema)
37+
pub fn run(file: &Path, max_depth: usize, schema_only: bool, verbose: u8) -> Result<()> {
3838
validate_json_extension(file)?;
3939
let timer = tracking::TimedExecution::start();
4040

@@ -45,19 +45,23 @@ pub fn run(file: &Path, max_depth: usize, verbose: u8) -> Result<()> {
4545
let content = fs::read_to_string(file)
4646
.with_context(|| format!("Failed to read file: {}", file.display()))?;
4747

48-
let schema = filter_json_string(&content, max_depth)?;
49-
println!("{}", schema);
48+
let output = if schema_only {
49+
filter_json_string(&content, max_depth)?
50+
} else {
51+
filter_json_compact(&content, max_depth)?
52+
};
53+
println!("{}", output);
5054
timer.track(
5155
&format!("cat {}", file.display()),
5256
"rtk json",
5357
&content,
54-
&schema,
58+
&output,
5559
);
5660
Ok(())
5761
}
5862

59-
/// Show JSON structure from stdin
60-
pub fn run_stdin(max_depth: usize, verbose: u8) -> Result<()> {
63+
/// Show JSON from stdin
64+
pub fn run_stdin(max_depth: usize, schema_only: bool, verbose: u8) -> Result<()> {
6165
let timer = tracking::TimedExecution::start();
6266

6367
if verbose > 0 {
@@ -70,13 +74,107 @@ pub fn run_stdin(max_depth: usize, verbose: u8) -> Result<()> {
7074
.read_to_string(&mut content)
7175
.context("Failed to read from stdin")?;
7276

73-
let schema = filter_json_string(&content, max_depth)?;
74-
println!("{}", schema);
75-
timer.track("cat - (stdin)", "rtk json -", &content, &schema);
77+
let output = if schema_only {
78+
filter_json_string(&content, max_depth)?
79+
} else {
80+
filter_json_compact(&content, max_depth)?
81+
};
82+
println!("{}", output);
83+
timer.track("cat - (stdin)", "rtk json -", &content, &output);
7684
Ok(())
7785
}
7886

79-
/// Parse a JSON string and return its schema representation.
87+
/// Parse a JSON string and return compact representation with values preserved.
88+
/// Long strings are truncated, arrays are summarized.
89+
pub fn filter_json_compact(json_str: &str, max_depth: usize) -> Result<String> {
90+
let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON")?;
91+
Ok(compact_json(&value, 0, max_depth))
92+
}
93+
94+
fn compact_json(value: &Value, depth: usize, max_depth: usize) -> String {
95+
let indent = " ".repeat(depth);
96+
97+
if depth > max_depth {
98+
return format!("{}...", indent);
99+
}
100+
101+
match value {
102+
Value::Null => format!("{}null", indent),
103+
Value::Bool(b) => format!("{}{}", indent, b),
104+
Value::Number(n) => format!("{}{}", indent, n),
105+
Value::String(s) => {
106+
if s.len() > 80 {
107+
format!("{}\"{}...\"", indent, &s[..77])
108+
} else {
109+
format!("{}\"{}\"", indent, s)
110+
}
111+
}
112+
Value::Array(arr) => {
113+
if arr.is_empty() {
114+
format!("{}[]", indent)
115+
} else if arr.len() > 5 {
116+
let first = compact_json(&arr[0], depth + 1, max_depth);
117+
format!("{}[{}, ... +{} more]", indent, first.trim(), arr.len() - 1)
118+
} else {
119+
let items: Vec<String> = arr
120+
.iter()
121+
.map(|v| compact_json(v, depth + 1, max_depth))
122+
.collect();
123+
let all_simple = arr.iter().all(|v| {
124+
matches!(
125+
v,
126+
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
127+
)
128+
});
129+
if all_simple {
130+
let inline: Vec<&str> = items.iter().map(|s| s.trim()).collect();
131+
format!("{}[{}]", indent, inline.join(", "))
132+
} else {
133+
let mut lines = vec![format!("{}[", indent)];
134+
for item in &items {
135+
lines.push(format!("{},", item));
136+
}
137+
lines.push(format!("{}]", indent));
138+
lines.join("\n")
139+
}
140+
}
141+
}
142+
Value::Object(map) => {
143+
if map.is_empty() {
144+
format!("{}{{}}", indent)
145+
} else {
146+
let mut lines = vec![format!("{}{{", indent)];
147+
let mut keys: Vec<_> = map.keys().collect();
148+
keys.sort();
149+
150+
for (i, key) in keys.iter().enumerate() {
151+
let val = &map[*key];
152+
let is_simple = matches!(
153+
val,
154+
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
155+
);
156+
157+
if is_simple {
158+
let val_str = compact_json(val, 0, max_depth);
159+
lines.push(format!("{} {}: {}", indent, key, val_str.trim()));
160+
} else {
161+
lines.push(format!("{} {}:", indent, key));
162+
lines.push(compact_json(val, depth + 1, max_depth));
163+
}
164+
165+
if i >= 20 {
166+
lines.push(format!("{} ... +{} more keys", indent, keys.len() - i - 1));
167+
break;
168+
}
169+
}
170+
lines.push(format!("{}}}", indent));
171+
lines.join("\n")
172+
}
173+
}
174+
}
175+
}
176+
177+
/// Parse a JSON string and return its schema representation (types only, no values).
80178
/// Useful for piping JSON from other commands (e.g., `gh api`, `curl`).
81179
pub fn filter_json_string(json_str: &str, max_depth: usize) -> Result<String> {
82180
let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON")?;

src/main.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,16 @@ enum Commands {
237237
command: Vec<String>,
238238
},
239239

240-
/// Show JSON structure without values
240+
/// Show JSON (compact values, or schema-only with --schema)
241241
Json {
242242
/// JSON file
243243
file: PathBuf,
244244
/// Max depth
245245
#[arg(short, long, default_value = "5")]
246246
depth: usize,
247+
/// Show structure only (strip all values)
248+
#[arg(long)]
249+
schema: bool,
247250
},
248251

249252
/// Summarize project dependencies
@@ -387,9 +390,9 @@ enum Commands {
387390
Wget {
388391
/// URL to download
389392
url: String,
390-
/// Output to stdout instead of file
391-
#[arg(short = 'O', long)]
392-
stdout: bool,
393+
/// Output file (-O - for stdout)
394+
#[arg(short = 'O', long = "output-document", allow_hyphen_values = true)]
395+
output: Option<String>,
393396
/// Additional wget arguments
394397
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
395398
args: Vec<String>,
@@ -1501,11 +1504,15 @@ fn main() -> Result<()> {
15011504
runner::run_test(&cmd, cli.verbose)?;
15021505
}
15031506

1504-
Commands::Json { file, depth } => {
1507+
Commands::Json {
1508+
file,
1509+
depth,
1510+
schema,
1511+
} => {
15051512
if file == Path::new("-") {
1506-
json_cmd::run_stdin(depth, cli.verbose)?;
1513+
json_cmd::run_stdin(depth, schema, cli.verbose)?;
15071514
} else {
1508-
json_cmd::run(&file, depth, cli.verbose)?;
1515+
json_cmd::run(&file, depth, schema, cli.verbose)?;
15091516
}
15101517
}
15111518

@@ -1702,11 +1709,18 @@ fn main() -> Result<()> {
17021709
}
17031710
}
17041711

1705-
Commands::Wget { url, stdout, args } => {
1706-
if stdout {
1712+
Commands::Wget { url, output, args } => {
1713+
if output.as_deref() == Some("-") {
17071714
wget_cmd::run_stdout(&url, &args, cli.verbose)?;
17081715
} else {
1709-
wget_cmd::run(&url, &args, cli.verbose)?;
1716+
// Pass -O <file> through to wget via args
1717+
let mut all_args = Vec::new();
1718+
if let Some(out_file) = &output {
1719+
all_args.push("-O".to_string());
1720+
all_args.push(out_file.clone());
1721+
}
1722+
all_args.extend(args);
1723+
wget_cmd::run(&url, &all_args, cli.verbose)?;
17101724
}
17111725
}
17121726

0 commit comments

Comments
 (0)