Title
zsh source provider: resolution, filtering, warnings
Summary
Wrap the zsh parser (09) in a source.Provider: resolve the history file
path, open it read-only, filter entries to the report range, apply
exclude_patterns, and emit Events and the documented warnings
(DESIGN.md §7.1).
Context
This provider is where the "simple-format history cannot be date-filtered"
reality becomes a clean user-facing behavior: zero events + a
zsh_no_timestamps warning that doctor (28) later explains.
Scope
internal/source/zsh/provider.go + tests.
Detailed Requirements
New(cfg config.SourcesZsh, resolve func() string) *Provider — resolve
is the already-config-aware path resolver from 05
(config.ZshHistoryFile()); provider stores the resolved path lazily at
Collect time (HISTFILE may differ between construction and run in tests).
ID() returns model.SourceZsh.
Collect:
- Missing file → warning
zsh_history_missing (message includes the
resolved path), zero events, nil error.
- Open
os.Open (read-only); never create/lock. Parse via
ParseReader with default caps.
Stats.Truncated → warning zsh_file_truncated.
Stats.Oversized > 0 || Stats.Malformed > 0 → warning
zsh_lines_skipped with both counts in the message (surfaces the
parser's skip-and-count so degradation is never silent).
- If
ExtendedCount == 0 && SimpleCount > 0 → warning
zsh_no_timestamps (message: entry count + one-line fix hint), return
zero events.
- Range filter: keep entries with
HasTimestamp && rng.Contains(Start).
exclude_patterns (pre-compiled at construction; compile errors were
already rejected by config validation): a match on the raw command
drops the entry silently (privacy feature — no per-entry warning, no
count leak; document in code comment).
- Event mapping per DESIGN §7.1:
Kind=KindCommand, Start, End = Start + Duration when Duration > 0, Project="", Title = first 200
runes of the command's first line (raw; sanitize/redact happen in the
18 pipeline stage), Meta = {"duration_s": <int>} when Duration > 0.
- Respect
ctx: check ctx.Err() at least every 1000 entries; on
cancellation return what was collected so far with nil error (collector
already adds the timeout warning).
- No sorting here (collector sorts globally).
Acceptance Criteria
Validation
go test -race -cover ./internal/source/zsh/ in PR.
Dependencies
08, 09.
Non-goals
doctor formatting of the fix hint (28), aggregation grouping (18),
redaction (16–18).
Design References
docs/DESIGN.md §7.1, §13
docs/research/zsh-history-format.md
Source of truth: docs/issues/10-zsh-provider.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 10 of 33).
Title
zsh source provider: resolution, filtering, warnings
Summary
Wrap the zsh parser (09) in a
source.Provider: resolve the history filepath, open it read-only, filter entries to the report range, apply
exclude_patterns, and emit Events and the documented warnings(DESIGN.md §7.1).
Context
This provider is where the "simple-format history cannot be date-filtered"
reality becomes a clean user-facing behavior: zero events + a
zsh_no_timestampswarning thatdoctor(28) later explains.Scope
internal/source/zsh/provider.go+ tests.Detailed Requirements
New(cfg config.SourcesZsh, resolve func() string) *Provider—resolveis the already-config-aware path resolver from 05
(
config.ZshHistoryFile()); provider stores the resolved path lazily atCollect time (HISTFILE may differ between construction and run in tests).
ID()returnsmodel.SourceZsh.Collect:zsh_history_missing(message includes theresolved path), zero events, nil error.
os.Open(read-only); never create/lock. Parse viaParseReaderwith default caps.Stats.Truncated→ warningzsh_file_truncated.Stats.Oversized > 0 || Stats.Malformed > 0→ warningzsh_lines_skippedwith both counts in the message (surfaces theparser's skip-and-count so degradation is never silent).
ExtendedCount == 0 && SimpleCount > 0→ warningzsh_no_timestamps(message: entry count + one-line fix hint), returnzero events.
HasTimestamp && rng.Contains(Start).exclude_patterns(pre-compiled at construction; compile errors werealready rejected by config validation): a match on the raw command
drops the entry silently (privacy feature — no per-entry warning, no
count leak; document in code comment).
Kind=KindCommand,Start,End = Start + Durationwhen Duration > 0,Project="",Title =first 200runes of the command's first line (raw; sanitize/redact happen in the
18 pipeline stage),
Meta = {"duration_s": <int>}when Duration > 0.ctx: checkctx.Err()at least every 1000 entries; oncancellation return what was collected so far with nil error (collector
already adds the timeout warning).
Acceptance Criteria
entries become Events with correct Start/End and duration Meta.
zsh_no_timestampswarning (and noothers).
zsh_history_missing, nil error.exclude_patterns=["^secretcmd"]drops matching entries; countdifference asserted; no warning emitted for drops.
zsh_lines_skippedwarning whose message contains both counts;clean fixture → no such warning.
(half-open contract).
in Title (assert with the multiline fixture).
before/after Collect (test helper asserts).
-raceclean; provider satisfiessource.Provider(compile-timevar _ source.Provider = (*Provider)(nil)).Validation
go test -race -cover ./internal/source/zsh/in PR.Dependencies
08, 09.
Non-goals
doctor formatting of the fix hint (28), aggregation grouping (18),
redaction (16–18).
Design References
docs/DESIGN.md§7.1, §13docs/research/zsh-history-format.mdSource of truth:
docs/issues/10-zsh-provider.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 10 of 33).