Delete GarbageCollector implementation
apps/backend/internal/office/infra/gc.go ↗Removes the unwired sweeper that deleted orphan worktrees and stale containers.
Deleted file
deleted file mode 100644
// Package infra provides infrastructure-level background jobs for the office
// domain, including garbage collection and reconciliation.
package infra
import (
"context"
"errors"
"os"
"path/filepath"
"time"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/office/repository/sqlite"
)
// DefaultGCInterval is the default interval between GC sweeps.
const DefaultGCInterval = 3 * time.Hour
// WorktreeGracePeriod is the minimum age a directory must have before the
// worktree sweep will consider it for deletion.
const WorktreeGracePeriod = 24 * time.Hour
type GarbageCollector struct {
repo *sqlite.Repository
worktreeInv WorktreeInventory
worktreeBase string
dockerClient DockerClient
interval time.Duration
logger *logger.Logger
}
func NewGarbageCollector(repo *sqlite.Repository, worktreeInv WorktreeInventory, log *logger.Logger, worktreeBase string, dockerClient DockerClient, interval time.Duration) *GarbageCollector {
if interval <= 0 {
interval = DefaultGCInterval
}
return &GarbageCollector{repo: repo, worktreeInv: worktreeInv, worktreeBase: worktreeBase, dockerClient: dockerClient, interval: interval, logger: log.WithFields(zap.String("component", "office-gc"))}
}
func (gc *GarbageCollector) Start(ctx context.Context) {
gc.logger.Info("GC sweep loop starting", zap.Duration("interval", gc.interval))
result := gc.Sweep(ctx)
gc.logResult(result)
ticker := time.NewTicker(gc.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
gc.logger.Info("GC sweep loop stopping")
return
case <-ticker.C:
result := gc.Sweep(ctx)
gc.logResult(result)
}
}
}
func (gc *GarbageCollector) Sweep(ctx context.Context) GCSweepResult {
var result GCSweepResult
wtResult := gc.sweepWorktrees(ctx)
ctrResult := gc.sweepContainers(ctx)
return result
}
Delete GC test suite
apps/backend/internal/office/infra/gc_test.go ↗Removes the 596-line test suite that covered the deleted sweeper.
Deleted tests
deleted file mode 100644
package infra_test
import (
"context"
"errors"
"os"
"path/filepath"
"testing"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/office/infra"
"github.com/kandev/kandev/internal/office/repository/sqlite"
)
type mockDockerClient struct {
containers []infra.GCContainerInfo
removed []string
removeErr error
}
func (m *mockDockerClient) ListContainers(_ context.Context, _ map[string]string) ([]infra.GCContainerInfo, error) {
return m.containers, nil
}
type fakeInventory struct {
paths []string
err error
}
func TestGC_WorktreeSweep_DeletesOldOrphan(t *testing.T) {
base := t.TempDir()
orphan := filepath.Join(base, "orphan_xyz")
if err := os.MkdirAll(orphan, 0o755); err != nil {
t.Fatal(err)
}
old := time.Now().Add(-25 * time.Hour)
if err := os.Chtimes(orphan, old, old); err != nil {
t.Fatal(err)
}
gc, _ := newTestGC(t, base, nil, &fakeInventory{})
result := gc.Sweep(context.Background())
if _, err := os.Stat(orphan); !os.IsNotExist(err) {
t.Errorf("expected orphan dir removed; stat err = %v", err)
}
}
func TestGC_OrphanContainerRemoved(t *testing.T) {
mock := &mockDockerClient{containers: []infra.GCContainerInfo{{ID: "ctr-orphan-1", State: "exited", Labels: map[string]string{"kandev.task_id": "no-such-task"}}}}
gc, _ := newTestGC(t, "", mock, nil)
result := gc.Sweep(context.Background())
if result.ContainersRemoved != 1 {
t.Errorf("containers_removed = %d, want 1", result.ContainersRemoved)
}
}
Remove GC from Services wiring
apps/backend/internal/office/services.go ↗Drops the GC field so Services no longer references dead code.
Services struct
package office
import (
"github.com/kandev/kandev/internal/office/agents"
"github.com/kandev/kandev/internal/office/approvals"
"github.com/kandev/kandev/internal/office/channels"
"github.com/kandev/kandev/internal/office/config"
"github.com/kandev/kandev/internal/office/configloader"
"github.com/kandev/kandev/internal/office/configsync"
"github.com/kandev/kandev/internal/office/costs"
"github.com/kandev/kandev/internal/office/dashboard"
"github.com/kandev/kandev/internal/office/infra"
"github.com/kandev/kandev/internal/office/labels"
"github.com/kandev/kandev/internal/office/onboarding"
"github.com/kandev/kandev/internal/office/projects"
"github.com/kandev/kandev/internal/office/repository/sqlite"
"github.com/kandev/kandev/internal/office/routines"
"github.com/kandev/kandev/internal/office/scheduler"
officeservice "github.com/kandev/kandev/internal/office/service"
"github.com/kandev/kandev/internal/office/skills"
taskservice "github.com/kandev/kandev/internal/task/service"
)
// Services holds references to all feature services in the office domain.
// It is the central wiring point for HTTP handlers and background jobs.
type Services struct {
Agents *agents.AgentService
Skills *skills.SkillService
Projects *projects.ProjectService
Costs *costs.CostService
Routines *routines.RoutineService
Approvals *approvals.ApprovalService
Channels *channels.ChannelService
Config *config.ConfigService
ConfigSync *configsync.Service
Dashboard *dashboard.DashboardService
Labels *labels.LabelService
Onboarding *onboarding.OnboardingService
Scheduler *scheduler.SchedulerService
TreeControls *officeservice.Service
Workspaces *officeservice.Service
Documents *taskservice.DocumentService
GC *infra.GarbageCollector
Reconciler *infra.Reconciler
Repo *sqlite.Repository
GitManager *configloader.GitManager
// KandevHome is the base storage directory for attachment files.
KandevHome string
}
Fix stale scheduler and infra docs
apps/backend/internal/office/scheduler/run.go ↗Updates the package doc to describe current duties, not the old tick loop.
Scheduler doc
// Package scheduler orchestrates run processing for the office domain.
// It wraps service.Service and owns the tick loop, retry logic, event
// subscribers, and idle-timeout management.
// It wraps service.Service and owns run processing, dispatch/tier routing,
// retry logic, reactivity, and mentions.
package scheduler
Reconciler doc
// Package infra provides infrastructure-level background jobs for the office domain.
package infra