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
170 lines
4.7 KiB
Go
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)
|
|
}
|