@@ -158,6 +158,12 @@ enum Command {
158158 #[ command( subcommand) ]
159159 action : WriteAction ,
160160 } ,
161+
162+ /// Migrate vault structure.
163+ Migrate {
164+ #[ command( subcommand) ]
165+ action : MigrateAction ,
166+ } ,
161167}
162168
163169#[ derive( Subcommand , Debug ) ]
@@ -307,6 +313,19 @@ enum ModelsAction {
307313 Info { name : String } ,
308314}
309315
316+ #[ derive( Subcommand , Debug ) ]
317+ enum MigrateAction {
318+ /// Classify notes and generate PARA migration preview.
319+ Para {
320+ /// Apply a previously generated preview.
321+ #[ arg( long) ]
322+ apply : bool ,
323+ /// Undo the last migration.
324+ #[ arg( long, conflicts_with = "apply" ) ]
325+ undo : bool ,
326+ } ,
327+ }
328+
310329/// Prompt user to enable intelligence, download models if yes.
311330fn prompt_intelligence ( data_dir : & std:: path:: Path ) -> Result < bool > {
312331 eprint ! (
@@ -1421,6 +1440,58 @@ async fn main() -> Result<()> {
14211440 }
14221441 }
14231442
1443+ Command :: Migrate { action } => {
1444+ let data_dir = Config :: data_dir ( ) ?;
1445+ if !index_exists ( & data_dir) {
1446+ eprintln ! ( "No index found. Run 'engraph index <path>' first." ) ;
1447+ std:: process:: exit ( 1 ) ;
1448+ }
1449+ let db_path = data_dir. join ( "engraph.db" ) ;
1450+ let store = store:: Store :: open ( & db_path) ?;
1451+ let vault_path_str = store. get_meta ( "vault_path" ) ?. expect ( "no vault path in index" ) ;
1452+ let vault_path = PathBuf :: from ( & vault_path_str) ;
1453+ let profile = Config :: load_vault_profile ( ) . ok ( ) . flatten ( ) ;
1454+
1455+ match action {
1456+ MigrateAction :: Para { apply, undo } => {
1457+ if undo {
1458+ let result = engraph:: migrate:: undo_last ( & store, & vault_path) ?;
1459+ println ! ( "Migration {} undone: {} files restored" , result. migration_id, result. restored) ;
1460+ if !result. errors . is_empty ( ) {
1461+ eprintln ! ( "Errors:" ) ;
1462+ for e in & result. errors { eprintln ! ( " {}" , e) ; }
1463+ }
1464+ } else if apply {
1465+ let preview = engraph:: migrate:: load_preview ( & data_dir) ?;
1466+ let result = engraph:: migrate:: apply_preview ( & preview, & store, & vault_path) ?;
1467+ println ! ( "Migration {} applied: {} files moved" , result. migration_id, result. moved) ;
1468+ if !result. errors . is_empty ( ) {
1469+ eprintln ! ( "Errors:" ) ;
1470+ for e in & result. errors { eprintln ! ( " {}" , e) ; }
1471+ }
1472+ } else {
1473+ // Generate preview
1474+ println ! ( "Scanning vault for PARA classification..." ) ;
1475+ let preview = engraph:: migrate:: generate_preview (
1476+ & store, & vault_path, profile. as_ref ( ) ,
1477+ ) ?;
1478+ engraph:: migrate:: save_preview ( & preview, & data_dir) ?;
1479+ println ! ( ) ;
1480+ println ! ( "Preview generated:" ) ;
1481+ println ! ( " Files to move: {}" , preview. files. len( ) ) ;
1482+ println ! ( " Uncertain: {}" , preview. uncertain. len( ) ) ;
1483+ println ! ( " Skipped: {}" , preview. skipped) ;
1484+ println ! ( ) ;
1485+ println ! ( "Preview saved to:" ) ;
1486+ println ! ( " {}" , data_dir. join( "migration-preview.md" ) . display( ) ) ;
1487+ println ! ( " {}" , data_dir. join( "migration-preview.json" ) . display( ) ) ;
1488+ println ! ( ) ;
1489+ println ! ( "Review the preview, then run: engraph migrate para --apply" ) ;
1490+ }
1491+ }
1492+ }
1493+ }
1494+
14241495 Command :: Models { action } => {
14251496 let defaults = engraph:: llm:: ModelDefaults :: default ( ) ;
14261497 match action {
0 commit comments