#!/usr/bin/env nu # Display graph statistics and health metrics # # Usage: nu kogral-stats.nu [--format ] [--kogral-dir ] def main [ --format: string = "summary" # Output format: table, json, or summary --kogral-dir: string = ".kogral" # KOGRAL directory --show-tags # Show top tags --show-orphans # Show orphaned nodes (no relationships) ] { print $"(ansi green_bold)KOGRAL Statistics(ansi reset)" print $"KOGRAL Directory: ($kogral_dir)\n" # Check if .kogral directory exists if not ($kogral_dir | path exists) { print $"(ansi red)Error: KOGRAL directory not found: ($kogral_dir)(ansi reset)" exit 1 } # Collect statistics print $"(ansi cyan)Collecting statistics...(ansi reset)" let stats = collect_stats $kogral_dir # Display based on format match $format { "json" => { $stats | to json }, "table" => { display_table $stats }, "summary" => { display_summary $stats $show_tags $show_orphans }, _ => { print $"(ansi red)Error: Invalid format. Use: table, json, or summary(ansi reset)" exit 1 } } } def collect_stats [kogral_dir: string] -> record { # Count files by type let notes_files = glob $"($kogral_dir)/notes/**/*.md" let decisions_files = glob $"($kogral_dir)/decisions/**/*.md" let guidelines_files = glob $"($kogral_dir)/guidelines/**/*.md" let patterns_files = glob $"($kogral_dir)/patterns/**/*.md" let journal_files = glob $"($kogral_dir)/journal/**/*.md" let notes_count = $notes_files | length let decisions_count = $decisions_files | length let guidelines_count = $guidelines_files | length let patterns_count = $patterns_files | length let journal_count = $journal_files | length let total_files = $notes_count + $decisions_count + $guidelines_count + $patterns_count + $journal_count # Calculate sizes let notes_size = if $notes_count > 0 { ls $notes_files | get size | math sum } else { 0 } let decisions_size = if $decisions_count > 0 { ls $decisions_files | get size | math sum } else { 0 } let guidelines_size = if $guidelines_count > 0 { ls $guidelines_files | get size | math sum } else { 0 } let patterns_size = if $patterns_count > 0 { ls $patterns_files | get size | math sum } else { 0 } let journal_size = if $journal_count > 0 { ls $journal_files | get size | math sum } else { 0 } let total_size = $notes_size + $decisions_size + $guidelines_size + $patterns_size + $journal_size # Collect tags let all_files = $notes_files | append $decisions_files | append $guidelines_files | append $patterns_files | append $journal_files let tags = collect_tags $all_files # Load config let config_path = $"($kogral_dir)/config.toml" let config = if ($config_path | path exists) { open $config_path | from toml } else { { graph: { name: "unknown", version: "unknown" } } } # Health metrics let health = calculate_health $total_files $tags { graph: { name: $config.graph.name, version: $config.graph.version }, counts: { notes: $notes_count, decisions: $decisions_count, guidelines: $guidelines_count, patterns: $patterns_count, journal: $journal_count, total: $total_files }, sizes: { notes: $notes_size, decisions: $decisions_size, guidelines: $guidelines_size, patterns: $patterns_size, journal: $journal_size, total: $total_size }, tags: $tags, health: $health } } def collect_tags [files: list] -> record { mut tag_counts = {} for file in $files { try { let content = open $file let has_frontmatter = $content | str starts-with "---" if $has_frontmatter { # Extract tags from frontmatter let frontmatter_end = $content | str index-of "---\n" --end 1 if $frontmatter_end != -1 { let frontmatter = $content | str substring 0..$frontmatter_end # Look for tags line let tags_line = $frontmatter | lines | find -r "^tags:" | first | default "" if ($tags_line | str length) > 0 { # Parse tags array [tag1, tag2, ...] let tags = $tags_line | str replace "tags:" "" | str trim | str replace -a "[" "" | str replace -a "]" "" | str replace -a "\"" "" | split row "," for tag in $tags { let tag_clean = $tag | str trim if ($tag_clean | str length) > 0 { $tag_counts = ($tag_counts | upsert $tag_clean {|old| ($old | default 0) + 1 }) } } } } } } } let total_tags = $tag_counts | values | math sum let unique_tags = $tag_counts | columns | length let top_tags = $tag_counts | transpose tag count | sort-by count --reverse | first 10 { total: $total_tags, unique: $unique_tags, top: $top_tags } } def calculate_health [total_files: int, tags: record] -> record { # Health metrics let has_content = $total_files > 0 let has_diversity = $total_files > 10 let well_tagged = $tags.total > ($total_files * 0.5) let score = if $has_content and $has_diversity and $well_tagged { "Excellent" } else if $has_content and $has_diversity { "Good" } else if $has_content { "Fair" } else { "Poor" } { score: $score, has_content: $has_content, has_diversity: $has_diversity, well_tagged: $well_tagged } } def display_summary [stats: record, show_tags: bool, show_orphans: bool] { print $"(ansi cyan_bold)═══ Graph Information ═══(ansi reset)" print $"Name: ($stats.graph.name)" print $"Version: ($stats.graph.version)" print $"\n(ansi cyan_bold)═══ Node Counts ═══(ansi reset)" print $"Notes: ($stats.counts.notes)" print $"Decisions: ($stats.counts.decisions)" print $"Guidelines: ($stats.counts.guidelines)" print $"Patterns: ($stats.counts.patterns)" print $"Journal: ($stats.counts.journal)" print $"(ansi green_bold)Total: ($stats.counts.total)(ansi reset)" print $"\n(ansi cyan_bold)═══ Storage ═══(ansi reset)" print $"Total size: ($stats.sizes.total)" print $"\n(ansi cyan_bold)═══ Tags ═══(ansi reset)" print $"Total tags: ($stats.tags.total)" print $"Unique tags: ($stats.tags.unique)" if $show_tags and ($stats.tags.top | length) > 0 { print $"\nTop tags:" for tag in $stats.tags.top { print $" ($tag.tag): ($tag.count)" } } print $"\n(ansi cyan_bold)═══ Health Score ═══(ansi reset)" let health_color = match $stats.health.score { "Excellent" => "green_bold", "Good" => "green", "Fair" => "yellow", "Poor" => "red", _ => "white" } print $"Overall: (ansi $health_color)($stats.health.score)(ansi reset)" print $"Has content: ($stats.health.has_content)" print $"Has diversity: ($stats.health.has_diversity)" print $"Well tagged: ($stats.health.well_tagged)" } def display_table [stats: record] { let table_data = [ { metric: "Notes", value: $stats.counts.notes }, { metric: "Decisions", value: $stats.counts.decisions }, { metric: "Guidelines", value: $stats.counts.guidelines }, { metric: "Patterns", value: $stats.counts.patterns }, { metric: "Journal", value: $stats.counts.journal }, { metric: "Total Files", value: $stats.counts.total }, { metric: "Total Size", value: $stats.sizes.total }, { metric: "Unique Tags", value: $stats.tags.unique }, { metric: "Health", value: $stats.health.score } ] $table_data }