Phase 5a slice D. The MCP timeline tool no longer depends on
*web.Server — it talks to *aggregate.Aggregator directly. The wrong-way
mcp → web layering that necessitated the TimelineBuilder interface is
gone.
- mcp/tools.go: TimelineBuilder interface deleted.
RegisterProjaxTools(s, st, agg *aggregate.Aggregator) now takes the
aggregator directly; passing nil keeps the timeline tool unregistered
(kill-switch contract unchanged).
- mcp/tools.go: TimelineArgs moved from web/ to mcp/ since it is the
MCP-facing input shape. The timeline tool runs the full pipeline:
store.ListByFilters → in-mem timeline-exclude + has-link narrowing →
agg.All(...) → Result.ToTimelineRows() → aggregate.BuildTimelineDays
→ timelineView. No web/ import in the timeline path.
- internal/aggregate/rows.go: new Result.ToTimelineRows() helper that
projects the typed rows into the flat TimelineRow sum-type both
web/timeline.go and mcp/tools.go consume. Single source of truth for
the Date-anchor choice across kinds.
- internal/aggregate/timeline_days.go: FormatPERDate lifted from web/
so timeline-row builders outside web/ can render PER strings without
re-importing web/.
- web/timeline.go: BuildTimelinePayloadFromArgs + TimelineArgs deleted
(no remaining callers — slice D inlined the MCP path).
- cmd/projax/main.go: pass srv.Aggregator() into RegisterProjaxTools.
MCP tree-filter parity note: the move to store.ListByFilters narrows
status to a single value (first of args.Status) and AND-matches
management (vs the web TreeFilter's OR). m's documented MCP uses
(tag + default status) round-trip identically. Logged as a footnote in
docs/plans/aggregator-refactor.md.
All mcp + web + aggregate tests green.
Task: t-projax-5a-aggregator