--- name: vault_layout_ensure kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func VaultLayoutEnsure(vaultPath string, dryRun bool) (LayoutReport, error)" description: "Normaliza el layout de un vault al esquema hibrido canónico data/{raw,processed,exports} + knowledge/{decisions,domains,models,benchmarks,test_documents}. Migra directorios legacy en la raíz del vault a su ubicación correcta; idempotente." tags: [vault, layout, migration, infra, filesystem, idempotent] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: - "fmt" - "os" - "path/filepath" - "strings" params: - name: vault_path desc: "Ruta al directorio raíz del vault. Puede ser absoluta, relativa o un symlink — se resuelve con filepath.Abs + filepath.EvalSymlinks. Trailing slashes se ignoran." - name: dry_run desc: "Si true, calcula el reporte completo (qué se crearía, migraría, etc.) pero no modifica el disco. Util para previsualizar antes de ejecutar." output: "LayoutReport con: VaultPath (ruta resuelta), Created (dirs creados), Migrated (renombres ejecutados, formato 'src -> dst'), AlreadyOK (destinos que ya existían), Skipped (entradas en raíz no reconocidas, no tocadas), DryRun (flag). Error si el path no existe, no es directorio, o hay conflicto de merge (mismo nombre de archivo en src y dst)." tested: true tests: - "TestVaultLayoutEnsure_DryRun_NoChange" - "TestVaultLayoutEnsure_FreshDir_CreatesLayout" - "TestVaultLayoutEnsure_LegacyDataLayout_Migrates" - "TestVaultLayoutEnsure_LegacyKnowledgeLayout_Migrates" - "TestVaultLayoutEnsure_AlreadyMigrated_Idempotent" - "TestVaultLayoutEnsure_Mixed_PartialMigration" - "TestVaultLayoutEnsure_MergeConflict_Errors" - "TestVaultLayoutEnsure_UnknownFiles_Skipped" - "TestVaultLayoutEnsure_NotADir_Errors" test_file_path: "functions/infra/vault_layout_ensure_test.go" file_path: "functions/infra/vault_layout_ensure.go" --- ## Ejemplo ```go // Previsualizar sin tocar disco: report, err := VaultLayoutEnsure("/home/lucas/vaults/turismo_spain", true) if err != nil { log.Fatal(err) } fmt.Printf("Would migrate: %v\n", report.Migrated) fmt.Printf("Would create: %v\n", report.Created) // Ejecutar la migración: report, err = VaultLayoutEnsure("/home/lucas/vaults/turismo_spain", false) if err != nil { log.Fatalf("migration failed: %v", err) } fmt.Printf("Migrated: %v\n", report.Migrated) fmt.Printf("Created: %v\n", report.Created) fmt.Printf("Skipped: %v\n", report.Skipped) ``` ## Comportamiento detallado **Directorios gestionados:** | Raíz (legacy) | Destino canónico | |---|---| | `raw/` | `data/raw/` | | `processed/` | `data/processed/` | | `exports/` | `data/exports/` | | `decisions/` | `knowledge/decisions/` | | `domains/` | `knowledge/domains/` | | `models/` | `knowledge/models/` | | `benchmarks/` | `knowledge/benchmarks/` | | `test_documents/` | `knowledge/test_documents/` | | `README.md` / `README.txt` | `knowledge/README.md` | **Lógica de migración (por cada entrada conocida):** - Solo `src` existe → rename atómico `src` → `dst`, registrado en `Migrated`. - Solo `dst` existe → ya migrado, registrado en `AlreadyOK`. - Ambos existen (dir) → merge: mueve cada hijo de `src/` a `dst/`; error si mismo nombre. Registrado en `Migrated` por hijo. - Ambos existen (archivo README) → error inmediato con paths concretos. - Ninguno existe → crea `dst` vacío, registrado en `Created`. **Archivos/dirs no reconocidos** en la raíz (`.git`, `vault_index.db`, archivos custom) se registran en `Skipped` y no se tocan. **Idempotencia:** segunda ejecución sobre un vault ya migrado reporta todo en `AlreadyOK` y no toca disco. ## Notas `LayoutReport` es un tipo local de esta función (no un tipo del registry). El struct exportado vive en `functions/infra/vault_layout_ensure.go` junto con la función. Para aplicar la migración a múltiples vaults en batch, invocar desde un pipeline que lea los paths de `vault.yaml` (ver `vault_manifest_read_go_infra`) y llame a `VaultLayoutEnsure` en cada uno.