Files
beads/cmd/bd/audit.go
beads/crew/dave 5dfb838d60 feat(completion): optimize ID prefix filtering and add completions to more commands
Improvements to shell completions from PR #935:

1. Add IDPrefix field to IssueFilter for efficient database-level filtering
   - Queries are now filtered at SQL level instead of fetching all issues
   - Updated sqlite, transaction, and memory stores to support IDPrefix

2. Add ValidArgsFunction to additional commands:
   - dep (add, remove, list, tree)
   - comments, comment (add)
   - delete
   - graph
   - label (add, remove, list)
   - duplicate, supersede
   - audit
   - move
   - relate, unrelate
   - refile
   - gate (show, resolve, add-waiter)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Executed-By: beads/crew/dave
Rig: beads
Role: crew
2026-01-06 19:05:34 -08:00

170 lines
4.7 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/audit"
)
var (
auditRecordKind string
auditRecordModel string
auditRecordPrompt string
auditRecordResponse string
auditRecordIssueID string
auditRecordToolName string
auditRecordExitCode int
auditRecordError string
auditRecordStdin bool
auditLabelValue string
auditLabelReason string
)
var auditCmd = &cobra.Command{
Use: "audit",
Short: "Record and label agent interactions (append-only JSONL)",
Long: `Audit log entries are appended to .beads/interactions.jsonl.
Each line is one event. This file is intended to be versioned in git and used for:
- auditing ("why did the agent do that?")
- dataset generation (SFT/RL fine-tuning)
Entries are append-only. Labeling creates a new "label" entry that references a parent entry.`,
}
var auditRecordCmd = &cobra.Command{
Use: "record",
Short: "Append an audit interaction entry",
Run: func(cmd *cobra.Command, _ []string) {
var e audit.Entry
// If stdin is piped and no explicit record fields were provided, assume stdin JSON.
// This matches "or pipe JSON via stdin" without requiring a flag.
fi, _ := os.Stdin.Stat()
stdinPiped := fi != nil && (fi.Mode()&os.ModeCharDevice) == 0
noFieldsProvided := auditRecordKind == "" &&
auditRecordModel == "" &&
auditRecordPrompt == "" &&
auditRecordResponse == "" &&
auditRecordIssueID == "" &&
auditRecordToolName == "" &&
auditRecordExitCode < 0 &&
auditRecordError == ""
if auditRecordStdin || (stdinPiped && noFieldsProvided) {
b, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to read stdin: %v\n", err)
os.Exit(1)
}
if err := json.Unmarshal(b, &e); err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid JSON on stdin: %v\n", err)
os.Exit(1)
}
// Allow --actor to override/augment stdin.
if actor != "" {
e.Actor = actor
}
} else {
if auditRecordKind == "" {
fmt.Fprintf(os.Stderr, "Error: --kind is required\n")
os.Exit(1)
}
e = audit.Entry{
Kind: auditRecordKind,
Actor: actor,
IssueID: auditRecordIssueID,
Model: auditRecordModel,
Prompt: auditRecordPrompt,
Response: auditRecordResponse,
ToolName: auditRecordToolName,
Error: auditRecordError,
}
if auditRecordExitCode >= 0 {
exit := auditRecordExitCode
e.ExitCode = &exit
}
}
id, err := audit.Append(&e)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if jsonOutput {
outputJSON(map[string]any{
"id": id,
"kind": e.Kind,
})
return
}
fmt.Println(id)
},
}
var auditLabelCmd = &cobra.Command{
Use: "label <entry-id>",
Short: "Append a label entry referencing an existing interaction",
Args: cobra.ExactArgs(1),
Run: func(_ *cobra.Command, args []string) {
parentID := args[0]
if auditLabelValue == "" {
fmt.Fprintf(os.Stderr, "Error: --label is required\n")
os.Exit(1)
}
e := audit.Entry{
Kind: "label",
Actor: actor,
ParentID: parentID,
Label: auditLabelValue,
Reason: auditLabelReason,
}
id, err := audit.Append(&e)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if jsonOutput {
outputJSON(map[string]any{
"id": id,
"parent_id": parentID,
"label": auditLabelValue,
})
return
}
fmt.Println(id)
},
}
func init() {
auditRecordCmd.Flags().StringVar(&auditRecordKind, "kind", "", "Entry kind (e.g. llm_call, tool_call, label)")
auditRecordCmd.Flags().StringVar(&auditRecordModel, "model", "", "Model name (llm_call)")
auditRecordCmd.Flags().StringVar(&auditRecordPrompt, "prompt", "", "Prompt text (llm_call)")
auditRecordCmd.Flags().StringVar(&auditRecordResponse, "response", "", "Response text (llm_call)")
auditRecordCmd.Flags().StringVar(&auditRecordIssueID, "issue-id", "", "Related issue id (bd-...)")
auditRecordCmd.Flags().StringVar(&auditRecordToolName, "tool-name", "", "Tool name (tool_call)")
auditRecordCmd.Flags().IntVar(&auditRecordExitCode, "exit-code", -1, "Exit code (tool_call)")
auditRecordCmd.Flags().StringVar(&auditRecordError, "error", "", "Error string (llm_call/tool_call)")
auditRecordCmd.Flags().BoolVar(&auditRecordStdin, "stdin", false, "Read a JSON object from stdin (must match audit.Entry schema)")
auditLabelCmd.Flags().StringVar(&auditLabelValue, "label", "", `Label value (e.g. "good" or "bad")`)
auditLabelCmd.Flags().StringVar(&auditLabelReason, "reason", "", "Reason for label")
// Issue ID completions
auditCmd.ValidArgsFunction = issueIDCompletion
auditCmd.AddCommand(auditRecordCmd)
auditCmd.AddCommand(auditLabelCmd)
rootCmd.AddCommand(auditCmd)
}