Documentation
¶
Overview ¶
Package audit provides waste-detection detectors for agent transcripts.
Ported from failproofai (https://github.com/FailproofAI/failproofai) with permission. These detectors catch "stupid behaviors" that waste tokens and wall-clock time but aren't dangerous enough to block at runtime.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var FindFromRoot = Detector{ Name: "find-from-root", Description: "Bash `find` against /, /home, /usr, etc.", Category: "Risky", Severity: "warn", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } match := findRe.FindStringSubmatch(command) if match == nil { return nil } raw := strings.Trim(match[1], "\"'") stripped := strings.TrimRight(raw, "/") if stripped == "" { stripped = "/" } if !riskyRoots[stripped] { return nil } return &DetectorHit{ DetectorName: "find-from-root", Example: truncate(command, 80), Severity: "warn", Category: "Risky", } }, }
FindFromRoot detects `find` against filesystem root.
var GitCommitNoVerify = Detector{ Name: "git-commit-no-verify", Description: "git commit invoked with --no-verify / -n, skipping hooks.", Category: "Risky", Severity: "warn", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } if !gitCommitRe.MatchString(command) { return nil } if noVerifyRe.MatchString(command) { return &DetectorHit{ DetectorName: "git-commit-no-verify", Example: truncate(command, 80), Severity: "warn", Category: "Risky", } } return nil }, }
GitCommitNoVerify detects `git commit --no-verify`.
var PreferEditOverReadCat = Detector{ Name: "prefer-edit-over-read-cat", Description: "Bash `cat`/`head`/`tail`/`less`/`more` on a single source file.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } cmd := strings.TrimSpace(command) if strings.ContainsAny(cmd, "|<>;&`$()") { return nil } parts := strings.Fields(cmd) if len(parts) < 2 { return nil } bin := parts[0] if bin != "cat" && bin != "head" && bin != "tail" && bin != "less" && bin != "more" { return nil } path := "" for _, p := range parts[1:] { if !strings.HasPrefix(p, "-") { path = strings.Trim(p, "\"'") } } if path == "" { return nil } if strings.HasSuffix(path, ".env") || strings.Contains(path, ".env.") { return nil } if !sourceExtRe.MatchString(path) { return nil } return &DetectorHit{ DetectorName: "prefer-edit-over-read-cat", Example: cmd, Severity: "info", Category: "Wasteful", } }, }
PreferEditOverReadCat detects `cat/head/tail/less/more` on source files.
var PreferEditOverSedAwk = Detector{ Name: "prefer-edit-over-sed-awk", Description: "Bash `sed`/`awk` on source files instead of using Edit tool.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } if !sedAwkRe.MatchString(command) { return nil } if sourceExtRe.MatchString(command) { return &DetectorHit{ DetectorName: "prefer-edit-over-sed-awk", Example: truncate(command, 80), Severity: "info", Category: "Wasteful", } } return nil }, }
PreferEditOverSedAwk detects sed/awk on source files instead of Edit tool.
var PreferWriteOverHeredoc = Detector{ Name: "prefer-write-over-heredoc", Description: "Bash heredoc (`cat << EOF > file`) instead of Write tool.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } if heredocRe.MatchString(command) { return &DetectorHit{ DetectorName: "prefer-write-over-heredoc", Example: truncate(command, 80), Severity: "info", Category: "Wasteful", } } return nil }, }
PreferWriteOverHeredoc detects heredoc usage instead of Write tool.
var RedundantCdCwd = Detector{ Name: "redundant-cd-cwd", Description: "Bash commands prefixed with `cd <cwd> && ...` even though commands already run in cwd.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" || event.CWD == "" { return nil } trimmed := strings.TrimLeft(command, " \t") match := cdCwdRe.FindStringSubmatch(trimmed) if match == nil { return nil } path := strings.TrimRight(match[1]+match[2]+match[3], "/") cwd := strings.TrimRight(event.CWD, "/") if path != cwd { return nil } rest := strings.TrimSpace(match[4]) return &DetectorHit{ DetectorName: "redundant-cd-cwd", Example: fmt.Sprintf("cd %s && %s", path, rest), Severity: "info", Category: "Wasteful", } }, }
RedundantCdCwd detects `cd <cwd> && command` where the cd is unnecessary.
var RereadAfterEdit = Detector{ Name: "reread-after-edit", Description: "Read of a file that was just Edit'd or Write'n in the same session.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, state DetectorSessionState) *DetectorHit { rs := getRereadState(state) for key, n := range rs.Countdown { if n <= 1 { delete(rs.Countdown, key) } else { rs.Countdown[key] = n - 1 } } filePath, _ := event.ToolInput["file_path"].(string) if filePath == "" { return nil } if event.ToolName == "Edit" || event.ToolName == "Write" { rs.Countdown[filePath] = rereadWindow return nil } if event.ToolName == "Read" { if _, ok := rs.Countdown[filePath]; ok { delete(rs.Countdown, filePath) return &DetectorHit{ DetectorName: "reread-after-edit", Example: fmt.Sprintf("Read %s immediately after Edit/Write", filePath), Severity: "info", Category: "Wasteful", } } } return nil }, }
RereadAfterEdit detects reading a file that was just edited.
var SleepPollingLoop = Detector{ Name: "sleep-polling-loop", Description: "Bash long `sleep` or while-sleep polling loops.", Category: "Wasteful", Severity: "info", Detect: func(event ToolEvent, _ DetectorSessionState) *DetectorHit { if event.ToolName != "Bash" { return nil } command, _ := event.ToolInput["command"].(string) if command == "" { return nil } if whileSleepRe.MatchString(command) { return &DetectorHit{ DetectorName: "sleep-polling-loop", Example: truncate(command, 80), Severity: "info", Category: "Wasteful", } } match := sleepRe.FindStringSubmatch(command) if match != nil { n, _ := strconv.ParseFloat(match[1], 64) unit := "s" if match[2] != "" { unit = match[2] } var seconds float64 switch unit { case "m": seconds = n * 60 case "h": seconds = n * 3600 case "d": seconds = n * 86400 default: seconds = n } if seconds >= sleepThresholdSeconds { return &DetectorHit{ DetectorName: "sleep-polling-loop", Example: truncate(command, 80), Severity: "info", Category: "Wasteful", } } } return nil }, }
SleepPollingLoop detects long sleep or while-sleep polling loops.
Functions ¶
This section is empty.
Types ¶
type Detector ¶
type Detector struct {
Name string
Description string
Category string
Severity string
Detect func(event ToolEvent, state DetectorSessionState) *DetectorHit
}
Detector is a pure function that detects wasteful behavior in tool events.
func AllDetectors ¶
func AllDetectors() []Detector
AllDetectors returns all registered audit detectors.
type DetectorHit ¶
type DetectorHit struct {
DetectorName string
Example string
Severity string // "info" or "warn"
Category string // "Wasteful" or "Risky"
}
DetectorHit represents one detected occurrence of wasteful behavior.
type DetectorSessionState ¶
type DetectorSessionState = map[string]interface{}
DetectorSessionState holds per-session state for stateful detectors.