Files
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

121 lines
4.1 KiB
Go

package cli
import (
"strings"
"time"
"github.com/kunchenguid/no-mistakes/internal/paths"
"github.com/kunchenguid/no-mistakes/internal/telemetry"
"github.com/kunchenguid/no-mistakes/internal/types"
)
// trackAxiSurface records a state-changing axi command (run, respond, abort)
// both as a pageview and as a command event. The pageview gives agent usage
// parity with the human surfaces (the TUI emits /tui, the wizard /wizard) so
// agent and human activity show up the same way in analytics; the command
// event, added alongside rather than replacing the pageview, keeps the
// per-command status and duration. It fires at command entry so the surface is
// recorded even when the command later fails. fields may be nil. Read-only
// surfaces must use trackReadSurface instead: their polling volume made the
// pageview+command pair the dominant source of remote telemetry rows.
func trackAxiSurface(command, path string, fields telemetry.Fields, fn func() error) error {
telemetry.Pageview(path, fields)
return trackCommand(command, fn)
}
// readSurfaceHeartbeat caps how long an unchanged read-only surface stays
// silent: with no state change its command event is emitted at most this
// often. State changes always emit immediately (see ReadSurfaceGate).
const readSurfaceHeartbeat = 10 * time.Minute
// readSurfaceNow is the read-surface gate's clock, injectable so tests can
// drive the heartbeat window deterministically. nil means time.Now.
var readSurfaceNow func() time.Time
// trackReadSurface records a high-frequency read-only command (axi-status,
// axi-home, axi-logs, status, runs) as a single sampled "command" event.
// Unlike trackAxiSurface it emits no pageview, and the command event is
// suppressed unless the observed state fingerprint changed since the last
// emit or the heartbeat interval elapsed. This is what keeps agent status
// polling loops from flooding remote analytics (one event per poll, forever)
// while every meaningful state transition still lands remotely. Mutation
// surfaces (axi run/respond/abort, run lifecycle) stay full-fidelity.
// fn returns the state fingerprint, an optional soft status, and the error.
func trackReadSurface(command string, fields telemetry.Fields, fn func() (string, string, error)) error {
start := time.Now()
fingerprint, status, err := fn()
resolved := commandStatus(status, err)
if !shouldEmitReadSurface(command, resolved+"|"+fingerprint) {
return err
}
merged := make(telemetry.Fields, len(fields)+3)
for k, v := range fields {
merged[k] = v
}
merged["command"] = command
merged["status"] = resolved
merged["duration_ms"] = time.Since(start).Milliseconds()
telemetry.Track("command", merged)
return err
}
func shouldEmitReadSurface(command, fingerprint string) bool {
if !telemetry.Enabled() {
return false
}
p, err := paths.New()
if err != nil {
return true // fail open: behave like the ungated pre-diet path
}
gate := telemetry.NewReadSurfaceGate(p.TelemetryGateFile(), readSurfaceHeartbeat, readSurfaceNow)
return gate.ShouldEmit(command, fingerprint)
}
func sanitizeAxiTelemetryStep(step string) string {
step = strings.TrimSpace(step)
if validStep(types.StepName(step)) {
return step
}
return "invalid"
}
func sanitizeAxiTelemetryAction(action string) string {
action = strings.TrimSpace(action)
switch types.ApprovalAction(action) {
case types.ActionApprove, types.ActionFix, types.ActionSkip:
return action
default:
return "invalid"
}
}
func trackCommand(name string, fn func() error) (err error) {
return trackCommandStatus(name, func() (string, error) {
if err := fn(); err != nil {
return "", err
}
return "success", nil
})
}
func trackCommandStatus(name string, fn func() (string, error)) (err error) {
start := time.Now()
status, err := fn()
telemetry.Track("command", telemetry.Fields{
"command": name,
"status": commandStatus(status, err),
"duration_ms": time.Since(start).Milliseconds(),
})
return err
}
func commandStatus(status string, err error) string {
if status != "" {
return status
}
if err != nil {
return "error"
}
return "success"
}