@hasna/terminal 2.0.5 → 2.3.0
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.
- package/dist/cli.js +52 -21
- package/package.json +1 -1
- package/src/ai.ts +77 -130
- package/src/cli.tsx +51 -21
- package/src/command-validator.ts +11 -0
- package/src/context-hints.ts +291 -0
- package/src/discover.ts +238 -0
- package/src/economy.ts +53 -0
- package/src/output-processor.ts +7 -18
- package/src/output-store.ts +65 -0
- package/src/providers/base.ts +3 -1
- package/src/providers/groq.ts +108 -0
- package/src/providers/index.ts +26 -2
- package/src/providers/providers.test.ts +4 -2
- package/src/providers/xai.ts +108 -0
- package/src/sessions-db.ts +81 -0
- package/temp/rtk/.claude/agents/code-reviewer.md +221 -0
- package/temp/rtk/.claude/agents/debugger.md +519 -0
- package/temp/rtk/.claude/agents/rtk-testing-specialist.md +461 -0
- package/temp/rtk/.claude/agents/rust-rtk.md +511 -0
- package/temp/rtk/.claude/agents/technical-writer.md +355 -0
- package/temp/rtk/.claude/commands/diagnose.md +352 -0
- package/temp/rtk/.claude/commands/test-routing.md +362 -0
- package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +16 -0
- package/temp/rtk/.claude/hooks/rtk-rewrite.sh +70 -0
- package/temp/rtk/.claude/hooks/rtk-suggest.sh +152 -0
- package/temp/rtk/.claude/rules/cli-testing.md +526 -0
- package/temp/rtk/.claude/skills/issue-triage/SKILL.md +348 -0
- package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +134 -0
- package/temp/rtk/.claude/skills/performance.md +435 -0
- package/temp/rtk/.claude/skills/pr-triage/SKILL.md +315 -0
- package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +71 -0
- package/temp/rtk/.claude/skills/repo-recap.md +206 -0
- package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +78 -0
- package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +124 -0
- package/temp/rtk/.claude/skills/security-guardian.md +503 -0
- package/temp/rtk/.claude/skills/ship.md +404 -0
- package/temp/rtk/.github/workflows/benchmark.yml +34 -0
- package/temp/rtk/.github/workflows/dco-check.yaml +12 -0
- package/temp/rtk/.github/workflows/release-please.yml +51 -0
- package/temp/rtk/.github/workflows/release.yml +343 -0
- package/temp/rtk/.github/workflows/security-check.yml +135 -0
- package/temp/rtk/.github/workflows/validate-docs.yml +78 -0
- package/temp/rtk/.release-please-manifest.json +3 -0
- package/temp/rtk/ARCHITECTURE.md +1491 -0
- package/temp/rtk/CHANGELOG.md +640 -0
- package/temp/rtk/CLAUDE.md +605 -0
- package/temp/rtk/CONTRIBUTING.md +199 -0
- package/temp/rtk/Cargo.lock +1668 -0
- package/temp/rtk/Cargo.toml +64 -0
- package/temp/rtk/Formula/rtk.rb +43 -0
- package/temp/rtk/INSTALL.md +390 -0
- package/temp/rtk/LICENSE +21 -0
- package/temp/rtk/README.md +386 -0
- package/temp/rtk/README_es.md +159 -0
- package/temp/rtk/README_fr.md +197 -0
- package/temp/rtk/README_ja.md +159 -0
- package/temp/rtk/README_ko.md +159 -0
- package/temp/rtk/README_zh.md +167 -0
- package/temp/rtk/ROADMAP.md +15 -0
- package/temp/rtk/SECURITY.md +217 -0
- package/temp/rtk/TEST_EXEC_TIME.md +102 -0
- package/temp/rtk/build.rs +57 -0
- package/temp/rtk/docs/AUDIT_GUIDE.md +432 -0
- package/temp/rtk/docs/FEATURES.md +1410 -0
- package/temp/rtk/docs/TROUBLESHOOTING.md +309 -0
- package/temp/rtk/docs/filter-workflow.md +102 -0
- package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
- package/temp/rtk/docs/tracking.md +583 -0
- package/temp/rtk/hooks/opencode-rtk.ts +39 -0
- package/temp/rtk/hooks/rtk-awareness.md +29 -0
- package/temp/rtk/hooks/rtk-rewrite.sh +61 -0
- package/temp/rtk/hooks/test-rtk-rewrite.sh +442 -0
- package/temp/rtk/install.sh +124 -0
- package/temp/rtk/release-please-config.json +10 -0
- package/temp/rtk/scripts/benchmark.sh +592 -0
- package/temp/rtk/scripts/check-installation.sh +162 -0
- package/temp/rtk/scripts/install-local.sh +37 -0
- package/temp/rtk/scripts/rtk-economics.sh +137 -0
- package/temp/rtk/scripts/test-all.sh +561 -0
- package/temp/rtk/scripts/test-aristote.sh +227 -0
- package/temp/rtk/scripts/test-tracking.sh +79 -0
- package/temp/rtk/scripts/update-readme-metrics.sh +32 -0
- package/temp/rtk/scripts/validate-docs.sh +73 -0
- package/temp/rtk/src/aws_cmd.rs +880 -0
- package/temp/rtk/src/binlog.rs +1645 -0
- package/temp/rtk/src/cargo_cmd.rs +1727 -0
- package/temp/rtk/src/cc_economics.rs +1157 -0
- package/temp/rtk/src/ccusage.rs +340 -0
- package/temp/rtk/src/config.rs +187 -0
- package/temp/rtk/src/container.rs +855 -0
- package/temp/rtk/src/curl_cmd.rs +134 -0
- package/temp/rtk/src/deps.rs +268 -0
- package/temp/rtk/src/diff_cmd.rs +367 -0
- package/temp/rtk/src/discover/mod.rs +274 -0
- package/temp/rtk/src/discover/provider.rs +388 -0
- package/temp/rtk/src/discover/registry.rs +2022 -0
- package/temp/rtk/src/discover/report.rs +202 -0
- package/temp/rtk/src/discover/rules.rs +667 -0
- package/temp/rtk/src/display_helpers.rs +402 -0
- package/temp/rtk/src/dotnet_cmd.rs +1771 -0
- package/temp/rtk/src/dotnet_format_report.rs +133 -0
- package/temp/rtk/src/dotnet_trx.rs +593 -0
- package/temp/rtk/src/env_cmd.rs +204 -0
- package/temp/rtk/src/filter.rs +462 -0
- package/temp/rtk/src/filters/README.md +52 -0
- package/temp/rtk/src/filters/ansible-playbook.toml +34 -0
- package/temp/rtk/src/filters/basedpyright.toml +47 -0
- package/temp/rtk/src/filters/biome.toml +45 -0
- package/temp/rtk/src/filters/brew-install.toml +37 -0
- package/temp/rtk/src/filters/composer-install.toml +40 -0
- package/temp/rtk/src/filters/df.toml +16 -0
- package/temp/rtk/src/filters/dotnet-build.toml +64 -0
- package/temp/rtk/src/filters/du.toml +16 -0
- package/temp/rtk/src/filters/fail2ban-client.toml +15 -0
- package/temp/rtk/src/filters/gcc.toml +49 -0
- package/temp/rtk/src/filters/gcloud.toml +22 -0
- package/temp/rtk/src/filters/hadolint.toml +24 -0
- package/temp/rtk/src/filters/helm.toml +29 -0
- package/temp/rtk/src/filters/iptables.toml +27 -0
- package/temp/rtk/src/filters/jj.toml +28 -0
- package/temp/rtk/src/filters/jq.toml +24 -0
- package/temp/rtk/src/filters/make.toml +41 -0
- package/temp/rtk/src/filters/markdownlint.toml +24 -0
- package/temp/rtk/src/filters/mix-compile.toml +27 -0
- package/temp/rtk/src/filters/mix-format.toml +15 -0
- package/temp/rtk/src/filters/mvn-build.toml +44 -0
- package/temp/rtk/src/filters/oxlint.toml +43 -0
- package/temp/rtk/src/filters/ping.toml +63 -0
- package/temp/rtk/src/filters/pio-run.toml +40 -0
- package/temp/rtk/src/filters/poetry-install.toml +50 -0
- package/temp/rtk/src/filters/pre-commit.toml +35 -0
- package/temp/rtk/src/filters/ps.toml +16 -0
- package/temp/rtk/src/filters/quarto-render.toml +41 -0
- package/temp/rtk/src/filters/rsync.toml +48 -0
- package/temp/rtk/src/filters/shellcheck.toml +27 -0
- package/temp/rtk/src/filters/shopify-theme.toml +29 -0
- package/temp/rtk/src/filters/skopeo.toml +45 -0
- package/temp/rtk/src/filters/sops.toml +16 -0
- package/temp/rtk/src/filters/ssh.toml +44 -0
- package/temp/rtk/src/filters/stat.toml +34 -0
- package/temp/rtk/src/filters/swift-build.toml +41 -0
- package/temp/rtk/src/filters/systemctl-status.toml +33 -0
- package/temp/rtk/src/filters/terraform-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-fmt.toml +16 -0
- package/temp/rtk/src/filters/tofu-init.toml +38 -0
- package/temp/rtk/src/filters/tofu-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-validate.toml +17 -0
- package/temp/rtk/src/filters/trunk-build.toml +39 -0
- package/temp/rtk/src/filters/ty.toml +50 -0
- package/temp/rtk/src/filters/uv-sync.toml +37 -0
- package/temp/rtk/src/filters/xcodebuild.toml +99 -0
- package/temp/rtk/src/filters/yamllint.toml +25 -0
- package/temp/rtk/src/find_cmd.rs +598 -0
- package/temp/rtk/src/format_cmd.rs +386 -0
- package/temp/rtk/src/gain.rs +723 -0
- package/temp/rtk/src/gh_cmd.rs +1651 -0
- package/temp/rtk/src/git.rs +2012 -0
- package/temp/rtk/src/go_cmd.rs +592 -0
- package/temp/rtk/src/golangci_cmd.rs +254 -0
- package/temp/rtk/src/grep_cmd.rs +288 -0
- package/temp/rtk/src/gt_cmd.rs +810 -0
- package/temp/rtk/src/hook_audit_cmd.rs +283 -0
- package/temp/rtk/src/hook_check.rs +171 -0
- package/temp/rtk/src/init.rs +1859 -0
- package/temp/rtk/src/integrity.rs +537 -0
- package/temp/rtk/src/json_cmd.rs +231 -0
- package/temp/rtk/src/learn/detector.rs +628 -0
- package/temp/rtk/src/learn/mod.rs +119 -0
- package/temp/rtk/src/learn/report.rs +184 -0
- package/temp/rtk/src/lint_cmd.rs +694 -0
- package/temp/rtk/src/local_llm.rs +316 -0
- package/temp/rtk/src/log_cmd.rs +248 -0
- package/temp/rtk/src/ls.rs +324 -0
- package/temp/rtk/src/main.rs +2482 -0
- package/temp/rtk/src/mypy_cmd.rs +389 -0
- package/temp/rtk/src/next_cmd.rs +241 -0
- package/temp/rtk/src/npm_cmd.rs +236 -0
- package/temp/rtk/src/parser/README.md +267 -0
- package/temp/rtk/src/parser/error.rs +46 -0
- package/temp/rtk/src/parser/formatter.rs +336 -0
- package/temp/rtk/src/parser/mod.rs +311 -0
- package/temp/rtk/src/parser/types.rs +119 -0
- package/temp/rtk/src/pip_cmd.rs +302 -0
- package/temp/rtk/src/playwright_cmd.rs +479 -0
- package/temp/rtk/src/pnpm_cmd.rs +573 -0
- package/temp/rtk/src/prettier_cmd.rs +221 -0
- package/temp/rtk/src/prisma_cmd.rs +482 -0
- package/temp/rtk/src/psql_cmd.rs +382 -0
- package/temp/rtk/src/pytest_cmd.rs +384 -0
- package/temp/rtk/src/read.rs +217 -0
- package/temp/rtk/src/rewrite_cmd.rs +50 -0
- package/temp/rtk/src/ruff_cmd.rs +402 -0
- package/temp/rtk/src/runner.rs +271 -0
- package/temp/rtk/src/summary.rs +297 -0
- package/temp/rtk/src/tee.rs +405 -0
- package/temp/rtk/src/telemetry.rs +248 -0
- package/temp/rtk/src/toml_filter.rs +1655 -0
- package/temp/rtk/src/tracking.rs +1416 -0
- package/temp/rtk/src/tree.rs +209 -0
- package/temp/rtk/src/tsc_cmd.rs +259 -0
- package/temp/rtk/src/utils.rs +432 -0
- package/temp/rtk/src/verify_cmd.rs +47 -0
- package/temp/rtk/src/vitest_cmd.rs +385 -0
- package/temp/rtk/src/wc_cmd.rs +401 -0
- package/temp/rtk/src/wget_cmd.rs +260 -0
- package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +11 -0
- package/temp/rtk/tests/fixtures/dotnet/format_changes.json +31 -0
- package/temp/rtk/tests/fixtures/dotnet/format_empty.json +1 -0
- package/temp/rtk/tests/fixtures/dotnet/format_success.json +12 -0
- package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +18 -0
- package/dist/App.js +0 -404
- package/dist/Browse.js +0 -79
- package/dist/FuzzyPicker.js +0 -47
- package/dist/Onboarding.js +0 -51
- package/dist/Spinner.js +0 -12
- package/dist/StatusBar.js +0 -49
- package/dist/ai.js +0 -368
- package/dist/cache.js +0 -41
- package/dist/command-rewriter.js +0 -64
- package/dist/command-validator.js +0 -77
- package/dist/compression.js +0 -107
- package/dist/diff-cache.js +0 -107
- package/dist/economy.js +0 -79
- package/dist/expand-store.js +0 -38
- package/dist/file-cache.js +0 -72
- package/dist/file-index.js +0 -62
- package/dist/history.js +0 -62
- package/dist/lazy-executor.js +0 -54
- package/dist/line-dedup.js +0 -59
- package/dist/loop-detector.js +0 -75
- package/dist/mcp/install.js +0 -98
- package/dist/mcp/server.js +0 -569
- package/dist/noise-filter.js +0 -86
- package/dist/output-processor.js +0 -136
- package/dist/output-router.js +0 -41
- package/dist/parsers/base.js +0 -2
- package/dist/parsers/build.js +0 -64
- package/dist/parsers/errors.js +0 -101
- package/dist/parsers/files.js +0 -78
- package/dist/parsers/git.js +0 -99
- package/dist/parsers/index.js +0 -48
- package/dist/parsers/tests.js +0 -89
- package/dist/providers/anthropic.js +0 -39
- package/dist/providers/base.js +0 -4
- package/dist/providers/cerebras.js +0 -95
- package/dist/providers/index.js +0 -49
- package/dist/recipes/model.js +0 -20
- package/dist/recipes/storage.js +0 -136
- package/dist/search/content-search.js +0 -68
- package/dist/search/file-search.js +0 -61
- package/dist/search/filters.js +0 -34
- package/dist/search/index.js +0 -5
- package/dist/search/semantic.js +0 -320
- package/dist/session-boot.js +0 -59
- package/dist/session-context.js +0 -55
- package/dist/sessions-db.js +0 -120
- package/dist/smart-display.js +0 -286
- package/dist/snapshots.js +0 -51
- package/dist/supervisor.js +0 -112
- package/dist/test-watchlist.js +0 -131
- package/dist/tree.js +0 -94
- package/dist/usage-cache.js +0 -65
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
use crate::tracking;
|
|
2
|
+
use anyhow::{Context, Result};
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
|
|
5
|
+
/// Known npm subcommands that should NOT get "run" injected.
|
|
6
|
+
/// Shared between production code and tests to avoid drift.
|
|
7
|
+
const NPM_SUBCOMMANDS: &[&str] = &[
|
|
8
|
+
"install",
|
|
9
|
+
"i",
|
|
10
|
+
"ci",
|
|
11
|
+
"uninstall",
|
|
12
|
+
"remove",
|
|
13
|
+
"rm",
|
|
14
|
+
"update",
|
|
15
|
+
"up",
|
|
16
|
+
"list",
|
|
17
|
+
"ls",
|
|
18
|
+
"outdated",
|
|
19
|
+
"init",
|
|
20
|
+
"create",
|
|
21
|
+
"publish",
|
|
22
|
+
"pack",
|
|
23
|
+
"link",
|
|
24
|
+
"audit",
|
|
25
|
+
"fund",
|
|
26
|
+
"exec",
|
|
27
|
+
"explain",
|
|
28
|
+
"why",
|
|
29
|
+
"search",
|
|
30
|
+
"view",
|
|
31
|
+
"info",
|
|
32
|
+
"show",
|
|
33
|
+
"config",
|
|
34
|
+
"set",
|
|
35
|
+
"get",
|
|
36
|
+
"cache",
|
|
37
|
+
"prune",
|
|
38
|
+
"dedupe",
|
|
39
|
+
"doctor",
|
|
40
|
+
"help",
|
|
41
|
+
"version",
|
|
42
|
+
"prefix",
|
|
43
|
+
"root",
|
|
44
|
+
"bin",
|
|
45
|
+
"bugs",
|
|
46
|
+
"docs",
|
|
47
|
+
"home",
|
|
48
|
+
"repo",
|
|
49
|
+
"ping",
|
|
50
|
+
"whoami",
|
|
51
|
+
"token",
|
|
52
|
+
"profile",
|
|
53
|
+
"team",
|
|
54
|
+
"access",
|
|
55
|
+
"owner",
|
|
56
|
+
"deprecate",
|
|
57
|
+
"dist-tag",
|
|
58
|
+
"star",
|
|
59
|
+
"stars",
|
|
60
|
+
"login",
|
|
61
|
+
"logout",
|
|
62
|
+
"adduser",
|
|
63
|
+
"unpublish",
|
|
64
|
+
"pkg",
|
|
65
|
+
"diff",
|
|
66
|
+
"rebuild",
|
|
67
|
+
"test",
|
|
68
|
+
"t",
|
|
69
|
+
"start",
|
|
70
|
+
"stop",
|
|
71
|
+
"restart",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
pub fn run(args: &[String], verbose: u8, skip_env: bool) -> Result<()> {
|
|
75
|
+
let timer = tracking::TimedExecution::start();
|
|
76
|
+
|
|
77
|
+
let mut cmd = Command::new("npm");
|
|
78
|
+
|
|
79
|
+
// Determine if this is "npm run <script>" or another npm subcommand (install, list, etc.)
|
|
80
|
+
// Only inject "run" when args look like a script name, not a known npm subcommand.
|
|
81
|
+
let first_arg = args.first().map(|s| s.as_str());
|
|
82
|
+
let is_run_explicit = first_arg == Some("run");
|
|
83
|
+
let is_npm_subcommand = first_arg
|
|
84
|
+
.map(|a| NPM_SUBCOMMANDS.contains(&a) || a.starts_with('-'))
|
|
85
|
+
.unwrap_or(false);
|
|
86
|
+
|
|
87
|
+
let effective_args = if is_run_explicit {
|
|
88
|
+
// "rtk npm run build" → "npm run build"
|
|
89
|
+
cmd.arg("run");
|
|
90
|
+
&args[1..]
|
|
91
|
+
} else if is_npm_subcommand {
|
|
92
|
+
// "rtk npm install express" → "npm install express"
|
|
93
|
+
args
|
|
94
|
+
} else {
|
|
95
|
+
// "rtk npm build" → "npm run build" (assume script name)
|
|
96
|
+
cmd.arg("run");
|
|
97
|
+
args
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
for arg in effective_args {
|
|
101
|
+
cmd.arg(arg);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if skip_env {
|
|
105
|
+
cmd.env("SKIP_ENV_VALIDATION", "1");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if verbose > 0 {
|
|
109
|
+
eprintln!("Running: npm {}", args.join(" "));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let output = cmd.output().context("Failed to run npm")?;
|
|
113
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
114
|
+
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
115
|
+
let raw = format!("{}\n{}", stdout, stderr);
|
|
116
|
+
|
|
117
|
+
let filtered = filter_npm_output(&raw);
|
|
118
|
+
println!("{}", filtered);
|
|
119
|
+
|
|
120
|
+
timer.track(
|
|
121
|
+
&format!("npm {}", args.join(" ")),
|
|
122
|
+
&format!("rtk npm {}", args.join(" ")),
|
|
123
|
+
&raw,
|
|
124
|
+
&filtered,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if !output.status.success() {
|
|
128
|
+
std::process::exit(output.status.code().unwrap_or(1));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Ok(())
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Filter npm run output - strip boilerplate, progress bars, npm WARN
|
|
135
|
+
fn filter_npm_output(output: &str) -> String {
|
|
136
|
+
let mut result = Vec::new();
|
|
137
|
+
|
|
138
|
+
for line in output.lines() {
|
|
139
|
+
// Skip npm boilerplate
|
|
140
|
+
if line.starts_with('>') && line.contains('@') {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Skip npm lifecycle scripts
|
|
144
|
+
if line.trim_start().starts_with("npm WARN") {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if line.trim_start().starts_with("npm notice") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// Skip progress indicators
|
|
151
|
+
if line.contains("⸩") || line.contains("⸨") || line.contains("...") && line.len() < 10 {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Skip empty lines
|
|
155
|
+
if line.trim().is_empty() {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
result.push(line.to_string());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if result.is_empty() {
|
|
163
|
+
"ok ✓".to_string()
|
|
164
|
+
} else {
|
|
165
|
+
result.join("\n")
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[cfg(test)]
|
|
170
|
+
mod tests {
|
|
171
|
+
use super::*;
|
|
172
|
+
|
|
173
|
+
#[test]
|
|
174
|
+
fn test_filter_npm_output() {
|
|
175
|
+
let output = r#"
|
|
176
|
+
> project@1.0.0 build
|
|
177
|
+
> next build
|
|
178
|
+
|
|
179
|
+
npm WARN deprecated inflight@1.0.6: This module is not supported
|
|
180
|
+
npm notice
|
|
181
|
+
|
|
182
|
+
Creating an optimized production build...
|
|
183
|
+
✓ Build completed
|
|
184
|
+
"#;
|
|
185
|
+
let result = filter_npm_output(output);
|
|
186
|
+
assert!(!result.contains("npm WARN"));
|
|
187
|
+
assert!(!result.contains("npm notice"));
|
|
188
|
+
assert!(!result.contains("> project@"));
|
|
189
|
+
assert!(result.contains("Build completed"));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[test]
|
|
193
|
+
fn test_npm_subcommand_routing() {
|
|
194
|
+
// Uses the shared NPM_SUBCOMMANDS constant — no drift between prod and test
|
|
195
|
+
fn needs_run_injection(args: &[&str]) -> bool {
|
|
196
|
+
let first = args.first().copied();
|
|
197
|
+
let is_run_explicit = first == Some("run");
|
|
198
|
+
let is_subcommand = first
|
|
199
|
+
.map(|a| NPM_SUBCOMMANDS.contains(&a) || a.starts_with('-'))
|
|
200
|
+
.unwrap_or(false);
|
|
201
|
+
!is_run_explicit && !is_subcommand
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Known subcommands should NOT get "run" injected
|
|
205
|
+
for subcmd in NPM_SUBCOMMANDS {
|
|
206
|
+
assert!(
|
|
207
|
+
!needs_run_injection(&[subcmd]),
|
|
208
|
+
"'npm {}' should NOT inject 'run'",
|
|
209
|
+
subcmd
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Script names SHOULD get "run" injected
|
|
214
|
+
for script in &["build", "dev", "lint", "typecheck", "deploy"] {
|
|
215
|
+
assert!(
|
|
216
|
+
needs_run_injection(&[script]),
|
|
217
|
+
"'npm {}' SHOULD inject 'run'",
|
|
218
|
+
script
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Flags should NOT get "run" injected
|
|
223
|
+
assert!(!needs_run_injection(&["--version"]));
|
|
224
|
+
assert!(!needs_run_injection(&["-h"]));
|
|
225
|
+
|
|
226
|
+
// Explicit "run" should NOT inject another "run"
|
|
227
|
+
assert!(!needs_run_injection(&["run", "build"]));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[test]
|
|
231
|
+
fn test_filter_npm_output_empty() {
|
|
232
|
+
let output = "\n\n\n";
|
|
233
|
+
let result = filter_npm_output(output);
|
|
234
|
+
assert_eq!(result, "ok ✓");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Parser Infrastructure
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The parser infrastructure provides a unified, three-tier parsing system for tool outputs with graceful degradation:
|
|
6
|
+
|
|
7
|
+
- **Tier 1 (Full)**: Complete JSON parsing with all structured data
|
|
8
|
+
- **Tier 2 (Degraded)**: Partial parsing with warnings (fallback regex)
|
|
9
|
+
- **Tier 3 (Passthrough)**: Raw output truncation with error markers
|
|
10
|
+
|
|
11
|
+
This ensures RTK **never returns false data silently** while maintaining maximum token efficiency.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌─────────────────────────────────────────────────────────┐
|
|
17
|
+
│ ToolCommand Builder │
|
|
18
|
+
│ Command::new("vitest").arg("--reporter=json") │
|
|
19
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
20
|
+
│
|
|
21
|
+
┌─────────────────────▼───────────────────────────────────┐
|
|
22
|
+
│ OutputParser<T> Trait │
|
|
23
|
+
│ parse() → ParseResult<T> │
|
|
24
|
+
│ ├─ Full(T) - Tier 1: Complete JSON parse │
|
|
25
|
+
│ ├─ Degraded(T, warn) - Tier 2: Partial with warnings │
|
|
26
|
+
│ └─ Passthrough(str) - Tier 3: Truncated raw output │
|
|
27
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
28
|
+
│
|
|
29
|
+
┌─────────────────────▼───────────────────────────────────┐
|
|
30
|
+
│ Canonical Types │
|
|
31
|
+
│ TestResult, LintResult, DependencyState, BuildOutput │
|
|
32
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
33
|
+
│
|
|
34
|
+
┌─────────────────────▼───────────────────────────────────┐
|
|
35
|
+
│ TokenFormatter Trait │
|
|
36
|
+
│ format_compact() / format_verbose() / format_ultra() │
|
|
37
|
+
└─────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage Example
|
|
41
|
+
|
|
42
|
+
### 1. Define Tool-Specific Parser
|
|
43
|
+
|
|
44
|
+
```rust
|
|
45
|
+
use crate::parser::{OutputParser, ParseResult, TestResult};
|
|
46
|
+
|
|
47
|
+
struct VitestParser;
|
|
48
|
+
|
|
49
|
+
impl OutputParser for VitestParser {
|
|
50
|
+
type Output = TestResult;
|
|
51
|
+
|
|
52
|
+
fn parse(input: &str) -> ParseResult<TestResult> {
|
|
53
|
+
// Tier 1: Try JSON parsing
|
|
54
|
+
match serde_json::from_str::<VitestJsonOutput>(input) {
|
|
55
|
+
Ok(json) => {
|
|
56
|
+
let result = TestResult {
|
|
57
|
+
total: json.num_total_tests,
|
|
58
|
+
passed: json.num_passed_tests,
|
|
59
|
+
failed: json.num_failed_tests,
|
|
60
|
+
// ... map fields
|
|
61
|
+
};
|
|
62
|
+
ParseResult::Full(result)
|
|
63
|
+
}
|
|
64
|
+
Err(e) => {
|
|
65
|
+
// Tier 2: Try regex extraction
|
|
66
|
+
if let Some(stats) = extract_stats_regex(input) {
|
|
67
|
+
ParseResult::Degraded(
|
|
68
|
+
stats,
|
|
69
|
+
vec![format!("JSON parse failed: {}", e)]
|
|
70
|
+
)
|
|
71
|
+
} else {
|
|
72
|
+
// Tier 3: Passthrough
|
|
73
|
+
ParseResult::Passthrough(truncate_output(input, 500))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Use Parser in Command Module
|
|
82
|
+
|
|
83
|
+
```rust
|
|
84
|
+
use crate::parser::{OutputParser, TokenFormatter, FormatMode};
|
|
85
|
+
|
|
86
|
+
pub fn run_vitest(args: &[String], verbose: u8) -> Result<()> {
|
|
87
|
+
let mut cmd = Command::new("pnpm");
|
|
88
|
+
cmd.arg("vitest").arg("--reporter=json");
|
|
89
|
+
// ... add args
|
|
90
|
+
|
|
91
|
+
let output = cmd.output()?;
|
|
92
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
93
|
+
|
|
94
|
+
// Parse output
|
|
95
|
+
let result = VitestParser::parse(&stdout);
|
|
96
|
+
|
|
97
|
+
// Format based on verbosity
|
|
98
|
+
let mode = FormatMode::from_verbosity(verbose);
|
|
99
|
+
let formatted = match result {
|
|
100
|
+
ParseResult::Full(data) => data.format(mode),
|
|
101
|
+
ParseResult::Degraded(data, warnings) => {
|
|
102
|
+
if verbose > 0 {
|
|
103
|
+
for warn in warnings {
|
|
104
|
+
eprintln!("[RTK:DEGRADED] {}", warn);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
data.format(mode)
|
|
108
|
+
}
|
|
109
|
+
ParseResult::Passthrough(raw) => {
|
|
110
|
+
eprintln!("[RTK:PASSTHROUGH] Parser failed, showing truncated output");
|
|
111
|
+
raw
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
println!("{}", formatted);
|
|
116
|
+
Ok(())
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Canonical Types
|
|
121
|
+
|
|
122
|
+
### TestResult
|
|
123
|
+
For test runners (vitest, playwright, jest, etc.)
|
|
124
|
+
- Fields: `total`, `passed`, `failed`, `skipped`, `duration_ms`, `failures`
|
|
125
|
+
- Formatter: Shows summary + failure details (compact: top 5, verbose: all)
|
|
126
|
+
|
|
127
|
+
### LintResult
|
|
128
|
+
For linters (eslint, biome, tsc, etc.)
|
|
129
|
+
- Fields: `total_files`, `files_with_issues`, `total_issues`, `errors`, `warnings`, `issues`
|
|
130
|
+
- Formatter: Groups by rule_id, shows top violations
|
|
131
|
+
|
|
132
|
+
### DependencyState
|
|
133
|
+
For package managers (pnpm, npm, cargo, etc.)
|
|
134
|
+
- Fields: `total_packages`, `outdated_count`, `dependencies`
|
|
135
|
+
- Formatter: Shows upgrade paths (current → latest)
|
|
136
|
+
|
|
137
|
+
### BuildOutput
|
|
138
|
+
For build tools (next, webpack, vite, cargo, etc.)
|
|
139
|
+
- Fields: `success`, `duration_ms`, `bundles`, `routes`, `warnings`, `errors`
|
|
140
|
+
- Formatter: Shows bundle sizes, route metrics
|
|
141
|
+
|
|
142
|
+
## Format Modes
|
|
143
|
+
|
|
144
|
+
### Compact (default, verbosity=0)
|
|
145
|
+
- Summary only
|
|
146
|
+
- Top 5-10 items
|
|
147
|
+
- Token-optimized
|
|
148
|
+
|
|
149
|
+
### Verbose (verbosity=1)
|
|
150
|
+
- Full details
|
|
151
|
+
- All items (up to 20)
|
|
152
|
+
- Human-readable
|
|
153
|
+
|
|
154
|
+
### Ultra (verbosity=2+)
|
|
155
|
+
- Symbols: ✓✗⚠📦⬆️
|
|
156
|
+
- Ultra-compressed
|
|
157
|
+
- 30-50% token reduction
|
|
158
|
+
|
|
159
|
+
## Error Handling
|
|
160
|
+
|
|
161
|
+
### ParseError Types
|
|
162
|
+
- `JsonError`: Line/column context for debugging
|
|
163
|
+
- `PatternMismatch`: Regex pattern failed
|
|
164
|
+
- `PartialParse`: Some fields missing
|
|
165
|
+
- `InvalidFormat`: Unexpected structure
|
|
166
|
+
- `MissingField`: Required field absent
|
|
167
|
+
- `VersionMismatch`: Tool version incompatible
|
|
168
|
+
- `EmptyOutput`: No data to parse
|
|
169
|
+
|
|
170
|
+
### Degradation Warnings
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
[RTK:DEGRADED] vitest parser: JSON parse failed at line 42, using regex fallback
|
|
174
|
+
[RTK:PASSTHROUGH] playwright parser: Pattern mismatch, showing truncated output
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Migration Guide
|
|
178
|
+
|
|
179
|
+
### Existing Module → Parser Trait
|
|
180
|
+
|
|
181
|
+
**Before:**
|
|
182
|
+
```rust
|
|
183
|
+
fn run_vitest(args: &[String]) -> Result<()> {
|
|
184
|
+
let output = Command::new("vitest").output()?;
|
|
185
|
+
let filtered = filter_vitest_output(&output.stdout);
|
|
186
|
+
println!("{}", filtered);
|
|
187
|
+
Ok(())
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**After:**
|
|
192
|
+
```rust
|
|
193
|
+
fn run_vitest(args: &[String], verbose: u8) -> Result<()> {
|
|
194
|
+
let output = Command::new("vitest")
|
|
195
|
+
.arg("--reporter=json")
|
|
196
|
+
.output()?;
|
|
197
|
+
|
|
198
|
+
let result = VitestParser::parse(&output.stdout);
|
|
199
|
+
let mode = FormatMode::from_verbosity(verbose);
|
|
200
|
+
|
|
201
|
+
match result {
|
|
202
|
+
ParseResult::Full(data) | ParseResult::Degraded(data, _) => {
|
|
203
|
+
println!("{}", data.format(mode));
|
|
204
|
+
}
|
|
205
|
+
ParseResult::Passthrough(raw) => {
|
|
206
|
+
println!("{}", raw);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
Ok(())
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Testing
|
|
214
|
+
|
|
215
|
+
### Unit Tests
|
|
216
|
+
```bash
|
|
217
|
+
cargo test parser::tests
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Integration Tests
|
|
221
|
+
```bash
|
|
222
|
+
# Test with real tool outputs
|
|
223
|
+
echo '{"testResults": [...]}' | cargo run -- vitest parse
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Tier Validation
|
|
227
|
+
```rust
|
|
228
|
+
#[test]
|
|
229
|
+
fn test_vitest_json_parsing() {
|
|
230
|
+
let json = include_str!("fixtures/vitest-v1.json");
|
|
231
|
+
let result = VitestParser::parse(json);
|
|
232
|
+
assert_eq!(result.tier(), 1); // Full parse
|
|
233
|
+
assert!(result.is_ok());
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn test_vitest_regex_fallback() {
|
|
238
|
+
let text = "Test Files 2 passed (2)\n Tests 13 passed (13)";
|
|
239
|
+
let result = VitestParser::parse(text);
|
|
240
|
+
assert_eq!(result.tier(), 2); // Degraded
|
|
241
|
+
assert!(!result.warnings().is_empty());
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Benefits
|
|
246
|
+
|
|
247
|
+
1. **Maintenance**: Tool version changes break gracefully (Tier 2/3 fallback)
|
|
248
|
+
2. **Reliability**: Never silent failures or false data
|
|
249
|
+
3. **Observability**: Clear degradation markers in verbose mode
|
|
250
|
+
4. **Token Efficiency**: Structured data enables better compression
|
|
251
|
+
5. **Consistency**: Unified interface across all tool types
|
|
252
|
+
6. **Testing**: Fixture-based regression tests for multiple versions
|
|
253
|
+
|
|
254
|
+
## Roadmap
|
|
255
|
+
|
|
256
|
+
### Phase 4: Module Migration
|
|
257
|
+
- [ ] vitest_cmd.rs → VitestParser
|
|
258
|
+
- [ ] playwright_cmd.rs → PlaywrightParser
|
|
259
|
+
- [ ] pnpm_cmd.rs → PnpmParser (list, outdated)
|
|
260
|
+
- [ ] lint_cmd.rs → EslintParser
|
|
261
|
+
- [ ] tsc_cmd.rs → TscParser
|
|
262
|
+
- [ ] gh_cmd.rs → GhParser
|
|
263
|
+
|
|
264
|
+
### Phase 5: Observability
|
|
265
|
+
- [ ] Extend tracking.db: `parse_tier`, `format_mode`
|
|
266
|
+
- [ ] `rtk parse-health` command
|
|
267
|
+
- [ ] Alert if degradation > 10%
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// Parser error types for structured output parsing
|
|
2
|
+
use thiserror::Error;
|
|
3
|
+
|
|
4
|
+
#[derive(Error, Debug)]
|
|
5
|
+
pub enum ParseError {
|
|
6
|
+
#[error("JSON parse failed at line {line}, column {col}: {msg}")]
|
|
7
|
+
JsonError {
|
|
8
|
+
line: usize,
|
|
9
|
+
col: usize,
|
|
10
|
+
msg: String,
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
#[error("Pattern mismatch: expected {expected}")]
|
|
14
|
+
PatternMismatch { expected: &'static str },
|
|
15
|
+
|
|
16
|
+
#[error("Partial parse: got {found}, missing fields: {missing:?}")]
|
|
17
|
+
PartialParse {
|
|
18
|
+
found: String,
|
|
19
|
+
missing: Vec<&'static str>,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
#[error("Invalid format: {0}")]
|
|
23
|
+
InvalidFormat(String),
|
|
24
|
+
|
|
25
|
+
#[error("Missing required field: {0}")]
|
|
26
|
+
MissingField(&'static str),
|
|
27
|
+
|
|
28
|
+
#[error("Version mismatch: got {got}, expected {expected}")]
|
|
29
|
+
VersionMismatch { got: String, expected: String },
|
|
30
|
+
|
|
31
|
+
#[error("Empty output")]
|
|
32
|
+
EmptyOutput,
|
|
33
|
+
|
|
34
|
+
#[error(transparent)]
|
|
35
|
+
Other(#[from] anyhow::Error),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl From<serde_json::Error> for ParseError {
|
|
39
|
+
fn from(err: serde_json::Error) -> Self {
|
|
40
|
+
ParseError::JsonError {
|
|
41
|
+
line: err.line(),
|
|
42
|
+
col: err.column(),
|
|
43
|
+
msg: err.to_string(),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|