feat: add offline mode and recommendation search#8
Open
aaronlippold wants to merge 80 commits intomainfrom
Open
feat: add offline mode and recommendation search#8aaronlippold wants to merge 80 commits intomainfrom
aaronlippold wants to merge 80 commits intomainfrom
Conversation
Offline Mode: - Add --offline global flag to prevent network calls - Add 'cache status' command to show cached data - Block auth login, catalog refresh, download, get when offline - 14 TDD tests in tests/unit/test_offline_mode.py Recommendation Search: - Add 'find' command for FTS5 search within recommendations - Search titles, descriptions, audit, remediation, controls - Add recommendations_fts table indexed on download - Support --benchmark, --profile filters - 15 TDD tests in tests/unit/test_recommendation_search.py Docs: - Updated commands-reference.md with both features - Updated CLAUDE.md to use uv instead of pip Authored by: Aaron Lippold<lippold@gmail.com>
- Add 'diff' command to compare benchmark versions - Use DeepDiff library for structural comparison - Match recommendations by ref ID (handles reordering) - Detect renumbered recommendations via title similarity - Output formats: table, json, markdown, summary - 15 TDD tests in tests/unit/test_benchmark_diff.py - Add deepdiff>=8.0.0 to dependencies Change types detected: - Added: New recommendations - Removed: Deleted recommendations - Modified: Same ref, content changed - Renumbered: Similar title (>85%), different ref Authored by: Aaron Lippold<lippold@gmail.com>
The diff command now automatically fetches benchmarks from CIS WorkBench if they are not cached locally. This makes comparing benchmark versions simpler - just provide two IDs and the tool handles the rest. - Auto-fetch when benchmark not found locally - Respects --offline flag to prevent network calls - Caches fetched benchmarks for future use - Clear error messages for auth requirements - Updated documentation with new examples Authored by: Aaron Lippold<lippold@gmail.com>
- Remove fixed column widths, let Rich auto-size based on terminal - Use ratio-based column sizing for Title (3:1 ratio) - Show abbreviated field changes with "+N more" for long lists - Table now expands to use available terminal width Authored by: Aaron Lippold<lippold@gmail.com>
- "Added" instead of "ADD" - "Removed" instead of "DEL" - "Modified" instead of "MOD" - "Renumbered" instead of "REN" - More descriptive Changes column text Authored by: Aaron Lippold<lippold@gmail.com>
- Add diff_tui.py: Interactive side-by-side diff browser with Textual - Add view.py/view_tui.py: New `cis-bench view` command to browse benchmarks - Add tui_base.py: Shared TUI components (DetailView, SaveDialog, html_to_markdown) - Add utils.py: Shared CLI utilities (load_benchmark, output_with_pager, auto_fetch) - Refactor diff.py to use shared utilities (DRY) TUI Features: - Side-by-side layout (40% list, 60% detail) - Natural sorting for CIS refs (1.1 < 1.2 < 1.10 < 2.1) - Reverse sort toggle (r key) - HTML to Markdown conversion for readable content - Skip visually identical fields in diffs (no encoding noise) - Auto-pager for non-interactive output (GNU/Unix standard) - Save report feature (s key) - Fullscreen toggle (f key) - Keyboard navigation (j/k, arrows, tab, page up/down) View command options: - Filter by --profile and --status - Auto-fetch from WorkBench if not cached - TTY auto-detection for interactive/table mode Tests: 41 new tests for TUI functionality Authored by: Aaron Lippold <lippold@gmail.com>
Document the beads workflow for this project: - Parent/child vs dependencies concepts - Creating epics with child tasks - Navigation commands (bd children, bd ready, bd blocked) - Session recovery workflow Authored by: Aaron Lippold <lippold@gmail.com>
- Update CLAUDE.md with proper uv run commands for all dev tools - Update contributing.md with uv run for pytest, ruff, pre-commit - Update testing.md with uv run commands and test count (1200+) - Update getting-started.md to recommend uv sync workflow - Add direnv auto-activation documentation for optional venv activation This ensures developers use the correct .venv environment instead of system Python, preventing "module not found" errors. Authored by: Aaron Lippold<lippold@gmail.com>
- Add HelpScreen modal class to tui_base.py - Display all keyboard shortcuts when user presses '?' - Close with Escape, '?', or 'q' - Add CSS styling for help dialog - Add action_show_help to BaseBrowserApp - Add 9 tests for help screen functionality Authored by: Aaron Lippold<lippold@gmail.com>
- Add offline parameter to DiffApp and ViewApp - Show [OFFLINE] indicator in summary bar when --offline flag active - Wire up offline parameter through diff.py and view.py commands - Update view_tui to use COMMON_BINDINGS (fixes missing ? help key) - Add 4 tests for offline indicator functionality Authored by: Aaron Lippold<lippold@gmail.com>
- Add SearchInput widget class for real-time filtering - Add '/' binding to COMMON_BINDINGS for search - Implement _apply_search_filter in DiffApp - Show match count while searching - Escape cancels search and restores all items - Add 6 tests for search functionality Authored by: Aaron Lippold<lippold@gmail.com>
- Document view command with profile and status filters - Add Interactive TUI Mode section with keyboard shortcuts - Document search functionality (/ key) and offline indicator - Add Scenario 15: Interactive Benchmark Exploration workflow Authored by: Aaron Lippold<lippold@gmail.com>
- Add TUI Layer section to architecture.md with component diagram - Add Textual to technology stack - Add TUI file structure to file organization - Add view/diff commands to CLI commands list - Add mkdocs-autorefs dependency (fixes build) Authored by: Aaron Lippold<lippold@gmail.com>
- Add jump to ref (g key) with JumpDialog - Add copy to clipboard (c key) using pyperclip - Add filter by change type (1-4, 0) in diff TUI - Mouse click already works via Textual DataTable Tests: 74 passing (14 new) Authored by: Aaron Lippold<lippold@gmail.com>
- Add multi-select support (space key) with selection tracking - Update commands-reference.md with all new TUI features: - Jump to ref (g key) - Copy to clipboard (c key) - Multi-select (space key) - Filter by change type (1-4, 0) Tests: 78 passing (4 new for multi-select) Authored by: Aaron Lippold<lippold@gmail.com>
Implements interactive catalog browser TUI: - CatalogBrowserApp extends BaseBrowserApp for consistent UX - CatalogDetailView shows benchmark metadata - Search, multi-select, jump-to-ID from shared components - New 'cis-bench browse' CLI command with filters Features: - Browse catalog with DataTable (ID, Title, Version, Platform, Published) - Real-time search filter (/ key) - Multi-select for batch operations (Space key) - Jump to benchmark ID (g key) - Star marker for latest versions Includes 21 unit tests following TDD approach. Authored by: Aaron Lippold<lippold@gmail.com>
Fixes AttributeError when description field is None during search. Authored by: Aaron Lippold<lippold@gmail.com>
1. Inline version with title - version always visible regardless of width 2. Selection checkbox column - visual indicator (●/○) for selected items 3. Enter key opens action menu - discoverable Download/View/Diff/Export 4. Responsive layout - hides detail pane on narrow terminals (<80 cols) Also: - Summary shows selection count - Search filter includes version matching Authored by: Aaron Lippold<lippold@gmail.com>
Reorganizes TUI code into modular structure: - tui/base.py: BaseBrowserApp, DetailView, COMMON_CSS, helpers - tui/widgets.py: SearchInput, HelpScreen, JumpDialog, SaveDialog - tui/catalog/: CatalogBrowserApp, CatalogDetailView, ActionMenu Benefits: - Clear separation of concerns - Easier to add new TUI types (diff, view) - DRY shared components - Better testability Old imports (tui_base.py, catalog_tui.py) re-export from new locations for backwards compatibility. Authored by: Aaron Lippold<lippold@gmail.com>
- Extract DiffDetailView and DiffApp to tui/diff/ module - Extract ViewDetailView and ViewApp to tui/view/ module - Convert diff_tui.py and view_tui.py to re-export shims - All 1269 tests passing TUI package structure now complete: tui/ ├── base.py # BaseBrowserApp, DetailView, COMMON_CSS ├── widgets.py # SearchInput, HelpScreen, JumpDialog, SaveDialog ├── catalog/ # CatalogBrowserApp ├── diff/ # DiffApp, DiffDetailView └── view/ # ViewApp, ViewDetailView Backwards compatibility maintained via shim files. Authored by: Aaron Lippold<lippold@gmail.com>
- Delete diff_tui.py, view_tui.py, tui_base.py, catalog_tui.py shims - Update CLI commands to import from tui/ package directly - Update tests to import from tui/ package directly Not released yet, so no backwards compatibility needed. Authored by: Aaron Lippold<lippold@gmail.com>
Extract shared functionality to BaseBrowserApp: - compose() with get_detail_view() hook and has_search_container attr - on_mount() with _get_columns() and _populate_table() hooks - on_row_highlighted() with _show_detail() hook - COMMON_CSS extended via CATALOG_CSS = COMMON_CSS + overrides - COMMON_BINDINGS extended via BINDINGS = COMMON_BINDINGS + [...] - Standardized _items/_all_items naming across all apps Removes ~300 lines of duplicated code from catalog/diff/view apps. All 106 TUI tests passing. Authored by: Aaron Lippold<lippold@gmail.com>
- action_reverse_sort() moved to BaseBrowserApp, calls abstract _rebuild_table() - Selection tracking via supports_selection attr and _on_selection_changed() hook - get_selected_items() now in base class - Removed duplicate code from all 3 app files All 114 TUI tests passing. Authored by: Aaron Lippold<lippold@gmail.com>
- Remove disabled checks from ActionMenu View/Export buttons (auto-fetch handles missing content via load_benchmark()) - Update _handle_action() to exit with action info for runner - Add run_catalog_browser action loop to launch appropriate TUI - Add direct keybindings: v=view, d=diff, e/s=export (skip menu) - Diff action validates exactly 2 benchmarks selected - After ViewApp/DiffApp exits, user returns to catalog browser Closes ep9.8 (View action), ep9.9 (Diff action) Authored by: Aaron Lippold<lippold@gmail.com>
- Show pending notifications when returning to catalog after error - Add on_mount handler to display errors from previous action - Print loading message to console while fetching - Differentiate FileNotFoundError from general errors Authored by: Aaron Lippold<lippold@gmail.com>
This refactor is incomplete and has bugs. DO NOT USE IN PRODUCTION. Changes attempted: - Convert exit/restart loop to push_screen pattern - Create ViewScreen/DiffScreen in screens.py - Add @work decorators for async loading - Update CatalogBrowserApp to push screens instead of exit Known issues: - DiffScreen._show_detail() has method signature mismatch - No integration tests to catch runtime errors - Code needs further debugging Authored by: Aaron Lippold<lippold@gmail.com>
Add asyncio_mode and asyncio_default_fixture_loop_scope settings to support async TUI tests using Textual's app.run_test(). Authored by: Aaron Lippold<lippold@gmail.com>
BaseBrowserScreen: - Screen-based equivalent of BaseBrowserApp for SPA navigation - Uses action_go_back() instead of action_quit() for proper screen stack - SCREEN_BINDINGS with Escape/q for back navigation LoadingModal: - Progress bar with percentage and status message - Cancellation support via Escape key - update_progress() for worker thread updates via call_from_thread() - is_cancelled property for cooperative cancellation Re-exports added to __init__.py for clean imports. Part of TUI SPA refactor (cis-bench-247) Authored by: Aaron Lippold<lippold@gmail.com>
DiffDetailView.update_content() expected change_data['new_ref'] but DiffScreen only provided 'ref' key for renumbered items. Added 'new_ref' key to renumbered items dict to match expected format. Authored by: Aaron Lippold<lippold@gmail.com>
- _load_and_view() shows LoadingModal with progress during benchmark load - _load_and_diff() shows LoadingModal for both benchmarks during diff - Workers update progress via call_from_thread() - User can cancel with Escape key - Proper cleanup of modal on success, error, or cancellation Uses @work decorator for background loading with thread=True. Part of TUI SPA refactor (cis-bench-247) Authored by: Aaron Lippold<lippold@gmail.com>
Add trailing two spaces to each field for markdown line breaks. Each detail field now displays on its own line for readability. Before: "ID: 11511 Platform: almalinux Community: ..." After: ID: 11511 Platform: almalinux Community: CIS AlmaLinux Authored by: Aaron Lippold<lippold@gmail.com>
Fixed mark_latest_versions() to properly identify latest version within each benchmark title group (e.g., AlmaLinux OS 8 vs OS 9 vs OS 10). Issues fixed: - Used CAST(benchmark_id AS INTEGER) for numeric comparison (was doing string comparison: "9794" > "23598") - Excluded vNext pre-release versions from being marked latest - Handles NULL published_date by using benchmark_id as proxy Also improved detail pane formatting with markdown line breaks. Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
Moves the ★ indicator to its own column for cleaner display. Column order: checkbox, cached, ID, Title, ★, Platform Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Add separate Version column between Title and Latest - Change catalog browser split from 45/55 to 65/35 - View and Diff screens keep their existing 40/60 split Columns: checkbox, cached, ID, Title, Version, Latest, Platform Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Add 6 tests for mark_latest_versions() logic: - Single group marks highest benchmark_id - Multiple groups each have own latest - Next versions excluded from latest - NULL versions excluded from latest - Integer comparison (not string) for benchmark_id - Resets previous latest flags on re-run - Fix test_catalog_css_has_overrides: 45/55% -> 65/35% - Fix test_columns_include_cached_status: 5 -> 7 columns Authored by: Aaron Lippold<lippold@gmail.com>
Sorting: - Sort by title alphabetically, then newest first within each group - Consistent sorting in init, rebuild, and search filter Detail pane improvements: - Add Dates section (published, last revision) - Add Classification section (platform, type, community, owner) - Use plain URL instead of markdown link (not clickable in terminal) Tests: - Update tests to check set membership instead of exact order Authored by: Aaron Lippold<lippold@gmail.com>
- Add action_open_in_browser using Textual's open_url() - Add keybinding 'o' for "Open URL" - Update detail pane with hint: "Press 'o' to open in browser" - Add tests for binding and action Authored by: Aaron Lippold<lippold@gmail.com>
- Add Published column showing publication date (YYYY-MM-DD) - Reduce title width slightly to fit new column - Update test to expect 8 columns Authored by: Aaron Lippold<lippold@gmail.com>
Database: - Add collections (GROUP_CONCAT) to _list_all and search queries - Add last_revision_date to both queries Detail pane: - Show Collections in Classification section Authored by: Aaron Lippold<lippold@gmail.com>
Add comprehensive metadata extraction from CIS WorkBench detail pages. - Parse web component JSON attributes (wb-benchmark-assets, wb-recommendation-data) - Extract from real HTML structure with proper whitespace handling - WebComponentDataExtractor utility for DRY reusable web component parsing - 4 real HTML fixtures (OS/Cloud/Container/Database, 393KB) - 102 parser tests passing (inc. 4 real HTML regression tests) Authored by: Aaron Lippold <lippold@gmail.com>
Expand Pydantic Benchmark model with 13 new metadata fields. New Models: - BenchmarkAsset: title, cpe_id - RevisionHistoryEntry: date, author, modification_count, diff_url New Benchmark Fields: - published_date, published_relative, release_type - contributors list, contributors_str property (for XCCDF) - parent_benchmark_url, parent_benchmark_title - community_url, milestone_name, milestone_url - intended_audience, acknowledgements - assets list, revision_history list Tests: 17 Pydantic model tests passing Authored by: Aaron Lippold <lippold@gmail.com>
Add database tables and columns for extended metadata with auto-migration. New Tables: - benchmark_assets: FK to benchmark, title, cpe_id - revision_history: FK to benchmark, date, author, modifications, diff_url New Columns on catalog_benchmarks: - release_type, contributors, parent_benchmark_id, parent_benchmark_url - intended_audience, acknowledgements, milestone_name, milestone_url Migration: - Auto-runs on database open (adds columns + creates tables) - Idempotent (safe to run multiple times) - Updates FTS5 with new searchable fields (contributors, intended_audience) Refactoring: - get_benchmark() uses model_dump() (dynamic, not hardcoded dict) - Query improvements: _list_all() and search() include all new columns Tests: 60 database tests passing Authored by: Aaron Lippold <lippold@gmail.com>
Create shared EnrichmentService used by both download paths. Architecture (DRY): - EnrichmentService.enrich_catalog_entry() - Single source of truth - Used by: cis-bench download, cis-bench catalog download - Prevents code duplication between download paths Enrichment Process: - Updates catalog_benchmarks with metadata from downloaded Benchmark - Populates benchmark_assets table with CPE-IDs - Populates revision_history table with revision entries - Clears old data before inserting (handles updates correctly) Integration: - Main download command now enriches catalog (if catalog DB exists) - Catalog download command uses same shared service - Future refresh command will use same service Tests: 9 enrichment tests passing Authored by: Aaron Lippold <lippold@gmail.com>
Update WorkbenchScraper to populate all new Pydantic model fields from parser. Changes: - get_benchmark_metadata() uses parse_benchmark_detail_page() for all fields - download_benchmark() populates all 13 new Benchmark fields from metadata - Logs published_date and release_type during download Tests: 32 scraper tests passing (30 existing + 2 new for extended metadata) Authored by: Aaron Lippold <lippold@gmail.com>
Update all exporters to include complete benchmark metadata. XCCDF (Critical): - CPE platform-specification from benchmark.assets - Published date in Dublin Core (dc:date) - Release type in Dublin Core (dc:type) - Contributors in Dublin Core (dc:creator) - create_platforms() method with version-specific types Markdown: - Publication info, Lineage, Contributors, Assets/CPE-IDs, Description sections CSV: - New benchmark columns: published_date, release_type, contributors, cpe_ids, parent JSON/YAML: - Auto-included via model_dump() (no changes needed) Tests: 54+ exporter tests passing (inc. 4 new XCCDF metadata tests) Authored by: Aaron Lippold <lippold@gmail.com>
Update CLI and TUI to display all extended benchmark metadata. CLI info command: - Shows all extended metadata (published, release_type, parent, CPE, contributors) - Supports multiple files (handles wildcards) - Exits with error code if any file fails TUI catalog detail pane: - Publication section (published, release_type, milestone) - Lineage section (forked from with URL) - Contributors, Intended Audience, Acknowledgements sections - Enhanced description display CLI/TUI Parity: - Both interfaces show same metadata - Consistent formatting and organization Authored by: Aaron Lippold <lippold@gmail.com>
Architecture: - Catalog tab: browse benchmarks, view details, actions (v/d/e/o) - Operations tab: bulk download, export, catalog refresh (placeholder) - Settings tab: auth, preferences, about (placeholder) Implementation: - TABS config as single source of truth for tab structure - Tests use config helpers (get_tab_count, get_tab_labels) - CatalogTabPane with focus toggle between table/detail pane - BaseTabPane with common bindings for all tab panes - Research doc on TabbedContent focus management 16 integration tests passing. Authored by: Aaron Lippold<lippold@gmail.com>
Migrated from standalone catalog/app.py: - Selection system: _selected_indices, action_toggle_select, get_selected_items - Downloaded status: _downloaded_ids, _load_downloaded_ids, ✓ indicator - Open in browser: action_open_in_browser via self.app.open_url() Tests adapted from test_catalog_browser.py (14 new tests, 30 total passing). Authored by: Aaron Lippold<lippold@gmail.com>
- Checkbox: cyan bold when selected, dim when not - Downloaded: green checkmark - Latest: yellow bold star Matches standalone catalog/app.py styling. Authored by: Aaron Lippold<lippold@gmail.com>
- Title: bold cyan - Version: bold green - Status: green dot for Published, yellow for other - Latest: yellow star badge - ID: cyan - URL in code block - Updated action hints Uses Rich Group to combine styled Text with Markdown sections. Authored by: Aaron Lippold<lippold@gmail.com>
Phase 2b - Catalog Tab Actions: - Add view action (v key) → ViewScreen via app.push_screen() - Add diff action (d key) → DiffScreen for 2 selected benchmarks - Add export action (e key) → ExportConfigDialog flow - Add action menu (Enter key) → ActionMenu modal - Use 'R' (Shift) for refresh (UX: uppercase for network ops) Phase 2c - Test Reorganization: - Split tests by type following Python best practices - Unit tests (23) → tests/unit/test_catalog_tab_pane.py - Integration tests (16) → tests/integration/test_main_tui.py All 39 tests passing. Authored by: Aaron Lippold<lippold@gmail.com>
Document the established pattern for test file organization: - Unit tests (sync) in tests/unit/ - Integration tests (async) in tests/integration/ - Design decision: split mixed files by test type - Example from TUI test reorganization (Phase 2c) Added best practices: - Separate sync from async tests - Mirror source structure - One class per feature area Authored by: Aaron Lippold<lippold@gmail.com>
Add unit tests following established pattern: - TestTabConfig: dataclass structure tests - TestTabsConfig: TABS list validation (unique IDs, labels) - TestTabHelperFunctions: get_tab_labels, get_tab_count - TestMainTUIAppBindings: quit binding exists - TestMainTUIAppClass: title, CSS, compose, inheritance 18 new unit tests, 57 total TUI tests now. Authored by: Aaron Lippold<lippold@gmail.com>
Reference established test pattern in project CLAUDE.md for session context: - Unit vs integration test placement criteria - TUI test file structure with examples - Link to full testing.md documentation Ensures Claude Code sessions start with test org context. Authored by: Aaron Lippold<lippold@gmail.com>
Replace LoadingModal screen with inline ProgressBar + Label widgets. This eliminates the visible flash caused by push → pop → push sequence. Before: Catalog → push LoadingModal → pop LoadingModal → push ViewScreen (flash) After: Catalog → show inline progress → push ViewScreen (clean transition) Changes: - Add ProgressBar and Label widgets to CatalogTabPane compose() - Add _show_loading, _update_loading, _hide_loading helper methods - Add escape handling to cancel loading operations - Refactor view/diff/export flows to use inline progress - Remove LoadingModal dependency for view/diff/export operations Acceptance criteria met: - No visible flash when opening View screen - No visible flash when opening Diff screen - Progress percentage shown during load - Status text shown during operations - Escape cancels loading operation - Clean single-screen transition Authored by: Aaron Lippold<lippold@gmail.com>
ViewScreen and DiffScreen set has_search_container = False but still inherit the search binding. When pressing /, action_start_search tried to find #search-container which doesn't exist, causing NoMatches error. Fix: Check has_search_container before trying to access search widgets. Show "Search not available in this view" warning if not available. Authored by: Aaron Lippold<lippold@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--offlineglobal flag to prevent accidental network callscache statuscommand to show what's available offlinefindcommand for FTS5 full-text search within recommendation contentdiffcommand to compare benchmark versions using DeepDiffNew Commands
Offline Mode
Recommendation Search
Benchmark Diff
Test plan