feat: #105 skim pkg — package manager parsers#107
Conversation
…audit) Add `skim pkg` subcommand with four package manager parsers following the three-tier degradation pattern (JSON -> regex -> passthrough): - npm: install, audit, outdated, ls (JSON + regex tiers) - pnpm: install (regex), audit (JSON), outdated (JSON) - pip: install (regex), check (regex), list --outdated (JSON) - cargo: audit (JSON + regex tiers) Also adds: - PkgResult/PkgOperation canonical types in output/canonical.rs - CommandType::Pkg variant in analytics - 17 rewrite rules for npm/pnpm/pip/pip3/cargo audit - 22 E2E pkg parser tests + 18 E2E rewrite tests - 41 unit tests across all four parsers - 15 fixture files with realistic tool output Co-Authored-By: Claude <noreply@anthropic.com>
Code Review: Architecture & Complexity Issues1. Duplicated
|
Code Review: Correctness & Regression Issues1.
|
Code Review: Test Coverage & Performance Issues1. Missing Unit Tests for 16 New Rewrite Rules (90% confidence)Severity: BLOCKING - Test Coverage Gap The PR adds 16 new The comment was updated from "all 15 rules" to "all 32 rules" but no matching unit tests were added—this is misleading. Fix: Add unit tests following the existing pattern: #[test]
fn test_npm_audit() {
let result = try_rewrite(&["npm", "audit"]).unwrap();
assert_eq!(result.tokens, vec!["skim", "pkg", "npm", "audit"]);
}
#[test]
fn test_npm_i() {
let result = try_rewrite(&["npm", "i", "express"]).unwrap();
assert_eq!(result.tokens, vec!["skim", "pkg", "npm", "install", "express"]);
}
#[test]
fn test_npm_list() {
let result = try_rewrite(&["npm", "list"]).unwrap();
assert_eq!(result.tokens, vec!["skim", "pkg", "npm", "ls"]);
}
#[test]
fn test_pnpm_i() {
let result = try_rewrite(&["pnpm", "i"]).unwrap();
assert_eq!(result.tokens, vec!["skim", "pkg", "pnpm", "install"]);
}
// Add similar tests for: npm ci, npm install, npm outdated, pnpm audit, pnpm outdated,
// pip install, pip check, pip list, pip3 install, pip3 check, pip3 list, cargo auditAlso add category validation: #[test]
fn test_pkg_category_for_npm_audit() {
let result = try_rewrite(&["npm", "audit"]).unwrap();
assert_eq!(result.category, RewriteCategory::Pkg);
}2. Cargo Audit Unnecessarily Clones Entire Vulnerability List (82% confidence)Severity: BLOCKING - Performance The code does Current code: let list = vulns
.get("list")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();Fix: Use a reference instead: let empty = vec![];
let list = vulns
.get("list")
.and_then(|v| v.as_array())
.unwrap_or(&empty);3. Dead Code:
|
Summary: Code Review FindingsBlocking Issues (Must Fix Before Merge)
Total Blocking: 6 issues across architecture, correctness, regression, and test coverage. Should-Fix Issues (Recommended)
Lower-Confidence Suggestions (60-79%)These appeared in only 1-2 review reports and can be addressed separately or in a follow-up:
Consolidated Blockers by PriorityPriority 1 (Data Correctness & API Contracts):
Priority 2 (Code Quality & Maintenance): Priority 3 (Recommended Cleanup): Review Coverage
Overall Recommendation: CHANGES_REQUESTED The three-tier degradation pattern, rewrite rule integration, and canonical output types are well-architected. The issues are fixable: dropped arguments, undercounting totals, missing aliases, code duplication, and test gaps. None are design problems—just implementation details. Suggested Merge Checklist
Review completed with 8 independent report analysis and 100% deduplication of findings. |
- Add 34 unit tests for all 18 Pkg rewrite rules (cargo audit, npm audit/install/i/ci/outdated/list/ls, pnpm audit/install/i/outdated, pip install/check/list, pip3 install/check/list) including skip-flag and category assertions - Add two missing rewrite aliases: npm list -> skim pkg npm ls (skip: --json) and pnpm i -> skim pkg pnpm install (skip: none) - Remove unnecessary .cloned() deep copy in cargo audit JSON parser; use borrowed reference with empty vec fallback instead - Refactor combine_output into shared Cow-based helper in pkg/mod.rs to eliminate duplication across cargo/npm/pip/pnpm parsers Co-Authored-By: Claude <noreply@anthropic.com>
…ip args, fix vuln counts - Remove dead-code guards in pip and pnpm run() — replace unreachable let-else with .expect() since args.is_empty() is already checked - Apply cargo fmt to npm.rs via-array handling block Co-Authored-By: Claude <noreply@anthropic.com>
) Split npm.rs (787 lines) into npm/ directory with 5 files: - mod.rs (67 lines): dispatcher + re-exports - install.rs (249 lines): npm install parser + tests - audit.rs (270 lines): npm audit parser + tests - outdated.rs (165 lines): npm outdated parser + tests - ls.rs (156 lines): npm ls parser + tests Extract extract_vuln_detail() helper from cargo.rs try_parse_audit_json, reducing it from 91 to ~69 lines. Pure structural refactor — zero behavioral changes. All existing tests pass.
Filenames already communicate purpose after the split — section headers like "// npm install" are noise in dedicated files.
The function always returns Some since all fields have unwrap_or fallbacks. Updated doc to accurately reflect this behavior.
crates/rskim/src/cmd/pkg/cargo.rs
Outdated
| /// Extract a single vulnerability entry from cargo audit JSON. | ||
| /// Returns `(detail_string, severity_str)`. Missing fields fall back to | ||
| /// `"unknown"` / `"?"` so this always returns `Some` in practice. | ||
| fn extract_vuln_detail(vuln: &serde_json::Value) -> Option<(String, &str)> { |
There was a problem hiding this comment.
Type Honesty Issue: The function signature promises Option<(String, &str)> but always returns Some(...) (85% confidence).
All code paths end with Some(...) and the doc comment correctly notes "this always returns Some in practice". The Option return type is misleading.
Suggested fix: Change return type to (String, &str) since it cannot fail:
fn extract_vuln_detail(vuln: &serde_json::Value) -> (String, &str) {
// ... same body ...
(detail, severity)
}Update the call site to a simple let (detail, severity) = extract_vuln_detail(vuln);
Comment from code review (claude.ai/code)
| fn try_parse_install_json(stdout: &str) -> Option<PkgResult> { | ||
| let value: serde_json::Value = serde_json::from_str(stdout).ok()?; | ||
|
|
||
| let added = value.get("added").and_then(|v| v.as_u64()).unwrap_or(0) as usize; |
There was a problem hiding this comment.
Unsafe Type Conversion: Three lines cast u64 to usize via as usize (82% confidence).
On 32-bit targets, usize is 32 bits and values above u32::MAX would silently truncate. While package counts this large are unrealistic, the as cast is a canonical Rust footgun for silent data loss.
Suggested fix: Use try_from with fallback:
let added = value.get("added")
.and_then(|v| v.as_u64())
.and_then(|v| usize::try_from(v).ok())
.unwrap_or(0);Apply the same pattern to all three u64 -> usize conversions (lines 74-76).
Comment from code review (claude.ai/code)
crates/rskim/src/cmd/pkg/cargo.rs
Outdated
| "audit" => run_audit(subcmd_args, show_stats, json_output), | ||
| other => { | ||
| eprintln!( | ||
| "skim pkg cargo: unknown subcommand '{other}'\n\ |
There was a problem hiding this comment.
Security: Terminal Escape Injection: User-controlled input is echoed directly to stderr without sanitization (80% confidence).
When an unrecognized subcommand is provided, the tool name is echoed via eprintln!("... unknown tool '{tool}' ..."). A malicious argument containing ANSI escape sequences could be used for terminal injection (e.g., change terminal title, clear screen on vulnerable emulators).
This is limited risk for a local CLI tool, but violates defense-in-depth principles.
Suggested fix: Sanitize non-printable characters before echoing:
let sanitized: String = tool.chars()
.map(|c| if c.is_ascii_graphic() || c == ' ' { c } else { '?' })
.collect();
eprintln!("skim pkg: unknown tool '{sanitized}'");Apply to all unknown-subcommand error messages in this file and related dispatchers.
Comment from code review (claude.ai/code)
Code Review Summary: feat-105-pkg-parsersOverviewThis PR adds comprehensive package manager support to skim ( High-Confidence Blockers (≥80%)1. Missing unit tests for
2. Missing three-tier integration tests for npm outdated/ls (Tests: 82%)
3. Cargo env var inconsistency (Consistency: 82%)
4. Repetitive
5. Cargo audit triple-regex-pass (Performance: 82%)
6. Inconsistent module structure (Architecture: 82%)
Medium-Confidence Findings (60-79%)Test Helpers Duplication: The Missing Edge Case Tests:
Pre-Existing Issues (informational):
Strengths✅ Excellent architecture: Strategy pattern with consistent dispatcher design across all four tools RecommendationAPPROVED WITH CONDITIONS: Address the 6 high-confidence findings before merge. Most are straightforward fixes (unit tests, consistency updates, helper extraction). The architecture is solid and follows established patterns well. Review generated by claude.ai/code |
Add tests for extract_json_flag (flag present/absent) and combine_output (borrowed/owned paths) in pkg/mod.rs. Add three-tier integration tests (json->Full, garbage->Passthrough) for npm outdated and npm ls parsers, matching install.rs pattern. Co-Authored-By: Claude <noreply@anthropic.com>
- Change extract_vuln_detail return from Option<(String, &str)> to plain (String, &str) since all fields have unwrap_or fallbacks and the function never returns None - Replace `as usize` casts in npm install parser with usize::try_from().ok() to avoid silent truncation on 32-bit targets - Include co-located batch fixes: sanitize user input in error messages, use CARGO_TERM_COLOR env var, add three-tier integration tests Co-Authored-By: Claude <noreply@anthropic.com>
Deduplicate three identical u64-to-usize conversion chains into a single helper function.
…parser - Add PkgSubcommandConfig + run_pkg_subcommand helper in pkg/mod.rs - Migrate all 10 run_* functions to use the shared helper - Rewrite try_parse_audit_regex from triple-regex to block-based parsing (handles missing fields and reordered fields correctly) - Remove dead regex statics (RE_CRATE, RE_TITLE, RE_ADVISORY_ID) - Add failing tests for missing-field and reordered-field cases (TDD) Resolves tech debt items 5-6 from PR #107 review. Co-Authored-By: Claude <noreply@anthropic.com>
…ith tech debt fixes Merges feat/104-lint-parsers and feat/105-pkg-parsers into a single consolidation branch. Resolves merge conflicts (additive — both PRs add independent modules, types, and tests to shared files). Post-merge fixes: - Add output_format field to pkg's ParsedCommandConfig construction - Add Serialize bound to run_pkg_subcommand generic parameter - Reconstruct mangled struct/impl/test boundaries in canonical.rs Tech debt resolved (items 1-3 from PR #106): - Unified lint JSON mode into shared run_parsed_command_with_mode - Extracted run_linter helper, migrated all 4 linters - Deleted run_lint_json_mode + LintJsonConfig Tech debt resolved (items 5-6 from PR #107): - Extracted run_pkg_subcommand helper, migrated all 10 functions - Block-based cargo audit parser replaces fragile triple-regex - Removed dead regex statics
## Summary - **Items 1-3 (PR #106):** Unified lint JSON mode into shared `run_parsed_command_with_mode` via `OutputFormat` enum. Extracted `run_linter` helper. Migrated all 4 linters (eslint, ruff, mypy, golangci). Deleted `run_lint_json_mode` + `LintJsonConfig`. - **Item 5 (PR #107):** Extracted `run_pkg_subcommand` helper. Migrated all 10 `run_*` functions across npm, pnpm, pip, cargo. - **Item 6 (PR #107):** Replaced fragile triple-regex cargo audit parser with block-based parsing. Handles missing/reordered fields correctly. Removed dead regex statics. Net reduction: ~150 lines of duplicated infrastructure eliminated. ## Test plan - [x] `cargo test` — all tests pass (913+ unit, 45+ integration, 11 doc tests) - [x] `cargo clippy -- -D warnings` — clean - [x] `cargo fmt -- --check` — clean - [x] Quality gates passed: Validator, Simplifier, Scrutinizer, Shepherd (both PRs) --------- Co-authored-by: Dean Sharon <deanshrn@gmain.com> Co-authored-by: Claude <noreply@anthropic.com>
|
Merged via consolidation PR #108 |
Summary
skim pkgsubcommand with four package manager parsers: npm, pnpm, pip, cargo auditskim rewriteChanges
New Types
PkgResult/PkgOperationcanonical types inoutput/canonical.rs(Install, Audit, Outdated, Check, List operations)CommandType::Pkgvariant in analytics moduleParsers (4 tools, 13 subcommands)
Rewrite Rules (17 new rules)
npm audit/install/i/ci/outdated/ls->skim pkg npm ...pnpm audit/install/outdated->skim pkg pnpm ...pip install/check/list->skim pkg pip ...pip3 install/check/list->skim pkg pip ...cargo audit->skim pkg cargo audit--json,--formatprevent rewrite when user already uses structured outputTests
Test plan
cargo clippy -- -D warningscleancargo fmt -- --checkcleanskim pkg,skim pkg npm, etc.Closes #105