@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,336 +0,0 @@
1
- /// Token-efficient formatting trait for canonical types
2
- use super::types::*;
3
-
4
- /// Output formatting modes
5
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
- pub enum FormatMode {
7
- /// Ultra-compact: Summary only (default)
8
- Compact,
9
- /// Verbose: Include details
10
- Verbose,
11
- /// Ultra-compressed: Symbols and abbreviations
12
- Ultra,
13
- }
14
-
15
- impl FormatMode {
16
- pub fn from_verbosity(verbosity: u8) -> Self {
17
- match verbosity {
18
- 0 => FormatMode::Compact,
19
- 1 => FormatMode::Verbose,
20
- _ => FormatMode::Ultra,
21
- }
22
- }
23
- }
24
-
25
- /// Trait for formatting canonical types into token-efficient strings
26
- pub trait TokenFormatter {
27
- /// Format as compact summary (default)
28
- fn format_compact(&self) -> String;
29
-
30
- /// Format with details (verbose mode)
31
- fn format_verbose(&self) -> String;
32
-
33
- /// Format with symbols (ultra-compressed mode)
34
- fn format_ultra(&self) -> String;
35
-
36
- /// Format according to mode
37
- fn format(&self, mode: FormatMode) -> String {
38
- match mode {
39
- FormatMode::Compact => self.format_compact(),
40
- FormatMode::Verbose => self.format_verbose(),
41
- FormatMode::Ultra => self.format_ultra(),
42
- }
43
- }
44
- }
45
-
46
- impl TokenFormatter for TestResult {
47
- fn format_compact(&self) -> String {
48
- let mut lines = vec![format!("PASS ({}) FAIL ({})", self.passed, self.failed)];
49
-
50
- if !self.failures.is_empty() {
51
- lines.push(String::new());
52
- for (idx, failure) in self.failures.iter().enumerate().take(5) {
53
- lines.push(format!("{}. {}", idx + 1, failure.test_name));
54
- let error_preview: String = failure
55
- .error_message
56
- .lines()
57
- .take(2)
58
- .collect::<Vec<_>>()
59
- .join(" ");
60
- lines.push(format!(" {}", error_preview));
61
- }
62
-
63
- if self.failures.len() > 5 {
64
- lines.push(format!("\n... +{} more failures", self.failures.len() - 5));
65
- }
66
- }
67
-
68
- if let Some(duration) = self.duration_ms {
69
- lines.push(format!("\nTime: {}ms", duration));
70
- }
71
-
72
- lines.join("\n")
73
- }
74
-
75
- fn format_verbose(&self) -> String {
76
- let mut lines = vec![format!(
77
- "Tests: {} passed, {} failed, {} skipped (total: {})",
78
- self.passed, self.failed, self.skipped, self.total
79
- )];
80
-
81
- if !self.failures.is_empty() {
82
- lines.push("\nFailures:".to_string());
83
- for (idx, failure) in self.failures.iter().enumerate() {
84
- lines.push(format!(
85
- "\n{}. {} ({})",
86
- idx + 1,
87
- failure.test_name,
88
- failure.file_path
89
- ));
90
- lines.push(format!(" {}", failure.error_message));
91
- if let Some(stack) = &failure.stack_trace {
92
- let stack_preview: String =
93
- stack.lines().take(3).collect::<Vec<_>>().join("\n ");
94
- lines.push(format!(" {}", stack_preview));
95
- }
96
- }
97
- }
98
-
99
- if let Some(duration) = self.duration_ms {
100
- lines.push(format!("\nDuration: {}ms", duration));
101
- }
102
-
103
- lines.join("\n")
104
- }
105
-
106
- fn format_ultra(&self) -> String {
107
- format!(
108
- "✓{} ✗{} ⊘{} ({}ms)",
109
- self.passed,
110
- self.failed,
111
- self.skipped,
112
- self.duration_ms.unwrap_or(0)
113
- )
114
- }
115
- }
116
-
117
- impl TokenFormatter for LintResult {
118
- fn format_compact(&self) -> String {
119
- let mut lines = vec![format!(
120
- "Errors: {} | Warnings: {} | Files: {}",
121
- self.errors, self.warnings, self.files_with_issues
122
- )];
123
-
124
- if !self.issues.is_empty() {
125
- // Group by rule_id
126
- let mut by_rule: std::collections::HashMap<String, Vec<&LintIssue>> =
127
- std::collections::HashMap::new();
128
- for issue in &self.issues {
129
- by_rule
130
- .entry(issue.rule_id.clone())
131
- .or_default()
132
- .push(issue);
133
- }
134
-
135
- let mut rules: Vec<_> = by_rule.iter().collect();
136
- rules.sort_by_key(|(_, issues)| std::cmp::Reverse(issues.len()));
137
-
138
- lines.push(String::new());
139
- for (rule, issues) in rules.iter().take(5) {
140
- lines.push(format!("{}: {} occurrences", rule, issues.len()));
141
- for issue in issues.iter().take(2) {
142
- lines.push(format!(" {}:{}", issue.file_path, issue.line));
143
- }
144
- }
145
-
146
- if by_rule.len() > 5 {
147
- lines.push(format!("\n... +{} more rule violations", by_rule.len() - 5));
148
- }
149
- }
150
-
151
- lines.join("\n")
152
- }
153
-
154
- fn format_verbose(&self) -> String {
155
- let mut lines = vec![format!(
156
- "Total issues: {} ({} errors, {} warnings) in {} files",
157
- self.total_issues, self.errors, self.warnings, self.files_with_issues
158
- )];
159
-
160
- if !self.issues.is_empty() {
161
- lines.push("\nIssues:".to_string());
162
- for issue in self.issues.iter().take(20) {
163
- let severity_symbol = match issue.severity {
164
- LintSeverity::Error => "✗",
165
- LintSeverity::Warning => "⚠",
166
- LintSeverity::Info => "ℹ",
167
- };
168
- lines.push(format!(
169
- "{} {}:{}:{} [{}] {}",
170
- severity_symbol,
171
- issue.file_path,
172
- issue.line,
173
- issue.column,
174
- issue.rule_id,
175
- issue.message
176
- ));
177
- }
178
-
179
- if self.issues.len() > 20 {
180
- lines.push(format!("\n... +{} more issues", self.issues.len() - 20));
181
- }
182
- }
183
-
184
- lines.join("\n")
185
- }
186
-
187
- fn format_ultra(&self) -> String {
188
- format!(
189
- "✗{} ⚠{} 📁{}",
190
- self.errors, self.warnings, self.files_with_issues
191
- )
192
- }
193
- }
194
-
195
- impl TokenFormatter for DependencyState {
196
- fn format_compact(&self) -> String {
197
- if self.outdated_count == 0 {
198
- return "All packages up-to-date ✓".to_string();
199
- }
200
-
201
- let mut lines = vec![format!(
202
- "{} outdated packages (of {})",
203
- self.outdated_count, self.total_packages
204
- )];
205
-
206
- for dep in self.dependencies.iter().take(10) {
207
- if let Some(latest) = &dep.latest_version {
208
- if &dep.current_version != latest {
209
- lines.push(format!(
210
- "{}: {} → {}",
211
- dep.name, dep.current_version, latest
212
- ));
213
- }
214
- }
215
- }
216
-
217
- if self.outdated_count > 10 {
218
- lines.push(format!("\n... +{} more", self.outdated_count - 10));
219
- }
220
-
221
- lines.join("\n")
222
- }
223
-
224
- fn format_verbose(&self) -> String {
225
- let mut lines = vec![format!(
226
- "Total packages: {} ({} outdated)",
227
- self.total_packages, self.outdated_count
228
- )];
229
-
230
- if self.outdated_count > 0 {
231
- lines.push("\nOutdated packages:".to_string());
232
- for dep in &self.dependencies {
233
- if let Some(latest) = &dep.latest_version {
234
- if &dep.current_version != latest {
235
- let dev_marker = if dep.dev_dependency { " (dev)" } else { "" };
236
- lines.push(format!(
237
- " {}: {} → {}{}",
238
- dep.name, dep.current_version, latest, dev_marker
239
- ));
240
- if let Some(wanted) = &dep.wanted_version {
241
- if wanted != latest {
242
- lines.push(format!(" (wanted: {})", wanted));
243
- }
244
- }
245
- }
246
- }
247
- }
248
- }
249
-
250
- lines.join("\n")
251
- }
252
-
253
- fn format_ultra(&self) -> String {
254
- format!("📦{} ⬆️{}", self.total_packages, self.outdated_count)
255
- }
256
- }
257
-
258
- impl TokenFormatter for BuildOutput {
259
- fn format_compact(&self) -> String {
260
- let status = if self.success { "✓" } else { "✗" };
261
- let mut lines = vec![format!(
262
- "{} Build: {} errors, {} warnings",
263
- status, self.errors, self.warnings
264
- )];
265
-
266
- if !self.bundles.is_empty() {
267
- let total_size: u64 = self.bundles.iter().map(|b| b.size_bytes).sum();
268
- lines.push(format!(
269
- "Bundles: {} ({:.1} KB)",
270
- self.bundles.len(),
271
- total_size as f64 / 1024.0
272
- ));
273
- }
274
-
275
- if !self.routes.is_empty() {
276
- lines.push(format!("Routes: {}", self.routes.len()));
277
- }
278
-
279
- if let Some(duration) = self.duration_ms {
280
- lines.push(format!("Time: {}ms", duration));
281
- }
282
-
283
- lines.join("\n")
284
- }
285
-
286
- fn format_verbose(&self) -> String {
287
- let status = if self.success { "Success" } else { "Failed" };
288
- let mut lines = vec![format!(
289
- "Build {}: {} errors, {} warnings",
290
- status, self.errors, self.warnings
291
- )];
292
-
293
- if !self.bundles.is_empty() {
294
- lines.push("\nBundles:".to_string());
295
- for bundle in &self.bundles {
296
- let gzip_info = bundle
297
- .gzip_size_bytes
298
- .map(|gz| format!(" (gzip: {:.1} KB)", gz as f64 / 1024.0))
299
- .unwrap_or_default();
300
- lines.push(format!(
301
- " {}: {:.1} KB{}",
302
- bundle.name,
303
- bundle.size_bytes as f64 / 1024.0,
304
- gzip_info
305
- ));
306
- }
307
- }
308
-
309
- if !self.routes.is_empty() {
310
- lines.push("\nRoutes:".to_string());
311
- for route in self.routes.iter().take(10) {
312
- lines.push(format!(" {}: {:.1} KB", route.path, route.size_kb));
313
- }
314
- if self.routes.len() > 10 {
315
- lines.push(format!(" ... +{} more routes", self.routes.len() - 10));
316
- }
317
- }
318
-
319
- if let Some(duration) = self.duration_ms {
320
- lines.push(format!("\nDuration: {}ms", duration));
321
- }
322
-
323
- lines.join("\n")
324
- }
325
-
326
- fn format_ultra(&self) -> String {
327
- let status = if self.success { "✓" } else { "✗" };
328
- format!(
329
- "{} ✗{} ⚠{} ({}ms)",
330
- status,
331
- self.errors,
332
- self.warnings,
333
- self.duration_ms.unwrap_or(0)
334
- )
335
- }
336
- }
@@ -1,311 +0,0 @@
1
- //! Parser infrastructure for tool output transformation
2
- //!
3
- //! This module provides a unified interface for parsing tool outputs with graceful degradation:
4
- //! - Tier 1 (Full): Complete JSON parsing with all fields
5
- //! - Tier 2 (Degraded): Partial parsing with warnings
6
- //! - Tier 3 (Passthrough): Raw output truncation with error marker
7
- //!
8
- //! The three-tier system ensures RTK never returns false data silently.
9
-
10
- pub mod error;
11
- pub mod formatter;
12
- pub mod types;
13
-
14
- pub use formatter::{FormatMode, TokenFormatter};
15
- pub use types::*;
16
-
17
- /// Parse result with degradation tier
18
- #[derive(Debug)]
19
- pub enum ParseResult<T> {
20
- /// Tier 1: Full parse with complete structured data
21
- Full(T),
22
-
23
- /// Tier 2: Degraded parse with partial data and warnings
24
- Degraded(T, Vec<String>),
25
-
26
- /// Tier 3: Passthrough - parsing failed, returning truncated raw output
27
- Passthrough(String),
28
- }
29
-
30
- impl<T> ParseResult<T> {
31
- /// Unwrap the parsed data, panicking on Passthrough
32
- pub fn unwrap(self) -> T {
33
- match self {
34
- ParseResult::Full(data) => data,
35
- ParseResult::Degraded(data, _) => data,
36
- ParseResult::Passthrough(_) => panic!("Called unwrap on Passthrough result"),
37
- }
38
- }
39
-
40
- /// Get the tier level (1 = Full, 2 = Degraded, 3 = Passthrough)
41
- pub fn tier(&self) -> u8 {
42
- match self {
43
- ParseResult::Full(_) => 1,
44
- ParseResult::Degraded(_, _) => 2,
45
- ParseResult::Passthrough(_) => 3,
46
- }
47
- }
48
-
49
- /// Check if parsing succeeded (Full or Degraded)
50
- pub fn is_ok(&self) -> bool {
51
- !matches!(self, ParseResult::Passthrough(_))
52
- }
53
-
54
- /// Map the parsed data while preserving tier
55
- pub fn map<U, F>(self, f: F) -> ParseResult<U>
56
- where
57
- F: FnOnce(T) -> U,
58
- {
59
- match self {
60
- ParseResult::Full(data) => ParseResult::Full(f(data)),
61
- ParseResult::Degraded(data, warnings) => ParseResult::Degraded(f(data), warnings),
62
- ParseResult::Passthrough(raw) => ParseResult::Passthrough(raw),
63
- }
64
- }
65
-
66
- /// Get warnings if Degraded tier
67
- pub fn warnings(&self) -> Vec<String> {
68
- match self {
69
- ParseResult::Degraded(_, warnings) => warnings.clone(),
70
- _ => vec![],
71
- }
72
- }
73
- }
74
-
75
- /// Unified parser trait for tool outputs
76
- pub trait OutputParser: Sized {
77
- type Output;
78
-
79
- /// Parse raw output into structured format
80
- ///
81
- /// Implementation should follow three-tier fallback:
82
- /// 1. Try JSON parsing (if tool supports --json/--format json)
83
- /// 2. Try regex/text extraction with partial data
84
- /// 3. Return truncated passthrough with `[RTK:PASSTHROUGH]` marker
85
- fn parse(input: &str) -> ParseResult<Self::Output>;
86
-
87
- /// Parse with explicit tier preference (for testing/debugging)
88
- fn parse_with_tier(input: &str, max_tier: u8) -> ParseResult<Self::Output> {
89
- let result = Self::parse(input);
90
- if result.tier() > max_tier {
91
- // Force degradation to passthrough if exceeds max tier
92
- return ParseResult::Passthrough(truncate_output(input, 500));
93
- }
94
- result
95
- }
96
- }
97
-
98
- /// Truncate output to max length with ellipsis
99
- pub fn truncate_output(output: &str, max_chars: usize) -> String {
100
- let chars: Vec<char> = output.chars().collect();
101
- if chars.len() <= max_chars {
102
- return output.to_string();
103
- }
104
-
105
- let truncated: String = chars[..max_chars].iter().collect();
106
- format!(
107
- "{}\n\n[RTK:PASSTHROUGH] Output truncated ({} chars → {} chars)",
108
- truncated,
109
- chars.len(),
110
- max_chars
111
- )
112
- }
113
-
114
- /// Helper to emit degradation warning
115
- pub fn emit_degradation_warning(tool: &str, reason: &str) {
116
- eprintln!("[RTK:DEGRADED] {} parser: {}", tool, reason);
117
- }
118
-
119
- /// Helper to emit passthrough warning
120
- pub fn emit_passthrough_warning(tool: &str, reason: &str) {
121
- eprintln!("[RTK:PASSTHROUGH] {} parser: {}", tool, reason);
122
- }
123
-
124
- /// Extract a complete JSON object from input that may have non-JSON prefix (pnpm banner, dotenv messages, etc.)
125
- ///
126
- /// Strategy:
127
- /// 1. Find `"numTotalTests"` (vitest-specific marker) or first standalone `{`
128
- /// 2. Brace-balance forward to find matching `}`
129
- /// 3. Return slice containing complete JSON object
130
- ///
131
- /// Handles: nested braces, string escapes, pnpm prefixes, dotenv banners
132
- ///
133
- /// Returns `None` if no valid JSON object found.
134
- pub fn extract_json_object(input: &str) -> Option<&str> {
135
- // Try vitest-specific marker first (most reliable)
136
- let start_pos = if let Some(pos) = input.find("\"numTotalTests\"") {
137
- // Walk backward to find opening brace of this object
138
- input[..pos].rfind('{').unwrap_or(0)
139
- } else {
140
- // Fallback: find first `{` on its own line or after whitespace
141
- let mut found_start = None;
142
- for (idx, line) in input.lines().enumerate() {
143
- let trimmed = line.trim();
144
- if trimmed.starts_with('{') {
145
- // Calculate byte offset
146
- found_start = Some(
147
- input[..]
148
- .lines()
149
- .take(idx)
150
- .map(|l| l.len() + 1)
151
- .sum::<usize>(),
152
- );
153
- break;
154
- }
155
- }
156
- found_start?
157
- };
158
-
159
- // Brace-balance forward from start_pos
160
- let mut depth = 0;
161
- let mut in_string = false;
162
- let mut escape_next = false;
163
- let chars: Vec<char> = input[start_pos..].chars().collect();
164
-
165
- for (i, &ch) in chars.iter().enumerate() {
166
- if escape_next {
167
- escape_next = false;
168
- continue;
169
- }
170
-
171
- match ch {
172
- '\\' if in_string => escape_next = true,
173
- '"' => in_string = !in_string,
174
- '{' if !in_string => depth += 1,
175
- '}' if !in_string => {
176
- depth -= 1;
177
- if depth == 0 {
178
- // Found matching closing brace
179
- let end_pos = start_pos + i + 1; // +1 to include the `}`
180
- return Some(&input[start_pos..end_pos]);
181
- }
182
- }
183
- _ => {}
184
- }
185
- }
186
-
187
- None
188
- }
189
-
190
- #[cfg(test)]
191
- mod tests {
192
- use super::*;
193
-
194
- #[test]
195
- fn test_parse_result_tier() {
196
- let full: ParseResult<i32> = ParseResult::Full(42);
197
- assert_eq!(full.tier(), 1);
198
- assert!(full.is_ok());
199
-
200
- let degraded: ParseResult<i32> = ParseResult::Degraded(42, vec!["warning".to_string()]);
201
- assert_eq!(degraded.tier(), 2);
202
- assert!(degraded.is_ok());
203
- assert_eq!(degraded.warnings().len(), 1);
204
-
205
- let passthrough: ParseResult<i32> = ParseResult::Passthrough("raw".to_string());
206
- assert_eq!(passthrough.tier(), 3);
207
- assert!(!passthrough.is_ok());
208
- }
209
-
210
- #[test]
211
- fn test_parse_result_map() {
212
- let full: ParseResult<i32> = ParseResult::Full(42);
213
- let mapped = full.map(|x| x * 2);
214
- assert_eq!(mapped.tier(), 1);
215
- assert_eq!(mapped.unwrap(), 84);
216
-
217
- let degraded: ParseResult<i32> = ParseResult::Degraded(42, vec!["warn".to_string()]);
218
- let mapped = degraded.map(|x| x * 2);
219
- assert_eq!(mapped.tier(), 2);
220
- assert_eq!(mapped.warnings().len(), 1);
221
- assert_eq!(mapped.unwrap(), 84);
222
- }
223
-
224
- #[test]
225
- fn test_truncate_output() {
226
- let short = "hello";
227
- assert_eq!(truncate_output(short, 10), "hello");
228
-
229
- let long = "a".repeat(1000);
230
- let truncated = truncate_output(&long, 100);
231
- assert!(truncated.contains("[RTK:PASSTHROUGH]"));
232
- assert!(truncated.contains("1000 chars → 100 chars"));
233
- }
234
-
235
- #[test]
236
- fn test_truncate_output_multibyte() {
237
- // Thai text: each char is 3 bytes
238
- let thai = "สวัสดีครับ".repeat(100);
239
- // Try truncating at a byte offset that might land mid-character
240
- let result = truncate_output(&thai, 50);
241
- assert!(result.contains("[RTK:PASSTHROUGH]"));
242
- // Should be valid UTF-8 (no panic)
243
- let _ = result.len();
244
- }
245
-
246
- #[test]
247
- fn test_truncate_output_emoji() {
248
- let emoji = "🎉".repeat(200);
249
- let result = truncate_output(&emoji, 100);
250
- assert!(result.contains("[RTK:PASSTHROUGH]"));
251
- }
252
-
253
- #[test]
254
- fn test_extract_json_object_clean() {
255
- let input = r#"{"numTotalTests": 13, "numPassedTests": 13}"#;
256
- let extracted = extract_json_object(input);
257
- assert_eq!(extracted, Some(input));
258
- }
259
-
260
- #[test]
261
- fn test_extract_json_object_with_pnpm_prefix() {
262
- let input = r#"
263
- Scope: all 6 workspace projects
264
- WARN deprecated inflight@1.0.6: This module is not supported
265
-
266
- {"numTotalTests": 13, "numPassedTests": 13, "numFailedTests": 0}
267
- "#;
268
- let extracted = extract_json_object(input).expect("Should extract JSON");
269
- assert!(extracted.contains("numTotalTests"));
270
- assert!(extracted.starts_with('{'));
271
- assert!(extracted.ends_with('}'));
272
- }
273
-
274
- #[test]
275
- fn test_extract_json_object_with_dotenv_prefix() {
276
- let input = r#"[dotenv] Loading environment variables from .env
277
- [dotenv] Injected 5 variables
278
-
279
- {"numTotalTests": 5, "testResults": [{"name": "test.js"}]}
280
- "#;
281
- let extracted = extract_json_object(input).expect("Should extract JSON");
282
- assert!(extracted.contains("numTotalTests"));
283
- assert!(extracted.contains("testResults"));
284
- }
285
-
286
- #[test]
287
- fn test_extract_json_object_nested_braces() {
288
- let input = r#"prefix text
289
- {"numTotalTests": 2, "testResults": [{"name": "test", "data": {"nested": true}}]}
290
- "#;
291
- let extracted = extract_json_object(input).expect("Should extract JSON");
292
- assert!(extracted.contains("\"nested\": true"));
293
- assert!(extracted.starts_with('{'));
294
- assert!(extracted.ends_with('}'));
295
- }
296
-
297
- #[test]
298
- fn test_extract_json_object_no_json() {
299
- let input = "Just plain text with no JSON";
300
- let extracted = extract_json_object(input);
301
- assert_eq!(extracted, None);
302
- }
303
-
304
- #[test]
305
- fn test_extract_json_object_string_with_braces() {
306
- let input = r#"{"numTotalTests": 1, "message": "test {should} not confuse parser"}"#;
307
- let extracted = extract_json_object(input).expect("Should extract JSON");
308
- assert!(extracted.contains("test {should} not confuse parser"));
309
- assert_eq!(extracted, input);
310
- }
311
- }