219 lines
6.8 KiB
Plaintext
219 lines
6.8 KiB
Plaintext
#!/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
|
|
}
|
|
}
|