@hasna/terminal 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/dist/App.js +404 -0
  2. package/dist/Browse.js +79 -0
  3. package/dist/FuzzyPicker.js +47 -0
  4. package/dist/Onboarding.js +51 -0
  5. package/dist/Spinner.js +12 -0
  6. package/dist/StatusBar.js +49 -0
  7. package/dist/ai.js +322 -0
  8. package/dist/cache.js +41 -0
  9. package/dist/cli.js +64 -16
  10. package/dist/command-rewriter.js +64 -0
  11. package/dist/command-validator.js +86 -0
  12. package/dist/compression.js +107 -0
  13. package/dist/context-hints.js +275 -0
  14. package/dist/diff-cache.js +107 -0
  15. package/dist/discover.js +212 -0
  16. package/dist/economy.js +123 -0
  17. package/dist/expand-store.js +38 -0
  18. package/dist/file-cache.js +72 -0
  19. package/dist/file-index.js +62 -0
  20. package/dist/history.js +62 -0
  21. package/dist/lazy-executor.js +54 -0
  22. package/dist/line-dedup.js +59 -0
  23. package/dist/loop-detector.js +75 -0
  24. package/dist/mcp/install.js +98 -0
  25. package/dist/mcp/server.js +569 -0
  26. package/dist/noise-filter.js +86 -0
  27. package/dist/output-processor.js +129 -0
  28. package/dist/output-router.js +41 -0
  29. package/dist/output-store.js +111 -0
  30. package/dist/parsers/base.js +2 -0
  31. package/dist/parsers/build.js +64 -0
  32. package/dist/parsers/errors.js +101 -0
  33. package/dist/parsers/files.js +78 -0
  34. package/dist/parsers/git.js +99 -0
  35. package/dist/parsers/index.js +48 -0
  36. package/dist/parsers/tests.js +89 -0
  37. package/dist/providers/anthropic.js +39 -0
  38. package/dist/providers/base.js +4 -0
  39. package/dist/providers/cerebras.js +95 -0
  40. package/dist/providers/groq.js +95 -0
  41. package/dist/providers/index.js +73 -0
  42. package/dist/providers/xai.js +95 -0
  43. package/dist/recipes/model.js +20 -0
  44. package/dist/recipes/storage.js +136 -0
  45. package/dist/search/content-search.js +68 -0
  46. package/dist/search/file-search.js +61 -0
  47. package/dist/search/filters.js +34 -0
  48. package/dist/search/index.js +5 -0
  49. package/dist/search/semantic.js +320 -0
  50. package/dist/session-boot.js +59 -0
  51. package/dist/session-context.js +55 -0
  52. package/dist/sessions-db.js +173 -0
  53. package/dist/smart-display.js +286 -0
  54. package/dist/snapshots.js +51 -0
  55. package/dist/supervisor.js +112 -0
  56. package/dist/test-watchlist.js +131 -0
  57. package/dist/tool-profiles.js +122 -0
  58. package/dist/tree.js +94 -0
  59. package/dist/usage-cache.js +65 -0
  60. package/package.json +8 -1
  61. package/src/ai.ts +8 -0
  62. package/src/cli.tsx +57 -18
  63. package/src/output-processor.ts +6 -1
  64. package/src/output-store.ts +58 -12
  65. package/src/tool-profiles.ts +139 -0
  66. package/.claude/scheduled_tasks.lock +0 -1
  67. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  68. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  69. package/CONTRIBUTING.md +0 -80
  70. package/benchmarks/benchmark.mjs +0 -115
  71. package/imported_modules.txt +0 -0
  72. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  73. package/temp/rtk/.claude/agents/debugger.md +0 -519
  74. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  75. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  76. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  77. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  78. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  79. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  80. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  81. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  82. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  83. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  84. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  85. package/temp/rtk/.claude/skills/performance.md +0 -435
  86. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  87. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  88. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  89. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  90. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  91. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  92. package/temp/rtk/.claude/skills/ship.md +0 -404
  93. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  94. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  95. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  96. package/temp/rtk/.github/workflows/release.yml +0 -343
  97. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  98. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  99. package/temp/rtk/.release-please-manifest.json +0 -3
  100. package/temp/rtk/ARCHITECTURE.md +0 -1491
  101. package/temp/rtk/CHANGELOG.md +0 -640
  102. package/temp/rtk/CLAUDE.md +0 -605
  103. package/temp/rtk/CONTRIBUTING.md +0 -199
  104. package/temp/rtk/Cargo.lock +0 -1668
  105. package/temp/rtk/Cargo.toml +0 -64
  106. package/temp/rtk/Formula/rtk.rb +0 -43
  107. package/temp/rtk/INSTALL.md +0 -390
  108. package/temp/rtk/LICENSE +0 -21
  109. package/temp/rtk/README.md +0 -386
  110. package/temp/rtk/README_es.md +0 -159
  111. package/temp/rtk/README_fr.md +0 -197
  112. package/temp/rtk/README_ja.md +0 -159
  113. package/temp/rtk/README_ko.md +0 -159
  114. package/temp/rtk/README_zh.md +0 -167
  115. package/temp/rtk/ROADMAP.md +0 -15
  116. package/temp/rtk/SECURITY.md +0 -217
  117. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  118. package/temp/rtk/build.rs +0 -57
  119. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  120. package/temp/rtk/docs/FEATURES.md +0 -1410
  121. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  122. package/temp/rtk/docs/filter-workflow.md +0 -102
  123. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  124. package/temp/rtk/docs/tracking.md +0 -583
  125. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  126. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  127. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  128. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  129. package/temp/rtk/install.sh +0 -124
  130. package/temp/rtk/release-please-config.json +0 -10
  131. package/temp/rtk/scripts/benchmark.sh +0 -592
  132. package/temp/rtk/scripts/check-installation.sh +0 -162
  133. package/temp/rtk/scripts/install-local.sh +0 -37
  134. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  135. package/temp/rtk/scripts/test-all.sh +0 -561
  136. package/temp/rtk/scripts/test-aristote.sh +0 -227
  137. package/temp/rtk/scripts/test-tracking.sh +0 -79
  138. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  139. package/temp/rtk/scripts/validate-docs.sh +0 -73
  140. package/temp/rtk/src/aws_cmd.rs +0 -880
  141. package/temp/rtk/src/binlog.rs +0 -1645
  142. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  143. package/temp/rtk/src/cc_economics.rs +0 -1157
  144. package/temp/rtk/src/ccusage.rs +0 -340
  145. package/temp/rtk/src/config.rs +0 -187
  146. package/temp/rtk/src/container.rs +0 -855
  147. package/temp/rtk/src/curl_cmd.rs +0 -134
  148. package/temp/rtk/src/deps.rs +0 -268
  149. package/temp/rtk/src/diff_cmd.rs +0 -367
  150. package/temp/rtk/src/discover/mod.rs +0 -274
  151. package/temp/rtk/src/discover/provider.rs +0 -388
  152. package/temp/rtk/src/discover/registry.rs +0 -2022
  153. package/temp/rtk/src/discover/report.rs +0 -202
  154. package/temp/rtk/src/discover/rules.rs +0 -667
  155. package/temp/rtk/src/display_helpers.rs +0 -402
  156. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  157. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  158. package/temp/rtk/src/dotnet_trx.rs +0 -593
  159. package/temp/rtk/src/env_cmd.rs +0 -204
  160. package/temp/rtk/src/filter.rs +0 -462
  161. package/temp/rtk/src/filters/README.md +0 -52
  162. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  163. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  164. package/temp/rtk/src/filters/biome.toml +0 -45
  165. package/temp/rtk/src/filters/brew-install.toml +0 -37
  166. package/temp/rtk/src/filters/composer-install.toml +0 -40
  167. package/temp/rtk/src/filters/df.toml +0 -16
  168. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  169. package/temp/rtk/src/filters/du.toml +0 -16
  170. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  171. package/temp/rtk/src/filters/gcc.toml +0 -49
  172. package/temp/rtk/src/filters/gcloud.toml +0 -22
  173. package/temp/rtk/src/filters/hadolint.toml +0 -24
  174. package/temp/rtk/src/filters/helm.toml +0 -29
  175. package/temp/rtk/src/filters/iptables.toml +0 -27
  176. package/temp/rtk/src/filters/jj.toml +0 -28
  177. package/temp/rtk/src/filters/jq.toml +0 -24
  178. package/temp/rtk/src/filters/make.toml +0 -41
  179. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  180. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  181. package/temp/rtk/src/filters/mix-format.toml +0 -15
  182. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  183. package/temp/rtk/src/filters/oxlint.toml +0 -43
  184. package/temp/rtk/src/filters/ping.toml +0 -63
  185. package/temp/rtk/src/filters/pio-run.toml +0 -40
  186. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  187. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  188. package/temp/rtk/src/filters/ps.toml +0 -16
  189. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  190. package/temp/rtk/src/filters/rsync.toml +0 -48
  191. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  192. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  193. package/temp/rtk/src/filters/skopeo.toml +0 -45
  194. package/temp/rtk/src/filters/sops.toml +0 -16
  195. package/temp/rtk/src/filters/ssh.toml +0 -44
  196. package/temp/rtk/src/filters/stat.toml +0 -34
  197. package/temp/rtk/src/filters/swift-build.toml +0 -41
  198. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  199. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  200. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  201. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  202. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  203. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  204. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  205. package/temp/rtk/src/filters/ty.toml +0 -50
  206. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  207. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  208. package/temp/rtk/src/filters/yamllint.toml +0 -25
  209. package/temp/rtk/src/find_cmd.rs +0 -598
  210. package/temp/rtk/src/format_cmd.rs +0 -386
  211. package/temp/rtk/src/gain.rs +0 -723
  212. package/temp/rtk/src/gh_cmd.rs +0 -1651
  213. package/temp/rtk/src/git.rs +0 -2012
  214. package/temp/rtk/src/go_cmd.rs +0 -592
  215. package/temp/rtk/src/golangci_cmd.rs +0 -254
  216. package/temp/rtk/src/grep_cmd.rs +0 -288
  217. package/temp/rtk/src/gt_cmd.rs +0 -810
  218. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  219. package/temp/rtk/src/hook_check.rs +0 -171
  220. package/temp/rtk/src/init.rs +0 -1859
  221. package/temp/rtk/src/integrity.rs +0 -537
  222. package/temp/rtk/src/json_cmd.rs +0 -231
  223. package/temp/rtk/src/learn/detector.rs +0 -628
  224. package/temp/rtk/src/learn/mod.rs +0 -119
  225. package/temp/rtk/src/learn/report.rs +0 -184
  226. package/temp/rtk/src/lint_cmd.rs +0 -694
  227. package/temp/rtk/src/local_llm.rs +0 -316
  228. package/temp/rtk/src/log_cmd.rs +0 -248
  229. package/temp/rtk/src/ls.rs +0 -324
  230. package/temp/rtk/src/main.rs +0 -2482
  231. package/temp/rtk/src/mypy_cmd.rs +0 -389
  232. package/temp/rtk/src/next_cmd.rs +0 -241
  233. package/temp/rtk/src/npm_cmd.rs +0 -236
  234. package/temp/rtk/src/parser/README.md +0 -267
  235. package/temp/rtk/src/parser/error.rs +0 -46
  236. package/temp/rtk/src/parser/formatter.rs +0 -336
  237. package/temp/rtk/src/parser/mod.rs +0 -311
  238. package/temp/rtk/src/parser/types.rs +0 -119
  239. package/temp/rtk/src/pip_cmd.rs +0 -302
  240. package/temp/rtk/src/playwright_cmd.rs +0 -479
  241. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  242. package/temp/rtk/src/prettier_cmd.rs +0 -221
  243. package/temp/rtk/src/prisma_cmd.rs +0 -482
  244. package/temp/rtk/src/psql_cmd.rs +0 -382
  245. package/temp/rtk/src/pytest_cmd.rs +0 -384
  246. package/temp/rtk/src/read.rs +0 -217
  247. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  248. package/temp/rtk/src/ruff_cmd.rs +0 -402
  249. package/temp/rtk/src/runner.rs +0 -271
  250. package/temp/rtk/src/summary.rs +0 -297
  251. package/temp/rtk/src/tee.rs +0 -405
  252. package/temp/rtk/src/telemetry.rs +0 -248
  253. package/temp/rtk/src/toml_filter.rs +0 -1655
  254. package/temp/rtk/src/tracking.rs +0 -1416
  255. package/temp/rtk/src/tree.rs +0 -209
  256. package/temp/rtk/src/tsc_cmd.rs +0 -259
  257. package/temp/rtk/src/utils.rs +0 -432
  258. package/temp/rtk/src/verify_cmd.rs +0 -47
  259. package/temp/rtk/src/vitest_cmd.rs +0 -385
  260. package/temp/rtk/src/wc_cmd.rs +0 -401
  261. package/temp/rtk/src/wget_cmd.rs +0 -260
  262. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  263. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  264. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  265. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  266. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
  267. package/tsconfig.json +0 -15
