235 lines
8.1 KiB
Plaintext
235 lines
8.1 KiB
Plaintext
|
|
#!/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
|
||
|
|
}
|