refactor(collect): make verbose mode emit progress
Some checks failed
Security Scan / security (push) Failing after 13s
Test / test (push) Successful in 2m29s

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 <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 07:12:56 +00:00
parent 9a0a1f4435
commit 1bccde8828
3 changed files with 42 additions and 0 deletions

View file

@ -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 {

View file

@ -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)

View file

@ -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)
}