@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,254 +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 Position {
10
- #[serde(rename = "Filename")]
11
- filename: String,
12
- #[serde(rename = "Line")]
13
- line: usize,
14
- #[serde(rename = "Column")]
15
- column: usize,
16
- }
17
-
18
- #[derive(Debug, Deserialize)]
19
- struct Issue {
20
- #[serde(rename = "FromLinter")]
21
- from_linter: String,
22
- #[serde(rename = "Text")]
23
- text: String,
24
- #[serde(rename = "Pos")]
25
- pos: Position,
26
- }
27
-
28
- #[derive(Debug, Deserialize)]
29
- struct GolangciOutput {
30
- #[serde(rename = "Issues")]
31
- issues: Vec<Issue>,
32
- }
33
-
34
- pub fn run(args: &[String], verbose: u8) -> Result<()> {
35
- let timer = tracking::TimedExecution::start();
36
-
37
- let mut cmd = Command::new("golangci-lint");
38
-
39
- // Force JSON output
40
- let has_format = args
41
- .iter()
42
- .any(|a| a == "--out-format" || a.starts_with("--out-format="));
43
-
44
- if !has_format {
45
- cmd.arg("run").arg("--out-format=json");
46
- } else {
47
- cmd.arg("run");
48
- }
49
-
50
- for arg in args {
51
- cmd.arg(arg);
52
- }
53
-
54
- if verbose > 0 {
55
- eprintln!("Running: golangci-lint run --out-format=json");
56
- }
57
-
58
- let output = cmd.output().context(
59
- "Failed to run golangci-lint. Is it installed? Try: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest",
60
- )?;
61
-
62
- let stdout = String::from_utf8_lossy(&output.stdout);
63
- let stderr = String::from_utf8_lossy(&output.stderr);
64
- let raw = format!("{}\n{}", stdout, stderr);
65
-
66
- let filtered = filter_golangci_json(&stdout);
67
-
68
- println!("{}", filtered);
69
-
70
- // Include stderr if present (config errors, etc.)
71
- if !stderr.trim().is_empty() && verbose > 0 {
72
- eprintln!("{}", stderr.trim());
73
- }
74
-
75
- timer.track(
76
- &format!("golangci-lint {}", args.join(" ")),
77
- &format!("rtk golangci-lint {}", args.join(" ")),
78
- &raw,
79
- &filtered,
80
- );
81
-
82
- // golangci-lint returns exit code 1 when issues found (expected behavior)
83
- // Don't exit with error code in that case
84
- Ok(())
85
- }
86
-
87
- /// Filter golangci-lint JSON output - group by linter and file
88
- fn filter_golangci_json(output: &str) -> String {
89
- let result: Result<GolangciOutput, _> = serde_json::from_str(output);
90
-
91
- let golangci_output = match result {
92
- Ok(o) => o,
93
- Err(e) => {
94
- // Fallback if JSON parsing fails
95
- return format!(
96
- "golangci-lint (JSON parse failed: {})\n{}",
97
- e,
98
- truncate(output, 500)
99
- );
100
- }
101
- };
102
-
103
- let issues = golangci_output.issues;
104
-
105
- if issues.is_empty() {
106
- return "✓ golangci-lint: No issues found".to_string();
107
- }
108
-
109
- let total_issues = issues.len();
110
-
111
- // Count unique files
112
- let unique_files: std::collections::HashSet<_> =
113
- issues.iter().map(|i| &i.pos.filename).collect();
114
- let total_files = unique_files.len();
115
-
116
- // Group by linter
117
- let mut by_linter: HashMap<String, usize> = HashMap::new();
118
- for issue in &issues {
119
- *by_linter.entry(issue.from_linter.clone()).or_insert(0) += 1;
120
- }
121
-
122
- // Group by file
123
- let mut by_file: HashMap<&str, usize> = HashMap::new();
124
- for issue in &issues {
125
- *by_file.entry(&issue.pos.filename).or_insert(0) += 1;
126
- }
127
-
128
- let mut file_counts: Vec<_> = by_file.iter().collect();
129
- file_counts.sort_by(|a, b| b.1.cmp(a.1));
130
-
131
- // Build output
132
- let mut result = String::new();
133
- result.push_str(&format!(
134
- "golangci-lint: {} issues in {} files\n",
135
- total_issues, total_files
136
- ));
137
- result.push_str("═══════════════════════════════════════\n");
138
-
139
- // Show top linters
140
- let mut linter_counts: Vec<_> = by_linter.iter().collect();
141
- linter_counts.sort_by(|a, b| b.1.cmp(a.1));
142
-
143
- if !linter_counts.is_empty() {
144
- result.push_str("Top linters:\n");
145
- for (linter, count) in linter_counts.iter().take(10) {
146
- result.push_str(&format!(" {} ({}x)\n", linter, count));
147
- }
148
- result.push('\n');
149
- }
150
-
151
- // Show top files
152
- result.push_str("Top files:\n");
153
- for (file, count) in file_counts.iter().take(10) {
154
- let short_path = compact_path(file);
155
- result.push_str(&format!(" {} ({} issues)\n", short_path, count));
156
-
157
- // Show top 3 linters in this file
158
- let mut file_linters: HashMap<String, usize> = HashMap::new();
159
- for issue in issues.iter().filter(|i| &i.pos.filename == *file) {
160
- *file_linters.entry(issue.from_linter.clone()).or_insert(0) += 1;
161
- }
162
-
163
- let mut file_linter_counts: Vec<_> = file_linters.iter().collect();
164
- file_linter_counts.sort_by(|a, b| b.1.cmp(a.1));
165
-
166
- for (linter, count) in file_linter_counts.iter().take(3) {
167
- result.push_str(&format!(" {} ({})\n", linter, count));
168
- }
169
- }
170
-
171
- if file_counts.len() > 10 {
172
- result.push_str(&format!("\n... +{} more files\n", file_counts.len() - 10));
173
- }
174
-
175
- result.trim().to_string()
176
- }
177
-
178
- /// Compact file path (remove common prefixes)
179
- fn compact_path(path: &str) -> String {
180
- let path = path.replace('\\', "/");
181
-
182
- if let Some(pos) = path.rfind("/pkg/") {
183
- format!("pkg/{}", &path[pos + 5..])
184
- } else if let Some(pos) = path.rfind("/cmd/") {
185
- format!("cmd/{}", &path[pos + 5..])
186
- } else if let Some(pos) = path.rfind("/internal/") {
187
- format!("internal/{}", &path[pos + 10..])
188
- } else if let Some(pos) = path.rfind('/') {
189
- path[pos + 1..].to_string()
190
- } else {
191
- path
192
- }
193
- }
194
-
195
- #[cfg(test)]
196
- mod tests {
197
- use super::*;
198
-
199
- #[test]
200
- fn test_filter_golangci_no_issues() {
201
- let output = r#"{"Issues":[]}"#;
202
- let result = filter_golangci_json(output);
203
- assert!(result.contains("✓ golangci-lint"));
204
- assert!(result.contains("No issues found"));
205
- }
206
-
207
- #[test]
208
- fn test_filter_golangci_with_issues() {
209
- let output = r#"{
210
- "Issues": [
211
- {
212
- "FromLinter": "errcheck",
213
- "Text": "Error return value not checked",
214
- "Pos": {"Filename": "main.go", "Line": 42, "Column": 5}
215
- },
216
- {
217
- "FromLinter": "errcheck",
218
- "Text": "Error return value not checked",
219
- "Pos": {"Filename": "main.go", "Line": 50, "Column": 10}
220
- },
221
- {
222
- "FromLinter": "gosimple",
223
- "Text": "Should use strings.Contains",
224
- "Pos": {"Filename": "utils.go", "Line": 15, "Column": 2}
225
- }
226
- ]
227
- }"#;
228
-
229
- let result = filter_golangci_json(output);
230
- assert!(result.contains("3 issues"));
231
- assert!(result.contains("2 files"));
232
- assert!(result.contains("errcheck"));
233
- assert!(result.contains("gosimple"));
234
- assert!(result.contains("main.go"));
235
- assert!(result.contains("utils.go"));
236
- }
237
-
238
- #[test]
239
- fn test_compact_path() {
240
- assert_eq!(
241
- compact_path("/Users/foo/project/pkg/handler/server.go"),
242
- "pkg/handler/server.go"
243
- );
244
- assert_eq!(
245
- compact_path("/home/user/app/cmd/main/main.go"),
246
- "cmd/main/main.go"
247
- );
248
- assert_eq!(
249
- compact_path("/project/internal/config/loader.go"),
250
- "internal/config/loader.go"
251
- );
252
- assert_eq!(compact_path("relative/file.go"), "file.go");
253
- }
254
- }
@@ -1,288 +0,0 @@
1
- use crate::tracking;
2
- use anyhow::{Context, Result};
3
- use regex::Regex;
4
- use std::collections::HashMap;
5
- use std::process::Command;
6
-
7
- pub fn run(
8
- pattern: &str,
9
- path: &str,
10
- max_line_len: usize,
11
- max_results: usize,
12
- context_only: bool,
13
- file_type: Option<&str>,
14
- extra_args: &[String],
15
- verbose: u8,
16
- ) -> Result<()> {
17
- let timer = tracking::TimedExecution::start();
18
-
19
- if verbose > 0 {
20
- eprintln!("grep: '{}' in {}", pattern, path);
21
- }
22
-
23
- // Fix: convert BRE alternation \| → | for rg (which uses PCRE-style regex)
24
- let rg_pattern = pattern.replace(r"\|", "|");
25
-
26
- let mut rg_cmd = Command::new("rg");
27
- rg_cmd.args(["-n", "--no-heading", &rg_pattern, path]);
28
-
29
- if let Some(ft) = file_type {
30
- rg_cmd.arg("--type").arg(ft);
31
- }
32
-
33
- for arg in extra_args {
34
- // Fix: skip grep-ism -r flag (rg is recursive by default; rg -r means --replace)
35
- if arg == "-r" || arg == "--recursive" {
36
- continue;
37
- }
38
- rg_cmd.arg(arg);
39
- }
40
-
41
- let output = rg_cmd
42
- .output()
43
- .or_else(|_| Command::new("grep").args(["-rn", pattern, path]).output())
44
- .context("grep/rg failed")?;
45
-
46
- let stdout = String::from_utf8_lossy(&output.stdout);
47
- let exit_code = output.status.code().unwrap_or(1);
48
-
49
- let raw_output = stdout.to_string();
50
-
51
- if stdout.trim().is_empty() {
52
- // Show stderr for errors (bad regex, missing file, etc.)
53
- if exit_code == 2 {
54
- let stderr = String::from_utf8_lossy(&output.stderr);
55
- if !stderr.trim().is_empty() {
56
- eprintln!("{}", stderr.trim());
57
- }
58
- }
59
- let msg = format!("🔍 0 for '{}'", pattern);
60
- println!("{}", msg);
61
- timer.track(
62
- &format!("grep -rn '{}' {}", pattern, path),
63
- "rtk grep",
64
- &raw_output,
65
- &msg,
66
- );
67
- if exit_code != 0 {
68
- std::process::exit(exit_code);
69
- }
70
- return Ok(());
71
- }
72
-
73
- let mut by_file: HashMap<String, Vec<(usize, String)>> = HashMap::new();
74
- let mut total = 0;
75
-
76
- for line in stdout.lines() {
77
- let parts: Vec<&str> = line.splitn(3, ':').collect();
78
-
79
- let (file, line_num, content) = if parts.len() == 3 {
80
- let ln = parts[1].parse().unwrap_or(0);
81
- (parts[0].to_string(), ln, parts[2])
82
- } else if parts.len() == 2 {
83
- let ln = parts[0].parse().unwrap_or(0);
84
- (path.to_string(), ln, parts[1])
85
- } else {
86
- continue;
87
- };
88
-
89
- total += 1;
90
- let cleaned = clean_line(content, max_line_len, context_only, pattern);
91
- by_file.entry(file).or_default().push((line_num, cleaned));
92
- }
93
-
94
- let mut rtk_output = String::new();
95
- rtk_output.push_str(&format!("🔍 {} in {}F:\n\n", total, by_file.len()));
96
-
97
- let mut shown = 0;
98
- let mut files: Vec<_> = by_file.iter().collect();
99
- files.sort_by_key(|(f, _)| *f);
100
-
101
- for (file, matches) in files {
102
- if shown >= max_results {
103
- break;
104
- }
105
-
106
- let file_display = compact_path(file);
107
- rtk_output.push_str(&format!("📄 {} ({}):\n", file_display, matches.len()));
108
-
109
- for (line_num, content) in matches.iter().take(10) {
110
- rtk_output.push_str(&format!(" {:>4}: {}\n", line_num, content));
111
- shown += 1;
112
- if shown >= max_results {
113
- break;
114
- }
115
- }
116
-
117
- if matches.len() > 10 {
118
- rtk_output.push_str(&format!(" +{}\n", matches.len() - 10));
119
- }
120
- rtk_output.push('\n');
121
- }
122
-
123
- if total > shown {
124
- rtk_output.push_str(&format!("... +{}\n", total - shown));
125
- }
126
-
127
- print!("{}", rtk_output);
128
- timer.track(
129
- &format!("grep -rn '{}' {}", pattern, path),
130
- "rtk grep",
131
- &raw_output,
132
- &rtk_output,
133
- );
134
-
135
- if exit_code != 0 {
136
- std::process::exit(exit_code);
137
- }
138
-
139
- Ok(())
140
- }
141
-
142
- fn clean_line(line: &str, max_len: usize, context_only: bool, pattern: &str) -> String {
143
- let trimmed = line.trim();
144
-
145
- if context_only {
146
- if let Ok(re) = Regex::new(&format!("(?i).{{0,20}}{}.*", regex::escape(pattern))) {
147
- if let Some(m) = re.find(trimmed) {
148
- let matched = m.as_str();
149
- if matched.len() <= max_len {
150
- return matched.to_string();
151
- }
152
- }
153
- }
154
- }
155
-
156
- if trimmed.len() <= max_len {
157
- trimmed.to_string()
158
- } else {
159
- let lower = trimmed.to_lowercase();
160
- let pattern_lower = pattern.to_lowercase();
161
-
162
- if let Some(pos) = lower.find(&pattern_lower) {
163
- let char_pos = lower[..pos].chars().count();
164
- let chars: Vec<char> = trimmed.chars().collect();
165
- let char_len = chars.len();
166
-
167
- let start = char_pos.saturating_sub(max_len / 3);
168
- let end = (start + max_len).min(char_len);
169
- let start = if end == char_len {
170
- end.saturating_sub(max_len)
171
- } else {
172
- start
173
- };
174
-
175
- let slice: String = chars[start..end].iter().collect();
176
- if start > 0 && end < char_len {
177
- format!("...{}...", slice)
178
- } else if start > 0 {
179
- format!("...{}", slice)
180
- } else {
181
- format!("{}...", slice)
182
- }
183
- } else {
184
- let t: String = trimmed.chars().take(max_len - 3).collect();
185
- format!("{}...", t)
186
- }
187
- }
188
- }
189
-
190
- fn compact_path(path: &str) -> String {
191
- if path.len() <= 50 {
192
- return path.to_string();
193
- }
194
-
195
- let parts: Vec<&str> = path.split('/').collect();
196
- if parts.len() <= 3 {
197
- return path.to_string();
198
- }
199
-
200
- format!(
201
- "{}/.../{}/{}",
202
- parts[0],
203
- parts[parts.len() - 2],
204
- parts[parts.len() - 1]
205
- )
206
- }
207
-
208
- #[cfg(test)]
209
- mod tests {
210
- use super::*;
211
-
212
- #[test]
213
- fn test_clean_line() {
214
- let line = " const result = someFunction();";
215
- let cleaned = clean_line(line, 50, false, "result");
216
- assert!(!cleaned.starts_with(' '));
217
- assert!(cleaned.len() <= 50);
218
- }
219
-
220
- #[test]
221
- fn test_compact_path() {
222
- let path = "/Users/patrick/dev/project/src/components/Button.tsx";
223
- let compact = compact_path(path);
224
- assert!(compact.len() <= 60);
225
- }
226
-
227
- #[test]
228
- fn test_extra_args_accepted() {
229
- // Test that the function signature accepts extra_args
230
- // This is a compile-time test - if it compiles, the signature is correct
231
- let _extra: Vec<String> = vec!["-i".to_string(), "-A".to_string(), "3".to_string()];
232
- // No need to actually run - we're verifying the parameter exists
233
- }
234
-
235
- #[test]
236
- fn test_clean_line_multibyte() {
237
- // Thai text that exceeds max_len in bytes
238
- let line = " สวัสดีครับ นี่คือข้อความที่ยาวมากสำหรับทดสอบ ";
239
- let cleaned = clean_line(line, 20, false, "ครับ");
240
- // Should not panic
241
- assert!(!cleaned.is_empty());
242
- }
243
-
244
- #[test]
245
- fn test_clean_line_emoji() {
246
- let line = "🎉🎊🎈🎁🎂🎄 some text 🎃🎆🎇✨";
247
- let cleaned = clean_line(line, 15, false, "text");
248
- assert!(!cleaned.is_empty());
249
- }
250
-
251
- // Fix: BRE \| alternation is translated to PCRE | for rg
252
- #[test]
253
- fn test_bre_alternation_translated() {
254
- let pattern = r"fn foo\|pub.*bar";
255
- let rg_pattern = pattern.replace(r"\|", "|");
256
- assert_eq!(rg_pattern, "fn foo|pub.*bar");
257
- }
258
-
259
- // Fix: -r flag (grep recursive) is stripped from extra_args (rg is recursive by default)
260
- #[test]
261
- fn test_recursive_flag_stripped() {
262
- let extra_args: Vec<String> = vec!["-r".to_string(), "-i".to_string()];
263
- let filtered: Vec<&String> = extra_args
264
- .iter()
265
- .filter(|a| *a != "-r" && *a != "--recursive")
266
- .collect();
267
- assert_eq!(filtered.len(), 1);
268
- assert_eq!(filtered[0], "-i");
269
- }
270
-
271
- // Verify line numbers are always enabled in rg invocation (grep_cmd.rs:24).
272
- // The -n/--line-numbers clap flag in main.rs is a no-op accepted for compat.
273
- #[test]
274
- fn test_rg_always_has_line_numbers() {
275
- // grep_cmd::run() always passes "-n" to rg (line 24).
276
- // This test documents that -n is built-in, so the clap flag is safe to ignore.
277
- let mut cmd = std::process::Command::new("rg");
278
- cmd.args(["-n", "--no-heading", "NONEXISTENT_PATTERN_12345", "."]);
279
- // If rg is available, it should accept -n without error (exit 1 = no match, not error)
280
- if let Ok(output) = cmd.output() {
281
- assert!(
282
- output.status.code() == Some(1) || output.status.success(),
283
- "rg -n should be accepted"
284
- );
285
- }
286
- // If rg is not installed, skip gracefully (test still passes)
287
- }
288
- }