Implements audit logging for agent interactions to support auditing and dataset generation (fixes #649). New features: - .beads/interactions.jsonl (append-only audit log) - bd audit record: log LLM calls, tool calls, or pipe JSON via stdin - bd audit label <id>: append labels (good/bad) for dataset curation - bd compact --audit: optionally log LLM prompt/response during compaction - bd init: creates empty interactions.jsonl - bd sync: includes interactions.jsonl in staging Audit entries are append-only - labeling creates new entries that reference parent entries by ID. Closes #649 Co-authored-by: Dmitry Chichkov <dchichkov@nvidia.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
55 lines
1.3 KiB
Go
55 lines
1.3 KiB
Go
package audit
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestAppend_CreatesFileAndWritesJSONL(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
beadsDir := filepath.Join(tmp, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0750); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
// beads.FindBeadsDir() validates that the directory contains project files
|
|
// (db or *.jsonl). Create an empty issues.jsonl so BEADS_DIR is accepted.
|
|
issuesPath := filepath.Join(beadsDir, "issues.jsonl")
|
|
if err := os.WriteFile(issuesPath, []byte{}, 0644); err != nil {
|
|
t.Fatalf("write issues.jsonl: %v", err)
|
|
}
|
|
t.Setenv("BEADS_DIR", beadsDir)
|
|
|
|
id1, err := Append(&Entry{Kind: "llm_call", Model: "test-model", Prompt: "p", Response: "r"})
|
|
if err != nil {
|
|
t.Fatalf("append: %v", err)
|
|
}
|
|
if id1 == "" {
|
|
t.Fatalf("expected id")
|
|
}
|
|
_, err = Append(&Entry{Kind: "label", ParentID: id1, Label: "good", Reason: "ok"})
|
|
if err != nil {
|
|
t.Fatalf("append label: %v", err)
|
|
}
|
|
|
|
p := filepath.Join(beadsDir, FileName)
|
|
f, err := os.Open(p)
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
sc := bufio.NewScanner(f)
|
|
lines := 0
|
|
for sc.Scan() {
|
|
lines++
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
if lines != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", lines)
|
|
}
|
|
}
|