summaryrefslogtreecommitdiff
path: root/internal/storage/db.go
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-03-14 16:02:28 +0000
committerClaudomator Agent <agent@claudomator>2026-03-14 16:02:28 +0000
commit59bc518eee4026fa072c163149389b05428b5398 (patch)
treefbcde553161e5ac4784d70426a57434e003b1287 /internal/storage/db.go
parent4029fdd82bdd657ed862c89f20eb03ff2594cde9 (diff)
feat: add Changestats struct and storage support
- Add task.Changestats{FilesChanged, LinesAdded, LinesRemoved} - Add changestats_json column to executions via additive migration - Add Changestats field to storage.Execution struct - Add UpdateExecutionChangestats(execID, *task.Changestats) method - Update all SELECT/INSERT/scan paths for executions - Test: TestExecution_StoreAndRetrieveChangestats (was red, now green) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/storage/db.go')
-rw-r--r--internal/storage/db.go44
1 files changed, 37 insertions, 7 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index 043009c..2b7e33f 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -83,6 +83,7 @@ func (s *DB) migrate() error {
`ALTER TABLE executions ADD COLUMN sandbox_dir TEXT`,
`ALTER TABLE tasks ADD COLUMN summary TEXT`,
`ALTER TABLE tasks ADD COLUMN interactions_json TEXT NOT NULL DEFAULT '[]'`,
+ `ALTER TABLE executions ADD COLUMN changestats_json TEXT`,
}
for _, m := range migrations {
if _, err := s.db.Exec(m); err != nil {
@@ -366,6 +367,8 @@ type Execution struct {
SessionID string // claude --session-id; persisted for resume
SandboxDir string // preserved sandbox path when task is BLOCKED; resume must run here
+ Changestats *task.Changestats // stored as JSON; nil if not yet recorded
+
// In-memory only: set when creating a resume execution, not stored in DB.
ResumeSessionID string
ResumeAnswer string
@@ -375,24 +378,33 @@ type Execution struct {
// CreateExecution inserts an execution record.
func (s *DB) CreateExecution(e *Execution) error {
+ var changestatsJSON *string
+ if e.Changestats != nil {
+ b, err := json.Marshal(e.Changestats)
+ if err != nil {
+ return fmt.Errorf("marshaling changestats: %w", err)
+ }
+ s := string(b)
+ changestatsJSON = &s
+ }
_, err := s.db.Exec(`
- INSERT INTO executions (id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ INSERT INTO executions (id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir, changestats_json)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
e.ID, e.TaskID, e.StartTime.UTC(), e.EndTime.UTC(), e.ExitCode, e.Status,
- e.StdoutPath, e.StderrPath, e.ArtifactDir, e.CostUSD, e.ErrorMsg, e.SessionID, e.SandboxDir,
+ e.StdoutPath, e.StderrPath, e.ArtifactDir, e.CostUSD, e.ErrorMsg, e.SessionID, e.SandboxDir, changestatsJSON,
)
return err
}
// GetExecution retrieves an execution by ID.
func (s *DB) GetExecution(id string) (*Execution, error) {
- row := s.db.QueryRow(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir FROM executions WHERE id = ?`, id)
+ row := s.db.QueryRow(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir, changestats_json FROM executions WHERE id = ?`, id)
return scanExecution(row)
}
// ListExecutions returns executions for a task.
func (s *DB) ListExecutions(taskID string) ([]*Execution, error) {
- rows, err := s.db.Query(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir FROM executions WHERE task_id = ? ORDER BY start_time DESC`, taskID)
+ rows, err := s.db.Query(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir, changestats_json FROM executions WHERE task_id = ? ORDER BY start_time DESC`, taskID)
if err != nil {
return nil, err
}
@@ -411,7 +423,7 @@ func (s *DB) ListExecutions(taskID string) ([]*Execution, error) {
// GetLatestExecution returns the most recent execution for a task.
func (s *DB) GetLatestExecution(taskID string) (*Execution, error) {
- row := s.db.QueryRow(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir FROM executions WHERE task_id = ? ORDER BY start_time DESC LIMIT 1`, taskID)
+ row := s.db.QueryRow(`SELECT id, task_id, start_time, end_time, exit_code, status, stdout_path, stderr_path, artifact_dir, cost_usd, error_msg, session_id, sandbox_dir, changestats_json FROM executions WHERE task_id = ? ORDER BY start_time DESC LIMIT 1`, taskID)
return scanExecution(row)
}
@@ -647,16 +659,34 @@ func scanExecution(row scanner) (*Execution, error) {
var e Execution
var sessionID sql.NullString
var sandboxDir sql.NullString
+ var changestatsJSON sql.NullString
err := row.Scan(&e.ID, &e.TaskID, &e.StartTime, &e.EndTime, &e.ExitCode, &e.Status,
- &e.StdoutPath, &e.StderrPath, &e.ArtifactDir, &e.CostUSD, &e.ErrorMsg, &sessionID, &sandboxDir)
+ &e.StdoutPath, &e.StderrPath, &e.ArtifactDir, &e.CostUSD, &e.ErrorMsg, &sessionID, &sandboxDir, &changestatsJSON)
if err != nil {
return nil, err
}
e.SessionID = sessionID.String
e.SandboxDir = sandboxDir.String
+ if changestatsJSON.Valid && changestatsJSON.String != "" {
+ var cs task.Changestats
+ if err := json.Unmarshal([]byte(changestatsJSON.String), &cs); err != nil {
+ return nil, fmt.Errorf("unmarshaling changestats: %w", err)
+ }
+ e.Changestats = &cs
+ }
return &e, nil
}
+// UpdateExecutionChangestats stores git change metrics for a completed execution.
+func (s *DB) UpdateExecutionChangestats(execID string, stats *task.Changestats) error {
+ b, err := json.Marshal(stats)
+ if err != nil {
+ return fmt.Errorf("marshaling changestats: %w", err)
+ }
+ _, err = s.db.Exec(`UPDATE executions SET changestats_json = ? WHERE id = ?`, string(b), execID)
+ return err
+}
+
func scanExecutionRows(rows *sql.Rows) (*Execution, error) {
return scanExecution(rows)
}