kogral/scripts/kogral-stats.nu

235 lines
8.1 KiB
Plaintext
Raw Permalink Normal View History

2026-01-23 16:12:50 +00:00
#!/usr/bin/env nu
# Display graph statistics and health metrics
#
# Usage: nu kogral-stats.nu [--format <table|json|summary>] [--kogral-dir <path>]
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
}