Title
internal/model: Event, Report, Warning types and ordering
Summary
Implement the canonical data types every other package exchanges: Event
(one activity record), the Report tree, Warning, the enums, and the
deterministic sort/dedup helpers, exactly as specified in DESIGN.md §6.2–§6.3
and §13.
Context
Providers (issues 09–15) emit []Event; aggregation (18) consumes them and
produces Report; renderers (20–21) and the LLM prompt builder (24) consume
Report. Deterministic ordering here is what makes golden-file testing
possible everywhere downstream.
Scope
internal/model/event.go, report.go, warning.go, plus _test.go
files. No I/O, no dependencies outside stdlib.
Detailed Requirements
- Types exactly per DESIGN.md §6.2/§6.3:
type SourceID string with constants SourceZsh("zsh"),
SourceGit("git"), SourceClaudeCode("claude-code"),
SourceCodex("codex").
type EventKind string with KindCommand, KindCommit,
KindAgentSession (values "command", "commit", "agent-session").
Event{Source, Kind, Start, End, Project, Ref, Title, Body, Meta}.
Warning{Source SourceID, Code, Message string} — Code values are the
stable strings from DESIGN §13; define them as constants in this
package (e.g. WarnZshNoTimestamps = "zsh_no_timestamps"), one constant
per code listed there.
Report, ReportKind ("daily", "standup"), ProjectActivity,
AgentSession, Commit, ShellActivity, CommandGroup, Totals,
Narrative{Text, Model string}, GenerationMeta{Version string, Sources []SourceID, LLMModel string, RedactionMode string},
StandupData{YesterdayDate string, Yesterday []ProjectActivity, Today []ProjectActivity, YesterdayShell, TodayShell ShellActivity, YesterdayTotals, TodayTotals Totals}.
Commit{Repo, Hash, Subject string, When time.Time, Files, Insertions, Deletions int}; AgentSession{Agent, Title, Ref string, Start, End time.Time, UserMsgs, AgentMsgs, Tasks int}; CommandGroup{Head string, Count int, First, Last time.Time, Examples []string};
Totals{Commits, Insertions, Deletions, Sessions, AgentUserMsgs, Commands int, SpanStart, SpanEnd time.Time}.
- Ordering helpers (pure functions, stable):
SortEvents([]Event) — by Start, then Source, then Ref, then Title.
SortProjects([]ProjectActivity) — activity score desc
(3*len(Commits) + 2*len(Sessions)), then name asc — exactly the
DESIGN §6.3 rule (shell commands are unattributed in v1 and never
contribute to the score).
- Within
ProjectActivity: commits by When asc then Hash; sessions by
Start asc then Ref; command groups by Count desc then Head asc.
Event.Validate() error — non-zero Start, known Source/Kind, End zero or
≥ Start.
- Dedup helper
DedupSessions([]Event) []Event — for KindAgentSession
events with non-empty Ref, keep the first occurrence per (Source, Ref)
pair; events with empty Ref are never deduplicated (unrelated sessions
must not collapse); preserve order.
- No JSON tags on
Report yet except where needed by the LLM prompt
builder later — add lower_snake_case JSON tags to all Report-tree structs
now (they are also useful for tests).
Acceptance Criteria
Validation
go test -race -cover ./internal/model/ output pasted into the PR;
scripts/check_net_imports.sh still green.
Dependencies
01, 04 (Report.Range is a timeutil.Range; timeutil has no model
dependency, so no cycle).
Non-goals
Aggregation logic (18), warning emission (sources do that), rendering,
serialization formats beyond struct tags.
Design References
docs/DESIGN.md §6.2, §6.3, §9.3 (ordering), §13 (warning codes)
Source of truth: docs/issues/03-model-package.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 03 of 33).
Title
internal/model: Event, Report, Warning types and ordering
Summary
Implement the canonical data types every other package exchanges:
Event(one activity record), the
Reporttree,Warning, the enums, and thedeterministic sort/dedup helpers, exactly as specified in DESIGN.md §6.2–§6.3
and §13.
Context
Providers (issues 09–15) emit
[]Event; aggregation (18) consumes them andproduces
Report; renderers (20–21) and the LLM prompt builder (24) consumeReport. Deterministic ordering here is what makes golden-file testingpossible everywhere downstream.
Scope
internal/model/event.go,report.go,warning.go, plus_test.gofiles. No I/O, no dependencies outside stdlib.
Detailed Requirements
type SourceID stringwith constantsSourceZsh("zsh"),SourceGit("git"),SourceClaudeCode("claude-code"),SourceCodex("codex").type EventKind stringwithKindCommand,KindCommit,KindAgentSession(values"command","commit","agent-session").Event{Source, Kind, Start, End, Project, Ref, Title, Body, Meta}.Warning{Source SourceID, Code, Message string}—Codevalues are thestable strings from DESIGN §13; define them as constants in this
package (e.g.
WarnZshNoTimestamps = "zsh_no_timestamps"), one constantper code listed there.
Report,ReportKind("daily","standup"),ProjectActivity,AgentSession,Commit,ShellActivity,CommandGroup,Totals,Narrative{Text, Model string},GenerationMeta{Version string, Sources []SourceID, LLMModel string, RedactionMode string},StandupData{YesterdayDate string, Yesterday []ProjectActivity, Today []ProjectActivity, YesterdayShell, TodayShell ShellActivity, YesterdayTotals, TodayTotals Totals}.Commit{Repo, Hash, Subject string, When time.Time, Files, Insertions, Deletions int};AgentSession{Agent, Title, Ref string, Start, End time.Time, UserMsgs, AgentMsgs, Tasks int};CommandGroup{Head string, Count int, First, Last time.Time, Examples []string};Totals{Commits, Insertions, Deletions, Sessions, AgentUserMsgs, Commands int, SpanStart, SpanEnd time.Time}.SortEvents([]Event)— by Start, then Source, then Ref, then Title.SortProjects([]ProjectActivity)— activity score desc(
3*len(Commits) + 2*len(Sessions)), then name asc — exactly theDESIGN §6.3 rule (shell commands are unattributed in v1 and never
contribute to the score).
ProjectActivity: commits byWhenasc then Hash; sessions byStartasc then Ref; command groups by Count desc then Head asc.Event.Validate() error— non-zero Start, known Source/Kind, End zero or≥ Start.
DedupSessions([]Event) []Event— forKindAgentSessionevents with non-empty
Ref, keep the first occurrence per (Source, Ref)pair; events with empty
Refare never deduplicated (unrelated sessionsmust not collapse); preserve order.
Reportyet except where needed by the LLM promptbuilder later — add lower_snake_case JSON tags to all Report-tree structs
now (they are also useful for tests).
Acceptance Criteria
tests shuffle inputs and assert identical output).
DedupSessionscollapses two events with identical (Source, Ref) toone (the live-vs-archived Codex case) and never collapses events with
empty Ref (two empty-Ref sessions stay two).
Event.Validaterejects: zero Start, unknown Source, unknown Kind,End < Start— one test case each.Validation
go test -race -cover ./internal/model/output pasted into the PR;scripts/check_net_imports.shstill green.Dependencies
01, 04 (
Report.Rangeis atimeutil.Range; timeutil has no modeldependency, so no cycle).
Non-goals
Aggregation logic (18), warning emission (sources do that), rendering,
serialization formats beyond struct tags.
Design References
docs/DESIGN.md§6.2, §6.3, §9.3 (ordering), §13 (warning codes)Source of truth:
docs/issues/03-model-package.md(PR #1, branchdocs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies:docs/ISSUE_PLAN.md(this is issue 03 of 33).