Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions cmd/hookwise/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func TestRenderBuiltinSegments(t *testing.T) {
emptyCache := map[string]interface{}{}

// Segments with no data should return empty (omitted from output).
noDataSegments := []string{"session", "cost", "project", "calendar", "pulse", "weather"}
noDataSegments := []string{"session", "cost", "project", "calendar", "weather"}
for _, name := range noDataSegments {
result := renderBuiltinSegment(name, emptyCache, nil)
if result != "" {
Expand Down Expand Up @@ -546,7 +546,6 @@ status_line:
segments:
- builtin: weather
- builtin: project
- builtin: pulse
- builtin: calendar
`
configPath := filepath.Join(tmpDir, core.ProjectConfigFile)
Expand Down Expand Up @@ -792,15 +791,6 @@ func TestStatusLinePlaceholderFallback(t *testing.T) {
"source": "placeholder",
},
},
"pulse": map[string]interface{}{
"type": "pulse",
"timestamp": "2026-03-07T10:00:00Z",
"data": map[string]interface{}{
"session_count": 0,
"active_sessions": 0,
"source": "placeholder",
},
},
"project": map[string]interface{}{
"type": "project",
"timestamp": "2026-03-07T10:00:00Z",
Expand All @@ -820,7 +810,7 @@ func TestStatusLinePlaceholderFallback(t *testing.T) {
},
}

for _, name := range []string{"weather", "pulse", "project", "calendar"} {
for _, name := range []string{"weather", "project", "calendar"} {
result := renderBuiltinSegment(name, feedCache, nil)
// Placeholder feeds should produce empty output (segment omitted).
if result != "" {
Expand Down
31 changes: 0 additions & 31 deletions cmd/hookwise/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,6 @@ func renderBuiltinSegment(name string, feedCache map[string]interface{}, summary
return renderProjectSegment(feedCache)
case "calendar":
return renderCalendarSegment(feedCache)
case "pulse":
return renderPulseSegment(feedCache)
case "weather":
return renderWeatherSegment(feedCache)
case "insights":
Expand Down Expand Up @@ -742,35 +740,6 @@ func calendarRelativeTime(now, eventStart time.Time) string {
return eventStart.Format("Mon") + " " + timeLabel
}

// renderPulseSegment renders the pulse segment from feed cache data.
func renderPulseSegment(feedCache map[string]interface{}) string {
data := feedData(feedCache, "pulse")
if data == nil {
return ""
}

sessionCount, ok := data["session_count"]
if !ok {
return ""
}

var count int
switch v := sessionCount.(type) {
case float64:
count = int(v)
case int:
count = v
default:
return ""
}

suffix := "sessions"
if count == 1 {
suffix = "session"
}
return ansiGreen + fmt.Sprintf("pulse: %d %s", count, suffix) + ansiReset
}

// renderInsightsSegment renders a compact one-line insights summary.
func renderInsightsSegment(feedCache map[string]interface{}) string {
data := feedData(feedCache, "insights")
Expand Down
10 changes: 0 additions & 10 deletions internal/core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ func GetDefaultConfig() HooksConfig {
},
Includes: []string{},
Feeds: FeedsConfig{
Pulse: PulseFeedConfig{
Enabled: true,
IntervalSeconds: 30,
Thresholds: PulseThresholds{Green: 0, Yellow: 30, Orange: 60, Red: 120, Skull: 180},
},
Project: ProjectFeedConfig{
Enabled: true,
IntervalSeconds: 60,
Expand Down Expand Up @@ -119,11 +114,6 @@ func GetDefaultConfig() HooksConfig {
StalenessDays: 30,
UsageDataPath: filepath.Join(home, ".claude", "usage-data"),
},
Practice: PracticeFeedConfig{
Enabled: true,
IntervalSeconds: 120,
DBPath: filepath.Join(home, ".practice-tracker", "practice-tracker.db"),
},
Weather: WeatherFeedConfig{
Enabled: false,
IntervalSeconds: 600,
Expand Down
25 changes: 2 additions & 23 deletions internal/core/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,12 +721,6 @@ func TestGetDefaultConfig_CoachingDefaults(t *testing.T) {

func TestGetDefaultConfig_FeedDefaults(t *testing.T) {
cfg := GetDefaultConfig()
if !cfg.Feeds.Pulse.Enabled {
t.Error("expected pulse feed enabled by default")
}
if cfg.Feeds.Pulse.IntervalSeconds != 30 {
t.Errorf("expected pulse interval=30, got %d", cfg.Feeds.Pulse.IntervalSeconds)
}
if !cfg.Feeds.Project.Enabled {
t.Error("expected project feed enabled by default")
}
Expand Down Expand Up @@ -1357,15 +1351,6 @@ func TestLoadConfig_FeedsConfig(t *testing.T) {
projectConfig := `
version: 1
feeds:
pulse:
enabled: true
interval_seconds: 15
thresholds:
green: 0
yellow: 15
orange: 30
red: 60
skull: 90
calendar:
enabled: true
lookahead_minutes: 60
Expand All @@ -1383,12 +1368,6 @@ feeds:
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.Feeds.Pulse.IntervalSeconds != 15 {
t.Errorf("expected pulse interval=15, got %d", cfg.Feeds.Pulse.IntervalSeconds)
}
if cfg.Feeds.Pulse.Thresholds.Skull != 90 {
t.Errorf("expected skull=90, got %d", cfg.Feeds.Pulse.Thresholds.Skull)
}
if !cfg.Feeds.Calendar.Enabled {
t.Error("expected calendar enabled")
}
Expand Down Expand Up @@ -1692,8 +1671,8 @@ version: 1
if cfg.Coaching.Communication.Tone != defaults.Coaching.Communication.Tone {
t.Errorf("expected default tone=%q, got %q", defaults.Coaching.Communication.Tone, cfg.Coaching.Communication.Tone)
}
if cfg.Feeds.Pulse.IntervalSeconds != defaults.Feeds.Pulse.IntervalSeconds {
t.Errorf("expected default pulse interval=%d, got %d", defaults.Feeds.Pulse.IntervalSeconds, cfg.Feeds.Pulse.IntervalSeconds)
if cfg.Feeds.Project.IntervalSeconds != defaults.Feeds.Project.IntervalSeconds {
t.Errorf("expected default project interval=%d, got %d", defaults.Feeds.Project.IntervalSeconds, cfg.Feeds.Project.IntervalSeconds)
}
if cfg.TUI.LaunchMethod != defaults.TUI.LaunchMethod {
t.Errorf("expected default launch method=%q, got %q", defaults.TUI.LaunchMethod, cfg.TUI.LaunchMethod)
Expand Down
22 changes: 0 additions & 22 deletions internal/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,6 @@ type FeedDefinition struct {
Enabled bool `json:"enabled"`
}

type PulseFeedConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"`
IntervalSeconds int `yaml:"interval_seconds" json:"intervalSeconds"`
Thresholds PulseThresholds `yaml:"thresholds" json:"thresholds"`
}

type PulseThresholds struct {
Green int `yaml:"green" json:"green"`
Yellow int `yaml:"yellow" json:"yellow"`
Orange int `yaml:"orange" json:"orange"`
Red int `yaml:"red" json:"red"`
Skull int `yaml:"skull" json:"skull"`
}

type ProjectFeedConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"`
IntervalSeconds int `yaml:"interval_seconds" json:"intervalSeconds"`
Expand Down Expand Up @@ -489,12 +475,6 @@ type InsightsFeedConfig struct {
UsageDataPath string `yaml:"usage_data_path" json:"usageDataPath"`
}

type PracticeFeedConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"`
IntervalSeconds int `yaml:"interval_seconds" json:"intervalSeconds"`
DBPath string `yaml:"db_path" json:"dbPath"`
}

type WeatherFeedConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"`
IntervalSeconds int `yaml:"interval_seconds" json:"intervalSeconds"`
Expand All @@ -510,12 +490,10 @@ type MemoriesFeedConfig struct {
}

type FeedsConfig struct {
Pulse PulseFeedConfig `yaml:"pulse" json:"pulse"`
Project ProjectFeedConfig `yaml:"project" json:"project"`
Calendar CalendarFeedConfig `yaml:"calendar" json:"calendar"`
News NewsFeedConfig `yaml:"news" json:"news"`
Insights InsightsFeedConfig `yaml:"insights" json:"insights"`
Practice PracticeFeedConfig `yaml:"practice" json:"practice"`
Weather WeatherFeedConfig `yaml:"weather" json:"weather"`
Memories MemoriesFeedConfig `yaml:"memories" json:"memories"`
Custom []CustomFeedConfig `yaml:"custom" json:"custom"`
Expand Down
39 changes: 1 addition & 38 deletions internal/feeds/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,6 @@ import (
// Each returns placeholder data; actual API/data-fetching logic is deferred.
// ---------------------------------------------------------------------------

// PulseProducer returns session count and recent activity summary.
type PulseProducer struct{}

func (p *PulseProducer) Name() string { return "pulse" }
func (p *PulseProducer) Produce(_ context.Context) (interface{}, error) {
return map[string]interface{}{
"type": "pulse",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"data": map[string]interface{}{
"session_count": 0,
"active_sessions": 0,
"recent_activity": "No recent activity (placeholder)",
"source": "placeholder",
},
}, nil
}

// ProjectProducer returns project directory info by running git commands.
// It implements ConfigAware to receive feed configuration.
type ProjectProducer struct {
Expand Down Expand Up @@ -735,24 +718,6 @@ func WeatherTestFixture() map[string]interface{} {
}
}

// PracticeProducer returns practice session summary.
type PracticeProducer struct{}

func (p *PracticeProducer) Name() string { return "practice" }
func (p *PracticeProducer) Produce(_ context.Context) (interface{}, error) {
return map[string]interface{}{
"type": "practice",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"data": map[string]interface{}{
"total_sessions": 0,
"streak_days": 0,
"last_practice_at": nil,
"focus_area": "none",
"source": "placeholder",
},
}, nil
}

// MemoriesProducer returns placeholder memories.
type MemoriesProducer struct{}

Expand Down Expand Up @@ -1188,14 +1153,12 @@ func InsightsTestFixture() map[string]interface{} {
}
}

// RegisterBuiltins registers all 8 built-in feed producers with the registry.
// RegisterBuiltins registers all 6 built-in feed producers with the registry.
func RegisterBuiltins(r *Registry) {
r.Register(&PulseProducer{})
r.Register(&ProjectProducer{})
r.Register(&NewsProducer{})
r.Register(&CalendarProducer{})
r.Register(&WeatherProducer{})
r.Register(&PracticeProducer{})
r.Register(&MemoriesProducer{})
r.Register(&InsightsProducer{})
}
20 changes: 9 additions & 11 deletions internal/feeds/feeds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,11 @@ func TestRegisterBuiltins_All8(t *testing.T) {
RegisterBuiltins(r)

all := r.All()
assert.Len(t, all, 8, "RegisterBuiltins should register exactly 8 producers")
assert.Len(t, all, 6, "RegisterBuiltins should register exactly 6 producers")

expectedNames := []string{
"pulse", "project", "news", "calendar",
"weather", "practice", "memories", "insights",
"project", "news", "calendar",
"weather", "memories", "insights",
}
sort.Strings(expectedNames)

Expand Down Expand Up @@ -503,11 +503,9 @@ func TestStopByPIDFile_NonExistentFile(t *testing.T) {
func TestDaemon_IntervalFor(t *testing.T) {
r := NewRegistry()
d := NewDaemon(core.DaemonConfig{}, core.FeedsConfig{
Pulse: core.PulseFeedConfig{IntervalSeconds: 30},
Weather: core.WeatherFeedConfig{IntervalSeconds: 900},
}, r)

assert.Equal(t, 30*time.Second, d.intervalFor("pulse"))
assert.Equal(t, 900*time.Second, d.intervalFor("weather"))
assert.Equal(t, defaultInterval, d.intervalFor("unknown-feed"))
assert.Equal(t, defaultInterval, d.intervalFor("project")) // 0 -> default
Expand All @@ -530,22 +528,22 @@ func TestDaemon_SkipsDisabledFeeds(t *testing.T) {
r := NewRegistry()

// Register two producers: one for an enabled feed, one for a disabled feed.
enabledProducer := newMockProducer("pulse", map[string]interface{}{"ok": true})
enabledProducer := newMockProducer("project", map[string]interface{}{"ok": true})
disabledProducer := newMockProducer("weather", map[string]interface{}{"temp": 72})
r.Register(enabledProducer)
r.Register(disabledProducer)

d, _ := newTestDaemon(t, r)
// Enable pulse, leave weather disabled (zero value = false).
d.feeds.Pulse.Enabled = true
// Enable project, leave weather disabled (zero value = false).
d.feeds.Project.Enabled = true
// Weather.Enabled is false by default (zero value).
d.SetStaggerOffset(0)

require.NoError(t, d.Start())
time.Sleep(300 * time.Millisecond)
require.NoError(t, d.Stop())

// Pulse (enabled) should have been called.
// Project (enabled) should have been called.
assert.GreaterOrEqual(t, enabledProducer.callCount.Load(), int64(1),
"enabled producer should have been called")

Expand All @@ -561,11 +559,11 @@ func TestDaemon_SkipsDisabledFeeds(t *testing.T) {
func TestDaemon_IsEnabled(t *testing.T) {
r := NewRegistry()
d := NewDaemon(core.DaemonConfig{}, core.FeedsConfig{
Pulse: core.PulseFeedConfig{Enabled: true},
Project: core.ProjectFeedConfig{Enabled: true},
Weather: core.WeatherFeedConfig{Enabled: false},
}, r)

assert.True(t, d.isEnabled("pulse"), "pulse should be enabled")
assert.True(t, d.isEnabled("project"), "project should be enabled")
assert.False(t, d.isEnabled("weather"), "weather should be disabled")
assert.True(t, d.isEnabled("unknown-custom-feed"), "unknown feeds default to enabled (fail-open)")
}
Expand Down
8 changes: 0 additions & 8 deletions internal/feeds/polling.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ func (d *Daemon) intervalFor(name string) time.Duration {
var seconds int

switch name {
case "pulse":
seconds = d.feeds.Pulse.IntervalSeconds
case "project":
seconds = d.feeds.Project.IntervalSeconds
case "calendar":
Expand All @@ -126,8 +124,6 @@ func (d *Daemon) intervalFor(name string) time.Duration {
seconds = d.feeds.News.IntervalSeconds
case "weather":
seconds = d.feeds.Weather.IntervalSeconds
case "practice":
seconds = d.feeds.Practice.IntervalSeconds
case "memories":
seconds = d.feeds.Memories.IntervalSeconds
case "insights":
Expand All @@ -145,8 +141,6 @@ func (d *Daemon) intervalFor(name string) time.Duration {
// unrecognised feeds are also considered enabled).
func (d *Daemon) isEnabled(name string) bool {
switch name {
case "pulse":
return d.feeds.Pulse.Enabled
case "project":
return d.feeds.Project.Enabled
case "calendar":
Expand All @@ -155,8 +149,6 @@ func (d *Daemon) isEnabled(name string) bool {
return d.feeds.News.Enabled
case "weather":
return d.feeds.Weather.Enabled
case "practice":
return d.feeds.Practice.Enabled
case "memories":
return d.feeds.Memories.Enabled
case "insights":
Expand Down