feat: add GitHub CLI integration (depends on #9)#10
feat: add GitHub CLI integration (depends on #9)#10pszymkowiak merged 3 commits intortk-ai:masterfrom
Conversation
Add rtk gh command for GitHub CLI operations with intelligent output filtering: Commands: - rtk gh pr list/view/checks/status: PR management (53-87% reduction) - rtk gh issue list/view: Issue tracking (26% reduction) - rtk gh run list/view: Workflow monitoring (82% reduction) - rtk gh repo view: Repository info (29% reduction) Features: - Level 1 optimizations (default): Remove header counts, @ prefix, compact mergeable status (+12-18% savings, zero UX loss) - Level 2 optimizations (--ultra-compact flag): ASCII icons, inline checks format (+22% total savings on PR view) - GraphQL response parsing and grouping - Preserves all critical information for code review Token Savings (validated on production repo): - rtk gh pr view: 87% (24.7K → 3.2K chars) - rtk gh pr checks: 79% (8.9K → 1.8K chars) - rtk gh run list: 82% (10.2K → 1.8K chars) Global --ultra-compact flag added to enable Level 2 optimizations across all GitHub commands. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
c0df987 to
0f7418e
Compare
- Add utils.rs as Key Architectural Component - Expand Module Responsibilities table (7→17 modules) - Document PR rtk-ai#9 in Fork-Specific Features section - Include token reduction metrics for all new commands
Change dtolnay/rust-action to dtolnay/rust-toolchain (correct name)
|
|
||
| - name: Install Rust | ||
| uses: dtolnay/rust-action@stable | ||
| uses: dtolnay/rust-toolchain@stable |
There was a problem hiding this comment.
Pull request overview
This PR adds GitHub CLI integration to rtk with token-optimized output for common gh commands. It introduces a two-level optimization system (conservative Level 1 by default, and aggressive Level 2 with the --ultra-compact flag) that achieves 26-87% token reduction on GitHub operations like PR reviews and CI monitoring.
Changes:
- Adds
rtk ghcommand with subcommands for PR, issue, workflow, and repo operations - Introduces global
--ultra-compactflag for Level 2 optimizations (ASCII icons, inline formatting) - Updates CLAUDE.md documentation with gh_cmd.rs module description
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/main.rs | Adds gh_cmd module, Gh command enum variant, and ultra_compact global flag |
| src/gh_cmd.rs | Implements GitHub CLI wrapper with JSON parsing and token-optimized formatting |
| CLAUDE.md | Documents gh_cmd module and its features in architecture section |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn view_issue(args: &[String], _verbose: u8) -> Result<()> { | ||
| if args.is_empty() { | ||
| return Err(anyhow::anyhow!("Issue number required")); | ||
| } | ||
|
|
||
| let issue_number = &args[0]; | ||
|
|
||
| let mut cmd = Command::new("gh"); | ||
| cmd.args(["issue", "view", issue_number, "--json", "number,title,state,author,body,url"]); | ||
|
|
||
| let output = cmd.output().context("Failed to run gh issue view")?; | ||
|
|
||
| if !output.status.success() { | ||
| eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | ||
| std::process::exit(output.status.code().unwrap_or(1)); | ||
| } | ||
|
|
||
| let json: Value = serde_json::from_slice(&output.stdout) | ||
| .context("Failed to parse gh issue view output")?; | ||
|
|
||
| let number = json["number"].as_i64().unwrap_or(0); | ||
| let title = json["title"].as_str().unwrap_or("???"); | ||
| let state = json["state"].as_str().unwrap_or("???"); | ||
| let author = json["author"]["login"].as_str().unwrap_or("???"); | ||
| let url = json["url"].as_str().unwrap_or(""); | ||
|
|
||
| let icon = if state == "OPEN" { "🟢" } else { "🔴" }; | ||
|
|
||
| println!("{} Issue #{}: {}", icon, number, title); | ||
| println!(" Author: @{}", author); | ||
| println!(" Status: {}", state); | ||
| println!(" URL: {}", url); | ||
|
|
||
| if let Some(body) = json["body"].as_str() { | ||
| if !body.is_empty() { | ||
| println!("\n Description:"); | ||
| for line in body.lines().take(3) { | ||
| if !line.trim().is_empty() { | ||
| println!(" {}", truncate(line, 80)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The view_issue function doesn't accept the ultra_compact parameter, unlike list_issues and other similar functions. According to the PR description, the @ prefix should be removed as part of Level 1 optimization (not Level 2), but this function always prints "Author: @{author}" (line 373). This is inconsistent with view_pr which prints just the author without the @ prefix (line 145). The function should accept the ultra_compact parameter and conditionally format the output, though the @ removal could be done at Level 1.
| fn view_run(args: &[String], _verbose: u8) -> Result<()> { | ||
| if args.is_empty() { | ||
| return Err(anyhow::anyhow!("Run ID required")); | ||
| } | ||
|
|
||
| let run_id = &args[0]; | ||
|
|
||
| let mut cmd = Command::new("gh"); | ||
| cmd.args(["run", "view", run_id]); | ||
|
|
||
| let output = cmd.output().context("Failed to run gh run view")?; | ||
|
|
||
| if !output.status.success() { | ||
| eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | ||
| std::process::exit(output.status.code().unwrap_or(1)); | ||
| } | ||
|
|
||
| // Parse output and show only failures | ||
| let stdout = String::from_utf8_lossy(&output.stdout); | ||
| let mut in_jobs = false; | ||
|
|
||
| println!("🏃 Workflow Run #{}", run_id); | ||
|
|
||
| for line in stdout.lines() { | ||
| if line.contains("JOBS") { | ||
| in_jobs = true; | ||
| } | ||
|
|
||
| if in_jobs { | ||
| if line.contains('✓') || line.contains("success") { | ||
| // Skip successful jobs in compact mode | ||
| continue; | ||
| } | ||
| if line.contains('✗') || line.contains("fail") { | ||
| println!(" ❌ {}", line.trim()); | ||
| } | ||
| } else if line.contains("Status:") || line.contains("Conclusion:") { | ||
| println!(" {}", line.trim()); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The view_run function doesn't accept the ultra_compact parameter, even though it's passed down through the call chain (run_workflow -> view_run). The function uses hardcoded emoji (🏃, ❌) which could be replaced with ASCII alternatives in ultra_compact mode for consistency with list_runs. Consider adding the ultra_compact parameter and conditionally formatting the icons.
| fn pr_checks(args: &[String], _verbose: u8, _ultra_compact: bool) -> Result<()> { | ||
| if args.is_empty() { | ||
| return Err(anyhow::anyhow!("PR number required")); | ||
| } | ||
|
|
||
| let pr_number = &args[0]; | ||
|
|
||
| let mut cmd = Command::new("gh"); | ||
| cmd.args(["pr", "checks", pr_number]); | ||
|
|
||
| let output = cmd.output().context("Failed to run gh pr checks")?; | ||
|
|
||
| if !output.status.success() { | ||
| eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | ||
| std::process::exit(output.status.code().unwrap_or(1)); | ||
| } | ||
|
|
||
| let stdout = String::from_utf8_lossy(&output.stdout); | ||
|
|
||
| // Parse and compress checks output | ||
| let mut passed = 0; | ||
| let mut failed = 0; | ||
| let mut pending = 0; | ||
| let mut failed_checks = Vec::new(); | ||
|
|
||
| for line in stdout.lines() { | ||
| if line.contains('✓') || line.contains("pass") { | ||
| passed += 1; | ||
| } else if line.contains('✗') || line.contains("fail") { | ||
| failed += 1; | ||
| failed_checks.push(line.trim().to_string()); | ||
| } else if line.contains('*') || line.contains("pending") { | ||
| pending += 1; | ||
| } | ||
| } | ||
|
|
||
| println!("🔍 CI Checks Summary:"); | ||
| println!(" ✅ Passed: {}", passed); | ||
| println!(" ❌ Failed: {}", failed); | ||
| if pending > 0 { | ||
| println!(" ⏳ Pending: {}", pending); | ||
| } | ||
|
|
||
| if !failed_checks.is_empty() { | ||
| println!("\n Failed checks:"); | ||
| for check in failed_checks { | ||
| println!(" {}", check); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The pr_checks function receives ultra_compact as a parameter but prefixes it with underscore (_ultra_compact), indicating it's intentionally unused. However, the function uses hardcoded emojis (🔍, ✅, ❌, ⏳) which could benefit from ASCII alternatives in ultra_compact mode for consistency. Either remove the parameter if it's not needed, or implement ultra_compact formatting.
| fn pr_status(_verbose: u8, _ultra_compact: bool) -> Result<()> { | ||
| let mut cmd = Command::new("gh"); | ||
| cmd.args(["pr", "status", "--json", "currentBranch,createdBy,reviewDecision,statusCheckRollup"]); | ||
|
|
||
| let output = cmd.output().context("Failed to run gh pr status")?; | ||
|
|
||
| if !output.status.success() { | ||
| eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | ||
| std::process::exit(output.status.code().unwrap_or(1)); | ||
| } | ||
|
|
||
| let json: Value = serde_json::from_slice(&output.stdout) | ||
| .context("Failed to parse gh pr status output")?; | ||
|
|
||
| if let Some(created_by) = json["createdBy"].as_array() { | ||
| println!("📝 Your PRs ({}):", created_by.len()); | ||
| for pr in created_by.iter().take(5) { | ||
| let number = pr["number"].as_i64().unwrap_or(0); | ||
| let title = pr["title"].as_str().unwrap_or("???"); | ||
| let reviews = pr["reviewDecision"].as_str().unwrap_or("PENDING"); | ||
| println!(" #{} {} [{}]", number, truncate(title, 50), reviews); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The pr_status function receives ultra_compact as a parameter but prefixes it with underscore (_ultra_compact), indicating it's intentionally unused. However, the function uses a hardcoded emoji (📝) which could be replaced with ASCII in ultra_compact mode. Either remove the parameter if it's not needed, or implement ultra_compact formatting for consistency with other functions.
| fn run_repo(args: &[String], _verbose: u8, _ultra_compact: bool) -> Result<()> { | ||
| // Parse subcommand (default to "view") | ||
| let (subcommand, rest_args) = if args.is_empty() { | ||
| ("view", &args[..]) | ||
| } else { | ||
| (args[0].as_str(), &args[1..]) | ||
| }; | ||
|
|
||
| if subcommand != "view" { | ||
| return run_passthrough("gh", "repo", args); | ||
| } | ||
|
|
||
| let mut cmd = Command::new("gh"); | ||
| cmd.arg("repo").arg("view"); | ||
|
|
||
| for arg in rest_args { | ||
| cmd.arg(arg); | ||
| } | ||
|
|
||
| cmd.args(["--json", "name,owner,description,url,stargazerCount,forkCount,isPrivate"]); | ||
|
|
||
| let output = cmd.output().context("Failed to run gh repo view")?; | ||
|
|
||
| if !output.status.success() { | ||
| eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | ||
| std::process::exit(output.status.code().unwrap_or(1)); | ||
| } | ||
|
|
||
| let json: Value = serde_json::from_slice(&output.stdout) | ||
| .context("Failed to parse gh repo view output")?; | ||
|
|
||
| let name = json["name"].as_str().unwrap_or("???"); | ||
| let owner = json["owner"]["login"].as_str().unwrap_or("???"); | ||
| let description = json["description"].as_str().unwrap_or(""); | ||
| let url = json["url"].as_str().unwrap_or(""); | ||
| let stars = json["stargazerCount"].as_i64().unwrap_or(0); | ||
| let forks = json["forkCount"].as_i64().unwrap_or(0); | ||
| let private = json["isPrivate"].as_bool().unwrap_or(false); | ||
|
|
||
| let visibility = if private { "🔒 Private" } else { "🌐 Public" }; | ||
|
|
||
| println!("📦 {}/{}", owner, name); | ||
| println!(" {}", visibility); | ||
| if !description.is_empty() { | ||
| println!(" {}", truncate(description, 80)); | ||
| } | ||
| println!(" ⭐ {} stars | 🔱 {} forks", stars, forks); | ||
| println!(" {}", url); | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The run_repo function receives ultra_compact as a parameter but prefixes it with underscore (_ultra_compact), indicating it's intentionally unused. However, the function uses hardcoded emojis (🔒, 🌐, 📦, ⭐, 🔱) which could be replaced with ASCII alternatives in ultra_compact mode. Either remove the parameter if it's not needed, or implement ultra_compact formatting for consistency.
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_truncate() { | ||
| assert_eq!(truncate("short", 10), "short"); | ||
| assert_eq!(truncate("this is a very long string", 15), "this is a ve..."); | ||
| } | ||
| } |
There was a problem hiding this comment.
The test for truncate is testing a duplicated function that should be removed in favor of using crate::utils::truncate. Once the truncate function is removed from this file (lines 567-573), this test should also be removed since utils.rs already has comprehensive tests for the truncate function.
| fn truncate(s: &str, max_len: usize) -> String { | ||
| if s.len() <= max_len { | ||
| s.to_string() | ||
| } else { | ||
| format!("{}...", &s[..max_len - 3]) | ||
| } | ||
| } |
There was a problem hiding this comment.
The truncate function is duplicated from src/utils.rs. This module should import and use crate::utils::truncate instead of reimplementing it. This would maintain consistency with other command modules (like lint_cmd.rs and tsc_cmd.rs) and avoid code duplication.
Updates documentation to reflect all features added in recent PRs: **Version Updates** - Update installation commands to v0.3.1 (DEB/RPM packages) **New Sections** - Add Global Flags section (-u/--ultra-compact, -v/--verbose) - Add JavaScript/TypeScript Stack section (10 new commands) **New Commands Documented** - Files: `rtk smart` (heuristic code summary) - Commands: `rtk gh` (GitHub CLI), `rtk wget`, `rtk config` - Data: `rtk gain --quota` and `--tier` flags - Containers: `rtk kubectl services` - JS/TS Stack: lint, tsc, next, prettier, vitest, playwright, prisma **Features Coverage** This update documents functionality from: - PR rtk-ai#5: Git argument parsing improvements - PR rtk-ai#6: pnpm support - PR rtk-ai#9: Modern JavaScript/TypeScript stack support - PR rtk-ai#10: GitHub CLI integration - PR rtk-ai#11: Quota analysis features - PR rtk-ai#14: Additional command improvements All commands documented are available in v0.3.1. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updates documentation to reflect all features added in recent PRs: **Version Updates** - Update installation commands to v0.3.1 (DEB/RPM packages) **New Sections** - Add Global Flags section (-u/--ultra-compact, -v/--verbose) - Add JavaScript/TypeScript Stack section (10 new commands) **New Commands Documented** - Files: `rtk smart` (heuristic code summary) - Commands: `rtk gh` (GitHub CLI), `rtk wget`, `rtk config` - Data: `rtk gain --quota` and `--tier` flags - Containers: `rtk kubectl services` - JS/TS Stack: lint, tsc, next, prettier, vitest, playwright, prisma **Features Coverage** This update documents functionality from: - PR rtk-ai#5: Git argument parsing improvements - PR rtk-ai#6: pnpm support - PR rtk-ai#9: Modern JavaScript/TypeScript stack support - PR rtk-ai#10: GitHub CLI integration - PR rtk-ai#11: Quota analysis features - PR rtk-ai#14: Additional command improvements All commands documented are available in v0.3.1. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
feat: add GitHub CLI integration (depends on rtk-ai#9)
Updates documentation to reflect all features added in recent PRs: **Version Updates** - Update installation commands to v0.3.1 (DEB/RPM packages) **New Sections** - Add Global Flags section (-u/--ultra-compact, -v/--verbose) - Add JavaScript/TypeScript Stack section (10 new commands) **New Commands Documented** - Files: `rtk smart` (heuristic code summary) - Commands: `rtk gh` (GitHub CLI), `rtk wget`, `rtk config` - Data: `rtk gain --quota` and `--tier` flags - Containers: `rtk kubectl services` - JS/TS Stack: lint, tsc, next, prettier, vitest, playwright, prisma **Features Coverage** This update documents functionality from: - PR rtk-ai#5: Git argument parsing improvements - PR rtk-ai#6: pnpm support - PR rtk-ai#9: Modern JavaScript/TypeScript stack support - PR rtk-ai#10: GitHub CLI integration - PR rtk-ai#11: Quota analysis features - PR rtk-ai#14: Additional command improvements All commands documented are available in v0.3.1. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Change run_rails_filtered to accept impl Fn instead of fn pointer, enabling closures that capture local state (issue rtk-ai#9) - Refactor run_routes to use run_rails_filtered with closure capturing has_grep flag (~40 lines removed) - Refactor run_generate to use run_rails_filtered with closure capturing generator_type/generator_name (~30 lines removed) - Replace double MOUNTED_ENGINES iteration (.any + .find) with single .find() call (issue rtk-ai#10) - Replace BTreeMap with HashMap for namespaces in filter_rails_routes since data is re-sorted by count anyway (issue rtk-ai#12) - Remove redundant !t.is_empty() check since t.len() > 1 implies non-empty (issue rtk-ai#13) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cette PR sera prête pour review après le merge de #9, car elle dépend du module partagé
utils.rsintroduit dans #9.Résumé
Ajoute la commande
rtk ghpour les opérations GitHub CLI avec filtrage intelligent de l'output, optimisé pour les reviews de PR et le monitoring CI dans le contexte LLM.Commandes
Système d'Optimisation à Deux Niveaux
Level 1 (Conservatif - Par défaut):
Level 2 (Modéré - Flag
-u):Métriques de Token Savings
Validé sur repo de production (methode-aristote/app):
Caractéristiques Techniques
--ultra-compactpour activer Level 2utils.rs(module partagé avec PR feat: add modern JavaScript tooling support (lint, tsc, next, prettier, playwright, prisma) #9)Changements
2 commits avec séparation claire:
3 fichiers modifiés, 752 insertions
Suit les patterns RTK existants
Zero breaking changes
Clippy-clean pour les nouveaux modules
Plan de Test
Pourquoi PR Séparée ?
Je convertirai cette PR en "ready for review" dès que #9 sera mergée.
🤖 Generated with Claude Code