覆盖 kb/storage/projectworkflow/docgov/freedoc 与两个 document CLI。 Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.1 KiB
Go
127 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/docgov"
|
|
"chat-agent-system/internal/docrevision"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// document-content-reconcile classifies output.md vs ledger head.
|
|
// Default is dry-run against an isolated DB. Live apply to dev requires
|
|
// --live --environment dev --doc-id --confirm REPAIR_SINGLE_DEV_DOC.
|
|
func main() {
|
|
cfg := cliConfig{}
|
|
flag.StringVar(&cfg.DSN, "dsn", os.Getenv("TEST_DATABASE_URL"), "postgres DSN")
|
|
flag.StringVar(&cfg.RepoRoot, "repo-root", "", "directory used to resolve relative work_dir")
|
|
flag.BoolVar(&cfg.Apply, "apply", false, "apply LEGACY_CANONICALIZATION_DRIFT repairs (default dry-run)")
|
|
flag.StringVar(&cfg.DocID, "doc-id", "", "single document id (required for --live)")
|
|
flag.StringVar(&cfg.BackupDir, "backup-dir", "", "required when --apply: write file + head metadata backups")
|
|
flag.BoolVar(&cfg.Live, "live", false, "explicit live/dev mode; default still refuses shared env")
|
|
flag.StringVar(&cfg.Environment, "environment", "", "live environment; only 'dev' is accepted")
|
|
flag.StringVar(&cfg.Confirm, "confirm", "", "live apply hard gate; must be REPAIR_SINGLE_DEV_DOC")
|
|
flag.StringVar(&cfg.StoredDataRoot, "stored-data-root", "", "stored/container data root, e.g. /app/data")
|
|
flag.StringVar(&cfg.HostDataRoot, "host-data-root", "", "host data root mapped from stored-data-root, e.g. $REPO/data/dev")
|
|
outPath := flag.String("report", "", "write JSON report path")
|
|
flag.Parse()
|
|
|
|
if err := validateCLI(cfg); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(2)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
pool, err := pgxpool.New(ctx, cfg.DSN)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
defer pool.Close()
|
|
|
|
svc := docrevision.NewService(pool)
|
|
if cfg.HostDataRoot != "" {
|
|
svc.DataDir = cfg.HostDataRoot
|
|
} else {
|
|
svc.DataDir = cfg.RepoRoot
|
|
}
|
|
svc.StoredDataRoot = cfg.StoredDataRoot
|
|
svc.HostDataRoot = cfg.HostDataRoot
|
|
gov := docgov.NewGovernanceEngine(pool)
|
|
|
|
q := `SELECT doc_id FROM document_records WHERE task_key <> '' ORDER BY doc_id`
|
|
args := []any{}
|
|
if strings.TrimSpace(cfg.DocID) != "" {
|
|
q = `SELECT doc_id FROM document_records WHERE doc_id=$1`
|
|
args = append(args, cfg.DocID)
|
|
}
|
|
rows, err := pool.Query(ctx, q, args...)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
defer rows.Close()
|
|
|
|
report := struct {
|
|
DryRun bool `json:"dry_run"`
|
|
Live bool `json:"live"`
|
|
Items []*docrevision.ReconcileResult `json:"items"`
|
|
Legacy int `json:"legacy_drift"`
|
|
Fork int `json:"content_fork"`
|
|
Align int `json:"aligned"`
|
|
}{DryRun: !cfg.Apply, Live: cfg.Live}
|
|
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
if _, gerr := gov.GetDocument(ctx, id); gerr != nil && cfg.Apply {
|
|
fmt.Fprintf(os.Stderr, "preload document %s: %v\n", id, gerr)
|
|
}
|
|
res, rerr := svc.ReconcileDocument(ctx, docrevision.ReconcileRequest{
|
|
DocID: id, Apply: cfg.Apply, BackupDir: cfg.BackupDir, HashUpdater: gov,
|
|
})
|
|
if res == nil {
|
|
res = &docrevision.ReconcileResult{DocID: id, Detail: rerr.Error()}
|
|
} else if rerr != nil && res.Detail == "" {
|
|
res.Detail = rerr.Error()
|
|
}
|
|
switch res.Class {
|
|
case docrevision.ProjectionLegacyCanonicalizationDrift:
|
|
report.Legacy++
|
|
case docrevision.ProjectionContentFork:
|
|
report.Fork++
|
|
case docrevision.ProjectionAligned:
|
|
report.Align++
|
|
}
|
|
report.Items = append(report.Items, res)
|
|
if rerr != nil && cfg.Apply {
|
|
enc, _ := json.MarshalIndent(report, "", " ")
|
|
fmt.Fprintln(os.Stderr, string(enc))
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
enc, _ := json.MarshalIndent(report, "", " ")
|
|
if *outPath != "" {
|
|
_ = os.WriteFile(*outPath, enc, 0o644)
|
|
}
|
|
fmt.Println(string(enc))
|
|
if !cfg.Apply && report.Legacy+report.Fork > 0 {
|
|
os.Exit(3)
|
|
}
|
|
}
|