Files
arcrun-collector/main.go
T
Leo bb96bceb04 feat(ingest-hash-trigger): 凍結 collector/ingest schema+Go collector 骨架(issue #5, SDD task 1+2)
- schemas/collector-trigger.v1.schema.json:collector→named-webhook 觸發 payload(source_hash/r2_key/renamed/R6 warnings)
- schemas/kbdb-ingest-request.v1.schema.json:source_hash 必填缺=400;儲存映射走 metadata_json.$.source_hash(守 kbdb 表不變鐵律);同 hash 重送=200 already_ingested
- schemas/MIGRATION-webhook-payload.md:舊 Gitea push payload 逐欄對照(給 task 4 改寫 workflow)
- collector/:Go module(純 stdlib)——manifest 讀寫+mtime fast-path 掃描+renamed hash 配對+40% 大量刪除防呆;CLI collector scan
- go test ./... 全綠 7/7(added/modified/removed/renamed/防呆五情境+fast-path+manifest 往返)
- Node 版 collector 標 legacy 並存(SDD task 4 拆除)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:31:11 +08:00

75 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// collector — hash 差異偵測 collectorSDD ingest-hash-triggerGo 版骨架)。
//
// 用法:
//
// collector scan --root <知識資料夾> --manifest <manifest.json 路徑> \
// [--max-removed-ratio 0.4] [--dry-run]
//
// 一次掃描:走訪 root、對照 manifest、把事件(符合
// schemas/collector-trigger.v1.schema.json)以 JSON 輸出到 stdout,並更新 manifest
// --dry-run 不寫)。daemon 常駐/launchd/R2 上傳/打 named-webhook=之後的 task
// 本階段不接網路。
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) < 2 || os.Args[1] != "scan" {
fmt.Fprintln(os.Stderr, "用法: collector scan --root <dir> --manifest <file> [--max-removed-ratio 0.4] [--dry-run]")
os.Exit(2)
}
fs := flag.NewFlagSet("scan", flag.ExitOnError)
root := fs.String("root", "", "知識資料夾根路徑(必填)")
manifestPath := fs.String("manifest", "", "manifest JSON 檔路徑(必填;不存在會建新)")
ratio := fs.Float64("max-removed-ratio", DefaultMaxRemovedRatio, "大量刪除防呆門檻(removed 數 > manifest 條目 × 本值 → 全部不下架、只發警告)")
dryRun := fs.Bool("dry-run", false, "只輸出事件,不更新 manifest")
if err := fs.Parse(os.Args[2:]); err != nil {
os.Exit(2)
}
if *root == "" || *manifestPath == "" {
fmt.Fprintln(os.Stderr, "錯誤:--root 與 --manifest 皆為必填")
os.Exit(2)
}
absRoot, err := filepath.Abs(*root)
if err != nil {
fatal(err)
}
absManifest, err := filepath.Abs(*manifestPath)
if err != nil {
fatal(err)
}
m, err := LoadManifest(absManifest, absRoot)
if err != nil {
fatal(err)
}
payload, err := Scan(absRoot, m, ScanOptions{
MaxRemovedRatio: *ratio,
SkipPaths: map[string]bool{absManifest: true}, // manifest 若住在 root 底下,不掃自己
})
if err != nil {
fatal(err)
}
if !*dryRun {
if err := m.Save(absManifest); err != nil {
fatal(err)
}
}
out, err := json.MarshalIndent(payload, "", " ")
if err != nil {
fatal(err)
}
fmt.Println(string(out))
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "collector:", err)
os.Exit(1)
}