// Command wingmigrate moves pages out of the retired wiki/{sources,concepts, // entities}/ layout into wiki// (hyperguild#205 S5). // // Default is a dry run that prints the plan. -apply performs the Auto items. // Page text goes to -llm-url, which MUST be a local model. package main import ( "context" "flag" "fmt" "log/slog" "os" "strings" "time" "github.com/mathiasbq/hyperguild/ingestion/internal/llm" "github.com/mathiasbq/hyperguild/ingestion/internal/migrate" "github.com/mathiasbq/hyperguild/ingestion/internal/wing" ) func main() { brain := flag.String("brain", "/var/lib/supervisor/brain", "brain directory") llmURL := flag.String("llm-url", "http://10.0.1.20:31234/v1", "OpenAI-compatible LOCAL endpoint") model := flag.String("model", "qwen35-9b-fast", "model name") threshold := flag.Float64("threshold", 0.9, "classifier confidence threshold") apply := flag.Bool("apply", false, "perform the Auto moves (default: dry run)") what := flag.String("what-if-eligible", "", "dry run only: comma-separated wings to treat as eligible, to see what tagging them would allow") flag.Parse() if *apply && *what != "" { slog.Error("-what-if-eligible is a dry-run aid and cannot be combined with -apply: eligibility is a sovereignty decision made in classification.yaml") os.Exit(2) } if err := run(*brain, *llmURL, *model, *threshold, *apply, *what); err != nil { slog.Error("wingmigrate", "err", err) os.Exit(1) } } func run(brain, llmURL, model string, threshold float64, apply bool, whatIf string) error { client := llm.New(llmURL, "", model, 3*time.Minute) base, err := wing.NewConfiguredResolver(brain, client.Complete, threshold) if err != nil { return err } if whatIf != "" { allowed := map[string]bool{} for _, w := range strings.Split(whatIf, ",") { allowed[strings.TrimSpace(w)] = true } base.Eligible = func(w string) bool { return allowed[w] } } rf := func(exclude string) *wing.Resolver { r := *base r.Scorer = wing.NewBM25Scorer(brain, base.Known, exclude) return &r } items, err := migrate.BuildPlan(context.Background(), brain, rf) if err != nil { return err } report(items) if !apply { fmt.Println("\n_dry run: nothing was changed_") return nil } n, err := migrate.Apply(brain, items) fmt.Printf("\napplied %d moves\n", n) return err } func report(items []migrate.Item) { auto, exc := 0, 0 for _, it := range items { if it.Status == migrate.Auto { auto++ } else { exc++ } } fmt.Printf("# wing migration plan\n\n%d pages: %d auto, %d exceptions\n\n", len(items), auto, exc) fmt.Println("| status | kind | from | to / proposal | how | why |") fmt.Println("|---|---|---|---|---|---|") for _, it := range items { dest := it.To if dest == "" { dest = it.Wing } fmt.Printf("| %s | %s | %s | %s | %s | %s |\n", it.Status, it.Kind, it.From, dest, it.Method, strings.ReplaceAll(it.Reason, "|", "/")) } }