Function bodies 169 total
daemon.Daemon.handleStartTask method · go · L1329-L1366 (38 LOC)internal/daemon/daemon.go
func (d *Daemon) handleStartTask(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req StartRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
if req.TaskID == "" {
http.Error(w, "task_id is required", http.StatusBadRequest)
return
}
// Clear stopped state
d.stoppedMu.Lock()
wasStopped := d.stoppedTasks[req.TaskID]
delete(d.stoppedTasks, req.TaskID)
d.stoppedMu.Unlock()
// Also clear per-task drain state
d.drainedMu.Lock()
delete(d.drainedTasks, req.TaskID)
d.drainedMu.Unlock()
if !wasStopped {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "already_running", "id": req.TaskID})
return
}
dlog.Info("persistent task %s started — will be dispatched on next tick", req.TaskName)
w.Header().Set("Content-Type", "adaemon.Daemon.pruneOldData method · go · L1726-L1741 (16 LOC)internal/daemon/daemon.go
func (d *Daemon) pruneOldData(now time.Time) {
paths := loadWatchedPaths()
if len(paths) == 0 {
return
}
// Only prune on cron ticks (once per minute) to avoid overhead
thisMinute := now.Truncate(time.Minute)
if !thisMinute.Equal(d.lastTick) {
return
}
for _, projPath := range paths {
d.pruneProject(projPath)
}
}daemon.Daemon.pruneProject method · go · L1743-L1757 (15 LOC)internal/daemon/daemon.go
func (d *Daemon) pruneProject(projPath string) {
anvilDir := filepath.Join(projPath, ".anvil")
// Prune logs
logsDir := filepath.Join(anvilDir, "logs")
if _, err := os.Stat(logsDir); err == nil {
d.pruneDir(logsDir, "log")
}
// Prune runs
runsDir := filepath.Join(anvilDir, "runs")
if _, err := os.Stat(runsDir); err == nil {
d.pruneDir(runsDir, "run")
}
}daemon.Daemon.pruneDir method · go · L1759-L1772 (14 LOC)internal/daemon/daemon.go
func (d *Daemon) pruneDir(dir, kind string) {
taskDirs, err := os.ReadDir(dir)
if err != nil {
return
}
for _, taskDir := range taskDirs {
if !taskDir.IsDir() {
continue
}
taskPath := filepath.Join(dir, taskDir.Name())
d.pruneTaskDir(taskPath, kind)
}
}daemon.Daemon.pruneTaskDir method · go · L1774-L1828 (55 LOC)internal/daemon/daemon.go
func (d *Daemon) pruneTaskDir(taskPath, kind string) {
entries, err := os.ReadDir(taskPath)
if err != nil {
return
}
// Collect files with their modification times
type fileInfo struct {
path string
modTime time.Time
}
var files []fileInfo
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}
files = append(files, fileInfo{entry.Name(), info.ModTime()})
}
// Sort by modification time (oldest first)
sort.Slice(files, func(i, j int) bool {
return files[i].modTime.Before(files[j].modTime)
})
var toDelete []string
cutoff := time.Now().Add(-d.config.Retention.MaxAge)
// Delete by age
if d.config.Retention.MaxAge > 0 {
for _, f := range files {
if f.modTime.Before(cutoff) {
toDelete = append(toDelete, f.path)
}
}
}
// Delete by count (keep MaxRuns most recent)
if d.config.Retention.MaxRuns > 0 && len(files) > d.config.Retention.MaxRuns {
keep := len(files) - d.config.Retention.MaxRuns
// files aredaemon.loadWatchedPaths function · go · L1830-L1873 (44 LOC)internal/daemon/daemon.go
func loadWatchedPaths() []string {
watchedDir := config.WatchedDir()
dirs, err := os.ReadDir(watchedDir)
if err != nil {
return nil
}
var paths []string
for _, d := range dirs {
if !d.IsDir() {
continue
}
dirPath := filepath.Join(watchedDir, d.Name())
entries, err := os.ReadDir(dirPath)
if err != nil {
continue
}
// Sort descending to get latest file first
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() > entries[j].Name()
})
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
continue
}
data, err := os.ReadFile(filepath.Join(dirPath, e.Name()))
if err != nil {
break
}
p := parseWatchedPath(string(data))
if p != "" {
paths = append(paths, p)
}
break
}
}
return paths
}daemon.parseWatchedPath function · go · L1879-L1894 (16 LOC)internal/daemon/daemon.go
func parseWatchedPath(content string) string {
start := strings.Index(content, "---\n")
if start == -1 {
return ""
}
end := strings.Index(content[start+4:], "\n---")
if end == -1 {
return ""
}
var fm watchFrontmatter
if err := yaml.Unmarshal([]byte(content[start+4:start+4+end]), &fm); err != nil {
return ""
}
return fm.Path
}Want this analysis on your repo? https://repobility.com/scan/
daemon.IsDaemonRunning function · go · L1897-L1904 (8 LOC)internal/daemon/daemon.go
func IsDaemonRunning() bool {
conn, err := net.Dial("unix", filepath.Join(config.Dir(), "daemon.sock"))
if err != nil {
return false
}
conn.Close()
return true
}daemon.socketClient function · go · L1907-L1916 (10 LOC)internal/daemon/daemon.go
func socketClient() *http.Client {
sockPath := filepath.Join(config.Dir(), "daemon.sock")
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", sockPath)
},
},
}
}daemon.SendPsRequest function · go · L1919-L1955 (37 LOC)internal/daemon/daemon.go
func SendPsRequest() ([]TaskInfo, error) {
resp, err := socketClient().Get("http://daemon/ps")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var tasks []TaskInfo
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
// SendStatusRequest queries the daemon's /status endpoint.
func SendStatusRequest() (*DaemonStatus, error) {
resp, err := socketClient().Get("http://daemon/status")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var status DaemonStatus
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
return nil, err
}
return &status, nil
}daemon.SendStatusRequest function · go · L1939-L1975 (37 LOC)internal/daemon/daemon.go
func SendStatusRequest() (*DaemonStatus, error) {
resp, err := socketClient().Get("http://daemon/status")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var status DaemonStatus
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
return nil, err
}
return &status, nil
}
// SendTimeoutRequest queries the daemon's /timeout endpoint and returns task timeout info.
func SendTimeoutRequest() ([]TaskInfo, error) {
resp, err := socketClient().Get("http://daemon/timeout")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var tasks []TaskInfo
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}daemon.SendTimeoutRequest function · go · L1958-L1995 (38 LOC)internal/daemon/daemon.go
func SendTimeoutRequest() ([]TaskInfo, error) {
resp, err := socketClient().Get("http://daemon/timeout")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var tasks []TaskInfo
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
// SendQueueRequest queries the daemon's /queue endpoint and returns queue status.
func SendQueueRequest() ([]TaskQueueInfo, error) {
resp, err := socketClient().Get("http://daemon/queue")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var tasks []TaskQueueInfo
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}daemon.SendQueueRequest function · go · L1978-L2010 (33 LOC)internal/daemon/daemon.go
func SendQueueRequest() ([]TaskQueueInfo, error) {
resp, err := socketClient().Get("http://daemon/queue")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("daemon request failed: %s", resp.Status)
}
var tasks []TaskQueueInfo
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
// SendDrainRequest tells the daemon to stop-on-idle (daemon-wide).
func SendDrainRequest() error {
resp, err := socketClient().Post("http://daemon/drain", "application/json", bytes.NewBufferString("{}"))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon drain failed: %s", string(body))
}
return nil
}daemon.SendDrainRequest function · go · L1998-L2030 (33 LOC)internal/daemon/daemon.go
func SendDrainRequest() error {
resp, err := socketClient().Post("http://daemon/drain", "application/json", bytes.NewBufferString("{}"))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon drain failed: %s", string(body))
}
return nil
}
// SendDrainTaskRequest marks a specific task (by ID) for stop-on-idle.
func SendDrainTaskRequest(id string) error {
data, err := json.Marshal(DrainTaskRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/drain/task", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon drain task failed: %s", string(body))
}
return nil
}daemon.SendDrainTaskRequest function · go · L2013-L2051 (39 LOC)internal/daemon/daemon.go
func SendDrainTaskRequest(id string) error {
data, err := json.Marshal(DrainTaskRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/drain/task", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon drain task failed: %s", string(body))
}
return nil
}
// SendRunRequest asks the daemon to immediately dispatch a task.
func SendRunRequest(projectPath, taskID, taskName string) error {
data, err := json.Marshal(RunRequest{ProjectPath: projectPath, TaskID: taskID, TaskName: taskName})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/run", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("%s", strings.TrimSpace(striOpen data scored by Repobility · https://repobility.com
daemon.SendRunRequest function · go · L2033-L2071 (39 LOC)internal/daemon/daemon.go
func SendRunRequest(projectPath, taskID, taskName string) error {
data, err := json.Marshal(RunRequest{ProjectPath: projectPath, TaskID: taskID, TaskName: taskName})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/run", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("%s", strings.TrimSpace(string(body)))
}
return nil
}
// SendStopRequest tells the daemon to permanently stop a persistent task.
func SendStopRequest(id string) error {
data, err := json.Marshal(StopRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/stop", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon stop failed: %s", string(body))
daemon.SendStopRequest function · go · L2054-L2091 (38 LOC)internal/daemon/daemon.go
func SendStopRequest(id string) error {
data, err := json.Marshal(StopRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/stop", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon stop failed: %s", string(body))
}
return nil
}
// SendStartRequest tells the daemon to start a stopped persistent task.
func SendStartRequest(projectPath, taskID, taskName string) error {
data, err := json.Marshal(StartRequest{ProjectPath: projectPath, TaskID: taskID, TaskName: taskName})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/start", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon start failed: %s", string(boddaemon.SendStartRequest function · go · L2074-L2112 (39 LOC)internal/daemon/daemon.go
func SendStartRequest(projectPath, taskID, taskName string) error {
data, err := json.Marshal(StartRequest{ProjectPath: projectPath, TaskID: taskID, TaskName: taskName})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/start", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon start failed: %s", string(body))
}
return nil
}
// SendKillRequest sends a kill request to the daemon
func SendKillRequest(id string) error {
data, err := json.Marshal(KillRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/kill", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon kill failed: %s", string(body))
}
return nildaemon.SendKillRequest function · go · L2094-L2128 (35 LOC)internal/daemon/daemon.go
func SendKillRequest(id string) error {
data, err := json.Marshal(KillRequest{ID: id})
if err != nil {
return err
}
resp, err := socketClient().Post("http://daemon/kill", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon kill failed: %s", string(body))
}
return nil
}
// SendReloadRequest sends a reload request to the daemon to reload its config.
func SendReloadRequest() error {
resp, err := socketClient().Post("http://daemon/reload", "application/json", bytes.NewBufferString("{}"))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("daemon reload failed: %s", string(body))
}
return nil
}daemon.newDaemonLogger function · go · L50-L56 (7 LOC)internal/daemon/logger.go
func newDaemonLogger() *daemonLogger {
fi, err := os.Stdout.Stat()
tty := err == nil && (fi.Mode()&os.ModeCharDevice) != 0
l := &daemonLogger{isTTY: tty}
l.openLogFile()
return l
}daemon.daemonLogger.openLogFile method · go · L61-L88 (28 LOC)internal/daemon/logger.go
func (l *daemonLogger) openLogFile() {
if err := config.EnsureDir(); err != nil {
return
}
path := config.DaemonLogPath()
// When daemonized, stdout is redirected to daemon.log by the parent process.
// Detect this to avoid writing each line twice.
if stdoutInfo, err := os.Stdout.Stat(); err == nil {
if logInfo, err := os.Stat(path); err == nil {
if os.SameFile(stdoutInfo, logInfo) {
return
}
}
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return
}
info, err := f.Stat()
if err != nil {
f.Close()
return
}
l.logFile = f
l.logSize = info.Size()
}daemon.daemonLogger.rotateIfNeeded method · go · L92-L102 (11 LOC)internal/daemon/logger.go
func (l *daemonLogger) rotateIfNeeded() {
if l.logFile == nil || l.logSize < maxLogSize {
return
}
l.logFile.Close()
path := config.DaemonLogPath()
_ = os.Rename(path, path+".1")
l.logFile = nil
l.logSize = 0
l.openLogFile()
}daemon.daemonLogger.c method · go · L105-L110 (6 LOC)internal/daemon/logger.go
func (l *daemonLogger) c(code, text string) string {
if !l.isTTY || code == "" {
return text
}
return code + text + ansiReset
}Source: Repobility analyzer · https://repobility.com
daemon.daemonLogger.priorityStr method · go · L112-L119 (8 LOC)internal/daemon/logger.go
func (l *daemonLogger) priorityStr(p int) string {
s := fmt.Sprintf("p%d", p)
idx := p
if idx >= len(priorityColors) {
idx = len(priorityColors) - 1
}
return l.c(priorityColors[idx], s)
}daemon.daemonLogger.println method · go · L134-L146 (13 LOC)internal/daemon/logger.go
func (l *daemonLogger) println(line string) {
fmt.Fprintln(os.Stdout, line)
// Write a plain (no ANSI) copy to the log file.
l.mu.Lock()
defer l.mu.Unlock()
if l.logFile != nil {
plain := ansiPattern.ReplaceAllString(line, "") + "\n"
n, _ := l.logFile.WriteString(plain)
l.logSize += int64(n)
l.rotateIfNeeded()
}
}daemon.daemonLogger.WorkerDone method · go · L187-L192 (6 LOC)internal/daemon/logger.go
func (l *daemonLogger) WorkerDone(id int, projName, name string, elapsed time.Duration) {
label := projName + "/" + name
l.println(fmt.Sprintf("%s %s %s done %s (%s)",
l.ts(), l.workerStr(id), l.c(ansiGreen, "✓"), l.c(ansiBold, label),
l.c(ansiDim, elapsed.Round(time.Second).String())))
}diagnostic.All function · go · L26-L36 (11 LOC)internal/diagnostic/diagnostics.go
func All() []CheckResult {
var results []CheckResult
results = append(results, checkDaemon()...)
results = append(results, checkConfig()...)
results = append(results, checkWatchedProjects()...)
results = append(results, checkTasks()...)
results = append(results, checkRecentRuns()...)
return results
}diagnostic.checkDaemon function · go · L39-L103 (65 LOC)internal/diagnostic/diagnostics.go
func checkDaemon() []CheckResult {
var results []CheckResult
// Check PID file
pidPath := config.PidFile()
pidData, err := os.ReadFile(pidPath)
if os.IsNotExist(err) {
results = append(results, CheckResult{
Name: "daemon_pid",
Status: "warn",
Message: "PID file does not exist",
Fix: "Start the daemon with 'anvil watch'",
})
// Can't check further without PID
return results
}
var pid int
if _, err := fmt.Sscanf(strings.TrimSpace(string(pidData)), "%d", &pid); err != nil {
results = append(results, CheckResult{
Name: "daemon_pid",
Status: "fail",
Message: "PID file is malformed",
Fix: "Remove " + pidPath + " and restart daemon",
})
return results
}
// Check if process is running
if err := syscallExists(pid); err != nil {
results = append(results, CheckResult{
Name: "daemon_process",
Status: "fail",
Message: fmt.Sprintf("Daemon process (PID %d) is not running", pid),
Fix: "Remove " + pidPath + " and restart ddiagnostic.checkConfig function · go · L106-L179 (74 LOC)internal/diagnostic/diagnostics.go
func checkConfig() []CheckResult {
var results []CheckResult
cfgPath := config.Path()
// Check if config exists
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
results = append(results, CheckResult{
Name: "config_exists",
Status: "warn",
Message: "Config file does not exist",
Fix: "Run 'anvil init' to create a default config",
})
return results
}
results = append(results, CheckResult{
Name: "config_exists",
Status: "pass",
Message: "Config file exists",
})
// Try to parse config
cfg, err := config.Load()
if err != nil {
results = append(results, CheckResult{
Name: "config_parse",
Status: "fail",
Message: fmt.Sprintf("Config file is invalid: %v", err),
Fix: "Fix the syntax errors in " + cfgPath,
})
return results
}
results = append(results, CheckResult{
Name: "config_parse",
Status: "pass",
Message: "Config file parses successfully",
})
// Check runners are configured
if len(cfg.Runners) == 0 {
resudiagnostic.checkWatchedProjects function · go · L182-L242 (61 LOC)internal/diagnostic/diagnostics.go
func checkWatchedProjects() []CheckResult {
var results []CheckResult
watchedDir := config.WatchedDir()
entries, err := os.ReadDir(watchedDir)
if os.IsNotExist(err) {
results = append(results, CheckResult{
Name: "watched_projects",
Status: "warn",
Message: "No watched projects directory",
Fix: "Add a project with 'anvil project add <path>'",
})
return results
}
if len(entries) == 0 {
results = append(results, CheckResult{
Name: "watched_projects",
Status: "warn",
Message: "No projects being watched",
Fix: "Add a project with 'anvil project add <path>'",
})
return results
}
var missing []string
for _, e := range entries {
if !e.IsDir() {
continue
}
path := filepath.Join(watchedDir, e.Name())
target, err := os.Readlink(path)
if err != nil {
continue // not a symlink, skip
}
// Resolve relative symlinks
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(path), target)
}
if _, err := os.Stadiagnostic.checkTasks function · go · L245-L348 (104 LOC)internal/diagnostic/diagnostics.go
func checkTasks() []CheckResult {
var results []CheckResult
watchedDir := config.WatchedDir()
entries, err := os.ReadDir(watchedDir)
if os.IsNotExist(err) || len(entries) == 0 {
// No projects, nothing to check
return results
}
var staleLocks []string
var invalidCron []string
var missingIDs []string
for _, e := range entries {
if !e.IsDir() {
continue
}
projPath := filepath.Join(watchedDir, e.Name())
target, err := os.Readlink(projPath)
if err != nil {
continue
}
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(projPath), target)
}
proj, err := project.Load(target)
if err != nil {
continue
}
todos, err := proj.LoadTodos()
if err != nil {
continue
}
for _, todo := range todos {
// Check for stale locks
if todo.IsLocked {
staleLocks = append(staleLocks, todo.Name)
}
// Check for missing ID
if todo.ID == "" {
missingIDs = append(missingIDs, todo.Name)
}
// Check for invalid cron
Repobility · code-quality intelligence · https://repobility.com
diagnostic.checkRecentRuns function · go · L351-L444 (94 LOC)internal/diagnostic/diagnostics.go
func checkRecentRuns() []CheckResult {
var results []CheckResult
watchedDir := config.WatchedDir()
entries, err := os.ReadDir(watchedDir)
if os.IsNotExist(err) || len(entries) == 0 {
return results
}
type failureInfo struct {
taskID string
name string
count int
}
var failures []failureInfo
for _, e := range entries {
if !e.IsDir() {
continue
}
projPath := filepath.Join(watchedDir, e.Name())
target, err := os.Readlink(projPath)
if err != nil {
continue
}
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(projPath), target)
}
proj, err := project.Load(target)
if err != nil {
continue
}
todos, err := proj.LoadTodos()
if err != nil {
continue
}
for _, todo := range todos {
if todo.ID == "" {
continue
}
// Check last 10 runs
records, err := project.ReadAllRunRecords(target, todo.ID)
if err != nil {
continue
}
// Only check runs from the last 24 hours
cutoff := time.Now()diagnostic.syscallExists function · go · L447-L458 (12 LOC)internal/diagnostic/diagnostics.go
func syscallExists(pid int) error {
// On Unix, we can use kill(pid, 0) to check if process exists
process, err := os.FindProcess(pid)
if err != nil {
return err
}
err = process.Signal(os.Signal(nil))
if err != nil {
return err
}
return nil
}project.LoadConfig function · go · L52-L65 (14 LOC)internal/project/project.go
func LoadConfig(projectPath string) (Config, error) {
data, err := os.ReadFile(ConfigPath(projectPath))
if err != nil {
if os.IsNotExist(err) {
return Config{}, nil
}
return Config{}, fmt.Errorf("reading project config: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return Config{}, fmt.Errorf("parsing project config: %w", err)
}
return cfg, nil
}project.Load function · go · L120-L126 (7 LOC)internal/project/project.go
func Load(path string) (*Project, error) {
cfg, err := LoadConfig(path)
if err != nil {
return nil, err
}
return &Project{Path: path, Config: cfg}, nil
}project.Project.LoadTodos method · go · L131-L295 (165 LOC)internal/project/project.go
func (p *Project) LoadTodos() ([]Todo, error) {
todosDir := filepath.Join(p.Path, ".anvil", "todos")
defaults := p.Config.Defaults
var todos []Todo
for pri := 0; pri <= 9; pri++ {
dir := filepath.Join(todosDir, fmt.Sprintf("p%d", pri))
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("reading todos p%d: %w", pri, err)
}
// Sort by name so oldest-timestamped files come first
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
continue
}
fp := filepath.Join(dir, e.Name())
raw, err := os.ReadFile(fp)
if err != nil {
continue
}
// Check for lock file (stale lock indicates daemon crashed mid-execution)
lockPath := fp + ".lock"
_, lockErr := os.Stat(lockPath)
hasLock := lockErr == nil
// Parse optional front-matter for a schedule.
// project.applyDefaults function · go · L300-L369 (70 LOC)internal/project/project.go
func applyDefaults(defaults TaskDefaults, fmKeys map[string]interface{},
skipPermissions *bool, allowedTools *[]string, preCheck *string,
onSuccess *string, onFailure *string, timeout *time.Duration,
retry *int, retryDelay *time.Duration, maxConcurrent *int,
persistentCooldown *time.Duration, persistentMaxRuntime *time.Duration,
persistentBudget *time.Duration, maxLogSize *int64, runnerOverride *string) {
has := func(key string) bool {
if fmKeys == nil {
return false
}
_, ok := fmKeys[key]
return ok
}
if !has("skip_permissions") && defaults.SkipPermissions {
*skipPermissions = defaults.SkipPermissions
}
if !has("allowed_tools") && len(defaults.AllowedTools) > 0 {
*allowedTools = defaults.AllowedTools
}
if !has("pre_check") && defaults.PreCheck != "" {
*preCheck = defaults.PreCheck
}
if !has("on_success") && defaults.OnSuccess != "" {
*onSuccess = defaults.OnSuccess
}
if !has("on_failure") && defaults.OnFailure != "" {
*onFailure = defaults.OnFailuproject.RemoveLock function · go · L383-L394 (12 LOC)internal/project/project.go
func RemoveLock(todo Todo) error {
lockPath := todo.Path + ".lock"
_, err := os.Stat(lockPath)
if os.IsNotExist(err) {
// No lock file to remove
return nil
}
if err != nil {
return fmt.Errorf("checking lock file %s: %w", lockPath, err)
}
return os.Remove(lockPath)
}project.Init function · go · L399-L411 (13 LOC)internal/project/project.go
func Init(path string, toolsFS fs.FS) error {
todosDir := filepath.Join(path, ".anvil", "todos")
if err := os.MkdirAll(todosDir, 0755); err != nil {
return fmt.Errorf("creating .anvil/todos: %w", err)
}
claudeDir := filepath.Join(path, ".claude")
if err := writeEmbeddedFS(claudeDir, toolsFS); err != nil {
return fmt.Errorf("writing .claude/ tools: %w", err)
}
return nil
}Want this analysis on your repo? https://repobility.com/scan/
project.writeEmbeddedFS function · go · L415-L438 (24 LOC)internal/project/project.go
func writeEmbeddedFS(destDir string, fsys fs.FS) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
target := filepath.Join(destDir, path)
if d.IsDir() {
return os.MkdirAll(target, 0755)
}
data, err := fs.ReadFile(fsys, path)
if err != nil {
return fmt.Errorf("reading embedded %s: %w", path, err)
}
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
return os.WriteFile(target, data, 0644)
})
}project.Project.AddTodo method · go · L442-L514 (73 LOC)internal/project/project.go
func (p *Project) AddTodo(priority int, schedule string, content string, preCheck string, allowedTools string, maxConcurrent int, skipPermissions bool, runnerCmd string) (string, error) {
if priority < 0 || priority > 9 {
return "", fmt.Errorf("priority must be 0-9, got %d", priority)
}
if strings.TrimSpace(content) == "" {
return "", fmt.Errorf("task content must not be empty")
}
// Validate cron expression before writing the task file.
// Skip validation for "persistent" since it's a special keyword, not a cron expression.
if schedule != "" && schedule != "persistent" {
if _, err := cron.Parse(schedule); err != nil {
return "", fmt.Errorf("invalid schedule %q: %w", schedule, err)
}
}
dir := filepath.Join(p.Path, ".anvil", "todos", fmt.Sprintf("p%d", priority))
if err := os.MkdirAll(dir, 0755); err != nil {
return "", fmt.Errorf("creating todos/p%d: %w", priority, err)
}
base := slugify(content)
filename := base + ".md"
// Avoid silent overwrites on slug project.newUUID function · go · L517-L523 (7 LOC)internal/project/project.go
func newUUID() string {
var b [16]byte
_, _ = rand.Read(b[:])
b[6] = (b[6] & 0x0f) | 0x40 // version 4
b[8] = (b[8] & 0x3f) | 0x80 // variant 1
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}project.SessionPathBySessionID function · go · L534-L539 (6 LOC)internal/project/project.go
func SessionPathBySessionID(projectPath string, sessionID string) string {
home, _ := os.UserHomeDir()
slug := strings.ReplaceAll(projectPath, "/", "-")
slug = strings.ReplaceAll(slug, "_", "-")
return filepath.Join(home, ".claude", "projects", slug, sessionID+".jsonl")
}project.WriteRunRecord function · go · L557-L576 (20 LOC)internal/project/project.go
func WriteRunRecord(projectPath string, rec RunRecord) error {
dir := runsDir(projectPath, rec.TaskID)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("creating runs dir: %w", err)
}
data, err := json.Marshal(rec)
if err != nil {
return fmt.Errorf("marshaling run record: %w", err)
}
recPath := filepath.Join(dir, rec.RunID+".json")
if err := os.WriteFile(recPath, data, 0644); err != nil {
return fmt.Errorf("writing run record: %w", err)
}
// Update "current" pointer to the latest runID
currentPath := filepath.Join(dir, "current")
return os.WriteFile(currentPath, []byte(rec.RunID), 0644)
}project.ReadCurrentRunRecord function · go · L579-L598 (20 LOC)internal/project/project.go
func ReadCurrentRunRecord(projectPath, taskID string) (RunRecord, error) {
currentPath := CurrentRunPath(projectPath, taskID)
runIDBytes, err := os.ReadFile(currentPath)
if err != nil {
return RunRecord{}, fmt.Errorf("reading current run pointer: %w", err)
}
runID := strings.TrimSpace(string(runIDBytes))
recPath := RunPath(projectPath, taskID, runID)
data, err := os.ReadFile(recPath)
if err != nil {
return RunRecord{}, fmt.Errorf("reading run record: %w", err)
}
var rec RunRecord
if err := json.Unmarshal(data, &rec); err != nil {
return RunRecord{}, fmt.Errorf("parsing run record: %w", err)
}
return rec, nil
}project.ReadAllRunRecords function · go · L601-L634 (34 LOC)internal/project/project.go
func ReadAllRunRecords(projectPath, taskID string) ([]RunRecord, error) {
dir := runsDir(projectPath, taskID)
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("reading runs dir: %w", err)
}
var records []RunRecord
for _, e := range entries {
if e.IsDir() || e.Name() == "current" || !strings.HasSuffix(e.Name(), ".json") {
continue
}
recPath := filepath.Join(dir, e.Name())
data, err := os.ReadFile(recPath)
if err != nil {
continue // skip unreadable records
}
var rec RunRecord
if err := json.Unmarshal(data, &rec); err != nil {
continue // skip malformed records
}
records = append(records, rec)
}
// Sort by start time, newest first
sort.Slice(records, func(i, j int) bool {
return records[i].Started.After(records[j].Started)
})
return records, nil
}project.LatestSessionID function · go · L638-L644 (7 LOC)internal/project/project.go
func LatestSessionID(projectPath, taskID string) (string, error) {
rec, err := ReadCurrentRunRecord(projectPath, taskID)
if err == nil {
return rec.SessionID, nil
}
return "", fmt.Errorf("no run record found for task %s", taskID)
}Open data scored by Repobility · https://repobility.com
project.slugify function · go · L652-L665 (14 LOC)internal/project/project.go
func slugify(s string) string {
s = strings.ToLower(s)
s = reNonAlphaNum.ReplaceAllString(s, "-")
s = reMultipleHyphen.ReplaceAllString(s, "-")
s = strings.Trim(s, "-")
if len(s) > 50 {
s = s[:50]
s = strings.TrimRight(s, "-")
}
if s == "" {
s = "task"
}
return s
}project.PruneProject function · go · L28-L49 (22 LOC)internal/project/retention.go
func PruneProject(projectPath string, opts PruneOptions) PruneResult {
if opts.Now.IsZero() {
opts.Now = time.Now()
}
var result PruneResult
// Find all task IDs by scanning the runs directory
logsBase := filepath.Join(projectPath, ".anvil", "logs")
runsBase := filepath.Join(projectPath, ".anvil", "runs")
taskIDs := discoverTaskIDs(logsBase, runsBase)
for _, taskID := range taskIDs {
r := pruneTask(logsBase, runsBase, taskID, opts)
result.LogsDeleted += r.LogsDeleted
result.RunsDeleted += r.RunsDeleted
result.Errors = append(result.Errors, r.Errors...)
}
return result
}project.discoverTaskIDs function · go · L52-L73 (22 LOC)internal/project/retention.go
func discoverTaskIDs(logsBase, runsBase string) []string {
seen := make(map[string]bool)
for _, base := range []string{logsBase, runsBase} {
entries, err := os.ReadDir(base)
if err != nil {
continue
}
for _, e := range entries {
if e.IsDir() {
seen[e.Name()] = true
}
}
}
ids := make([]string, 0, len(seen))
for id := range seen {
ids = append(ids, id)
}
sort.Strings(ids)
return ids
}