Skip to content

Commit e44d399

Browse files
fix(metrics): propagate --days and --project filters to all analyzers
- Pre-filter sessions by days before passing to AnalyzeEfficiency, AnalyzeCommits, AnalyzeConfidence, AnalyzeOutcomes, etc. Previously only AnalyzeVelocity applied the days window; all other sections silently reported all-time data regardless of --days. - Filter facets and agentTasks by session-ID sets derived from the filtered session slice, so Satisfaction, Friction Trends, and Agent Performance also respect --days and --project. - Export FilterSessionsByDays from the analyzer package. - Truncate tool names longer than 22 chars to prevent mid-name line wrapping in session inspect and metrics efficiency sections. - Add priority_label string to suggest --json output alongside the raw priority integer. - Update fix --ai default model from claude-sonnet-4-20250514 to claude-sonnet-4-6.
1 parent 861b0e1 commit e44d399

6 files changed

Lines changed: 85 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
All notable changes to claudewatch are documented here.
44

5+
## [Unreleased]
6+
7+
### Fixed
8+
9+
- **`claudewatch metrics --days` filter scope** — the `--days` window was applied only inside `AnalyzeVelocity` (Session Volume and Productivity sections) but not to the session slice passed to any other analyzer. Efficiency, Satisfaction, Token Usage, Commits, Confidence, Friction Trends, Cost per Outcome, and CLAUDE.md Effectiveness all silently reported all-time data regardless of `--days`. Fix: sessions are now pre-filtered by days immediately after the project filter, before any analyzer is called. `facets` and `agentTasks` are filtered to the same window via session-ID sets. Also fixes `--project` not filtering Agent Performance — `agentTasks` was loaded globally without session correlation.
10+
11+
- **Long MCP tool names wrap mid-name in session inspect and metrics efficiency**`StyleLabel` has a fixed `Width(24)`, so tool names longer than 24 characters (e.g. `mcp__commitmux__commitmux_search`) would wrap partway through the name onto the next line. Fix: names longer than 22 characters are now truncated to 22 + `..` before rendering, keeping output within the label column.
12+
13+
- **`suggest --json` missing human-readable priority label** — the JSON output serialized `Priority` as a raw integer (`2`, `3`, `4`) with no string equivalent, while the text output showed `[HIGH]`, `[MEDIUM]`, `[LOW]`. Fix: JSON output now includes a `priority_label` field (`"HIGH"`, `"MEDIUM"`, `"LOW"`, `"CRITICAL"`) alongside the integer.
14+
15+
- **`fix --ai` default model was stale** — default model was `claude-sonnet-4-20250514`; updated to `claude-sonnet-4-6`.
16+
517
## [0.7.3] - 2026-03-02
618

719
### Fixed

