kogral/scripts/kogral-migrate.nu

219 lines
6.8 KiB
Plaintext
Raw Normal View History

2026-01-23 16:12:50 +00:00
#!/usr/bin/env nu
# Run schema migrations for KOGRAL
#
# Usage: nu kogral-migrate.nu [--target <version>] [--dry-run]
def main [
--target: string = "latest" # Target migration version
--dry-run # Show what would be migrated without making changes
--kogral-dir: string = ".kogral" # KOGRAL directory
] {
print $"(ansi green_bold)KOGRAL Migration(ansi reset)"
print $"Target version: ($target)"
print $"KOGRAL Directory: ($kogral_dir)"
if $dry_run {
print $"(ansi yellow)DRY RUN MODE - No changes will be made(ansi reset)"
}
# 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
}
# Load current version from config
let config_path = $"($kogral_dir)/config.toml"
if not ($config_path | path exists) {
print $"(ansi red)Error: Config file not found: ($config_path)(ansi reset)"
exit 1
}
let config = open $config_path | from toml
let current_version = $config.graph.version
print $"\n(ansi cyan_bold)Current schema version:(ansi reset) ($current_version)"
# Define available migrations
let migrations = [
{ version: "1.0.0", description: "Initial schema" },
{ version: "1.1.0", description: "Add metadata field to nodes" },
{ version: "1.2.0", description: "Add embedding support" },
]
print $"\n(ansi cyan_bold)Available migrations:(ansi reset)"
for migration in $migrations {
let indicator = if $migration.version == $current_version {
$"(ansi green)✓ [CURRENT](ansi reset)"
} else {
" "
}
print $"($indicator) ($migration.version) - ($migration.description)"
}
# Determine migrations to run
let target_version = if $target == "latest" {
$migrations | last | get version
} else {
$target
}
print $"\n(ansi cyan_bold)Target version:(ansi reset) ($target_version)"
if $current_version == $target_version {
print $"\n(ansi green)Already at target version. No migrations needed.(ansi reset)"
exit 0
}
# Find migrations to apply
let to_apply = $migrations | where version > $current_version and version <= $target_version
if ($to_apply | length) == 0 {
print $"\n(ansi yellow)No migrations to apply(ansi reset)"
exit 0
}
print $"\n(ansi cyan_bold)Migrations to apply:(ansi reset)"
for migration in $to_apply {
print $" → ($migration.version): ($migration.description)"
}
if $dry_run {
print $"\n(ansi yellow)[DRY RUN] Would apply ($to_apply | length) migration(s)(ansi reset)"
exit 0
}
# Apply migrations
print $"\n(ansi cyan_bold)Applying migrations...(ansi reset)"
mut final_version = $current_version
for migration in $to_apply {
print $"\n(ansi blue)Migrating to ($migration.version)...(ansi reset)"
apply_migration $migration $kogral_dir
$final_version = $migration.version
}
# Phase: Update version in config
print $"\n(ansi cyan_bold)Updating config version...(ansi reset)"
update_config_version $config_path $final_version
print $"\n(ansi green_bold)✓ Migration completed(ansi reset)"
print $"Schema version: ($current_version) → ($final_version)"
}
def apply_migration [migration: record, kogral_dir: string] {
match $migration.version {
"1.0.0" => {
print " ✓ Initial schema (no action needed)"
},
"1.1.0" => {
# Phase 1: Add metadata field to existing nodes
print " Adding metadata field support..."
add_metadata_field $kogral_dir
print " ✓ Metadata field added"
},
"1.2.0" => {
# Phase 2: Add embedding support
print " Adding embedding support..."
add_embedding_support $kogral_dir
print " ✓ Embedding support added"
},
_ => {
print $" (ansi yellow)Unknown migration version: ($migration.version)(ansi reset)"
}
}
}
def update_config_version [config_path: string, new_version: string] {
# Phase 1: Read current config
let config = open $config_path | from toml
# Phase 2: Update version
let updated = $config | insert "graph.version" $new_version
# Phase 3: Convert back to TOML and save
$updated | to toml | save --force $config_path
print " ✓ Config version updated"
}
def add_metadata_field [kogral_dir: string] {
# Phase 1: Find all markdown files
let all_files = find_all_markdown_files $kogral_dir
# Phase 2: Process each file
mut updated = 0
for file in $all_files {
let content = open $file
let lines = $content | lines
# Phase 3: Check if metadata field exists
let has_metadata_field = $lines | any { |l| $l =~ '^(metadata|---):' }
if not $has_metadata_field {
# Phase 4: Add empty metadata field before closing ---
let updated_content = insert_metadata_field $content
$updated_content | save --force $file
$updated = $updated + 1
}
}
print $" Updated ($updated) files"
}
def add_embedding_support [kogral_dir: string] {
# Phase 1: Find all markdown files
let all_files = find_all_markdown_files $kogral_dir
# Phase 2: Note that embeddings will be generated on next reindex
print $" Embedding vectors will be generated on next reindex"
print $" Run 'kogral-reindex.nu' after migration to populate embeddings"
}
def find_all_markdown_files [kogral_dir: string] {
# Phase 1: Collect from all node type directories
mut all_files = []
for dir_type in ["notes" "decisions" "guidelines" "patterns" "journal"] {
let dir_path = $"($kogral_dir)/($dir_type)"
if ($dir_path | path exists) {
let files = glob $"($dir_path)/**/*.md"
$all_files = ($all_files | append $files)
}
}
$all_files
}
def insert_metadata_field [content: string] {
let lines = $content | lines
# Phase 1: Find the closing --- of frontmatter
mut closing_idx = 0
mut found_opening = false
for idx in (0..<($lines | length)) {
if $idx == 0 and ($lines | get $idx | str trim) == "---" {
$found_opening = true
continue
}
if $found_opening and ($lines | get $idx | str trim) == "---" {
$closing_idx = $idx
break
}
}
# Phase 2: Insert metadata field before closing ---
if $found_opening and $closing_idx > 0 {
let before = $lines | take $closing_idx
let after = $lines | skip $closing_idx
let updated = $before | append ["metadata: {}"] | append $after
$updated | str join "\n"
} else {
# No frontmatter, return as-is
$content
}
}