Files
Agent-System/cmd/document-content-reconcile/main.go
T
weichenandCursor 9038c65329 fix: 补齐 rows.Err() 检查(R-2,其余包)
覆盖 kb/storage/projectworkflow/docgov/freedoc 与两个 document CLI。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 03:40:24 +00:00

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)
}
}