Skip to content

Commit 907f57d

Browse files
committed
test message
1 parent 2cf9ca6 commit 907f57d

2 files changed

Lines changed: 125 additions & 7 deletions

File tree

cmd/iterate/features_sessions.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,24 @@ import (
1313
)
1414

1515
// ---------------------------------------------------------------------------
16-
// Audit log
16+
// Audit log (JSON Lines format)
1717
// ---------------------------------------------------------------------------
1818

1919
var auditLogPath string
2020

21+
// auditEntry is one JSON Lines record written to the audit log.
22+
type auditEntry struct {
23+
Timestamp string `json:"ts"`
24+
Tool string `json:"tool"`
25+
Args map[string]interface{} `json:"args,omitempty"`
26+
Result string `json:"result,omitempty"`
27+
IsError bool `json:"error,omitempty"`
28+
}
29+
2130
func initAuditLog() {
2231
home, _ := os.UserHomeDir()
23-
auditLogPath = filepath.Join(home, ".iterate", "audit.log")
24-
_ = os.MkdirAll(filepath.Dir(auditLogPath), 0o755) // best-effort cleanup
32+
auditLogPath = filepath.Join(home, ".iterate", "audit.jsonl")
33+
_ = os.MkdirAll(filepath.Dir(auditLogPath), 0o755)
2534
}
2635

2736
func logAudit(toolName string, args map[string]interface{}, result string) {
@@ -33,12 +42,26 @@ func logAudit(toolName string, args map[string]interface{}, result string) {
3342
return
3443
}
3544
defer f.Close()
36-
argBytes, _ := json.Marshal(args)
45+
3746
snippet := result
38-
if len(snippet) > 120 {
39-
snippet = snippet[:120] + "…"
47+
if len(snippet) > 200 {
48+
snippet = snippet[:200] + "…"
49+
}
50+
isErr := strings.HasPrefix(strings.ToLower(strings.TrimSpace(result)), "error")
51+
52+
entry := auditEntry{
53+
Timestamp: time.Now().UTC().Format(time.RFC3339),
54+
Tool: toolName,
55+
Args: args,
56+
Result: snippet,
57+
IsError: isErr,
58+
}
59+
line, err := json.Marshal(entry)
60+
if err != nil {
61+
return
4062
}
41-
fmt.Fprintf(f, "[%s] %s %s → %s\n", time.Now().Format("2006-01-02 15:04:05"), toolName, argBytes, snippet)
63+
f.Write(line) //nolint:errcheck
64+
f.Write([]byte{'\n'}) //nolint:errcheck
4265
}
4366

4467
// ---------------------------------------------------------------------------

internal/commands/session.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package commands
22

33
import (
4+
"bufio"
5+
"encoding/json"
46
"fmt"
57
"os"
68
"path/filepath"
9+
"strconv"
710
"strings"
811
"time"
912

@@ -160,6 +163,14 @@ func registerSessionUtilityB(r *Registry) {
160163
Category: "session",
161164
Handler: cmdIterateInit,
162165
})
166+
167+
r.Register(Command{
168+
Name: "/auditlog",
169+
Aliases: []string{"/alog"},
170+
Description: "show recent audit log entries [n]",
171+
Category: "session",
172+
Handler: cmdAuditLog,
173+
})
163174
}
164175

165176
func cmdQuit(ctx Context) Result {
@@ -460,6 +471,90 @@ func cmdChanges(ctx Context) Result {
460471
return Result{Handled: true}
461472
}
462473

474+
// auditLogRecord mirrors the JSON Lines format written by logAudit in features_sessions.go.
475+
type auditLogRecord struct {
476+
Timestamp string `json:"ts"`
477+
Tool string `json:"tool"`
478+
Args map[string]interface{} `json:"args,omitempty"`
479+
Result string `json:"result,omitempty"`
480+
IsError bool `json:"error,omitempty"`
481+
}
482+
483+
func cmdAuditLog(ctx Context) Result {
484+
n := 20
485+
if ctx.HasArg(1) {
486+
if v, err := strconv.Atoi(ctx.Arg(1)); err == nil && v > 0 {
487+
n = v
488+
}
489+
}
490+
491+
home, _ := os.UserHomeDir()
492+
logPath := filepath.Join(home, ".iterate", "audit.jsonl")
493+
494+
f, err := os.Open(logPath)
495+
if err != nil {
496+
if os.IsNotExist(err) {
497+
fmt.Println("No audit log found.")
498+
} else {
499+
PrintError("open audit log: %v", err)
500+
}
501+
return Result{Handled: true}
502+
}
503+
defer f.Close()
504+
505+
// Read all lines then take the last n.
506+
var lines []string
507+
sc := bufio.NewScanner(f)
508+
sc.Buffer(make([]byte, 1024*1024), 1024*1024)
509+
for sc.Scan() {
510+
if line := strings.TrimSpace(sc.Text()); line != "" {
511+
lines = append(lines, line)
512+
}
513+
}
514+
515+
start := len(lines) - n
516+
if start < 0 {
517+
start = 0
518+
}
519+
recent := lines[start:]
520+
521+
fmt.Printf("%s── Audit log (last %d) ─────────────%s\n", ColorDim, len(recent), ColorReset)
522+
for _, line := range recent {
523+
var rec auditLogRecord
524+
if err := json.Unmarshal([]byte(line), &rec); err != nil {
525+
fmt.Printf(" %s%s%s\n", ColorDim, line, ColorReset)
526+
continue
527+
}
528+
ts := rec.Timestamp
529+
if t, err := time.Parse(time.RFC3339, ts); err == nil {
530+
ts = t.Local().Format("15:04:05")
531+
}
532+
statusCol := ColorLime
533+
statusIcon := "✓"
534+
if rec.IsError {
535+
statusCol = ColorRed
536+
statusIcon = "✗"
537+
}
538+
result := rec.Result
539+
if len(result) > 60 {
540+
result = result[:60] + "…"
541+
}
542+
result = strings.ReplaceAll(result, "\n", " ")
543+
fmt.Printf(" %s%s%s %s%-18s%s %s%s%s\n",
544+
statusCol, statusIcon, ColorReset,
545+
ColorDim, ts, ColorReset,
546+
ColorBold, rec.Tool, ColorReset)
547+
if result != "" {
548+
fmt.Printf(" %s%s%s\n", ColorDim, result, ColorReset)
549+
}
550+
}
551+
if len(recent) == 0 {
552+
fmt.Println(" (empty)")
553+
}
554+
fmt.Printf("%s──────────────────────────────────%s\n\n", ColorDim, ColorReset)
555+
return Result{Handled: true}
556+
}
557+
463558
func cmdIterateInit(ctx Context) Result {
464559
iterateMDPath := filepath.Join(ctx.RepoPath, "ITERATE.md")
465560
if _, err := os.Stat(iterateMDPath); err == nil {

0 commit comments

Comments
 (0)