@@ -1,402 +0,0 @@
1
- use crate::tracking;
2
- use crate::utils::truncate;
3
- use anyhow::{Context, Result};
4
- use serde::Deserialize;
5
- use std::collections::HashMap;
6
- use std::process::Command;
7
-
8
- #[derive(Debug, Deserialize)]
9
- struct RuffLocation {
10
- row: usize,
11
- column: usize,
12
- }
13
-
14
- #[derive(Debug, Deserialize)]
15
- struct RuffFix {
16
- #[allow(dead_code)]
17
- applicability: Option<String>,
18
- }
19
-
20
- #[derive(Debug, Deserialize)]
21
- struct RuffDiagnostic {
22
- code: String,
23
- message: String,
24
- location: RuffLocation,
25
- #[allow(dead_code)]
26
- end_location: Option<RuffLocation>,
27
- filename: String,
28
- fix: Option<RuffFix>,
29
- }
30
-
31
- pub fn run(args: &[String], verbose: u8) -> Result<()> {
32
- let timer = tracking::TimedExecution::start();
33
-
34
- // Detect subcommand: check, format, or version
35
- let is_check = args.is_empty()
36
- || args[0] == "check"
37
- || (!args[0].starts_with('-') && args[0] != "format" && args[0] != "version");
38
-
39
- let is_format = args.iter().any(|a| a == "format");
40
-
41
- let mut cmd = Command::new("ruff");
42
-
43
- if is_check {
44
- // Force JSON output for check command
45
- if !args.contains(&"--output-format".to_string()) {
46
- cmd.arg("check").arg("--output-format=json");
47
- } else {
48
- cmd.arg("check");
49
- }
50
-
51
- // Add user arguments (skip "check" if it was the first arg)
52
- let start_idx = if !args.is_empty() && args[0] == "check" {
53
- 1
54
- } else {
55
- 0
56
- };
57
- for arg in &args[start_idx..] {
58
- cmd.arg(arg);
59
- }
60
-
61
- // Default to current directory if no path specified
62
- if args
63
- .iter()
64
- .skip(start_idx)
65
- .all(|a| a.starts_with('-') || a.contains('='))
66
- {
67
- cmd.arg(".");
68
- }
69
- } else {
70
- // Format or other commands - pass through
71
- for arg in args {
72
- cmd.arg(arg);
73
- }
74
- }
75
-
76
- if verbose > 0 {
77
- eprintln!("Running: ruff {}", args.join(" "));
78
- }
79
-
80
- let output = cmd
81
- .output()
82
- .context("Failed to run ruff. Is it installed? Try: pip install ruff")?;
83
-
84
- let stdout = String::from_utf8_lossy(&output.stdout);
85
- let stderr = String::from_utf8_lossy(&output.stderr);
86
- let raw = format!("{}\n{}", stdout, stderr);
87
-
88
- let filtered = if is_check && !stdout.trim().is_empty() {
89
- filter_ruff_check_json(&stdout)
90
- } else if is_format {
91
- filter_ruff_format(&raw)
92
- } else {
93
- // Fallback for other commands (version, etc.)
94
- raw.trim().to_string()
95
- };
96
-
97
- println!("{}", filtered);
98
-
99
- timer.track(
100
- &format!("ruff {}", args.join(" ")),
101
- &format!("rtk ruff {}", args.join(" ")),
102
- &raw,
103
- &filtered,
104
- );
105
-
106
- // Preserve exit code for CI/CD
107
- if !output.status.success() {
108
- std::process::exit(output.status.code().unwrap_or(1));
109
- }
110
-
111
- Ok(())
112
- }
113
-
114
- /// Filter ruff check JSON output - group by rule and file
115
- pub fn filter_ruff_check_json(output: &str) -> String {
116
- let diagnostics: Result<Vec<RuffDiagnostic>, _> = serde_json::from_str(output);
117
-
118
- let diagnostics = match diagnostics {
119
- Ok(d) => d,
120
- Err(e) => {
121
- // Fallback if JSON parsing fails
122
- return format!(
123
- "Ruff check (JSON parse failed: {})\n{}",
124
- e,
125
- truncate(output, 500)
126
- );
127
- }
128
- };
129
-
130
- if diagnostics.is_empty() {
131
- return "✓ Ruff: No issues found".to_string();
132
- }
133
-
134
- let total_issues = diagnostics.len();
135
- let fixable_count = diagnostics.iter().filter(|d| d.fix.is_some()).count();
136
-
137
- // Count unique files
138
- let unique_files: std::collections::HashSet<_> =
139
- diagnostics.iter().map(|d| &d.filename).collect();
140
- let total_files = unique_files.len();
141
-
142
- // Group by rule code
143
- let mut by_rule: HashMap<String, usize> = HashMap::new();
144
- for diag in &diagnostics {
145
- *by_rule.entry(diag.code.clone()).or_insert(0) += 1;
146
- }
147
-
148
- // Group by file
149
- let mut by_file: HashMap<&str, usize> = HashMap::new();
150
- for diag in &diagnostics {
151
- *by_file.entry(&diag.filename).or_insert(0) += 1;
152
- }
153
-
154
- let mut file_counts: Vec<_> = by_file.iter().collect();
155
- file_counts.sort_by(|a, b| b.1.cmp(a.1));
156
-
157
- // Build output
158
- let mut result = String::new();
159
- result.push_str(&format!(
160
- "Ruff: {} issues in {} files",
161
- total_issues, total_files
162
- ));
163
-
164
- if fixable_count > 0 {
165
- result.push_str(&format!(" ({} fixable)", fixable_count));
166
- }
167
- result.push('\n');
168
- result.push_str("═══════════════════════════════════════\n");
169
-
170
- // Show top rules
171
- let mut rule_counts: Vec<_> = by_rule.iter().collect();
172
- rule_counts.sort_by(|a, b| b.1.cmp(a.1));
173
-
174
- if !rule_counts.is_empty() {
175
- result.push_str("Top rules:\n");
176
- for (rule, count) in rule_counts.iter().take(10) {
177
- result.push_str(&format!(" {} ({}x)\n", rule, count));
178
- }
179
- result.push('\n');
180
- }
181
-
182
- // Show top files
183
- result.push_str("Top files:\n");
184
- for (file, count) in file_counts.iter().take(10) {
185
- let short_path = compact_path(file);
186
- result.push_str(&format!(" {} ({} issues)\n", short_path, count));
187
-
188
- // Show top 3 rules in this file
189
- let mut file_rules: HashMap<String, usize> = HashMap::new();
190
- for diag in diagnostics.iter().filter(|d| &d.filename == *file) {
191
- *file_rules.entry(diag.code.clone()).or_insert(0) += 1;
192
- }
193
-
194
- let mut file_rule_counts: Vec<_> = file_rules.iter().collect();
195
- file_rule_counts.sort_by(|a, b| b.1.cmp(a.1));
196
-
197
- for (rule, count) in file_rule_counts.iter().take(3) {
198
- result.push_str(&format!(" {} ({})\n", rule, count));
199
- }
200
- }
201
-
202
- if file_counts.len() > 10 {
203
- result.push_str(&format!("\n... +{} more files\n", file_counts.len() - 10));
204
- }
205
-
206
- if fixable_count > 0 {
207
- result.push_str(&format!(
208
- "\n💡 Run `ruff check --fix` to auto-fix {} issues\n",
209
- fixable_count
210
- ));
211
- }
212
-
213
- result.trim().to_string()
214
- }
215
-
216
- /// Filter ruff format output - show files that need formatting
217
- pub fn filter_ruff_format(output: &str) -> String {
218
- let mut files_to_format: Vec<String> = Vec::new();
219
- let mut files_checked = 0;
220
-
221
- for line in output.lines() {
222
- let trimmed = line.trim();
223
- let lower = trimmed.to_lowercase();
224
-
225
- // Count "would reformat" lines (check mode) - case insensitive
226
- if lower.contains("would reformat:") {
227
- // Extract filename from "Would reformat: path/to/file.py"
228
- if let Some(filename) = trimmed.split(':').nth(1) {
229
- files_to_format.push(filename.trim().to_string());
230
- }
231
- }
232
-
233
- // Count total checked files - look for patterns like "3 files left unchanged"
234
- if lower.contains("left unchanged") {
235
- // Find "X file(s) left unchanged" pattern specifically
236
- // Split by comma to handle "2 files would be reformatted, 3 files left unchanged"
237
- let parts: Vec<&str> = trimmed.split(',').collect();
238
- for part in parts {
239
- let part_lower = part.to_lowercase();
240
- if part_lower.contains("left unchanged") {
241
- let words: Vec<&str> = part.trim().split_whitespace().collect();
242
- // Look for number before "file" or "files"
243
- for (i, word) in words.iter().enumerate() {
244
- if (word == &"file" || word == &"files") && i > 0 {
245
- if let Ok(count) = words[i - 1].parse::<usize>() {
246
- files_checked = count;
247
- break;
248
- }
249
- }
250
- }
251
- break;
252
- }
253
- }
254
- }
255
- }
256
-
257
- let output_lower = output.to_lowercase();
258
-
259
- // Check if all files are formatted
260
- if files_to_format.is_empty() && output_lower.contains("left unchanged") {
261
- return "✓ Ruff format: All files formatted correctly".to_string();
262
- }
263
-
264
- let mut result = String::new();
265
-
266
- if output_lower.contains("would reformat") {
267
- // Check mode: show files that need formatting
268
- if files_to_format.is_empty() {
269
- result.push_str("✓ Ruff format: All files formatted correctly\n");
270
- } else {
271
- result.push_str(&format!(
272
- "Ruff format: {} files need formatting\n",
273
- files_to_format.len()
274
- ));
275
- result.push_str("═══════════════════════════════════════\n");
276
-
277
- for (i, file) in files_to_format.iter().take(10).enumerate() {
278
- result.push_str(&format!("{}. {}\n", i + 1, compact_path(file)));
279
- }
280
-
281
- if files_to_format.len() > 10 {
282
- result.push_str(&format!(
283
- "\n... +{} more files\n",
284
- files_to_format.len() - 10
285
- ));
286
- }
287
-
288
- if files_checked > 0 {
289
- result.push_str(&format!("\n✓ {} files already formatted\n", files_checked));
290
- }
291
-
292
- result.push_str("\n💡 Run `ruff format` to format these files\n");
293
- }
294
- } else {
295
- // Write mode or other output - show summary
296
- result.push_str(output.trim());
297
- }
298
-
299
- result.trim().to_string()
300
- }
301
-
302
- /// Compact file path (remove common prefixes)
303
- fn compact_path(path: &str) -> String {
304
- let path = path.replace('\\', "/");
305
-
306
- if let Some(pos) = path.rfind("/src/") {
307
- format!("src/{}", &path[pos + 5..])
308
- } else if let Some(pos) = path.rfind("/lib/") {
309
- format!("lib/{}", &path[pos + 5..])
310
- } else if let Some(pos) = path.rfind("/tests/") {
311
- format!("tests/{}", &path[pos + 7..])
312
- } else if let Some(pos) = path.rfind('/') {
313
- path[pos + 1..].to_string()
314
- } else {
315
- path
316
- }
317
- }
318
-
319
- #[cfg(test)]
320
- mod tests {
321
- use super::*;
322
-
323
- #[test]
324
- fn test_filter_ruff_check_no_issues() {
325
- let output = "[]";
326
- let result = filter_ruff_check_json(output);
327
- assert!(result.contains("✓ Ruff"));
328
- assert!(result.contains("No issues found"));
329
- }
330
-
331
- #[test]
332
- fn test_filter_ruff_check_with_issues() {
333
- let output = r#"[
334
- {
335
- "code": "F401",
336
- "message": "`os` imported but unused",
337
- "location": {"row": 1, "column": 8},
338
- "end_location": {"row": 1, "column": 10},
339
- "filename": "src/main.py",
340
- "fix": {"applicability": "safe"}
341
- },
342
- {
343
- "code": "F401",
344
- "message": "`sys` imported but unused",
345
- "location": {"row": 2, "column": 8},
346
- "end_location": {"row": 2, "column": 11},
347
- "filename": "src/main.py",
348
- "fix": null
349
- },
350
- {
351
- "code": "E501",
352
- "message": "Line too long (100 > 88 characters)",
353
- "location": {"row": 10, "column": 89},
354
- "end_location": {"row": 10, "column": 100},
355
- "filename": "src/utils.py",
356
- "fix": null
357
- }
358
- ]"#;
359
- let result = filter_ruff_check_json(output);
360
- assert!(result.contains("3 issues"));
361
- assert!(result.contains("2 files"));
362
- assert!(result.contains("1 fixable"));
363
- assert!(result.contains("F401"));
364
- assert!(result.contains("E501"));
365
- assert!(result.contains("main.py"));
366
- assert!(result.contains("utils.py"));
367
- }
368
-
369
- #[test]
370
- fn test_filter_ruff_format_all_formatted() {
371
- let output = "5 files left unchanged";
372
- let result = filter_ruff_format(output);
373
- assert!(result.contains("✓ Ruff format"));
374
- assert!(result.contains("All files formatted correctly"));
375
- }
376
-
377
- #[test]
378
- fn test_filter_ruff_format_needs_formatting() {
379
- let output = r#"Would reformat: src/main.py
380
- Would reformat: tests/test_utils.py
381
- 2 files would be reformatted, 3 files left unchanged"#;
382
- let result = filter_ruff_format(output);
383
- assert!(result.contains("2 files need formatting"));
384
- assert!(result.contains("main.py"));
385
- assert!(result.contains("test_utils.py"));
386
- assert!(result.contains("3 files already formatted"));
387
- }
388
-
389
- #[test]
390
- fn test_compact_path() {
391
- assert_eq!(
392
- compact_path("/Users/foo/project/src/main.py"),
393
- "src/main.py"
394
- );
395
- assert_eq!(compact_path("/home/user/app/lib/utils.py"), "lib/utils.py");
396
- assert_eq!(
397
- compact_path("C:\\Users\\foo\\project\\tests\\test.py"),
398
- "tests/test.py"
399
- );
400
- assert_eq!(compact_path("relative/file.py"), "file.py");
401
- }
402
- }
@@ -1,271 +0,0 @@
1
- use crate::tracking;
2
- use anyhow::{Context, Result};
3
- use regex::Regex;
4
- use std::process::{Command, Stdio};
5
-
6
- /// Run a command and filter output to show only errors/warnings
7
- pub fn run_err(command: &str, verbose: u8) -> Result<()> {
8
- let timer = tracking::TimedExecution::start();
9
-
10
- if verbose > 0 {
11
- eprintln!("Running: {}", command);
12
- }
13
-
14
- let output = if cfg!(target_os = "windows") {
15
- Command::new("cmd")
16
- .args(["/C", command])
17
- .stdout(Stdio::piped())
18
- .stderr(Stdio::piped())
19
- .output()
20
- } else {
21
- Command::new("sh")
22
- .args(["-c", command])
23
- .stdout(Stdio::piped())
24
- .stderr(Stdio::piped())
25
- .output()
26
- }
27
- .context("Failed to execute command")?;
28
-
29
- let stdout = String::from_utf8_lossy(&output.stdout);
30
- let stderr = String::from_utf8_lossy(&output.stderr);
31
- let raw = format!("{}\n{}", stdout, stderr);
32
- let filtered = filter_errors(&raw);
33
- let mut rtk = String::new();
34
-
35
- if filtered.is_empty() {
36
- if output.status.success() {
37
- rtk.push_str("✅ Command completed successfully (no errors)");
38
- } else {
39
- rtk.push_str(&format!(
40
- "❌ Command failed (exit code: {:?})\n",
41
- output.status.code()
42
- ));
43
- let lines: Vec<&str> = raw.lines().collect();
44
- for line in lines.iter().rev().take(10).rev() {
45
- rtk.push_str(&format!(" {}\n", line));
46
- }
47
- }
48
- } else {
49
- rtk.push_str(&filtered);
50
- }
51
-
52
- let exit_code = output
53
- .status
54
- .code()
55
- .unwrap_or(if output.status.success() { 0 } else { 1 });
56
- if let Some(hint) = crate::tee::tee_and_hint(&raw, "err", exit_code) {
57
- println!("{}\n{}", rtk, hint);
58
- } else {
59
- println!("{}", rtk);
60
- }
61
- timer.track(command, "rtk run-err", &raw, &rtk);
62
- Ok(())
63
- }
64
-
65
- /// Run tests and show only failures
66
- pub fn run_test(command: &str, verbose: u8) -> Result<()> {
67
- let timer = tracking::TimedExecution::start();
68
-
69
- if verbose > 0 {
70
- eprintln!("Running tests: {}", command);
71
- }
72
-
73
- let output = if cfg!(target_os = "windows") {
74
- Command::new("cmd")
75
- .args(["/C", command])
76
- .stdout(Stdio::piped())
77
- .stderr(Stdio::piped())
78
- .output()
79
- } else {
80
- Command::new("sh")
81
- .args(["-c", command])
82
- .stdout(Stdio::piped())
83
- .stderr(Stdio::piped())
84
- .output()
85
- }
86
- .context("Failed to execute test command")?;
87
-
88
- let stdout = String::from_utf8_lossy(&output.stdout);
89
- let stderr = String::from_utf8_lossy(&output.stderr);
90
- let raw = format!("{}\n{}", stdout, stderr);
91
-
92
- let exit_code = output
93
- .status
94
- .code()
95
- .unwrap_or(if output.status.success() { 0 } else { 1 });
96
- let summary = extract_test_summary(&raw, command);
97
- if let Some(hint) = crate::tee::tee_and_hint(&raw, "test", exit_code) {
98
- println!("{}\n{}", summary, hint);
99
- } else {
100
- println!("{}", summary);
101
- }
102
- timer.track(command, "rtk run-test", &raw, &summary);
103
- Ok(())
104
- }
105
-
106
- fn filter_errors(output: &str) -> String {
107
- lazy_static::lazy_static! {
108
- static ref ERROR_PATTERNS: Vec<Regex> = vec![
109
- // Generic errors
110
- Regex::new(r"(?i)^.*error[\s:\[].*$").unwrap(),
111
- Regex::new(r"(?i)^.*\berr\b.*$").unwrap(),
112
- Regex::new(r"(?i)^.*warning[\s:\[].*$").unwrap(),
113
- Regex::new(r"(?i)^.*\bwarn\b.*$").unwrap(),
114
- Regex::new(r"(?i)^.*failed.*$").unwrap(),
115
- Regex::new(r"(?i)^.*failure.*$").unwrap(),
116
- Regex::new(r"(?i)^.*exception.*$").unwrap(),
117
- Regex::new(r"(?i)^.*panic.*$").unwrap(),
118
- // Rust specific
119
- Regex::new(r"^error\[E\d+\]:.*$").unwrap(),
120
- Regex::new(r"^\s*--> .*:\d+:\d+$").unwrap(),
121
- // Python
122
- Regex::new(r"^Traceback.*$").unwrap(),
123
- Regex::new(r#"^\s*File ".*", line \d+.*$"#).unwrap(),
124
- // JavaScript/TypeScript
125
- Regex::new(r"^\s*at .*:\d+:\d+.*$").unwrap(),
126
- // Go
127
- Regex::new(r"^.*\.go:\d+:.*$").unwrap(),
128
- ];
129
- }
130
-
131
- let mut result = Vec::new();
132
- let mut in_error_block = false;
133
- let mut blank_count = 0;
134
-
135
- for line in output.lines() {
136
- let is_error_line = ERROR_PATTERNS.iter().any(|p| p.is_match(line));
137
-
138
- if is_error_line {
139
- in_error_block = true;
140
- blank_count = 0;
141
- result.push(line.to_string());
142
- } else if in_error_block {
143
- if line.trim().is_empty() {
144
- blank_count += 1;
145
- if blank_count >= 2 {
146
- in_error_block = false;
147
- } else {
148
- result.push(line.to_string());
149
- }
150
- } else if line.starts_with(' ') || line.starts_with('\t') {
151
- // Continuation of error
152
- result.push(line.to_string());
153
- blank_count = 0;
154
- } else {
155
- in_error_block = false;
156
- }
157
- }
158
- }
159
-
160
- result.join("\n")
161
- }
162
-
163
- fn extract_test_summary(output: &str, command: &str) -> String {
164
- let mut result = Vec::new();
165
- let lines: Vec<&str> = output.lines().collect();
166
-
167
- // Detect test framework
168
- let is_cargo = command.contains("cargo test");
169
- let is_pytest = command.contains("pytest");
170
- let is_jest =
171
- command.contains("jest") || command.contains("npm test") || command.contains("yarn test");
172
- let is_go = command.contains("go test");
173
-
174
- // Collect failures
175
- let mut failures = Vec::new();
176
- let mut in_failure = false;
177
- let mut failure_lines = Vec::new();
178
-
179
- for line in lines.iter() {
180
- // Cargo test
181
- if is_cargo {
182
- if line.contains("test result:") {
183
- result.push(line.to_string());
184
- }
185
- if line.contains("FAILED") && !line.contains("test result") {
186
- failures.push(line.to_string());
187
- }
188
- if line.starts_with("failures:") {
189
- in_failure = true;
190
- }
191
- if in_failure && line.starts_with(" ") {
192
- failure_lines.push(line.to_string());
193
- }
194
- }
195
-
196
- // Pytest
197
- if is_pytest {
198
- if line.contains(" passed") || line.contains(" failed") || line.contains(" error") {
199
- result.push(line.to_string());
200
- }
201
- if line.contains("FAILED") {
202
- failures.push(line.to_string());
203
- }
204
- }
205
-
206
- // Jest
207
- if is_jest {
208
- if line.contains("Tests:") || line.contains("Test Suites:") {
209
- result.push(line.to_string());
210
- }
211
- if line.contains("✕") || line.contains("FAIL") {
212
- failures.push(line.to_string());
213
- }
214
- }
215
-
216
- // Go test
217
- if is_go {
218
- if line.starts_with("ok") || line.starts_with("FAIL") || line.starts_with("---") {
219
- result.push(line.to_string());
220
- }
221
- if line.contains("FAIL") {
222
- failures.push(line.to_string());
223
- }
224
- }
225
- }
226
-
227
- // Build output
228
- let mut output = String::new();
229
-
230
- if !failures.is_empty() {
231
- output.push_str("❌ FAILURES:\n");
232
- for f in failures.iter().take(10) {
233
- output.push_str(&format!(" {}\n", f));
234
- }
235
- if failures.len() > 10 {
236
- output.push_str(&format!(" ... +{} more failures\n", failures.len() - 10));
237
- }
238
- output.push('\n');
239
- }
240
-
241
- if !result.is_empty() {
242
- output.push_str("📊 SUMMARY:\n");
243
- for r in &result {
244
- output.push_str(&format!(" {}\n", r));
245
- }
246
- } else {
247
- // Fallback: show last few lines
248
- output.push_str("📊 OUTPUT (last 5 lines):\n");
249
- let start = lines.len().saturating_sub(5);
250
- for line in &lines[start..] {
251
- if !line.trim().is_empty() {
252
- output.push_str(&format!(" {}\n", line));
253
- }
254
- }
255
- }
256
-
257
- output
258
- }
259
-
260
- #[cfg(test)]
261
- mod tests {
262
- use super::*;
263
-
264
- #[test]
265
- fn test_filter_errors() {
266
- let output = "info: compiling\nerror: something failed\n at line 10\ninfo: done";
267
- let filtered = filter_errors(output);
268
- assert!(filtered.contains("error"));
269
- assert!(!filtered.contains("info"));
270
- }
271
- }