From the independent review of #207: - parked notes are created O_EXCL with -2,-3 suffixes (same-named notes from different subfolders were overwritten and their originals deleted) - proposed_wing is emitted only if a plain wing name; a note's own proposal_* keys are dropped so they cannot outrank the resolver's (first-wins reads) - CRLF notes: wing: key honoured, one valid frontmatter block on parking - KnownWings skips wiki dirs that are not canonical lower-case names - adds the wingmigrate dry-run/apply command Each fix has a test that failed first and a mutant that fails it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
// Command wingmigrate moves pages out of the retired wiki/{sources,concepts,
|
|
// entities}/ layout into wiki/<wing>/ (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, "|", "/"))
|
|
}
|
|
}
|