internal/analyzer/velocity.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
// AnalyzeVelocity computes productivity metrics from session data, filtered
1010
// to the last N days. If days is 0 or negative, all sessions are included.
1111
func AnalyzeVelocity(sessions []claude.SessionMeta, days int) VelocityMetrics {
12-
filtered := filterSessionsByDays(sessions, days)
12+
filtered := FilterSessionsByDays(sessions, days)
1313

1414
metrics := VelocityMetrics{
1515
TotalSessions: len(filtered),
@@ -39,9 +39,9 @@ func AnalyzeVelocity(sessions []claude.SessionMeta, days int) VelocityMetrics {
3939
return metrics
4040
}
4141

42-
// filterSessionsByDays returns sessions whose StartTime falls within the last
42+
// FilterSessionsByDays returns sessions whose StartTime falls within the last
4343
// N days. If days <= 0, all sessions are returned.
44-
func filterSessionsByDays(sessions []claude.SessionMeta, days int) []claude.SessionMeta {
44+
func FilterSessionsByDays(sessions []claude.SessionMeta, days int) []claude.SessionMeta {
4545
if days <= 0 {
4646
return sessions
4747
}

internal/app/fix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func init() {
4242
fixCmd.Flags().BoolVar(&fixFlagAll, "all", false, "Fix all projects with score < 50")
4343
fixCmd.Flags().BoolVar(&fixFlagJSON, "json", false, "Output proposed changes as JSON")
4444
fixCmd.Flags().BoolVar(&fixFlagAI, "ai", false, "Use Claude API for project-specific CLAUDE.md generation")
45-
fixCmd.Flags().StringVar(&fixFlagModel, "model", "claude-sonnet-4-20250514", "Claude model to use for AI generation")
45+
fixCmd.Flags().StringVar(&fixFlagModel, "model", "claude-sonnet-4-6", "Claude model to use for AI generation")
4646
rootCmd.AddCommand(fixCmd)
4747
}
4848

internal/app/metrics.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ func runMetrics(cmd *cobra.Command, args []string) error {
8888
sessions = filterSessionsByProject(sessions, metricsProject)
8989
}
9090

91+
// Filter by days — applied early so all downstream analyzers see the same window.
92+
sessions = analyzer.FilterSessionsByDays(sessions, metricsDays)
93+
9194
// Load facets.
9295
facets, err := claude.ParseAllFacets(cfg.ClaudeHome)
9396
if err != nil {
@@ -98,15 +101,22 @@ func runMetrics(cmd *cobra.Command, args []string) error {
98101
facets = scanner.FilterFacetsByProject(facets, sessions, metricsProject)
99102
}
100103

104+
// Filter facets to the same session window as the day-filtered sessions.
105+
facets = filterFacetsBySessionIDs(facets, sessions)
106+
101107
// Load agent tasks from session transcripts.
102108
agentTasks, err := claude.ParseAgentTasks(cfg.ClaudeHome)
103109
if err != nil {
104110
// Non-fatal if transcript parsing fails.
105111
agentTasks = nil
106112
}
107113

114+
// Filter agent tasks to the active session window.
115+
agentTasks = filterAgentTasksBySessionIDs(agentTasks, sessions)
116+
108117
// Run analyzers.
109-
velocity := analyzer.AnalyzeVelocity(sessions, metricsDays)
118+
// Sessions are pre-filtered by days above; pass 0 to skip the internal re-filter.
119+
velocity := analyzer.AnalyzeVelocity(sessions, 0)
110120
efficiency := analyzer.AnalyzeEfficiency(sessions)
111121
satisfaction := analyzer.AnalyzeSatisfaction(facets)
112122
agents := analyzer.AnalyzeAgents(agentTasks)
@@ -254,8 +264,12 @@ func renderEfficiency(e analyzer.EfficiencyMetrics) {
254264
limit = len(sorted)
255265
}
256266
for _, kv := range sorted[:limit] {
267+
name := kv.key
268+
if len(name) > 22 {
269+
name = name[:22] + ".."
270+
}
257271
fmt.Printf(" %s %s\n",
258-
output.StyleLabel.Render(kv.key),
272+
output.StyleLabel.Render(name),
259273
output.StyleValue.Render(fmt.Sprintf("%d", kv.value)))
260274
}
261275
}
@@ -796,6 +810,42 @@ func computeTokenUsage(sessions []claude.SessionMeta) tokenUsage {
796810
}
797811
}
798812

813+
// filterFacetsBySessionIDs keeps only facets whose SessionID is in the given sessions.
814+
func filterFacetsBySessionIDs(facets []claude.SessionFacet, sessions []claude.SessionMeta) []claude.SessionFacet {
815+
if len(sessions) == 0 {
816+
return nil
817+
}
818+
ids := make(map[string]struct{}, len(sessions))
819+
for _, s := range sessions {
820+
ids[s.SessionID] = struct{}{}
821+
}
822+
var filtered []claude.SessionFacet
823+
for _, f := range facets {
824+
if _, ok := ids[f.SessionID]; ok {
825+
filtered = append(filtered, f)
826+
}
827+
}
828+
return filtered
829+
}
830+
831+
// filterAgentTasksBySessionIDs keeps only agent tasks whose SessionID is in the given sessions.
832+
func filterAgentTasksBySessionIDs(tasks []claude.AgentTask, sessions []claude.SessionMeta) []claude.AgentTask {
833+
if len(sessions) == 0 {
834+
return nil
835+
}
836+
ids := make(map[string]struct{}, len(sessions))
837+
for _, s := range sessions {
838+
ids[s.SessionID] = struct{}{}
839+
}
840+
var filtered []claude.AgentTask
841+
for _, t := range tasks {
842+
if _, ok := ids[t.SessionID]; ok {
843+
filtered = append(filtered, t)
844+
}
845+
}
846+
return filtered
847+
}
848+
799849
// detectClaudeMDChanges finds projects with CLAUDE.md files and returns their
800850
// modification times as change events for effectiveness analysis.
801851
func detectClaudeMDChanges(projects []scanner.Project) []analyzer.ClaudeMDChange {

internal/app/sessions.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,11 @@ func renderInspect(r sessionRow) {
308308
limit = len(tools)
309309
}
310310
for _, t := range tools[:limit] {
311-
muted(t.name, fmt.Sprintf("%d", t.count))
311+
name := t.name
312+
if len(name) > 22 {
313+
name = name[:22] + ".."
314+
}
315+
muted(name, fmt.Sprintf("%d", t.count))
312316
}
313317
}
314318

internal/app/suggest.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,18 @@ func filterByProject(suggestions []suggest.Suggestion, project string) []suggest
314314
func outputSuggestJSON(suggestions []suggest.Suggestion) error {
315315
enc := json.NewEncoder(os.Stdout)
316316
enc.SetIndent("", " ")
317-
return enc.Encode(suggestions)
317+
type suggestionOut struct {
318+
suggest.Suggestion
319+
PriorityLabel string `json:"priority_label"`
320+
}
321+
out := make([]suggestionOut, len(suggestions))
322+
for i, s := range suggestions {
323+
label := priorityToLabel(s.Priority)
324+
// Strip brackets from the label for clean JSON (e.g. "HIGH" not "[HIGH]").
325+
label = label[1 : len(label)-1]
326+
out[i] = suggestionOut{s, label}
327+
}
328+
return enc.Encode(out)
318329
}
319330

320331
func renderSuggestions(suggestions []suggest.Suggestion) {

0 commit comments

Comments
 (0)