Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

78 lines
2.2 KiB
Go

package hooks
import (
"encoding/json"
"os"
"path/filepath"
"time"
"github.com/zzet/gortex/internal/platform"
)
// DecisionKind enumerates the outcomes the Grep-redirect probe can log.
type DecisionKind string
const (
DecisionProbedHit DecisionKind = "probed_hit"
DecisionProbedMiss DecisionKind = "probed_miss"
DecisionSkippedNonSymbol DecisionKind = "skipped_non_symbol"
DecisionTimedOut DecisionKind = "timed_out"
// DecisionNudged records that ModeAdaptiveNudge fired its
// once-per-burst soft-deny after a streak of non-symbolic calls.
DecisionNudged DecisionKind = "nudged"
)
type hookDecision struct {
Timestamp string `json:"ts"`
Tool string `json:"tool"`
Pattern string `json:"pattern"`
Decision DecisionKind `json:"decision"`
Hits int `json:"hits,omitempty"`
DurationMS int64 `json:"duration_ms,omitempty"`
}
// hookDecisionsPath returns the telemetry file path. Respects GORTEX_HOOK_LOG
// so tests can redirect writes. Defaults to ~/.gortex/cache (or the
// $XDG_CACHE_HOME equivalent when that variable is set).
func hookDecisionsPath() string {
if p := os.Getenv("GORTEX_HOOK_LOG"); p != "" {
return p
}
if v := os.Getenv("XDG_CACHE_HOME"); v == "" || !filepath.IsAbs(v) {
if _, err := os.UserHomeDir(); err != nil {
return ""
}
}
return filepath.Join(platform.CacheDir(), "hook-decisions.jsonl")
}
// logHookDecision appends one JSONL record. Best-effort: errors are swallowed
// because telemetry must never block a hook.
func logHookDecision(tool, pattern string, decision DecisionKind, hits int, dur time.Duration) {
path := hookDecisionsPath()
if path == "" {
return
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return
}
rec := hookDecision{
Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
Tool: tool,
Pattern: pattern,
Decision: decision,
Hits: hits,
DurationMS: dur.Milliseconds(),
}
line, err := json.Marshal(rec)
if err != nil {
return
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return
}
defer f.Close()
_, _ = f.Write(append(line, '\n'))
}