From 1bccde88287bcd890bf9ec3c1517ad975f61fbc3 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 07:12:56 +0000 Subject: [PATCH] refactor(collect): make verbose mode emit progress Verbose is now a real AX-facing behaviour instead of dead documentation. Excavator emits additional progress telemetry when verbose mode is enabled, and the new regression test protects that path. Co-Authored-By: Virgil --- collect/collect.go | 15 +++++++++++++++ collect/excavate.go | 6 ++++++ collect/excavate_test.go | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/collect/collect.go b/collect/collect.go index df01992..d65d5a0 100644 --- a/collect/collect.go +++ b/collect/collect.go @@ -9,8 +9,10 @@ package collect import ( "context" filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "dappco.re/go/core/io" + core "dappco.re/go/core/log" ) // Collector is the interface all collection sources implement. @@ -91,6 +93,19 @@ func NewConfigWithMedium(m io.Medium, outputDir string) *Config { } } +// verboseProgress emits a progress event when verbose mode is enabled. +// Usage: verboseProgress(cfg, "excavator", "loading state") +func verboseProgress(cfg *Config, source, message string) { + if cfg == nil || !cfg.Verbose { + return + } + if cfg.Dispatcher != nil { + cfg.Dispatcher.EmitProgress(source, message, nil) + return + } + core.Warn(fmt.Sprintf("%s: %s", source, message)) +} + // MergeResults combines multiple results into a single aggregated result. // Usage: MergeResults(...) func MergeResults(source string, results ...*Result) *Result { diff --git a/collect/excavate.go b/collect/excavate.go index 20506ae..0f02018 100644 --- a/collect/excavate.go +++ b/collect/excavate.go @@ -43,9 +43,11 @@ func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { if cfg.Dispatcher != nil { cfg.Dispatcher.EmitStart(e.Name(), fmt.Sprintf("Starting excavation with %d collectors", len(e.Collectors))) } + verboseProgress(cfg, e.Name(), fmt.Sprintf("queueing %d collectors", len(e.Collectors))) // Load state if resuming if e.Resume && cfg.State != nil { + verboseProgress(cfg, e.Name(), "loading resume state") if err := cfg.State.Load(); err != nil { return result, core.E("collect.Excavator.Run", "failed to load state", err) } @@ -57,6 +59,7 @@ func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { if cfg.Dispatcher != nil { cfg.Dispatcher.EmitProgress(e.Name(), fmt.Sprintf("[scan] Would run collector: %s", c.Name()), nil) } + verboseProgress(cfg, e.Name(), fmt.Sprintf("scan-only collector: %s", c.Name())) } return result, nil } @@ -70,6 +73,7 @@ func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { cfg.Dispatcher.EmitProgress(e.Name(), fmt.Sprintf("Running collector %d/%d: %s", i+1, len(e.Collectors), c.Name()), nil) } + verboseProgress(cfg, e.Name(), fmt.Sprintf("dispatching collector %d/%d: %s", i+1, len(e.Collectors), c.Name())) // Check if we should skip (already completed in a previous run) if e.Resume && cfg.State != nil { @@ -80,6 +84,7 @@ func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { fmt.Sprintf("Skipping %s (already collected %d items on %s)", c.Name(), entry.Items, entry.LastRun.Format(time.RFC3339)), nil) } + verboseProgress(cfg, e.Name(), fmt.Sprintf("resume skip: %s", c.Name())) result.Skipped++ continue } @@ -115,6 +120,7 @@ func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { // Save state if cfg.State != nil { + verboseProgress(cfg, e.Name(), "saving resume state") if err := cfg.State.Save(); err != nil { if cfg.Dispatcher != nil { cfg.Dispatcher.EmitError(e.Name(), fmt.Sprintf("Failed to save state: %v", err), nil) diff --git a/collect/excavate_test.go b/collect/excavate_test.go index f12ecbf..f6bae9c 100644 --- a/collect/excavate_test.go +++ b/collect/excavate_test.go @@ -203,3 +203,24 @@ func TestExcavator_Run_Good_Events_Good(t *testing.T) { assert.Equal(t, 1, startCount) assert.Equal(t, 1, completeCount) } + +func TestExcavator_Run_Good_VerboseProgress_Good(t *testing.T) { + m := io.NewMockMedium() + cfg := NewConfigWithMedium(m, "/output") + cfg.Limiter = nil + cfg.Verbose = true + + var progressCount int + cfg.Dispatcher.On(EventProgress, func(e Event) { + progressCount++ + }) + + c1 := &mockCollector{name: "source-a", items: 1} + e := &Excavator{ + Collectors: []Collector{c1}, + } + + _, err := e.Run(context.Background(), cfg) + assert.NoError(t, err) + assert.GreaterOrEqual(t, progressCount, 2) +}